Wasp-os Reference Manual

System

Wasp-os system manager

wasp.system

wasp.system is the system-wide singleton instance of Manager. Application must use this instance to access the system services provided by the manager.

wasp.watch

wasp.watch is an import of watch and is simply provided as a shortcut (and to reduce memory by keeping it out of other namespaces).

class wasp.EventMask

Enumerated event masks.

class wasp.EventType

Enumerated interface actions.

MicroPython does not implement the enum module so EventType is simply a regular object which acts as a namespace.

class wasp.Manager

Wasp-os system manager

The manager is responsible for handling top-level UI events and dispatching them to the foreground application. It also provides services to the application.

The manager is expected to have a single system-wide instance which can be accessed via wasp.system .

brightness

Cached copy of the brightness current written to the hardware.

cancel_alarm(time, action)

Unqueue an alarm.

keep_awake()

Reset the keep awake timer.

navigate(direction=None)

Navigate to a new application.

Left/right navigation is used to switch between applications in the quick application ring. Applications on the ring are not permitted to subscribe to :py:data`EventMask.SWIPE_LEFTRIGHT` events.

Swipe up is used to bring up the launcher. Clock applications are not permitted to subscribe to :py:data`EventMask.SWIPE_UPDOWN` events since they should expect to be the default application (and is important that we can trigger the launcher from the default application).

Parameters:direction (int) – The direction of the navigation
notify_duration

Cached copy of the current vibrator pulse duration in milliseconds

notify_level

Cached copy of the current notify level

register(app, quick_ring=False, watch_face=False)

Register an application with the system.

Parameters:app (object) – The application to regsister
request_event(event_mask)

Subscribe to events.

Parameters:event_mask (int) – The set of events to subscribe to.
request_tick(period_ms=None)

Request (and subscribe to) a periodic tick event.

Note: With the current simplistic timer implementation sub-second tick intervals are not possible.

run(no_except=True)

Run the system manager synchronously.

This allows all watch management activities to handle in the normal execution context meaning any exceptions and other problems can be observed interactively via the console.

schedule(enable=True)

Run the system manager synchronously.

set_alarm(time, action)

Queue an alarm.

Parameters:
  • time (int) – Time to trigger the alarm (use time.mktime)
  • action (function) – Action to perform when the alarm expires.
set_theme(new_theme) → bool

Sets the system theme.

Accepts anything that supports indexing, and has a len() equivalent to the default theme.

sleep()

Enter the deepest sleep state possible.

switch(app)

Switch to the requested application.

theme(theme_part: str) → int

Returns the relevant part of theme. For more see ../tools/themer.py

wake()

Return to a running state.

class wasp.PinHandler(pin)

Pin (and Signal) event generator.

TODO: Currently this driver doesn’t actually implement any debounce but it will!

get_event()

Receive a pin change event.

Check for a pending pin change event and, if an event is pending, return it.

Returns:boolean of the pin state if an event is received, None otherwise.

Watch driver instances

watch.backlight

Backlight driver, typically a board specific driver with a single set() method.

watch.battery

Battery driver, typically the generic metering driver, drivers.battery.Battery.

watch.button

An instance of machine.Pin (or a signal) that an application can use to poll the state of the hardware button.

watch.display

Display driver, typically drivers.st7789.ST7789_SPI.

watch.drawable

Drawing library for watch.display. It will be adapted to match the bit depth of the display, draw565.Draw565 for example.

watch.rtc

RTC driver, typically drivers.nrf_rtc.RTC.

watch.touch

Touchscreen driver, for example drivers.cst816s.CST816S.

watch.vibrator

Vibration motor driver, typically drivers.vibrator.Vibrator.

RGB565 drawing library

class draw565.Draw565(display)

Drawing library for RGB565 displays.

A full framebufer is not required although the library will ‘borrow’ a line buffer from the underlying display driver.

__init__(display)

Initialise the library.

Defaults to white-on-black for monochrome drawing operations and 24pt Sans Serif text.

blit(image, x, y, fg=65535, c1=19049, c2=31727)

Decode and draw an encoded image.

Parameters:
  • image – Image data in either 1-bit RLE or 2-bit RLE formats. The format will be autodetected
  • x – X coordinate for the left-most pixels in the image
  • y – Y coordinate for the top-most pixels in the image
