From c0065190b5dbfd1f27cfd437a93f9d6a9cc6ed82 Mon Sep 17 00:00:00 2001 From: Francesco Gazzetta Date: Thu, 9 Sep 2021 14:40:09 +0200 Subject: [PATCH] Add morse app Signed-off-by: Francesco Gazzetta --- README.rst | 4 + docs/apps.rst | 2 + res/MorseApp.png | Bin 0 -> 5040 bytes res/morse_icon.png | Bin 0 -> 6686 bytes wasp/apps/morse.py | 148 ++++++++++++++++++++++++++++++++ wasp/apps/software.py | 1 + wasp/boards/manifest_240x240.py | 1 + 7 files changed, 156 insertions(+) create mode 100644 res/MorseApp.png create mode 100644 res/morse_icon.png create mode 100644 wasp/apps/morse.py 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 0000000000000000000000000000000000000000..6ebec03559d818bae74aa0f209625a13dbdee33e GIT binary patch literal 5040 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 literal 0 HcmV?d00001 diff --git a/res/morse_icon.png b/res/morse_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..9d7a26b853d747975715a645005c24231d3fd58d GIT binary patch literal 6686 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 NHkNjzLi61R{{xuK&iMcU literal 0 HcmV?d00001 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',