diff --git a/README.rst b/README.rst index ae40bdf..afef113 100644 --- a/README.rst +++ b/README.rst @@ -153,6 +153,10 @@ simulator: :alt: Heart rate application running on the wasp-os simulator :width: 179 +.. image:: res/MorseApp.png + :alt: Morse translator/notepad application running on the wasp-os simulator + :width: 179 + .. image:: res/SportsApp.png :alt: Sports applications, a combined stopwatch and step counter :width: 179 diff --git a/docs/apps.rst b/docs/apps.rst index a931b6c..6db26c8 100644 --- a/docs/apps.rst +++ b/docs/apps.rst @@ -51,6 +51,8 @@ Applications .. automodule:: apps.haiku +.. automodule:: apps.morse + .. automodule:: apps.musicplayer .. automodule:: apps.sports diff --git a/res/MorseApp.png b/res/MorseApp.png new file mode 100644 index 0000000..6ebec03 Binary files /dev/null and b/res/MorseApp.png differ diff --git a/res/morse_icon.png b/res/morse_icon.png new file mode 100644 index 0000000..9d7a26b Binary files /dev/null and b/res/morse_icon.png differ diff --git a/wasp/apps/morse.py b/wasp/apps/morse.py new file mode 100644 index 0000000..61ba2f1 --- /dev/null +++ b/wasp/apps/morse.py @@ -0,0 +1,148 @@ +# SPDX-License-Identifier: LGPL-3.0-or-later +# Copyright (C) 2021 Francesco Gazzetta + +"""Morse translator and notepad +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This app is a simple morse translator that also doubles as a notepad. +Swipe up for a line, swipe down for a dot, tap for end letter, double tap for +end word. +Up to 7 lines of translation will be displayed on a 240x240 screen, and old +lines will be deleted. +There is a preview of the next letter at the bottom of the screen. + + +.. figure:: res/MorseApp.png + :width: 179 +""" + +import wasp +import icons +import fonts +from math import floor +from micropython import const + + +_WIDTH = const(240) +_HEIGHT = const(240) +# No easy way to make this depend on _WIDTH +_MAXINPUT = const(16) + +# These two need to match +_FONTH = const(24) +_FONT = fonts.sans24 + +_LINEH = int(_FONTH * 1.25) +_MAXLINES = floor(_HEIGHT / _LINEH) - 1 # the "-1" is the input line + +_code = { + "" : " ", + + ".-" : "A", + "-..." : "B", + "-.-." : "C", + "-.." : "D", + "." : "E", + "..-." : "F", + "--." : "G", + "...." : "H", + ".." : "I", + ".---" : "J", + "-.-" : "K", + ".-.." : "L", + "--" : "M", + "-." : "N", + "---" : "O", + ".--." : "P", + "--.-" : "Q", + ".-." : "R", + "..." : "S", + "-" : "T", + "..-" : "U", + "...-" : "V", + ".--" : "W", + "-..-" : "X", + "-.--" : "Y", + "--.." : "Z", + + ".----": "1", + "..---": "2", + "...--": "3", + "....-": "4", + ".....": "5", + "-....": "6", + "--...": "7", + "---..": "8", + "----.": "9", + "-----": "0", + } + +class MorseApp(): + NAME = 'Morse' + # 2-bit RLE, 96x64, generated from res/morse_icon.png, 143 bytes + ICON = ( + b'\x02' + b'`@' + b'?\xff\xff\xff\xff\x13\xc4?\x1c\xc6?\x1b\xc6?\x1b\xc6' + b'?\x1a\xc8?\x19\xc8?\x18\xca?\x17\xc4\x02\xc4?\x17' + b'\xc4\x02\xc4?\x16\xc5\x02\xc5?\x15\xc4\x04\xc4?\x15\xc4' + b'\x04\xc4?\x14\xc4\x06\xc4?\x13\xc4\x06\xc4?\x12\xc5\x06' + b'\xc5?\x11\xc4\x08\xc4?\x11\xd0?\x10\xd2?\x0f\xd2?' + b'\x0e\xc5\n\xc4?\x0e\xc4\x0c\xc4?\r\xc4\x0c\xc4?\x0c' + b'\xc5\x0c\xc5?\x0b\xc4\x0e\xc4?\x0b\xc4\x0e\xc4?\n\xc4' + b'\x10\xc4?\xff\xff\xff\xff\x83\xc4\x0e\xda4\xc4\x0e\xda4' + b'\xc4\x0e\xda4\xc4\x0e\xda?\xff\xff\xff\xfe' + ) + + def __init__(self): + self.letter = "" + self.text = [""] + pass + + def foreground(self): + self._draw() + wasp.system.request_event(wasp.EventMask.TOUCH | + wasp.EventMask.SWIPE_UPDOWN) + + def swipe(self, event): + if len(self.letter) < _MAXINPUT: + self.letter += "-" if event[0] == wasp.EventType.UP else "." + self._update() + + def touch(self, event): + addition = _code[self.letter] if self.letter in _code else "/{}/".format(self.letter) + self.letter = "" + merged = self.text[-1] + addition + # Check if the new text overflows the screen and add a new line if that's the case + split = wasp.watch.drawable.wrap(merged, _WIDTH) + if len(split) > 2: + self.text.append(self.text[-1][split[1]:split[2]] + addition) + self.text[-2] = self.text[-2][split[0]:split[1]] + if len(self.text) > _MAXLINES: + self.text.pop(0) + # Ideally a full refresh should be done only when we exceed + # _MAXLINES, but this should be fast enough + self._draw() + else: + self.text[-1] = merged + self._update() + + def _draw(self): + """Draw the display from scratch""" + draw = wasp.watch.drawable + draw.fill() + i = 0 + for line in self.text: + draw.string(line, 0, _LINEH * i) + i += 1 + self._update() + + def _update(self): + """Update the dynamic parts of the application display, specifically the + input line and last line of the text. + The full text area is updated in _draw() instead.""" + draw = wasp.watch.drawable + draw.string(self.text[-1], 0, _LINEH*(len(self.text)-1)) + guess = _code[self.letter] if self.letter in _code else "?" + draw.string("{} {}".format(self.letter, guess), 0, _HEIGHT - _FONTH, width=_WIDTH) + diff --git a/wasp/apps/software.py b/wasp/apps/software.py index c51036b..87d2ea5 100644 --- a/wasp/apps/software.py +++ b/wasp/apps/software.py @@ -42,6 +42,7 @@ class SoftwareApp(): db.append(('calc', factory('Calculator'))) db.append(('faces', factory('Faces'))) db.append(('gameoflife', factory('Game Of Life'))) + db.append(('morse', factory('Morse'))) db.append(('musicplayer', factory('Music Player'))) db.append(('play2048', factory('Play 2048'))) db.append(('snake', factory('Snake Game'))) diff --git a/wasp/boards/manifest_240x240.py b/wasp/boards/manifest_240x240.py index f0eb474..c4a6d51 100644 --- a/wasp/boards/manifest_240x240.py +++ b/wasp/boards/manifest_240x240.py @@ -14,6 +14,7 @@ manifest = ( 'apps/gameoflife.py', 'apps/haiku.py', 'apps/heart.py', + 'apps/morse.py', 'apps/musicplayer.py', 'apps/launcher.py', 'apps/pager.py',