docs: Introduce the Application Writer's Guide.
This commit is contained in:
parent
bb8e95cdd4
commit
a2fd52ef2b
10 changed files with 427 additions and 13 deletions
3
Makefile
3
Makefile
|
@ -72,8 +72,7 @@ docs:
|
|||
|
||||
|
||||
sim:
|
||||
PYTHONDONTWRITEBYTECODE=1 \
|
||||
PYTHONPATH=$(PWD)/wasp/boards/simulator:$(PWD)/wasp \
|
||||
PYTHONDONTWRITEBYTECODE=1 PYTHONPATH=.:wasp/boards/simulator:wasp \
|
||||
python3 -i wasp/main.py
|
||||
|
||||
.PHONY: bootloader reloader docs micropython
|
||||
|
|
4
TODO.md
4
TODO.md
|
@ -64,10 +64,10 @@ applications.
|
|||
* [X] Stopwatch app
|
||||
* [X] Settings app
|
||||
* [X] PC-hosted simulation platform
|
||||
* [o] Documentation
|
||||
* [O] Documentation
|
||||
- [X] Sphinx framework and integration with github.io
|
||||
- [X] Document bootloader protocols
|
||||
- [ ] Application writer's guide
|
||||
- [X] Application writer's guide
|
||||
- [ ] Write full docstring documentation for all WASP components
|
||||
* [X] Application Launcher
|
||||
* [X] Debug notifications
|
||||
|
|
350
docs/appguide.rst
Normal file
350
docs/appguide.rst
Normal file
|
@ -0,0 +1,350 @@
|
|||
Application Writer's Guide
|
||||
==========================
|
||||
|
||||
.. contents::
|
||||
:local:
|
||||
|
||||
Introduction
|
||||
------------
|
||||
|
||||
WASP, the Watch Application System in Python, has one pervasive goal that
|
||||
influences almost everything about it, from its name to its development
|
||||
roadmap: make writing applications easy (and fun).
|
||||
|
||||
Applications that can be loaded, changed, adapted and remixed by the user
|
||||
are what **really** distinguishes a smart watch from a "feature watch"[#]_.
|
||||
In other words if we want a watch built around a tiny microcontroller to be
|
||||
sufficiently "smart" then it has to be all about the applications.
|
||||
|
||||
This guide will help you get started writing applications for WASP. Have fun!
|
||||
|
||||
.. [#] The fixed function mobile phones that existed before iOS and Android
|
||||
took over the industry were retrospectively renamed "feature phones" to
|
||||
distinguish them from newer devices. Many of them were superficially similar
|
||||
to early Android devices but is was the application ecosystem that really
|
||||
made smart phones smart.
|
||||
|
||||
Hello World for WASP
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Let's start by examining a simple "Hello, World!" application for WASP.
|
||||
|
||||
.. literalinclude:: hello.py
|
||||
:linenos:
|
||||
|
||||
There are a couple of points of interest:
|
||||
|
||||
1. Applications have a :py:data:`~.TemplateApp.NAME`, which is shown in the
|
||||
launcher. Most applications also provide an :py:data:`~.TemplateApp.ICON`
|
||||
but a default is displayed if this is omitted.
|
||||
2. This example uses :py:meth:`~.TemplateApp.__init__` to initialize
|
||||
the state of the application, this ensure the state remains "sticky"
|
||||
when the application is activated and deactivated.
|
||||
3. :py:meth:`~.TemplateApp.foreground` is the only mandatory application entry
|
||||
point and is responsible for redrawing the screen. This application does
|
||||
not implement :py:meth:`~.TemplateApp.background` because there is nothing
|
||||
for us to do!
|
||||
4. The use of :py:meth:`~.TemplateApp._draw` is optional. We could just do
|
||||
the work in :py:meth:`~.TemplateApp.foreground` but this application follows
|
||||
a common WASP idiom that is normally used to pattern to distinguish a full
|
||||
refresh of the screen from an fast update (a redraw).
|
||||
|
||||
Application life-cycle
|
||||
----------------------
|
||||
|
||||
Applications in WASP are triggered by and do all their processing
|
||||
from calls their entry points. The entry points can be coarsely categorized
|
||||
event notifications, timer callbacks (the application tick) and
|
||||
system notifications.
|
||||
|
||||
System notifications control the application life-cycle and the entry point
|
||||
calls, together with the implicit application states are shown below.
|
||||
|
||||
.. graphviz::
|
||||
|
||||
digraph lifecycle {
|
||||
START -> BACKGROUND [ label=" __init__() " ];
|
||||
BACKGROUND -> START [ label=" __del__() " ];
|
||||
BACKGROUND -> ACTIVE [ label=" foreground() " ];
|
||||
ACTIVE -> BACKGROUND [ label=" background() " ];
|
||||
ACTIVE -> GO_TO_CLOCK [ label=" sleep() -> False " ];
|
||||
GO_TO_CLOCK -> BACKGROUND [ label=" background() " ];
|
||||
ACTIVE -> SLEEPING [ label=" sleep() -> True " ];
|
||||
SLEEPING -> ACTIVE [ label=" wake() " ];
|
||||
|
||||
START [ shape=box ];
|
||||
BACKGROUND [ shape=box, style=rounded ]
|
||||
ACTIVE [ shape=box, style=rounded ]
|
||||
SLEEPING [ shape=box, style=rounded ]
|
||||
GO_TO_CLOCK [ label="GOTO ClockApp" ];
|
||||
}
|
||||
|
||||
When an application is initialized is enters the ``BACKGROUND`` state. A
|
||||
backgrounded application will not execute but it should nevertheless
|
||||
maintain its user visible state whilst in the background. To conserve
|
||||
memory WASP does not permit two applications to run simultaneously but
|
||||
because each application preserves its state when in the background it will
|
||||
appear to the user as though all applications are running all the time.
|
||||
|
||||
For example, a stopwatch application should record the time that it was started
|
||||
and remember that start time, regardless of it's state, until either the
|
||||
stopwatch is stopped of the application is destroyed.
|
||||
|
||||
A backgrounded application can enter the ``ACTIVE`` state via a call to
|
||||
:py:meth:`~.TemplateApp.foreground`. When it is active the application owns the
|
||||
screen and should draw and maintain its UI.
|
||||
|
||||
If the system manager want to put an active application to sleep then it will
|
||||
call :py:meth:`~.TemplateApp.sleep`. If the application returns True then the
|
||||
application will stop running (e.g. receive no events and no application tick)
|
||||
but instead must wait to receive a notification via
|
||||
:py:meth:`~.TemplateApp.wake` telling the application that the device
|
||||
is waking up and that it may update the screen if needed.
|
||||
|
||||
If an application does not support sleeping then it can simply not implement
|
||||
:py:meth:`~.TemplateApp.sleep` (or :py:meth:`~.TemplateApp.wake`) although it
|
||||
can also return False from :py:meth:`~.TemplateApp.sleep` if this is preferred.
|
||||
In this case the system manager will automatically return to the default
|
||||
application, typically the main clock face.
|
||||
|
||||
Note: *Most applications do not need to support sleep() since it is often
|
||||
a better user experience for the watch to return to the default application
|
||||
when they complete an interaction.*
|
||||
|
||||
API primer
|
||||
----------
|
||||
|
||||
This API primer introduces some of the most important and frequently used
|
||||
interfaces for WASP. For more comprehensive coverage see the
|
||||
:ref:`WASP Reference Manual` which contains extensive API documentation
|
||||
covering the entire of WASP, including its drivers.
|
||||
|
||||
System management
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
The system management API does provide a number of low-level calls that
|
||||
can register new applications and navigate between them. However most
|
||||
applications need not use these. Instead most applications use a small
|
||||
set of methods. In particular almost all applictions need to call a couple of
|
||||
methods from :py:meth:`~.TemplateApp.foreground` in order to register
|
||||
for notifications:
|
||||
|
||||
* :py:meth:`~.Manager.request_event` - register for UI events such as button
|
||||
presses and touch screen activity.
|
||||
* :py:meth:`~.Manager.request_tick` - register to receive an application tick
|
||||
and specify the tick frequency.
|
||||
|
||||
Additionally if your application is a game or a similar program that should
|
||||
not allow the watch to go to sleep then it should arrange to call
|
||||
:py:meth:`~.Manager.keep_awake` from the application's
|
||||
:py:meth:`~.TemplateApp.tick` method.
|
||||
|
||||
Drawing
|
||||
~~~~~~~
|
||||
|
||||
Most applications using the drawing toolbox, :py:data:`wasp.watch.drawable`,
|
||||
in order to handle the display. Applications are permitted to directly access
|
||||
:py:data:`wasp.watch.display` if they require direct pixel access or want to
|
||||
exploit specific features of the display hardware (inverse video, partial
|
||||
display, etc) but for simple applications then the following simple drawing
|
||||
functions are sufficient.
|
||||
|
||||
* :py:meth:`~.Draw565.blit` - blit an image to the display at specified (x, y)
|
||||
coordinates, image type is detected automatically
|
||||
* :py:meth:`~.Draw565.fill` - fill a rectangle, without arguments the default
|
||||
is a black rectangle covering the entire screen which is useful to clear
|
||||
the screen prior to an update
|
||||
* :py:meth:`~.Draw565.string` - render a string, optionally centring it
|
||||
automatically
|
||||
* :py:meth:`~.Draw565.wrap` - automatically determine where to break a string
|
||||
so it can be rendered to a specified width
|
||||
|
||||
Most applications run some variant of the following code from their
|
||||
:py:meth:`~.TemplateApp.foreground` or :py:meth:`~.TemplateApp._draw` methods
|
||||
in order to clear the display ready for a redraw.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
draw = wasp.watch.drawable
|
||||
draw.fill()
|
||||
# now use draw to render the rest of the screen
|
||||
|
||||
Some applications customize the above code slightly if they require a custom
|
||||
background colour and it may even be omitted entirely if the application
|
||||
explicitly draws every pixel on the display.
|
||||
|
||||
Finally, WASP provides a small number of widgets that allow common fragments of
|
||||
logic and redrawing code to be shared between applications:
|
||||
|
||||
* :py:class:`.BatteryMeter`
|
||||
* :py:class:`.ScrollingIndicator`
|
||||
|
||||
MicroPython
|
||||
~~~~~~~~~~~
|
||||
|
||||
Many of the features of WASP are inherited directly from MicroPython_. It is
|
||||
useful to have a basic understanding of MicroPython and, in particular, put
|
||||
in a little time learning the best ways to copy with running
|
||||
`MicroPython on microcontrollers`__.
|
||||
|
||||
.. _MicroPython: https://micropython.org/
|
||||
__ http://docs.micropython.org/en/latest/reference/constrained.html
|
||||
|
||||
|
||||
How to run your application
|
||||
---------------------------
|
||||
|
||||
Testing on the simulator
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
WASP provides a simulator that can be used to test applications before
|
||||
downloading them to the device. The simulator is useful for ensuring the
|
||||
code is syntactically correct and that there are not major runtime problems
|
||||
(e.g. missing symbols).
|
||||
|
||||
Note: *The simulator does not model the RAM or code size limits of the
|
||||
real device. It may still be necessary to tune the application for minimal
|
||||
footprint after testing on the simulator.*
|
||||
|
||||
Firstly launch the simulator:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
sh$ make sim
|
||||
PYTHONDONTWRITEBYTECODE=1 PYTHONPATH=.:wasp/boards/simulator:wasp \\
|
||||
python3 -i wasp/main.py
|
||||
MOTOR: set on
|
||||
BACKLIGHT: 2
|
||||
Watch is running, use Ctrl-C to stop
|
||||
|
||||
From the simulator console we can register the application with the following
|
||||
code:
|
||||
|
||||
.. code-block:: python
|
||||
:linenos:
|
||||
|
||||
^C
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
KeyboardInterrupt
|
||||
>>> from myapp import MyApp
|
||||
>>> wasp.system.register(MyApp())
|
||||
>>> wasp.system.run()
|
||||
Watch is running, use Ctrl-C to stop
|
||||
|
||||
When an application is registered it does not start automatically but it will
|
||||
have been added to the launcher and you will be able to select in the simulator
|
||||
by using the Arrow keys to bring up the launcher and then clicking on your
|
||||
application.
|
||||
|
||||
The application can also be registered automatically when you load the
|
||||
simulator if you add it to ``wasp/main.py``. Try adding lines 5 and 6 from
|
||||
the above example into this file (between ``import wasp`` and
|
||||
``wasp.system.run()``).
|
||||
|
||||
Testing on the device
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
If we have an application under development when we can launch a quick test
|
||||
that does not result in the application being permanently stored on the device.
|
||||
Providing there is enough available RAM then this can lead to a very quick
|
||||
edit-test cycles.
|
||||
|
||||
Try:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
sh$ tools/wasptool \\
|
||||
--exec myapp.py \\
|
||||
--eval "wasp.system.register(MyApp())"
|
||||
Preparing to run myapp.py:
|
||||
[##################################################] 100%
|
||||
|
||||
Like the simulator, when an application is registered it does not start
|
||||
automatically but it will have been added to the launcher and can be launched
|
||||
using the normal gestures to control the device.
|
||||
|
||||
Note: *If the progress bar jams at the same point each time then it is likely
|
||||
your application is too large to be compiled on the target. You may have to
|
||||
adopt the frozen module approach from the next section.*
|
||||
|
||||
Making it permanent
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
To ensure you application survives a system reset (press the hardware
|
||||
button for around five seconds until the splash screen is seen, wait
|
||||
five seconds and then press again) then we must copy it to the device
|
||||
and ensure it gets launched during system startup.
|
||||
|
||||
Note: *Applications stored in external FLASH have a greater RAM overhead
|
||||
than applications that are frozen into the WASP binary. See next section
|
||||
for additional details.*
|
||||
|
||||
To copy your application to the external FLASH try:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
sh$ ./tools/wasptool --upload myapp.py
|
||||
Uploading myapp.py:
|
||||
[##################################################] 100%
|
||||
|
||||
At this point your application is stored on the external FLASH but it will
|
||||
not automatically be loaded. This requires you to update the ``main.py`` file
|
||||
stored in the external FLASH. When WASP runs for the first time it
|
||||
automatically generates this file (using ``wasp/main.py`` as a template)
|
||||
and copies it to the external FLASH. After this point ``main.py`` is user
|
||||
modifiable and can be used to tweak the configuration of the watch before
|
||||
it starts running.
|
||||
|
||||
Edit ``wasp/main.py`` to add the following two lines between ``import wasp``
|
||||
and the ``wasp.system.run()``:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from myapp import MyApp
|
||||
wasp.system.register(MyApp())
|
||||
|
||||
Having done that we can use ``wasptool`` to upload the modified file
|
||||
to the watch:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
sh$ ./tools/wasptool --upload wasp/main.py
|
||||
Uploading wasp/main.py:
|
||||
[##################################################] 100%
|
||||
|
||||
Note: *If the new code on the watch throws an exception (including
|
||||
an out-of-memory exception) then your watch will display a black
|
||||
screen at startup. If that happens, and you don't know how to debug
|
||||
the problem, then you can use wasptool to restore the original version
|
||||
of main.py .*
|
||||
|
||||
Freezing your application into the WASP binary
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Freezing your application causes it to consume dramatically less RAM. That is
|
||||
because the code is both pre-compiled (meaning we don't need any RAM budget to
|
||||
run the compiler) **and** it can execute directly from the internal FLASH
|
||||
memory.
|
||||
|
||||
Freezing your application simply 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 to ``main.py``
|
||||
|
||||
Note: *The micropython import path "prefers" frozen modules to those
|
||||
found in the external filesystem. If your application is both frozen and
|
||||
copied to external FLASH then the frozen version will be loaded.*
|
||||
|
||||
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
|
||||
:members:
|
||||
:private-members:
|
||||
:special-members:
|
|
@ -30,6 +30,7 @@ author = 'Daniel Thompson'
|
|||
# ones.
|
||||
extensions = [
|
||||
'sphinx.ext.autodoc',
|
||||
'sphinx.ext.graphviz',
|
||||
'recommonmark',
|
||||
]
|
||||
|
||||
|
|
1
docs/hello.py
Symbolic link
1
docs/hello.py
Symbolic link
|
@ -0,0 +1 @@
|
|||
../wasp/apps/hello.py
|
|
@ -11,6 +11,7 @@ Welcome to WASP-OS's documentation!
|
|||
:caption: Contents:
|
||||
|
||||
README
|
||||
appguide
|
||||
wasp
|
||||
TODO
|
||||
license
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
WASP Internals
|
||||
==============
|
||||
.. _WASP Reference Manual:
|
||||
|
||||
WASP Reference Manual
|
||||
=====================
|
||||
|
||||
System management
|
||||
-----------------
|
||||
|
@ -27,10 +29,6 @@ Applications
|
|||
:members:
|
||||
:undoc-members:
|
||||
|
||||
.. automodule:: apps.template
|
||||
:members:
|
||||
:undoc-members:
|
||||
|
||||
.. automodule:: apps.testapp
|
||||
:members:
|
||||
:undoc-members:
|
||||
|
@ -79,7 +77,6 @@ Libraries
|
|||
|
||||
.. automodule:: fonts.sans24
|
||||
:members:
|
||||
:undoc-members:
|
||||
|
||||
.. automodule:: logo
|
||||
:members:
|
||||
|
|
19
hello.py
Normal file
19
hello.py
Normal file
|
@ -0,0 +1,19 @@
|
|||
# SPDX-License-Identifier: MY-LICENSE
|
||||
# Copyright (C) YEAR(S), AUTHOR
|
||||
|
||||
import wasp
|
||||
|
||||
class HelloApp():
|
||||
"""A hello world application for wasp-os."""
|
||||
NAME = "Hello"
|
||||
|
||||
def __init__(self, msg="Hello, world!"):
|
||||
self.msg = msg
|
||||
|
||||
def foreground(self):
|
||||
self._draw()
|
||||
|
||||
def _draw(self):
|
||||
draw = wasp.watch.drawable
|
||||
draw.fill()
|
||||
draw.string(self.msg, 0, 108, width=240)
|
19
wasp/apps/hello.py
Normal file
19
wasp/apps/hello.py
Normal file
|
@ -0,0 +1,19 @@
|
|||
# SPDX-License-Identifier: MY-LICENSE
|
||||
# Copyright (C) YEAR(S), AUTHOR
|
||||
|
||||
import wasp
|
||||
|
||||
class HelloApp():
|
||||
"""A hello world application for wasp-os."""
|
||||
NAME = "Hello"
|
||||
|
||||
def __init__(self, msg="Hello, world!"):
|
||||
self.msg = msg
|
||||
|
||||
def foreground(self):
|
||||
self._draw()
|
||||
|
||||
def _draw(self):
|
||||
draw = wasp.watch.drawable
|
||||
draw.fill()
|
||||
draw.string(self.msg, 0, 108, width=240)
|
|
@ -1,13 +1,40 @@
|
|||
# SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
# Copyright (C) 2020 Daniel Thompson
|
||||
"""Template application implementing all application method calls.
|
||||
"""The complete set of wasp-os application entry points are documented
|
||||
below as part of a template application. Note that the template does
|
||||
not rely on any specific parent class. This is because applications in
|
||||
wasp-os can rely on *duck typing* making a class hierarchy pointless.
|
||||
"""
|
||||
|
||||
import wasp
|
||||
import icons
|
||||
|
||||
class TemplateApp():
|
||||
"""Template application ready to use as a basis for new applications.
|
||||
"""Template application.
|
||||
|
||||
The template application includes every application entry point. It
|
||||
is used as a reference guide and can also be used as a template for
|
||||
creating new applications.
|
||||
|
||||
.. data:: NAME = 'Template'
|
||||
|
||||
Applications must provide a short ``NAME`` that is used by the
|
||||
launcher to describe the application. Names that are longer than
|
||||
8 characters are likely to be abridged by the launcher in order
|
||||
to fit on the screen.
|
||||
|
||||
.. data:: ICON = RLE2DATA
|
||||
|
||||
Applications can optionally provide an icon for display by the
|
||||
launcher. Applications that expect to be installed on the quick
|
||||
ring will not be listed by the launcher and need not provide any
|
||||
icon. When no icon is provided the system will use a default
|
||||
icon.
|
||||
|
||||
The icon is an opportunity to differentiate your application from others
|
||||
so supplying an icon is strongly recommended. The icon, when provided,
|
||||
must not be larger than 96x64.
|
||||
|
||||
"""
|
||||
NAME = 'Template'
|
||||
ICON = icons.app
|
||||
|
|
Loading…
Add table
Reference in a new issue