bounding_box(s)

Return the bounding box of a string.

Parameters:s – A string
Returns:Tuple of (width, height)
darken(color, step=1)

Get a darker shade from the same palette.

The approach is somewhat unsophisticated. It is essentially just a desaturating subtract for each of the RGB fields.

Parameters:color – Shade to darken
Returns:New colour
fill(bg=None, x=0, y=0, w=None, h=None)

Draw a solid colour rectangle.

If no arguments a provided the whole display will be filled with the background colour (typically black).

Parameters:
  • bg – Background colour (in RGB565 format)
  • x – X coordinate of the left-most pixels of the rectangle
  • y – Y coordinate of the top-most pixels of the rectangle
  • w – Width of the rectangle, defaults to None (which means select the right-most pixel of the display)
  • h – Height of the rectangle, defaults to None (which means select the bottom-most pixel of the display)
lighten(color, step=1)

Get a lighter shade from the same palette.

The approach is somewhat unsophisticated. It is essentially just a saturating add for each of the RGB fields.

Parameters:color – Shade to lighten
Returns:New colour
line(x0, y0, x1, y1, width=1, color=None)

Draw a line between points (x0, y0) and (x1, y1).

Example:

draw = wasp.watch.drawable
draw.line(0, 120, 240, 240, 0xf800)
Parameters:
  • x0 – X coordinate of the start of the line
  • y0 – Y coordinate of the start of the line
  • x1 – X coordinate of the end of the line
  • y1 – Y coordinate of the end of the line
  • width – Width of the line in pixels
  • color – Colour to draw line, defaults to the foreground colour
polar(x, y, theta, r0, r1, width=1, color=None)

Draw a line using polar coordinates.

The coordinate system is tuned for clock applications so it adopts navigational conventions rather than mathematical ones. Specifically the reference direction is drawn vertically upwards and the angle is measures clockwise in degrees.

Example:

draw = wasp.watch.drawable
draw.line(360 / 12, 16, 64)
Parameters:
  • theta – Angle, in degrees
  • r0 – Radius of the start of the line
  • y0 – Radius of the end of the line
  • x – X coordinate of the origin
  • y – Y coordinate of the origin
  • width – Width of the line in pixels
  • color – Colour to draw line in, defaults to the foreground colour
reset()

Restore the default colours and font.

Default colours are white-on-block (white foreground, black background) and the default font is 24pt Sans Serif.

rleblit(image, pos=(0, 0), fg=65535, bg=0)

Decode and draw a 1-bit RLE image.

Deprecated since version M2: Use blit() instead.

set_color(color, bg=0)

Set the foreground and background colours.

The supplied colour will be used for all monochrome drawing operations. If no background colour is provided then the background will be set to black.

Parameters:
  • color – Foreground colour
  • bg – Background colour, defaults to black
set_font(font)

Set the font used for rendering text.

Parameters:font – A font module generated using font_to_py.py.
string(s, x, y, width=None, right=False)

Draw a string at the supplied position.

Parameters:
  • s – String to render
  • x – X coordinate for the left-most pixels in the image
  • y – Y coordinate for the top-most pixels in the image
  • width – If no width is provided then the text will be left justified, otherwise the text will be centred within the provided width and, importantly, the remaining width will be filled with the background colour (to ensure that if we update one string with a narrower one there is no need to “undraw” it)
  • right – If True (and width is set) then right justify rather than centre the text
wrap(s, width)

Chunk a string so it can rendered within a specified width.

Example:

draw = wasp.watch.drawable
chunks = draw.wrap(long_string, 240)

# line(1) will provide the first line
# line(len(chunks)-1) will provide the last line
def line(n):
    return long_string[chunks[n-1]:chunks[n]]
Parameters:
  • s – String to be chunked
  • width – Width to wrap the text into
Returns:

List of chunk boundaries

Step logger

Capture and record data from the step counter

class steplogger.StepIterator(fname, data=None)
close()
class steplogger.StepLogger(manager)
data(t)

Widget library

The widget library allows common fragments of logic and drawing code to be shared between applications.

class widgets.BatteryMeter

Battery meter widget.

