diff options
Diffstat (limited to 'wasp')
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) |
