diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 113be50..529ee4a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -39,7 +39,7 @@ jobs: run: | sudo apt-get update sudo apt-get install -y python3-sphinx python3-cryptography python3-cbor graphviz - sudo pip3 install recommonmark + sudo pip3 install recommonmark tomli - name: Update submodules id: update-submodules diff --git a/.gitignore b/.gitignore index 952b95e..5117af8 100644 --- a/.gitignore +++ b/.gitignore @@ -9,5 +9,8 @@ cscope.out docs/build attic/ wasp/boards/*/watch.py +wasp/boards/manifest_user_apps.py +wasp/apps/user +wasp/appregistry.py .idea .vscode diff --git a/Makefile b/Makefile index 2954ad3..897c48d 100644 --- a/Makefile +++ b/Makefile @@ -23,7 +23,10 @@ clean : reloader/build-$(BOARD) reloader/src/boards/$(BOARD)/bootloader.h \ micropython/mpy-cross/build \ micropython/ports/nrf/build-$(BOARD)-s132 \ - wasp/boards/$(BOARD)/watch.py + wasp/boards/$(BOARD)/watch.py \ + wasp/apps/user \ + wasp/boards/manifest_user_apps.py \ + wasp/appregistry.py # Avoid a recursive update... it grabs far too much submodules : @@ -61,7 +64,7 @@ wasp/boards/$(BOARD_SAFE)/watch.py : wasp/boards/$(BOARD_SAFE)/watch.py.in micropython/mpy-cross/mpy-cross: $(MAKE) -C micropython/mpy-cross -micropython: build-$(BOARD_SAFE) wasp/boards/$(BOARD_SAFE)/watch.py micropython/mpy-cross/mpy-cross +micropython: build-$(BOARD_SAFE) wasp/boards/manifest_user_apps.py wasp/boards/$(BOARD_SAFE)/watch.py micropython/mpy-cross/mpy-cross $(RM) micropython/ports/nrf/build-$(BOARD)-s132/frozen_content.c $(MAKE) -C micropython/ports/nrf \ BOARD=$(BOARD) SD=s132 \ @@ -73,6 +76,14 @@ micropython: build-$(BOARD_SAFE) wasp/boards/$(BOARD_SAFE)/watch.py micropython/ --application micropython/ports/nrf/build-$(BOARD)-s132/firmware.hex \ build-$(BOARD)/micropython.zip +wasp/boards/manifest_user_apps.py: wasp.toml + $(RM) -r \ + wasp/apps/user \ + wasp/boards/manifest_user_apps.py \ + wasp/appregistry.py + mkdir -p wasp/apps/user + $(PYTHON) tools/configure_wasp_apps.py wasp.toml + build-$(BOARD_SAFE): mkdir -p build-$(BOARD) @@ -98,12 +109,12 @@ APPS_MPY=$(APPS_PY:%.py=%.mpy) .PHONY: apps apps: $(APPS_MPY) -docs: +docs: wasp/boards/manifest_user_apps.py $(RM) -rf docs/build/html/* $(MAKE) -C docs html touch docs/build/html/.nojekyll -sim: +sim: wasp/boards/manifest_user_apps.py PYTHONDONTWRITEBYTECODE=1 PYTHONPATH=.:wasp/boards/simulator:wasp \ $(PYTHON) -i wasp/boards/simulator/main.py @@ -111,8 +122,8 @@ ifeq ("$(origin K)", "command line") PYTEST_RESTRICT = -k '$(K)' endif -check: - PYTHONDONTWRITEBYTECODE=1 PYTHONPATH=.:wasp/boards/simulator:wasp \ +check: wasp/boards/manifest_user_apps.py + PYTHONDONTWRITEBYTECODE=1 PYTHONPATH=.:wasp/boards/simulator:wasp:wasp/apps/system \ $(PYTEST) -v -W ignore $(PYTEST_RESTRICT) wasp/boards/simulator diff --git a/README.rst b/README.rst index 15104c8..809641c 100644 --- a/README.rst +++ b/README.rst @@ -124,6 +124,12 @@ Videos - +Custom builds +------------- + +Wasp-os is designed to allow users to easily create their own custom builds. Simply modify the wasp.toml file +to include your favorite apps and watch faces. See the docs for more information on how to build wasp-os. + Screenshots ----------- @@ -134,138 +140,149 @@ PineTime: :alt: wasp-os digital clock app running on PineTime :width: 233 -Screenshots of the built in applications running on the wasp-os +Screenshots of the available applications running on the wasp-os simulator: .. image:: res/Bootloader.png :alt: Bootloader splash screen overlaid on the simulator watch art :width: 179 -.. image:: res/ClockApp.png +Watch faces: + +.. image:: res/screenshots/ClockApp.png :alt: Digital clock application running on the wasp-os simulator :width: 179 -.. image:: res/DemoApp.png - :alt: Simple always-on demo for showing off wasp-os at conferences and shows +.. image:: res/screenshots/WeekClockApp.png + :alt: Digital clock application with week day running on the wasp-os simulator :width: 179 -.. image:: res/DisaBLEApp.png - :alt: Small application for disabling bluetooth to save power and enhance security - :width: 179 - -.. image:: res/GalleryApp.png - :alt: Gallery application running on the wasp-os simulator - :width: 179 - -.. image:: res/HeartApp.png - :alt: Heart rate application running on the wasp-os simulator - :width: 179 - -.. image:: res/MorseApp.png - :alt: Morse translator/notepad application running on the wasp-os simulator - :width: 179 - -.. image:: res/SportsApp.png - :alt: Sports applications, a combined stopwatch and step counter - :width: 179 - -.. image:: res/StopclockApp.png - :alt: Stop watch application running on the wasp-os simulator - :width: 179 - -.. image:: res/StepsApp.png - :alt: Step counter application running on the wasp-os simulator - :width: 179 - -.. image:: res/LauncherApp.png - :alt: Application launcher running on the wasp-os simulator - :width: 179 - -.. image:: res/SettingsApp.png - :alt: Settings application running on the wasp-os simulator - :width: 179 - -.. image:: res/SoftwareApp.png - :alt: Software selection app running on the wasp-os simulator - :width: 179 - - -wasp-os also contains a library of additional applications for you to choose. -These are disabled by default but can be easily enabled using the Software -application (and the "blank" white screen is a torch application): - -.. image:: res/SelfTestApp.png - :alt: Self test application running a rendering benchmark on the simulator - :width: 179 - -.. image:: res/TorchApp.png - :alt: Torch application running on the wasp-os simulator - :width: 179 - -.. image:: res/ChronoApp.png +.. image:: res/screenshots/ChronoApp.png :alt: Analogue clock application running in the wasp-os simulator :width: 179 -.. image:: res/DualApp.png +.. image:: res/screenshots/DualClockApp.png :alt: An other clock application running in the wasp-os simulator :width: 179 -.. image:: res/FiboApp.png +.. image:: res/screenshots/FibonacciClockApp.png :alt: Fibonacci clock application running in the wasp-os simulator :width: 179 -.. image:: res/HaikuApp.png - :alt: Haiku application running in the wasp-os simulator +.. image:: res/screenshots/WordClockApp.png + :alt: Shows a time as words in the wasp-os simulator :width: 179 -.. image:: res/LifeApp.png - :alt: Game of Life running in the wasp-os simulator - :width: 179 +Games: -.. image:: res/AlarmApp.png - :alt: Alarm clock application running in the wasp-os simulator - :width: 179 - -.. image:: res/MusicApp.png - :alt: Music Player running in the wasp-os simulator - :width: 179 - -.. image:: res/CalcApp.png - :alt: Calculator running in the wasp-os simulator - :width: 179 - -.. image:: res/2048App.png +.. image:: res/screenshots/Play2048App.png :alt: Let's play the 2048 game (in the wasp-os simulator) :width: 179 -.. image:: res/SnakeApp.png +.. image:: res/screenshots/GameOfLifeApp.png + :alt: Game of Life running in the wasp-os simulator + :width: 179 + +.. image:: res/screenshots/SnakeApp.png :alt: Snake Game running in the wasp-os simulator :width: 179 -.. image:: res/TimerApp.png +Time management apps: + +.. image:: res/screenshots/AlarmApp.png + :alt: Alarm clock application running in the wasp-os simulator + :width: 179 + +.. image:: res/screenshots/StopwatchApp.png + :alt: Stop watch application running on the wasp-os simulator + :width: 179 + +.. image:: res/screenshots/TimerApp.png :alt: Countdown timer application running in the wasp-os simulator :width: 179 -.. image:: res/WeatherApp.png - :alt: Weather application running in the wasp-os simulator +System apps: + +.. image:: res/screenshots/DisaBLEApp.png + :alt: Small application for disabling bluetooth to save power and enhance security :width: 179 -.. image:: res/WeekClkApp.png - :alt: Digital clock application, including the week day +.. image:: res/screenshots/LauncherApp.png + :alt: Application launcher running on the wasp-os simulator :width: 179 -.. image:: res/WordClkApp.png - :alt: Shows a time as words in the wasp-os simulator +.. image:: res/screenshots/SettingsApp.png + :alt: Settings application running on the wasp-os simulator :width: 179 -.. image:: res/LevelApp.png - :alt: Shows a time as words in the wasp-os simulator +.. image:: res/screenshots/SoftwareApp.png + :alt: Software selection app running on the wasp-os simulator :width: 179 -.. image:: res/BeaconApp.png +.. image:: res/screenshots/FacesApp.png + :alt: Switch watch faces + :width: 179 + + +Other apps: (The "blank" white screenshot is a flashlight app) + +.. image:: res/screenshots/BeaconApp.png :alt: Flash the relatively powerful HRS LED repeatedly :width: 179 -.. image:: res/FinderApp.png +.. image:: res/screenshots/CalculatorApp.png + :alt: Calculator running in the wasp-os simulator + :width: 179 + +.. image:: res/screenshots/DemoApp.png + :alt: Simple always-on demo for showing off wasp-os at conferences and shows + :width: 179 + +.. image:: res/screenshots/FlashlightApp.png + :alt: Torch application running on the wasp-os simulator + :width: 179 + +.. image:: res/screenshots/GalleryApp.png + :alt: Gallery application running on the wasp-os simulator + :width: 179 + +.. image:: res/screenshots/HeartApp.png + :alt: Heart rate application running on the wasp-os simulator + :width: 179 + +.. image:: res/screenshots/HaikuApp.png + :alt: Haiku application running in the wasp-os simulator + :width: 179 + +.. image:: res/screenshots/LevelApp.png + :alt: Shows a time as words in the wasp-os simulator + :width: 179 + +.. image:: res/screenshots/MorseApp.png + :alt: Morse translator/notepad application running on the wasp-os simulator + :width: 179 + +.. image:: res/screenshots/PhoneFinderApp.png :alt: Find your phone by causing it to ring :width: 179 + +.. image:: res/screenshots/SportsApp.png + :alt: Sports applications, a combined stopwatch and step counter + :width: 179 + +.. image:: res/screenshots/StepCounterApp.png + :alt: Step counter application running on the wasp-os simulator + :width: 179 + +.. image:: res/screenshots/TestApp.png + :alt: Self test application running a rendering benchmark on the simulator + :width: 179 + +.. image:: res/screenshots/MusicPlayerApp.png + :alt: Music Player running in the wasp-os simulator + :width: 179 + +.. image:: res/screenshots/WeatherApp.png + :alt: Weather application running in the wasp-os simulator + :width: 179 + diff --git a/wasp/apps/alarm.py b/apps/alarm.py similarity index 99% rename from wasp/apps/alarm.py rename to apps/alarm.py index 843de2d..08a905d 100644 --- a/wasp/apps/alarm.py +++ b/apps/alarm.py @@ -8,7 +8,7 @@ An application to set a vibration alarm. All settings can be accessed from the Watch UI. Press the button to turn off ringing alarms. - .. figure:: res/AlarmApp.png + .. figure:: res/screenshots/AlarmApp.png :width: 179 Screenshot of the Alarm Application diff --git a/apps/Beacon.py b/apps/beacon.py similarity index 98% rename from apps/Beacon.py rename to apps/beacon.py index d8423cf..7141734 100644 --- a/apps/Beacon.py +++ b/apps/beacon.py @@ -11,7 +11,7 @@ The blinking is handled by the HRS, so this app consumes very little power. With BLE and/or step counter disabled and blinking frequency set to the minimum, the watch's battery will last for many days. -.. figure:: res/BeaconApp.png +.. figure:: res/screenshots/BeaconApp.png :width: 179 """ diff --git a/wasp/apps/calc.py b/apps/calculator.py similarity index 98% rename from wasp/apps/calc.py rename to apps/calculator.py index a3dbeec..25cef6f 100644 --- a/wasp/apps/calc.py +++ b/apps/calculator.py @@ -6,7 +6,7 @@ This is a simple calculator app that uses the build-in eval() function to compute the solution. -.. figure:: res/CalcApp.png +.. figure:: res/screenshots/CalculatorApp.png :width: 179 """ diff --git a/wasp/apps/demo.py b/apps/demo.py similarity index 99% rename from wasp/apps/demo.py rename to apps/demo.py index b49655c..7a9fe2d 100644 --- a/wasp/apps/demo.py +++ b/apps/demo.py @@ -9,7 +9,7 @@ MicroPython logos. It cycles through a variety of colours and swaps between the logos every 5 images (so if you change anything make sure len(colors) is not a multiple of 5). -.. figure:: res/DemoApp.png +.. figure:: res/screenshots/DemoApp.png :width: 179 The demo also includes code to keep the devie awake making it diff --git a/wasp/apps/disaBLE.py b/apps/disa_b_l_e.py similarity index 97% rename from wasp/apps/disaBLE.py rename to apps/disa_b_l_e.py index 977f0ff..d40a2ef 100644 --- a/wasp/apps/disaBLE.py +++ b/apps/disa_b_l_e.py @@ -10,7 +10,7 @@ This app shows the bluetooth status and provides a button to disable/enable it. Unfortunately, re-enabling bluetooth normally has some issues, so as a workaround the "enable" button restarts the watch. -.. figure:: res/DisaBLEApp.png +.. figure:: res/screenshots/DisaBLEApp.png :width: 179 """ diff --git a/wasp/apps/faces.py b/apps/faces.py similarity index 81% rename from wasp/apps/faces.py rename to apps/faces.py index 1079417..90356c4 100644 --- a/wasp/apps/faces.py +++ b/apps/faces.py @@ -5,7 +5,7 @@ A tool to select a suitable watch face. -.. figure:: res/FacesApp.png +.. figure:: res/screenshots/FacesApp.png :width: 179 The app is intended to be enabled by default and has, therefore, been carefully @@ -14,6 +14,7 @@ structured to minimize memory usage when the app is not active. import wasp import icons +import appregistry class FacesApp(): """Choose a default watch face.""" @@ -23,12 +24,8 @@ class FacesApp(): def foreground(self): """Activate the application.""" choices = [] - choices.append(('clock', 'Clock')) - choices.append(('week_clock', 'WeekClock')) - choices.append(('chrono', 'Chrono')) - choices.append(('dual_clock', 'DualClock')) - choices.append(('fibonacci_clock', 'FibonacciClock')) - choices.append(('word_clock', 'WordClock')) + for face in appregistry.faces_list: + choices.append(face) self.choices = choices self.choice = 0 @@ -68,6 +65,6 @@ class FacesApp(): """Draw the display from scratch.""" wasp.watch.drawable.fill() (module, label) = self.choices[self.choice] - wasp.system.register('apps.{}.{}App'.format(module, label), watch_face=True) + wasp.system.register('{}.{}App'.format(module, label), watch_face=True) wasp.system.quick_ring[0].preview() self.si.draw() diff --git a/wasp/apps/flashlight.py b/apps/flashlight.py similarity index 94% rename from wasp/apps/flashlight.py rename to apps/flashlight.py index 3de7c21..3bf935c 100644 --- a/wasp/apps/flashlight.py +++ b/apps/flashlight.py @@ -6,7 +6,7 @@ Shows a bright screen that you can tap to change brightness or switch to redlight. -.. figure:: res/TorchApp.png +.. figure:: res/screenshots/FlashlightApp.png :width: 179 """ @@ -14,7 +14,7 @@ import wasp import icons -class TorchApp(object): +class FlashlightApp(object): """Trivial flashlight application.""" NAME = 'Torch' ICON = icons.torch diff --git a/wasp/apps/gallery.py b/apps/gallery.py similarity index 98% rename from wasp/apps/gallery.py rename to apps/gallery.py index e1e0c9d..045398d 100644 --- a/wasp/apps/gallery.py +++ b/apps/gallery.py @@ -6,7 +6,7 @@ An application that shows images stored in the filesystem. -.. figure:: res/GalleryApp.png +.. figure:: res/screenshots/GalleryApp.png :width: 179 The images have to be uploaded in the "gallery" directory. @@ -27,7 +27,7 @@ And to upload: import wasp import icons -from apps.pager import PagerApp +from apps.system.pager import PagerApp class GalleryApp(): NAME = 'Gallery' diff --git a/apps/GameOfLife.py b/apps/game_of_life.py similarity index 99% rename from apps/GameOfLife.py rename to apps/game_of_life.py index 7e2b518..08b2108 100644 --- a/apps/GameOfLife.py +++ b/apps/game_of_life.py @@ -6,7 +6,7 @@ The Game of Life is a "no player game" played on a two dimensional grid where the rules interact to make interesting patterns. - .. figure:: res/LifeApp.png + .. figure:: res/screenshots/GameOfLifeApp.png :width: 179 Screenshot of the Game of Life application diff --git a/wasp/apps/haiku.py b/apps/haiku.py similarity index 95% rename from wasp/apps/haiku.py rename to apps/haiku.py index 2e8c8a6..0be3e3e 100644 --- a/wasp/apps/haiku.py +++ b/apps/haiku.py @@ -6,7 +6,7 @@ These three lines poems are fun to write and fit nicely on a tiny screen. -.. figure:: res/HaikuApp.png +.. figure:: res/screenshots/HaikuApp.png :width: 179 If there is a file called haiku.txt in the flash filesystem then this app @@ -22,7 +22,7 @@ import icons import io import sys -from apps.pager import PagerApp +from apps.system.pager import PagerApp class HaikuApp(PagerApp): NAME = 'Haiku' diff --git a/wasp/apps/heart.py b/apps/heart.py similarity index 98% rename from wasp/apps/heart.py rename to apps/heart.py index 66752d7..47e2b71 100644 --- a/wasp/apps/heart.py +++ b/apps/heart.py @@ -6,7 +6,7 @@ A graphing heart rate monitor using a PPG sensor. -.. figure:: res/HeartApp.png +.. figure:: res/screenshots/HeartApp.png :width: 179 This program also implements some (entirely optional) debug features to diff --git a/wasp/apps/hello.py b/apps/hello.py similarity index 100% rename from wasp/apps/hello.py rename to apps/hello.py diff --git a/apps/Level.py b/apps/level.py similarity index 99% rename from apps/Level.py rename to apps/level.py index 302d21d..1d93bc7 100644 --- a/apps/Level.py +++ b/apps/level.py @@ -8,7 +8,7 @@ A tap opens a menu with the option to calibrate or reset the level. To calibrate, place the watch on a flat surface, then tap the "Calibrate" button while ensuring the watch is stationary. -.. figure:: res/LevelApp.png +.. figure:: res/screenshots/LevelApp.png :width: 179 """ diff --git a/apps/Morse.py b/apps/morse.py similarity index 99% rename from apps/Morse.py rename to apps/morse.py index beb8497..aa27524 100644 --- a/apps/Morse.py +++ b/apps/morse.py @@ -13,7 +13,7 @@ lines will be deleted. There is a preview of the next letter at the bottom of the screen. -.. figure:: res/MorseApp.png +.. figure:: res/screenshots/MorseApp.png :width: 179 """ diff --git a/wasp/apps/musicplayer.py b/apps/music_player.py similarity index 99% rename from wasp/apps/musicplayer.py rename to apps/music_player.py index a078c74..d5b1517 100644 --- a/wasp/apps/musicplayer.py +++ b/apps/music_player.py @@ -5,7 +5,7 @@ """Music Player for GadgetBridge ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - .. figure:: res/MusicApp.png + .. figure:: res/screenshots/MusicPlayerApp.png :width: 179 Screenshot of the Music Player application diff --git a/apps/PhoneFinder.py b/apps/phone_finder.py similarity index 97% rename from apps/PhoneFinder.py rename to apps/phone_finder.py index f53aaa7..663bf59 100644 --- a/apps/PhoneFinder.py +++ b/apps/phone_finder.py @@ -5,7 +5,7 @@ An application to find a phone connected via Gadgetbridge. - .. figure:: res/FinderApp.png + .. figure:: res/screenshots/PhoneFinderApp.png :width: 179 Screenshot of the Phone Finder Application diff --git a/wasp/apps/play2048.py b/apps/play2048.py similarity index 99% rename from wasp/apps/play2048.py rename to apps/play2048.py index 6db2685..f20fc08 100644 --- a/wasp/apps/play2048.py +++ b/apps/play2048.py @@ -6,7 +6,7 @@ A popular sliding block puzzle game in which tiles are combined to make the number 2048. - .. figure:: res/2048App.png + .. figure:: res/screenshots/Play2048App.png :width: 179 Screenshot of the 2048 game application diff --git a/apps/ReadMe.py b/apps/read_me.py similarity index 88% rename from apps/ReadMe.py rename to apps/read_me.py index bf6962c..349962c 100644 --- a/apps/ReadMe.py +++ b/apps/read_me.py @@ -15,4 +15,4 @@ class ReadMeApp(): draw = wasp.watch.drawable draw.fill() draw.string('Autoloaded from', 0, 96, width=240) - draw.string('apps/ReadMe.py', 0, 96+32, width=240) + draw.string('apps/read_me.py', 0, 96+32, width=240) diff --git a/wasp/apps/snake.py b/apps/snake.py similarity index 99% rename from wasp/apps/snake.py rename to apps/snake.py index fc00138..500662b 100644 --- a/wasp/apps/snake.py +++ b/apps/snake.py @@ -6,7 +6,7 @@ This is a classic arcade game called snake. -.. figure:: res/SnakeApp.png +.. figure:: res/screenshots/SnakeApp.png :width: 179 Screenshot of the snake game @@ -46,7 +46,7 @@ snake_icon = ( import wasp, time from random import randint -class SnakeGameApp(): +class SnakeApp(): NAME = 'Snake' ICON = snake_icon diff --git a/wasp/apps/sports.py b/apps/sports.py similarity index 98% rename from wasp/apps/sports.py rename to apps/sports.py index e966855..a96e60e 100644 --- a/wasp/apps/sports.py +++ b/apps/sports.py @@ -6,7 +6,7 @@ A combined stopwatch and step counter. -.. figure:: res/SportsApp.png +.. figure:: res/screenshots/SportsApp.png :width: 179 """ import wasp diff --git a/wasp/apps/stopwatch.py b/apps/stopwatch.py similarity index 98% rename from wasp/apps/stopwatch.py rename to apps/stopwatch.py index 48fe461..3abb317 100644 --- a/wasp/apps/stopwatch.py +++ b/apps/stopwatch.py @@ -6,7 +6,7 @@ Simple stop/start watch with support for split times. -.. figure:: res/StopclockApp.png +.. figure:: res/screenshots/StopwatchApp.png :width: 179 """ import wasp diff --git a/wasp/apps/template.py b/apps/template.py similarity index 100% rename from wasp/apps/template.py rename to apps/template.py diff --git a/wasp/apps/testapp.py b/apps/test.py similarity index 99% rename from wasp/apps/testapp.py rename to apps/test.py index f4a9a0e..3b1c37a 100644 --- a/wasp/apps/testapp.py +++ b/apps/test.py @@ -7,7 +7,7 @@ A collection of tests used to develop features or provide useful metrics such as performance indicators or memory usage. -.. figure:: res/SelfTestApp.png +.. figure:: res/screenshots/TestApp.png :width: 179 """ @@ -18,7 +18,7 @@ import fonts import icons import machine -from apps.pager import PagerApp +from apps.system.pager import PagerApp class TestApp(): """Self test application.""" diff --git a/wasp/apps/timer.py b/apps/timer.py similarity index 99% rename from wasp/apps/timer.py rename to apps/timer.py index 648d45c..cab9ec6 100644 --- a/wasp/apps/timer.py +++ b/apps/timer.py @@ -5,7 +5,7 @@ An application to set a vibration in a specified amount of time. Like a kitchen timer. - .. figure:: res/TimerApp.png + .. figure:: res/screenshots/TimerApp.png :width: 179 Screenshot of the Timer Application diff --git a/wasp/apps/weather.py b/apps/weather.py similarity index 99% rename from wasp/apps/weather.py rename to apps/weather.py index 15e8758..f2eedfd 100644 --- a/wasp/apps/weather.py +++ b/apps/weather.py @@ -5,7 +5,7 @@ """Weather for GadgetBridge and wasp-os companion ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - .. figure:: res/WeatherApp.png + .. figure:: res/screenshots/WeatherApp.png :width: 179 Screenshot of the Weather application diff --git a/docs/appguide.rst b/docs/appguide.rst index 8a3fe18..e9187bd 100644 --- a/docs/appguide.rst +++ b/docs/appguide.rst @@ -203,6 +203,31 @@ a little time into learning the best practices when running .. _MicroPython: https://micropython.org/ __ http://docs.micropython.org/en/latest/reference/constrained.html +App naming conventions and placement +------------------------------------ + +Your app must be named in a specific way and placed in the /apps directory to be compatible with wasp-os. +Watch faces follow the same rules but are placed in the /watch_faces directory. + +1. The name of the python file must be in snake case (ie: music_player.py) +2. The class of your app must be the name of the file in pascal case with "App" appended (ie: MusicPlayerApp) +3. The NAME variable in your app must short and will be used on the launcher screen (ie: NAME = 'Music') + +If you wish to submit your app to the project it must additionally meet these requirements: + +1. The app must be added to docs/apps.rst +2. The app must be added to the README.rst +3. A simulator screenshot must exist in the /res/screenshots directory having the name of the app class (ie: MusicPlayerApp.png). Press s in the simulator to take a screenshot. +4. The app must include a README comment at the top of the file (see existing apps) +5. The app README must include a link to the simulator screenshot in the /res/screenshots directory +6. If your app has an icon (encouraged) than the image used to generate the RLE must be in the /res/icons directory. Its name should be the snake case name of the app file with "_icon" appended. (ie: music_player_icon.png) + +To check if your app meets these requirements you can run the following command: + +.. code-block:: sh + + make check + How to run your application --------------------------- @@ -391,12 +416,7 @@ because they can execute directly from the internal FLASH rather than running from RAM. Additionally the code is pre-compiled, which also means we don't need any RAM budget to run the compiler. -Freezing your application requires you to modify the ``manifest.py`` -file for your board (e.g. ``wasp/boards/pinetime/manifest.py``) to include -your application and then the whole binary must be re-compiled as normal. - -After that you an use the same technique described in the previous -section to add an import and register for you application from ``main.py`` +To freeze your app into the wasp-os binary add it to the wasp.toml file. .. note:: @@ -442,25 +462,13 @@ To delete a file from the device: >>>os.remove("apps/myapp.mpy") >>>del os -Application naming conventions -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -You app will have two names. A short name that will be displayed on the home -screen and a long name to be displayed in the software list. Your files must be -named according to the following rules: - -1) The class must be named the long name of the python file plus "App" (ie: "eggtimer.py" and "class EggTimerApp") -2) Within your class the variable "NAME" must be set to the short name (ie: NAME= 'Timer' ) -3) Your app's documentation screenshot must be stored as the short name plus "App" (ie: res/TimerApp.png) -4) The png used to generate your icon should be stored as the long name plus icon (ie: res/egg_timer_icon.png) - Application entry points ------------------------ Applications provide entry points for the system manager to use to notify the application of a change in system state or an user interface event. -.. automodule:: apps.template +.. automodule:: template :members: :private-members: :special-members: diff --git a/docs/apps.rst b/docs/apps.rst index 147c602..ac75070 100644 --- a/docs/apps.rst +++ b/docs/apps.rst @@ -9,77 +9,77 @@ Application Library Watch faces ----------- -.. automodule:: apps.faces +.. automodule:: faces -.. automodule:: apps.clock +.. automodule:: clock -.. automodule:: apps.chrono +.. automodule:: chrono -.. automodule:: apps.dual_clock +.. automodule:: dual_clock -.. automodule:: apps.fibonacci_clock +.. automodule:: fibonacci_clock -.. automodule:: apps.week_clock +.. automodule:: week_clock -.. automodule:: apps.word_clock +.. automodule:: word_clock Built-in -------- -.. automodule:: apps.heart +.. automodule:: apps.system.step_counter -.. automodule:: apps.stopwatch +.. automodule:: apps.system.launcher -.. automodule:: apps.steps +.. automodule:: apps.system.settings -.. automodule:: apps.launcher +.. automodule:: apps.system.software -.. automodule:: apps.settings - -.. automodule:: apps.software - -.. automodule:: apps.pager +.. automodule:: apps.system.pager Applications ------------ -.. automodule:: apps.alarm +.. automodule:: alarm -.. automodule:: Beacon +.. automodule:: beacon -.. automodule:: apps.calc +.. automodule:: calculator -.. automodule:: apps.demo +.. automodule:: demo -.. automodule:: apps.disaBLE +.. automodule:: disa_b_l_e -.. automodule:: apps.flashlight +.. automodule:: flashlight -.. automodule:: PhoneFinder +.. automodule:: gallery -.. automodule:: apps.gallery +.. automodule:: haiku -.. automodule:: apps.haiku +.. automodule:: heart -.. automodule:: Level +.. automodule:: level -.. automodule:: Morse +.. automodule:: morse -.. automodule:: apps.musicplayer +.. automodule:: music_player -.. automodule:: apps.sports +.. automodule:: phone_finder -.. automodule:: apps.testapp +.. automodule:: sports -.. automodule:: apps.timer +.. automodule:: stopwatch -.. automodule:: apps.weather +.. automodule:: test + +.. automodule:: timer + +.. automodule:: weather Games ----- -.. automodule:: GameOfLife +.. automodule:: game_of_life -.. automodule:: apps.play2048 +.. automodule:: play2048 -.. automodule:: apps.snake +.. automodule:: snake diff --git a/docs/conf.py b/docs/conf.py index 25edb59..9cf4f8f 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -14,6 +14,7 @@ import os import sys sys.path.insert(0, os.path.abspath('../wasp')) sys.path.insert(0, os.path.abspath('../apps')) +sys.path.insert(0, os.path.abspath('../watch_faces')) sys.path.insert(0, os.path.abspath('../wasp/boards/sphinx')) diff --git a/docs/hello.py b/docs/hello.py index dd0c2a1..9f00358 120000 --- a/docs/hello.py +++ b/docs/hello.py @@ -1 +1 @@ -../wasp/apps/hello.py \ No newline at end of file +../apps/hello.py \ No newline at end of file diff --git a/docs/install.rst b/docs/install.rst index d63a047..4f1485a 100644 --- a/docs/install.rst +++ b/docs/install.rst @@ -25,7 +25,7 @@ following commands: wget git build-essential libsdl2-2.0-0 python3-click python3-gi \ python3-numpy python3-pexpect python3-pil python3-pip python3-pydbus \ python3-serial unzip - pip3 install --user cbor pysdl2 + pip3 install --user cbor pysdl2 tomli Additionally if you wish to regenerate the documentation you will require a complete sphinx toolchain: @@ -114,6 +114,17 @@ To rebuild the documentation: The docs will be browsable in ``docs/build/html`` as per Sphinx standards. +Custom builds +------------- + +Wasp-os can be configured to include a custom selection of apps and watch faces using the wasp.toml file. +There are many more apps available than can fit on a device. Choose your favorites and roll your own flavor of wasp. +Apps that are configured as quick_ring will be automatically added to the wasp quick ring (swipe left and right from +the watch face). If an app is configured with auto load it will load into memory at startup and any apps that +are not auto loaded can be enabled using the software app. Add as many watch faces as you like and switch +between them using the faces app. + + Binary downloads ---------------- diff --git a/res/2048_icon.png b/res/icons/2048_icon.png similarity index 100% rename from res/2048_icon.png rename to res/icons/2048_icon.png diff --git a/res/alarm_icon.png b/res/icons/alarm_icon.png similarity index 100% rename from res/alarm_icon.png rename to res/icons/alarm_icon.png diff --git a/res/beacon_icon.png b/res/icons/beacon_icon.png similarity index 100% rename from res/beacon_icon.png rename to res/icons/beacon_icon.png diff --git a/res/calc.png b/res/icons/calculator_icon.png similarity index 100% rename from res/calc.png rename to res/icons/calculator_icon.png diff --git a/res/demo_icon.png b/res/icons/demo_icon.png similarity index 100% rename from res/demo_icon.png rename to res/icons/demo_icon.png diff --git a/res/disaBLE_icon.png b/res/icons/disaBLE_icon.png similarity index 100% rename from res/disaBLE_icon.png rename to res/icons/disaBLE_icon.png diff --git a/res/clock_icon.png b/res/icons/faces_icon.png similarity index 100% rename from res/clock_icon.png rename to res/icons/faces_icon.png diff --git a/res/fibo_icon.png b/res/icons/fibonacci_clock_icon.png similarity index 100% rename from res/fibo_icon.png rename to res/icons/fibonacci_clock_icon.png diff --git a/res/torch_icon.png b/res/icons/flashlight_icon.png similarity index 100% rename from res/torch_icon.png rename to res/icons/flashlight_icon.png diff --git a/res/gallery_icon.png b/res/icons/gallery_icon.png similarity index 100% rename from res/gallery_icon.png rename to res/icons/gallery_icon.png diff --git a/res/gameoflife.png b/res/icons/game_of_life_icon.png similarity index 100% rename from res/gameoflife.png rename to res/icons/game_of_life_icon.png diff --git a/res/level_icon.png b/res/icons/level_icon.png similarity index 100% rename from res/level_icon.png rename to res/icons/level_icon.png diff --git a/res/morse_icon.png b/res/icons/morse_icon.png similarity index 100% rename from res/morse_icon.png rename to res/icons/morse_icon.png diff --git a/res/music_icon.png b/res/icons/music_icon.png similarity index 100% rename from res/music_icon.png rename to res/icons/music_icon.png diff --git a/res/phone_finder_icon.png b/res/icons/phone_finder_icon.png similarity index 100% rename from res/phone_finder_icon.png rename to res/icons/phone_finder_icon.png diff --git a/res/settings_icon.png b/res/icons/settings_icon.png similarity index 100% rename from res/settings_icon.png rename to res/icons/settings_icon.png diff --git a/res/snake_icon.png b/res/icons/snake_icon.png similarity index 100% rename from res/snake_icon.png rename to res/icons/snake_icon.png diff --git a/res/software_icon.png b/res/icons/software_icon.png similarity index 100% rename from res/software_icon.png rename to res/icons/software_icon.png diff --git a/res/timer_icon.png b/res/icons/timer_icon.png similarity index 100% rename from res/timer_icon.png rename to res/icons/timer_icon.png diff --git a/res/weather_icon.png b/res/icons/weather_icon.png similarity index 100% rename from res/weather_icon.png rename to res/icons/weather_icon.png diff --git a/res/AlarmApp.png b/res/screenshots/AlarmApp.png similarity index 100% rename from res/AlarmApp.png rename to res/screenshots/AlarmApp.png diff --git a/res/BeaconApp.png b/res/screenshots/BeaconApp.png similarity index 100% rename from res/BeaconApp.png rename to res/screenshots/BeaconApp.png diff --git a/res/CalcApp.png b/res/screenshots/CalculatorApp.png similarity index 100% rename from res/CalcApp.png rename to res/screenshots/CalculatorApp.png diff --git a/res/ChronoApp.png b/res/screenshots/ChronoApp.png similarity index 100% rename from res/ChronoApp.png rename to res/screenshots/ChronoApp.png diff --git a/res/ClockApp.png b/res/screenshots/ClockApp.png similarity index 100% rename from res/ClockApp.png rename to res/screenshots/ClockApp.png diff --git a/res/DemoApp.png b/res/screenshots/DemoApp.png similarity index 100% rename from res/DemoApp.png rename to res/screenshots/DemoApp.png diff --git a/res/DisaBLEApp.png b/res/screenshots/DisaBLEApp.png similarity index 100% rename from res/DisaBLEApp.png rename to res/screenshots/DisaBLEApp.png diff --git a/res/DualApp.png b/res/screenshots/DualClockApp.png similarity index 100% rename from res/DualApp.png rename to res/screenshots/DualClockApp.png diff --git a/res/FacesApp.png b/res/screenshots/FacesApp.png similarity index 100% rename from res/FacesApp.png rename to res/screenshots/FacesApp.png diff --git a/res/FiboApp.png b/res/screenshots/FibonacciClockApp.png similarity index 100% rename from res/FiboApp.png rename to res/screenshots/FibonacciClockApp.png diff --git a/res/TorchApp.png b/res/screenshots/FlashlightApp.png similarity index 100% rename from res/TorchApp.png rename to res/screenshots/FlashlightApp.png diff --git a/res/GalleryApp.png b/res/screenshots/GalleryApp.png similarity index 100% rename from res/GalleryApp.png rename to res/screenshots/GalleryApp.png diff --git a/res/LifeApp.png b/res/screenshots/GameOfLifeApp.png similarity index 100% rename from res/LifeApp.png rename to res/screenshots/GameOfLifeApp.png diff --git a/res/HaikuApp.png b/res/screenshots/HaikuApp.png similarity index 100% rename from res/HaikuApp.png rename to res/screenshots/HaikuApp.png diff --git a/res/HeartApp.png b/res/screenshots/HeartApp.png similarity index 100% rename from res/HeartApp.png rename to res/screenshots/HeartApp.png diff --git a/res/LauncherApp.png b/res/screenshots/LauncherApp.png similarity index 100% rename from res/LauncherApp.png rename to res/screenshots/LauncherApp.png diff --git a/res/LevelApp.png b/res/screenshots/LevelApp.png similarity index 100% rename from res/LevelApp.png rename to res/screenshots/LevelApp.png diff --git a/res/MorseApp.png b/res/screenshots/MorseApp.png similarity index 100% rename from res/MorseApp.png rename to res/screenshots/MorseApp.png diff --git a/res/MusicApp.png b/res/screenshots/MusicPlayerApp.png similarity index 100% rename from res/MusicApp.png rename to res/screenshots/MusicPlayerApp.png diff --git a/res/FinderApp.png b/res/screenshots/PhoneFinderApp.png similarity index 100% rename from res/FinderApp.png rename to res/screenshots/PhoneFinderApp.png diff --git a/res/2048App.png b/res/screenshots/Play2048App.png similarity index 100% rename from res/2048App.png rename to res/screenshots/Play2048App.png diff --git a/res/SettingsApp.png b/res/screenshots/SettingsApp.png similarity index 100% rename from res/SettingsApp.png rename to res/screenshots/SettingsApp.png diff --git a/res/SnakeApp.png b/res/screenshots/SnakeApp.png similarity index 100% rename from res/SnakeApp.png rename to res/screenshots/SnakeApp.png diff --git a/res/SoftwareApp.png b/res/screenshots/SoftwareApp.png similarity index 100% rename from res/SoftwareApp.png rename to res/screenshots/SoftwareApp.png diff --git a/res/SportsApp.png b/res/screenshots/SportsApp.png similarity index 100% rename from res/SportsApp.png rename to res/screenshots/SportsApp.png diff --git a/res/StepsApp.png b/res/screenshots/StepCounterApp.png similarity index 100% rename from res/StepsApp.png rename to res/screenshots/StepCounterApp.png diff --git a/res/StopclockApp.png b/res/screenshots/StopwatchApp.png similarity index 100% rename from res/StopclockApp.png rename to res/screenshots/StopwatchApp.png diff --git a/res/SelfTestApp.png b/res/screenshots/TestApp.png similarity index 100% rename from res/SelfTestApp.png rename to res/screenshots/TestApp.png diff --git a/res/TimerApp.png b/res/screenshots/TimerApp.png similarity index 100% rename from res/TimerApp.png rename to res/screenshots/TimerApp.png diff --git a/res/WeatherApp.png b/res/screenshots/WeatherApp.png similarity index 100% rename from res/WeatherApp.png rename to res/screenshots/WeatherApp.png diff --git a/res/WeekClkApp.png b/res/screenshots/WeekClockApp.png similarity index 100% rename from res/WeekClkApp.png rename to res/screenshots/WeekClockApp.png diff --git a/res/WordClkApp.png b/res/screenshots/WordClockApp.png similarity index 100% rename from res/WordClkApp.png rename to res/screenshots/WordClockApp.png diff --git a/tools/configure_wasp_apps.py b/tools/configure_wasp_apps.py new file mode 100644 index 0000000..2a933c1 --- /dev/null +++ b/tools/configure_wasp_apps.py @@ -0,0 +1,93 @@ +"""Configure the wasp distribution based on the provided wasp.toml config + + This script generates the following files and directories + wasp/apps/user/ + wasp/boards/manifest_user_apps.py + wasp/appregistry.py +""" + +import tomli +import os +import sys + + +def _snake_case_to_pascal_case(s): + out = '' + for word in s.split('_'): + out = out + word[:1].upper() + word[1:] + return out + + +def _file_path_to_class_name(path): + return _snake_case_to_pascal_case(path.split('/')[-1].split('.')[0]) + 'App' + + +def _file_path_to_display_name(path): + return _snake_case_to_pascal_case(path.split('/')[-1].split('.')[0]) + + +with open(sys.argv[1:][0], 'rb') as config_file: + config = tomli.load(config_file) + + # Copy selected apps to the wasp app user directory + for app in config.get('app'): + os.system('cp ' + app.get('file') + ' wasp/apps/user/' + app.get('file').split('/')[-1]) + + # Copy selected watch faces to the wasp app user directory + for app in config.get('watchface'): + os.system('cp ' + app.get('file') + ' wasp/apps/user/' + app.get('file').split('/')[-1]) + + # Create the user app manifest containing all user apps and watch faces + with open('wasp/boards/manifest_user_apps.py', 'w') as manifest_file: + manifest_file.write('# This file is auto generated from the wasp.toml. Manual changes will be overwritten. \n\n') + manifest_file.write('manifest = (\n') + for app in config.get('app'): + manifest_file.write(' \'' + 'apps/user/' + app.get('file').split('/')[-1] + '\',\n') + for watchface in config.get('watchface'): + manifest_file.write(' \'' + 'apps/user/' + watchface.get('file').split('/')[-1] + '\',\n') + manifest_file.write(')') + + # Create a registry for the os to use to populate its lists + with open('wasp/appregistry.py', 'w') as reg_file: + + # Software to display in the software app + reg_file.write('# This file is auto generated from the wasp.toml. Manual changes will be overwritten. \n\n') + reg_file.write('software_list = (\n') + for app in config.get('app'): + if not app.get('quick_ring'): + app_module_name = 'apps.user.' + app.get('file').split('/')[-1].split('.')[0] + app_display_name = _file_path_to_display_name(app.get('file')) + reg_file.write(' (\'' + app_module_name + '\', \'' + app_display_name + '\'),\n') + reg_file.write(')\n\n') + + # Software to display in the faces app + reg_file.write('faces_list = (\n') + for face in config.get('watchface'): + watchface_module = 'apps.user.' + face.get('file').split('/')[-1].split('.')[0] + watchface_class = _file_path_to_display_name(face.get('file')) + reg_file.write(' (\'' + watchface_module + '\',\'' + watchface_class + '\'),\n') + reg_file.write(')\n\n') + + + # Software to be loaded at startup (default watch face, quick ring apps, any apps marked auto_load in the config) + reg_file.write('autoload_list = (\n') + + # Fist app to autoload should be the default watch face + default_watchface = None + for watchface in config.get('watchface'): + if watchface.get('default'): + default_watchface = watchface + elif not default_watchface: + default_watchface = watchface + watchface_path = 'apps.user.' + default_watchface.get('file').split('/')[-1].split('.')[0] + '.' + _file_path_to_class_name(default_watchface.get('file')) + reg_file.write(' (\'' + watchface_path + '\', True, False, True),\n') + + # The next apps should be the quick ring and any auto_load apps (in order specified in the config) + for app in config.get('app'): + if (app.get('quick_ring') or app.get('auto_load')): + app_path = 'apps.user.' + app.get('file').split('/')[-1].split('.')[0] + '.' + _file_path_to_class_name(app.get('file')) + app_quick_ring = str(not (app.get('quick_ring') is None)) + app_no_except = str(not (app.get('no_except') is None)) + reg_file.write(' (\'' + app_path + '\', ' + app_quick_ring + ', False, ' + app_no_except + '),\n') + reg_file.write(')\n\n') + diff --git a/tools/docker/Dockerfile b/tools/docker/Dockerfile index 6f1bead..49294fe 100644 --- a/tools/docker/Dockerfile +++ b/tools/docker/Dockerfile @@ -12,7 +12,7 @@ RUN set -xe; \ libcairo2-dev python3-serial unzip python3-sphinx graphviz \ python3-recommonmark python3-pytest \ ; \ - pip3 install cbor pysdl2 pygobject cryptography; + pip3 install cbor pysdl2 pygobject cryptography tomli; RUN set -xe; \ wget --progress=dot:mega -O - https://developer.arm.com/-/media/Files/downloads/gnu-rm/10-2020q4/gcc-arm-none-eabi-10-2020-q4-major-x86_64-linux.tar.bz2 | tar xjf - -C /opt diff --git a/tools/pynus b/tools/pynus index 1164d3d..5572b01 160000 --- a/tools/pynus +++ b/tools/pynus @@ -1 +1 @@ -Subproject commit 1164d3d844290fc330e366e1881f7c10cf7a8be6 +Subproject commit 5572b01b26e04c7f8c79e8407f4d202e7258bf6b diff --git a/wasp.toml b/wasp.toml new file mode 100644 index 0000000..261c16a --- /dev/null +++ b/wasp.toml @@ -0,0 +1,39 @@ +# The default build for wasp-os. Modify to customize your own build. +# At least one watch face must be specified. Quick ring apps will be added in order. + +[[app]] +file = 'apps/stopwatch.py' +quick_ring = true + +[[app]] +file = 'apps/heart.py' +quick_ring = true + +[[app]] +file = 'apps/alarm.py' +auto_load = true + +[[app]] +file = 'apps/timer.py' +auto_load = true + +[[app]] +file = 'apps/calculator.py' + +[[app]] +file = 'apps/disa_b_l_e.py' + +[[app]] # If only one watch face is included this app can be removed +file = 'apps/faces.py' +auto_load = true +no_except = true + +[[watchface]] +file = 'watch_faces/clock.py' + +[[watchface]] # Requires ClockApp to also be installed +file = 'watch_faces/week_clock.py' +default = true + +[[watchface]] +file = 'watch_faces/chrono.py' \ No newline at end of file diff --git a/wasp/apps/launcher.py b/wasp/apps/system/launcher.py similarity index 98% rename from wasp/apps/launcher.py rename to wasp/apps/system/launcher.py index 735ac70..3db5b14 100644 --- a/wasp/apps/launcher.py +++ b/wasp/apps/system/launcher.py @@ -4,7 +4,7 @@ """Application launcher ~~~~~~~~~~~~~~~~~~~~~~~ -.. figure:: res/LauncherApp.png +.. figure:: res/screenshots/LauncherApp.png :width: 179 """ diff --git a/wasp/apps/pager.py b/wasp/apps/system/pager.py similarity index 100% rename from wasp/apps/pager.py rename to wasp/apps/system/pager.py diff --git a/wasp/apps/settings.py b/wasp/apps/system/settings.py similarity index 99% rename from wasp/apps/settings.py rename to wasp/apps/system/settings.py index 7afd7ef..2cc3cf7 100644 --- a/wasp/apps/settings.py +++ b/wasp/apps/system/settings.py @@ -7,7 +7,7 @@ Allows a very small set of user preferences (including the date and time) to be set on the device itself. -.. figure:: res/SettingsApp.png +.. figure:: res/screenshots/SettingsApp.png :width: 179 .. note:: diff --git a/wasp/apps/software.py b/wasp/apps/system/software.py similarity index 79% rename from wasp/apps/software.py rename to wasp/apps/system/software.py index 381f6a0..a18bd5c 100644 --- a/wasp/apps/software.py +++ b/wasp/apps/system/software.py @@ -5,7 +5,7 @@ A tool to enable/disable applications. -.. figure:: res/SoftwareApp.png +.. figure:: res/screenshots/SoftwareApp.png :width: 179 Most applications are disabled by default at boot in order to conserve @@ -18,6 +18,7 @@ import wasp import icons import os import gc +import appregistry class SoftwareApp(): @@ -39,19 +40,8 @@ class SoftwareApp(): y = 0 db = [] - db.append(('alarm', factory('Alarm'))) - db.append(('calc', factory('Calculator'))) - db.append(('disaBLE', factory('DisaBLE'))) - db.append(('faces', factory('Faces'))) - db.append(('gallery', factory('Gallery'))) - db.append(('musicplayer', factory('Music Player'))) - db.append(('play2048', factory('Play 2048'))) - db.append(('snake', factory('Snake Game'))) - db.append(('sports', factory('Sports'))) - db.append(('flashlight', factory('Torch'))) - db.append(('testapp', factory('Test'))) - db.append(('timer', factory('Timer'))) - db.append(('weather', factory('Weather'))) + for app in appregistry.software_list: + db.append((app[0], factory(app[1]))) # Handle user-loaded applications try: @@ -62,7 +52,14 @@ class SoftwareApp(): if app.endswith('.mpy'): name = app[:-4] if name: - db.append((name, factory(name))) + # Don't add apps that already exist (prioritize frozen apps) + load = True + for db_app in db: + if db_app[0][db_app[0].rindex('.') + 1:] == name: + load = False + break + if load: + db.append(('apps.' + name, factory(self._snake_case_to_pascal_case(name)))) except OSError: # apps does not exist... pass @@ -117,7 +114,7 @@ class SoftwareApp(): label = checkbox.label.replace(' ', '') if checkbox.state: gc.collect() - wasp.system.register('apps.{}.{}App'.format(module, label)) + wasp.system.register('{}.{}App'.format(module, label)) else: for app in wasp.system.launcher_ring: if type(app).__name__.startswith(label): @@ -131,3 +128,10 @@ class SoftwareApp(): self.si.draw() for _, checkbox in self.get_page(): checkbox.draw() + + @staticmethod + def _snake_case_to_pascal_case(s): + out = '' + for word in s.split('_'): + out = out + word[:1].upper() + word[1:] + return out \ No newline at end of file diff --git a/wasp/apps/steps.py b/wasp/apps/system/step_counter.py similarity index 99% rename from wasp/apps/steps.py rename to wasp/apps/system/step_counter.py index 021db38..38209e3 100644 --- a/wasp/apps/steps.py +++ b/wasp/apps/system/step_counter.py @@ -6,7 +6,7 @@ Provide a daily step count. - .. figure:: res/StepsApp.png + .. figure:: res/screenshots/StepCounterApp.png :width: 179 The step counts automatically reset at midnight. diff --git a/wasp/boards/k9/manifest.py b/wasp/boards/k9/manifest.py index c160c21..85594b0 100644 --- a/wasp/boards/k9/manifest.py +++ b/wasp/boards/k9/manifest.py @@ -5,9 +5,10 @@ import os, sys sys.path.append(os.path.dirname(os.getcwd())) import manifest_240x240 +import manifest_user_apps freeze('.', 'watch.py', opt=3) -freeze('../..', manifest_240x240.manifest + +freeze('../..', manifest_240x240.manifest + manifest_user_apps.manifest + ( 'boot.py', 'draw565.py', diff --git a/wasp/boards/manifest_240x240.py b/wasp/boards/manifest_240x240.py index fb350d7..1d5fea1 100644 --- a/wasp/boards/manifest_240x240.py +++ b/wasp/boards/manifest_240x240.py @@ -3,33 +3,11 @@ """Shared manifest for applications that work well on a 240x240 display.""" manifest = ( - 'apps/alarm.py', - 'apps/calc.py', - 'apps/clock.py', - 'apps/chrono.py', - 'apps/disaBLE.py', - 'apps/dual_clock.py', - 'apps/faces.py', - 'apps/fibonacci_clock.py', - 'apps/flashlight.py', - 'apps/gallery.py', - 'apps/haiku.py', - 'apps/heart.py', - 'apps/musicplayer.py', - 'apps/launcher.py', - 'apps/pager.py', - 'apps/play2048.py', - 'apps/settings.py', - 'apps/software.py', - 'apps/sports.py', - 'apps/steps.py', - 'apps/stopwatch.py', - 'apps/snake.py', - 'apps/testapp.py', - 'apps/timer.py', - 'apps/weather.py', - 'apps/week_clock.py', - 'apps/word_clock.py', + 'apps/system/launcher.py', + 'apps/system/pager.py', + 'apps/system/software.py', + 'apps/system/settings.py', + 'apps/system/step_counter.py', 'fonts/__init__.py', 'fonts/clock.py', 'fonts/clock_dual.py', @@ -38,6 +16,7 @@ manifest = ( 'fonts/sans28.py', 'fonts/sans36.py', 'icons.py', - 'steplogger.py', 'widgets.py', + 'steplogger.py', + 'appregistry.py', ) diff --git a/wasp/boards/p8/manifest.py b/wasp/boards/p8/manifest.py index 9243d77..0cfb58c 100644 --- a/wasp/boards/p8/manifest.py +++ b/wasp/boards/p8/manifest.py @@ -5,9 +5,10 @@ import os, sys sys.path.append(os.path.dirname(os.getcwd())) import manifest_240x240 +import manifest_user_apps freeze('.', 'watch.py', opt=3) -freeze('../..', manifest_240x240.manifest + +freeze('../..', manifest_240x240.manifest + manifest_user_apps.manifest + ( 'boot.py', 'draw565.py', diff --git a/wasp/boards/pinetime/manifest.py b/wasp/boards/pinetime/manifest.py index 9243d77..0cfb58c 100644 --- a/wasp/boards/pinetime/manifest.py +++ b/wasp/boards/pinetime/manifest.py @@ -5,9 +5,10 @@ import os, sys sys.path.append(os.path.dirname(os.getcwd())) import manifest_240x240 +import manifest_user_apps freeze('.', 'watch.py', opt=3) -freeze('../..', manifest_240x240.manifest + +freeze('../..', manifest_240x240.manifest + manifest_user_apps.manifest + ( 'boot.py', 'draw565.py', diff --git a/wasp/boards/simulator/conftest.py b/wasp/boards/simulator/conftest.py index 29f8206..b5a5e97 100644 --- a/wasp/boards/simulator/conftest.py +++ b/wasp/boards/simulator/conftest.py @@ -2,23 +2,29 @@ import glob import importlib import inspect import pytest +import pprint +import json def discover_app_constructors(): apps = [] + appClasses = [] - globs_system = glob.glob('wasp/apps/*.py') + globs_system = glob.glob('wasp/apps/system/*.py') names_system = [ g[5:-3].replace('/', '.') for g in globs_system ] globs_user = glob.glob('apps/*.py') names_user = [ g[:-3].replace('/', '.') for g in globs_user ] - modules = [ importlib.import_module(n) for n in names_system + names_user ] + globs_watchface = glob.glob('watch_faces/*.py') + names_watchface = [ g[:-3].replace('/', '.') for g in globs_watchface ] + modules = [ importlib.import_module(n) for n in names_system + names_user + names_watchface ] for m in modules: for sym in m.__dict__.keys(): - if len(sym) > 3 and sym[-3:] == 'App': + if len(sym) > 3 and sym[-3:] == 'App' and not sym in appClasses: constructor = m.__dict__[sym] sig = inspect.signature(constructor) if len(sig.parameters) == 0: apps.append(constructor) + appClasses.append(sym) return apps diff --git a/wasp/boards/simulator/main.py b/wasp/boards/simulator/main.py index e63d469..d308df8 100644 --- a/wasp/boards/simulator/main.py +++ b/wasp/boards/simulator/main.py @@ -5,7 +5,7 @@ import wasp # Test app is used a lot on the simulator. Let's make sure it is # registered by default. -wasp.system.register('apps.testapp.TestApp') +wasp.system.register('apps.test.TestApp') # Ensure there's something interesting to look at ;-) wasp.system.set_music_info({ diff --git a/wasp/boards/simulator/test_qa.py b/wasp/boards/simulator/test_qa.py index a43cbc1..1a90380 100644 --- a/wasp/boards/simulator/test_qa.py +++ b/wasp/boards/simulator/test_qa.py @@ -4,12 +4,13 @@ import importlib import os from PIL import Image -EXCLUDE = ('Notifications', 'Template', 'Faces', 'ReadMe') +EXCLUDE = ('NotificationApp', 'PagerApp', 'TemplateApp', 'FacesApp', 'ReadMeApp') def test_screenshot(constructor): - if constructor.NAME in EXCLUDE: + if f'{constructor.__name__}' in EXCLUDE or f'{constructor.__module__}'.startswith('apps.user.'): return - fname = f'res/{constructor.NAME}App.png'.replace(' ', '') + + fname = f'res/screenshots/{constructor.__name__}.png'.replace(' ', '') # Every application requires a screenshot be captured for use in the # documentation. The screenshots must conform to standard dimensions @@ -25,9 +26,10 @@ def test_screenshot(constructor): assert screenshot.height == 406 def test_README(constructor): - if constructor.NAME in EXCLUDE: + if f'{constructor.__name__}' in EXCLUDE or f'{constructor.__module__}'.startswith('apps.user.'): return - fname = f'res/{constructor.NAME}App.png'.replace(' ', '') + + fname = f'res/screenshots/{constructor.__name__}.png'.replace(' ', '') with open('README.rst') as f: readme = f.readlines() @@ -56,7 +58,7 @@ def test_README(constructor): assert ':width: 179' in readme[offset+2] def test_app_library(constructor): - if constructor.NAME in EXCLUDE: + if f'{constructor.__name__}' in EXCLUDE or f'{constructor.__module__}'.startswith('apps.user.'): return with open('docs/apps.rst') as f: @@ -65,14 +67,30 @@ def test_app_library(constructor): waspdoc = f.read() # Every application must be listed in the Application Library - needle_system = f'.. automodule:: {constructor.__module__}' + needle_system = f'.. automodule:: {constructor.__module__}'.replace('apps.system.', '') needle_user_defined = f'.. automodule:: {constructor.__module__}'.replace('apps.', '') - assert needle_system in appdoc or needle_user_defined in appdoc + needle_watch_faces = f'.. automodule:: {constructor.__module__}'.replace('watch_faces.', '') + assert needle_system in appdoc or needle_user_defined in appdoc or needle_watch_faces in appdoc + +def test_app_naming(constructor): + # The class name of every app must be the PascalCase version of its file name in snake_case appended with "App" + if f'{constructor.__name__}' in EXCLUDE or f'{constructor.__module__}'.startswith('apps.user.'): + return + + module = f'{constructor.__module__}' + if module.startswith('apps.system.'): + assert _snake_case_to_pascal_case(module.replace('apps.system.', '')) + 'App' == f'{constructor.__name__}' + elif module.startswith('apps.'): + assert _snake_case_to_pascal_case(module.replace('apps.', '')) + 'App' == f'{constructor.__name__}' + elif module.startswith('watch_faces.'): + assert _snake_case_to_pascal_case(module.replace('watch_faces.', '')) + 'App' == f'{constructor.__name__}' + def test_docstrings(constructor): - if constructor.NAME in EXCLUDE: + if f'{constructor.__name__}' in EXCLUDE or f'{constructor.__module__}'.startswith('apps.user.'): return - fname = f'res/{constructor.NAME}App.png'.replace(' ', '') + + fname = f'res/screenshots/{constructor.__name__}.png'.replace(' ', '') class_doc = constructor.__doc__ module_doc = importlib.import_module(constructor.__module__).__doc__ @@ -99,3 +117,9 @@ def test_docstrings(constructor): # The second line of the module documentation should be an # underline (e.g. the first line must be a section header) assert(module_doc.split('\n')[1].startswith('~~~~')) + +def _snake_case_to_pascal_case(s): + out = '' + for word in s.split('_'): + out = out + word[:1].upper() + word[1:] + return out \ No newline at end of file diff --git a/wasp/boards/simulator/test_smoke.py b/wasp/boards/simulator/test_smoke.py index 3cb92b9..8c7d789 100644 --- a/wasp/boards/simulator/test_smoke.py +++ b/wasp/boards/simulator/test_smoke.py @@ -1,8 +1,8 @@ import pytest import time import wasp -import apps.testapp -import apps.settings +import apps.test +import settings def step(): wasp.system._tick() @@ -32,7 +32,7 @@ def test_step(system): def test_quick_ring(system): names = [ x.NAME for x in system.quick_ring ] - assert('Clock' in names) + assert('WeekClk' in names) assert('Steps' in names) assert('Stopclock' in names) assert('Heart' in names) @@ -103,7 +103,7 @@ def test_selftests(system): will do something useful! For example it will run the benchmark for every one of the benchmark tests. """ - system.switch(apps.testapp.TestApp()) + system.switch(apps.test.TestApp()) system.step() start_point = system.app.test @@ -117,7 +117,7 @@ def test_selftests(system): assert(start_point == system.app.test) def test_selftest_crash(system): - system.switch(apps.testapp.TestApp()) + system.switch(apps.test.TestApp()) system.step() def select(name): @@ -148,7 +148,7 @@ def test_settings(system): This is a simple "does it crash" test and the only thing we do to stimulate the app is press in the centre of the screen. """ - system.switch(apps.settings.SettingsApp()) + system.switch(settings.SettingsApp()) system.step() start_point = system.app._current_setting diff --git a/wasp/requirements.txt b/wasp/requirements.txt index 2506ab7..10fad09 100644 --- a/wasp/requirements.txt +++ b/wasp/requirements.txt @@ -9,3 +9,4 @@ pygobject pyserial pysdl2 pytest +tomli diff --git a/wasp/wasp.py b/wasp/wasp.py index 2dca1f1..b24fa01 100644 --- a/wasp/wasp.py +++ b/wasp/wasp.py @@ -21,10 +21,11 @@ import steplogger import sys import watch import widgets +import appregistry -from apps.launcher import LauncherApp -from apps.pager import PagerApp, CrashApp, NotificationApp -from apps.steps import StepCounterApp +from apps.system.launcher import LauncherApp +from apps.system.pager import PagerApp, CrashApp, NotificationApp +from apps.system.step_counter import StepCounterApp class EventType(): """Enumerated interface actions. @@ -165,14 +166,13 @@ class Manager(): def register_defaults(self): """Register the default applications.""" - self.register('apps.clock.ClockApp', True, no_except=True) - self.register('apps.steps.StepCounterApp', True, no_except=True) - self.register('apps.stopwatch.StopwatchApp', True, no_except=True) - self.register('apps.heart.HeartApp', True, no_except=True) - self.register('apps.faces.FacesApp', no_except=True) - self.register('apps.settings.SettingsApp', no_except=True) - self.register('apps.software.SoftwareApp', no_except=True) + for app in appregistry.autoload_list: + self.register(app[0], app[1], app[2], app[3]) + + self.register('apps.system.step_counter.StepCounterApp', True, no_except=True) + self.register('apps.system.settings.SettingsApp', no_except=True) + self.register('apps.system.software.SoftwareApp', no_except=True) def register(self, app, quick_ring=False, watch_face=False, no_except=False): """Register an application with the system. diff --git a/wasp/apps/chrono.py b/watch_faces/chrono.py similarity index 98% rename from wasp/apps/chrono.py rename to watch_faces/chrono.py index 4496a83..cbe904d 100644 --- a/wasp/apps/chrono.py +++ b/watch_faces/chrono.py @@ -6,7 +6,7 @@ Shows the time as a traditional watch face together with a battery meter. -.. figure:: res/ChronoApp.png +.. figure:: res/screenshots/ChronoApp.png :width: 179 Screenshot of the analogue clock application diff --git a/wasp/apps/clock.py b/watch_faces/clock.py similarity index 98% rename from wasp/apps/clock.py rename to watch_faces/clock.py index 6270999..c23d9e1 100644 --- a/wasp/apps/clock.py +++ b/watch_faces/clock.py @@ -6,7 +6,7 @@ Shows a time (as HH:MM) together with a battery meter and the date. -.. figure:: res/ClockApp.png +.. figure:: res/screenshots/ClockApp.png :width: 179 """ diff --git a/wasp/apps/dual_clock.py b/watch_faces/dual_clock.py similarity index 98% rename from wasp/apps/dual_clock.py rename to watch_faces/dual_clock.py index ff57fbd..156f04f 100644 --- a/wasp/apps/dual_clock.py +++ b/watch_faces/dual_clock.py @@ -6,7 +6,7 @@ Shows a time (as HH and MM vertically) together with a battery meter. -.. figure:: res/DualApp.png +.. figure:: res/screenshots/DualClockApp.png :width: 179 """ diff --git a/wasp/apps/fibonacci_clock.py b/watch_faces/fibonacci_clock.py similarity index 98% rename from wasp/apps/fibonacci_clock.py rename to watch_faces/fibonacci_clock.py index 3e9fadc..89024d3 100644 --- a/wasp/apps/fibonacci_clock.py +++ b/watch_faces/fibonacci_clock.py @@ -9,7 +9,7 @@ mathematician Fibonacci in the 13th century. This is a sequence starting with 1 and 1, where each subsequent number is the sum of the previous two. For the clock I used the first 5 terms: 1, 1, 2, 3 and 5. - .. figure:: res/FiboApp.png + .. figure:: res/screenshots/FibonacciClockApp.png :width: 179 Screenshot of the fibonacci clock application diff --git a/wasp/apps/week_clock.py b/watch_faces/week_clock.py similarity index 88% rename from wasp/apps/week_clock.py rename to watch_faces/week_clock.py index 99d3c41..3b87b61 100644 --- a/wasp/apps/week_clock.py +++ b/watch_faces/week_clock.py @@ -6,11 +6,11 @@ Shows a time (as HH:MM) together with a battery meter, the date, and the weekday. -.. figure:: res/WeekClkApp.png +.. figure:: res/screenshots/WeekClockApp.png :width: 179 """ -from apps.clock import ClockApp, MONTH +from apps.user.clock import ClockApp, MONTH WDAY = 'MonTueWedThuFriSatSun' diff --git a/wasp/apps/word_clock.py b/watch_faces/word_clock.py similarity index 99% rename from wasp/apps/word_clock.py rename to watch_faces/word_clock.py index c1504c0..7a26d02 100644 --- a/wasp/apps/word_clock.py +++ b/watch_faces/word_clock.py @@ -8,7 +8,7 @@ Shows a time as words together with a battery meter and the date. -.. figure:: res/WordClkApp.png +.. figure:: res/screenshots/WordClockApp.png :width: 179 """