Merge branch 'master' into RobbeWeMissYou
This commit is contained in:
commit
086ae0e3fd
16 changed files with 845 additions and 6 deletions
|
@ -63,3 +63,7 @@ Some things we had to consider:
|
|||
- payload is 8 bytes per packet
|
||||
- packets can be delayed or not received on every node, so detection of this and retransmission might be needed: if the bomb interactor solves a module and the packet that communicates this with the bomb does not get delivered to the controller, the bomb will still go off, even if all modules have been solved
|
||||
- we can't send an infinite amount of packets; the higher our bitrate is, the shorter our wires need to be
|
||||
|
||||
## Development setup
|
||||
|
||||
We use [this](https://github.com/autowp/arduino-mcp2515/) library for CAN communications. See [this](https://github.com/autowp/arduino-mcp2515/#software-usage) header for 3 simple steps on how to use it in the arduino IDE
|
||||
|
|
3
debugging_tool/.editorconfig
Normal file
3
debugging_tool/.editorconfig
Normal file
|
@ -0,0 +1,3 @@
|
|||
[*.py]
|
||||
indent_style = space
|
||||
indent_size = 4
|
138
debugging_tool/.gitignore
vendored
Normal file
138
debugging_tool/.gitignore
vendored
Normal file
|
@ -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/
|
7
debugging_tool/requirements.txt
Normal file
7
debugging_tool/requirements.txt
Normal file
|
@ -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
|
108
debugging_tool/server.py
Normal file
108
debugging_tool/server.py
Normal file
|
@ -0,0 +1,108 @@
|
|||
from threading import Thread
|
||||
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 = []
|
||||
|
||||
|
||||
@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):
|
||||
with serial.Serial('/dev/ttyACM0', 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())
|
||||
print(len(messagelog))
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
return send_file('static/index.html')
|
||||
|
||||
@app.route('/api.json')
|
||||
def api():
|
||||
return jsonify(shared_message_log)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
thread = Thread(target=serial_reader, args=(shared_message_log, ))
|
||||
thread.start()
|
||||
app.run(debug=True, host='0.0.0.0')
|
29
debugging_tool/sketch_can_debugger/sketch_can_debugger.ino
Normal file
29
debugging_tool/sketch_can_debugger/sketch_can_debugger.ino
Normal file
|
@ -0,0 +1,29 @@
|
|||
#include <mcp2515.h>
|
||||
|
||||
MCP2515 mcp2515(10);
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
mcp2515.reset();
|
||||
mcp2515.setBitrate(CAN_50KBPS);
|
||||
mcp2515.setNormalMode();
|
||||
for (int i = 0; i < 5; i++) {
|
||||
Serial.println("begin");
|
||||
delay(1000);
|
||||
}
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
74
debugging_tool/static/index.html
Normal file
74
debugging_tool/static/index.html
Normal file
|
@ -0,0 +1,74 @@
|
|||
<!doctype html>
|
||||
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>CAN debugger</title>
|
||||
<style>
|
||||
|
||||
@keyframes fadein {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
.message {
|
||||
display: flex;
|
||||
animation: fadein 1s;
|
||||
}
|
||||
.message > * {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
padding: 0 1.5ch 0 1.5ch;
|
||||
}
|
||||
.hide_details .message .pretty_raw_sender_id {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.hide_details .message .raw_message {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.human_readable_type {
|
||||
order: -5;
|
||||
}
|
||||
.time {
|
||||
order: 5;
|
||||
}
|
||||
.sender_id {
|
||||
order: 1;
|
||||
}
|
||||
|
||||
.parsed {
|
||||
order: 2;
|
||||
flex: 1;
|
||||
background: lightgreen;
|
||||
}
|
||||
|
||||
.pretty_raw_sender_id, .raw_message {
|
||||
font-family: monospace, monospace;
|
||||
}
|
||||
|
||||
.pretty_raw_sender_id {
|
||||
order: 9998;
|
||||
}
|
||||
|
||||
.raw_message {
|
||||
order: 9999;
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div>
|
||||
<input type="checkbox" id="show_raw" name="show_raw" checked onchange="updateShow()">
|
||||
<label for="show_raw">Show raw address and payload</label>
|
||||
<input type="checkbox" id="pause" name="pause">
|
||||
<label for="pause">Pause</label>
|
||||
</div>
|
||||
<div id="messages">
|
||||
|
||||
</div>
|
||||
<script src="static/script.js"></script>
|
||||
</body>
|
||||
</html>
|
75
debugging_tool/static/script.js
Normal file
75
debugging_tool/static/script.js
Normal file
|
@ -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);
|
||||
};
|
11
docs/hardware/README.md
Normal file
11
docs/hardware/README.md
Normal file
|
@ -0,0 +1,11 @@
|
|||
# Hardware documentation
|
||||
|
||||
|
||||
![Breadboard with Arduino Nano and CAN module](./basic_module.png)
|
||||
|
||||
## Parts of basic module
|
||||
|
||||
- 2x 330 ohm resistor
|
||||
- RGB common cathode LED
|
||||
- MCP2515 CAN module
|
||||
- Arduino nano (clone)
|
BIN
docs/hardware/basic_module.fzz
Normal file
BIN
docs/hardware/basic_module.fzz
Normal file
Binary file not shown.
BIN
docs/hardware/basic_module.png
Normal file
BIN
docs/hardware/basic_module.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 301 KiB |
|
@ -18,7 +18,7 @@ b bb bbbbbbbb
|
|||
| | ↓
|
||||
| ↓ module-ID: 2⁸=256
|
||||
↓ type: 2²=4
|
||||
priority bit (so that each type can send priority messages if need be)
|
||||
priority bit (so that each type can send priority messages if need be, 0 = high priority, 1 = low priority)
|
||||
|
||||
type:
|
||||
- 0 module-ID 0: controller, >0: info
|
||||
|
@ -30,7 +30,7 @@ Payload:
|
|||
[ B B B B B B B B ]
|
||||
-
|
||||
↓
|
||||
type (per module type)
|
||||
type of the message (per module type)
|
||||
|
||||
- - - - - - - - - - - - - - - - - - -
|
||||
|
||||
|
@ -79,15 +79,15 @@ Types for info:
|
|||
|
||||
Types for modules:
|
||||
|
||||
- 0 hanlo
|
||||
- 0 register
|
||||
[ X B B B B B B B ]
|
||||
--------------
|
||||
reserved
|
||||
|
||||
- 1 strike
|
||||
[ X B B B B B B B ]
|
||||
--------------
|
||||
reserved
|
||||
↓ -----↓------
|
||||
#strikes reserved
|
||||
|
||||
- 2 solved (not for needy modules)
|
||||
[ X B B B B B B B ]
|
||||
|
|
|
@ -1,3 +1,23 @@
|
|||
# Wireguide
|
||||
|
||||
This is a parametric model of a tool to put the individual wires of an ethernet-cable in the correct position to easily attach an IDC-connector (vampire tap)
|
||||
The original plan of connecting the CAN-modules was to use an ethernet cable:
|
||||
we would strip the outer layer of the cable where we want to connect a module,
|
||||
then put on an IDC connector. We would then use one twisted pair for CAN data, and
|
||||
the other pairs for 12v. We wanted to use an ethernetcable because we have a lot of
|
||||
spare ethernetcable that's too good to throw away, but too long to be practical.
|
||||
The CAN specifications also demand a twisted cable.
|
||||
|
||||
So in theory, this was a great solution. Unfortunately, theory and practice are
|
||||
the same in theory, but not in pracice: connecting the IDC connectors proved to be
|
||||
very time-intensive (first stripping the outer cable, then partially untwisting the strands
|
||||
and putting them in the correct order) and error-prone (the slightest issue in alignment
|
||||
will cause two strands to become electrically connected).
|
||||
|
||||
To partially fix this, we designed a small tool that would be able to more easily keep
|
||||
the strands lined up before pressing the IDC-connector together. This also proved
|
||||
to be rather hard, so we gave up and just ordered some automotive twisted wire that is
|
||||
made for CAN traffic.
|
||||
|
||||
The term you'll need when trying to buy this twisted wire is `canbus wire`.
|
||||
|
||||
`wireguide.scad` is a parametric model of a tool to put the individual wires of an ethernet-cable in the correct position to easily attach an IDC-connector (vampire tap)
|
||||
|
|
98
src/debug_module/debug_module.ino
Normal file
98
src/debug_module/debug_module.ino
Normal file
|
@ -0,0 +1,98 @@
|
|||
#include <mcp2515.h>
|
||||
#include "pitches.h"
|
||||
|
||||
MCP2515 mcp2515(10);
|
||||
|
||||
uint16_t id = 0b01100000011;
|
||||
// type: 3 (other)
|
||||
// module-id: 3
|
||||
|
||||
int soundpin = 9;
|
||||
|
||||
|
||||
void setup() {
|
||||
// Serial.begin(9600);
|
||||
Serial.begin(115200);
|
||||
mcp2515.reset();
|
||||
mcp2515.setBitrate(CAN_50KBPS);
|
||||
mcp2515.setNormalMode();
|
||||
Serial.println("begin");
|
||||
|
||||
// play_sound();
|
||||
}
|
||||
|
||||
void loop() {
|
||||
char message[9];
|
||||
message[8] = '\0';
|
||||
|
||||
// Send text typed in the console over the can network
|
||||
struct can_frame send_frame;
|
||||
int read = Serial.readBytesUntil('\n', send_frame.data, 8);
|
||||
if (read > 0) {
|
||||
send_frame.can_id = id;
|
||||
send_frame.can_dlc = read;
|
||||
mcp2515.sendMessage(&send_frame);
|
||||
Serial.println("sent");
|
||||
}
|
||||
|
||||
// Read from the can bus
|
||||
struct can_frame receive_frame;
|
||||
if (mcp2515.readMessage(&receive_frame) == MCP2515::ERROR_OK) {
|
||||
Serial.print("[prio-bit: ");
|
||||
Serial.print(receive_frame.can_id >> 10 & 0b00000001);
|
||||
Serial.print("\tmodule-type: ");
|
||||
Serial.print(receive_frame.can_id >> 8 & 0b00000011);
|
||||
Serial.print("\tmodule-id: ");
|
||||
|
||||
Serial.print(receive_frame.can_id & 0b11111111);
|
||||
Serial.print("]\tLength: ");
|
||||
|
||||
Serial.print(receive_frame.can_dlc, HEX); // print DLC
|
||||
Serial.print("\t Body: ");
|
||||
|
||||
// First byte has the type of the message
|
||||
|
||||
// print per byte in hex
|
||||
for (int i = 0; i < receive_frame.can_dlc; i++) { // print the data
|
||||
Serial.print(receive_frame.data[i], HEX);
|
||||
Serial.print(" ");
|
||||
}
|
||||
Serial.println("");
|
||||
|
||||
// print all
|
||||
//memcpy(message, receive_frame.data, 8);
|
||||
//Serial.println(message);
|
||||
}
|
||||
}
|
||||
|
||||
// notes in the melody:
|
||||
int melody[] = {
|
||||
NOTE_C4, NOTE_G3, NOTE_G3, NOTE_A3, NOTE_G3, 0, NOTE_B3, NOTE_C4
|
||||
};
|
||||
|
||||
// note durations: 4 = quarter note, 8 = eighth note, etc.:
|
||||
int noteDurations[] = {
|
||||
4, 8, 8, 4, 4, 4, 4, 4
|
||||
};
|
||||
|
||||
void play_melody() {
|
||||
// iterate over the notes of the melody:
|
||||
for (int thisNote = 0; thisNote < 8; thisNote++) {
|
||||
|
||||
// to calculate the note duration, take one second divided by the note type.
|
||||
//e.g. quarter note = 1000 / 4, eighth note = 1000/8, etc.
|
||||
int noteDuration = 1000 / noteDurations[thisNote];
|
||||
tone(soundpin, melody[thisNote], noteDuration);
|
||||
|
||||
// to distinguish the notes, set a minimum time between them.
|
||||
// the note's duration + 30% seems to work well:
|
||||
int pauseBetweenNotes = noteDuration * 1.30;
|
||||
delay(pauseBetweenNotes);
|
||||
// stop the tone playing:
|
||||
noTone(soundpin);
|
||||
}
|
||||
}
|
||||
|
||||
void play_sound() {
|
||||
|
||||
}
|
93
src/debug_module/pitches.h
Normal file
93
src/debug_module/pitches.h
Normal file
|
@ -0,0 +1,93 @@
|
|||
/*************************************************
|
||||
* Public Constants
|
||||
*************************************************/
|
||||
|
||||
#define NOTE_B0 31
|
||||
#define NOTE_C1 33
|
||||
#define NOTE_CS1 35
|
||||
#define NOTE_D1 37
|
||||
#define NOTE_DS1 39
|
||||
#define NOTE_E1 41
|
||||
#define NOTE_F1 44
|
||||
#define NOTE_FS1 46
|
||||
#define NOTE_G1 49
|
||||
#define NOTE_GS1 52
|
||||
#define NOTE_A1 55
|
||||
#define NOTE_AS1 58
|
||||
#define NOTE_B1 62
|
||||
#define NOTE_C2 65
|
||||
#define NOTE_CS2 69
|
||||
#define NOTE_D2 73
|
||||
#define NOTE_DS2 78
|
||||
#define NOTE_E2 82
|
||||
#define NOTE_F2 87
|
||||
#define NOTE_FS2 93
|
||||
#define NOTE_G2 98
|
||||
#define NOTE_GS2 104
|
||||
#define NOTE_A2 110
|
||||
#define NOTE_AS2 117
|
||||
#define NOTE_B2 123
|
||||
#define NOTE_C3 131
|
||||
#define NOTE_CS3 139
|
||||
#define NOTE_D3 147
|
||||
#define NOTE_DS3 156
|
||||
#define NOTE_E3 165
|
||||
#define NOTE_F3 175
|
||||
#define NOTE_FS3 185
|
||||
#define NOTE_G3 196
|
||||
#define NOTE_GS3 208
|
||||
#define NOTE_A3 220
|
||||
#define NOTE_AS3 233
|
||||
#define NOTE_B3 247
|
||||
#define NOTE_C4 262
|
||||
#define NOTE_CS4 277
|
||||
#define NOTE_D4 294
|
||||
#define NOTE_DS4 311
|
||||
#define NOTE_E4 330
|
||||
#define NOTE_F4 349
|
||||
#define NOTE_FS4 370
|
||||
#define NOTE_G4 392
|
||||
#define NOTE_GS4 415
|
||||
#define NOTE_A4 440
|
||||
#define NOTE_AS4 466
|
||||
#define NOTE_B4 494
|
||||
#define NOTE_C5 523
|
||||
#define NOTE_CS5 554
|
||||
#define NOTE_D5 587
|
||||
#define NOTE_DS5 622
|
||||
#define NOTE_E5 659
|
||||
#define NOTE_F5 698
|
||||
#define NOTE_FS5 740
|
||||
#define NOTE_G5 784
|
||||
#define NOTE_GS5 831
|
||||
#define NOTE_A5 880
|
||||
#define NOTE_AS5 932
|
||||
#define NOTE_B5 988
|
||||
#define NOTE_C6 1047
|
||||
#define NOTE_CS6 1109
|
||||
#define NOTE_D6 1175
|
||||
#define NOTE_DS6 1245
|
||||
#define NOTE_E6 1319
|
||||
#define NOTE_F6 1397
|
||||
#define NOTE_FS6 1480
|
||||
#define NOTE_G6 1568
|
||||
#define NOTE_GS6 1661
|
||||
#define NOTE_A6 1760
|
||||
#define NOTE_AS6 1865
|
||||
#define NOTE_B6 1976
|
||||
#define NOTE_C7 2093
|
||||
#define NOTE_CS7 2217
|
||||
#define NOTE_D7 2349
|
||||
#define NOTE_DS7 2489
|
||||
#define NOTE_E7 2637
|
||||
#define NOTE_F7 2794
|
||||
#define NOTE_FS7 2960
|
||||
#define NOTE_G7 3136
|
||||
#define NOTE_GS7 3322
|
||||
#define NOTE_A7 3520
|
||||
#define NOTE_AS7 3729
|
||||
#define NOTE_B7 3951
|
||||
#define NOTE_C8 4186
|
||||
#define NOTE_CS8 4435
|
||||
#define NOTE_D8 4699
|
||||
#define NOTE_DS8 4978
|
179
src/module/module.ino
Normal file
179
src/module/module.ino
Normal file
|
@ -0,0 +1,179 @@
|
|||
#include <CAN.h>
|
||||
#include <ezButton.h>
|
||||
|
||||
#define HANDLE_MESSAGE(type) case type: handle_##type(); break;
|
||||
|
||||
#define PUZZLE_M_HANLO 0
|
||||
#define PUZZLE_M_STRIKE 1
|
||||
#define PUZZLE_M_SOLVED 2
|
||||
|
||||
|
||||
enum gamestate {
|
||||
PENDING, RUNNING, SOLVED, TIMEOUT, STRIKEOUT
|
||||
};
|
||||
|
||||
enum controller_m_type {
|
||||
ACK, HELLO, GAME_START, GAME_STATE, GAME_SOLVED, GAME_TIMEOUT, GAME_STRIKEOUT
|
||||
};
|
||||
|
||||
typedef struct state {
|
||||
gamestate game_state;
|
||||
uint32_t time;
|
||||
uint8_t cur_strikes;
|
||||
uint8_t max_strikes;
|
||||
} state_s;
|
||||
|
||||
state_s state = { .game_state = PENDING, .time = (uint32_t) -1 };
|
||||
|
||||
uint16_t id = 0b00100000010;
|
||||
|
||||
ezButton green_button(5); // create ezButton object that attach to pin 5;
|
||||
ezButton red_button(7); // create ezButton object that attach to pin 7;
|
||||
|
||||
/* Send Hanlo to controller */
|
||||
void hanlo() {
|
||||
Serial.println("-> HANLO");
|
||||
CAN.beginPacket(id);
|
||||
CAN.write(PUZZLE_M_HANLO); /* Message Type */
|
||||
CAN.endPacket();
|
||||
}
|
||||
|
||||
/* Send strike to controller */
|
||||
void strike() {
|
||||
Serial.println("-> STRIKE");
|
||||
CAN.beginPacket(id);
|
||||
CAN.write(PUZZLE_M_STRIKE); /* Message Type */
|
||||
CAN.endPacket();
|
||||
}
|
||||
|
||||
/* Send solve to controller */
|
||||
void solved() {
|
||||
Serial.println("-> SOLVED");
|
||||
CAN.beginPacket(id);
|
||||
CAN.write(PUZZLE_M_SOLVED); /* Message Type */
|
||||
CAN.endPacket();
|
||||
}
|
||||
|
||||
/* Handle Ack message from controller */
|
||||
void handle_ACK() {
|
||||
Serial.println("<- ACK");
|
||||
}
|
||||
|
||||
/* Handle Hello message from controller */
|
||||
void handle_HELLO() {
|
||||
Serial.println("<- HELLO");
|
||||
hanlo();
|
||||
}
|
||||
|
||||
/* Handle Game start message from controller */
|
||||
void handle_GAME_START() {
|
||||
Serial.println("<- GAME_START");
|
||||
state.game_state = RUNNING;
|
||||
handle_GAME_STATE();
|
||||
}
|
||||
|
||||
/* Handle State message from controller */
|
||||
void handle_GAME_STATE() {
|
||||
Serial.println("<- GAME_STATE");
|
||||
state.time =
|
||||
((uint32_t) CAN.read() << 24) |
|
||||
((uint32_t) CAN.read() << 16) |
|
||||
((uint32_t) CAN.read() << 8) |
|
||||
CAN.read();
|
||||
state.cur_strikes = CAN.read();
|
||||
state.max_strikes = CAN.read();
|
||||
|
||||
Serial.print("TIME: ");
|
||||
Serial.println(state.time);
|
||||
Serial.print("CUR_STRIKES: ");
|
||||
Serial.println(state.cur_strikes);
|
||||
Serial.print("MAX_STRIKES: ");
|
||||
Serial.println(state.max_strikes);
|
||||
}
|
||||
|
||||
/* Handle Solved message from controller */
|
||||
void handle_GAME_SOLVED() {
|
||||
Serial.println("<- GAME_SOLVED");
|
||||
state.game_state = SOLVED;
|
||||
}
|
||||
|
||||
/* Handle Timeout message from controller */
|
||||
void handle_GAME_TIMEOUT() {
|
||||
Serial.println("<- GAME_TIMEOUT");
|
||||
state.game_state = TIMEOUT;
|
||||
}
|
||||
|
||||
/* Handle Strikeout message from controller */
|
||||
void handle_GAME_STRIKEOUT() {
|
||||
Serial.println("<- GAME_STRIKEOUT");
|
||||
state.game_state = STRIKEOUT;
|
||||
}
|
||||
|
||||
void doCAN() {
|
||||
if (CAN.parsePacket()) {
|
||||
Serial.println("--- GOT_CAN ---");
|
||||
/* only react to messages from the controller */
|
||||
if (CAN.filter(0b00000000000, 0b01100000000)) {
|
||||
Serial.println("--- HANDLING ---");
|
||||
if (CAN.peek() != -1) {
|
||||
uint8_t message_type = CAN.read();
|
||||
Serial.print("MESSAGE_TYPE: ");
|
||||
Serial.println(message_type, HEX);
|
||||
switch(message_type) {
|
||||
HANDLE_MESSAGE(ACK)
|
||||
HANDLE_MESSAGE(HELLO)
|
||||
HANDLE_MESSAGE(GAME_START)
|
||||
HANDLE_MESSAGE(GAME_STATE)
|
||||
HANDLE_MESSAGE(GAME_SOLVED)
|
||||
HANDLE_MESSAGE(GAME_TIMEOUT)
|
||||
HANDLE_MESSAGE(GAME_STRIKEOUT)
|
||||
default:
|
||||
Serial.println("--- NO_HANDLER_FOUND ---");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Serial.println("--- IGNORING ---");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void setup() {
|
||||
/* Init Serial */
|
||||
Serial.begin(9600);
|
||||
Serial.println("--- INITIATING ---");
|
||||
Serial.print("ID: ");
|
||||
Serial.println(id, HEX);
|
||||
|
||||
/* Init Can pins */
|
||||
// CAN.setPins(cs,irq);
|
||||
|
||||
/* Set SPI Frequency */
|
||||
// CAN.setSPIFrequency(frequency);
|
||||
|
||||
if (!CAN.begin(50E3)) {
|
||||
Serial.println("--- FAILED_TO_INIT_CAN ---");
|
||||
while(1) {};
|
||||
}
|
||||
|
||||
red_button.setDebounceTime(50);
|
||||
green_button.setDebounceTime(50);
|
||||
|
||||
Serial.println("--- INITIALIZED ---");
|
||||
}
|
||||
|
||||
void loop() {
|
||||
red_button.loop();
|
||||
green_button.loop();
|
||||
|
||||
/* Handle CAN messages */
|
||||
doCAN();
|
||||
|
||||
if (green_button.isPressed()) {
|
||||
solved();
|
||||
}
|
||||
|
||||
if (red_button.isPressed()) {
|
||||
strike();
|
||||
}
|
||||
// TODO
|
||||
}
|
Loading…
Reference in a new issue