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
register(app, quick_ring=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.
sleep()

Enter the deepest sleep state possible.

switch(app)

Switch to the requested application.

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
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)
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)

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)
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

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.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.NotificationBar(x=2, y=8)

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=14847)

A slider to select values.

draw()

Draw the slider.

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

nRF-family RTC driver

class drivers.nrf_rtc.RTC(counter)

Real Time Clock based on the nRF-family low power counter.

__init__(counter)

Wrap an RTCounter to provide a fully fledged Real Time Clock.

If the PNVRAM is valid then we use it to initialize the RTC otherwise we just make something up.

Parameters:counter (RTCounter) – The RTCCounter channel to adopt.
get_localtime()

Get the current time and date.

Returns:Wall time formatted as (yyyy, mm, dd, HH, MM, SS, wday, yday)
get_time()

Get the current time.

Returns:Wall time formatted as (HH, MM, SS)
get_uptime_ms()

Return the current uptime in milliseconds.

set_localtime(t)

Set the current wall time.

Parameters:t (sequence) – Wall time formatted as (yyyy, mm, dd, HH, MM, SS), any additional elements in sequence will be ignored.
time()

Get time in the same format as time.time

update()

Check for counter updates.

Returns:True of the wall time has changed, False otherwise.
uptime

Provide the current uptime in seconds.

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=0, y=0, width=None, height=None)

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 (bytearray) – 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.

Applications

Digital clock

Shows a time (as HH:MM) together with a battery meter and the date.

class apps.clock.ClockApp

Simple digital clock application.

_images/ClockApp.png

Screenshot of the clock application

ICON = 'Default digital clock icon'
NAME = 'Clock'
foreground()

Activate the application.

Configure the status bar, redraw the display and request a periodic tick callback every second.

sleep()

Prepare to enter the low power mode.

Returns:True, which tells the system manager not to automatically switch to the default application before sleeping.
tick(ticks)

Periodic callback to update the display.

wake()

Return from low power mode.

Time will have changes whilst we have been asleep so we must udpate the display (but there is no need for a full redraw because the display RAM is preserved during a sleep.

Flashlight

Shows a pure white screen with the backlight set to maximum.

class apps.flashlight.FlashlightApp

Trivial flashlight application.

_images/TorchApp.png

Screenshot of the flashlight application

ICON = 'Default torch or flashlight icon'
NAME = 'Torch'
background()

De-activate the application (without losing state).

draw()

Redraw the display from scratch.

foreground()

Activate the application.

tick(ticks)

Application launcher

class apps.launcher.LauncherApp

An application launcher application.

_images/LauncherApp.png

Screenshot of the application launcher

ICON = 'Default application icon'
NAME = 'Launcher'
foreground()

Activate the application.

swipe(event)
touch(event)

Pager applications

The pager is used to present text based information to the user. It is primarily intended for notifications but is also used to provide debugging information when applications crash.

class apps.pager.CrashApp(exc)

Crash handler application.

This application is launched automatically whenever another application crashes. Our main job it to indicate as loudly as possible that the system is no longer running correctly. This app deliberately enables inverted video mode in order to deliver that message as strongly as possible.

background()

Restore a normal display mode.

Conceal the display before the transition otherwise the inverted bombs get noticed by the user.

foreground()

Indicate the system has crashed by drawing a couple of bomb icons.

If you owned an Atari ST back in the mid-eighties then I hope you recognise this as a tribute a long forgotten home computer!

swipe(event)

Show the exception message in a pager.

class apps.pager.NotificationApp
NAME = 'Notifications'
foreground()

Activate the application.

class apps.pager.PagerApp(msg)

Show a long text message in a pager.

ICON = 'Default application icon'
NAME = 'Pager'
background()

De-activate the application.

foreground()

Activate the application.

swipe(event)

Swipe to page up/down.

Self Tests

class apps.testapp.TestApp

Simple test application.

_images/SelfTestApp.png

Screenshot of the self test application

ICON = 'Default application icon'
NAME = 'Self Test'
foreground()

Activate the application.

press(button, state)
swipe(event)
touch(event)

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.