diff --git a/README.rst b/README.rst index b6dbc0e..a49b724 100644 --- a/README.rst +++ b/README.rst @@ -167,6 +167,10 @@ simulator: :alt: Software selection app running on the wasp-os simulator :width: 179 +.. image:: res/PhoneApp.png + :alt: Phone application running on the wasp-os simulator + :width: 179 + wasp-os also contains a library of additional applications for you to choose. These are disabled by default but can be easily enabled using the Software diff --git a/docs/apps.rst b/docs/apps.rst index 01e1e16..2bbd0ec 100644 --- a/docs/apps.rst +++ b/docs/apps.rst @@ -36,6 +36,8 @@ Built-in .. automodule:: apps.pager +.. automodule:: apps.phone + Applications ------------ diff --git a/res/PhoneApp.png b/res/PhoneApp.png new file mode 100644 index 0000000..369701a Binary files /dev/null and b/res/PhoneApp.png differ diff --git a/res/hangup.png b/res/hangup.png new file mode 100644 index 0000000..6e54e98 Binary files /dev/null and b/res/hangup.png differ diff --git a/res/phone.png b/res/phone.png new file mode 100644 index 0000000..0f4b624 Binary files /dev/null and b/res/phone.png differ diff --git a/wasp/apps/phone.py b/wasp/apps/phone.py new file mode 100644 index 0000000..5b2a154 --- /dev/null +++ b/wasp/apps/phone.py @@ -0,0 +1,150 @@ +# SPDX-License-Identifier: LGPL-3.0-or-later +# Copyright (C) 2020 Daniel Thompson +# Copyright (C) 2020 Carlos Gil + +"""Phone for GadgetBridge and wasp-os companion +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + .. figure:: res/PhoneApp.png + :width: 179 + + Screenshot of the Phone application + +Phone Controller: + +* Touch: answer/end +* Event BACK: ignore/close + +""" + +import wasp + +import icons +import time + +from micropython import const + +DISPLAY_WIDTH = const(240) +ICON_SIZE = const(72) +CENTER_AT = const((DISPLAY_WIDTH - ICON_SIZE) // 2) + +class PhoneApp(object): + """ Phone application.""" + NAME = 'Phone' + + def __init__(self): + self._anwser = wasp.widgets.GfxButton(24, CENTER_AT, icons.phone) + self._end = wasp.widgets.GfxButton(DISPLAY_WIDTH - ICON_SIZE - 24, CENTER_AT, icons.hangup) + self._state = "end" + self._name = '' + self._number = '' + self._state_changed = True + self._name_changed = True + self._number_changed = True + + def _send_cmd(self, cmd): + print('\r') + for i in range(1): + for i in range(0, len(cmd), 20): + print(cmd[i: i + 20], end='') + time.sleep(0.2) + print(' ') + print(' ') + + def _fill_space(self, key): + if key == 'top': + wasp.watch.drawable.fill( + x=0, y=0, w=DISPLAY_WIDTH, h=CENTER_AT) + elif key == 'down': + wasp.watch.drawable.fill(x=0, y=CENTER_AT + ICON_SIZE, + w=DISPLAY_WIDTH, + h=DISPLAY_WIDTH - (CENTER_AT + ICON_SIZE)) + + def foreground(self): + """Activate the application.""" + number = wasp.system.phonestate.get('number') + name = wasp.system.phonestate.get('name') + state = wasp.system.phonestate.get('cmd') + + if number: + self._number = number + if name: + self._name = name + if state: + self._state = state + wasp.watch.drawable.fill() + self.draw() + wasp.system.request_tick(1000) + wasp.system.request_event(wasp.EventMask.TOUCH) + + def background(self): + """De-activate the application (without losing state).""" + self._state_changed = True + self._name_changed = True + self._number_changed = True + + def tick(self, ticks): + wasp.system.keep_awake() + state_now = wasp.system.phonestate.get('cmd') + name_now = wasp.system.phonestate.get('name') + number_now = wasp.system.phonestate.get('number') + if state_now: + if state_now != self._state: + self._state = state_now + self._state_changed = True + else: + self._state_changed = False + if name_now: + if name_now != self._name: + self._name = name_now + self._name_changed = True + else: + self._name_changed = False + if number_now: + if number_now != self._number: + self._number = number_now + self._number_changed = True + else: + self._number_changed = False + wasp.system.phonestate = {} + self._update() + + def touch(self, event): + if self._anwser.touch(event): + self._send_cmd('{"t":"call", "n":"ACCEPT"} ') + wasp.system.navigate(wasp.EventType.BACK) + elif self._end.touch(event): + self._send_cmd('{"t":"call", "n":"REJECT"} ') + wasp.system.navigate(wasp.EventType.BACK) + + def draw(self): + """Redraw the display from scratch.""" + self._draw() + + def _draw(self): + """Redraw the updated zones.""" + if self._state_changed: + self._anwser.draw() + if self._name_changed: + self._draw_label(self._name, 24 + 144) + if self._number_changed: + self._draw_label(self._number, 12) + self._end.draw() + + def _draw_label(self, label, pos): + """Redraw label info""" + if label: + draw = wasp.watch.drawable + chunks = draw.wrap(label, 240) + self._fill_space(pos) + for i in range(len(chunks)-1): + sub = label[chunks[i]:chunks[i+1]].rstrip() + draw.string(sub, 0, pos + 24 * i, 240) + + def _update(self): + if(self._state == "start" or self._state == "incoming"): + wasp.watch.vibrator.pulse(ms=wasp.system.notify_duration) + + + def update(self): + pass \ No newline at end of file diff --git a/wasp/boards/manifest_240x240.py b/wasp/boards/manifest_240x240.py index ccd4f64..79f26ea 100644 --- a/wasp/boards/manifest_240x240.py +++ b/wasp/boards/manifest_240x240.py @@ -17,6 +17,7 @@ manifest = ( 'apps/launcher.py', 'apps/pager.py', 'apps/play2048.py', + 'apps/phone.py', 'apps/settings.py', 'apps/software.py', 'apps/steps.py', diff --git a/wasp/boards/simulator/main.py b/wasp/boards/simulator/main.py index 78a1168..6f589a8 100644 --- a/wasp/boards/simulator/main.py +++ b/wasp/boards/simulator/main.py @@ -13,6 +13,12 @@ wasp.system.set_music_info({ 'artist': 'Dreams of Bamboo', }) +wasp.system.set_phone_state({ + 'cmd': 'end', + 'name': '5555555555', + 'number': 'Unknown Caller', + }) + # Increase the display blanking time to avoid spamming the console # with backlight activations. diff --git a/wasp/boards/sphinx/icons.py b/wasp/boards/sphinx/icons.py index a5cceac..9fd6c65 100644 --- a/wasp/boards/sphinx/icons.py +++ b/wasp/boards/sphinx/icons.py @@ -11,3 +11,5 @@ settings = 'Default settings icon' torch = 'Default torch or flashlight icon' up_arrow = 'Small (16x9) up arrow' down_arrow = 'Small (16x9) down arrow' +phone = 'A phone icon rotated to look as if it is picked up' +hangup = 'A phone icon' \ No newline at end of file diff --git a/wasp/gadgetbridge.py b/wasp/gadgetbridge.py index 18285ab..8e71f09 100644 --- a/wasp/gadgetbridge.py +++ b/wasp/gadgetbridge.py @@ -56,6 +56,8 @@ def GB(cmd): wasp.system.toggle_music(cmd) elif task == 'musicinfo': wasp.system.set_music_info(cmd) + elif task == 'call': + wasp.system.set_phone_state(cmd) else: pass #_info('Command "{}" is not implemented'.format(cmd)) diff --git a/wasp/icons.py b/wasp/icons.py index 30c5774..a2068fe 100644 --- a/wasp/icons.py +++ b/wasp/icons.py @@ -303,3 +303,29 @@ checkbox = ( b'Z\xc7X\xe4\x01\xde\x03\xdc\x02' ) +# 2-bit RLE, generated from res/phone.png, 126 bytes +phone = ( + b'\x02' + b'HH' + b'?\xff\xff\xa8\xc4?\x03\xc7?\x01\xc9>\xcb<\xcc;' + b'\xce:\xcf8\xd07\xd26\xd26\xd25\xd44\xd44' + b'\xd44\xd35\xd35\xd26\xd17\xcf9\xce:\xcd<' + b'\xcc<\xcc<\xcc=\xcb=\xcc<\xcd<\xcc<\xcd<' + b"\xcc<\xcd<\xcd<\xcd<\xcd\x0e\xc5(\xce\x0c\xc8'" + b'\xce\n\xcb&\xce\x07\xce&\xd0\x03\xd0&\xe3&\xe3&' + b"\xe3&\xe2'\xe1(\xe0)\xdf*\xdd,\xdc.\xd90" + b'\xd63\xd47\xcf<\xc9?\xff\xff\xa9' +) + +# 2-bit RLE, generated from res/hangup.png, 104 bytes +hangup = ( + b'\x02' + b'HH' + b'?\xff\xff\xff\xff\xff8\xd40\xdc)\xe3"\xe7 \xea' + b'\x1c\xee\x19\xf0\x17\xf2\x15\xf4\x13\xf6\x11\xda\x05\xd8\x11\xd4' + b'\x10\xd4\x0f\xd2\x15\xd2\x0f\xd1\x17\xd2\x0e\xd1\x17\xd2\r\xd2' + b'\x17\xd2\r\xd2\x17\xd2\r\xd2\x17\xd2\r\xd2\x17\xd2\r\xd2' + b'\x17\xd2\r\xd2\x17\xd2\r\xd2\x18\xd1\r\xd1\x19\xd1\r\xd1' + b'\x1a\xd0\r\xd0\x1d\xce\x0e\xcd!\xcb\x10\xc8?\xff\xff\xff' + b'\xff\xff\xff\xffP' +) \ No newline at end of file diff --git a/wasp/wasp.py b/wasp/wasp.py index abcb974..a3f03ec 100644 --- a/wasp/wasp.py +++ b/wasp/wasp.py @@ -30,6 +30,7 @@ from apps.settings import SettingsApp from apps.steps import StepCounterApp from apps.software import SoftwareApp from apps.stopwatch import StopwatchApp +from apps.phone import PhoneApp class EventType(): """Enumerated interface actions. @@ -117,6 +118,8 @@ class Manager(): self.notifications = {} self.musicstate = {} self.musicinfo = {} + self.call = PhoneApp() + self.phonestate = {} self._theme = ( b'\x7b\xef' # ble @@ -320,6 +323,12 @@ class Manager(): def set_music_info(self, info): self.musicinfo = info + def set_phone_state(self, info): + self.phonestate = info + if(self.phonestate.get("cmd") == "start" or self.phonestate.get("cmd") == "incoming"): + self.wake() + self.switch(self.call) + def set_alarm(self, time, action): """Queue an alarm.