summaryrefslogtreecommitdiff
path: root/wasp
diff options
context:
space:
mode:
Diffstat (limited to 'wasp')
-rw-r--r--wasp/apps/clock.py47
-rw-r--r--wasp/apps/flashlight.py35
-rw-r--r--wasp/apps/launcher.py80
-rw-r--r--wasp/apps/testapp.py173
-rw-r--r--wasp/boards/dsd6/manifest.py3
-rw-r--r--wasp/boards/dsd6/watch.py3
-rw-r--r--wasp/boards/nitrogen/manifest.py3
-rw-r--r--wasp/boards/nitrogen/watch.py3
-rw-r--r--wasp/boards/pinetime/manifest.py8
-rw-r--r--wasp/boards/pinetime/watch.py3
-rw-r--r--wasp/boards/simulator/display.py33
-rw-r--r--wasp/boards/simulator/machine.py3
-rw-r--r--wasp/boards/simulator/micropython.py3
-rw-r--r--wasp/boards/simulator/watch.py3
-rw-r--r--wasp/boards/sphinx/machine.py3
-rw-r--r--wasp/boards/sphinx/watch.py3
-rw-r--r--wasp/boot.py3
-rw-r--r--wasp/demo.py3
-rw-r--r--wasp/draw565.py65
-rw-r--r--wasp/drivers/battery.py3
-rw-r--r--wasp/drivers/cst816s.py3
-rw-r--r--wasp/drivers/nrf_rtc.py3
-rw-r--r--wasp/drivers/signal.py3
-rw-r--r--wasp/drivers/st7789.py3
-rw-r--r--wasp/drivers/vibrator.py5
-rw-r--r--wasp/icons.py17
-rw-r--r--wasp/logo.py3
-rw-r--r--wasp/main.py8
-rw-r--r--wasp/manager.py150
-rw-r--r--wasp/shell.py3
-rw-r--r--wasp/wasp.py321
-rw-r--r--wasp/widgets.py21
32 files changed, 771 insertions, 249 deletions
diff --git a/wasp/apps/clock.py b/wasp/apps/clock.py
index f99091a..4236d9a 100644
--- a/wasp/apps/clock.py
+++ b/wasp/apps/clock.py
@@ -1,7 +1,10 @@
+# SPDX-License-Identifier: LGPL-3.0-or-later
+# Copyright (C) 2020 Daniel Thompson
+
+import wasp
+
+import icons
import fonts.clock as digits
-import watch
-import widgets
-import manager
DIGITS = (
digits.clock_0,
@@ -18,35 +21,22 @@ DIGITS = (
MONTH = 'JanFebMarAprMayJunJulAugSepOctNovDec'
-class ClockApp(object):
+class ClockApp():
"""Simple digital clock application.
Shows a time (as HH:MM) together with a battery meter and the date.
"""
+ NAME = 'Clock'
+ ICON = icons.clock
def __init__(self):
- self.meter = widgets.BatteryMeter()
-
- def handle_event(self, event_view):
- """Process events that the app is subscribed to."""
- if event_view[0] == manager.EVENT_TICK:
- self.update()
- else:
- # TODO: Raise an unexpected event exception
- pass
+ self.meter = wasp.widgets.BatteryMeter()
- def foreground(self, manager, effect=None):
+ def foreground(self):
"""Activate the application."""
self.on_screen = ( -1, -1, -1, -1, -1, -1 )
- self.draw(effect)
- manager.request_tick(1000)
-
- def tick(self, ticks):
- self.update()
-
- def background(self):
- """De-activate the application (without losing state)."""
- pass
+ self.draw()
+ wasp.system.request_tick(1000)
def sleep(self):
return True
@@ -54,9 +44,12 @@ class ClockApp(object):
def wake(self):
self.update()
- def draw(self, effect=None):
+ def tick(self, ticks):
+ self.update()
+
+ def draw(self):
"""Redraw the display from scratch."""
- draw = watch.drawable
+ draw = wasp.watch.drawable
draw.fill()
draw.rleblit(digits.clock_colon, pos=(2*48, 80), fg=0xb5b6)
@@ -70,14 +63,14 @@ class ClockApp(object):
The updates are a lazy as possible and rely on an prior call to
draw() to ensure the screen is suitably prepared.
"""
- now = watch.rtc.get_localtime()
+ now = wasp.watch.rtc.get_localtime()
if now[3] == self.on_screen[3] and now[4] == self.on_screen[4]:
if now[5] != self.on_screen[5]:
self.meter.update()
self.on_screen = now
return False
- draw = watch.drawable
+ draw = wasp.watch.drawable
draw.rleblit(DIGITS[now[4] % 10], pos=(4*48, 80))
draw.rleblit(DIGITS[now[4] // 10], pos=(3*48, 80), fg=0xbdb6)
draw.rleblit(DIGITS[now[3] % 10], pos=(1*48, 80))
diff --git a/wasp/apps/flashlight.py b/wasp/apps/flashlight.py
index f1f9418..c4702a0 100644
--- a/wasp/apps/flashlight.py
+++ b/wasp/apps/flashlight.py
@@ -1,32 +1,33 @@
-import watch
-import manager
+# SPDX-License-Identifier: LGPL-3.0-or-later
+# Copyright (C) 2020 Daniel Thompson
+
+import wasp
+
+import icons
class FlashlightApp(object):
"""Trivial flashlight application.
Shows a pure white screen with the backlight set to maximum.
"""
+ NAME = 'Torch'
+ ICON = icons.torch
- def __init__(self):
- self.backlight = None
-
- def foreground(self, manager, effect=None):
+ def foreground(self):
"""Activate the application."""
- self.on_screen = ( -1, -1, -1, -1, -1, -1 )
- self.draw(effect)
- manager.request_tick(1000)
+ self.draw()
+ wasp.system.request_tick(1000)
+
+ self._brightness = wasp.system.brightness
+ wasp.system.brightness = 3
def background(self):
"""De-activate the application (without losing state)."""
- pass
-
- def sleep(self):
- return False
+ wasp.system.brightness = self._brightness
def tick(self, ticks):
- pass
+ wasp.system.keep_awake()
- def draw(self, effect=None):
+ def draw(self):
"""Redraw the display from scratch."""
- display = watch.display
- display.fill(0xffff)
+ wasp.watch.display.fill(0xffff)
diff --git a/wasp/apps/launcher.py b/wasp/apps/launcher.py
new file mode 100644
index 0000000..274ea9c
--- /dev/null
+++ b/wasp/apps/launcher.py
@@ -0,0 +1,80 @@
+# SPDX-License-Identifier: LGPL-3.0-or-later
+# Copyright (C) 2020 Daniel Thompson
+
+import wasp
+import icons
+
+class LauncherApp():
+ """An application launcher application.
+ """
+ NAME = 'Launcher'
+ ICON = icons.app
+
+ def foreground(self):
+ """Activate the application."""
+ self._page = 0
+ self._draw()
+ wasp.system.request_event(wasp.EventMask.TOUCH |
+ wasp.EventMask.SWIPE_UPDOWN)
+
+ def swipe(self, event):
+ i = self._page
+ n = self._num_pages
+ if event[0] == wasp.EventType.UP:
+ i += 1
+ if i >= n:
+ i -= 1
+ wasp.watch.vibrator.pulse()
+ return
+ else:
+ i -= 1
+ if i < 0:
+ wasp.system.switch(wasp.system.applications[0])
+ return
+
+ self._page = i
+ wasp.watch.display.mute(True)
+ self._draw()
+ wasp.watch.display.mute(False)
+
+ def touch(self, event):
+ page = self._get_page(self._page)
+ x = event[1]
+ y = event[2]
+ app = page[2 * (y // 120) + (x // 120)]
+ if app:
+ wasp.system.switch(app)
+ else:
+ wasp.watch.vibrator.pulse()
+
+ @property
+ def _num_pages(self):
+ """Work out what the highest possible pages it."""
+ num_apps = len(wasp.system.applications)
+ return (num_apps + 3) // 4
+
+ def _get_page(self, i):
+ apps = wasp.system.applications
+ page = apps[4*i: 4*(i+1)]
+ while len(page) < 4:
+ page.append(None)
+ return page
+
+ def _draw(self):
+ """Redraw the display from scratch."""
+ def draw_app(app, x, y):
+ if not app:
+ return
+ draw.set_color(0xffff)
+ draw.rleblit(app.ICON, (x+13, y+12))
+ draw.set_color(0xbdb6)
+ draw.string(app.NAME, x, y+120-30, 120)
+
+ draw = wasp.watch.drawable
+ page = self._get_page(self._page)
+
+ draw.fill()
+ draw_app(page[0], 0, 0)
+ draw_app(page[1], 120, 0)
+ draw_app(page[2], 0, 120)
+ draw_app(page[3], 120, 120)
diff --git a/wasp/apps/testapp.py b/wasp/apps/testapp.py
index 9f512f1..eece822 100644
--- a/wasp/apps/testapp.py
+++ b/wasp/apps/testapp.py
@@ -1,62 +1,157 @@
-import watch
-import widgets
-import manager
+# SPDX-License-Identifier: LGPL-3.0-or-later
+# Copyright (C) 2020 Daniel Thompson
+
import machine
+import wasp
+import icons
class TestApp():
"""Simple test application.
"""
+ NAME = 'Self Test'
+ ICON = icons.app
+
+ # 2-bit RLE, generated from res/app_icon.png, 457 bytes
+ RLE_2BIT = (
+ 96, 64,
+ b'\x1e@md<d<d;f?X\xec2\xf0/'
+ b'\xf2-\xf4,\xc3.\xc3,\xc3.\xc3,\xc3.\xc3,'
+ b'\xc3.\xc3,\xc3.\xc3,\xc3\x0c\x80\xfc\x83\x10\xc0]'
+ b'\xc3\x0c@\xffC,C\n\x87\x0c\xc7\nC,C\t'
+ b'\x83\x02\x84\n\xc4\x02\xc3\tC,C\x08\x82\x07\x82\x08'
+ b'\xc2\x07\xc2\x08C,C\x07\x82\t\x82\x06\xc2\t\xc2\x07'
+ b'C,C\x06\x82\x0b\x82\x04\xc2\x0b\xc2\x06C,C\x06'
+ b'\x82\x0b\x82\x04\xc2\x0b\xc2\x06C,C\x05\x82\x0c\x82\x04'
+ b'\xc2\x0c\xc2\x05C,C\x05\x82\x0c\x82\x04\xc2\x0c\xc2\x05'
+ b'C,C\x05\x83\x0b\x82\x04\xc2\x0b\xc3\x05C,C\x06'
+ b'\x82\x0b\x82\x04\xc2\x0b\xc2\x06C,C\x06\x82\x0b\x82\x04'
+ b'\xc2\x0b\xc1\x07C,C\x07\x82\n\x82\x04\xc2\n\xc2\x07'
+ b'C+D\x08\x82\t\x82\x04\xc2\t\xc2\x08C*E\t'
+ b'\x8c\x04\xcc\tC*E\n\x8b\x04\xcb\nC*E.'
+ b'C*E.C*E.C*E.C*E\n'
+ b'\x80\xe9\x8b\x04\xc0o\xcb\nC+D\t\x8c\x04\xcc\t'
+ b'C,C\x08\x82\t\x82\x04\xc2\t\xc2\x08C,C\x07'
+ b'\x82\n\x82\x04\xc2\n\xc2\x07C,C\x06\x82\x0b\x82\x04'
+ b'\xc2\x0b\xc1\x07C,C\x06\x82\x0b\x82\x04\xc2\x0b\xc2\x06'
+ b'C,C\x05\x83\x0b\x82\x04\xc2\x0b\xc3\x05C,C\x05'
+ b'\x82\x0c\x82\x04\xc2\x0c\xc2\x05C,C\x05\x82\x0c\x82\x04'
+ b'\xc2\x0c\xc2\x05C,C\x06\x82\x0b\x82\x04\xc2\x0b\xc2\x06'
+ b'C,C\x06\x82\x0b\x82\x04\xc2\x0b\xc2\x06C,C\x07'
+ b'\x82\t\x82\x06\xc2\t\xc2\x07C,C\x08\x82\x07\x82\x08'
+ b'\xc2\x07\xc2\x08C,C\t\x83\x02\x84\n\xc4\x02\xc3\t'
+ b'C,C\n\x86\x0e\xc6\nC,C\x0c\x83\x10\xc3\x0c'
+ b'C,C.C,C.C,C.C,C.'
+ b'C,C.C,t-r/p2l?X@'
+ b'mf;d<d<d\x1e'
+ )
def __init__(self):
- self.tests = ('Touch', 'String')
+ self.tests = ('Touch', 'String', 'Button', 'Crash', '1-bit RLE', '2-bit RLE')
self.test = self.tests[0]
+ self.scroll = wasp.widgets.ScrollIndicator()
- def foreground(self, system, effect=None):
+ def foreground(self):
"""Activate the application."""
self.on_screen = ( -1, -1, -1, -1, -1, -1 )
- self.draw(effect)
- system.request_event(manager.EVENT_TOUCH | manager.EVENT_SWIPE_LEFTRIGHT)
-
- def background(self):
- """De-activate the application (without losing state)."""
- pass
+ self.draw()
+ wasp.system.request_event(wasp.EventMask.TOUCH |
+ wasp.EventMask.SWIPE_UPDOWN |
+ wasp.EventMask.BUTTON)
- def sleep(self):
- return False
+ def press(self, button, state):
+ draw = wasp.watch.drawable
+ if self.test == 'Touch':
+ draw.string('Button', 0, 108, width=240)
+ if self.test == 'String':
+ self.benchmark_string()
+ elif self.test == 'Button':
+ draw.string('{}: {}'.format(button, state), 0, 108, width=240)
+ elif self.test == 'Crash':
+ self.crash()
def swipe(self, event):
tests = self.tests
- i = tests.index(self.test) + 1
- if i >= len(tests):
- i = 0
+ i = tests.index(self.test)
+
+ if event[0] == wasp.EventType.UP:
+ i += 1
+ if i >= len(tests):
+ i = 0
+ else:
+ i -= 1
+ if i < 0:
+ i = len(tests) - 1
self.test = tests[i]
self.draw()
def touch(self, event):
- draw = watch.drawable
if self.test == 'Touch':
- draw.string('({}, {})'.format(event[1], event[2]),
- 0, 108, width=240)
+ wasp.watch.drawable.string('({}, {})'.format(
+ event[1], event[2]), 0, 108, width=240)
elif self.test == 'String':
- draw.fill(0, 0, 30, 240, 240-30)
- t = machine.Timer(id=1, period=8000000)
- t.start()
- draw.string("The quick brown", 12, 24+24)
- draw.string("fox jumped over", 12, 24+48)
- draw.string("the lazy dog.", 12, 24+72)
- draw.string("0123456789", 12, 24+120)
- draw.string('!"£$%^&*()', 12, 24+144)
- elapsed = t.time()
- t.stop()
- del t
- draw.string('{}s'.format(elapsed / 1000000), 12, 24+192)
-
- return True
-
- def draw(self, effect=None):
+ self.benchmark_string()
+ elif self.test == '1-bit RLE':
+ self.benchmark_rle_1bit()
+ elif self.test == '2-bit RLE':
+ self.benchmark_rle_2bit()
+
+ def benchmark_rle_1bit(self):
+ draw = wasp.watch.drawable
+ draw.fill(0, 0, 30, 240, 240-30)
+ self.scroll.draw()
+ t = machine.Timer(id=1, period=8000000)
+ t.start()
+ for i in range(0, 128, 16):
+ draw.rleblit(self.ICON, (i, 30 + i))
+ elapsed = t.time()
+ t.stop()
+ del t
+ draw.string('{}s'.format(elapsed / 1000000), 12, 24+192)
+
+ def benchmark_rle_2bit(self):
+ draw = wasp.watch.drawable
+ draw.fill(0, 0, 30, 240, 240-30)
+ self.scroll.draw()
+ t = machine.Timer(id=1, period=8000000)
+ t.start()
+ for i in range(0, 128, 16):
+ draw.rle2bit(self.RLE_2BIT, i, 30 + i)
+ elapsed = t.time()
+ t.stop()
+ del t
+ draw.string('{}s'.format(elapsed / 1000000), 12, 24+192)
+
+ def benchmark_string(self):
+ draw = wasp.watch.drawable
+ draw.fill(0, 0, 30, 240, 240-30)
+ self.scroll.draw()
+ t = machine.Timer(id=1, period=8000000)
+ t.start()
+ draw.string("The quick brown", 12, 24+24)
+ draw.string("fox jumped over", 12, 24+48)
+ draw.string("the lazy dog.", 12, 24+72)
+ draw.string("0123456789", 12, 24+120)
+ draw.string('!"£$%^&*()', 12, 24+144)
+ elapsed = t.time()
+ t.stop()
+ del t
+ draw.string('{}s'.format(elapsed / 1000000), 12, 24+192)
+
+ def draw(self):
"""Redraw the display from scratch."""
- watch.display.mute(True)
- watch.drawable.fill()
- watch.drawable.string('{} test'.format(self.test),
+ wasp.watch.display.mute(True)
+ draw = wasp.watch.drawable
+ draw.fill()
+ draw.string('{} test'.format(self.test),
0, 6, width=240)
- watch.display.mute(False)
+ self.scroll.draw()
+
+ if self.test == 'Crash':
+ draw.string("Press button to", 12, 24+24)
+ draw.string("throw exception.", 12, 24+48)
+ elif self.test == '1-bit RLE':
+ draw.rleblit(self.ICON, (120-48, 120-32))
+ elif self.test == '2-bit RLE':
+ draw.rle2bit(self.RLE_2BIT, 120-48, 120-32)
+
+ wasp.watch.display.mute(False)
diff --git a/wasp/boards/dsd6/manifest.py b/wasp/boards/dsd6/manifest.py
index 9efab20..bee48c0 100644
--- a/wasp/boards/dsd6/manifest.py
+++ b/wasp/boards/dsd6/manifest.py
@@ -1,3 +1,6 @@
+# SPDX-License-Identifier: LGPL-3.0-or-later
+# Copyright (C) 2020 Daniel Thompson
+
freeze('../..',
(
'demo.py',
diff --git a/wasp/boards/dsd6/watch.py b/wasp/boards/dsd6/watch.py
index fdfdba9..0148d95 100644
--- a/wasp/boards/dsd6/watch.py
+++ b/wasp/boards/dsd6/watch.py
@@ -1,3 +1,6 @@
+# SPDX-License-Identifier: LGPL-3.0-or-later
+# Copyright (C) 2020 Daniel Thompson
+
from machine import RTCounter as RTC
# Start measuring time (and feeding the watchdog)
diff --git a/wasp/boards/nitrogen/manifest.py b/wasp/boards/nitrogen/manifest.py
index f60ebd9..b17711b 100644
--- a/wasp/boards/nitrogen/manifest.py
+++ b/wasp/boards/nitrogen/manifest.py
@@ -1,3 +1,6 @@
+# SPDX-License-Identifier: LGPL-3.0-or-later
+# Copyright (C) 2020 Daniel Thompson
+
freeze('../..',
(
'demo.py',
diff --git a/wasp/boards/nitrogen/watch.py b/wasp/boards/nitrogen/watch.py
index fdfdba9..0148d95 100644
--- a/wasp/boards/nitrogen/watch.py
+++ b/wasp/boards/nitrogen/watch.py
@@ -1,3 +1,6 @@
+# SPDX-License-Identifier: LGPL-3.0-or-later
+# Copyright (C) 2020 Daniel Thompson
+
from machine import RTCounter as RTC
# Start measuring time (and feeding the watchdog)
diff --git a/wasp/boards/pinetime/manifest.py b/wasp/boards/pinetime/manifest.py
index 6a19c1a..30d54e2 100644
--- a/wasp/boards/pinetime/manifest.py
+++ b/wasp/boards/pinetime/manifest.py
@@ -1,9 +1,13 @@
+# SPDX-License-Identifier: LGPL-3.0-or-later
+# Copyright (C) 2020 Daniel Thompson
+
freeze('.', 'watch.py', opt=3)
freeze('../..',
(
'apps/clock.py',
- 'apps/testapp.py',
'apps/flashlight.py',
+ 'apps/launcher.py',
+ 'apps/testapp.py',
'boot.py',
'demo.py',
'draw565.py',
@@ -17,8 +21,8 @@ freeze('../..',
'fonts/sans24.py',
'icons.py',
'logo.py',
- 'manager.py',
'shell.py',
+ 'wasp.py',
'widgets.py',
),
opt=3
diff --git a/wasp/boards/pinetime/watch.py b/wasp/boards/pinetime/watch.py
index 8248d9e..1caeea5 100644
--- a/wasp/boards/pinetime/watch.py
+++ b/wasp/boards/pinetime/watch.py
@@ -1,3 +1,6 @@
+# SPDX-License-Identifier: LGPL-3.0-or-later
+# Copyright (C) 2020 Daniel Thompson
+
# Start measuring time (and feeding the watchdog) before *anything* else
from machine import RTCounter
from drivers.nrf_rtc import RTC
diff --git a/wasp/boards/simulator/display.py b/wasp/boards/simulator/display.py
index 3d7d388..c226050 100644
--- a/wasp/boards/simulator/display.py
+++ b/wasp/boards/simulator/display.py
@@ -1,3 +1,6 @@
+# SPDX-License-Identifier: LGPL-3.0-or-later
+# Copyright (C) 2020 Daniel Thompson
+
""" Simulated ST7789 display and CST816S touchscreen. """
import sys
@@ -38,7 +41,8 @@ class ST7789Sim(object):
self.y = self.rowclip[0]
elif self.cmd == RAMWR:
- pixelview = sdl2.ext.PixelView(windowsurface)
+ #pixelview = sdl2.ext.PixelView(windowsurface)
+ pixelview = sdl2.ext.pixels2d(windowsurface)
half = False
for d in data:
@@ -49,11 +53,14 @@ class ST7789Sim(object):
rgb |= d
half = False
- pixel = ((rgb & 0xf800) >> 8,
- (rgb & 0x07e0) >> 3,
- (rgb & 0x001f) << 3)
+ #pixel = ((rgb & 0xf800) >> 8,
+ # (rgb & 0x07e0) >> 3,
+ # (rgb & 0x001f) << 3)
+ pixel = (((rgb & 0xf800) << 8) +
+ ((rgb & 0x07e0) << 5) +
+ ((rgb & 0x001f) << 3))
- pixelview[self.y][self.x] = pixel
+ pixelview[self.x][self.y] = pixel
self.x += 1
if self.x > self.colclip[1]:
@@ -83,14 +90,22 @@ class CST816SSim():
self.regs[1] = 0
def handle_key(self, key):
+ """Use key presses to provoke different touchscreen events.
+
+ Note: The Down key provokes an upward swipe and vice versa.
+ Same for left and right. That is because the swipe up
+ gesture means show me the screen the is below me (hence
+ the controls are inverted compared to joystick-like
+ direction control).
+ """
if key.keysym.sym == sdl2.SDLK_DOWN:
- self.regs[1] = 1
- elif key.keysym.sym == sdl2.SDLK_UP:
self.regs[1] = 2
+ elif key.keysym.sym == sdl2.SDLK_UP:
+ self.regs[1] = 1
elif key.keysym.sym == sdl2.SDLK_LEFT:
- self.regs[1] = 3
- elif key.keysym.sym == sdl2.SDLK_RIGHT:
self.regs[1] = 4
+ elif key.keysym.sym == sdl2.SDLK_RIGHT:
+ self.regs[1] = 3
self.regs[3] = 0x80
self.raise_interrupt()
diff --git a/wasp/boards/simulator/machine.py b/wasp/boards/simulator/machine.py
index 62a95c5..2fc9754 100644
--- a/wasp/boards/simulator/machine.py
+++ b/wasp/boards/simulator/machine.py
@@ -1,3 +1,6 @@
+# SPDX-License-Identifier: LGPL-3.0-or-later
+# Copyright (C) 2020 Daniel Thompson
+
import display
import time
diff --git a/wasp/boards/simulator/micropython.py b/wasp/boards/simulator/micropython.py
index 578a12d..41c30a4 100644
--- a/wasp/boards/simulator/micropython.py
+++ b/wasp/boards/simulator/micropython.py
@@ -1,3 +1,6 @@
+# SPDX-License-Identifier: LGPL-3.0-or-later
+# Copyright (C) 2020 Daniel Thompson
+
def const(fn):
return fn
diff --git a/wasp/boards/simulator/watch.py b/wasp/boards/simulator/watch.py
index f38d7a1..ce281f4 100644
--- a/wasp/boards/simulator/watch.py
+++ b/wasp/boards/simulator/watch.py
@@ -1,3 +1,6 @@
+# SPDX-License-Identifier: LGPL-3.0-or-later
+# Copyright (C) 2020 Daniel Thompson
+
import time
def sleep_ms(ms):
time.sleep(ms / 1000)
diff --git a/wasp/boards/sphinx/machine.py b/wasp/boards/sphinx/machine.py
index b35a2a7..dfddb6d 100644
--- a/wasp/boards/sphinx/machine.py
+++ b/wasp/boards/sphinx/machine.py
@@ -1,3 +1,6 @@
+# SPDX-License-Identifier: LGPL-3.0-or-later
+# Copyright (C) 2020 Daniel Thompson
+
class ADC():
pass
diff --git a/wasp/boards/sphinx/watch.py b/wasp/boards/sphinx/watch.py
index dccee44..57b590e 100644
--- a/wasp/boards/sphinx/watch.py
+++ b/wasp/boards/sphinx/watch.py
@@ -1,3 +1,6 @@
+# SPDX-License-Identifier: LGPL-3.0-or-later
+# Copyright (C) 2020 Daniel Thompson
+
import time
def sleep_ms(ms):
time.sleep(ms / 1000)
diff --git a/wasp/boot.py b/wasp/boot.py
index de05e13..3e9cae3 100644
--- a/wasp/boot.py
+++ b/wasp/boot.py
@@ -1 +1,4 @@
+# SPDX-License-Identifier: LGPL-3.0-or-later
+# Copyright (C) 2020 Daniel Thompson
+
import watch
diff --git a/wasp/demo.py b/wasp/demo.py
index 6d3f6f5..a16c306 100644
--- a/wasp/demo.py
+++ b/wasp/demo.py
@@ -1,3 +1,6 @@
+# SPDX-License-Identifier: LGPL-3.0-or-later
+# Copyright (C) 2020 Daniel Thompson
+
#
# Logo demo for PineTime
#
diff --git a/wasp/draw565.py b/wasp/draw565.py
index bd72c65..50ff592 100644
--- a/wasp/draw565.py
+++ b/wasp/draw565.py
@@ -1,3 +1,6 @@
+# SPDX-License-Identifier: LGPL-3.0-or-later
+# Copyright (C) 2020 Daniel Thompson
+
import fonts.sans24
import micropython
@@ -30,6 +33,17 @@ def _bitblit(bitbuf, pixels, bgfg: int, count: int):
pxp += 1
@micropython.viper
+def _expand_rgb(eightbit: int) -> int:
+ r = eightbit >> 5
+ r = (r << 2) | (r >> 1)
+ g = (eightbit >> 2) & 7
+ g *= 9
+ b = eightbit & 3
+ b *= 10
+
+ return (r << 11) | (g << 5) | b
+
+@micropython.viper
def _fill(mv, color: int, count: int, offset: int):
p = ptr8(mv)
colorhi = color >> 8
@@ -75,6 +89,10 @@ class Draw565(object):
and 24 pt Sans Serif text.
"""
self._display = display
+ self.reset()
+
+ def reset(self):
+ """Restore the default colour and font."""
self.set_color(0xffff)
self.set_font(fonts.sans24)
@@ -116,6 +134,53 @@ class Draw565(object):
else:
color = bg
+ @micropython.native
+ def rle2bit(self, image, x, y):
+ """Decode and draw a 2-bit RLE image."""
+ display = self._display
+ (sx, sy, rle) = image
+
+ display.set_window(x, y, sx, sy)
+
+ buf = memoryview(display.linebuffer)[0:2*sx]
+ bp = 0
+ rl = 0
+ palette = [ 0, 0xfffe, 0x7bef, 0xffff ]
+ next_color = 1
+
+ def blit_run(color, rl):
+ nonlocal bp
+ while rl:
+ count = min(sx - bp, rl)
+ _fill(buf, color, count, bp)
+ bp += count
+ rl -= count
+
+ if bp >= sx:
+ display.write_data(buf)
+ bp = 0
+
+ for op in rle:
+ if rl == 0:
+ px = op >> 6
+ rl = op & 0x3f
+ if 0 == rl:
+ rl = -1
+ elif rl < 63:
+ blit_run(palette[px], rl)
+ rl = 0
+ elif rl > 0:
+ rl += op
+ if op < 255:
+ blit_run(palette[px], rl)
+ rl = 0
+ else:
+ palette[next_color] = _expand_rgb(op)
+ next_color += 1
+ if next_color >= len(palette):
+ next_color = 1
+ rl = 0
+
def set_color(self, color, bg=0):
"""Set the foreground (color) and background (bg) color.
diff --git a/wasp/drivers/battery.py b/wasp/drivers/battery.py
index cc1eafb..c13f117 100644
--- a/wasp/drivers/battery.py
+++ b/wasp/drivers/battery.py
@@ -1,3 +1,6 @@
+# SPDX-License-Identifier: LGPL-3.0-or-later
+# Copyright (C) 2020 Daniel Thompson
+
# Generic lithium ion battery driver
from machine import Pin, ADC
diff --git a/wasp/drivers/cst816s.py b/wasp/drivers/cst816s.py
index 323b9cc..d0f3cd2 100644
--- a/wasp/drivers/cst816s.py
+++ b/wasp/drivers/cst816s.py
@@ -1,3 +1,6 @@
+# SPDX-License-Identifier: LGPL-3.0-or-later
+# Copyright (C) 2020 Daniel Thompson
+
"""Hynitron CST816S touch contoller driver for MicroPython.
After modifying this file we can test the changes by replacing the
diff --git a/wasp/drivers/nrf_rtc.py b/wasp/drivers/nrf_rtc.py
index d907461..497c3d6 100644
--- a/wasp/drivers/nrf_rtc.py
+++ b/wasp/drivers/nrf_rtc.py
@@ -1,3 +1,6 @@
+# SPDX-License-Identifier: LGPL-3.0-or-later
+# Copyright (C) 2020 Daniel Thompson
+
""" Real Time Clock based on the nRF-family low power counter """
import time
diff --git a/wasp/drivers/signal.py b/wasp/drivers/signal.py
index 18ce8bf..974adb5 100644
--- a/wasp/drivers/signal.py
+++ b/wasp/drivers/signal.py
@@ -1,3 +1,6 @@
+# SPDX-License-Identifier: LGPL-3.0-or-later
+# Copyright (C) 2020 Daniel Thompson
+
class Signal(object):
'''Simplified Signal class
diff --git a/wasp/drivers/st7789.py b/wasp/drivers/st7789.py
index 89ebc4c..980435b 100644
--- a/wasp/drivers/st7789.py
+++ b/wasp/drivers/st7789.py
@@ -1,3 +1,6 @@
+# SPDX-License-Identifier: LGPL-3.0-or-later
+# Copyright (C) 2020 Daniel Thompson
+
"""Sitronix ST7789 display driver for MicroPython.
Note: Although the ST7789 supports a variety of communication protocols
diff --git a/wasp/drivers/vibrator.py b/wasp/drivers/vibrator.py
index eba6018..359e738 100644
--- a/wasp/drivers/vibrator.py
+++ b/wasp/drivers/vibrator.py
@@ -1,3 +1,6 @@
+# SPDX-License-Identifier: LGPL-3.0-or-later
+# Copyright (C) 2020 Daniel Thompson
+
# Generic PWM capable vibrator
import time
@@ -11,7 +14,7 @@ class Vibrator(object):
self.period = 16000
self.active_low = active_low
- def pulse(self, duty=50, ms=100):
+ def pulse(self, duty=25, ms=40):
pwm = PWM(0, self.pin, freq=self.freq, duty=duty, period=self.period)
pwm.init()
time.sleep_ms(ms)
diff --git a/wasp/icons.py b/wasp/icons.py
index 409a3d6..76b5ee7 100644
--- a/wasp/icons.py
+++ b/wasp/icons.py
@@ -1,3 +1,20 @@
+# SPDX-License-Identifier: LGPL-3.0-or-later
+# Copyright (C) 2020 Daniel Thompson
+
# 1-bit RLE, generated from res/battery.png, 189 bytes
battery = (36, 48, b'\x97\x0e\x14\x12\x11\x14\x10\x14\x0c\x08\x0c\x08\x08\x08\x0c\x08\x08\x08\x0c\x08\x08\x08\x0c\x08\x08\x04\x14\x04\x08\x04\x14\x04\x08\x04\x0c\x04\x04\x04\x08\x04\x0b\x05\x04\x04\x08\x04\n\x06\x04\x04\x08\x04\t\x07\x04\x04\x08\x04\x08\x07\x05\x04\x08\x04\x07\x07\x06\x04\x08\x04\x06\x07\x07\x04\x08\x04\x05\x07\x08\x04\x08\x04\x04\x0e\x02\x04\x08\x04\x03\x0f\x02\x04\x08\x04\x02\x10\x02\x04\x08\x04\x02\x10\x02\x04\x08\x04\x02\x0f\x03\x04\x08\x04\x02\x0e\x04\x04\x08\x04\x08\x07\x05\x04\x08\x04\x07\x07\x06\x04\x08\x04\x06\x07\x07\x04\x08\x04\x05\x07\x08\x04\x08\x04\x04\x07\t\x04\x08\x04\x04\x06\n\x04\x08\x04\x04\x05\x0b\x04\x08\x04\x04\x04\x0c\x04\x08\x04\x14\x04\x08\x04\x14\x04\x08\x04\x14\x04\x08\x04\x14\x04\x08\x1c\x08\x1c\x08\x1c\x08\x1c\x98')
+# 1-bit RLE, generated from res/app_icon.png, 441 bytes
+app = (96, 64, b'\x1e$<$<$;&\x97,20/2-4,\x03.\x03,\x03.\x03,\x03.\x03,\x03.\x03,\x03.\x03,\x03\x0c\x03\x10\x03\x0c\x03,\x03\n\x07\x0c\x07\n\x03,\x03\t\x03\x02\x04\n\x04\x02\x03\t\x03,\x03\x08\x02\x07\x02\x08\x02\x07\x02\x08\x03,\x03\x07\x02\t\x02\x06\x02\t\x02\x07\x03,\x03\x06\x02\x0b\x02\x04\x02\x0b\x02\x06\x03,\x03\x06\x02\x0b\x02\x04\x02\x0b\x02\x06\x03,\x03\x05\x02\x0c\x02\x04\x02\x0c\x02\x05\x03,\x03\x05\x02\x0c\x02\x04\x02\x0c\x02\x05\x03,\x03\x05\x03\x0b\x02\x04\x02\x0b\x03\x05\x03,\x03\x06\x02\x0b\x02\x04\x02\x0b\x02\x06\x03,\x03\x06\x02\x0b\x02\x04\x02\x0b\x01\x07\x03,\x03\x07\x02\n\x02\x04\x02\n\x02\x07\x03+\x04\x08\x02\t\x02\x04\x02\t\x02\x08\x03*\x05\t\x0c\x04\x0c\t\x03*\x05\n\x0b\x04\x0b\n\x03*\x05.\x03*\x05.\x03*\x05.\x03*\x05.\x03*\x05\n\x0b\x04\x0b\n\x03+\x04\t\x0c\x04\x0c\t\x03,\x03\x08\x02\t\x02\x04\x02\t\x02\x08\x03,\x03\x07\x02\n\x02\x04\x02\n\x02\x07\x03,\x03\x06\x02\x0b\x02\x04\x02\x0b\x01\x07\x03,\x03\x06\x02\x0b\x02\x04\x02\x0b\x02\x06\x03,\x03\x05\x03\x0b\x02\x04\x02\x0b\x03\x05\x03,\x03\x05\x02\x0c\x02\x04\x02\x0c\x02\x05\x03,\x03\x05\x02\x0c\x02\x04\x02\x0c\x02\x05\x03,\x03\x06\x02\x0b\x02\x04\x02\x0b\x02\x06\x03,\x03\x06\x02\x0b\x02\x04\x02\x0b\x02\x06\x03,\x03\x07\x02\t\x02\x06\x02\t\x02\x07\x03,\x03\x08\x02\x07\x02\x08\x02\x07\x02\x08\x03,\x03\t\x03\x02\x04\n\x04\x02\x03\t\x03,\x03\n\x06\x0e\x06\n\x03,\x03\x0c\x03\x10\x03\x0c\x03,\x03.\x03,\x03.\x03,\x03.\x03,\x03.\x03,\x03.\x03,4-2/02,\x97&;$<$<$\x1e')
+
+# 1-bit RLE, generated from res/clock_icon.png, 301 bytes
+clock = (96, 64, b'\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\xcd\x06\r\x06!\x05\x0b\x08\x0c\x08\x0b\n\x1d\t\x07\x0c\n\x08\n\x0c\x1b\x0b\x06\x0e\x08\x03\x02\x03\n\x04\x05\x04\x1a\x04\x03\x04\x06\x02\x08\x04\r\x03\t\x04\x07\x03\x19\x04\x05\x04\x10\x04\x0c\x03\t\x03\t\x02\x19\x03\x07\x03\x11\x03\x0c\x03\t\x03\t\x03\n\x04\t\x04\x07\x04\x10\x03\x0c\x03\t\x03\t\x03\n\x04\t\x03\t\x03\x10\x03\x0c\x03\t\x03\t\x03\n\x04\t\x03\t\x03\x10\x03\x0c\x03\t\x04\x07\x04\n\x04\t\x03\t\x03\x0f\x04\x0c\x03\n\x04\x05\x05\n\x04\t\x03\x03\x02\x04\x03\x0e\x04\r\x03\n\n\x01\x03\x17\x03\x02\x04\x03\x03\r\x05\r\x03\x0b\t\x01\x03\x17\x03\x02\x03\x04\x03\x0c\x05\x0e\x03\r\x05\x03\x03\x17\x03\t\x03\x0b\x05\x0f\x03\x15\x03\x17\x03\t\x03\n\x05\x10\x03\x14\x04\x17\x03\t\x03\t\x05\x11\x03\x14\x03\x18\x04\x07\x04\x08\x04\x13\x03\x14\x03\x19\x03\x07\x03\x08\x04\x14\x03\x13\x04\x0b\x04\n\x03\x06\x04\x07\x04\x15\x03\x0b\x01\x05\x05\x0c\x04\x0b\x04\x03\x04\x07\x03\x12\r\x06\n\r\x04\x0b\x0b\x06\x0f\x07\r\x06\t\x0e\x04\x0c\t\x07\x0f\x07\r\x07\x06\x10\x04\x0e\x05\t\x0f\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\xaa')
+
+# 1-bit RLE, generated from res/torch_icon.png, 283 bytes
+torch = (96, 64, b'\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00e\x06W\nT\x04\x06\x02S\x03\x07\x02S\x02\n\x01\x0b\x029\x05\x08\x02\t\x02\x08\x03:\x07\x06\x02\x0b\x01\x06\x02$(\n\x02\x03\x03%(\x0c\x01+\x02%\x01\x0b\x02+\x02%\x01\x0c\x01+\x02\x05\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x05\x01\x0b\x02+\x02\x04\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x04\x01\x0c\x01+\x02%\x01\x0b\x02\x03\n\x1e\x02\x05\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x05\x01\x0c\x01+\x02\x04\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x04\x01\x0b\x02+\x02%\x01\x0c\x01+\x02%\x01\x0b\x02+(\x0c\x01,(\n\x02\x03\x03L\x02\x0b\x01\x06\x02K\x02\t\x02\x08\x03H\x02\n\x01\x0b\x02G\x03\x07\x02U\x04\x06\x02V\nY\x06\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\xe2')
+
+# 1-bit RLE, generated from res/up_arrow.png, 16 bytes
+up_arrow = (16, 9, b'\x07\x02\r\x04\x0b\x06\t\x08\x07\n\x05\x0c\x03\x0e\x01 ')
+
+# 1-bit RLE, generated from res/down_arrow.png, 17 bytes
+down_arrow = (16, 9, b'\x00 \x01\x0e\x03\x0c\x05\n\x07\x08\t\x06\x0b\x04\r\x02\x07')
diff --git a/wasp/logo.py b/wasp/logo.py
index f526e2a..2020550 100644
--- a/wasp/logo.py
+++ b/wasp/logo.py
@@ -1,3 +1,6 @@
+# SPDX-License-Identifier: LGPL-3.0-or-later
+# Copyright (C) 2020 Daniel Thompson
+
pine64 = (240, 240, b'x\x01\xee\x03\xec\x05\xea\x07\xe8\t\xe6\x0b\xe4\r\xe2\x0f\xe0\x11\xde\x13\xdc\x15\xda\x17\xd8\x19\xd6\x1b\xd4\x1d\xd2\x1f\xd1 \xcf!\xce#\xcc%\xca\'\xc8)\xc6+\xc4-\xc3-\xc6\'\xcb#\xd0\x1d\xb6\x02\x1d\x19\x1b\x03\x99\x05\x1e\x13\x1c\x05\x99\x08\x1d\x0f\x1c\x08\x98\n\x1e\t\x1d\n\x97\r\x1e\x05\x1c\r\x97\x10:\x10\x96\x126\x12\x95\x151\x15\x95\x17-\x18\x93\x1b(\x1a\x93\x1d$\x1c\x93\x1e!\x1f\x91\x1d%\x1d\x91\x1b*\x1a\x91\x19.\x19\x8f\x17\x19\x01\x19\x17\x8f\x15\x19\x05\x19\x15\x8f\x13\x19\t\x1a\x13\x8d\x12\x19\r\x1a\x11\x8d\x10\x18\x12\x1a\x10\x8c\r\x19\x17\x19\x0e\x8b\x0c\x19\x1b\x19\x0c\x8b\n\x19\x1f\x1a\n\x89\t\x18$\x1a\x08\x89\x07\x18)\x19\x06\x89\x04\x19-\x19\x05\x87\x03\x191\x19\x03\x87\x01\x186\x19\x01\x9e;\xb3?\xafC\xabG\xa6L\xa2Q\x9dU\x98Z\x94^\x90c\x8bg\x87k\x85kn\x01\x18f\x1a\x01V\x03\x19a\x1a\x03V\x05\x19]\x1a\x05V\x07\x19Y\x19\x08V\n\x18T\x1a\nV\x0c\x19O\x1a\x0cV\x0e\x19K\x19\x0fV\x11\x18G\x19\x11V\x13\x18B\x1a\x13V\x16\x18=\x19\x16V\x18\x189\x19\x18V\x1a\x185\x19\x1aV\x1c\x181\x19\x1cV\x1f\x18+\x19\x1fV!\x18\'\x19!V#\x18#\x19#V%\x18\x1f\x18&V(\x17\x1a\x19(V*\x18\x15\x19*V,\x18\x11\x18-V/\x17\r\x18/V1\x17\x08\x191V4\x17\x03\x184V6.6V8*8V:&:V= =V<"<V:&:V7+8V505V3\x19\x01\x1a3V0\x1a\x05\x1b0V.\x19\x0b\x1a.V,\x19\x0f\x1a,V)\x1a\x13\x1b)V\'\x1a\x17\x1b\'V%\x19\x1d\x1a%V#\x19!\x1a#V \x1a%\x1b V\x1e\x1a)\x1b\x1eV\x1c\x1a.\x1a\x1cV\x19\x1b2\x1b\x19V\x17\x1a7\x1b\x17V\x14\x1b<\x1a\x15V\x12\x1b@\x1b\x12V\x10\x1aE\x1b\x10V\x0e\x1aI\x1b\x0eV\x0b\x1bM\x1c\x0bV\t\x1bQ\x1c\tV\x07\x1aW\x1b\x07V\x04\x1b[\x1b\x05V\x02\x1b_\x1c\x02qc\x8ai\x85m\x81q}uyzvyZ\x03\x1cu\x1d\x03=\x04\x1cq\x1d\x04>\x06\x1cm\x1d\x06?\x08\x1ch\x1c\x08@\n\x1cd\x1c\nA\x0c\x1b_\x1c\x0cB\x0e\x1b[\x1c\x0eB\x11\x1bV\x1b\x11C\x12\x1bR\x1b\x12D\x14\x1bM\x1c\x14E\x16\x1aI\x1b\x16F\x18\x1aE\x1b\x18G\x1a\x1a?\x1b\x1aH\x1c\x1a;\x1b\x1cI\x1e\x197\x1a\x1eJ \x193\x1a J"\x1a-\x1b!L$\x19)\x1a$L&\x19%\x1a&M(\x18!\x1a\'N*\x19\x1c\x19*O+\x19\x18\x19+P.\x18\x13\x19.Q/\x18\x0f\x19/R2\x18\n\x182S3\x18\x05\x193T5\x18\x01\x195T8,8U9(9V<"<W< <X:$:Y7(7Z4-5Z222[/\x1a\x01\x1b/\\-\x19\x06\x1b-])\x1a\x0b\x1b)^\'\x1a\x0f\x1b\'_$\x1a\x13\x1b$`!\x1a\x19\x1b!a\x1e\x1a\x1d\x1b\x1eb\x1c\x1a!\x1b\x1cc\x19\x1a%\x1b\x19d\x16\x1a*\x1c\x16d\x14\x1a/\x1b\x14e\x11\x1a3\x1b\x11f\x0f\x1a7\x1b\x0fg\x0b\x1a<\x1c\x0bh\t\x1a@\x1c\ti\x06\x1aE\x1b\x06j\x03\x1bI\x1c\x03\x86M\xa0S\x9bW\x97[\x93_\x8ee\x89i\x85m\x85i\x89e\x8d`\x93[\x97W\x9bS\x9fN\xa5I\xa9E\xadA\xb1<\xb68\xbb3\xbf/\xc3+\xc7&\xcd!\xd1\x1d\xd5\x19\xda\x13\xdf\x0f\xe2\x0c\xe4\x0c\xe4\x0c\xe4\x0c\xe4\x0c\xe4\x0c\xe4\x0c\xe4\x0c\xe4\x0c\xe4\x0c\xe4\x0c\xe4\x0c\xe4\x0c\xe4\x0c\xe4\x0c\xe4\x0c\xe4\x0c\xe4\x0c\xe4\x0c\xe4\x0c\xe4\x0c\xe4\x0c\xe4\x0c\xe4\x0c\xe4\x0c\xe4\x0c\xe4\x0c\xe4\x0c\xe4\x0c\xe4\x0cs')
micropython = (240, 240, b'\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\x1fc\tc!c\tc!c\tc!c\tc!c\tc!c\tc!c\tc!c\tc!c\tc!c\tc!c\tc!c\tc!c\tc!c\tc!c\tc!c\tc!c\tc!c\tc!c\tc!c\tc!c\tc!c\tc!c\tc!c\tc!c\tc!c\tc!c\tc!c\tc!c\tc!c\tc!c\tc!c\tc!c\tc!c\tc!c\tc!c\tc!c\tc!c\tc!c\tc!c\tc!c\tc!c\tc!c\tc!c\tc!c\tc!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\t-\t-\t-!-\tc\t-!-\tc\t-!-\tc\t-!-\tc\t-!-\tc\t-!-\tc\t-!-\tc\t\x12\x0c\x0f!-\tc\t\x12\x0c\x0f!-\tc\t\x12\x0c\x0f!-\tc\t\x12\x0c\x0f!-\tc\t\x12\x0c\x0f!-\tc\t\x12\x0c\x0f!-\tc\t\x12\x0c\x0f!-\tc\t\x12\x0c\x0f!-\tc\t\x12\x0c\x0f!-\tc\t\x12\x0c\x0f!-\tc\t\x12\x0c\x0f!-\tc\t\x12\x0c\x0f!-\tc\t\x12\x0c\x0f!-\tc\t\x12\x0c\x0f!-\tc\t\x12\x0c\x0f!-\tc\t\x12\x0c\x0f!-\tc\t\x12\x0c\x0f!-\tc\t\x12\x0c\x0f!-\tc\t\x12\x0c\x0f!-\tc\t\x12\x0c\x0f!-\tc\t\x12\x0c\x0f!-\tc\t-!-\tc\t-!-\tc\t-!-\tc\t-!-\tc\t-!-\tc\t-!-\tc\t-!-\tc\t-!-\tc\t-!-\tc\t-!-\tc\t-!-\tc\t-!-\tc\t-!-\tc\t-!-\tc\t-!-\tc\t-!-\tc\t-!-\tc\t-\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\x11')
diff --git a/wasp/main.py b/wasp/main.py
index cc4c8b6..88fb87e 100644
--- a/wasp/main.py
+++ b/wasp/main.py
@@ -1,3 +1,5 @@
-import manager, watch
-wasp = manager.Manager(watch)
-wasp.run()
+# SPDX-License-Identifier: LGPL-3.0-or-later
+# Copyright (C) 2020 Daniel Thompson
+
+import wasp
+wasp.system.run()
diff --git a/wasp/manager.py b/wasp/manager.py
deleted file mode 100644
index 3dafebe..0000000
--- a/wasp/manager.py
+++ /dev/null
@@ -1,150 +0,0 @@
-import gc
-import machine
-
-from apps.clock import ClockApp
-from apps.flashlight import FlashlightApp
-from apps.testapp import TestApp
-
-DOWN = 1
-UP = 2
-LEFT = 3
-RIGHT = 4
-
-EVENT_TOUCH = 0x0001
-EVENT_SWIPE_LEFTRIGHT = 0x0002
-EVENT_SWIPE_UPDOWN = 0x0004
-EVENT_BUTTON = 0x0008
-
-class Manager(object):
- def __init__(self, watch):
- self.watch = watch
-
- self.app = None
-
- self.applications = [
- ClockApp(),
- FlashlightApp(),
- TestApp()
- ]
-
- self.watch.display.poweron()
- self.switch(self.applications[0])
- self.watch.backlight.set(2)
-
- self.sleep_at = watch.rtc.uptime + 90
- self.charging = True
-
- def switch(self, app):
- if self.app:
- self.app.background()
-
- # Clear out any configuration from the old application
- self.event_mask = 0
- self.tick_period_ms = 0
- self.tick_expiry = None
-
- self.app = app
- self.watch.display.mute(True)
- app.foreground(self)
- self.watch.display.mute(False)
-
- def navigate(self, direction=None):
- """Navigate between different applications.
-
- Currently the direction is ignored.
- """
- app_list = self.applications
-
- if direction == DOWN:
- i = app_list.index(self.app) + 1
- if i >= len(app_list):
- i = 0
- self.switch(app_list[i])
- elif direction == UP:
- i = app_list.index(self.app) - 1
- if i < 0:
- i = len(app_list)-1
- self.switch(app_list[i])
-
- def request_event(self, event_mask):
- self.event_mask |= event_mask
-
- def request_tick(self, 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.
- """
- self.tick_period_ms = period_ms
- self.tick_expiry = self.watch.rtc.get_uptime_ms() + period_ms
-
- def handle_event(self, event):
- self.sleep_at = self.watch.rtc.uptime + 15
-
- event_mask = self.event_mask
- if event[0] < 5:
- updown = event[0] == 1 or event[0] == 2
- if (bool(event_mask & EVENT_SWIPE_UPDOWN) and updown) or \
- (bool(event_mask & EVENT_SWIPE_LEFTRIGHT) and not updown):
- if not self.app.swipe(event):
- self.navigate(event[0])
- else:
- self.navigate(event[0])
- elif event[0] == 5 and self.event_mask & EVENT_TOUCH:
- self.app.touch(event)
-
- def tick(self):
- rtc = self.watch.rtc
-
- if self.sleep_at:
- if rtc.update() and self.tick_expiry:
- now = rtc.get_uptime_ms()
-
- if self.tick_expiry <= now:
- ticks = 0
- while self.tick_expiry <= now:
- self.tick_expiry += self.tick_period_ms
- ticks += 1
- self.app.tick(ticks)
-
- if self.watch.button.value():
- self.sleep_at = self.watch.rtc.uptime + 15
-
- event = self.watch.touch.get_event()
- if event:
- self.handle_event(event)
-
- if self.watch.rtc.uptime > self.sleep_at:
- self.watch.backlight.set(0)
- if not self.app.sleep():
- self.switch(self.applications[0])
- self.app.sleep()
- self.watch.display.poweroff()
- self.charging = self.watch.battery.charging()
- self.sleep_at = None
-
- gc.collect()
- else:
- self.watch.rtc.update()
-
- charging = self.watch.battery.charging()
- if self.watch.button.value() or self.charging != charging:
- self.watch.display.poweron()
- self.app.wake()
- self.watch.backlight.set(2)
-
- # Discard any pending touch events
- _ = self.watch.touch.get_event()
-
- self.sleep_at = self.watch.rtc.uptime + 15
-
- def run(self):
- """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.
- """
- while True:
- self.tick()
- machine.deepsleep()
diff --git a/wasp/shell.py b/wasp/shell.py
index 5ad9623..ace9513 100644
--- a/wasp/shell.py
+++ b/wasp/shell.py
@@ -1,3 +1,6 @@
+# SPDX-License-Identifier: LGPL-3.0-or-later
+# Copyright (C) 2020 Daniel Thompson
+
# SPDX-License-Identifier: MIT
# Copyright (c) 2020 Daniel Thompson
# Copyright (c) 2016 Paul Sokolovsky
diff --git a/wasp/wasp.py b/wasp/wasp.py
new file mode 100644
index 0000000..6016bd5
--- /dev/null
+++ b/wasp/wasp.py
@@ -0,0 +1,321 @@
+# SPDX-License-Identifier: LGPL-3.0-or-later
+# Copyright (C) 2020 Daniel Thompson
+"""WASP system management (including constants)
+
+.. data:: system = Manager()
+
+ system is the system-wide instance of the Manager class. Applications
+ can use this instance to access system services.
+"""
+
+import gc
+import machine
+import watch
+import widgets
+
+from apps.clock import ClockApp
+from apps.flashlight import FlashlightApp
+from apps.launcher import LauncherApp
+from apps.testapp import TestApp
+
+class EventType():
+ """Enumerated interface actions.
+
+ MicroPython does not implement the enum module so EventType
+ is simply a regular object which acts as a namespace.
+ """
+ DOWN = 1
+ UP = 2
+ LEFT = 3
+ RIGHT = 4
+ TOUCH = 5
+
+ HOME = 256
+
+class EventMask():
+ """Enumerated event masks.
+ """
+ TOUCH = 0x0001
+ SWIPE_LEFTRIGHT = 0x0002
+ SWIPE_UPDOWN = 0x0004
+ BUTTON = 0x0008
+
+class PinHandler():
+ """Pin (and Signal) event generator.
+
+ TODO: Currently this driver doesn't actually implement any
+ debounce but it will!
+ """
+
+ def __init__(self, pin):
+ """
+ :param Pin pin: The pin to generate events from
+ """
+ self._pin = pin
+ self._value = pin.value()
+
+ def get_event(self):
+ """Receive a pin change event.
+
+ Check for a pending pin change event and, if an event is pending,
+ return it.
+
+ :return: boolean of the pin state if an event is received, None
+ otherwise.
+ """
+ new_value = self._pin.value()
+ if self._value == new_value:
+ return None
+
+ self._value = new_value
+ return new_value
+
+class Manager():
+ """WASP 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 :py:data:`wasp.system` .
+ """
+
+ def __init__(self):
+ self.app = None
+
+ self.applications = []
+ self.blank_after = 15
+ self.charging = True
+ self.launcher = LauncherApp()
+
+ self._brightness = 2
+ self._button = PinHandler(watch.button)
+
+ # TODO: Eventually these should move to main.py
+ self.register(ClockApp(), True)
+ self.register(FlashlightApp(), True)
+ self.register(TestApp(), True)
+
+ def register(self, app, quick_ring=True):
+ """Register an application with the system.
+
+ :param object app: The application to regsister
+ """
+ self.applications.append(app)
+
+ @property
+ def brightness(self):
+ """Cached copy of the brightness current written to the hardware."""
+ return self._brightness
+
+ @brightness.setter
+ def brightness(self, value):
+ self._brightness = value
+ watch.backlight.set(self._brightness)
+
+ def switch(self, app):
+ """Switch to the requested application.
+ """
+ if self.app:
+ if 'background' in dir(self.app):
+ self.app.background()
+ else:
+ # System start up...
+ watch.display.poweron()
+ watch.display.mute(True)
+ watch.backlight.set(self._brightness)
+ self.sleep_at = watch.rtc.uptime + 90
+
+ # Clear out any configuration from the old application
+ self.event_mask = 0
+ self.tick_period_ms = 0
+ self.tick_expiry = None
+
+ self.app = app
+ watch.display.mute(True)
+ watch.drawable.reset()
+ app.foreground()
+ watch.display.mute(False)
+
+ def navigate(self, 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).
+
+ :param int direction: The direction of the navigation
+ """
+ app_list = self.applications
+
+ if direction == EventType.LEFT:
+ if self.app in app_list:
+ i = app_list.index(self.app) + 1
+ if i >= len(app_list):
+ i = 0
+ else:
+ i = 0
+ self.switch(app_list[i])
+ elif direction == EventType.RIGHT:
+ if self.app in app_list:
+ i = app_list.index(self.app) - 1
+ if i < 0:
+ i = len(app_list)-1
+ else:
+ i = 0
+ self.switch(app_list[i])
+ elif direction == EventType.UP:
+ self.switch(self.launcher)
+ elif direction == EventType.DOWN:
+ if self.app != app_list[0]:
+ self.switch(app_list[0])
+ else:
+ watch.vibrator.pulse()
+ elif direction == EventType.HOME:
+ if self.app != app_list[0]:
+ self.switch(app_list[0])
+ else:
+ self.sleep()
+
+ def request_event(self, event_mask):
+ """Subscribe to events.
+
+ :param int event_mask: The set of events to subscribe to.
+ """
+ self.event_mask |= event_mask
+
+ def request_tick(self, 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.
+ """
+ self.tick_period_ms = period_ms
+ self.tick_expiry = watch.rtc.get_uptime_ms() + period_ms
+
+ def keep_awake(self):
+ """Reset the keep awake timer."""
+ self.sleep_at = watch.rtc.uptime + self.blank_after
+
+ def sleep(self):
+ """Enter the deepest sleep state possible.
+ """
+ watch.backlight.set(0)
+ if 'sleep' not in dir(self.app) or not self.app.sleep():
+ self.switch(self.applications[0])
+ self.app.sleep()
+ watch.display.poweroff()
+ self.charging = watch.battery.charging()
+ self.sleep_at = None
+
+ def wake(self):
+ """Return to a running state.
+ """
+ watch.display.poweron()
+ self.app.wake()
+ watch.backlight.set(self._brightness)
+
+ # Discard any pending touch events
+ _ = watch.touch.get_event()
+
+ self.keep_awake()
+
+ def _handle_button(self, state):
+ """Process a button-press (or unpress) event.
+ """
+ self.keep_awake()
+
+ if bool(self.event_mask & EventMask.BUTTON):
+ # Currently we only support one button
+ if not self.app.press(EventType.HOME, state):
+ # If app reported None or False then we are done
+ return
+
+ if state:
+ self.navigate(EventType.HOME)
+
+ def _handle_touch(self, event):
+ """Process a touch event.
+ """
+ self.keep_awake()
+
+ event_mask = self.event_mask
+ if event[0] < 5:
+ updown = event[0] == 1 or event[0] == 2
+ if (bool(event_mask & EventMask.SWIPE_UPDOWN) and updown) or \
+ (bool(event_mask & EventMask.SWIPE_LEFTRIGHT) and not updown):
+ if self.app.swipe(event):
+ self.navigate(event[0])
+ else:
+ self.navigate(event[0])
+ elif event[0] == 5 and self.event_mask & EventMask.TOUCH:
+ self.app.touch(event)
+
+ def _tick(self):
+ """Handle the system tick.
+
+ This function may be called frequently and includes short
+ circuit logic to quickly exit if we haven't reached a tick
+ expiry point.
+ """
+ rtc = watch.rtc
+
+ if self.sleep_at:
+ if rtc.update() and self.tick_expiry:
+ now = rtc.get_uptime_ms()
+
+ if self.tick_expiry <= now:
+ ticks = 0
+ while self.tick_expiry <= now:
+ self.tick_expiry += self.tick_period_ms
+ ticks += 1
+ self.app.tick(ticks)
+
+ state = self._button.get_event()
+ if None != state:
+ self._handle_button(state)
+
+ event = watch.touch.get_event()
+ if event:
+ self._handle_touch(event)
+
+ if self.sleep_at and watch.rtc.uptime > self.sleep_at:
+ self.sleep()
+
+ gc.collect()
+ else:
+ watch.rtc.update()
+
+ charging = watch.battery.charging()
+ if 1 == self._button.get_event() or self.charging != charging:
+ self.wake()
+
+ def run(self):
+ """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.
+ """
+ if not self.app:
+ self.switch(self.applications[0])
+
+ # Reminder: wasptool uses this string to confirm the device has
+ # been set running again.
+ print('Watch is running, use Ctrl-C to stop')
+
+ while True:
+ self._tick()
+ # Currently there is no code to control how fast the system
+ # ticks. In other words this code will break if we improve the
+ # power management... we are currently relying on no being able
+ # to stay in the low-power state for very long.
+ machine.deepsleep()
+
+system = Manager()
diff --git a/wasp/widgets.py b/wasp/widgets.py
index 3b2d6e1..9aa39a0 100644
--- a/wasp/widgets.py
+++ b/wasp/widgets.py
@@ -1,3 +1,6 @@
+# SPDX-License-Identifier: LGPL-3.0-or-later
+# Copyright (C) 2020 Daniel Thompson
+
import icons
import watch
@@ -8,7 +11,7 @@ class BatteryMeter(object):
def draw(self):
self.level = -2
self.update()
-
+
def update(self):
icon = icons.battery
draw = watch.drawable
@@ -47,3 +50,19 @@ class BatteryMeter(object):
draw.fill(rgb, x, 38 - h, w, h)
self.level = level
+
+class ScrollIndicator():
+ def __init__(self, x=240-18, y=240-24):
+ self._pos = (x, y)
+ self.up = True
+ self.down = True
+
+ def draw(self):
+ self.update()
+
+ def update(self):
+ draw = watch.drawable
+ if self.up:
+ draw.rleblit(icons.up_arrow, pos=self._pos, fg=0x7bef)
+ if self.down:
+ draw.rleblit(icons.down_arrow, pos=(self._pos[0], self._pos[1] + 13), fg=0x7bef)