From 96262777a3cbb9a1cc0b1fe30922ea4ee39ff9e3 Mon Sep 17 00:00:00 2001 From: Leone25 <39310565+Leone25@users.noreply.github.com> Date: Wed, 11 Sep 2024 20:12:01 +0200 Subject: [PATCH] added arduino support --- .gitignore | 3 +- arduino/arduino.ino | 78 +++++++++++++++++++++++++++++++ backend/Gpio.js | 4 +- backend/SerialIO.js | 109 +++++++++++++++++++++++++++++++++++++++++++ backend/config.js | 3 ++ backend/index.js | 4 +- backend/package.json | 1 + 7 files changed, 198 insertions(+), 4 deletions(-) create mode 100644 arduino/arduino.ino create mode 100644 backend/SerialIO.js diff --git a/.gitignore b/.gitignore index 8f56395..18d3a15 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ node_modules -backend/leaderboard.json \ No newline at end of file +backend/leaderboard.json +package-lock.json \ No newline at end of file diff --git a/arduino/arduino.ino b/arduino/arduino.ino new file mode 100644 index 0000000..30b6903 --- /dev/null +++ b/arduino/arduino.ino @@ -0,0 +1,78 @@ +#include + +#define BUTTON0 2 +#define BUTTON1 3 +#define RESETBUTTON 4 +#define LED 13 + +ezButton button0(BUTTON0); +ezButton button1(BUTTON1); +ezButton resetButton(RESETBUTTON); + +void setup() { + Serial.begin(9600); + pinMode(LED, OUTPUT); + button0.setDebounceTime(50); + button1.setDebounceTime(50); + resetButton.setDebounceTime(50); +} + +#define LOW 0 +#define HIGH 1 +#define BLINKING 2 + +int ledState = LOW; +int blinkTime = 1000; +unsigned long lastTime = 0; + +void loop() { + button0.loop(); + button1.loop(); + resetButton.loop(); + + if (button0.isPressed()) { + Serial.println("button-0-1"); + } + + if (button0.isReleased()) { + Serial.println("button-0-0"); + } + + if (button1.isPressed()) { + Serial.println("button-1-1"); + } + + if (button1.isReleased()) { + Serial.println("button-1-0"); + } + + if (resetButton.isPressed()) { + Serial.println("reset-1"); + } + + if (resetButton.isReleased()) { + Serial.println("reset-0"); + } + + if (Serial.available()) { + String command = Serial.readStringUntil('\n'); + if (command == "led-on") { + ledState = HIGH; + digitalWrite(LED, HIGH); + } else if (command == "led-off") { + ledState = LOW; + digitalWrite(LED, LOW); + } else if (command.startsWith("led-blink")) { + ledState = BLINKING; + blinkTime = command.substring(10).toInt(); + lastTime = millis(); + } + } + + if (ledState == BLINKING) { + if (millis() - lastTime > blinkTime) { + lastTime += blinkTime; + digitalWrite(LED, !digitalRead(LED)); + } + } +} diff --git a/backend/Gpio.js b/backend/Gpio.js index b367c9f..3aad1f5 100644 --- a/backend/Gpio.js +++ b/backend/Gpio.js @@ -49,11 +49,11 @@ export default class Gpio { } } if (states.every(s => s === 1)) { - this.timer.stop(); + timer.stop(); } } else { if (states.every(s => s === 0)) { - this.timer.start(); + timer.start(); this.lastButton1Press -= 1000; this.lastButton2Press -= 1000; } diff --git a/backend/SerialIO.js b/backend/SerialIO.js new file mode 100644 index 0000000..8c6a7a9 --- /dev/null +++ b/backend/SerialIO.js @@ -0,0 +1,109 @@ +import { SerialPort } from 'serialport'; + +import config from './config.js'; + +import { timer } from './index.js'; + +export default class SerialIO { + constructor() { + this.lastLedState = 'off'; + this.lastButton1Press = -1000; + this.lastButton2Press = -1000; + this.states = [0, 0]; + this.openTimeout = null; + + this.buffer = ''; + + this.serial = new SerialPort({ + path: config.serialPort, + baudRate: 9600, + autoOpen: false, + }); + + this.serial.on('data', (data) => { + this.buffer += data.toString(); + let lines = this.buffer.split('\n'); + this.buffer = lines.pop(); + for (let line of lines) { + this.handleCommand(line); + } + }); + + timer.on('start', () => { + this.setLed('on'); + }); + timer.on('stop', () => { + this.setLed('off'); + }); + timer.on('reset', () => { + this.setLed('off'); + }); + timer.on('newHighscore', () => { + this.setLed('blink', 200); + }); + + if (config.serialPort) this.begin(); + } + + begin() { + this.serial.open((err) => { + if (err) { + console.error(err); + this.openTimeout = setTimeout(() => { + this.begin(); + }, 1000); + } + }); + } + + handleCommand(command) { + if (command.startsWith('button')) { + let [_, pin, state] = command.split('-'); + this.handleButtonUpdate(parseInt(pin), parseInt(state)); + } else if (command.startsWith('resetButton')) { + let [_, state] = command.split('-'); + this.handleResetButton(parseInt(state)); + } + } + + handleButtonUpdate(pin, state) { + this.states[pin] = state; + this[`lastButton${pin}Press`] = performance.now(); + if (this.timer.isRunning) { + for (let i = 0; i < 2; i++) { + if (this.states[i] === 0 && performance.now() - this[`lastButton${i + 1}Press`] < 500) { // debounce + this.states[i] = 1; + } + } + if (states.every(s => s === 1)) { + timer.stop(); + } + } else { + if (states.every(s => s === 0)) { + timer.start(); + this.lastButton1Press -= 1000; + this.lastButton2Press -= 1000; + } + } + } + + handleResetButton(state) { + if (state === 1) { + timer.reset(); + } + } + + /** + * + * @param {('on'|'off'|'blink')} state + * @param {number} interval (optional, only used for 'blink' state) + */ + setLed(state, interval = 500) { + if (state === 'blink') { + this.lastLedState = 'blink' + interval; + } else { + this.lastLedState = state; + } + this.serial.write('\nled-' + this.lastLedState + '\n'); + } +} \ No newline at end of file diff --git a/backend/config.js b/backend/config.js index 56b2a62..9c73663 100644 --- a/backend/config.js +++ b/backend/config.js @@ -3,6 +3,9 @@ export default { "port": 3000, "leaderboardFile": "./leaderboard.json", + "serialPort": "/dev/ttyACM0", // set to null to disable serial + + // pins are only for the Raspberry Pi "button1Pin": 17, "button2Pin": 18, "resetButtonPin": 22, diff --git a/backend/index.js b/backend/index.js index 70a8a61..594cf24 100644 --- a/backend/index.js +++ b/backend/index.js @@ -19,6 +19,7 @@ const argv = yargs(process.argv.slice(2)) .argv; import Timer from "./Timer.js"; +import SerialIO from "./SerialIO.js"; export const app = express(); const httpServer = createServer(app); @@ -29,9 +30,10 @@ export const io = new Server(httpServer, { }); export const timer = new Timer(); +export const serialIO = new SerialIO(); if (["arm", "arm64"].includes(arch)) { - const gpio = new (await import("array-gpio")).default(); + const gpio = new (await import("./Gpio.js")).default(); } if (argv.prod) { diff --git a/backend/package.json b/backend/package.json index fc25f80..9077d8d 100644 --- a/backend/package.json +++ b/backend/package.json @@ -10,6 +10,7 @@ }, "dependencies": { "express": "^4.19.2", + "serialport": "^12.0.0", "socket.io": "^4.7.5", "uuid": "^10.0.0", "yargs": "^17.7.2"