Piezo buzzer, portlist and update documentation

This commit is contained in:
Nomi Nonsense (Nonszy) 2024-03-03 18:47:55 +07:00
parent 71c4ec5072
commit f90387bab9
11 changed files with 259 additions and 19 deletions

View File

@ -1,7 +1,28 @@
# Lunar Vein: Arduino # Lunar Vein: Arduino
An API server and client to communicate with Arduino boards, that's it. Idk about electronics and networking actually. But that piece of knowledge motivated me to interact it with other software. Enjoy (⁠づ⁠ ̄⁠ ⁠³⁠ ̄⁠)⁠づ A REST API based serial communication, assisted Firmata protocol and johnny-five API, enabling software communication with the Arduino board using the Server API., that's it. Idk about electronics and networking actually. But that piece of knowledge motivated me to interact it with other software. Enjoy (⁠づ⁠ ̄⁠ ⁠³⁠ ̄⁠)⁠づ
### Setup ## Setup
- Install all packages with `npm i` or `yarn` ### Board
Arduino assembly guide also available in [johnny-five](https://github.com/rwaldron/johnny-five?tab=readme-ov-file#setup-and-assemble-arduino) documentation
1. Download [Arduino IDE](https://www.arduino.cc/en/software)
2. Plug in your Board via USB
3. Open Arduino IDE then select your Board
4. Go to `File > Examples > Firmata` Select `StandarFirmataPlus`
5. Upload the sketch
### Server
1. Clone project and Install
```bash
> git clone https://github.com/norman-andrians/lunar-vein-arduino.git && cd lunar-vein-arduino
> npm i
```
2. Add `.env` file in the project root directory with the `SERIAL_PORT` variable
```
SERIAL_PORT=/dev/ttyUSB0
```
3. Run the project
```bash
> npm start
```

59
package-lock.json generated
View File

@ -10,7 +10,9 @@
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@serialport/bindings": "^9.2.9", "@serialport/bindings": "^9.2.9",
"@serialport/list": "^12.0.0",
"chalk": "^4.1.2", "chalk": "^4.1.2",
"dotenv": "^16.4.5",
"express": "^4.18.3", "express": "^4.18.3",
"firmata": "^2.3.0", "firmata": "^2.3.0",
"johnny-five": "^2.1.0", "johnny-five": "^2.1.0",
@ -23,6 +25,7 @@
"@types/firmata": "^0.19.8", "@types/firmata": "^0.19.8",
"@types/johnny-five": "^2.1.9", "@types/johnny-five": "^2.1.9",
"@types/nodemon": "^1.19.6", "@types/nodemon": "^1.19.6",
"@types/serialport": "^8.0.5",
"@types/socket.io": "^3.0.2", "@types/socket.io": "^3.0.2",
"ts-node": "^10.9.2", "ts-node": "^10.9.2",
"typescript": "^5.3.3" "typescript": "^5.3.3"
@ -200,6 +203,24 @@
"url": "https://opencollective.com/serialport/donate" "url": "https://opencollective.com/serialport/donate"
} }
}, },
"node_modules/@serialport/list": {
"version": "12.0.0",
"resolved": "https://registry.npmjs.org/@serialport/list/-/list-12.0.0.tgz",
"integrity": "sha512-TyGSCbnBbdpjL93uYb3gyFINkux5q2Ksqz3yL5yqwS6RpORNDudD/x7BEvG1o7SNAwhbC6JZAaXq4WnE3fr9sg==",
"dependencies": {
"@serialport/bindings-cpp": "12.0.1",
"commander": "11.0.0"
},
"bin": {
"serialport-list": "dist/index.js"
},
"engines": {
"node": ">=16.0.0"
},
"funding": {
"url": "https://opencollective.com/serialport/donate"
}
},
"node_modules/@serialport/parser-byte-length": { "node_modules/@serialport/parser-byte-length": {
"version": "12.0.0", "version": "12.0.0",
"resolved": "https://registry.npmjs.org/@serialport/parser-byte-length/-/parser-byte-length-12.0.0.tgz", "resolved": "https://registry.npmjs.org/@serialport/parser-byte-length/-/parser-byte-length-12.0.0.tgz",
@ -420,6 +441,16 @@
"@types/serialport": "^4" "@types/serialport": "^4"
} }
}, },
"node_modules/@types/firmata/node_modules/@types/serialport": {
"version": "4.0.16",
"resolved": "https://registry.npmjs.org/@types/serialport/-/serialport-4.0.16.tgz",
"integrity": "sha512-JbeHlODoEPbKmo0Q1xv/EbawShWYyNZJEW5V8cuPRvRofwwC5VKzGbZZkk8MfIo5lkpmcJWhKzTGgQ1jVXUgjQ==",
"dev": true,
"dependencies": {
"@types/node": "*",
"@types/streamjs": "*"
}
},
"node_modules/@types/http-errors": { "node_modules/@types/http-errors": {
"version": "2.0.4", "version": "2.0.4",
"resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz",
@ -481,13 +512,12 @@
} }
}, },
"node_modules/@types/serialport": { "node_modules/@types/serialport": {
"version": "4.0.16", "version": "8.0.5",
"resolved": "https://registry.npmjs.org/@types/serialport/-/serialport-4.0.16.tgz", "resolved": "https://registry.npmjs.org/@types/serialport/-/serialport-8.0.5.tgz",
"integrity": "sha512-JbeHlODoEPbKmo0Q1xv/EbawShWYyNZJEW5V8cuPRvRofwwC5VKzGbZZkk8MfIo5lkpmcJWhKzTGgQ1jVXUgjQ==", "integrity": "sha512-Lw082WIy46fYLnOzyf+8QN/vZaR3d9ol9WNyEGHsKJJ1pmZogFbloHGbnXyNcxfV9aTbgviWU8jktrIjRheYFQ==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@types/node": "*", "@types/node": "*"
"@types/streamjs": "*"
} }
}, },
"node_modules/@types/serve-static": { "node_modules/@types/serve-static": {
@ -896,6 +926,14 @@
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
}, },
"node_modules/commander": {
"version": "11.0.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-11.0.0.tgz",
"integrity": "sha512-9HMlXtt/BNoYr8ooyjjNRdIilOTkVJXB+GhxMTtOKwk0R4j4lS4NpjuqmRxroBfnfTSHQIHQB7wryHhXarNjmQ==",
"engines": {
"node": ">=16"
}
},
"node_modules/concat-map": { "node_modules/concat-map": {
"version": "0.0.1", "version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@ -1053,6 +1091,17 @@
"node": ">=0.3.1" "node": ">=0.3.1"
} }
}, },
"node_modules/dotenv": {
"version": "16.4.5",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz",
"integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://dotenvx.com"
}
},
"node_modules/ee-first": { "node_modules/ee-first": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",

View File

@ -12,7 +12,9 @@
}, },
"dependencies": { "dependencies": {
"@serialport/bindings": "^9.2.9", "@serialport/bindings": "^9.2.9",
"@serialport/list": "^12.0.0",
"chalk": "^4.1.2", "chalk": "^4.1.2",
"dotenv": "^16.4.5",
"express": "^4.18.3", "express": "^4.18.3",
"firmata": "^2.3.0", "firmata": "^2.3.0",
"johnny-five": "^2.1.0", "johnny-five": "^2.1.0",
@ -25,6 +27,7 @@
"@types/firmata": "^0.19.8", "@types/firmata": "^0.19.8",
"@types/johnny-five": "^2.1.9", "@types/johnny-five": "^2.1.9",
"@types/nodemon": "^1.19.6", "@types/nodemon": "^1.19.6",
"@types/serialport": "^8.0.5",
"@types/socket.io": "^3.0.2", "@types/socket.io": "^3.0.2",
"ts-node": "^10.9.2", "ts-node": "^10.9.2",
"typescript": "^5.3.3" "typescript": "^5.3.3"

28
src/controller/piezo.ts Normal file
View File

@ -0,0 +1,28 @@
import { Request, Response } from "express";
import { Piezo } from "johnny-five";
import { Pitch } from "../melodies";
export function piezoTone (req: Request, res: Response) {
const pin: number = Number.parseInt(req.params.p);
const note: string = req.params.n;
if (Number.isNaN(pin)) {
return res.status(400).json({
status: 400,
message: 'Invalid pin param, it should be integer'
});
}
const piezo = new Piezo(pin);
piezo.play({
song: note.toUpperCase(),
beats: 1/2,
tempo: 100
})
return res.status(200).json({
status: 200,
message: `Piezo ${pin} tone ${note}`
});
}

View File

@ -1,5 +1,5 @@
import http from 'node:http'; import http from 'node:http';
import express, { Request, Response } from 'express'; import express from 'express';
import { Server } from 'socket.io'; import { Server } from 'socket.io';
import chalk from 'chalk'; import chalk from 'chalk';
@ -8,6 +8,7 @@ import { board, suBoard, comport } from './setup';
import { isBoardConnected } from './middleware/connection'; import { isBoardConnected } from './middleware/connection';
import view from './routes/view'; import view from './routes/view';
import api from './routes/api'; import api from './routes/api';
import { selectPort } from './ports';
const app = express(); const app = express();
const server = http.createServer(app); const server = http.createServer(app);
@ -22,10 +23,11 @@ app.use(express.static('client'));
app.use('/', view); app.use('/', view);
app.use('/api-arduino', isBoardConnected, api); app.use('/api-arduino', isBoardConnected, api);
console.log("\nRunning Server...");
app.listen(port, host, () => { app.listen(port, host, () => {
console.log(`Server is connected and running in ${host} at port ${port} 🗣️🗣️🗣️`); console.log(`Server is connected and running in ${host} at port ${port} 🗣️🗣️🗣️`);
console.log(`Press CTRL+C to exit`); console.log(`* Press ${chalk.bold(chalk.yellow("CTRL+C"))} to exit`);
console.log(`URL: http://${host}:${port}/\n`); console.log(`* URL: ${chalk.bold(`http://${host}:${port}/\n`)}`);
console.log(chalk.yellow(`Connecting to Board`)); console.log(chalk.yellow(`Connecting to Board`));
board.on('ready', () => { board.on('ready', () => {

91
src/melodies/index.ts Normal file
View File

@ -0,0 +1,91 @@
export const Pitch = {
B0: 31,
C1: 33,
CS1: 35,
D1: 37,
DS1: 39,
E1: 41,
F1: 44,
FS1: 46,
G1: 49,
GS1: 52,
A1: 55,
AS1: 58,
B1: 62,
C2: 65,
CS2: 69,
D2: 73,
DS2: 78,
E2: 82,
F2: 87,
FS2: 93,
G2: 98,
GS2: 104,
A2: 110,
AS2: 117,
B2: 123,
C3: 131,
CS3: 139,
D3: 147,
DS3: 156,
E3: 165,
F3: 175,
FS3: 185,
G3: 196,
GS3: 208,
A3: 220,
AS3: 233,
B3: 247,
C4: 262,
CS4: 277,
D4: 294,
DS4: 311,
E4: 330,
F4: 349,
FS4: 370,
G4: 392,
GS4: 415,
A4: 440,
AS4: 466,
B4: 494,
C5: 523,
CS5: 554,
D5: 587,
DS5: 622,
E5: 659,
F5: 698,
FS5: 740,
G5: 784,
GS5: 831,
A5: 880,
AS5: 932,
B5: 988,
C6: 1047,
CS6: 1109,
D6: 1175,
DS6: 1245,
E6: 1319,
F6: 1397,
FS6: 1480,
G6: 1568,
GS6: 1661,
A6: 1760,
AS6: 1865,
B6: 1976,
C7: 2093,
CS7: 2217,
D7: 2349,
DS7: 2489,
E7: 2637,
F7: 2794,
FS7: 2960,
G7: 3136,
GS7: 3322,
A7: 3520,
AS7: 3729,
B7: 3951,
C8: 4186,
CS8: 4435,
D8: 4699,
DS8: 4978
}

29
src/ports/index.ts Normal file
View File

@ -0,0 +1,29 @@
import readline from 'node:readline/promises';
import { ReadPorts } from "./read";
import { board, suBoard } from '../setup';
export async function selectPort (cb: () => void) {
const ports = await ReadPorts();
const line =readline.createInterface({
input: process.stdin,
output: process.stdout
});
console.log(`Select number of ports (0-${ports.length})`);
ports.forEach((port, i) => {
console.log(`${i+1}. ${port}`);
})
const selectedInput: string = await line.question("Enter number: ");
const selectedNumber: number = Number.parseInt(selectedInput);
const selectedPort: string = ports[selectedNumber-1];
suBoard.port = selectedPort;
board.port = selectedPort;
console.log(`Selected port: ${selectedPort}`);
line.close();
cb();
}

8
src/ports/read.ts Normal file
View File

@ -0,0 +1,8 @@
import * as SerialPort from 'serialport';
export async function ReadPorts (): Promise<string[]> {
const ports = await SerialPort.SerialPort.list();
return ports
.filter(port => port.pnpId != null)
.map(port => port.path)
}

View File

@ -1,12 +1,14 @@
import { Response } from "express"; import { Response } from "express";
import { Router } from "express"; import { Router } from "express";
import { readLed, readRgbLed, writeLed, writeRgbLed } from "../controller/led"; import { readLed, readRgbLed, writeLed, writeRgbLed } from "../controller/led";
import { readPin, setPin } from "../controller/pin"; import { readPin, setPin } from "../controller/pin";
import { piezoTone } from "../controller/piezo";
const router = Router(); const router: Router = Router();
router.get('/hello', (req, res: Response) => { router.get('/hello', (req, res: Response): Response<string> => {
res.status(200).send("Hello"); return res.status(200).send("Hello");
}) })
router.get('/pin/:p', readPin); router.get('/pin/:p', readPin);
@ -18,4 +20,6 @@ router.patch('/led/:p/:a', writeLed);
router.get('/rgb-led', readRgbLed); router.get('/rgb-led', readRgbLed);
router.patch('/rgb-led/', writeRgbLed); router.patch('/rgb-led/', writeRgbLed);
router.patch('/piezo/:p/:n', piezoTone);
export default router; export default router;

View File

@ -1,11 +1,14 @@
import { SuBoard } from './support'; import { SuBoard } from './support';
import { Board } from 'johnny-five'; import { Board } from 'johnny-five';
import { config } from 'dotenv';
export const comport: string = '/dev/ttyUSB0'; config();
export const board: Board = new Board({
export const comport: string = process.env.SERIAL_PORT || '/dev/ttyUSB0';
export const suBoard: SuBoard = new SuBoard();
export const board: Board | null = new Board({
port: comport, port: comport,
repl: false, repl: false,
debug: false debug: false
}); });
export const suBoard: SuBoard = new SuBoard();

View File

@ -1,4 +1,5 @@
export interface SupportBoard { export interface SupportBoard {
port: string | null,
connected: boolean, connected: boolean,
PINS: { PINS: {
pwm: number[], pwm: number[],
@ -8,6 +9,7 @@ export interface SupportBoard {
} }
export class SuBoard implements SupportBoard { export class SuBoard implements SupportBoard {
public port = null;
public connected = false; public connected = false;
public PINS = { public PINS = {
pwm: [], pwm: [],