A simple battery meter with a charging indicator, will draw at the top-right of the display.

draw()

Draw from meter (from scratch).

update()

Update the meter.

The update is lazy and won’t redraw unless the level has changed.

class widgets.Button(x, y, w, h, label)

A button with a text label.

draw()

Draw the button.

touch(event)

Handle touch events.

class widgets.Checkbox(x, y, label=None)

A simple (labelled) checkbox.

draw()

Draw the checkbox and label.

touch(event)

Handle touch events.

update()

Draw the checkbox.

class widgets.Clock(enabled=True)

Small clock widget.

draw()

Redraw the clock from scratch.

The container is required to clear the canvas prior to the redraw and the clock is only drawn if it is enabled.

update()

Update the clock widget if needed.

This is a lazy update that only redraws if the time has changes since the last call and the clock is enabled.

Returns:An time tuple if the time has changed since the last call, None otherwise.
class widgets.ConfirmationView

Confirmation widget allowing user confirmation of a setting.

class widgets.GfxButton(x, y, gfx)

A button with a graphical icon.

draw()

Draw the button.

class widgets.NotificationBar(x=0, y=0)

Show BT status and if there are pending notifications.

draw()

Redraw the notification widget.

For this simple widget draw() is simply a synonym for update() because we unconditionally update from scratch.

update()

Update the widget.

This widget does not implement lazy redraw internally since this can often be implemented (with less state) by the container.

class widgets.ScrollIndicator(x=222, y=216)

Scrolling indicator.

A pair of arrows that prompted the user to swipe up/down to access additional pages of information.

draw()

Draw from scrolling indicator.

For this simple widget draw() is simply a synonym for update().

update()

Update from scrolling indicator.

class widgets.Slider(steps, x=10, y=90, color=None)

A slider to select values.

draw()

Draw the slider.

class widgets.Spinner(x, y, mn, mx, field=1)

A simple Spinner widget.

In order to have large enough hit boxes the spinner is a fairly large widget and requires 60x120 px.

draw()

Draw the spinner.

update()

Update the spinner value.

class widgets.StatusBar

Combo widget to handle notification, time and battery level.

clock

True if the clock should be included in the status bar, False otherwise.

draw()

Redraw the status bar from scratch.

update()

Lazily update the status bar.

Returns:An time tuple if the time has changed since the last call, None otherwise.

Device drivers

Generic lithium ion battery driver

class drivers.battery.Battery(battery, charging, power=None)

Generic lithium ion battery driver.

__init__(battery, charging, power=None)

Specify the pins used to provide battery status.

Parameters:
  • battery (Pin) – The ADC-capable pin that can be used to measure battery voltage.
  • charging (Pin) – A pin (or Signal) that reports the charger status.
  • power (Pin) – A pin (or Signal) that reports whether the device has external power, defaults to None (which means use the charging pin for power reporting too).
charging()

Get the charging state of the battery.

Returns:True if the battery is charging, False otherwise.
level()

Estimate the battery level.

The current the estimation approach is extremely simple. It is assumes the discharge from 4v to 3.5v is roughly linear and 4v is 100% and that 3.5v is 5%. Below 3.5v the voltage will start to drop pretty sharply to we will drop from 5% to 0% pretty fast… but we’ll live with that for now.

Returns:Estimate battery level in percent.
power()

Check whether the device has external power.

Returns:True if the device has an external power source, False otherwise.
voltage_mv()

Read the battery voltage.

Assumes a 50/50 voltage divider and a 3.3v power supply

Returns:Battery voltage, in millivolts.

Hynitron CST816S touch contoller driver

class drivers.cst816s.CST816S(bus, intr, rst, schedule=None)

Hynitron CST816S I2C touch controller driver.

__init__(bus, intr, rst, schedule=None)

Specify the bus used by the touch controller.

Parameters:bus (machine.I2C) – I2C bus for the CST816S.
get_event()

Receive a touch event.

Check for a pending touch event and, if an event is pending, prepare it ready to go in the event queue.

Returns:An event record if an event is received, None otherwise.
get_touch_data(pin_obj)

Receive a touch event by interrupt.

Check for a pending touch event and, if an event is pending, prepare it ready to go in the event queue.

reset_touch_data()

