From 858d976eff4ea702822965504ebdbca2bfba9ba0 Mon Sep 17 00:00:00 2001 From: Anodynous Date: Fri, 19 Mar 2021 21:02:39 +0200 Subject: [PATCH 01/30] fix requirements --- plover_plugin/setup.cfg | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plover_plugin/setup.cfg b/plover_plugin/setup.cfg index a50e7bb..f57fb3a 100644 --- a/plover_plugin/setup.cfg +++ b/plover_plugin/setup.cfg @@ -21,9 +21,10 @@ keywords = plover plover_plugin stenogotchi zip_safe = True install_requires = plover>=4.0.0.dev8 - jsonlib-python3 + jsonpickle dbus-python textstat + PyGObject packages = stenogotchi_link From d6b9112eb811388da77c26382aeaa9a1ba611a88 Mon Sep 17 00:00:00 2001 From: Anodynous Date: Fri, 19 Mar 2021 21:14:23 +0200 Subject: [PATCH 02/30] Add requirements --- requirements.txt | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 requirements.txt diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..bd88702 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,13 @@ +PyGObject == 3.30.4 +dbus_python == 1.2.16 +pydbus == 0.6.0 +evdev == 1.4.0 +RPi.GPIO == 0.7.0 +file_read_backwards == 2.0.0 +toml == 0.10.2 +spidev == 3.5 +Flask == 1.1.2 +Flask_Cors == 3.0.10 +Flask_WTF == 0.14.3 +requests == 2.25.1 +Pillow == 8.1.2 \ No newline at end of file From 3263dd401168a1a5dcaec13826698062f7fe9784 Mon Sep 17 00:00:00 2001 From: Anodynous Date: Fri, 19 Mar 2021 21:59:03 +0200 Subject: [PATCH 03/30] Reboot at name change --- stenogotchi/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/stenogotchi/__init__.py b/stenogotchi/__init__.py index a6b6b06..dba517a 100644 --- a/stenogotchi/__init__.py +++ b/stenogotchi/__init__.py @@ -40,7 +40,7 @@ def set_name(new_name): fp.write(patched) os.system("hostname '%s'" % new_name) - stenogotchi.reboot() + reboot() def name(): @@ -161,7 +161,6 @@ def restart(mode): else: os.system("touch /root/.stenogotchi-manual") - #os.system("service bettercap restart") #os.system("service stenogotchi restart") From c8a371069b3eee19748dbd2c88a2244cd6769292 Mon Sep 17 00:00:00 2001 From: Anodynous Date: Fri, 19 Mar 2021 22:02:06 +0200 Subject: [PATCH 04/30] Minor changes --- README.md | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 8280730..1e273bd 100644 --- a/README.md +++ b/README.md @@ -122,7 +122,19 @@ All commands should be executed as root. The installation process can be complet [Plugins] enabled_extensions = ["stenogotchi_link"] -11. Significantly reduce boot time +11. Launch Stenogotchi manually for initial setup. Configure settings after reboot completes. + + python3 ./stenogotchi/stenogotchi.py + nano /etc/stenogotchi/config.toml + + #----------modify the config as you see fit----------# + main.plugins.buttonshim.enabled = true + main.plugins.upslite.enabled = true + main.plugins.evdevkb.enabled = true + main.plugins.plover_link.bt_autoconnect_mac = 'DE:AD:BE:EF' + #---------- + +12. Significantly reduce boot time * Set ARM initial turbo to the max (60s) under dietpi-config > performance options to reduce boot time. You can also play around with overclocking, throttling and cpu governor to find a suitable balance between performance and power draw. * Disable dietpi and apt update check at boot: @@ -140,22 +152,10 @@ All commands should be executed as root. The installation process can be complet CONFIG_BOOT_WAIT_FOR_NETWORK=0 CONFIG_NTP_MODE=0 -12. Launch Stenogotchi once for initial setup (unless you already rebooted in previous step). Configure settings and reboot. - - python3 ./stenogotchi/stenogotchi.py - nano /etc/stenogotchi/config.toml - - #----------modify the config as you see fit----------# - main.plugins.evdevkb.enabled = true - main.plugins.buttonshim.enabled = true - main.plugins.plover_link.bt_autoconnect_mac = 'DE:AD:BE:EF' - #---------- - - reboot ## Configuration - Configuration files are placed in /etc/stenogotchi/ -- Create a file called config.toml with overrides to the defaults. Don't edit default.toml directly as it is overridden on version updates +- Create a file called config.toml with overrides to the defaults. Don't edit default.toml directly as it is overwritten on version updates. ## Usage ![stenogotchi_2](https://user-images.githubusercontent.com/17461433/107883149-d5539680-6ef5-11eb-86fe-41f0b6293eed.jpg) From 383d67d78ba3e063fffe5147b26c5a383f4fa465 Mon Sep 17 00:00:00 2001 From: Anodynous Date: Fri, 19 Mar 2021 22:58:20 +0200 Subject: [PATCH 05/30] Add logtail plugin for viewing log through web ui --- stenogotchi/plugins/default/logtail.py | 273 +++++++++++++++++++++++++ 1 file changed, 273 insertions(+) create mode 100644 stenogotchi/plugins/default/logtail.py diff --git a/stenogotchi/plugins/default/logtail.py b/stenogotchi/plugins/default/logtail.py new file mode 100644 index 0000000..36c5705 --- /dev/null +++ b/stenogotchi/plugins/default/logtail.py @@ -0,0 +1,273 @@ +import os +import logging +import threading +from itertools import islice +from time import sleep +from datetime import datetime,timedelta +from stenogotchi import plugins +from stenogotchi.utils import StatusFile +from flask import render_template_string +from flask import jsonify +from flask import abort +from flask import Response + + +TEMPLATE = """ +{% extends "base.html" %} +{% set active_page = "plugins" %} +{% block title %} + Logtail +{% endblock %} + +{% block styles %} + {{ super() }} + +{% endblock %} + +{% block script %} + var table = document.getElementById('table'); + var filter = document.getElementById('filter'); + var filterVal = filter.value.toUpperCase(); + + var xhr = new XMLHttpRequest(); + xhr.open('GET', '{{ url_for('plugins') }}/logtail/stream'); + xhr.send(); + var position = 0; + var data; + var time; + var level; + var msg; + var colorClass; + + function handleNewData() { + var messages = xhr.responseText.split('\\n'); + filterVal = filter.value.toUpperCase(); + messages.slice(position, -1).forEach(function(value) { + + if (value.charAt(0) != '[') { + msg = value; + time = ''; + level = ''; + } else { + data = value.split(']'); + time = data.shift() + ']'; + level = data.shift() + ']'; + msg = data.join(']'); + + switch(level) { + case ' [INFO]': + colorClass = 'info'; + break; + case ' [WARNING]': + colorClass = 'warning'; + break; + case ' [ERROR]': + colorClass = 'error'; + break; + case ' [DEBUG]': + colorClass = 'debug'; + break; + default: + colorClass = 'default'; + break; + } + } + + var tr = document.createElement('tr'); + var td1 = document.createElement('td'); + var td2 = document.createElement('td'); + var td3 = document.createElement('td'); + + td1.textContent = time; + td2.textContent = level; + td3.textContent = msg; + + tr.appendChild(td1); + tr.appendChild(td2); + tr.appendChild(td3); + + tr.className = colorClass; + + if (filterVal.length > 0 && value.toUpperCase().indexOf(filterVal) == -1) { + tr.style.display = "none"; + } + + table.appendChild(tr); + }); + position = messages.length - 1; + } + + var scrollingElement = (document.scrollingElement || document.body) + function scrollToBottom () { + scrollingElement.scrollTop = scrollingElement.scrollHeight; + } + + var timer; + var scrollElm = document.getElementById('autoscroll'); + timer = setInterval(function() { + handleNewData(); + if (scrollElm.checked) { + scrollToBottom(); + } + if (xhr.readyState == XMLHttpRequest.DONE) { + clearInterval(timer); + } + }, 1000); + + var typingTimer; + var doneTypingInterval = 1000; + + filter.onkeyup = function() { + clearTimeout(typingTimer); + typingTimer = setTimeout(doneTyping, doneTypingInterval); + } + + filter.onkeydown = function() { + clearTimeout(typingTimer); + } + + function doneTyping() { + document.body.style.cursor = 'progress'; + var tr, tds, td, i, txtValue; + filterVal = filter.value.toUpperCase(); + tr = table.getElementsByTagName("tr"); + for (i = 1; i < tr.length; i++) { + txtValue = tr[i].textContent || tr[i].innerText; + if (txtValue.toUpperCase().indexOf(filterVal) > -1) { + tr[i].style.display = "table-row"; + } else { + tr[i].style.display = "none"; + } + } + document.body.style.cursor = 'default'; + } +{% endblock %} + +{% block content %} +
+ + +
+
+ + + + + + +
+ Time + + Level + + Message +
+{% endblock %} +""" + + +class Logtail(plugins.Plugin): + __author__ = '33197631+dadav@users.noreply.github.com' + __version__ = '0.1.0' + __license__ = 'GPL3' + __description__ = 'This plugin tails the logfile.' + + def __init__(self): + self.lock = threading.Lock() + self.options = dict() + self.ready = False + + def on_config_changed(self, config): + self.config = config + self.ready = True + + def on_loaded(self): + """ + Gets called when the plugin gets loaded + """ + logging.info("Logtail plugin loaded.") + + + def on_webhook(self, path, request): + if not self.ready: + return "Plugin not ready" + + if not path or path == "/": + return render_template_string(TEMPLATE) + + if path == 'stream': + def generate(): + with open(self.config['main']['log']['path']) as f: + yield ''.join(f.readlines()[-self.options.get('max-lines', 4096):]) + while True: + yield f.readline() + + return Response(generate(), mimetype='text/plain') + + abort(404) From ddad6794ede46f65cdb6ef27e16d145227d5932c Mon Sep 17 00:00:00 2001 From: Anodynous Date: Sat, 20 Mar 2021 02:08:19 +0200 Subject: [PATCH 06/30] Add option to specify bt name and multiple bt macs --- README.md | 15 ++++-- stenogotchi/defaults.toml | 1 + stenogotchi/plugins/default/__init__.py | 0 stenogotchi/plugins/default/plover_link.py | 61 ++++++++++++---------- 4 files changed, 47 insertions(+), 30 deletions(-) delete mode 100644 stenogotchi/plugins/default/__init__.py diff --git a/README.md b/README.md index 1e273bd..767d005 100644 --- a/README.md +++ b/README.md @@ -131,7 +131,7 @@ All commands should be executed as root. The installation process can be complet main.plugins.buttonshim.enabled = true main.plugins.upslite.enabled = true main.plugins.evdevkb.enabled = true - main.plugins.plover_link.bt_autoconnect_mac = 'DE:AD:BE:EF' + main.plugins.plover_link.bt_autoconnect_mac = '00:DE:AD:BE:EF:00,11:DE:AD:BE:EF:11' #---------- 12. Significantly reduce boot time @@ -154,8 +154,17 @@ All commands should be executed as root. The installation process can be complet ## Configuration -- Configuration files are placed in /etc/stenogotchi/ -- Create a file called config.toml with overrides to the defaults. Don't edit default.toml directly as it is overwritten on version updates. +* Configuration files are placed in /etc/stenogotchi/ + * Create a file called config.toml with overrides to the defaults. Don't edit default.toml directly as it is overwritten on version updates. +* Define your bluetooth devices in main.plugins.plover_link.bt_autoconnect_mac for auto-connect upon boot. Multiple comma separated devices in order of preference can be given. If no connection attempts are successful, the device will fall back to listening for incoming connection attempts. + * Issues with pairing or connecting after changes in bluetooth configurations can usually be fixed by unpairing the devices and re-pairing. On the Stenogotchi this is handled through bluetoothctl. + + bluetoothctl + [bluetooth]# paired-devices + Device 00:DE:AD:BE:EF:00 Anodynous' Ipad + [bluetooth]# remove 00:DE:AD:BE:EF:00 + [bluetooth]# exit + ## Usage ![stenogotchi_2](https://user-images.githubusercontent.com/17461433/107883149-d5539680-6ef5-11eb-86fe-41f0b6293eed.jpg) diff --git a/stenogotchi/defaults.toml b/stenogotchi/defaults.toml index 0562527..e35e6de 100644 --- a/stenogotchi/defaults.toml +++ b/stenogotchi/defaults.toml @@ -71,6 +71,7 @@ ui.display.color = "black" # Plugins main.plugins.plover_link.enabled = true main.plugins.plover_link.bt_autoconnect_mac = '' +main.plugins.plover_link.bt_device_name = 'Stenogotchi' main.plugins.evdevkb.enabled = true diff --git a/stenogotchi/plugins/default/__init__.py b/stenogotchi/plugins/default/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/stenogotchi/plugins/default/plover_link.py b/stenogotchi/plugins/default/plover_link.py index 27e55f9..eedca72 100644 --- a/stenogotchi/plugins/default/plover_link.py +++ b/stenogotchi/plugins/default/plover_link.py @@ -66,8 +66,6 @@ class BTKbDevice: """ create a bluetooth device to emulate a HID keyboard """ - # Set default name to use for BT Keyboard - MY_DEV_NAME = 'Bluetooth Keyboard' # Service port - must match port configured in SDP record P_CTRL = 17 # Service port - must match port configured in SDP record#Interrrupt port @@ -115,17 +113,20 @@ def __init__(self, hci=0): arg0=self.DEVICE_INTERFACE, path_keyword='path') - logging.info('PloverLink: Configuring for name {}'.format(BTKbDevice.MY_DEV_NAME)) - self.config_hid_profile() # set the Bluetooth device configuration - self.alias = BTKbDevice.MY_DEV_NAME + try: + self.alias = plugins.loaded['plover_link']._agent._config['main']['plugins']['plover_link']['bt_device_name'] + except: + self.alias = 'Bluetooth Keyboard' self.discoverabletimeout = 0 self.discoverable = True self.bthost_mac = None self.bthost_name = "" + logging.info('PloverLink: Configured BT device with name {}'.format(self.alias)) + def interfaces_added(self): pass @@ -277,32 +278,38 @@ def auto_connect(self): logging.info('PloverLink: No bt_autoconnect_mac set in config. Listening for incoming connections instead...') self.listen() else: - logging.info('PloverLink: Trying to auto connect to preferred BT host...') - try: - self.ccontrol = socket.socket(socket.AF_BLUETOOTH, - socket.SOCK_SEQPACKET, - socket.BTPROTO_L2CAP) - self.cinterrupt = socket.socket(socket.AF_BLUETOOTH, - socket.SOCK_SEQPACKET, - socket.BTPROTO_L2CAP) - self.ccontrol.connect((bt_autoconnect_mac, self.P_CTRL)) - self.cinterrupt.connect((bt_autoconnect_mac, self.P_INTR)) - - logging.info('PloverLink: Reconnected to ' + bt_autoconnect_mac) - self.bthost_mac = bt_autoconnect_mac - self.bthost_name = self.get_connected_device_name() - self._agent.set_bt_connected(self.bthost_name) + logging.info('PloverLink: Trying to auto connect to preferred BT host(s)...') + bt_mac_array = bt_autoconnect_mac.split(',') + for x in range (len(bt_mac_array)): + logging.info('PloverLink: Trying to auto connect to {}'.format(bt_mac_array[x])) + try: + self.ccontrol = socket.socket(socket.AF_BLUETOOTH, + socket.SOCK_SEQPACKET, + socket.BTPROTO_L2CAP) + self.cinterrupt = socket.socket(socket.AF_BLUETOOTH, + socket.SOCK_SEQPACKET, + socket.BTPROTO_L2CAP) + self.ccontrol.connect((bt_mac_array[x], self.P_CTRL)) + self.cinterrupt.connect((bt_mac_array[x], self.P_INTR)) + + logging.info('PloverLink: Reconnected to ' + bt_mac_array[x]) + self.bthost_mac = bt_mac_array[x] + self.bthost_name = self.get_connected_device_name() + self._agent.set_bt_connected(self.bthost_name) - except Exception as ex: - logging.info('PloverLink: Failed to auto_connect, falling back to listen mode to await new connection.' + str(ex)) - self.listen() + break # stop trying to auto connect upon success + except Exception as ex: + logging.info('PloverLink: Failed to auto connect due to reason: {}'.format(str(ex))) + if x == len(bt_mac_array) - 1: + logging.info('PloverLink: Unsuccessful auto connect attempt. Listening for incoming connections instead...') + self.listen() + def get_connected_device_name(self): - import pydbus # No point using both dbus and pydbus, should fix the code here to work without pydbus + import pydbus # TODO: See if dependency on pydbus can be removed bus = pydbus.SystemBus() - adapter = bus.get('org.bluez', self.dev_path) # doesn't seem to do anything mngr = bus.get('org.bluez', '/') mngd_objs = mngr.GetManagedObjects() @@ -336,7 +343,7 @@ def __init__(self): self._agent = plugins.loaded['plover_link']._agent self.device = BTKbDevice() # create and setup our BTKbDevice - self.device.auto_connect() # attemt to auto connect to preferred BT host before falling back to listening for new incoming connection + self.device.auto_connect() # connect to preferred bt_mac. If unspecified or unavailable fall back to awaiting incoming connections @dbus.service.method('com.github.stenogotchi', in_signature='ay') # bytearray def send_keys(self, cmd): @@ -380,7 +387,7 @@ def signal_to_plover(self, message): class PloverLink(ObjectClass): __autohor__ = 'Anodynous' - __version__ = '0.1' + __version__ = '0.2' __license__ = 'MIT' __description__ = 'This plugin enables connectivity to Plover through D-Bus. Note that it needs root permissions due to using sockets' From 82014b355eb762f2fce057fbdda57b6d0c2354e7 Mon Sep 17 00:00:00 2001 From: Anodynous Date: Sat, 20 Mar 2021 12:04:37 +0200 Subject: [PATCH 07/30] Add mention of RTC module --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 767d005..5d97d0b 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ All commands should be executed as root. The installation process can be complet apt-get install xserver-xorg-video-fbdev libtiff5 libopenjp2-7 bluez python3-rpi.gpio python3-gi screen rfkill -y pip3 install file_read_backwards flask flask-wtf flask-cors evdev python-xlib pillow spidev jsonpickle pydbus dbus-python -4. Clone the Plover repository and comment out PyQt5 and SIP from requirements_distribution. They will fail to install and need to be compiled from source, an 8+ hour process on the rpi0, if you want access to the Plover GUI. Luckily, they are redundant in our setup as the Stenogotchi runs headless. +4. Clone the Plover repository and comment out PyQt5 and SIP from requirements_distribution. They will fail to install and need to be compiled from source, an 8+ hour process on the RPI0w, if you want access to the Plover GUI. Luckily, they are redundant in our setup as the Stenogotchi runs headless. git clone https://github.com/openstenoproject/plover.git nano ./plover/requirements_distribution.txt @@ -144,7 +144,7 @@ All commands should be executed as root. The installation process can be complet CONFIG_CHECK_DIETPI_UPDATES=0 CONFIG_CHECK_APT_UPDATES=0 - * Disable waiting for network and time sync at boot (unless you always will have wifi available or need reliable timestamps in logs): + * Disable waiting for network and time sync at boot. Doing this you should be aware that the RPI0w does not have a hardware clock. It will lose track of real world time as soon it is powered off, making log timestamps or any time based action you may set up unreliable. None of this is important for the core functionality of the Stenogotchi and disabling time-sync at boot can shave up to a minute off the boot process. By adding a cheap I2C hardware clock you can completely remove the need for network sync. Many modules are small enough to fit in the empty space of the UPS-Lite or under the eINK screen [and are easy to wire](https://www.pishop.us/product/ds3231-real-time-clock-module-for-raspberry-pi/) and [set up](https://learn.adafruit.com/adding-a-real-time-clock-to-raspberry-pi/set-rtc-time). Just don't forget to isolate it with some tape. nano /boot/dietpi.txt From 9689f1c88c7b5f3eb1b672512f0a38a73c30feae Mon Sep 17 00:00:00 2001 From: Anodynous Date: Sat, 20 Mar 2021 12:30:02 +0200 Subject: [PATCH 08/30] Update ui mode indicator when plover is ready --- stenogotchi/ui/view.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/stenogotchi/ui/view.py b/stenogotchi/ui/view.py index 23e8c05..fc41b96 100644 --- a/stenogotchi/ui/view.py +++ b/stenogotchi/ui/view.py @@ -281,11 +281,15 @@ def on_plover_boot(self): def on_plover_ready(self): self.set('face', faces.AWAKE) self.set('status', self._voice.on_plover_ready()) + if self._state.get('mode') == 'NONE': + self.set('mode', 'STENO') self.update() def on_plover_quit(self): self.set('face', faces.BROKEN) self.set('status', 'Uh-oh... I think Plover just quit on us') + if self._state.get('mode') == 'STENO': + self.set('mode', 'NONE') self.update() def on_set_wpm(self, wpm): From 415ba08025367ebb4bf3bb72de0ef74cdceccd32 Mon Sep 17 00:00:00 2001 From: Anodynous Date: Sat, 20 Mar 2021 12:30:39 +0200 Subject: [PATCH 09/30] Version bump --- stenogotchi/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stenogotchi/_version.py b/stenogotchi/_version.py index 8dbfdad..cba8e59 100644 --- a/stenogotchi/_version.py +++ b/stenogotchi/_version.py @@ -1 +1 @@ -__version__ = '0.0.3' \ No newline at end of file +__version__ = '0.0.4' \ No newline at end of file From f8d5202268329b24cb47ef6c683164b8d6f459e2 Mon Sep 17 00:00:00 2001 From: Anodynous Date: Sat, 20 Mar 2021 14:27:12 +0200 Subject: [PATCH 10/30] Moved to class. Better logging and ui updates. --- stenogotchi/plugins/default/buttonshim.py | 707 +++++++++++----------- 1 file changed, 348 insertions(+), 359 deletions(-) diff --git a/stenogotchi/plugins/default/buttonshim.py b/stenogotchi/plugins/default/buttonshim.py index 1ad25c4..1c24a23 100644 --- a/stenogotchi/plugins/default/buttonshim.py +++ b/stenogotchi/plugins/default/buttonshim.py @@ -1,8 +1,11 @@ # ############################################################### +# Updated 20-03-2021 by Anodynous +# - Moved all functionality into class to remove dependency on global variables and improve integration with other plugins and core Stenogotchi functionality. # # Updated 13-01-2021 by Anodynous # - Added support for hold action in addition to press. -# Based on: https://github.com/pimoroni/button-shim/commit/143f35b4b56626bd7062bdff3245658af19822b4 +# Based on: https://github.com/evilsocket/pwnagotchi-plugins-contrib/blob/master/buttonshim.py +# which in turn is based on https://github.com/pimoroni/button-shim/commit/143f35b4b56626bd7062bdff3245658af19822b4 # ################################################################ @@ -26,22 +29,14 @@ import Queue as queue ADDR = 0x3f - -#adapted from version 0.0.2 -__version__ = '0.0.2x' - -_bus = None - LED_DATA = 7 LED_CLOCK = 6 - REG_INPUT = 0x00 REG_OUTPUT = 0x01 REG_POLARITY = 0x02 REG_CONFIG = 0x03 NUM_BUTTONS = 5 - BUTTON_A = 0 """Button A""" BUTTON_B = 1 @@ -52,22 +47,15 @@ """Button D""" BUTTON_E = 4 """Button E""" - NAMES = ['A', 'B', 'C', 'D', 'E'] """Sometimes you want to print the plain text name of the button that's triggered. - You can use:: - buttonshim.NAMES[button_index] - To accomplish this. - """ ERROR_LIMIT = 10 - FPS = 60 - LED_GAMMA = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, @@ -88,19 +76,7 @@ # The LED is an APA102 driven via the i2c IO expander. # We must set and clear the Clock and Data pins -# Each byte in _reg_queue represents a snapshot of the pin state - -_reg_queue = [] -_update_queue = [] -_brightness = 0.5 - -_led_queue = queue.Queue() - -_t_poll = None - -_running = False - -_states = 0b00011111 +# Each byte in self._reg_queue represents a snapshot of the pin state class Handler(): @@ -120,437 +96,450 @@ def __init__(self, plugin): self.hold_fired = False self.plugin = plugin -_handlers = [None,None,None,None,None] -_button_was_held = False -_plover_wpm_meters_enabled = False - -def _run(): - global _running, _states - _running = True - _last_states = 0b00011111 - _errors = 0 - while _running: - led_data = None - - try: - led_data = _led_queue.get(False) - _led_queue.task_done() +class Buttonshim(plugins.Plugin): + __author__ = 'Anodynous' + __version__ = '0.0.2' + __license__ = 'GPL3' + __description__ = 'Pimoroni Button Shim GPIO Button and RGB LED support plugin based on the pimoroni-buttonshim-lib and the pwnagotchi-gpio-buttons-plugin' - except queue.Empty: - pass + def __init__(self): + self._agent = None + self.running = False + self.options = dict() + self._running = False + self._plover_wpm_meters_enabled = False + + self._states = None + self._bus = None + self._reg_queue = [] + self._update_queue = [] + self._brightness = 0.5 + self._led_queue = queue.Queue() + self._t_poll = None + self._running = False + self._states = 0b00011111 + self._handlers = [None,None,None,None,None] + self._button_was_held = False + - try: - if led_data: - for chunk in _chunk(led_data, 32): - _bus.write_i2c_block_data(ADDR, REG_OUTPUT, chunk) + def on_loaded(self): + logging.info("[buttonshim] GPIO Button plugin loaded.") + self.running = True + self._handlers = [Handler(self) for x in range(NUM_BUTTONS)] + self.on_press([BUTTON_A, BUTTON_B, BUTTON_C, BUTTON_D, BUTTON_E], self.press_handler) + self.on_hold([BUTTON_A, BUTTON_B, BUTTON_C, BUTTON_D, BUTTON_E], self.hold_handler) + self.on_release([BUTTON_A, BUTTON_B, BUTTON_C, BUTTON_D, BUTTON_E], self.release_handler) - _states = _bus.read_byte_data(ADDR, REG_INPUT) + def on_ready(self, agent): + self._agent = agent - except IOError: - _errors += 1 - if _errors > ERROR_LIMIT: - _running = False - raise IOError("More than {} IO errors have occurred!".format(ERROR_LIMIT)) + def set_ui_update(self, key, value): + self._agent.view().set(key, value) - for x in range(NUM_BUTTONS): - last = (_last_states >> x) & 1 - curr = (_states >> x) & 1 - handler = _handlers[x] + def trigger_ui_update(self): + self._agent.view().update() - # If last > curr then it's a transition from 1 to 0 - # since the buttons are active low, that's a press event - if last > curr: - handler.t_pressed = time.time() - handler.hold_fired = False + def _run(self): + self._running = True + _last_states = 0b00011111 + _errors = 0 - if callable(handler.press): - handler.t_repeat = time.time() - Thread(target=handler.press, args=(x, True, handler.plugin)).start() + while self._running: + led_data = None - continue + try: + led_data = self._led_queue.get(False) + self._led_queue.task_done() - if last < curr and callable(handler.release): - Thread(target=handler.release, args=(x, False, handler.plugin)).start() - continue + except queue.Empty: + pass - if curr == 0: - if callable(handler.hold) and not handler.hold_fired and (time.time() - handler.t_pressed) > handler.hold_time: - Thread(target=handler.hold, args=(x,)).start() - handler.hold_fired = True + try: + if led_data: + for chunk in self._chunk(led_data, 32): + self._bus.write_i2c_block_data(ADDR, REG_OUTPUT, chunk) - if handler.repeat and callable(handler.press) and (time.time() - handler.t_repeat) > handler.repeat_time: - _handlers[x].t_repeat = time.time() - Thread(target=_handlers[x].press, args=(x, True, handler.plugin)).start() + self._states = self._bus.read_byte_data(ADDR, REG_INPUT) - _last_states = _states + except IOError: + _errors += 1 + if _errors > ERROR_LIMIT: + self._running = False + raise IOError("More than {} IO errors have occurred!".format(ERROR_LIMIT)) - time.sleep(1.0 / FPS) + for x in range(NUM_BUTTONS): + last = (_last_states >> x) & 1 + curr = (self._states >> x) & 1 + handler = self._handlers[x] + # If last > curr then it's a transition from 1 to 0 + # since the buttons are active low, that's a press event + if last > curr: + handler.t_pressed = time.time() + handler.hold_fired = False -def _quit(): - global _running + if callable(handler.press): + handler.t_repeat = time.time() + Thread(target=handler.press, args=(x, True, handler.plugin)).start() - if _running: - _led_queue.join() - set_pixel(0, 0, 0) - _led_queue.join() + continue - _running = False - _t_poll.join() + if last < curr and callable(handler.release): + Thread(target=handler.release, args=(x, False, handler.plugin)).start() + continue + if curr == 0: + if callable(handler.hold) and not handler.hold_fired and (time.time() - handler.t_pressed) > handler.hold_time: + Thread(target=handler.hold, args=(x,)).start() + handler.hold_fired = True -def setup(): - global _t_poll, _bus + if handler.repeat and callable(handler.press) and (time.time() - handler.t_repeat) > handler.repeat_time: + self._handlers[x].t_repeat = time.time() + Thread(target=self._handlers[x].press, args=(x, True, handler.plugin)).start() - if _bus is not None: - return + _last_states = self._states - _bus = smbus.SMBus(1) + time.sleep(1.0 / FPS) - _bus.write_byte_data(ADDR, REG_CONFIG, 0b00011111) - _bus.write_byte_data(ADDR, REG_POLARITY, 0b00000000) - _bus.write_byte_data(ADDR, REG_OUTPUT, 0b00000000) - _t_poll = Thread(target=_run) - _t_poll.daemon = True - _t_poll.start() + def _quit(self): - set_pixel(0, 0, 0) + if self._running: + self._led_queue.join() + self.set_pixel(0, 0, 0) + self._led_queue.join() - atexit.register(_quit) + self._running = False + self._t_poll.join() -def _set_bit(pin, value): - global _reg_queue + def setup(self): + if self._bus is not None: + return - if value: - _reg_queue[-1] |= (1 << pin) - else: - _reg_queue[-1] &= ~(1 << pin) + self._bus = smbus.SMBus(1) + self._bus.write_byte_data(ADDR, REG_CONFIG, 0b00011111) + self._bus.write_byte_data(ADDR, REG_POLARITY, 0b00000000) + self._bus.write_byte_data(ADDR, REG_OUTPUT, 0b00000000) -def _next(): - global _reg_queue + self._t_poll = Thread(target=self._run) + self._t_poll.daemon = True + self._t_poll.start() - if len(_reg_queue) == 0: - _reg_queue = [0b00000000] - else: - _reg_queue.append(_reg_queue[-1]) + self.set_pixel(0, 0, 0) + atexit.register(self._quit) -def _enqueue(): - global _reg_queue - _led_queue.put(_reg_queue) + def _set_bit(self, pin, value): + if value: + self._reg_queue[-1] |= (1 << pin) + else: + self._reg_queue[-1] &= ~(1 << pin) - _reg_queue = [] + def _next(self): + if len(self._reg_queue) == 0: + self._reg_queue = [0b00000000] + else: + self._reg_queue.append(self._reg_queue[-1]) -def _chunk(l, n): - for i in range(0, len(l)+1, n): - yield l[i:i + n] + def _enqueue(self): + self._led_queue.put(self._reg_queue) -def _write_byte(byte): - for x in range(8): - _next() - _set_bit(LED_CLOCK, 0) - _set_bit(LED_DATA, byte & 0b10000000) - _next() - _set_bit(LED_CLOCK, 1) - byte <<= 1 + self._reg_queue = [] -def on_hold(buttons, handler=None, hold_time=1): - """Attach a hold handler to one or more buttons. + def _chunk(self, l, n): + for i in range(0, len(l)+1, n): + yield l[i:i + n] - This handler is fired when you hold a button for hold_time seconds. - When fired it will run in its own Thread. + def _write_byte(self, byte): + for i in range(8): + self._next() + self._set_bit(LED_CLOCK, 0) + self._set_bit(LED_DATA, byte & 0b10000000) + self._next() + self._set_bit(LED_CLOCK, 1) + byte <<= 1 - It will be passed one argument, the button index:: - @buttonshim.on_hold(buttonshim.BUTTON_A) - def handler(button): - # Your code here + def on_hold(self, buttons, handler=None, hold_time=1): + """Attach a hold handler to one or more buttons. - :param buttons: A single button, or a list of buttons - :param handler: Optional: a function to bind as the handler - :param hold_time: Optional: the hold time in seconds (default 2) + This handler is fired when you hold a button for hold_time seconds. - """ - setup() + When fired it will run in its own Thread. - if buttons is None: - buttons = [BUTTON_A, BUTTON_B, BUTTON_C, BUTTON_D, BUTTON_E] + It will be passed one argument, the button index:: - if isinstance(buttons, int): - buttons = [buttons] + @buttonshim.on_hold(buttonshim.BUTTON_A) + def handler(button): + # Your code here - def attach_handler(handler): - for button in buttons: - _handlers[button].hold = handler - _handlers[button].hold_time = hold_time + :param buttons: A single button, or a list of buttons + :param handler: Optional: a function to bind as the handler + :param hold_time: Optional: the hold time in seconds (default 2) - if handler is not None: - attach_handler(handler) - else: - return attach_handler + """ + self.setup() + if buttons is None: + buttons = [BUTTON_A, BUTTON_B, BUTTON_C, BUTTON_D, BUTTON_E] -def on_press(buttons, handler=None, repeat=False, repeat_time=0.5): - """Attach a press handler to one or more buttons. + if isinstance(buttons, int): + buttons = [buttons] - This handler is fired when you press a button. + def attach_handler(handler): + for button in buttons: + self._handlers[button].hold = handler + self._handlers[button].hold_time = hold_time - When fired it will be run in its own Thread. + if handler is not None: + attach_handler(handler) + else: + return attach_handler - It will be passed two arguments, the button index and a - boolean indicating whether the button has been pressed/released:: - @buttonshim.on_press(buttonshim.BUTTON_A) - def handler(button, pressed): - # Your code here + def on_press(self, buttons, handler=None, repeat=False, repeat_time=0.5): + """Attach a press handler to one or more buttons. - :param buttons: A single button, or a list of buttons - :param handler: Optional: a function to bind as the handler - :param repeat: Optional: Repeat the handler if the button is held - :param repeat_time: Optional: Time, in seconds, after which to repeat + This handler is fired when you press a button. - """ - setup() + When fired it will be run in its own Thread. - if buttons is None: - buttons = [BUTTON_A, BUTTON_B, BUTTON_C, BUTTON_D, BUTTON_E] + It will be passed two arguments, the button index and a + boolean indicating whether the button has been pressed/released:: - if isinstance(buttons, int): - buttons = [buttons] + @buttonshim.on_press(buttonshim.BUTTON_A) + def handler(button, pressed): + # Your code here - def attach_handler(handler): - for button in buttons: - _handlers[button].press = handler - _handlers[button].repeat = repeat - _handlers[button].repeat_time = repeat_time + :param buttons: A single button, or a list of buttons + :param handler: Optional: a function to bind as the handler + :param repeat: Optional: Repeat the handler if the button is held + :param repeat_time: Optional: Time, in seconds, after which to repeat - if handler is not None: - attach_handler(handler) - else: - return attach_handler + """ + self.setup() + if buttons is None: + buttons = [BUTTON_A, BUTTON_B, BUTTON_C, BUTTON_D, BUTTON_E] -def on_release(buttons=None, handler=None): - """Attach a release handler to one or more buttons. + if isinstance(buttons, int): + buttons = [buttons] - This handler is fired when you let go of a button. + def attach_handler(handler): + for button in buttons: + self._handlers[button].press = handler + self._handlers[button].repeat = repeat + self._handlers[button].repeat_time = repeat_time - When fired it will be run in its own Thread. + if handler is not None: + attach_handler(handler) + else: + return attach_handler - It will be passed two arguments, the button index and a - boolean indicating whether the button has been pressed/released:: - @buttonshim.on_release(buttonshim.BUTTON_A) - def handler(button, pressed): - # Your code here + def on_release(self, buttons=None, handler=None): + """Attach a release handler to one or more buttons. - :param buttons: A single button, or a list of buttons - :param handler: Optional: a function to bind as the handler + This handler is fired when you let go of a button. - """ - setup() + When fired it will be run in its own Thread. - if buttons is None: - buttons = [BUTTON_A, BUTTON_B, BUTTON_C, BUTTON_D, BUTTON_E] + It will be passed two arguments, the button index and a + boolean indicating whether the button has been pressed/released:: - if isinstance(buttons, int): - buttons = [buttons] + @buttonshim.on_release(buttonshim.BUTTON_A) + def handler(button, pressed): + # Your code here - def attach_handler(handler): - for button in buttons: - _handlers[button].release = handler + :param buttons: A single button, or a list of buttons + :param handler: Optional: a function to bind as the handler - if handler is not None: - attach_handler(handler) - else: - return attach_handler + """ + self.setup() + if buttons is None: + buttons = [BUTTON_A, BUTTON_B, BUTTON_C, BUTTON_D, BUTTON_E] -def set_brightness(brightness): - global _brightness + if isinstance(buttons, int): + buttons = [buttons] - setup() + def attach_handler(handler): + for button in buttons: + self._handlers[button].release = handler - if not isinstance(brightness, int) and not isinstance(brightness, float): - raise ValueError("Brightness should be an int or float") + if handler is not None: + attach_handler(handler) + else: + return attach_handler - if brightness < 0.0 or brightness > 1.0: - raise ValueError("Brightness should be between 0.0 and 1.0") - _brightness = brightness + def set_brightness(self, brightness): + self.setup() + if not isinstance(brightness, int) and not isinstance(brightness, float): + raise ValueError("Brightness should be an int or float") -def set_pixel(r, g, b): - """Set the Button SHIM RGB pixel + if brightness < 0.0 or brightness > 1.0: + raise ValueError("Brightness should be between 0.0 and 1.0") - Display an RGB colour on the Button SHIM pixel. + self._brightness = brightness - :param r: Amount of red, from 0 to 255 - :param g: Amount of green, from 0 to 255 - :param b: Amount of blue, from 0 to 255 - You can use HTML colours directly with hexadecimal notation in Python. EG:: + def set_pixel(self, r, g, b): + """Set the Button SHIM RGB pixel - buttonshim.set_pixel(0xFF, 0x00, 0xFF) + Display an RGB colour on the Button SHIM pixel. - """ - setup() + :param r: Amount of red, from 0 to 255 + :param g: Amount of green, from 0 to 255 + :param b: Amount of blue, from 0 to 255 - if not isinstance(r, int) or r < 0 or r > 255: - raise ValueError("Argument r should be an int from 0 to 255") + You can use HTML colours directly with hexadecimal notation in Python. EG:: - if not isinstance(g, int) or g < 0 or g > 255: - raise ValueError("Argument g should be an int from 0 to 255") + buttonshim.self.set_pixel(0xFF, 0x00, 0xFF) - if not isinstance(b, int) or b < 0 or b > 255: - raise ValueError("Argument b should be an int from 0 to 255") + """ + self.setup() - r, g, b = [int(x * _brightness) for x in (r, g, b)] + if not isinstance(r, int) or r < 0 or r > 255: + raise ValueError("Argument r should be an int from 0 to 255") - _write_byte(0) - _write_byte(0) - _write_byte(0b11101111) - _write_byte(LED_GAMMA[b & 0xff]) - _write_byte(LED_GAMMA[g & 0xff]) - _write_byte(LED_GAMMA[r & 0xff]) - _write_byte(0) - _write_byte(0) - _enqueue() + if not isinstance(g, int) or g < 0 or g > 255: + raise ValueError("Argument g should be an int from 0 to 255") -def blink(r, g, b, ontime, offtime, blinktimes): - logging.info("[buttonshim] Blink") - for i in range(0, blinktimes): - set_pixel(r, g, b) - time.sleep(ontime) - set_pixel(0, 0, 0) - time.sleep(offtime) + if not isinstance(b, int) or b < 0 or b > 255: + raise ValueError("Argument b should be an int from 0 to 255") -def press_handler(button, pressed, plugin): - """ On press reset button held status """ - global _button_was_held - _button_was_held = False + r, g, b = [int(x * self._brightness) for x in (r, g, b)] -def hold_handler(button): - """ On long press run built in internal Stenogotchi commands """ - # Set button held status to prevent release_handler from triggering on release - global _button_was_held - _button_was_held = True + self._write_byte(0) + self._write_byte(0) + self._write_byte(0b11101111) + self._write_byte(LED_GAMMA[b & 0xff]) + self._write_byte(LED_GAMMA[g & 0xff]) + self._write_byte(LED_GAMMA[r & 0xff]) + self._write_byte(0) + self._write_byte(0) + self._enqueue() - # Blink in response to long hold event - red = 0 - green = 70 - blue = 70 - on_time = 1 - off_time = 0 - blink_times = 1 - thread = Thread(target=blink, args=(red, green, blue, on_time, off_time, blink_times)) - thread.start() + def blink(self, r, g, b, ontime, offtime, blinktimes): + logging.info("[buttonshim] Blink") + for i in range(0, blinktimes): + self.set_pixel(r, g, b) + time.sleep(ontime) + self.set_pixel(0, 0, 0) + time.sleep(offtime) - def toggle_qwerty_steno(): - try: - cap_state = plugins.loaded['evdevkb'].get_capture_state() - if not cap_state: - plugins.loaded['evdevkb'].start_capture() - else: - plugins.loaded['evdevkb'].stop_capture() - except Exception as ex: - logging.error(f"BUTTONSHIM: Check if evdevkb is loaded, exceptio: {str(ex)}") - - def toggle_wpm_meters(): - global _plover_wpm_meters_enabled - command = {} + def press_handler(self, button, pressed, plugin): + """ On press reset button held status """ + self._button_was_held = False - if _plover_wpm_meters_enabled: - command = {'stop_wpm_meter': 'wpm and strokes'} # get these options from main config, add duration timing as well - elif not _plover_wpm_meters_enabled: - command = {'start_wpm_meter': 'wpm and strokes'} # get these options from main config, add duration timing as well + def hold_handler(self, button): + """ On long press run built in internal Stenogotchi commands """ + # Set button held status to prevent release_handler from triggering on release + self._button_was_held = True - _plover_wpm_meters_enabled = not _plover_wpm_meters_enabled - plugins.loaded['plover_link'].send_signal_to_plover(command) + # Blink in response to long hold event + red = 0 + green = 70 + blue = 70 + on_time = 1 + off_time = 0 + blink_times = 1 + thread = Thread(target=self.blink, args=(red, green, blue, on_time, off_time, blink_times)) + thread.start() - if NAMES[button] == 'A': - # Toggle QWERTY/STENO mode - toggle_qwerty_steno() - - elif NAMES[button] == 'B': - # Toggle WPM & strokes meters for Plover - toggle_wpm_meters() - - elif NAMES[button] == 'C': - # Clean the screen (should not be needed on waveshare_2, but could be useful on other display modules) - plugins.loaded['buttonshim']._agent.view().init_display() - plugins.loaded['buttonshim']._agent.view().update(force=True) - - elif NAMES[button] == 'D': - # Toggle wifi on/off - stenogotchi.set_wifi_onoff() - for i in range(5): - plugins.loaded['buttonshim']._agent._update_wifi() - time.sleep(2) + def toggle_qwerty_steno(): + try: + cap_state = plugins.loaded['evdevkb'].get_capture_state() + if not cap_state: + plugins.loaded['evdevkb'].start_capture() + logging.info(f"[buttonshim] Switched to QWERTY mode") + else: + plugins.loaded['evdevkb'].stop_capture() + logging.info(f"[buttonshim] Switched to STENO mode") + except Exception as ex: + logging.error(f"BUTTONSHIM: Check if evdevkb is loaded, exceptio: {str(ex)}") + + def toggle_wpm_meters(): + command = {} + + if self._plover_wpm_meters_enabled: + command = {'stop_wpm_meter': 'wpm and strokes'} # get these options from main config, add duration timing as well + self.set_ui_update('wpm', '') + self.set_ui_update('strokes', '') + self.trigger_ui_update() + logging.info(f"[buttonshim] Disabled WPM readings") + elif not self._plover_wpm_meters_enabled: + command = {'start_wpm_meter': 'wpm and strokes'} # get these options from main config, add duration timing as well + self.set_ui_update('wpm', '0') + self.set_ui_update('strokes', '0.00') + self.trigger_ui_update() + logging.info(f"[buttonshim] Enabled WPM readings") + + self._plover_wpm_meters_enabled = not self._plover_wpm_meters_enabled + plugins.loaded['plover_link'].send_signal_to_plover(command) + + if NAMES[button] == 'A': + # Toggle QWERTY/STENO mode + toggle_qwerty_steno() + + elif NAMES[button] == 'B': + # Toggle WPM & strokes meters for Plover + toggle_wpm_meters() + + elif NAMES[button] == 'C': + # Clean the screen (should not be needed on waveshare_2, but could be useful on other display modules) + plugins.loaded['buttonshim']._agent.view().init_display() + plugins.loaded['buttonshim']._agent.view().update(force=True) + logging.info(f"[buttonshim] Initiated screen refresh") - elif NAMES[button] == 'E': - # Initiate clean shutdown process - stenogotchi.shutdown() + elif NAMES[button] == 'D': + # Toggle wifi on/off + stenogotchi.set_wifi_onoff() + for i in range(5): + plugins.loaded['buttonshim']._agent._update_wifi() + time.sleep(2) + logging.info(f"[buttonshim] Toggled wifi state") + + elif NAMES[button] == 'E': + # Initiate clean shutdown process + logging.info(f"[buttonshim] Initiated clean shutdow") + stenogotchi.shutdown() -def release_handler(button, pressed, plugin): - """ On short press run command from config """ - if not _button_was_held: - logging.info(f"[buttonshim] Button Pressed! Loading command from slot '{button}' for button '{NAMES[button]}'") - bCfg = plugin.options['buttons'][NAMES[button]] - blinkCfg = bCfg['blink'] - logging.debug(blink) - if blinkCfg['enabled'] == True: - logging.debug(f"[buttonshim] Blinking led") - red = int(blinkCfg['red']) - green = int(blinkCfg['green']) - blue = int(blinkCfg['blue']) - on_time = float(blinkCfg['on_time']) - off_time = float(blinkCfg['off_time']) - blink_times = int(blinkCfg['blink_times']) - logging.debug(f"red {red} green {green} blue {blue} on_time {on_time} off_time {off_time} blink_times {blink_times}") - thread = Thread(target=blink, args=(red, green, blue, on_time, off_time, blink_times)) - thread.start() - logging.debug(f"[buttonshim] Blink thread started") - command = bCfg['command'] - if command == '': - logging.debug(f"[buttonshim] Command empty") - else: - logging.debug(f"[buttonshim] Process create: {command}") - process = subprocess.Popen(command, shell=True, stdin=None, stdout=open("/dev/null", "w"), stderr=None, executable="/bin/bash") - process.wait() - process = None - logging.debug(f"[buttonshim] Process end") - - -class Buttonshim(plugins.Plugin): - __author__ = 'gon@o2online.de' - __version__ = '0.0.1' - __license__ = 'GPL3' - __description__ = 'Pimoroni Button Shim GPIO Button and RGB LED support plugin based on the pimoroni-buttonshim-lib and the pwnagotchi-gpio-buttons-plugin' - - def __init__(self): - self._agent = None - self.running = False - self.options = dict() - global _handlers - _handlers = [Handler(self) for x in range(NUM_BUTTONS)] - on_press([BUTTON_A, BUTTON_B, BUTTON_C, BUTTON_D, BUTTON_E], press_handler) - on_hold([BUTTON_A, BUTTON_B, BUTTON_C, BUTTON_D, BUTTON_E], hold_handler) - on_release([BUTTON_A, BUTTON_B, BUTTON_C, BUTTON_D, BUTTON_E], release_handler) - - def on_loaded(self): - logging.info("[buttonshim] GPIO Button plugin loaded.") - self.running = True - - def on_ready(self, agent): - self._agent = agent - + def release_handler(self, button, pressed, plugin): + """ On short press run command from config """ + if not self._button_was_held: + logging.info(f"[buttonshim] Button Pressed! Loading command from slot '{button}' for button '{NAMES[button]}'") + bCfg = plugin.options['buttons'][NAMES[button]] + blinkCfg = bCfg['blink'] + logging.debug(self.blink) + if blinkCfg['enabled'] == True: + logging.debug(f"[buttonshim] Blinking led") + red = int(blinkCfg['red']) + green = int(blinkCfg['green']) + blue = int(blinkCfg['blue']) + on_time = float(blinkCfg['on_time']) + off_time = float(blinkCfg['off_time']) + blink_times = int(blinkCfg['blink_times']) + logging.debug(f"red {red} green {green} blue {blue} on_time {on_time} off_time {off_time} blink_times {blink_times}") + thread = Thread(target=self.blink, args=(red, green, blue, on_time, off_time, blink_times)) + thread.start() + logging.debug(f"[buttonshim] Blink thread started") + command = bCfg['command'] + if command == '': + logging.debug(f"[buttonshim] Command empty") + else: + logging.debug(f"[buttonshim] Process create: {command}") + process = subprocess.Popen(command, shell=True, stdin=None, stdout=open("/dev/null", "w"), stderr=None, executable="/bin/bash") + process.wait() + process = None + logging.debug(f"[buttonshim] Process end") From a160aeeb359061d3cbd6db18c1931032efa2aea7 Mon Sep 17 00:00:00 2001 From: Anodynous Date: Sat, 20 Mar 2021 15:35:34 +0200 Subject: [PATCH 11/30] User configurable wpm_method and wpm_timeout --- plover_plugin/setup.cfg | 4 +-- plover_plugin/stenogotchi_link/clients.py | 8 +++-- .../stenogotchi_link/stenogotchi_link.py | 14 ++++----- plover_plugin/stenogotchi_link/wpm.py | 30 +++++++++---------- stenogotchi/defaults.toml | 10 +++++++ stenogotchi/plugins/default/buttonshim.py | 19 ++++++++---- 6 files changed, 52 insertions(+), 33 deletions(-) diff --git a/plover_plugin/setup.cfg b/plover_plugin/setup.cfg index f57fb3a..98a6f53 100644 --- a/plover_plugin/setup.cfg +++ b/plover_plugin/setup.cfg @@ -1,7 +1,7 @@ [metadata] name = stenogotchi_link -version = 0.0.3 -description = A plugin for exposing Plover events and transmitting output to Stenogotchi +version = 0.0.4 +description = A plugin for exposing Plover events and communicating with Stenogotchi over D-Bus long_description = author = Anodynous author_email = diff --git a/plover_plugin/stenogotchi_link/clients.py b/plover_plugin/stenogotchi_link/clients.py index 198cbfb..5663c5e 100644 --- a/plover_plugin/stenogotchi_link/clients.py +++ b/plover_plugin/stenogotchi_link/clients.py @@ -75,13 +75,15 @@ def plover_strokes_stats(self, s): def stenogotchi_signal_handler(self, dict): # Enable and disable wpm/strokes meters if 'start_wpm_meter' in dict: + wpm_method = dict['wpm_method'] + wpm_timeout = int(dict['wpm_timeout']) logging.debug('Starting WPM meter') if dict['start_wpm_meter'] == 'wpm and strokes': - self._engineserver.start_wpm_meter(enable_wpm=True, enable_strokes=True) + self._engineserver.start_wpm_meter(enable_wpm=True, enable_strokes=True, method=wpm_method, timeout=wpm_timeout) elif dict['start_wpm_meter'] == 'wpm': - self._engineserver.start_wpm_meter(enable_wpm=True, enable_strokes=False) + self._engineserver.start_wpm_meter(enable_wpm=True, enable_strokes=False, method=wpm_method, timeout=wpm_timeout) elif dict['start_wpm_meter'] == 'strokes': - self._engineserver.start_wpm_meter(enable_wpm=False, enable_strokes=True) + self._engineserver.start_wpm_meter(enable_wpm=False, enable_strokes=True, method=wpm_method, timeout=wpm_timeout) if 'stop_wpm_meter' in dict: logging.debug('Stopping WPM meter') if dict['stop_wpm_meter'] == 'wpm and strokes': diff --git a/plover_plugin/stenogotchi_link/stenogotchi_link.py b/plover_plugin/stenogotchi_link/stenogotchi_link.py index 3956c7f..41b6d9e 100644 --- a/plover_plugin/stenogotchi_link/stenogotchi_link.py +++ b/plover_plugin/stenogotchi_link/stenogotchi_link.py @@ -47,13 +47,13 @@ def stop(self): self._disconnect_hooks() self._stenogotchiclient.plover_is_running(False) - def start_wpm_meter(self, enable_wpm=False, enable_strokes=False, method='ncra'): + def start_wpm_meter(self, enable_wpm=False, enable_strokes=False, wpm_method='ncra', wpm_timeout=60): """ Starts WPM and/or Strokes meters """ if enable_wpm: - self._wpm_meter = PloverWpmMeter(stenogotchi_link=self, wpm_method=method) + self._wpm_meter = PloverWpmMeter(stenogotchi_link=self, wpm_method=wpm_method, timeout=wpm_timeout) if enable_strokes: - self._strokes_meter = PloverStrokesMeter(stenogotchi_link=self, strokes_method=method) + self._strokes_meter = PloverStrokesMeter(stenogotchi_link=self, strokes_method=wpm_method, timeout=wpm_timeout) def stop_wpm_meter(self, disable_wpm=True, disable_strokes=True): if disable_wpm: @@ -64,12 +64,12 @@ def stop_wpm_meter(self, disable_wpm=True, disable_strokes=True): self._strokes_meter = None def _on_wpm_meter_update_strokes(self, stats): - """ Sends strokes stats from past 1 min to stenogotchi as a string """ - self._stenogotchiclient.plover_strokes_stats(stats['strokes60']) + """ Sends strokes stats to stenogotchi as a string """ + self._stenogotchiclient.plover_strokes_stats(stats['strokes_user']) def _on_wpm_meter_update_wpm(self, stats): - """ Sends wpm stats from past 1 min to stenogotchi as a string """ - self._stenogotchiclient.plover_wpm_stats(stats['wpm60']) + """ Sends wpm stats to stenogotchi as a string """ + self._stenogotchiclient.plover_wpm_stats(stats['wpm_user']) def get_server_status(self): """Gets the status of the server. diff --git a/plover_plugin/stenogotchi_link/wpm.py b/plover_plugin/stenogotchi_link/wpm.py index 8c02ebe..6e05192 100644 --- a/plover_plugin/stenogotchi_link/wpm.py +++ b/plover_plugin/stenogotchi_link/wpm.py @@ -72,12 +72,7 @@ def quit(self): class PloverWpmMeter(BaseMeter): - _TIMEOUTS = { - "wpm10": 10, - "wpm60": 60, - } - - def __init__(self, stenogotchi_link, wpm_method='ncra'): + def __init__(self, stenogotchi_link, wpm_method='ncra', timeout=60): super().__init__() self._stenogotchi_link = stenogotchi_link self.strokes = [] @@ -86,6 +81,10 @@ def __init__(self, stenogotchi_link, wpm_method='ncra'): 'traditional': False, # Traditional (by characters) 'spaces': False, # Spaces (by whitespace) } + self._timeouts = { + "wpm10": 10, + "wpm_user": timeout, + } self.set_wpm_method(wpm_method) self.wpm_stats = {} @@ -102,9 +101,9 @@ def get_stats(self): return self.wpm_stats def on_timer(self): - max_timeout = max(self._TIMEOUTS.values()) + max_timeout = max(self._timeouts.values()) self.chars = _filter_old_items(self.chars, max_timeout) - for name, timeout in self._TIMEOUTS.items(): + for name, timeout in self._timeouts.items(): chars = _filter_old_items(self.chars, timeout) wpm = _wpm_of_chars(chars, method=self.get_wpm_method()) self.wpm_stats[name] = str(wpm) @@ -115,12 +114,7 @@ def trigger_event_update(self): class PloverStrokesMeter(BaseMeter): - _TIMEOUTS = { - "strokes10": 10, - "strokes60": 60, - } - - def __init__(self, stenogotchi_link, strokes_method='ncra'): + def __init__(self, stenogotchi_link, strokes_method='ncra', timeout=60): super().__init__() self._stenogotchi_link = stenogotchi_link self.actions = [] @@ -129,6 +123,10 @@ def __init__(self, stenogotchi_link, strokes_method='ncra'): 'traditional': False, # Traditional (by characters) 'spaces': False, # Spaces (by whitespace) } + self._timeouts = { + "wpm10": 10, + "wpm_user": timeout, + } self.set_strokes_method(strokes_method) self.strokes_stats = {} @@ -156,10 +154,10 @@ def on_translation(self, old, new): self.actions += _timestamp_items(new) def on_timer(self): - max_timeout = max(self._TIMEOUTS.values()) + max_timeout = max(self._timeouts.values()) self.chars = _filter_old_items(self.chars, max_timeout) self.actions = _filter_old_items(self.actions, max_timeout) - for name, timeout in self._TIMEOUTS.items(): + for name, timeout in self._timeouts.items(): chars = _filter_old_items(self.chars, timeout) num_strokes = len(_filter_old_items(self.actions, timeout)) strokes_per_word = _spw_of_chars( diff --git a/stenogotchi/defaults.toml b/stenogotchi/defaults.toml index e35e6de..544d1e4 100644 --- a/stenogotchi/defaults.toml +++ b/stenogotchi/defaults.toml @@ -73,6 +73,16 @@ main.plugins.plover_link.enabled = true main.plugins.plover_link.bt_autoconnect_mac = '' main.plugins.plover_link.bt_device_name = 'Stenogotchi' +# For WPM readings a word is defined in one of three ways: +## NCRA: The National Court Reporters Association defines a “word” as 1.4 syllables. This is the measure used for official NCRA testing material. +## Traditional: The traditional metric for “word” in the context of keyboarding is defined to be 5 characters per word, including spaces. This is compatible with the notion of “word” in many typing speed utilities. +## Spaces: A word is a whitespace-separated sequence of characters. This metric of course doesn’t take into account the fact that some words are longer than others, both in length and syllables. +### specify either 'ncra', 'traditional' or 'spaces' as preferred method using main.plugins.plover_link.wpm_method +### specify in seconds moving time window for which wpm is calculated and updated using main.plugins.plover_link.wpm_timeout + +main.plugins.plover_link.wpm_method = 'ncra' +main.plugins.plover_link.wpm_timeout = '60' + main.plugins.evdevkb.enabled = true main.plugins.upslite.enabled = false diff --git a/stenogotchi/plugins/default/buttonshim.py b/stenogotchi/plugins/default/buttonshim.py index 1c24a23..82f241c 100644 --- a/stenogotchi/plugins/default/buttonshim.py +++ b/stenogotchi/plugins/default/buttonshim.py @@ -467,21 +467,30 @@ def toggle_qwerty_steno(): plugins.loaded['evdevkb'].stop_capture() logging.info(f"[buttonshim] Switched to STENO mode") except Exception as ex: - logging.error(f"BUTTONSHIM: Check if evdevkb is loaded, exceptio: {str(ex)}") + logging.error(f"BUTTONSHIM: Check if evdevkb is loaded, exception: {str(ex)}") def toggle_wpm_meters(): command = {} + try: + wpm_method = self._agent._config['main']['plugins']['plover_link']['wpm_method'] + wpm_timeout = self._agent._config['main']['plugins']['plover_link']['wpm_timeout'] + except Exception as ex: + logging.error(f"BUTTONSHIM: Check that wpm_method and wpm_timeout is configured. Falling back to defaults. Exception: {str(ex)}") + wpm_method = 'ncra' + wpm_timeout = '60' if self._plover_wpm_meters_enabled: - command = {'stop_wpm_meter': 'wpm and strokes'} # get these options from main config, add duration timing as well + command = {'stop_wpm_meter': 'wpm and strokes'} self.set_ui_update('wpm', '') self.set_ui_update('strokes', '') self.trigger_ui_update() logging.info(f"[buttonshim] Disabled WPM readings") elif not self._plover_wpm_meters_enabled: - command = {'start_wpm_meter': 'wpm and strokes'} # get these options from main config, add duration timing as well - self.set_ui_update('wpm', '0') - self.set_ui_update('strokes', '0.00') + command = {'stop_wpm_meter': 'wpm and strokes', + 'wpm_method' : wpm_method, + 'wpm_timeout' : wpm_timeout} + self.set_ui_update('wpm', wpm_method) + self.set_ui_update('strokes', wpm_timeout) self.trigger_ui_update() logging.info(f"[buttonshim] Enabled WPM readings") From d65b19a44f96bef891f26b1c8910af2fe8029e7d Mon Sep 17 00:00:00 2001 From: Anodynous Date: Sat, 20 Mar 2021 15:42:42 +0200 Subject: [PATCH 12/30] Formatting --- README.md | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 5d97d0b..c064f3e 100644 --- a/README.md +++ b/README.md @@ -38,38 +38,38 @@ All commands should be executed as root. The installation process can be complet 3. [5min] Install additional dependencies - apt-get install xserver-xorg-video-fbdev libtiff5 libopenjp2-7 bluez python3-rpi.gpio python3-gi screen rfkill -y - pip3 install file_read_backwards flask flask-wtf flask-cors evdev python-xlib pillow spidev jsonpickle pydbus dbus-python + apt-get install xserver-xorg-video-fbdev libtiff5 libopenjp2-7 bluez python3-rpi.gpio python3-gi screen rfkill -y + pip3 install file_read_backwards flask flask-wtf flask-cors evdev python-xlib pillow spidev jsonpickle pydbus dbus-python 4. Clone the Plover repository and comment out PyQt5 and SIP from requirements_distribution. They will fail to install and need to be compiled from source, an 8+ hour process on the RPI0w, if you want access to the Plover GUI. Luckily, they are redundant in our setup as the Stenogotchi runs headless. - git clone https://github.com/openstenoproject/plover.git - nano ./plover/requirements_distribution.txt - ... - #PyQt5-sip==4.19.13 - #PyQt5==5.11.3 - ... + git clone https://github.com/openstenoproject/plover.git + nano ./plover/requirements_distribution.txt + ... + #PyQt5-sip==4.19.13 + #PyQt5==5.11.3 + ... 5. [5min] Install Plover and plover-plugins - pip3 install --user -r ./plover/requirements.txt - pip3 install --user -e ./plover -r ./plover/requirements_plugins.txt --no-build-isolation + pip3 install --user -r ./plover/requirements.txt + pip3 install --user -e ./plover -r ./plover/requirements_plugins.txt --no-build-isolation 6. Clone the Stenogotchi repository and install the stenogotchi_link plover plugin - git clone https://github.com/Anodynous/stenogotchi.git - pip3 install ./stenogotchi/plover_plugin/ + git clone https://github.com/Anodynous/stenogotchi.git + pip3 install ./stenogotchi/plover_plugin/ 7. Add configuration file for the service used to communicate over D-Bus between Plover and Stenogotchi - cp ./stenogotchi/plover_plugin/stenogotchi_link/com.github.stenogotchi.conf /etc/dbus-1/system.d/ + cp ./stenogotchi/plover_plugin/stenogotchi_link/com.github.stenogotchi.conf /etc/dbus-1/system.d/ 8. Remove the input bluetooth plugin so that it does not grab the sockets we require access to. We make this the default behaviour by appending '-P input' to the pre-existing line in below service file. - nano /lib/systemd/system/bluetooth.service + nano /lib/systemd/system/bluetooth.service - #---------- - ExecStart=/usr/lib/bluetooth/bluetoothd -P input + #---------- + ExecStart=/usr/lib/bluetooth/bluetoothd -P input 9. Configure Plover and Stenogotchi to start at boot From d4a201444be05111f1b18e7d9a5f9548d6b528f9 Mon Sep 17 00:00:00 2001 From: Anodynous Date: Sat, 20 Mar 2021 15:55:03 +0200 Subject: [PATCH 13/30] Fix bug preventing starting wpm_meter --- stenogotchi/plugins/default/buttonshim.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stenogotchi/plugins/default/buttonshim.py b/stenogotchi/plugins/default/buttonshim.py index 82f241c..405b522 100644 --- a/stenogotchi/plugins/default/buttonshim.py +++ b/stenogotchi/plugins/default/buttonshim.py @@ -486,7 +486,7 @@ def toggle_wpm_meters(): self.trigger_ui_update() logging.info(f"[buttonshim] Disabled WPM readings") elif not self._plover_wpm_meters_enabled: - command = {'stop_wpm_meter': 'wpm and strokes', + command = {'start_wpm_meter': 'wpm and strokes', 'wpm_method' : wpm_method, 'wpm_timeout' : wpm_timeout} self.set_ui_update('wpm', wpm_method) From 002b4b90f7cbfef6d5abe1aca198800198740834 Mon Sep 17 00:00:00 2001 From: Anodynous Date: Sat, 20 Mar 2021 16:08:46 +0200 Subject: [PATCH 14/30] Bugfixes to wpm readings --- plover_plugin/stenogotchi_link/clients.py | 6 +++--- stenogotchi/plugins/default/buttonshim.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/plover_plugin/stenogotchi_link/clients.py b/plover_plugin/stenogotchi_link/clients.py index 5663c5e..036208a 100644 --- a/plover_plugin/stenogotchi_link/clients.py +++ b/plover_plugin/stenogotchi_link/clients.py @@ -79,11 +79,11 @@ def stenogotchi_signal_handler(self, dict): wpm_timeout = int(dict['wpm_timeout']) logging.debug('Starting WPM meter') if dict['start_wpm_meter'] == 'wpm and strokes': - self._engineserver.start_wpm_meter(enable_wpm=True, enable_strokes=True, method=wpm_method, timeout=wpm_timeout) + self._engineserver.start_wpm_meter(enable_wpm=True, enable_strokes=True, wpm_method=wpm_method, wpm_timeout=wpm_timeout) elif dict['start_wpm_meter'] == 'wpm': - self._engineserver.start_wpm_meter(enable_wpm=True, enable_strokes=False, method=wpm_method, timeout=wpm_timeout) + self._engineserver.start_wpm_meter(enable_wpm=True, enable_strokes=False, wpm_method=wpm_method, wpm_timeout=wpm_timeout) elif dict['start_wpm_meter'] == 'strokes': - self._engineserver.start_wpm_meter(enable_wpm=False, enable_strokes=True, method=wpm_method, timeout=wpm_timeout) + self._engineserver.start_wpm_meter(enable_wpm=False, enable_strokes=True, wpm_method=wpm_method, wpm_timeout=wpm_timeout) if 'stop_wpm_meter' in dict: logging.debug('Stopping WPM meter') if dict['stop_wpm_meter'] == 'wpm and strokes': diff --git a/stenogotchi/plugins/default/buttonshim.py b/stenogotchi/plugins/default/buttonshim.py index 405b522..bdced53 100644 --- a/stenogotchi/plugins/default/buttonshim.py +++ b/stenogotchi/plugins/default/buttonshim.py @@ -492,7 +492,7 @@ def toggle_wpm_meters(): self.set_ui_update('wpm', wpm_method) self.set_ui_update('strokes', wpm_timeout) self.trigger_ui_update() - logging.info(f"[buttonshim] Enabled WPM readings") + logging.info(f"[buttonshim] Enabled WPM readings using method {wpm_method} and timeout {wpm_timeout}") self._plover_wpm_meters_enabled = not self._plover_wpm_meters_enabled plugins.loaded['plover_link'].send_signal_to_plover(command) From 61d7aadb02212d61f494f704e58dcb9fcac3ba01 Mon Sep 17 00:00:00 2001 From: Anodynous Date: Sat, 20 Mar 2021 16:29:10 +0200 Subject: [PATCH 15/30] Log message spelling --- stenogotchi/plugins/default/buttonshim.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stenogotchi/plugins/default/buttonshim.py b/stenogotchi/plugins/default/buttonshim.py index bdced53..41237b6 100644 --- a/stenogotchi/plugins/default/buttonshim.py +++ b/stenogotchi/plugins/default/buttonshim.py @@ -521,7 +521,7 @@ def toggle_wpm_meters(): elif NAMES[button] == 'E': # Initiate clean shutdown process - logging.info(f"[buttonshim] Initiated clean shutdow") + logging.info(f"[buttonshim] Initiated clean shutdown") stenogotchi.shutdown() def release_handler(self, button, pressed, plugin): From 6dc2043f3a2f07e3dc7fbea36029e7c4bb438759 Mon Sep 17 00:00:00 2001 From: Anodynous Date: Sat, 20 Mar 2021 16:59:32 +0200 Subject: [PATCH 16/30] Bugfix preventing user defined strokes readings --- plover_plugin/stenogotchi_link/wpm.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plover_plugin/stenogotchi_link/wpm.py b/plover_plugin/stenogotchi_link/wpm.py index 6e05192..3f48a44 100644 --- a/plover_plugin/stenogotchi_link/wpm.py +++ b/plover_plugin/stenogotchi_link/wpm.py @@ -124,8 +124,8 @@ def __init__(self, stenogotchi_link, strokes_method='ncra', timeout=60): 'spaces': False, # Spaces (by whitespace) } self._timeouts = { - "wpm10": 10, - "wpm_user": timeout, + "strokes10": 10, + "strokes_user": timeout, } self.set_strokes_method(strokes_method) self.strokes_stats = {} From 6403d53152001919f351e5e034870d22aa00d691 Mon Sep 17 00:00:00 2001 From: Anodynous Date: Sat, 20 Mar 2021 17:20:11 +0200 Subject: [PATCH 17/30] Add option to clear eINK display at shutdown --- stenogotchi/__init__.py | 6 ++++++ stenogotchi/defaults.toml | 1 + 2 files changed, 7 insertions(+) diff --git a/stenogotchi/__init__.py b/stenogotchi/__init__.py index dba517a..5b69679 100644 --- a/stenogotchi/__init__.py +++ b/stenogotchi/__init__.py @@ -138,10 +138,16 @@ def shutdown(): logging.warning("shutting down ...") from stenogotchi.ui import view + if view.ROOT: view.ROOT.on_shutdown() # give it some time to refresh the ui time.sleep(10) + + if config['ui']['display']['clear_at_shutdown']: + from stenogotchi.ui.display import Display + display = Display(config=config) + display.clear() logging.warning("syncing...") diff --git a/stenogotchi/defaults.toml b/stenogotchi/defaults.toml index 544d1e4..78ede7b 100644 --- a/stenogotchi/defaults.toml +++ b/stenogotchi/defaults.toml @@ -67,6 +67,7 @@ ui.display.enabled = true ui.display.rotation = 0 ui.display.type = "waveshare_2" ui.display.color = "black" +ui.display.clear_at_shutdown = false # Plugins main.plugins.plover_link.enabled = true From cdec076d36657d385430637b5b71c9842b920d70 Mon Sep 17 00:00:00 2001 From: Anodynous Date: Sat, 20 Mar 2021 19:15:00 +0200 Subject: [PATCH 18/30] Truncate wpm method to fit ui --- stenogotchi/plugins/default/buttonshim.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/stenogotchi/plugins/default/buttonshim.py b/stenogotchi/plugins/default/buttonshim.py index 41237b6..e922c8f 100644 --- a/stenogotchi/plugins/default/buttonshim.py +++ b/stenogotchi/plugins/default/buttonshim.py @@ -489,9 +489,13 @@ def toggle_wpm_meters(): command = {'start_wpm_meter': 'wpm and strokes', 'wpm_method' : wpm_method, 'wpm_timeout' : wpm_timeout} - self.set_ui_update('wpm', wpm_method) - self.set_ui_update('strokes', wpm_timeout) + + wpm_method_ui = wpm_method[0:6] + wpm_timeout_ui = wpm_timeout + 's' + self.set_ui_update('wpm', wpm_method_ui) + self.set_ui_update('strokes', wpm_timeout_ui) self.trigger_ui_update() + logging.info(f"[buttonshim] Enabled WPM readings using method {wpm_method} and timeout {wpm_timeout}") self._plover_wpm_meters_enabled = not self._plover_wpm_meters_enabled From 20642b522f9fdf5cb0d81f8f83d97671f1db316f Mon Sep 17 00:00:00 2001 From: Anodynous Date: Sat, 20 Mar 2021 19:36:09 +0200 Subject: [PATCH 19/30] Remove comment --- stenogotchi/ui/hw/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/stenogotchi/ui/hw/__init__.py b/stenogotchi/ui/hw/__init__.py index a7afa92..116b5c2 100644 --- a/stenogotchi/ui/hw/__init__.py +++ b/stenogotchi/ui/hw/__init__.py @@ -1,4 +1,3 @@ -#from stenogotchi.ui.hw.waveshare2 import WaveshareV2 from stenogotchi.ui.hw.waveshare2 import WaveshareV2 def display_for(config): From 2d95bc2d51878af14885a29b0b0508fcfb306d76 Mon Sep 17 00:00:00 2001 From: Anodynous Date: Sat, 20 Mar 2021 20:50:53 +0200 Subject: [PATCH 20/30] Better clear_at_shutdown implementation --- stenogotchi/__init__.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/stenogotchi/__init__.py b/stenogotchi/__init__.py index 5b69679..7efdfe0 100644 --- a/stenogotchi/__init__.py +++ b/stenogotchi/__init__.py @@ -142,12 +142,13 @@ def shutdown(): if view.ROOT: view.ROOT.on_shutdown() # give it some time to refresh the ui - time.sleep(10) + time.sleep(5) - if config['ui']['display']['clear_at_shutdown']: - from stenogotchi.ui.display import Display - display = Display(config=config) - display.clear() + if view.ROOT._config['ui']['display']['clear_at_shutdown']: + view.ROOT._agent._view.init_display() + view.ROOT._agent._view.clear() + # give it some time to clear the ui + time.sleep(5) logging.warning("syncing...") From fc7c7b70acbf7aeb0f1c8aede3d33affe58afe19 Mon Sep 17 00:00:00 2001 From: Anodynous Date: Sat, 20 Mar 2021 21:59:44 +0200 Subject: [PATCH 21/30] Wider range of moods on common events --- stenogotchi/ui/view.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/stenogotchi/ui/view.py b/stenogotchi/ui/view.py index fc41b96..fa4df74 100644 --- a/stenogotchi/ui/view.py +++ b/stenogotchi/ui/view.py @@ -213,7 +213,8 @@ def wait(self, secs, sleeping=True): self.on_normal() def on_shutdown(self): - self.set('face', faces.SLEEP) + face = random.choice((faces.SLEEP, faces.SLEEP2)) + self.set('face', face) self.set('status', self._voice.on_shutdown()) self.update(force=True) self._frozen = True @@ -274,19 +275,22 @@ def on_custom(self, text): self.update() def on_plover_boot(self): - self.set('face', faces.SLEEP) + face = random.choice((faces.SLEEP, faces.SLEEP2, faces.BORED)) + self.set('face', face) self.set('status', self._voice.on_plover_boot()) self.update() def on_plover_ready(self): - self.set('face', faces.AWAKE) + face = random.choice((faces.AWAKE, faces.LOOK_R_HAPPY, faces.LOOK_L_HAPPY, faces.HAPPY, faces.EXCITED, faces.GRATEFUL)) + self.set('face', face) self.set('status', self._voice.on_plover_ready()) if self._state.get('mode') == 'NONE': self.set('mode', 'STENO') self.update() def on_plover_quit(self): - self.set('face', faces.BROKEN) + face = random.choice((faces.BROKEN, faces.DEBUG)) + self.set('face', face) self.set('status', 'Uh-oh... I think Plover just quit on us') if self._state.get('mode') == 'STENO': self.set('mode', 'NONE') From 7ae0d669f72bcc3a6c1f45de427e709787c70c53 Mon Sep 17 00:00:00 2001 From: Anodynous Date: Sat, 20 Mar 2021 22:01:56 +0200 Subject: [PATCH 22/30] Enable clear_at_shutdown to preserve eINK display --- stenogotchi/defaults.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stenogotchi/defaults.toml b/stenogotchi/defaults.toml index 78ede7b..cdeb219 100644 --- a/stenogotchi/defaults.toml +++ b/stenogotchi/defaults.toml @@ -67,7 +67,7 @@ ui.display.enabled = true ui.display.rotation = 0 ui.display.type = "waveshare_2" ui.display.color = "black" -ui.display.clear_at_shutdown = false +ui.display.clear_at_shutdown = true # Plugins main.plugins.plover_link.enabled = true From 100c6d89fcda9d8cb4a081d4038dc91e21c28ec8 Mon Sep 17 00:00:00 2001 From: Anodynous Date: Sat, 20 Mar 2021 22:30:16 +0200 Subject: [PATCH 23/30] Minor mood adjustment --- stenogotchi/ui/view.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stenogotchi/ui/view.py b/stenogotchi/ui/view.py index fa4df74..0416825 100644 --- a/stenogotchi/ui/view.py +++ b/stenogotchi/ui/view.py @@ -281,7 +281,7 @@ def on_plover_boot(self): self.update() def on_plover_ready(self): - face = random.choice((faces.AWAKE, faces.LOOK_R_HAPPY, faces.LOOK_L_HAPPY, faces.HAPPY, faces.EXCITED, faces.GRATEFUL)) + face = random.choice((faces.AWAKE, faces.LOOK_R_HAPPY, faces.HAPPY, faces.EXCITED, faces.GRATEFUL, faces.MOTIVATED)) self.set('face', face) self.set('status', self._voice.on_plover_ready()) if self._state.get('mode') == 'NONE': From 2841077a7af7df08b778ad58e2eb82d95d40854a Mon Sep 17 00:00:00 2001 From: Anodynous Date: Sat, 20 Mar 2021 22:44:04 +0200 Subject: [PATCH 24/30] Set default wpm method to traditional --- stenogotchi/defaults.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stenogotchi/defaults.toml b/stenogotchi/defaults.toml index cdeb219..0cd2b9c 100644 --- a/stenogotchi/defaults.toml +++ b/stenogotchi/defaults.toml @@ -81,7 +81,7 @@ main.plugins.plover_link.bt_device_name = 'Stenogotchi' ### specify either 'ncra', 'traditional' or 'spaces' as preferred method using main.plugins.plover_link.wpm_method ### specify in seconds moving time window for which wpm is calculated and updated using main.plugins.plover_link.wpm_timeout -main.plugins.plover_link.wpm_method = 'ncra' +main.plugins.plover_link.wpm_method = 'traditional' main.plugins.plover_link.wpm_timeout = '60' main.plugins.evdevkb.enabled = true From 296fda25871140608b98a6cb837647d05c9db0be Mon Sep 17 00:00:00 2001 From: Anodynous Date: Sat, 20 Mar 2021 22:44:30 +0200 Subject: [PATCH 25/30] Align wpm update frequency with user setting --- plover_plugin/stenogotchi_link/wpm.py | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/plover_plugin/stenogotchi_link/wpm.py b/plover_plugin/stenogotchi_link/wpm.py index 3f48a44..c9f54d2 100644 --- a/plover_plugin/stenogotchi_link/wpm.py +++ b/plover_plugin/stenogotchi_link/wpm.py @@ -46,12 +46,12 @@ def send_engine_command(self, c): class BaseMeter(): - def __init__(self): + def __init__(self, timeout=60): # Set timer to calculate wpm/strokes stats each second self._timer = RepeatTimer(1, self.on_timer) self._timer.start() # Set timer to publish wpm/strokes stats each minute - self._event_timer = RepeatTimer(60, self.trigger_event_update) + self._event_timer = RepeatTimer(timeout, self.trigger_event_update) self._event_timer.start() self.chars = [] @@ -73,7 +73,7 @@ def quit(self): class PloverWpmMeter(BaseMeter): def __init__(self, stenogotchi_link, wpm_method='ncra', timeout=60): - super().__init__() + super().__init__(timeout) self._stenogotchi_link = stenogotchi_link self.strokes = [] self.wpm_methods = { @@ -115,7 +115,7 @@ def trigger_event_update(self): class PloverStrokesMeter(BaseMeter): def __init__(self, stenogotchi_link, strokes_method='ncra', timeout=60): - super().__init__() + super().__init__(timeout) self._stenogotchi_link = stenogotchi_link self.actions = [] self.strokes_methods = { @@ -225,11 +225,3 @@ def _spw_of_chars(num_strokes, chars, method): return 0 return num_strokes / num_words - - -def _spw_of_chars(num_strokes, chars, method): - num_words = _words_in_chars(chars, method) - if not num_words: - return 0 - - return num_strokes / num_words \ No newline at end of file From 14f248f8f2e5b41d5be4fc2bfbe6a90620b4adef Mon Sep 17 00:00:00 2001 From: Anodynous Date: Sat, 20 Mar 2021 23:34:33 +0200 Subject: [PATCH 26/30] More consistent logging --- plover_plugin/stenogotchi_link/clients.py | 8 +-- .../stenogotchi_link/stenogotchi_link.py | 20 +++---- stenogotchi.py | 3 +- stenogotchi/fs/__init__.py | 4 +- stenogotchi/plugins/default/buttonshim.py | 10 ++-- stenogotchi/plugins/default/evdevkb.py | 24 ++++---- stenogotchi/plugins/default/example.py | 6 +- stenogotchi/plugins/default/logtail.py | 2 +- stenogotchi/plugins/default/memtemp.py | 2 +- stenogotchi/plugins/default/plover_link.py | 60 +++++++++---------- stenogotchi/plugins/default/upslite.py | 8 +-- 11 files changed, 74 insertions(+), 73 deletions(-) diff --git a/plover_plugin/stenogotchi_link/clients.py b/plover_plugin/stenogotchi_link/clients.py index 036208a..ee9b457 100644 --- a/plover_plugin/stenogotchi_link/clients.py +++ b/plover_plugin/stenogotchi_link/clients.py @@ -46,7 +46,7 @@ def _setup_object(self): dbus_interface=SERVER_DBUS, signal_name='signal_to_plover') except dbus.exceptions.DBusException as e: - logging.error(f'Failed to initialize D-Bus object: {str(e)}') + logging.error(f'[stenogotchi_link] Failed to initialize D-Bus object: {str(e)}') def _exit(self): self._mainloop.quit() @@ -77,7 +77,7 @@ def stenogotchi_signal_handler(self, dict): if 'start_wpm_meter' in dict: wpm_method = dict['wpm_method'] wpm_timeout = int(dict['wpm_timeout']) - logging.debug('Starting WPM meter') + logging.info('[stenogotchi_link] Starting WPM meter') if dict['start_wpm_meter'] == 'wpm and strokes': self._engineserver.start_wpm_meter(enable_wpm=True, enable_strokes=True, wpm_method=wpm_method, wpm_timeout=wpm_timeout) elif dict['start_wpm_meter'] == 'wpm': @@ -85,7 +85,7 @@ def stenogotchi_signal_handler(self, dict): elif dict['start_wpm_meter'] == 'strokes': self._engineserver.start_wpm_meter(enable_wpm=False, enable_strokes=True, wpm_method=wpm_method, wpm_timeout=wpm_timeout) if 'stop_wpm_meter' in dict: - logging.debug('Stopping WPM meter') + logging.info('[stenogotchi_link] Stopping WPM meter') if dict['stop_wpm_meter'] == 'wpm and strokes': self._engineserver.stop_wpm_meter(disable_wpm=True, disable_strokes=True) elif dict['stop_wpm_meter'] == 'wpm': @@ -172,7 +172,7 @@ def send_plover_keycode(self, keycode, modifiers=0): # if (modifiers & (1 << n)) #] if modifiers > 1: - logging.debug("Modifier received: " + str(modifiers) +" keycode" + str(keycode)) + logging.debug("[stenogotchi_link] Modifier received: " + str(modifiers) +" keycode" + str(keycode)) # Update modifier keys #for mod_keycode in modifiers_list: # self.update_mod_keys(plover_modkey(mod_keycode), 1) diff --git a/plover_plugin/stenogotchi_link/stenogotchi_link.py b/plover_plugin/stenogotchi_link/stenogotchi_link.py index 41b6d9e..64288c9 100644 --- a/plover_plugin/stenogotchi_link/stenogotchi_link.py +++ b/plover_plugin/stenogotchi_link/stenogotchi_link.py @@ -38,7 +38,7 @@ def __init__(self, engine: StenoEngine) -> None: def start(self): """ Starts the server. """ self._connect_hooks() - logging.debug("Plover_link started") + logging.info("[stenogotchi_link] Plover_link started") self._stenogotchiclient.plover_is_running(True) # Called when Plover exits or user disables the extension @@ -82,7 +82,7 @@ def _connect_hooks(self): """Creates hooks into all of Plover's events.""" if not self._engine: - logging.debug(ERROR_MISSING_ENGINE) + logging.error(f'[stenogotchi_link] {ERROR_MISSING_ENGINE}') raise AssertionError(ERROR_MISSING_ENGINE) for hook in self._engine.HOOKS: @@ -138,7 +138,7 @@ def _on_output_changed(self, enabled: bool): """ data = {'output_changed': enabled} - logging.debug(data) + logging.debug(f'[stenogotchi_link] _on_output_changed data: {data}') self._stenogotchiclient.plover_output_enabled(enabled) @@ -152,7 +152,7 @@ def _on_config_changed(self, config_update: Config): config_json = jsonpickle.encode(config_update, unpicklable=False) data = {'config_changed': json.loads(config_json)} - logging.debug(data) + logging.debug(f'[stenogotchi_link] _on_config_changed data: {data}') def _on_dictionaries_loaded(self, dictionaries: StenoDictionaryCollection): """Broadcasts when all of the dictionaries get loaded. @@ -187,31 +187,31 @@ def _on_send_key_combination(self, combination: str): """ data = {'send_key_combination': combination} - logging.debug(data) + logging.debug(f'[stenogotchi_link] _on_send_key_combination data: {data}') def _on_add_translation(self): """Broadcasts when the add translation tool is opened via a command.""" data = {'add_translation': True} - logging.debug(data) + logging.debug(f'[stenogotchi_link] _on_add_translation data: {data}') def _on_focus(self): """Broadcasts when the main window is focused via a command.""" data = {'focus': True} - logging.debug(data) + logging.debug(f'[stenogotchi_link] _on_focus data: {data}') def _on_configure(self): """Broadcasts when the configuration tool is opened via a command.""" data = {'configure': True} - logging.debug(data) + logging.debug(f'[stenogotchi_link] _on_configure data: {data}') def _on_lookup(self): """Broadcasts when the lookup tool is opened via a command.""" data = {'lookup': True} - logging.debug(data) + logging.debug(f'[stenogotchi_link] _on_lookup data: {data}') def _on_quit(self): """Broadcasts when the application is terminated. @@ -219,7 +219,7 @@ def _on_quit(self): """ data = {'quit': True} - logging.debug(data) + logging.debug(f'[stenogotchi_link] _on_quit data: {data}') self._stenogotchiclient.plover_is_running(False) diff --git a/stenogotchi.py b/stenogotchi.py index 1b283fe..03a6577 100644 --- a/stenogotchi.py +++ b/stenogotchi.py @@ -26,8 +26,7 @@ def do_manual_mode(agent): agent.mode = 'manual' agent.last_session.parse(agent.view(), args.skip_session) if not args.skip_session: - logging.info( - "the last session lasted %s" % (agent.last_session.duration_human)) + logging.info("the last session lasted %s" % (agent.last_session.duration_human)) while True: display.on_manual_mode(agent.last_session) diff --git a/stenogotchi/fs/__init__.py b/stenogotchi/fs/__init__.py index bf6ea84..4e3b97a 100644 --- a/stenogotchi/fs/__init__.py +++ b/stenogotchi/fs/__init__.py @@ -73,11 +73,11 @@ def setup_mounts(config): if not is_mounted: if not m.mount(): - logging.debug(f"Error while mounting {m.mountpoint}") + logging.error(f"Error while mounting {m.mountpoint}") continue if not m.sync(to_ram=True): - logging.debug(f"Error while syncing to {m.mountpoint}") + logging.error(f"Error while syncing to {m.mountpoint}") m.umount() continue diff --git a/stenogotchi/plugins/default/buttonshim.py b/stenogotchi/plugins/default/buttonshim.py index e922c8f..aa8e586 100644 --- a/stenogotchi/plugins/default/buttonshim.py +++ b/stenogotchi/plugins/default/buttonshim.py @@ -431,7 +431,7 @@ def set_pixel(self, r, g, b): self._enqueue() def blink(self, r, g, b, ontime, offtime, blinktimes): - logging.info("[buttonshim] Blink") + logging.debug("[buttonshim] Blink") for i in range(0, blinktimes): self.set_pixel(r, g, b) time.sleep(ontime) @@ -467,7 +467,7 @@ def toggle_qwerty_steno(): plugins.loaded['evdevkb'].stop_capture() logging.info(f"[buttonshim] Switched to STENO mode") except Exception as ex: - logging.error(f"BUTTONSHIM: Check if evdevkb is loaded, exception: {str(ex)}") + logging.exception(f"[buttonshim] Check if evdevkb is loaded, exception: {str(ex)}") def toggle_wpm_meters(): command = {} @@ -475,7 +475,7 @@ def toggle_wpm_meters(): wpm_method = self._agent._config['main']['plugins']['plover_link']['wpm_method'] wpm_timeout = self._agent._config['main']['plugins']['plover_link']['wpm_timeout'] except Exception as ex: - logging.error(f"BUTTONSHIM: Check that wpm_method and wpm_timeout is configured. Falling back to defaults. Exception: {str(ex)}") + logging.exception(f"[buttonshim] Check that wpm_method and wpm_timeout is configured. Falling back to defaults. Exception: {str(ex)}") wpm_method = 'ncra' wpm_timeout = '60' @@ -534,7 +534,7 @@ def release_handler(self, button, pressed, plugin): logging.info(f"[buttonshim] Button Pressed! Loading command from slot '{button}' for button '{NAMES[button]}'") bCfg = plugin.options['buttons'][NAMES[button]] blinkCfg = bCfg['blink'] - logging.debug(self.blink) + logging.debug(f'[buttonshim] {self.blink}'') if blinkCfg['enabled'] == True: logging.debug(f"[buttonshim] Blinking led") red = int(blinkCfg['red']) @@ -543,7 +543,7 @@ def release_handler(self, button, pressed, plugin): on_time = float(blinkCfg['on_time']) off_time = float(blinkCfg['off_time']) blink_times = int(blinkCfg['blink_times']) - logging.debug(f"red {red} green {green} blue {blue} on_time {on_time} off_time {off_time} blink_times {blink_times}") + logging.debug(f"[buttonshim] red {red} green {green} blue {blue} on_time {on_time} off_time {off_time} blink_times {blink_times}") thread = Thread(target=self.blink, args=(red, green, blue, on_time, off_time, blink_times)) thread.start() logging.debug(f"[buttonshim] Blink thread started") diff --git a/stenogotchi/plugins/default/evdevkb.py b/stenogotchi/plugins/default/evdevkb.py index 076ff4c..76c94b4 100644 --- a/stenogotchi/plugins/default/evdevkb.py +++ b/stenogotchi/plugins/default/evdevkb.py @@ -260,19 +260,20 @@ def get_keyboards(self): has_key_a = evdev.ecodes.KEY_A in device.capabilities().get(evdev.ecodes.EV_KEY, []) if has_key_a: keyboards.append(device) - logging.debug(f"Found keyboard '{device.name}' at path '{device.path}'") + logging.debug(f"[evdevkb] Found keyboard '{device.name}' at path '{device.path}'") return keyboards def set_keyboards(self): # Sets all keyboards as device to listen for key-inputs from - while not self.have_kb: - keyboards = self.get_keyboards() - if keyboards: - self.devs = keyboards - self.have_kb = True - else: - logging.debug('Keyboard not found, waiting 3 seconds and retrying') - sleep(3) + while self.do_capture: + while not self.have_kb: + keyboards = self.get_keyboards() + if keyboards: + self.devs = keyboards + self.have_kb = True + else: + logging.debug('[evdevkb] Keyboard not found, waiting 3 seconds and retrying') + sleep(3) def update_mod_keys(self, mod_key, value): """ @@ -361,15 +362,16 @@ def trigger_ui_update(self, input_mode): self._agent.view().update() def start_capture(self): - logging.debug('Capturing evdev keypress events...') + logging.info('[evdevkb] Capturing evdev keypress events...') self.trigger_ui_update('QWERTY') self.evdevkb = EvdevKbrd(skip_dbus=True) + self.evdevkb.set_do_capture(True) self.do_capture = True self.evdevkb.set_keyboards() self.evdevkb.event_loop() def stop_capture(self): - logging.debug('Ignoring evdev keypress events...') + logging.info('[evdevkb] Ignoring evdev keypress events...') self.evdevkb.set_do_capture(False) self.do_capture = False self.evdevkb = None diff --git a/stenogotchi/plugins/default/example.py b/stenogotchi/plugins/default/example.py index cc4af8e..8601677 100644 --- a/stenogotchi/plugins/default/example.py +++ b/stenogotchi/plugins/default/example.py @@ -13,7 +13,7 @@ class Example(plugins.Plugin): __description__ = 'An example plugin for pwnagotchi that implements all the available callbacks.' def __init__(self): - logging.debug("example plugin created") + logging.debug("[example] example plugin created") # called when http://:/plugins// is called # must return a html page @@ -23,7 +23,7 @@ def on_webhook(self, path, request): # called when the plugin is loaded def on_loaded(self): - logging.warning("WARNING: this plugin should be disabled! options = " % self.options) + logging.warning("[example] WARNING: this plugin should be disabled! options = " % self.options) # called before the plugin is unloaded def on_unload(self, ui): @@ -52,7 +52,7 @@ def on_display_setup(self, display): # called when everything is ready and the main loop is about to start def on_ready(self, agent): - logging.info("unit is ready") + logging.info("[example] unit is ready") # you can run custom bettercap commands if you want # agent.run('ble.recon on') # or set a custom state diff --git a/stenogotchi/plugins/default/logtail.py b/stenogotchi/plugins/default/logtail.py index 36c5705..685810a 100644 --- a/stenogotchi/plugins/default/logtail.py +++ b/stenogotchi/plugins/default/logtail.py @@ -251,7 +251,7 @@ def on_loaded(self): """ Gets called when the plugin gets loaded """ - logging.info("Logtail plugin loaded.") + logging.info("[logtail] Logtail plugin loaded.") def on_webhook(self, path, request): diff --git a/stenogotchi/plugins/default/memtemp.py b/stenogotchi/plugins/default/memtemp.py index 5e7427f..afedbdf 100644 --- a/stenogotchi/plugins/default/memtemp.py +++ b/stenogotchi/plugins/default/memtemp.py @@ -42,7 +42,7 @@ def __init__(self): self.cpu_load_avg = 0 def on_loaded(self): - logging.info("memtemp plugin loaded.") + logging.info("[memtemp] memtemp plugin loaded.") _thread.start_new_thread(self._cpu_poller(), ()) def mem_usage(self): diff --git a/stenogotchi/plugins/default/plover_link.py b/stenogotchi/plugins/default/plover_link.py index eedca72..02bce51 100644 --- a/stenogotchi/plugins/default/plover_link.py +++ b/stenogotchi/plugins/default/plover_link.py @@ -37,25 +37,25 @@ class HumanInterfaceDeviceProfile(dbus.service.Object): @dbus.service.method('org.bluez.Profile1', in_signature='', out_signature='') def Release(self): - logging.info('PloverLink: Release') + logging.info('[plover_link] PloverLink: Release') mainloop.quit() @dbus.service.method('org.bluez.Profile1', in_signature='oha{sv}', out_signature='') def NewConnection(self, path, fd, properties): self.fd = fd.take() - logging.info('PloverLink: NewConnection({}, {})'.format(path, self.fd)) + logging.info('[plover_link] NewConnection({}, {})'.format(path, self.fd)) for key in properties.keys(): if key == 'Version' or key == 'Features': - logging.info('PloverLink: {} = 0x{:04x}'.format(key, + logging.info('[plover_link] {} = 0x{:04x}'.format(key, properties[key])) else: - logging.info('PloverLink: {} = {}'.format(key, properties[key])) + logging.info('[plover_link] {} = {}'.format(key, properties[key])) @dbus.service.method('org.bluez.Profile1', in_signature='o', out_signature='') def RequestDisconnection(self, path): - logging.info('PloverLink: RequestDisconnection {}'.format(path)) + logging.info('[plover_link] RequestDisconnection {}'.format(path)) if self.fd > 0: os.close(self.fd) @@ -92,7 +92,7 @@ def __init__(self, hci=0): self.sinterrupt = None self.cinterrupt = None # Socket object for interrupt self.dev_path = '/org/bluez/hci{}'.format(hci) - logging.info('PloverLink: Setting up BT device') + logging.info('[plover_link] Setting up BT device') self.bus = dbus.SystemBus() self.adapter_methods = dbus.Interface( self.bus.get_object('org.bluez', @@ -125,7 +125,7 @@ def __init__(self, hci=0): self.bthost_mac = None self.bthost_name = "" - logging.info('PloverLink: Configured BT device with name {}'.format(self.alias)) + logging.info('[plover_link] Configured BT device with name {}'.format(self.alias)) def interfaces_added(self): pass @@ -137,7 +137,7 @@ def _properties_changed(self, interface, changed, invalidated, path): self.on_disconnect() def on_disconnect(self): - logging.info('PloverLink: The client has been disconnected') + logging.info('[plover_link] The client has been disconnected') self.bthost_mac = None self.bthost_name = "" self._agent.set_bt_disconnected() @@ -201,7 +201,7 @@ def config_hid_profile(self): Setup and register HID Profile """ - logging.info('PloverLink: Configuring Bluez Profile') + logging.info('[plover_link] Configuring Bluez Profile') service_record = self.read_sdp_service_record() opts = { @@ -223,7 +223,7 @@ def config_hid_profile(self): BTKbDevice.UUID, opts) - logging.info('PloverLink: Profile registered ') + logging.info('[plover_link] Profile registered ') @staticmethod def read_sdp_service_record(): @@ -231,7 +231,7 @@ def read_sdp_service_record(): Read and return SDP record from a file :return: (string) SDP record """ - logging.info('PloverLink: Reading service record') + logging.info('[plover_link] Reading service record') try: fh = open(BTKbDevice.SDP_RECORD_PATH, 'r') except OSError: @@ -244,7 +244,7 @@ def listen(self): Listen for connections coming from HID client """ - logging.info('PloverLink: Waiting for connections') + logging.info('[plover_link] Waiting for connections') self.scontrol = socket.socket(socket.AF_BLUETOOTH, socket.SOCK_SEQPACKET, socket.BTPROTO_L2CAP) @@ -261,10 +261,10 @@ def listen(self): self.sinterrupt.listen(1) self.ccontrol, cinfo = self.scontrol.accept() - logging.info('PloverLink: {} connected on the control socket'.format(cinfo[0])) + logging.info('[plover_link] {} connected on the control socket'.format(cinfo[0])) self.cinterrupt, cinfo = self.sinterrupt.accept() - logging.info('PloverLink: {} connected on the interrupt channel'.format(cinfo[0])) + logging.info('[plover_link] {} connected on the interrupt channel'.format(cinfo[0])) self.bthost_mac = cinfo[0] self.bthost_name = self.get_connected_device_name() @@ -275,13 +275,13 @@ def auto_connect(self): bt_autoconnect_mac = plugins.loaded['plover_link']._agent._config['main']['plugins']['plover_link']['bt_autoconnect_mac'] if not bt_autoconnect_mac: - logging.info('PloverLink: No bt_autoconnect_mac set in config. Listening for incoming connections instead...') + logging.info('[plover_link] No bt_autoconnect_mac set in config. Listening for incoming connections instead...') self.listen() else: - logging.info('PloverLink: Trying to auto connect to preferred BT host(s)...') + logging.info('[plover_link] Trying to auto connect to preferred BT host(s)...') bt_mac_array = bt_autoconnect_mac.split(',') for x in range (len(bt_mac_array)): - logging.info('PloverLink: Trying to auto connect to {}'.format(bt_mac_array[x])) + logging.info('[plover_link] Trying to auto connect to {}'.format(bt_mac_array[x])) try: self.ccontrol = socket.socket(socket.AF_BLUETOOTH, socket.SOCK_SEQPACKET, @@ -292,7 +292,7 @@ def auto_connect(self): self.ccontrol.connect((bt_mac_array[x], self.P_CTRL)) self.cinterrupt.connect((bt_mac_array[x], self.P_INTR)) - logging.info('PloverLink: Reconnected to ' + bt_mac_array[x]) + logging.info('[plover_link] Reconnected to ' + bt_mac_array[x]) self.bthost_mac = bt_mac_array[x] self.bthost_name = self.get_connected_device_name() self._agent.set_bt_connected(self.bthost_name) @@ -300,9 +300,9 @@ def auto_connect(self): break # stop trying to auto connect upon success except Exception as ex: - logging.info('PloverLink: Failed to auto connect due to reason: {}'.format(str(ex))) + logging.info('[plover_link] Failed to auto connect due to reason: {}'.format(str(ex))) if x == len(bt_mac_array) - 1: - logging.info('PloverLink: Unsuccessful auto connect attempt. Listening for incoming connections instead...') + logging.info('[plover_link] Unsuccessful auto connect attempt. Listening for incoming connections instead...') self.listen() @@ -318,7 +318,7 @@ def get_connected_device_name(self): if con_state: addr = mngd_objs[path].get('org.bluez.Device1', {}).get('Address') name = mngd_objs[path].get('org.bluez.Device1', {}).get('Name') - logging.info(f'PloverLink: Device {name} [{addr}] is connected') + logging.info(f'[plover_link] Device {name} [{addr}] is connected') return name @@ -337,7 +337,7 @@ class StenogotchiService(dbus.service.Object): HID messages from Stenogotchi evdevkb plugin """ def __init__(self): - logging.info('PloverLink: Setting up Stenogotchi D-Bus service') + logging.info('[plover_link] Setting up Stenogotchi D-Bus service') bus_name = dbus.service.BusName('com.github.stenogotchi', bus=dbus.SystemBus()) dbus.service.Object.__init__(self, bus_name, '/com/github/stenogotchi') @@ -351,7 +351,7 @@ def send_keys(self, cmd): @dbus.service.method('com.github.stenogotchi', in_signature='b') # boolean def plover_is_running(self, b): - logging.info('PloverLink: plover_is_running = ' + str(b)) + logging.debug('[plover_link] plover_is_running = ' + str(b)) if b: self._agent.set_plover_boot() else: @@ -359,25 +359,25 @@ def plover_is_running(self, b): @dbus.service.method('com.github.stenogotchi', in_signature='b') # boolean def plover_is_ready(self, b): - logging.info('PloverLink: plover_is_ready = ' + str(b)) + logging.debug('[plover_link] plover_is_ready = ' + str(b)) self._agent.set_plover_ready() @dbus.service.method('com.github.stenogotchi', in_signature='s') # string def plover_machine_state(self, s): - logging.info('PloverLink: plover_machine_state = ' + s) + logging.debug('[plover_link] plover_machine_state = ' + s) @dbus.service.method('com.github.stenogotchi', in_signature='b') # boolean def plover_output_enabled(self, b): - logging.info('PloverLink: plover_output_enabled = ' + str(b)) + logging.debug('[plover_link] plover_output_enabled = ' + str(b)) @dbus.service.method('com.github.stenogotchi', in_signature='s') # string def plover_wpm_stats(self, s): - logging.info('PloverLink: plover_wpm_stats = ' + s) + logging.debug('[plover_link] plover_wpm_stats = ' + s) self._agent.set_wpm_stats(s) @dbus.service.method('com.github.stenogotchi', in_signature='s') # string def plover_strokes_stats(self, s): - logging.info('PloverLink: plover_strokes_stats = ' + s) + logging.debug('[plover_link] plover_strokes_stats = ' + s) self._agent.set_strokes_stats(s) @dbus.service.signal('com.github.stenogotchi', signature='a{sv}') # dictionary of strings to variants @@ -408,9 +408,9 @@ def on_ready(self, agent): try: self.mainloop.run() self.running = True - logging.info("PloverLink: PloverLink is up") + logging.info("[plover_link] PloverLink is up") except: - logging.error("PloverLink: Could not start PloverLink") + logging.error("[plover_link] Could not start PloverLink") def on_unload(self, ui): self.mainloop.quit() diff --git a/stenogotchi/plugins/default/upslite.py b/stenogotchi/plugins/default/upslite.py index 3482594..e889543 100644 --- a/stenogotchi/plugins/default/upslite.py +++ b/stenogotchi/plugins/default/upslite.py @@ -50,7 +50,7 @@ def on_loaded(self): self._power_on_reset() self._quickstart() except: - logging.error("Could not start UPS-Lite plugin") + logging.error("[upslite] Could not start UPS-Lite plugin") # Called when the ui is updated def on_ui_update(self, ui): @@ -67,15 +67,15 @@ def on_ui_update(self, ui): # Check for critical level. Initiate shutdown if too low if not self.is_plugged: if self.charge <= self.options['shutdown_level']: - logging.info(f'[UPSLITE] Battery charge critical: {self.charge}') + logging.info(f'[upslite] Battery charge critical: {self.charge}') ui.update(force=True, new_data={'status': 'Battery level critical. Shutting down in 1m unless connected to charger...'}) time.sleep(60) self._check_plugged() if not self.is_plugged: - logging.info('[UPSLITE] Shutting down') + logging.info('[upslite] Shutting down') stenogotchi.shutdown() else: - logging.info('[UPSLITE] Battery charging. Aborting shutdown process') + logging.info('[upslite] Battery charging. Aborting shutdown process') ui.update(force=True, new_data={'status': 'Pheew... That was a close one! Feeling better already.'}) def _read_voltage(self): From 5793261b50e0c5b7c361b8e572b4f5e8119a7ed3 Mon Sep 17 00:00:00 2001 From: Anodynous Date: Sun, 21 Mar 2021 00:01:09 +0200 Subject: [PATCH 27/30] Bugfix, remove extra character --- stenogotchi/plugins/default/buttonshim.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stenogotchi/plugins/default/buttonshim.py b/stenogotchi/plugins/default/buttonshim.py index aa8e586..97dec44 100644 --- a/stenogotchi/plugins/default/buttonshim.py +++ b/stenogotchi/plugins/default/buttonshim.py @@ -534,7 +534,7 @@ def release_handler(self, button, pressed, plugin): logging.info(f"[buttonshim] Button Pressed! Loading command from slot '{button}' for button '{NAMES[button]}'") bCfg = plugin.options['buttons'][NAMES[button]] blinkCfg = bCfg['blink'] - logging.debug(f'[buttonshim] {self.blink}'') + logging.debug(f'[buttonshim] {self.blink}') if blinkCfg['enabled'] == True: logging.debug(f"[buttonshim] Blinking led") red = int(blinkCfg['red']) From 0b56e36ce24a8a23021c96522061f0cd56b45be2 Mon Sep 17 00:00:00 2001 From: Anodynous Date: Sun, 21 Mar 2021 00:24:33 +0200 Subject: [PATCH 28/30] Logging changes --- stenogotchi/plugins/default/plover_link.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/stenogotchi/plugins/default/plover_link.py b/stenogotchi/plugins/default/plover_link.py index 02bce51..473fd30 100644 --- a/stenogotchi/plugins/default/plover_link.py +++ b/stenogotchi/plugins/default/plover_link.py @@ -201,7 +201,7 @@ def config_hid_profile(self): Setup and register HID Profile """ - logging.info('[plover_link] Configuring Bluez Profile') + logging.debug('[plover_link] Configuring Bluez Profile') service_record = self.read_sdp_service_record() opts = { @@ -223,7 +223,7 @@ def config_hid_profile(self): BTKbDevice.UUID, opts) - logging.info('[plover_link] Profile registered ') + logging.debug('[plover_link] Profile registered ') @staticmethod def read_sdp_service_record(): @@ -231,7 +231,7 @@ def read_sdp_service_record(): Read and return SDP record from a file :return: (string) SDP record """ - logging.info('[plover_link] Reading service record') + logging.debug('[plover_link] Reading service record') try: fh = open(BTKbDevice.SDP_RECORD_PATH, 'r') except OSError: From a9b25f53b2124b6bffad6921e083faa9a17811e4 Mon Sep 17 00:00:00 2001 From: Anodynous Date: Sun, 21 Mar 2021 01:13:55 +0200 Subject: [PATCH 29/30] Correctly show wifi as [OFF] when disabled at boot --- stenogotchi/agent.py | 6 +++--- stenogotchi/plugins/default/buttonshim.py | 7 ++++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/stenogotchi/agent.py b/stenogotchi/agent.py index 9509b43..b095cec 100644 --- a/stenogotchi/agent.py +++ b/stenogotchi/agent.py @@ -23,7 +23,7 @@ def __init__(self, view, config): self._view = view self._view.set_agent(self) self._web_ui = Server(self, config['ui']) - self._wifi_connected = False + self._wifi_connected = None self._history = {} #self.last_session = LastSession(self._config) @@ -52,7 +52,7 @@ def _update_uptime(self): def _update_wifi(self): status, ip = stenogotchi.get_wifi_status() - + if status == 'UP': if self._wifi_connected: return @@ -68,7 +68,7 @@ def _update_wifi(self): self._wifi_connected = False self.set_wifi_connected(ssid, ip) elif status == 'DOWN' or not status: - if not self._wifi_connected: + if self._wifi_connected == False: return self._wifi_connected = False self.set_wifi_disconnected('[OFF]') diff --git a/stenogotchi/plugins/default/buttonshim.py b/stenogotchi/plugins/default/buttonshim.py index 97dec44..9f82756 100644 --- a/stenogotchi/plugins/default/buttonshim.py +++ b/stenogotchi/plugins/default/buttonshim.py @@ -511,15 +511,16 @@ def toggle_wpm_meters(): elif NAMES[button] == 'C': # Clean the screen (should not be needed on waveshare_2, but could be useful on other display modules) - plugins.loaded['buttonshim']._agent.view().init_display() - plugins.loaded['buttonshim']._agent.view().update(force=True) + self._agent.view().init_display() + self._agent.view().update(force=True) logging.info(f"[buttonshim] Initiated screen refresh") elif NAMES[button] == 'D': # Toggle wifi on/off stenogotchi.set_wifi_onoff() + # Check for changes in wifi status over a short while for i in range(5): - plugins.loaded['buttonshim']._agent._update_wifi() + self._agent._update_wifi() time.sleep(2) logging.info(f"[buttonshim] Toggled wifi state") From 0fc333a285ffabde214528b7f385a810aa650963 Mon Sep 17 00:00:00 2001 From: Anodynous Date: Sun, 21 Mar 2021 11:28:33 +0200 Subject: [PATCH 30/30] Add project changelog --- CHANGELOG.md | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..025a830 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,48 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] +### Added +### Changed +### Fixed +### Removed + +## [0.0.4] - 2021-03-21 +### Added +- This CHANGELOG file. +- User configurable bluetooth device name using main.plugins.plover_link.bt_device_name. +- User configurable list of bluetooth mac addresses, in order of priority, to auto-connect to using main.plugins.plover_link.bt_autoconnect_mac. +- User configurable option to clear eINK display at shutdown using ui.display.clear_at_shutdown. +- User configurable wpm calculation method using main.plugins.plover_link.wpm_method. +- User configurable wpm update frequency and calculation window in seconds using main.plugins.plover_link.wpm_timeout. +- More variety in mood indicators on common events. +- Requirements file. + +### Changed +- Improved installation guide and documentation in README. +- All functionality in buttonshim plugin reworked into class for better integration with the project. +- More consistent logging messages. +- Stenogotchi_link version upgrade to v0.0.4. + +### Fixed +- Reboot after initial setup or hostname change not working. +- Wifi status not showing as [OFF] if wifi is disabled at boot. +- Mode not changing to STENO when Plover becomes operational. +- All button press events not producing logging messages. +- Dependencies for stenogotchi_link Plover plugin corrected in setup.cfg. + +## [0.0.3] - 2021-03-18 +### Added +- First public pre-release version on GitHub. +- Stenogotchi, portable stenography using Plover and bluetooth keyboard emulation on a Raspberry Pi Zero W. With support for Waveshare 2.13 v2, ButtonSHIM and UPS-Lite v1.2 modules. +- Plover plugin stenogotchi_link for communicating between Plover and Stenogotchi. +- README now includes tested installation guide no longer requiring building PyQt5 from source. +- README now includes basic configuration and usage documentation. +- LICENSE file. + +[Unreleased]: https://github.com/Anodynous/stenogotchi/compare/v0.0.4...dev +[0.0.4]: https://github.com/Anodynous/stenogotchi/compare/v0.0.3...v0.0.4 +[0.0.3]: https://github.com/Anodynous/stenogotchi/releases/tag/v0.0.3 \ No newline at end of file