From f37452df468c0452c30d418def4b22d77df02856 Mon Sep 17 00:00:00 2001 From: redfast00 Date: Sun, 16 Aug 2020 04:16:36 +0200 Subject: [PATCH 1/4] Start writing web-based debugger --- debugging_tool/.editorconfig | 3 + debugging_tool/.gitignore | 138 +++++++++++++++++++++++++++++++ debugging_tool/requirements.txt | 7 ++ debugging_tool/server.py | 99 ++++++++++++++++++++++ debugging_tool/static/index.html | 74 +++++++++++++++++ debugging_tool/static/script.js | 75 +++++++++++++++++ docs/protocol.txt | 2 +- 7 files changed, 397 insertions(+), 1 deletion(-) create mode 100644 debugging_tool/.editorconfig create mode 100644 debugging_tool/.gitignore create mode 100644 debugging_tool/requirements.txt create mode 100644 debugging_tool/server.py create mode 100644 debugging_tool/static/index.html create mode 100644 debugging_tool/static/script.js diff --git a/debugging_tool/.editorconfig b/debugging_tool/.editorconfig new file mode 100644 index 0000000..74d6498 --- /dev/null +++ b/debugging_tool/.editorconfig @@ -0,0 +1,3 @@ +[*.py] +indent_style = space +indent_size = 4 diff --git a/debugging_tool/.gitignore b/debugging_tool/.gitignore new file mode 100644 index 0000000..a81c8ee --- /dev/null +++ b/debugging_tool/.gitignore @@ -0,0 +1,138 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ diff --git a/debugging_tool/requirements.txt b/debugging_tool/requirements.txt new file mode 100644 index 0000000..98b6c0f --- /dev/null +++ b/debugging_tool/requirements.txt @@ -0,0 +1,7 @@ +click==7.1.2 +Flask==1.1.2 +itsdangerous==1.1.0 +Jinja2==2.11.2 +MarkupSafe==1.1.1 +pyserial==3.4 +Werkzeug==1.0.1 diff --git a/debugging_tool/server.py b/debugging_tool/server.py new file mode 100644 index 0000000..595407b --- /dev/null +++ b/debugging_tool/server.py @@ -0,0 +1,99 @@ +from threading import Thread +from flask import Flask, jsonify, send_file +from time import sleep +from dataclasses import dataclass +from datetime import datetime + +app = Flask(__name__) +shared_message_log = [] + + +@dataclass +class Message: + payload: bytes + received_from: int + received_at: datetime + internal_id: int + + def readable_time(self): + return self.received_at.strftime('%H:%M:%S') + + def priority_bit(self): + return (self.received_from >> 10) & 0b1 + + def sender_type(self): + return (self.received_from >> 8) & 0b11 + + def sender_id(self): + return (self.received_from >> 0) & 0b1111_1111 + + def human_readable_type(self): + return ['controller', 'puzzle', 'needy', 'RESERVED TYPE'][self.sender_type()] + + def _parse_state_update(self): + timeleft = self.payload[1] << 0x18 | self.payload[2] << 0x10 | self.payload[3] << 0x08 | self.payload[4] + strikes = self.payload[5] + max_strikes = self.payload[6] + + return f'{timeleft/1000:3.2f} {strikes:02}/{max_strikes:02}' + + def parse_message(self): + sender_type = self.sender_type() + message_type = self.payload[0] + if sender_type == 0b00: # controller + if message_type == 0: + return "ACK" + elif message_type == 1: + return "HELLO" + elif message_type == 2: + return "START " + self._parse_state_update() + elif message_type == 3: + return "STATE " + self._parse_state_update() + elif message_type == 4: + return "SOLVED " + self._parse_state_update() + elif message_type == 5: + return "TIMEOUT " + self._parse_state_update() + elif message_type == 6: + return "STRIKEOUT " + self._parse_state_update() + elif sender_type == 0b01: # puzzle + if message_type == 0: + return "REGISTER" + elif message_type == 1: + return f"STRIKE {self.payload[5]}" + elif message_type == 2: + return f"SOLVED" + else: + return f"PARSE ERROR {self.received_from:011b} {self.payload.hex(' ')}" + + def serialize(self): + return { + 'time': self.readable_time(), + 'parsed': self.parse_message(), + 'pretty_raw_sender_id': f'{self.priority_bit():01b} {self.sender_type():02b} {self.sender_id():08b}', + 'raw_message': f"{self.payload.hex(' ')}", + 'human_readable_type': self.human_readable_type(), + 'sender_id': self.sender_id(), + 'internal_id': self.internal_id + } + + +def serial_reader(messagelog): + while True: + sleep(5) + received = Message(b'\x00' * 8, 0b101000110, datetime.now(), len(messagelog)) + messagelog.append(received.serialize()) + + +@app.route('/') +def index(): + return send_file('static/index.html') + +@app.route('/api.json') +def api(): + return jsonify([m for m in shared_message_log]) + + +if __name__ == '__main__': + thread = Thread(target=serial_reader, args=(shared_message_log, )) + thread.start() + app.run(debug=True) diff --git a/debugging_tool/static/index.html b/debugging_tool/static/index.html new file mode 100644 index 0000000..a0b0f23 --- /dev/null +++ b/debugging_tool/static/index.html @@ -0,0 +1,74 @@ + + + + + + CAN debugger + + + + +
+ + + + +
+
+ +
+ + + diff --git a/debugging_tool/static/script.js b/debugging_tool/static/script.js new file mode 100644 index 0000000..3cd6635 --- /dev/null +++ b/debugging_tool/static/script.js @@ -0,0 +1,75 @@ +maxseen = 0; + +function updateShow() { + if (document.getElementById('show_raw').checked) { + document.getElementById('messages').classList = ''; + } else { + document.getElementById('messages').classList = 'hide_details'; + } +} + +function updateMessages() { + fetch('/api.json') + .then( + function(response) { + if (response.status !== 200) { + console.log('FAIL: ' + response.status); + return; + } + response.json().then(function(data) { + console.log(data); + if (data.length > maxseen) { + var messageContainer = document.getElementById('messages'); + for (let i = maxseen; i < data.length; i++) { + var current = data[i]; + var time = document.createElement("p"); + time.innerHTML = current['time']; + time.className = 'time'; + + var parsed = document.createElement("p"); + parsed.innerHTML = current['parsed']; + parsed.className = 'parsed'; + + var sender_id = document.createElement("p"); + sender_id.innerHTML = current['sender_id']; + sender_id.className = 'sender_id'; + + var pretty_raw_sender_id = document.createElement("p"); + pretty_raw_sender_id.innerHTML = current['pretty_raw_sender_id']; + pretty_raw_sender_id.className = 'pretty_raw_sender_id'; + + var raw_message = document.createElement("p"); + raw_message.innerHTML = current['raw_message']; + raw_message.className = 'raw_message'; + + var human_readable_type = document.createElement("p"); + human_readable_type.innerHTML = current['human_readable_type']; + human_readable_type.className = 'human_readable_type'; + + var newNode = document.createElement("div"); + newNode.className = "message"; + newNode.append(time, parsed, sender_id, pretty_raw_sender_id, raw_message, human_readable_type); + messageContainer.prepend(newNode) + } + maxseen = data.length; + } + }); + } + ) +} + + +window.onload = function() { + updateShow() + console.log("loaded"); + updateMessages(); + + + setInterval(function() { + if (document.getElementById('pause').checked) { + return; + } + updateMessages() + + }, 1000); +}; diff --git a/docs/protocol.txt b/docs/protocol.txt index b4add6b..e06bfb9 100644 --- a/docs/protocol.txt +++ b/docs/protocol.txt @@ -70,7 +70,7 @@ Types for controller: Types for modules: - - 0 hanlo + - 0 register [ X B B B B B B B ] -------------- reserved From 5d5ad2decd6335e92e9d6c6017e5c38a26216fb1 Mon Sep 17 00:00:00 2001 From: redfast00 Date: Wed, 19 Aug 2020 02:10:30 +0200 Subject: [PATCH 2/4] Add debugging Arduino code --- debugging_tool/server.py | 18 +++++++++---- .../sketch_can_debugger.ino | 26 +++++++++++++++++++ 2 files changed, 39 insertions(+), 5 deletions(-) create mode 100644 debugging_tool/sketch_can_debugger/sketch_can_debugger.ino diff --git a/debugging_tool/server.py b/debugging_tool/server.py index 595407b..7307277 100644 --- a/debugging_tool/server.py +++ b/debugging_tool/server.py @@ -3,6 +3,7 @@ from flask import Flask, jsonify, send_file from time import sleep from dataclasses import dataclass from datetime import datetime +import serial app = Flask(__name__) shared_message_log = [] @@ -78,11 +79,18 @@ class Message: def serial_reader(messagelog): - while True: - sleep(5) - received = Message(b'\x00' * 8, 0b101000110, datetime.now(), len(messagelog)) - messagelog.append(received.serialize()) - + with serial.Serial('/dev/ttyUSB0', 115200, timeout=10) as ser: + while True: + line = ser.readline() + print(line.decode('ascii')) + if line.startswith(b"message"): + line = line.decode('ascii') + line = line.strip() + parts = line.split(' ') + sender = int(parts[1]) + message = bytes(int(p) for p in parts[2:]) + received = Message(message, sender, datetime.now(), len(messagelog)) + messagelog.append(received.serialize() @app.route('/') def index(): diff --git a/debugging_tool/sketch_can_debugger/sketch_can_debugger.ino b/debugging_tool/sketch_can_debugger/sketch_can_debugger.ino new file mode 100644 index 0000000..fb62d5b --- /dev/null +++ b/debugging_tool/sketch_can_debugger/sketch_can_debugger.ino @@ -0,0 +1,26 @@ +#include + +MCP2515 mcp2515(10); + +void setup() { + Serial.begin(115200); + mcp2515.reset(); + mcp2515.setBitrate(CAN_50KBPS); + mcp2515.setNormalMode(); + Serial.println("begin"); +} + +void loop() { + char message[9]; + message[8] = '\0'; + struct can_frame receive_frame; + if (mcp2515.readMessage(&receive_frame) == MCP2515::ERROR_OK) { + Serial.print("message "); + Serial.print(receive_frame.can_id, DEC); + for (int i = 0; i < 8; i++) { + Serial.print(" "); + Serial.print(receive_frame.data[i], DEC); + } + Serial.print("\n"); + } +} From 519c518cd4f8047ab86cb5e702ebac3cf0ce76d6 Mon Sep 17 00:00:00 2001 From: redfast00 Date: Wed, 19 Aug 2020 02:12:51 +0200 Subject: [PATCH 3/4] Spaces to tabs --- debugging_tool/static/index.html | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/debugging_tool/static/index.html b/debugging_tool/static/index.html index a0b0f23..b886bff 100644 --- a/debugging_tool/static/index.html +++ b/debugging_tool/static/index.html @@ -2,8 +2,8 @@ - - CAN debugger + + CAN debugger