Reset touch data.

Reset touch data, call this function after processing an event.

sleep()

Put touch controller chip on sleep mode to save power.

wake()

Wake up touch controller chip.

Just reset the chip in order to wake it up

Inverting pin wrapper

class drivers.signal.Signal(pin, invert=False)

Simplified Signal class

Note

The normal C implementation of the Signal class used by MicroPython doesn’t work on the nRF family. This class provides a temporary workaround until that can be addressed.

__init__(pin, invert=False)

Create a Signal object by wrapping a pin.

off()

Deactivate the signal.

on()

Activate the signal.

value(v=None)

Get or set the state of the signal.

Parameters:v – Value to set, defaults to None (which means get the signal state instead.
Returns:The state of the signal if v is None, otherwise None.

Sitronix ST7789 display driver

Note

Although the ST7789 supports a variety of communication protocols currently this driver only has support for SPI interfaces. However it is structured such that other serial protocols can easily be added.

class drivers.st7789.ST7789(width, height)

Sitronix ST7789 display driver

__init__(width, height)

Configure the size of the display.

Parameters:
  • width (int) – Display width, in pixels
  • height (int) – Display height in pixels
fill(bg, x=0, y=0, w=None, h=None)

Draw a solid colour rectangle.

If no arguments a provided the whole display will be filled with the background colour (typically black).

Parameters:
  • bg – Background colour (in RGB565 format)
  • x – X coordinate of the left-most pixels of the rectangle
  • y – Y coordinate of the top-most pixels of the rectangle
  • w – Width of the rectangle, defaults to None (which means select the right-most pixel of the display)
  • h – Height of the rectangle, defaults to None (which means select the bottom-most pixel of the display)
init_display()

Reset and initialize the display.

invert(invert)

Invert the display.

Parameters:invert (bool) – True to invert the display, False for normal mode.
mute(mute)

Mute the display.

When muted the display will be entirely black.

Parameters:mute (bool) – True to mute the display, False for normal mode.
poweroff()

Put the display into sleep mode.

poweron()

Wake the display and leave sleep mode.

rawblit(buf, x, y, width, height)

Blit raw pixels to the display.

Parameters:
  • buf – Pixel buffer
  • x – X coordinate of the left-most pixels of the rectangle
  • y – Y coordinate of the top-most pixels of the rectangle
  • w – Width of the rectangle, defaults to None (which means select the right-most pixel of the display)
  • h – Height of the rectangle, defaults to None (which means select the bottom-most pixel of the display)
set_window(x, y, width, height)

Set the clipping rectangle.

All writes to the display will be wrapped at the edges of the rectangle.

Parameters:
  • x – X coordinate of the left-most pixels of the rectangle
  • y – Y coordinate of the top-most pixels of the rectangle
  • w – Width of the rectangle, defaults to None (which means select the right-most pixel of the display)
  • h – Height of the rectangle, defaults to None (which means select the bottom-most pixel of the display)
class drivers.st7789.ST7789_SPI(width, height, spi, cs, dc, res=None, rate=8000000)
quick_write(buf)

Send data to the display as part of an optimized write sequence.

Parameters:buf (bytes-like) – Data, must be in a form that can be directly consumed by the SPI bus.
quick_end()

Complete an optimized write sequence.

quick_start()

Prepare for an optimized write sequence.

Optimized write sequences allow applications to produce data in chunks without having any overhead managing the chip select.

reset()

Reset the display.

Uses the hardware reset pin if there is one, otherwise it will issue a software reset command.

write_cmd(cmd)

Send a command opcode to the display.

Parameters:cmd (sequence) – Command, will be automatically converted so it can be issued to the SPI bus.
write_data(buf)

Send data to the display.

Parameters:buf (bytearray) – Data, must be in a form that can be directly consumed by the SPI bus.

Generic PWM capable vibration motor driver

class drivers.vibrator.Vibrator(pin, active_low=False)

Vibration motor driver.

__init__(pin, active_low=False)

Specify the pin and configuration used to operate the motor.

Parameters:
  • pin (machine.Pin) – The PWM-capable pin used to driver the vibration motor.
  • active_low (bool) – Invert the resting state of the motor.
pulse(duty=25, ms=40)

Briefly pulse the motor.

Parameters:
  • duty (int) – Duty cycle, in percent.
  • ms (int) – Duration, in milliseconds.

Bootloader

The bootloader implements a couple of protocols that allow the bootloader and payload to communicate during a reset or on handover from bootloader to application.

GPREGRET protocol

GPREGRET is a general purpose 8-bit retention register that is preserved in all power states of the nRF52 (including System OFF mode when SRAM content is destroyed).

It can be used by the application to request specific bootloader behaviours during a reset:

Name Value Description
OTA_APPJUM 0xb1 Bootloader entered (without reset) from application.
OTA_RESET 0xa8 Enter OTA (Bluetooth) recovery mode
SERIAL_ONLY_RESET 0x4e Enter UART recovery mode (if applicable)
UF2_RESET 0x57 Enter USB recovery mode (if applicable)
FORCE_APP_BOOT 0x65 Force direct application boot (no splash screen)

PNVRAM protocol

The pseudo non-volatile RAM is a small block of regular static RAM that, once initialized, can be used to share information.

The PNVRAM starts at 0x200039c0 and is 32 bytes long.

Address Description
0x200039c0 Guard value. Must be set to 0x1abe11ed .
0x200039c4 Course grained RTC value (bootloader must preserve but can ignore).
0x200039c8 RTC millisecond counter (bootloader must increment this).
0x200039cc Reserved
0x200039d0 Reserved
0x200039d4 Reserved
0x200039d8 Reserved
0x200039cc Guard value. Must be set to 0x10adab1e .

Note: The PNVRAM protocol allows up to 28 bytes to be transfered (compared to 2 bytes via GPREGRET and GPREGRET2) but it is less versatile. For example FORCE_APP_BOOT cannot be implmented using PNVRAM.

The RTC millisecond counter is incremented whenever the bootloader is active (during splash screen or early UART recovery mode, during an update). It can be consumed by the application to prevent the current time being lost during an update.

Watchdog protocol

Form-factor devices such as smart watches and fitness trackers do not usually have any hardware mechanism to allow the user to force a failed device into bootloader mode. This makes them difficult to develop on because opening the case to access a SWD or reset pins may compromise their waterproofing.

wasp-os uses a watchdog timer (WDT) combined with a single hardware button in order to provide a robust mechanism to allow the user to force entry into a over-the-air firmware recovery mode that allows the buggy application to be replaced.

The software responsibilities to implement this are split between the bootloader and the application, although the application responsibilities are intentionally minimal.

The bootloader implements an over-the-air recovery mode, as well as handling normal boot, where it’s role is to display the splash screen.

Additionally the bootloader implements several watchdog related features necessary for robust reboot handling:

  1. The bootloader configures the watchdog prior to booting the main application. This is a simple, single channel reload request, watchdog with a 5 second timeout.
  2. The bootloader checks the reset reason prior too booting the main application. If it detects a watchdog reset the bootloader switches automatically to DFU mode.
  3. The bootlaoder initialized the pinmux allowing the hardware button state to be observed.
  4. The bootloader monitors the hardware button and switches back to the main application when it is pressed.

From this list #1 and #2 are needed to ensure robust WDT handling whilst #3 and # 4 ensure the user can switch back to application from the device itself if they ever accidentally trigger entry to recovery mode.

The application’s role is to carefully pet the watchdog so that it will trigger automatically if the hardware button is held down for five seconds. Key points for application robustness include:

  1. Unlike a normal watchdog we can be fairly reckless about where in the code we pet the dog. For example petting the dog from a timer interrupt is fine because we only need the dog to bark if the hardware button is pressed.
  2. The routine to pet the dog is predicated on the hardware button not being pressed.
  3. The routine to pet the dog is also predicated on the hardware button still being correctly configured.

To avoid mistakes the application should contain no subroutines that unconditionally pet the dog; they should all implement #2 and #3 from the above list.

Note: nRF52 microcontrollers implement a distributed pin-muxing mechanism meaning most peripheral can acidentally “steal” a pin if the pin is requested by the peripheral. This requires a fully robust implementation of #3 to visit the PSEL registers of every peripheral that can control pins. The code currently used in wasp-os does not yet meet this criteria.