diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml deleted file mode 100644 index f1425c9..0000000 --- a/.github/workflows/codeql-analysis.yml +++ /dev/null @@ -1,71 +0,0 @@ -# For most projects, this workflow file will not need changing; you simply need -# to commit it to your repository. -# -# You may wish to alter this file to override the set of languages analyzed, -# or to provide custom queries or build logic. -# -# ******** NOTE ******** -# We have attempted to detect the languages in your repository. Please check -# the `language` matrix defined below to confirm you have the correct set of -# supported CodeQL languages. -# -name: "CodeQL" - -on: - push: - branches: [ master ] - pull_request: - # The branches below must be a subset of the branches above - branches: [ master ] - schedule: - - cron: '28 3 * * 1' - -jobs: - analyze: - name: Analyze - runs-on: ubuntu-latest - permissions: - actions: read - contents: read - security-events: write - - strategy: - fail-fast: false - matrix: - language: [ 'python' ] - # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] - # Learn more: - # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed - - steps: - - name: Checkout repository - uses: actions/checkout@v2 - - # Initializes the CodeQL tools for scanning. - - name: Initialize CodeQL - uses: github/codeql-action/init@v1 - with: - languages: ${{ matrix.language }} - # If you wish to specify custom queries, you can do so here or in a config file. - # By default, queries listed here will override any specified in a config file. - # Prefix the list here with "+" to use these queries and those in the config file. - # queries: ./path/to/local/query, your-org/your-repo/queries@main - - # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). - # If this step fails, then you should remove it and run the build manually (see below) - - name: Autobuild - uses: github/codeql-action/autobuild@v1 - - # ℹ️ Command-line programs to run using the OS shell. - # 📚 https://git.io/JvXDl - - # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines - # and modify them (or add more) to build your code if your project - # uses a compiled language - - #- run: | - # make bootstrap - # make release - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v1 diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..efc1591 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,6 @@ +- v0.32 Removed DBUS backend for LED, new MQTT backend and corresponding services +- v0.31 Animations and prototyped mqtt linkage to hangboard +- v0.30 simplified installation procedure, fix service logging and installtion collisions +- v0.28 merged moonboard mini protocol +- v0.27 merged bt fix test +- v0.23 running in gz setup diff --git a/README.md b/README.md index 7a821f6..d788141 100755 --- a/README.md +++ b/README.md @@ -44,7 +44,4 @@ Free standing foldaway version of moonboard. Moonboard with 150mm kicker and tot ## Tested setups - Raspi W Zero with iPhone 5, 8, X, 11 (iOS >= 14) -# Versions -- v0.28 merged moonboard mini protocol -- v0.27 merged bt fix test -- v0.23 running in gz setup + diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..b4ad90f --- /dev/null +++ b/TODO.md @@ -0,0 +1,6 @@ +# Minor stuff +- uninstaller for services + +# Minor cleanups +- Select python version from "env" for all scripts +- FIXME in files diff --git a/ble/Dockerfile b/ble/Dockerfile new file mode 100644 index 0000000..ae02ed5 --- /dev/null +++ b/ble/Dockerfile @@ -0,0 +1,13 @@ +FROM debian:latest + +RUN apt-get -y update && apt-get upgrade +RUN apt-get -y install git vim python3-pip gcc make build-essential libatlas-base-dev python-dev swig scons + +WORKDIR /ble +ADD . /ble/ + +#https://georgik.rocks/how-to-start-d-bus-in-docker-container/ +#dbus-uuidgen > /var/lib/dbus/machine-id +#mkdir -p /var/run/dbus +#dbus-daemon --config-file=/usr/share/dbus-1/system.conf --print-address +#python3 moonboard_BLE_service.py \ No newline at end of file diff --git a/ble/ERROR-RECONNECT.md b/ble/ERROR-RECONNECT.md new file mode 100644 index 0000000..690fafe --- /dev/null +++ b/ble/ERROR-RECONNECT.md @@ -0,0 +1,22 @@ +{"START": ["E4", "H3"], "MOVES": ["E8", "B13", "A16", "A11"], "TOP": ["D18"], "FLAGS": [""]} +start adv +< HCI Command: ogf 0x08, ocf 0x000a, plen 1 + 01 +> HCI Event: 0x0e plen 4 + 01 0A 20 0C +New data 31352c5031302c45353423 +New data dec? 15,P10,E54# + + +Traceback (most recent call last): + File "moon_ble_mqtt_proto.py", line 165, in + mbble.main(logger,adapter='/org/bluez/hci0') # FIXME: use configured adapter + File "moon_ble_mqtt_proto.py", line 129, in main + self.monitor_btmon(logger,unstuffer) + File "moon_ble_mqtt_proto.py", line 116, in monitor_btmon + self.process_rx(unstuffer,logger,data) + File "moon_ble_mqtt_proto.py", line 93, in process_rx + new_problem_string= unstuffer.process_bytes(ba) + File "/home/pi/moonboard/ble/moonboard_app_protocol.py", line 54, in process_bytes + s = bytearray.fromhex(ba).decode() +UnicodeDecodeError: 'utf-8' codec can't decode byte 0xff in position 2: invalid start byte diff --git a/ble/Makefile b/ble/Makefile new file mode 100644 index 0000000..b651bae --- /dev/null +++ b/ble/Makefile @@ -0,0 +1,22 @@ +#api-doc: +# docker run --rm -it -v $(PWD)/ble_api.yaml:/app/asyncapi.yml -v $(PWD)/api:/app/output asyncapi/generator -o /app/output /app/asyncapi.yml @asyncapi/html-template --force-write + +install: + service="moonboard_ble.service" + echo "Install moonboard service for: moonboard_ble.service" + sudo cp ./moonboard_ble.service /lib/systemd/system/ + sudo chmod 644 /lib/systemd/system/moonboard_ble.service + sudo systemctl daemon-reload + sudo systemctl enable moonboard_ble.service + sudo systemctl restart moonboard_ble.service + +install-dbus: + service="com.moonboard.service" + echo "Install moonboard service for: com.moonboard.service" + sudo cp ./com.moonboard.service /lib/systemd/system/ + sudo chmod 644 /lib/systemd/system/com.moonboard.service + sudo cp com.moonboard.conf /etc/dbus-1/system.d/com.moonboard.conf + sudo systemctl daemon-reload + sudo systemctl enable com.moonboard.service + sudo systemctl restart com.moonboard.service + sudo systemctl restart dbus diff --git a/ble/README.md b/ble/README.md index 3eed2dd..e9869af 100755 --- a/ble/README.md +++ b/ble/README.md @@ -107,4 +107,11 @@ See Bluetooth specification 5.0 (Core_v5.0.pdf), 7.8.5 LE Set Advertising Parame ## Bluez DBUS [Good introduction](http://smartspacestuff.blogspot.com/2016/02/i-got-figurin-out-dbus-bluez.html) -[API](https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/doc) \ No newline at end of file +[API](https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/doc) + + +# MQTT Interface +## Debugging ++ Status of service `systemctl status moonboard_ble.service` ++ Check for received events `mosquitto_sub -h raspi-moonboard -t "moonboard/ble/problem"` ++ Status of DBUS backend: `sudo systemctl status com.moonboard.service` \ No newline at end of file diff --git a/ble/com.moonboard.conf b/ble/com.moonboard.conf old mode 100755 new mode 100644 diff --git a/ble/com.moonboard.service b/ble/com.moonboard.service old mode 100755 new mode 100644 index c2e5427..00e79a0 --- a/ble/com.moonboard.service +++ b/ble/com.moonboard.service @@ -1,13 +1,16 @@ [Unit] -Description=Moonboard DBUS Service -After=multi-user.target -SystemdService=dbus-org.com.moonbooard.service +Description=Moonboard BLE DBUS Service +After=multi-user.target bluetooth.service dbus.service hciuart.service +SystemdService=dbus-org.com.moonbooard.service +# FIXME: SystemdService not known [Service] Type=dbus BusName=com.moonboard -ExecStart=/usr/bin/python3 /home/pi/moonboard/ble/moonboard_BLE_service.py +ExecStart=/usr/bin/python3 /home/pi/moonboard/ble/moonboard_ble_dbus_service.py User=root +StandardOutput=file:/var/log/moonboard_ble_stdout.log +StandardError=file:/var/log/moonboard_ble_stderr.log [Install] WantedBy=multi-user.target diff --git a/ble/moonboard_app_protocol.py b/ble/moonboard_app_protocol.py index 71152e6..b70014e 100755 --- a/ble/moonboard_app_protocol.py +++ b/ble/moonboard_app_protocol.py @@ -23,7 +23,7 @@ def decode_problem_string(s, flags): t,p = h[0],position_trans(int(h[1:]), num_rows) if t=='S': holds['START'].append(p) - if t=='P': + if t=='P' or t == 'R' or t == 'L' or t == 'M' or t =='F': holds['MOVES'].append(p) if t=='E': holds['TOP'].append(p) @@ -51,7 +51,7 @@ def process_bytes(self, ba): handle some error due to multiple connected devices sending simoultaneously. """ - s = bytearray.fromhex(ba).decode() + s = bytearray.fromhex(ba).decode(errors="ignore") self.logger.debug("incoming bytes:"+str(s)) if s[0] == '~' and s[-1] == '*': diff --git a/ble/moonboard_ble.service b/ble/moonboard_ble.service new file mode 100755 index 0000000..31d2933 --- /dev/null +++ b/ble/moonboard_ble.service @@ -0,0 +1,16 @@ +[Unit] +Description=Moonboard BLE Service +After=multi-user.target bluetooth.service hciuart.service mosquitto.service + +[Service] +WorkingDirectory=/home/pi/moonboard/ble/ +ExecStart=/usr/bin/python3 /home/pi/moonboard/ble/moonboard_ble_service.py +Type=idle +User=root +Restart=on-failure +RestartSec=5s +StandardOutput=file:/var/log/moonboard_ble_stdout.log +StandardError=file:/var/log/moonboard_ble_stderr.log + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/ble/moonboard_BLE_service.py b/ble/moonboard_ble_dbus_service.py old mode 100755 new mode 100644 similarity index 72% rename from ble/moonboard_BLE_service.py rename to ble/moonboard_ble_dbus_service.py index d4ffc42..55c0d96 --- a/ble/moonboard_BLE_service.py +++ b/ble/moonboard_ble_dbus_service.py @@ -10,6 +10,8 @@ from moonboard_app_protocol import UnstuffSequence, decode_problem_string import os +import errno +import time import threading import pty @@ -82,24 +84,82 @@ def __init__(self, bus, socket,logger): dbus.service.Object.__init__(self, bus, self.path) self.add_service(UartService(bus,self.get_path(), 0, self.process_rx)) - monitor_thread = threading.Thread(target=self.monitor_btmon) + monitor_thread = threading.Thread(target=self.monitor_btmon, daemon=True) monitor_thread.start() - def monitor_btmon(self): - out_r, out_w = pty.openpty() - cmd = ["sudo","btmon"] - process = subprocess.Popen(cmd, stdout=out_w) - f = OutStream(out_r) + def _restart_advertising(self): + """Re-setup and start BLE advertising after a disconnect.""" + self.logger.info('Client disconnected, restarting advertising...') + try: + setup_adv(self.logger) + start_adv(self.logger) + self.logger.info('Advertising restarted successfully') + except Exception as e: + self.logger.error('Failed to restart advertising: ' + str(e)) + + def monitor_btmon(self): + restart_delay = 2 + max_restart_delay = 30 + while True: - lines, readable = f.read_lines() - if not readable: break - for line in lines: - if line != '': - line = line.decode() - if 'Data:' in line: - data = line.replace(' ','').replace('\x1b','').replace('[0m','').replace('Data:','') - self.process_rx(data) - self.logger.info('New data '+ data) + out_r, out_w = pty.openpty() + cmd = ["sudo", "btmon"] + try: + process = subprocess.Popen(cmd, stdout=out_w) + self.logger.info('btmon started (pid %d)', process.pid) + except Exception as e: + self.logger.error('Failed to start btmon: ' + str(e)) + os.close(out_r) + os.close(out_w) + time.sleep(restart_delay) + restart_delay = min(restart_delay * 2, max_restart_delay) + continue + + f = OutStream(out_r) + restart_delay = 2 # reset backoff on successful start + + try: + while True: + lines, readable = f.read_lines() + if not readable: + break + for line in lines: + if line != b'': + try: + line = line.decode(errors='ignore') + except Exception: + continue + if 'Disconnect Complete' in line: + self._restart_advertising() + elif 'Data:' in line: + data = line.replace(' ','').replace('\x1b','').replace('[0m','').replace('Data:','') + self.process_rx(data) + self.logger.info('New data '+ data) + except Exception as e: + self.logger.error('btmon monitoring error: ' + str(e)) + + # Cleanup + try: + process.terminate() + process.wait(timeout=5) + except Exception: + try: + process.kill() + process.wait(timeout=5) + except Exception: + pass + try: + os.close(out_r) + except OSError: + pass + try: + os.close(out_w) + except OSError: + pass + + self.logger.warning('btmon exited, restarting in %ds...', restart_delay) + time.sleep(restart_delay) + restart_delay = min(restart_delay * 2, max_restart_delay) def process_rx(self,ba): new_problem_string= self.unstuffer.process_bytes(ba) diff --git a/ble/moonboard_ble_service.py b/ble/moonboard_ble_service.py new file mode 100755 index 0000000..162c7fd --- /dev/null +++ b/ble/moonboard_ble_service.py @@ -0,0 +1,237 @@ +import sys +from gatt_base.gatt_lib_advertisement import Advertisement +from gatt_base.gatt_lib_characteristic import Characteristic +from gatt_base.gatt_lib_service import Service +import string,json +import subprocess +import logging +from moonboard_app_protocol import UnstuffSequence, decode_problem_string +import paho.mqtt.client as mqtt + +import os +import errno +import time +import threading +import pty + +# FIXME: remove dead code +# FIXME: cleanup code & simplify(!) + +BLUEZ_SERVICE_NAME = 'org.bluez' +DBUS_OM_IFACE = 'org.freedesktop.DBus.ObjectManager' +LE_ADVERTISING_MANAGER_IFACE = 'org.bluez.LEAdvertisingManager1' +GATT_MANAGER_IFACE = 'org.bluez.GattManager1' +GATT_CHRC_IFACE = 'org.bluez.GattCharacteristic1' +UART_SERVICE_UUID = '6e400001-b5a3-f393-e0a9-e50e24dcca9e' +UART_RX_CHARACTERISTIC_UUID = '6e400002-b5a3-f393-e0a9-e50e24dcca9e' +UART_TX_CHARACTERISTIC_UUID = '6e400003-b5a3-f393-e0a9-e50e24dcca9e' +LOCAL_NAME = 'Moonboard A' +SERVICE_NAME= 'com.moonboard' + +# FIXME: on connection lost: error: UnicodeDecodeError: 'utf-8' codec can't decode byte 0xff in position 2: invalid start byte + +class RxCharacteristic(Characteristic): + def __init__(self, bus, index, service, process_rx): + Characteristic.__init__(self, bus, index, UART_RX_CHARACTERISTIC_UUID, + ['write'], service) + self.process_rx=process_rx + + def WriteValue(self, value, options): + pass + #self.process_rx(value) + +class UartService(Service): + def __init__(self, bus,path, index, process_rx): + Service.__init__(self, bus,path, index, UART_SERVICE_UUID, True) + self.add_characteristic(RxCharacteristic(bus, 1, self, process_rx)) + + +class OutStream: # FIXME: simplify + def __init__(self, fileno): + self._fileno = fileno + self._buffer = b"" + + def read_lines(self): + try: + output = os.read(self._fileno, 1000) + except OSError as e: + if e.errno != errno.EIO: raise + output = b"" + lines = output.split(b"\n") + lines[0] = self._buffer + lines[0] # prepend previous + # non-finished line. + if output: + self._buffer = lines[-1] + finished_lines = lines[:-1] + readable = True + else: + self._buffer = b"" + if len(lines) == 1 and not lines[0]: + # We did not have buffer left, so no output at all. + lines = [] + finished_lines = lines + readable = False + + finished_lines = [line.rstrip(b"\r") + for line in finished_lines] + + return finished_lines, readable + +class MoonboardBLE(): + def __init__(self): + self._start_mqtt() + return + + def setup_adv(self,logger): + """ + Setup Advertisinf""" + logger.info('setup adv') + setup_adv = [ + "hcitool -i hci0 cmd 0x08 0x000a 00", + "hcitool -i hci0 cmd 0x08 0x0008 18 02 01 06 02 0a 00 11 07 9e ca dc 24 0e e5 a9 e0 93 f3 a3 b5 01 00 40 6e 00 00 00 00 00 00 00", + "hcitool -i hci0 cmd 0x08 0x0009 0d 0c 09 4d 6f 6f 6e 62 6f 61 72 64 20 41", + "hcitool -i hci0 cmd 0x08 0x0006 80 02 c0 03 00 00 00 00 00 00 00 00 00 07 00" + ] + for c in setup_adv: + os.system("sudo "+ c) + + def start_adv(self,logger,start=True): + """ + Start Advertising + """ + if start: + start='01' + logger.info('start adv') + else: + start='00' + logger.info('stop adv') + start_adv= "hcitool -i hci0 cmd 0x08 0x000a {}".format(start) + os.system("sudo " +start_adv) + + def process_rx(self, unstuffer,logger,ba): + new_problem_string= unstuffer.process_bytes(ba) + flags = unstuffer.flags + + if new_problem_string is not None: + problem= decode_problem_string(new_problem_string, flags) + print(json.dumps(problem)) # FIXME + self._sendmessage("/problem", json.dumps(problem)) # FIXME + unstuffer.flags = '' + self.start_adv(logger) + + def monitor_btmon(self, logger, unstuffer): + restart_delay = 2 + max_restart_delay = 30 + + while True: + out_r, out_w = pty.openpty() + cmd = ["sudo", "btmon"] + try: + process = subprocess.Popen(cmd, stdout=out_w) + logger.info('btmon started (pid %d)', process.pid) + except Exception as e: + logger.error('Failed to start btmon: ' + str(e)) + os.close(out_r) + os.close(out_w) + time.sleep(restart_delay) + restart_delay = min(restart_delay * 2, max_restart_delay) + continue + + f = OutStream(out_r) + restart_delay = 2 # reset backoff on successful start + + try: + while True: + lines, readable = f.read_lines() + if not readable: + break + for line in lines: + if line != b'': + try: + line = line.decode(errors='ignore') + except Exception: + continue + if 'Disconnect Complete' in line: + logger.info('Client disconnected, restarting advertising...') + try: + self.setup_adv(logger) + self.start_adv(logger) + logger.info('Advertising restarted successfully') + except Exception as e: + logger.error('Failed to restart advertising: ' + str(e)) + elif 'Data:' in line: + data = line.replace(' ','').replace('\x1b','').replace('[0m','').replace('Data:','') + self.process_rx(unstuffer, logger, data) + t1 = bytearray.fromhex(data).decode(errors="ignore") + logger.info('New data '+ data + ' and decoded ' + t1) + except Exception as e: + logger.error('btmon monitoring error: ' + str(e)) + + # Cleanup + try: + process.terminate() + process.wait(timeout=5) + except Exception: + try: + process.kill() + process.wait(timeout=5) + except Exception: + pass + try: + os.close(out_r) + except OSError: + pass + try: + os.close(out_w) + except OSError: + pass + + logger.warning('btmon exited, restarting in %ds...', restart_delay) + time.sleep(restart_delay) + restart_delay = min(restart_delay * 2, max_restart_delay) + + + def main(self,logger,adapter): + logger.info("Bluetooth adapter: "+ str(adapter)) + + unstuffer= UnstuffSequence(logger) + + self.setup_adv(logger) + self.start_adv(logger) + self.monitor_btmon(logger,unstuffer) + + def _start_mqtt(self): + # Connect to MQTT + hostname = "raspi-moonboard" # FIXME + port = 1883 # FIXME + self._client = mqtt.Client() + self._client.connect(hostname, port,60) + self._sendmessage("/status", "Starting") + + def _sendmessage(self, topic="/none", message="None"): + ttopic = "moonboard/ble"+topic + mmessage = str(message) + #logging.debug("MQTT>: " + ttopic + " ###> " + mmessage) + self._client.publish(ttopic, mmessage) + + +if __name__ == '__main__': + + import argparse + parser = argparse.ArgumentParser(description='Moonboard bluetooth service') + parser.add_argument('--debug', action = "store_true") + + args = parser.parse_args() + argsd=vars(args) + + logger = logging.getLogger('moonboard.ble') + logger.setLevel(logging.DEBUG) + logger.addHandler(logging.StreamHandler()) + + if args.debug: + logger.setLevel(logging.DEBUG) + else: + logger.setLevel(logging.INFO) + + mbble = MoonboardBLE() + mbble.main(logger,adapter='/org/bluez/hci0') # FIXME: use configured adapter diff --git a/ble/simple_client.py b/ble/simple_client.py deleted file mode 100755 index 563e9b5..0000000 --- a/ble/simple_client.py +++ /dev/null @@ -1,30 +0,0 @@ -from gi.repository import GLib -import dbus -from dbus.mainloop.glib import DBusGMainLoop - - -def handle_notification(s): - print('New Problem: '+s) - - -if __name__ == '__main__': - - dbml = DBusGMainLoop(set_as_default=True) - - bus = dbus.SystemBus() - proxy = bus.get_object('com.moonboard','/com/moonboard') - - proxy.connect_to_signal('new_problem', handle_notification) - loop = GLib.MainLoop() - - dbus.set_default_main_loop(dbml) - - # Run the loop - try: - loop.run() - except KeyboardInterrupt: - print("keyboard interrupt received") - except Exception as e: - print("Unexpected exception occurred: '{}'".format(str(e))) - finally: - loop.quit() \ No newline at end of file diff --git a/doc/OVERVIEW-SOFTWARE.md b/doc/OVERVIEW-SOFTWARE.md index d8bf257..65d6481 100755 --- a/doc/OVERVIEW-SOFTWARE.md +++ b/doc/OVERVIEW-SOFTWARE.md @@ -30,3 +30,7 @@ To stop / restart the moonboard services (i.e. for debugging) use systemctl: *** FIXME *** The LED driver scripts are located in the folder LED. moonboard.py is invoked by the BLE service. Alternatively you can run moonboard.py directly, i.e. for LED strip installation or hold setup. + + +# Docker version ++ Install docker `curl -fsSL https://get.docker.com -o get-docker.sh` and `sudo sh get-docker.sh` \ No newline at end of file diff --git a/doc/TESTING.md b/doc/TESTING.md new file mode 100644 index 0000000..41b1004 --- /dev/null +++ b/doc/TESTING.md @@ -0,0 +1,30 @@ +# Bluetooth monitoring + +``` +ssh -lpi raspi-moonboard sudo btmon +``` + +# DBUS Monitoring +``` +ssh -lpi raspi-moonboard sudo dbus-monitor --system|less +``` + +# Logfiles +/var/log/moonboard_stdout.log +/var/log/moonboard_ble_stdout.log +``` +ssh -lpi raspi-moonboard "tail -f /var/log/moonboard*" +``` + +# Resetting services +``` +ssh -lpi raspi-moonboard "./moonboard/scripts/fix_startup.sh" +``` + +# Checking status of services +ssh -lpi raspi-moonboard sudo systemctl status moonboard.service + +ssh -lpi raspi-moonboard sudo systemctl status com.moonboard.service + +# Updating +ssh -lpi raspi-moonboard "cd moonboard ; git pull; cd install ; ./install.sh" diff --git a/install/10-prepare-raspi.sh b/install/10-prepare-raspi.sh new file mode 100755 index 0000000..0c6e32b --- /dev/null +++ b/install/10-prepare-raspi.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +echo "Enable SPI" +sudo sed -i 's/\#dtparam=spi=on/dtparam=spi=on/g' /boot/config.txt + +echo "Disable Audio" +sudo sed -i 's/\dtparam=audio=on/#dtparam=audio=on/g' /boot/config.txt +echo blacklist snd_bcm2835 | sudo tee /etc/modprobe.d/raspi-blacklist.conf + + +# Install dependencies +echo "Install dependencies" +sudo apt-get update +sudo apt-get upgrade + +echo "Install + build led drivers" +sudo apt-get -y install git vim python3-pip python3-rpi.gpio gcc make build-essential +sudo apt-get -y install libatlas-base-dev +sudo apt-get -y install python-dev swig scons # for building WS2811 drivers + diff --git a/install/20-prepare-python.sh b/install/20-prepare-python.sh new file mode 100755 index 0000000..6be4f33 --- /dev/null +++ b/install/20-prepare-python.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +# Installing python dependencies # FIXME venv +echo "Installing python dependencies" +pip3 install -r requirements.txt +sudo pip3 install -r requirements.txt \ No newline at end of file diff --git a/install/30-install-services.sh b/install/30-install-services.sh new file mode 100755 index 0000000..fdc07f2 --- /dev/null +++ b/install/30-install-services.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +echo "Install services" # FIXME +cd /home/pi/moonboard/ble +make install +cd .. + +cd /home/pi/moonboard/led +make install +cd .. diff --git a/install/install-phase2.sh b/install/install-phase2.sh deleted file mode 100755 index 79dacf4..0000000 --- a/install/install-phase2.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/bash -# FIXME: installdir -echo "Install DBUS service" -sudo cp /home/pi/moonboard/ble/com.moonboard.conf /etc/dbus-1/system.d -cd /home/pi/moonboard/ble -sudo /home/pi/moonboard/services/install_service.sh com.moonboard.service > /tmp/moonboard-service-install.log - - -sudo systemctl enable com.moonboard -sudo systemctl enable moonboard.service - -# Remove phase 2 from boot -sudo systemctl disable moonboard-install.service -sudo systemctl start moonboard.service diff --git a/install/install.sh b/install/install.sh index e3556b6..a1dc556 100755 --- a/install/install.sh +++ b/install/install.sh @@ -1,66 +1,21 @@ #!/bin/bash -echo "Enable SPI" -sudo sed -i 's/\#dtparam=spi=on/dtparam=spi=on/g' /boot/config.txt +# FIXME: must run from this dir in checkout out version -echo "Disable Audio" -sudo sed -i 's/\dtparam=audio=on/#dtparam=audio=on/g' /boot/config.txt -echo blacklist snd_bcm2835 | sudo tee /etc/modprobe.d/raspi-blacklist.conf +#./10-prepare-raspi.sh # FIXME +#./20-prepare-python.sh # FIXME +#./30-install-services.sh # FIXME +echo "Applying patch to bluez for iPhone" +sudo sed -i '/ExecStart/ c ExecStart=/usr/lib/bluetooth/bluetoothd -P battery' /usr/lib/systemd/system/bluetooth.service -# Install dependencies -echo "Install dependencies" -sudo apt-get update -sudo apt-get upgrade -echo "Install + build led drivers" -sudo apt-get -y install git vim python3-pip python3-rpi.gpio gcc make build-essential -sudo apt-get -y install libatlas-base-dev -sudo apt-get -y install python-dev swig scons # for building WS2811 drivers +#echo "Install application" +#test -d moonboard || git clone https://github.com/8cH9azbsFifZ/moonboard.git +#cd moonboard +#git pull -echo "Install application" -test -d moonboard || git clone https://github.com/8cH9azbsFifZ/moonboard.git -cd moonboard -git pull -# Installing python dependencies -echo "Installing python dependencies" -pip3 install -r install/requirements.txt -sudo pip3 install -r install/requirements.txt -# pip3 uninstall -y -r install/requirements.txt # uninstall - -echo "Install service" # FIXME -cd /home/pi/moonboard/services -sudo ./install_service.sh moonboard.service -cd /home/pi/moonboard - - -echo "Install DBUS service" -sudo cp /home/pi/moonboard/ble/com.moonboard.conf /etc/dbus-1/system.d -sudo cp /home/pi/moonboard/ble/com.moonboard.service /usr/share/dbus-1/system-services/ - - -echo "Prepare logfiles" -sudo touch /var/log/moonboard -sudo chown pi:pi /var/log/moonboard -sudo chown pi:pi /var/log/moonboard - -# Prepare phase 2 to run at boot -sudo cp --verbose /home/pi/moonboard/services/moonboard-install.service /lib/systemd/system/moonboard-install.service -sudo chmod 644 /lib/systemd/system/moonboard-install.service -sudo systemctl daemon-reload -sudo systemctl enable moonboard-install.service - -echo "Restarting in 5 seconds to finalize changes. CTRL+C to cancel." -sleep 1 > /dev/null -printf "." -sleep 1 > /dev/null -printf "." -sleep 1 > /dev/null -printf "." -sleep 1 > /dev/null -printf "." -sleep 1 > /dev/null -printf " Restarting" -sudo shutdown -r now +#printf " Restarting" # FIXME +#sudo shutdown -r now diff --git a/install/requirements.txt b/install/requirements.txt index 55bddbc..cf3ef21 100644 --- a/install/requirements.txt +++ b/install/requirements.txt @@ -4,36 +4,38 @@ beautifulsoup4==4.7.1 BiblioPixel==3.4.46 BiblioPixelAnimations==3.20190410.153508 BiblioPixelTriggers==1.0.2 -certifi==2019.3.9 +certifi==2024.7.4 chardet==3.0.4 Click==7.0 croniter==0.3.30 -Flask==1.0.2 +Flask gitdb2==2.0.5 -GitPython==2.1.11 +GitPython==3.1.41 idna==2.8 isort==4.3.17 itsdangerous==1.1.0 -Jinja2==2.11.3 +Jinja2==3.1.6 lazy-object-proxy==1.3.1 loady==1.8.10 MarkupSafe==1.1.1 mccabe==0.6.1 -numpy==1.16.3 +numpy==1.22.0 olefile==0.46 -Pillow==8.3.2 +Pillow==9.3.0 pylint==2.3.1 pyserial==3.4 python-dateutil==2.8.0 PyYAML==5.4 -requests==2.21.0 +requests==2.32.2 six==1.12.0 smmap2==2.0.5 soupsieve==1.9.1 -urllib3==1.26.5 +urllib3==1.26.19 websockets==9.1 -Werkzeug==0.15.3 +Werkzeug==3.0.6 wrapt==1.11.1 spidev python-periphery rpi-ws281x==4.2.2 +colormap +easydev \ No newline at end of file diff --git a/led/Makefile b/led/Makefile new file mode 100644 index 0000000..7209976 --- /dev/null +++ b/led/Makefile @@ -0,0 +1,11 @@ +#api-doc: +# docker run --rm -it -v $(PWD)/ble_api.yaml:/app/asyncapi.yml -v $(PWD)/api:/app/output asyncapi/generator -o /app/output /app/asyncapi.yml @asyncapi/html-template --force-write + +install: + service="moonboard_led.service" + echo "Install moonboard service for: moonboard_led.service" + sudo cp ./moonboard_led.service /lib/systemd/system/ + sudo chmod 644 /lib/systemd/system/moonboard_led.service + sudo systemctl daemon-reload + sudo systemctl enable moonboard_led.service + sudo systemctl restart moonboard_led.service diff --git a/led/README.md b/led/README.md new file mode 100644 index 0000000..25c5bde --- /dev/null +++ b/led/README.md @@ -0,0 +1,3 @@ +# MQTT Interface +## Debugging ++ Status of service `sudo systemctl status moonboard_led.service ` \ No newline at end of file diff --git a/led/animation.py b/led/animation.py new file mode 100755 index 0000000..1ecea74 --- /dev/null +++ b/led/animation.py @@ -0,0 +1,614 @@ +# -*- coding: utf-8 -*- +from bibliopixel.colors import COLORS +from bibliopixel import Strip +from bibliopixel.drivers.PiWS281X import PiWS281X +from bibliopixel.drivers.dummy_driver import DriverDummy +from bibliopixel.drivers.SPI.WS2801 import WS2801 +from bibliopixel.drivers.SimPixel import SimPixel +from bibliopixel.drivers.spi_interfaces import SPI_INTERFACES +import string +import json +import time +import random +from colormap import hex2rgb + +def clamp(n, minn, maxn): + return max(min(maxn, n), minn) + + + +# FIXME: Describe Layouts +# FIXME: Delete this +LED_LAYOUT = { + 'nest':[ + # Top panel + [137, 138, 149, 150, 161, 162, 173, 174, 185, 186, 197], + [136, 139, 148, 151, 160, 163, 172, 175, 184, 187, 196], + [135, 140, 147, 152, 159, 164, 171, 176, 183, 188, 195], + [134, 141, 146, 153, 158, 165, 170, 177, 182, 189, 194], + [133, 142, 145, 154, 157, 166, 169, 178, 181, 190, 193], + [132, 143, 144, 155, 156, 167, 168, 179, 180, 191, 192], + # Middle panel + [131, 120, 119, 108, 107, 96, 95, 84, 83, 72, 71], + [130, 121, 118, 109, 106, 97, 94, 85, 82, 73, 70], + [129, 122, 117, 110, 105, 98, 93, 86, 81, 74, 69], + [128, 123, 116, 111, 104, 99, 92, 87, 80, 75, 68], + [127, 124, 115, 112, 103, 100, 91, 88, 79, 76, 67], + [126, 125, 114, 113, 102, 101, 90, 89, 78, 77, 66], + # Bottom panel + [5, 6, 17, 18, 29, 30, 41, 42, 53, 54, 65], + [4, 7, 16, 19, 28, 31, 40, 43, 52, 55, 64], + [3, 8, 15, 20, 27, 32, 39, 44, 51, 56, 63], + [2, 9, 14, 21, 26, 33, 38, 45, 50, 57, 62], + [1, 10, 13, 22, 25, 34, 37, 46, 49, 58, 61], + [0, 11, 12, 23, 24, 35, 36, 47, 48, 59, 60]], + +'evo': [[ 17, 18, 53, 54, 89, 90, 125, 126, 161, 162, 197], + [ 16, 19, 52, 55, 88, 91, 124, 127, 160, 163, 196], + [ 15, 20, 51, 56, 87, 92, 123, 128, 159, 164, 195], + [ 14, 21, 50, 57, 86, 93, 122, 129, 158, 165, 194], + [ 13, 22, 49, 58, 85, 94, 121, 130, 157, 166, 193], + [ 12, 23, 48, 59, 84, 95, 120, 131, 156, 167, 192], + [ 11, 24, 47, 60, 83, 96, 119, 132, 155, 168, 191], + [ 10, 25, 46, 61, 82, 97, 118, 133, 154, 169, 190], + [ 9, 26, 45, 62, 81, 98, 117, 134, 153, 170, 189], + [ 8, 27, 44, 63, 80, 99, 116, 135, 152, 171, 188], + [ 7, 28, 43, 64, 79, 100, 115, 136, 151, 172, 187], + [ 6, 29, 42, 65, 78, 101, 114, 137, 150, 173, 186], + [ 5, 30, 41, 66, 77, 102, 113, 138, 149, 174, 185], + [ 4, 31, 40, 67, 76, 103, 112, 139, 148, 175, 184], + [ 3, 32, 39, 68, 75, 104, 111, 140, 147, 176, 183], + [ 2, 33, 38, 69, 74, 105, 110, 141, 146, 177, 182], + [ 1, 34, 37, 70, 73, 106, 109, 142, 145, 178, 181], + [ 0, 35, 36, 71, 72, 107, 108, 143, 144, 179, 180]] + } + +class MoonBoard: + DEFAULT_PROBLEM_COLORS = {'START':COLORS.blue,'TOP':COLORS.red,'MOVES':COLORS.green} + DEFAULT_COLOR = COLORS.blue #FIXME ? + X_GRID_NAMES = string.ascii_uppercase[0:11] # FIXME: del + LED_SPACING = 3 # Use every n-th LED only - used for 3 x 4x5 LED strp # FIXME: normal=1 + ROWS = 18 + COLS = 11 + NUM_PIXELS = ROWS*COLS * LED_SPACING + DEFAULT_BRIGHTNESS = 100 # FIXME: to config file + SETUP = 'MoonboardMasters2017' # FIXME: to config file / Arg + + + # FIXME: json + MAPPING= { } + + with open('/home/pi/moonboard/led/led_mapping.json') as json_file: + data = json.load(json_file) + MAPPING = data + + + def __init__(self, driver_type, led_layout=None, brightness=DEFAULT_BRIGHTNESS): + try: + if driver_type == "PiWS281x": + driver = PiWS281X(self.NUM_PIXELS) + elif driver_type == "WS2801": + driver = WS2801(self.NUM_PIXELS, dev='/dev/spidev0.1',spi_interface= SPI_INTERFACES.PERIPHERY,spi_speed=1) + elif driver_type == "SimPixel": + driver = SimPixel(self.NUM_PIXELS) + driver.open_browser() + else: + raise ValueError("driver_type {driver_type} unknow.".format(driver_type) ) + except (ImportError, ValueError) as e: + print("Not able to initialize the driver. Error{}".format(e)) + print("Use bibliopixel.drivers.dummy_driver") + driver = DriverDummy(self.NUM_PIXELS) + + if led_layout is not None: + self.layout = Strip (driver, brightness=brightness,threadedUpdate=True) + else: + self.layout = Strip (driver, brightness=brightness,threadedUpdate=True) + self.layout.cleanup_drivers() + self.layout.start() + self.animation = None + + def clear(self): + self.stop_animation() + self.layout.all_off() + self.layout.push_to_driver() + + def set_hold(self, hold, color=DEFAULT_COLOR): + self.layout.set(self.MAPPING[hold], color) + + def show_hold(self, hold, color=DEFAULT_COLOR): + self.set_hold(hold, color) + self.layout.push_to_driver() + + def show_problem(self, holds, hold_colors={}): + self.clear() + for k in ['START', 'MOVES', 'TOP']: + for hold in holds[k]: + self.set_hold( + hold, + hold_colors.get(k, self.DEFAULT_PROBLEM_COLORS[k]), + ) + self.layout.push_to_driver() + + # run all colors in led´s to see if something is missing + def led_test(self): + print('led test') + duration = 0.4 + COLORS = ['red', 'green', 'blue'] + + for color in range(len(COLORS)): + for i in range(1,self.ROWS+1): + for j in range (0,self.COLS): + le = chr(j+65) + h = le+str(i) + #print (h) + self.layout.set(self.MAPPING[h], COLORS[color]) + self.layout.push_to_driver() + time.sleep(duration) + + time.sleep (1.2) + self.clear() + + + + def run_flare(self, my_col="F", use_cols=False): + # Reference: http://www.anirama.com/1000leds/1d-fireworks/ + NUM_LEDS = 18 # FIXME + #NUM_SPARKS = NUM_LEDS/2 #// max number (could be NUM_LEDS / 2); + + sparkPos = [0,0,0,0,0,0,0,0,0] # width of flare: 3 LED + sparkVel = [0,0,0,0,0,0,0,0,0] + sparkCol = [0,0,0,0,0,0,0,0,0] + tmp_col = ord (my_col) + spark_col = [tmp_col,tmp_col,tmp_col,tmp_col,tmp_col,tmp_col,tmp_col,tmp_col,tmp_col] + + flarePos = 0. + gravity = -.004 * 10 # m/s/s + flareVel = random.randint(50,90) / 100. *1.55 # trial and error to get reasonable range + brightness = 1. + + # initialize launch sparks + print ("Init launch sparks") + for i in range (0,3): # FIXME nSparks + sparkPos[i] = 0. + sparkVel[i] = flareVel * float(random.randint(80,120) / 100) # * (flareVel / 5) + # random around 20% of flare velocity + sparkCol[i] = sparkVel[i] * 1000 + sparkCol[i] = clamp(sparkCol[i], 0, 255) + print (str(i)+ " with "+str(sparkVel[i])+" and "+str(sparkCol[i])) + + # Phase 1: Flare + print ("Launch flare") + self.clear() + while flareVel >= -.2: + # sparks + print ("Run spark with velocity "+str(flareVel)) + + # Disable all led in column + for i in range (1,NUM_LEDS+1): + tmp_led = my_col + str (i) + self.layout.set(self.MAPPING[tmp_led], (0,0,0)) + + for i in range (0,3): + sparkPos[i] = sparkPos[i] + sparkVel[i] + sparkPos[i] = clamp(sparkPos[i], 0.0, NUM_LEDS*1.0) + sparkVel[i] = sparkVel[i] + gravity + sparkCol[i] = sparkCol[i] -.8*7 # FIXME + sparkCol[i] = clamp(sparkCol[i], 0.0, 255.0) + tmp_row = clamp(int (sparkPos[i]), 1, NUM_LEDS) + c = (sparkCol[i],0,0) # FIXME: cleanup + tmp_led = my_col + str (tmp_row) + print ("Spark position "+str(tmp_led)+" with "+str(sparkPos[i])+" and "+str(sparkCol[i])) + self.layout.set(self.MAPPING[tmp_led], c) + + + # flare + tmp_row = clamp(int (flarePos), 1, NUM_LEDS) + tmp_led = my_col + str (tmp_row) + flareCol = (255.,0,0) # FIXME + print ("Flare position "+str(tmp_led)+" with "+str(flarePos)+" and "+str(flareCol)) + self.layout.set(self.MAPPING[tmp_led], flareCol) + self.layout.push_to_driver() + flarePos = flarePos + flareVel + flarePos = clamp(flarePos, 0, NUM_LEDS) + flareVel = flareVel + gravity + brightness = brightness * 0.9 + + # Phase 2: Explosion + print ("Run explosion") + nSparks = int (flarePos / 2) + + # Initialize Sparks + for i in range (0,nSparks): + sparkPos[i] = flarePos + sparkVel[i] = float(random.randint(-100,100) / 100) # from -1 to 1 + sparkCol[i] = abs(sparkVel[i]) * 500 # set colors before scaling velocity to keep them bright + sparkCol[i] = clamp(sparkCol[i], 0., 255.) + sparkVel[i] = sparkVel[i] * flarePos / NUM_LEDS # proportional to height + + sparkCol[0] = 255 # // this will be our known spark + dying_gravity = gravity + c1 = 120*1.5 + c2 = 50*2.7 + + while sparkCol[0] > c2/128: # as long as our known spark is lit, work with all the sparks + print ("Run spark with reference spark lit "+str(sparkCol[0])) + + # Disable all led in column + for i in range (1,NUM_LEDS+1): + tmp_led = my_col + str (i) + self.layout.set(self.MAPPING[tmp_led], (0,0,0)) + + for i in range (0,nSparks): + sparkPos[i] = sparkPos[i] + sparkVel[i] + sparkPos[i] = clamp(sparkPos[i], 0, NUM_LEDS) + sparkVel[i] = sparkVel[i] + dying_gravity + sparkCol[i] = sparkCol[i] * .9 # FIXME + sparkCol[i] = clamp(sparkCol[i], 0, 255) # // red cross dissolve + + tmp_col = my_col + if use_cols: + spark_col[i] = spark_col[i] + sparkVel[i] + spark_col[i] = clamp(spark_col[i],65,65+11-1) # FIXME + tmp_col = chr(int(spark_col[i])) + + c = (0,0,0) + tmp_row = clamp(int (sparkPos[i]), 1, NUM_LEDS) + tmp_led = tmp_col + str (tmp_row) + if(sparkCol[i] > c1): #// fade white to yellow + c = (255, 255, (255 * (sparkCol[i] - c1)) / (255 - c1)) + elif sparkCol[i] < c2: # // fade from red to black + c = ((255 * sparkCol[i]) / c2, 0, 0) + else: # // fade from yellow to red + c = (255, (255 * (sparkCol[i] - c2)) / (c1 - c2), 0) + + print ("Spark position "+str(tmp_led)+" with "+str(sparkPos[i])+" and "+str(tmp_col)+" and "+str(c)) + + self.layout.set(self.MAPPING[tmp_led], c) + + dying_gravity = dying_gravity * .995 #// as sparks burn out they fall slower + self.layout.push_to_driver() + + def run_flare_multi(self, my_col="F", use_cols=False): + # Reference: http://www.anirama.com/1000leds/1d-fireworks/ + NUM_LEDS = 18 # FIXME + #NUM_SPARKS = NUM_LEDS/2 #// max number (could be NUM_LEDS / 2); + + sparkPos = [0,0,0,0,0,0,0,0,0] # width of flare: 3 LED + sparkVel = [0,0,0,0,0,0,0,0,0] + sparkCol = [0,0,0,0,0,0,0,0,0] + tmp_col = ord (my_col) + spark_col = [tmp_col,tmp_col,tmp_col,tmp_col,tmp_col,tmp_col,tmp_col,tmp_col,tmp_col] + + flarePos = 0. + gravity = -.004 * 10 # m/s/s + flareVel = random.randint(50,90) / 100. *1.55 # trial and error to get reasonable range + brightness = 1. + + # initialize launch sparks + print ("Init launch sparks") + for i in range (0,3): # FIXME nSparks + sparkPos[i] = 0. + sparkVel[i] = flareVel * float(random.randint(80,120) / 100) # * (flareVel / 5) + # random around 20% of flare velocity + sparkCol[i] = sparkVel[i] * 1000 + sparkCol[i] = clamp(sparkCol[i], 0, 255) + print (str(i)+ " with "+str(sparkVel[i])+" and "+str(sparkCol[i])) + + # Phase 1: Flare + print ("Launch flare") + self.clear() + while flareVel >= -.2: + # sparks + print ("Run spark with velocity "+str(flareVel)) + + # Disable all led in column + for i in range (1,NUM_LEDS+1): + for my_col in ["A","B","C","D","E","F","G","H","I","J","K"]: + tmp_led = my_col + str (i) + self.layout.set(self.MAPPING[tmp_led], (0,0,0)) + + for i in range (0,3): + sparkPos[i] = sparkPos[i] + sparkVel[i] + sparkPos[i] = clamp(sparkPos[i], 0.0, NUM_LEDS*1.0) + sparkVel[i] = sparkVel[i] + gravity + sparkCol[i] = sparkCol[i] -.8*7 # FIXME + sparkCol[i] = clamp(sparkCol[i], 0.0, 255.0) + tmp_row = clamp(int (sparkPos[i]), 1, NUM_LEDS) + c = (sparkCol[i],0,0) # FIXME: cleanup + for my_col in ["A","B","C","D","E","F","G","H","I","J","K"]: + tmp_led = my_col + str (tmp_row) + print ("Spark position "+str(tmp_led)+" with "+str(sparkPos[i])+" and "+str(sparkCol[i])) + self.layout.set(self.MAPPING[tmp_led], c) + + + # flare + tmp_row = clamp(int (flarePos), 1, NUM_LEDS) + for my_col in ["A","B","C","D","E","F","G","H","I","J","K"]: + tmp_led = my_col + str (tmp_row) + flareCol = (255.,0,0) # FIXME + print ("Flare position "+str(tmp_led)+" with "+str(flarePos)+" and "+str(flareCol)) + self.layout.set(self.MAPPING[tmp_led], flareCol) + + self.layout.push_to_driver() + flarePos = flarePos + flareVel + flarePos = clamp(flarePos, 0, NUM_LEDS) + flareVel = flareVel + gravity + brightness = brightness * 0.9 + + # Phase 2: Explosion + print ("Run explosion") + nSparks = int (flarePos / 2) + + # Initialize Sparks + for i in range (0,nSparks): + sparkPos[i] = flarePos + sparkVel[i] = float(random.randint(-100,100) / 100) # from -1 to 1 + sparkCol[i] = abs(sparkVel[i]) * 500 # set colors before scaling velocity to keep them bright + sparkCol[i] = clamp(sparkCol[i], 0., 255.) + sparkVel[i] = sparkVel[i] * flarePos / NUM_LEDS # proportional to height + + sparkCol[0] = 255 # // this will be our known spark + dying_gravity = gravity + c1 = 120*1.5 + c2 = 50*2.7 + + while sparkCol[0] > c2/128: # as long as our known spark is lit, work with all the sparks + print ("Run spark with reference spark lit "+str(sparkCol[0])) + + # Disable all led in column + for i in range (1,NUM_LEDS+1): + for my_col in ["A","B","C","D","E","F","G","H","I","J","K"]: + tmp_led = my_col + str (i) + self.layout.set(self.MAPPING[tmp_led], (0,0,0)) + + for i in range (0,nSparks): + sparkPos[i] = sparkPos[i] + sparkVel[i] + sparkPos[i] = clamp(sparkPos[i], 0, NUM_LEDS) + sparkVel[i] = sparkVel[i] + dying_gravity + sparkCol[i] = sparkCol[i] * .9 # FIXME + sparkCol[i] = clamp(sparkCol[i], 0, 255) # // red cross dissolve + + if(sparkCol[i] > c1): #// fade white to yellow + c = (255, 255, (255 * (sparkCol[i] - c1)) / (255 - c1)) + elif sparkCol[i] < c2: # // fade from red to black + c = ((255 * sparkCol[i]) / c2, 0, 0) + else: # // fade from yellow to red + c = (255, (255 * (sparkCol[i] - c2)) / (c1 - c2), 0) + + for my_col in ["A","B","C","D","E","F","G","H","I","J","K"]: + tmp_col = my_col + + c = (0,0,0) + tmp_row = clamp(int (sparkPos[i]), 1, NUM_LEDS) + tmp_led = tmp_col + str (tmp_row) + + + print ("Spark position "+str(tmp_led)+" with "+str(sparkPos[i])+" and "+str(tmp_col)+" and "+str(c)) + + self.layout.set(self.MAPPING[tmp_led], c) + + dying_gravity = dying_gravity * .995 #// as sparks burn out they fall slower + self.layout.push_to_driver() + + def run_animation(self, duration = 0.01): + # The moonboard can serve a (x,y) = (11,18) --> 198 Pixel display + # Refs: + # - http://www.anirama.com/1000leds/1d-fireworks/ + duration2 = duration * 0 + + for i in range(1,self.ROWS+1): + for j in range (0,self.COLS): + + le = chr(j+65) + h = le+str(i) + print (h) + for c in [COLORS.purple, COLORS.blue, COLORS.red]: + self.layout.set(self.MAPPING[h], c) + self.layout.push_to_driver() + time.sleep(duration) + + time.sleep(duration2) + + self.clear() + + def display_melon(self): + # Ref: http://pixelartmaker.com/art/d8f3ce82dd215ed + self.clear() + red = (255,0,0) + black = (0,0,0) + lila = hex2rgb("#ff99dd") #(255,100,200) #ff99dd + green1 = hex2rgb("#118233") #COLORS.lime) + green2 = hex2rgb("#089c48") #COLORS.limegreen + + for h in ["C1","D1","E1","F1","G1"]: + self.layout.set(self.MAPPING[h], green2) + for h in ["B2","H2"]: + self.layout.set(self.MAPPING[h], green2) + for h in ["C2","D2","E2","F2","G2"]: + self.layout.set(self.MAPPING[h], green1) + for h in ["A3","I3"]: + self.layout.set(self.MAPPING[h], green2) + for h in ["H3"]: + self.layout.set(self.MAPPING[h], green1) + for h in ["B3","C3","D3","E3","F3","G3"]: + self.layout.set(self.MAPPING[h], lila) + for h in ["A4","C4","D4","E4","F4","G4"]: + self.layout.set(self.MAPPING[h], red) + for h in ["H4"]: + self.layout.set(self.MAPPING[h], lila) + for h in ["I4"]: + self.layout.set(self.MAPPING[h], green1) + for h in ["J4"]: + self.layout.set(self.MAPPING[h], green2) + for h in ["A5","B5","C5","E5","G4"]: + self.layout.set(self.MAPPING[h], red) + for h in ["H5"]: + self.layout.set(self.MAPPING[h], lila) + for h in ["I5"]: + self.layout.set(self.MAPPING[h], green1) + for h in ["J5"]: + self.layout.set(self.MAPPING[h], green2) + for h in ["B6","C6","D6","E6","F6","G6"]: + self.layout.set(self.MAPPING[h], red) + for h in ["H6"]: + self.layout.set(self.MAPPING[h], lila) + for h in ["I6"]: + self.layout.set(self.MAPPING[h], green1) + for h in ["J6"]: + self.layout.set(self.MAPPING[h], green2) + for h in ["C7","D7","F7","G7"]: + self.layout.set(self.MAPPING[h], red) + for h in ["H7"]: + self.layout.set(self.MAPPING[h], lila) + for h in ["I7"]: + self.layout.set(self.MAPPING[h], green1) + for h in ["J7"]: + self.layout.set(self.MAPPING[h], green2) + for h in ["D8","E8","F8"]: + self.layout.set(self.MAPPING[h], red) + for h in ["H8"]: + self.layout.set(self.MAPPING[h], lila) + for h in ["I8"]: + self.layout.set(self.MAPPING[h], green1) + for h in ["J8"]: + self.layout.set(self.MAPPING[h], green2) + for h in ["E9","F9"]: + self.layout.set(self.MAPPING[h], red) + for h in ["G9"]: + self.layout.set(self.MAPPING[h], lila) + for h in ["H9"]: + self.layout.set(self.MAPPING[h], green1) + for h in ["I9"]: + self.layout.set(self.MAPPING[h], green2) + for h in ["F10"]: + self.layout.set(self.MAPPING[h], lila) + for h in ["G10"]: + self.layout.set(self.MAPPING[h], green1) + for h in ["H10"]: + self.layout.set(self.MAPPING[h], green2) + + self.layout.push_to_driver() + time.sleep(10) + + + + def run_animation_single_color(self, duration = 5,color=(255,0,0)): + for i in range(1,self.ROWS+1): + for j in range (0,self.COLS): + + le = chr(j+65) + h = le+str(i) + print (h) + self.layout.set(self.MAPPING[h], color) + + self.layout.push_to_driver() + + time.sleep(duration) + + self.clear() + + def run_animation_xmas(self, duration = 0.01): + + duration2 = 1#duration * 10 + + for c in [COLORS.purple, COLORS.blue, COLORS.red, COLORS.green]: + + for i in range(1,self.ROWS+1): + for j in range (0,self.COLS): + + le = chr(j+65) + h = le+str(i) + print (h) + self.layout.set(self.MAPPING[h], c) + + self.layout.push_to_driver() + + time.sleep(duration) + + time.sleep(duration2) + + + + + + self.clear() + + def display_holdset(self, holdset="Hold Set A", duration=10, **kwds): + print ("Display holdset: " + str(holdset)) + + with open('../problems/HoldSetup.json') as json_file: # FIXME: path + data = json.load(json_file) + for hold in data[self.SETUP]: + hs = (data[self.SETUP][hold]['HoldSet']) + color = COLORS.black + + if (hs == holdset):# FIXME + color = COLORS.green + + self.layout.set(self.MAPPING[hold], color) + + #self.set_hold (hold, color) + #print "Orientation" + + self.layout.push_to_driver() + + wait_holdset_duration = duration # FIXME + time.sleep(wait_holdset_duration) + + self.clear() + + + + def stop_animation(self): + if self.animation is not None: + self.animation.stop() + + +class TestAnimation: + COLOR=[COLORS.Green, COLORS.Blue] + def __init__(self, layout, ): + self.layout = layout + + def step(self, amt=1): + pass + +if __name__=="__main__": + import argparse + import time + import subprocess + + parser = argparse.ArgumentParser(description='Test led system') + + parser.add_argument('--driver_type', type=str, + help='driver type, depends on leds and device controlling the led.',choices=['PiWS281x', 'WS2801', 'SimPixel'], + default = "PiWS281x") + parser.add_argument('--duration', type=int, default=10, + help='Delay of progress.') + parser.add_argument('--holdset', type=str, help="Display a holdset for current layout", + choices=['Hold Set A', 'Hold Set B', 'Hold Set C', 'Original School Holds', "Wooden Holds"], + default = "Hold Set A") + args = parser.parse_args() + + led_layout = None + + MOONBOARD = MoonBoard(args.driver_type,led_layout ) + + # Display a holdset + #MOONBOARD.display_holdset(args.holdset, args.duration) + + print("Run animation,") + #MOONBOARD.run_animation() + #MOONBOARD.run_flare(my_col="A") + while True: + # + MOONBOARD.run_animation_xmas() + MOONBOARD.display_melon() + MOONBOARD.run_flare(my_col="F") + MOONBOARD.run_animation_single_color(color=(255,1,154)) + MOONBOARD.run_animation_single_color(color=(255,255,0)) + #MOONBOARD.run_animation_single_color(color=(0,0,255)) + + print(f"wait {args.duration} seconds,") + time.sleep(args.duration) + print("clear and exit.") + MOONBOARD.clear() \ No newline at end of file diff --git a/led/led_mapping.json b/led/led_mapping.json index f118f2d..7211e33 100755 --- a/led/led_mapping.json +++ b/led/led_mapping.json @@ -1 +1 @@ -{"A1": 0, "A2": 3, "A3": 6, "A4": 9, "A5": 12, "A6": 15, "A7": 18, "A8": 21, "A9": 24, "A10": 27, "A11": 30, "A12": 33, "A13": 36, "A14": 39, "A15": 42, "A16": 45, "A17": 48, "A18": 51, "B18": 54, "B17": 57, "B16": 60, "B15": 63, "B14": 66, "B13": 69, "B12": 72, "B11": 75, "B10": 78, "B9": 81, "B8": 84, "B7": 87, "B6": 90, "B5": 93, "B4": 96, "B3": 99, "B2": 102, "B1": 105, "C1": 108, "C2": 111, "C3": 114, "C4": 117, "C5": 120, "C6": 123, "C7": 126, "C8": 129, "C9": 132, "C10": 135, "C11": 138, "C12": 141, "C13": 144, "C14": 147, "C15": 150, "C16": 153, "C17": 156, "C18": 159, "D18": 162, "D17": 165, "D16": 168, "D15": 171, "D14": 174, "D13": 177, "D12": 180, "D11": 183, "D10": 186, "D9": 189, "D8": 192, "D7": 195, "D6": 198, "D5": 201, "D4": 204, "D3": 207, "D2": 210, "D1": 213, "E1": 216, "E2": 219, "E3": 222, "E4": 225, "E5": 228, "E6": 231, "E7": 234, "E8": 237, "E9": 240, "E10": 243, "E11": 246, "E12": 249, "E13": 252, "E14": 256, "E15": 259, "E16": 262, "E17": 265, "E18": 268, "F18": 271, "F17": 274, "F16": 277, "F15": 280, "F14": 283, "F13": 286, "F12": 289, "F11": 292, "F10": 295, "F9": 298, "F8": 301, "F7": 304, "F6": 307, "F5": 310, "F4": 313, "F3": 316, "F2": 319, "F1": 322, "G1": 325, "G2": 328, "G3": 331, "G4": 334, "G5": 337, "G6": 340, "G7": 343, "G8": 346, "G9": 349, "G10": 352, "G11": 355, "G12": 358, "G13": 361, "G14": 364, "G15": 367, "G16": 370, "G17": 373, "G18": 376, "H18": 379, "H17": 382, "H16": 385, "H15": 388, "H14": 391, "H13": 394, "H12": 397, "H11": 400, "H10": 403, "H9": 406, "H8": 409, "H7": 412, "H6": 415, "H5": 418, "H4": 421, "H3": 424, "H2": 427, "H1": 430, "I1": 433, "I2": 436, "I3": 439, "I4": 442, "I5": 445, "I6": 448, "I7": 451, "I8": 454, "I9": 457, "I10": 461, "I11": 464, "I12": 467, "I13": 470, "I14": 473, "I15": 476, "I16": 479, "I17": 482, "I18": 485, "J18": 488, "J17": 491, "J16": 494, "J15": 497, "J14": 500, "J13": 503, "J12": 506, "J11": 509, "J10": 512, "J9": 515, "J8": 518, "J7": 521, "J6": 524, "J5": 527, "J4": 530, "J3": 533, "J2": 536, "J1": 539, "K1": 542, "K2": 545, "K3": 548, "K4": 551, "K5": 554, "K6": 557, "K7": 560, "K8": 563, "K9": 566, "K10": 569, "K11": 572, "K12": 575, "K13": 578, "K14": 581, "K15": 584, "K16": 587, "K17": 590, "K18": 593} \ No newline at end of file +{"A1": 0, "A2": 3, "A3": 6, "A4": 9, "A5": 12, "A6": 15, "A7": 18, "A8": 21, "A9": 24, "A10": 27, "A11": 30, "A12": 33, "A13": 36, "A14": 39, "A15": 42, "A16": 45, "A17": 48, "A18": 51, "B18": 54, "B17": 57, "B16": 60, "B15": 63, "B14": 66, "B13": 69, "B12": 72, "B11": 75, "B10": 78, "B9": 81, "B8": 84, "B7": 87, "B6": 90, "B5": 93, "B4": 96, "B3": 99, "B2": 102, "B1": 105, "C1": 108, "C2": 111, "C3": 114, "C4": 117, "C5": 120, "C6": 123, "C7": 126, "C8": 129, "C9": 132, "C10": 135, "C11": 138, "C12": 141, "C13": 144, "C14": 147, "C15": 150, "C16": 153, "C17": 156, "C18": 159, "D18": 162, "D17": 165, "D16": 168, "D15": 171, "D14": 174, "D13": 177, "D12": 180, "D11": 183, "D10": 186, "D9": 189, "D8": 192, "D7": 195, "D6": 198, "D5": 201, "D4": 204, "D3": 207, "D2": 210, "D1": 213, "E1": 216, "E2": 219, "E3": 222, "E4": 225, "E5": 228, "E6": 231, "E7": 234, "E8": 237, "E9": 240, "E10": 243, "E11": 246, "E12": 249, "E13": 252, "E14": 256, "E15": 259, "E16": 262, "E17": 265, "E18": 268, "F18": 271, "F17": 274, "F16": 277, "F15": 280, "F14": 283, "F13": 286, "F12": 289, "F11": 292, "F10": 295, "F9": 298, "F8": 301, "F7": 304, "F6": 307, "F5": 310, "F4": 313, "F3": 316, "F2": 319, "F1": 322, "G1": 325, "G2": 328, "G3": 331, "G4": 334, "G5": 337, "G6": 340, "G7": 343, "G8": 346, "G9": 349, "G10": 352, "G11": 355, "G12": 358, "G13": 361, "G14": 364, "G15": 367, "G16": 370, "G17": 373, "G18": 376, "H18": 379, "H17": 382, "H16": 385, "H15": 388, "H14": 391, "H13": 394, "H12": 397, "H11": 400, "H10": 403, "H9": 406, "H8": 409, "H7": 412, "H6": 415, "H5": 418, "H4": 421, "H3": 424, "H2": 427, "H1": 430, "I1": 433, "I2": 436, "I3": 439, "I4": 442, "I5": 445, "I6": 448, "I7": 451, "I8": 454, "I9": 457, "I10": 461, "I11": 464, "I12": 467, "I13": 470, "I14": 473, "I15": 476, "I16": 479, "I17": 482, "I18": 485, "J18": 488, "J17": 491, "J16": 494, "J15": 497, "J14": 500, "J13": 503, "J12": 506, "J11": 509, "J10": 512, "J9": 515, "J8": 518, "J7": 521, "J6": 524, "J5": 527, "J4": 530, "J3": 533, "J2": 536, "J1": 539, "K1": 542, "K2": 545, "K3": 548, "K4": 551, "K5": 554, "K6": 557, "K7": 560, "K8": 563, "K9": 566, "K10": 569, "K11": 572, "K12": 575, "K13": 578, "K14": 581, "K15": 584, "K16": 587, "K17": 590, "K18": 593} diff --git a/led/moonboard_led.service b/led/moonboard_led.service new file mode 100644 index 0000000..33ea6bd --- /dev/null +++ b/led/moonboard_led.service @@ -0,0 +1,14 @@ +[Unit] +Description=Moonboard LED Service +After=multi-user.target mosquitto.service + +[Service] +WorkingDirectory=/home/pi/moonboard/led/ +ExecStart=/usr/bin/python3 /home/pi/moonboard/led/moonboard_led_service.py +Type=idle +User=root +StandardOutput=file:/var/log/moonboard_led_stdout.log +StandardError=file:/var/log/moonboard_led_stderr.log + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/led/moonboard_led_service.py b/led/moonboard_led_service.py new file mode 100644 index 0000000..eaec441 --- /dev/null +++ b/led/moonboard_led_service.py @@ -0,0 +1,104 @@ +# -*- coding: utf-8 -*- +import argparse +from moonboard import MoonBoard +from functools import partial +import json +import RPi.GPIO as GPIO +import os +#import signal +import sys +import logging +import time +import paho.mqtt.client as paho # FIXME pip install +import math # floor + +# external power LED and power button +LED_GPIO = 26 +BUTTON_GPIO = 3 + + +import logging +logging.basicConfig(level=logging.DEBUG, + format='Display(%(threadName)-10s) %(message)s', + ) + +class Database(): + def __init__(self, driver_type="", led_layout=""): + self._MOONBOARD = MoonBoard(driver_type, led_layout) + + # Init timers + self._time_current = time.time() + self._time_last = self._time_current + self._update_interval = 1.0 #0.5 # Update interval for display in seconds + + + def _on_message(self, client, userdata, message): + logging.debug("Received message " + str(message.payload.decode("utf-8"))) + #mm = message.payload.decode("utf-8") + msg = json.loads(message.payload.decode("utf-8")) + + color_start = (0,255,0) + color_moves = (0,0,255) + color_top = (255,0,0) + self._MOONBOARD.clear() + for s in msg["START"]: + self._MOONBOARD.layout.set(self._MOONBOARD.MAPPING[s], color_start) + for m in msg["MOVES"]: + self._MOONBOARD.layout.set(self._MOONBOARD.MAPPING[m], color_moves) + for t in msg["TOP"]: + self._MOONBOARD.layout.set(self._MOONBOARD.MAPPING[t], color_top) + #self._MOONBOARD.layout.set(self._MOONBOARD.MAPPING[ihold], color_1er_done) + self._MOONBOARD.layout.push_to_driver() + + + + def _record_data(self, hostname="localhost",port=1883): + logging.debug("Start recording data from mqtt to database") + self._client= paho.Client("client-001") # FIXME + self._client.on_message=self._on_message + self._client.connect(hostname,port,60)#connect + + # FIXME: subscribe to all? + + self._client.subscribe("moonboard/ble/problem") + + + self._client.loop_forever() + +# Main stuff + +if __name__ == "__main__": + + parser = argparse.ArgumentParser(description='') + + parser.add_argument('--driver_type', + help='driver type, depends on leds and device controlling the led.', + choices=['PiWS281x', 'WS2801', 'SimPixel'], + default='PiWS281x') + + parser.add_argument('--brightness', default=100, type=int) + + parser.add_argument('--led_mapping', + type=str, + default='led_mapping.json', + ) + + parser.add_argument('--debug', action = "store_true") + + args = parser.parse_args() + argsd=vars(args) + logger = logging.getLogger('run') + logger.setLevel(logging.DEBUG) + logger.addHandler(logging.StreamHandler()) + + if args.debug: + logger.setLevel(logging.DEBUG) + else: + logger.setLevel(logging.INFO) + + #problems + led_layout = args.led_mapping + driver_type = args.driver_type + + d = Database(driver_type=driver_type, led_layout=led_layout) + d._record_data(hostname="raspi-moonboard") diff --git a/led/run_testing_interface_hangboard.py b/led/run_testing_interface_hangboard.py new file mode 100644 index 0000000..0697951 --- /dev/null +++ b/led/run_testing_interface_hangboard.py @@ -0,0 +1,167 @@ +# -*- coding: utf-8 -*- +import argparse +from moonboard import MoonBoard +from functools import partial +import json +import RPi.GPIO as GPIO +import os +#import signal +import sys +import logging +import time +import paho.mqtt.client as paho # FIXME pip install +import math # floor + +# external power LED and power button +LED_GPIO = 26 +BUTTON_GPIO = 3 + + +import logging +logging.basicConfig(level=logging.DEBUG, + format='Display(%(threadName)-10s) %(message)s', + ) + + + +class Database(): + def __init__(self, driver_type="", led_layout=""): + self._MOONBOARD = MoonBoard(driver_type, led_layout) + + # Init timers + self._time_current = time.time() + self._time_last = self._time_current + self._update_interval = 1.0 #0.5 # Update interval for display in seconds + + + def _on_message(self, client, userdata, message): + logging.debug("Received message " + str(message.payload.decode("utf-8"))) + + msg = json.loads(message.payload.decode("utf-8")) + + # Check if interval large enough + time_last = self._time_current + self._time_current = time.time() + del_time = self._time_current - self._time_last + logging.debug(del_time) + if del_time < self._update_interval: + return + + self._time_last = time_last + + logging.debug ("Using it") + #l = "\rLoad: %.1f " % msg["loadcurrent"] + #t = "Time: " + str(msg["time"]) + #lmax = "\rLoad Max: %.1f " % msg["loadmaximal"] + ll = msg["loadcurrent"] + lc = 0 + lc1 = 0 + lc10 = 0 + if ll > 0: + lc = int(ll) + lc1 = int(repr(lc)[-1]) + lc10 = math.floor(lc/10) + #lc10 = int(repr(lc)[-2]) + #lc100 = int(repr(lc)[-3]) + logging.debug("Using: lc10:"+str(lc10)) + logging.debug("Using: lc1:"+str(lc1)) + + logging.debug ("Clean board") + + self._MOONBOARD.clear() + + logging.debug ("Begin display holds") + #charmax = chr(ord('@')+lc1) + + ## 10er + ymax = 10 + ymin = 1 + ichar = "E"#chr(ord('@')+i1) + ytmp = ymax - lc10 + color_10er_done = (255,0,0) + color_10er_not_done = (0,0,50) + for y in range(ymin,ymax): + ihold = ichar+str(y) + + if y < ytmp: + self._MOONBOARD.layout.set(self._MOONBOARD.MAPPING[ihold], color_10er_not_done) + else: + logging.debug("Use hold "+ihold) + self._MOONBOARD.layout.set(self._MOONBOARD.MAPPING[ihold], color_10er_done) + + ## 1er + ichar = "F"#chr(ord('@')+i1) + ytmp = ymax - lc1 + color_1er_done = (0,255,0) + color_1er_not_done = (0,0,50) + for y in range(ymin,ymax): + ihold = ichar+str(y) + + if y < ytmp: + self._MOONBOARD.layout.set(self._MOONBOARD.MAPPING[ihold], color_1er_not_done) + else: + logging.debug("Use hold "+ihold) + self._MOONBOARD.layout.set(self._MOONBOARD.MAPPING[ihold], color_1er_done) + + + + # FIXME: Runs too long - next event occurs.... + self._MOONBOARD.layout.push_to_driver() + + #self._MOONBOARD.show_hold("A4") + #self._MOONBOARD.show_hold("B4") + + + def _record_data(self, hostname="localhost",port=1883): + logging.debug("Start recording data from mqtt to database") + self._client= paho.Client("client-001") # FIXME + self._client.on_message=self._on_message + self._client.connect(hostname,port,60)#connect + + # FIXME: subscribe to all? + + self._client.subscribe("hangboard/sensor/load/loadstatus") + #self._client.subscribe("hangboard/sensor/sensorstatus") + #self._client.subscribe("hangboard/sensor/lastexercise") + #self._client.subscribe("hangboard/workout/userstatistics") + #self._client.subscribe("hangboard/workout/upcoming") + + self._client.loop_forever() + +# Main stuff + +if __name__ == "__main__": + + parser = argparse.ArgumentParser(description='') + + parser.add_argument('--driver_type', + help='driver type, depends on leds and device controlling the led.', + choices=['PiWS281x', 'WS2801', 'SimPixel'], + default='PiWS281x') + + parser.add_argument('--brightness', default=100, type=int) + + parser.add_argument('--led_mapping', + type=str, + default='led_mapping.json', + ) + + parser.add_argument('--debug', action = "store_true") + + args = parser.parse_args() + argsd=vars(args) + logger = logging.getLogger('run') + logger.setLevel(logging.DEBUG) + logger.addHandler(logging.StreamHandler()) + + if args.debug: + logger.setLevel(logging.DEBUG) + else: + logger.setLevel(logging.INFO) + + #problems + led_layout = args.led_mapping + driver_type = args.driver_type + + d = Database(driver_type=driver_type, led_layout=led_layout) + d._record_data(hostname="raspi-hangboard") diff --git a/run.py b/run.py old mode 100755 new mode 100644 index 32373bb..62b69c8 --- a/run.py +++ b/run.py @@ -1,11 +1,10 @@ # -*- coding: utf-8 -*- import argparse -from led.moonboard import MoonBoard +from led.moonboard import MoonBoard,LED_LAYOUT from gi.repository import GLib import dbus from dbus.mainloop.glib import DBusGMainLoop from functools import partial -import json import json import RPi.GPIO as GPIO import os @@ -13,13 +12,10 @@ import sys import logging - - # external power LED and power button LED_GPIO = 26 BUTTON_GPIO = 3 - # Button function def button_pressed_callback(channel): print("Button pressed") @@ -27,7 +23,6 @@ def button_pressed_callback(channel): #print('Shutting down') #os.system("sudo shutdown -h now") - def new_problem_cb(mb,holds_string): holds = json.loads(holds_string) mb.show_problem(holds) @@ -35,19 +30,18 @@ def new_problem_cb(mb,holds_string): if __name__ == "__main__": - # Comment out button stuff - yet... - # # BUTTON + LED setup - # GPIO.setmode(GPIO.BCM) - # GPIO.setwarnings(False) - # GPIO.setup(LED_GPIO, GPIO.OUT) - # GPIO.output(LED_GPIO,1) - # GPIO.setup(BUTTON_GPIO, GPIO.IN, pull_up_down=GPIO.PUD_UP) - # # interupt handling for the power button - # GPIO.add_event_detect(BUTTON_GPIO, GPIO.RISING, - # callback=button_pressed_callback, bouncetime=300) + # BUTTON + LED setup + GPIO.setmode(GPIO.BCM) + GPIO.setwarnings(False) + GPIO.setup(LED_GPIO, GPIO.OUT) + GPIO.output(LED_GPIO,1) + GPIO.setup(BUTTON_GPIO, GPIO.IN, pull_up_down=GPIO.PUD_UP) + # interupt handling for the power button + GPIO.add_event_detect(BUTTON_GPIO, GPIO.RISING, + callback=button_pressed_callback, bouncetime=300) - # #signal.signal(signal.SIGINT, signal_handler) - # #signal.pause() + #signal.signal(signal.SIGINT, signal_handler) + #signal.pause() parser = argparse.ArgumentParser(description='') @@ -58,9 +52,9 @@ def new_problem_cb(mb,holds_string): parser.add_argument('--brightness', default=100, type=int) - parser.add_argument('--led_mapping', - type=str, - default='led_mapping.json', + parser.add_argument('--led_layout', + default=None, + choices=list(LED_LAYOUT.keys()) ) parser.add_argument('--debug', action = "store_true") @@ -77,13 +71,12 @@ def new_problem_cb(mb,holds_string): else: logger.setLevel(logging.INFO) - MOONBOARD = MoonBoard( - args.driver_type, - args.led_mapping) - - print(f"Led mapping:{args.led_mapping}") - print(f"Driver type:{args.driver_type}") + # problems + led_layout = LED_LAYOUT.get(args.led_layout) if args.led_layout is not None else None + MOONBOARD = MoonBoard(args.driver_type, led_layout) + # run led test + MOONBOARD.led_test() MOONBOARD.clear() # connect to dbus signal new problem diff --git a/scripts/fix_startup.sh b/scripts/fix_startup.sh index 4c0150d..4fe5f93 100755 --- a/scripts/fix_startup.sh +++ b/scripts/fix_startup.sh @@ -1,7 +1,4 @@ #!/bin/bash -sudo systemctl stop moonboard -sudo systemctl stop com.moonboard - -# startup in correct order -sudo systemctl restart com.moonboard -sudo systemctl restart moonboard +for service in moonboard_led.service moonboard_ble.service; do + sudo systemctl restart "$service" +done diff --git a/scripts/reset.sh b/scripts/reset.sh deleted file mode 100755 index 20158c0..0000000 --- a/scripts/reset.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/bash - -sudo systemctl restart dbus - -sudo systemctl restart com.moonboard - -sudo systemctl restart moonboard - diff --git a/scripts/run.sh b/scripts/run.sh deleted file mode 100755 index 853907a..0000000 --- a/scripts/run.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/bash -set -o errexit -LOG_FILE=/var/log/moonboard - -exec 1>$LOG_FILE -exec 2>&1 - -sudo /usr/bin/python3 /home/pi/moonboard/run.py --debug --driver PiWS281x $1 \ No newline at end of file diff --git a/scripts/update.sh b/scripts/update.sh new file mode 100755 index 0000000..1f8ac36 --- /dev/null +++ b/scripts/update.sh @@ -0,0 +1,33 @@ +#!/bin/bash +set -e + +REPO_DIR="$(cd "$(dirname "$0")/.." && pwd)" +BRANCH="${1:-main}" + +echo "=== Moonboard Update ===" +echo "Repo: $REPO_DIR" +echo "Branch: $BRANCH" + +# Pull latest code +echo "" +echo "Pulling latest changes..." +cd "$REPO_DIR" +git fetch origin "$BRANCH" +git checkout "$BRANCH" +git pull origin "$BRANCH" + +# Restart services +echo "" +echo "Restarting services..." +for service in moonboard_ble moonboard_led; do + if systemctl is-enabled "${service}.service" &>/dev/null; then + echo " Restarting ${service}..." + sudo systemctl restart "${service}.service" + echo " ${service}: $(systemctl is-active ${service}.service)" + else + echo " ${service}: not installed, skipping" + fi +done + +echo "" +echo "Update complete." diff --git a/services/install_service.sh b/services/install_service.sh deleted file mode 100755 index caeb63e..0000000 --- a/services/install_service.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash -FILE=$1 -echo "Copy service file $FILE" -cp --verbose $FILE /lib/systemd/system/$FILE -echo "set permission" -chmod 644 /lib/systemd/system/$FILE -echo "restart service" -systemctl daemon-reload -systemctl enable $FILE -systemctl restart $FILE diff --git a/services/moonboard-install.service b/services/moonboard-install.service deleted file mode 100755 index 76473e4..0000000 --- a/services/moonboard-install.service +++ /dev/null @@ -1,11 +0,0 @@ -[Unit] -Description=Install Phase 2 -After=multi-user.target - -[Service] -ExecStart=/home/pi/moonboard/install-phase2.sh -User=root - -[Install] -WantedBy=multi-user.target - diff --git a/services/moonboard.service b/services/moonboard.service deleted file mode 100755 index 02d5829..0000000 --- a/services/moonboard.service +++ /dev/null @@ -1,12 +0,0 @@ - -[Unit] -Description= moon board app service -After=multi-user.target - -[Service] -User=pi -Type=idle -ExecStart=/bin/bash /home/pi/moonboard/scripts/run.sh - -[Install] -WantedBy=multi-user.target \ No newline at end of file