From f90387bab96eaf46be075d1cd73190761b209fab Mon Sep 17 00:00:00 2001 From: norman-andrians Date: Sun, 3 Mar 2024 18:47:55 +0700 Subject: [PATCH] Piezo buzzer, portlist and update documentation --- README.md | 27 ++++++++++-- package-lock.json | 59 +++++++++++++++++++++++--- package.json | 3 ++ src/controller/piezo.ts | 28 +++++++++++++ src/index.ts | 8 ++-- src/melodies/index.ts | 91 +++++++++++++++++++++++++++++++++++++++++ src/ports/index.ts | 29 +++++++++++++ src/ports/read.ts | 8 ++++ src/routes/api.ts | 10 +++-- src/setup/index.ts | 13 +++--- src/setup/support.ts | 2 + 11 files changed, 259 insertions(+), 19 deletions(-) create mode 100644 src/controller/piezo.ts create mode 100644 src/melodies/index.ts create mode 100644 src/ports/index.ts create mode 100644 src/ports/read.ts diff --git a/README.md b/README.md index 340ce36..056117d 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,28 @@ # 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` \ No newline at end of file +### 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 + ``` \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index a724092..deb0bce 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,9 @@ "license": "MIT", "dependencies": { "@serialport/bindings": "^9.2.9", + "@serialport/list": "^12.0.0", "chalk": "^4.1.2", + "dotenv": "^16.4.5", "express": "^4.18.3", "firmata": "^2.3.0", "johnny-five": "^2.1.0", @@ -23,6 +25,7 @@ "@types/firmata": "^0.19.8", "@types/johnny-five": "^2.1.9", "@types/nodemon": "^1.19.6", + "@types/serialport": "^8.0.5", "@types/socket.io": "^3.0.2", "ts-node": "^10.9.2", "typescript": "^5.3.3" @@ -200,6 +203,24 @@ "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": { "version": "12.0.0", "resolved": "https://registry.npmjs.org/@serialport/parser-byte-length/-/parser-byte-length-12.0.0.tgz", @@ -420,6 +441,16 @@ "@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": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", @@ -481,13 +512,12 @@ } }, "node_modules/@types/serialport": { - "version": "4.0.16", - "resolved": "https://registry.npmjs.org/@types/serialport/-/serialport-4.0.16.tgz", - "integrity": "sha512-JbeHlODoEPbKmo0Q1xv/EbawShWYyNZJEW5V8cuPRvRofwwC5VKzGbZZkk8MfIo5lkpmcJWhKzTGgQ1jVXUgjQ==", + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/@types/serialport/-/serialport-8.0.5.tgz", + "integrity": "sha512-Lw082WIy46fYLnOzyf+8QN/vZaR3d9ol9WNyEGHsKJJ1pmZogFbloHGbnXyNcxfV9aTbgviWU8jktrIjRheYFQ==", "dev": true, "dependencies": { - "@types/node": "*", - "@types/streamjs": "*" + "@types/node": "*" } }, "node_modules/@types/serve-static": { @@ -896,6 +926,14 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "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": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -1053,6 +1091,17 @@ "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": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", diff --git a/package.json b/package.json index efd68a3..9a144b8 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,9 @@ }, "dependencies": { "@serialport/bindings": "^9.2.9", + "@serialport/list": "^12.0.0", "chalk": "^4.1.2", + "dotenv": "^16.4.5", "express": "^4.18.3", "firmata": "^2.3.0", "johnny-five": "^2.1.0", @@ -25,6 +27,7 @@ "@types/firmata": "^0.19.8", "@types/johnny-five": "^2.1.9", "@types/nodemon": "^1.19.6", + "@types/serialport": "^8.0.5", "@types/socket.io": "^3.0.2", "ts-node": "^10.9.2", "typescript": "^5.3.3" diff --git a/src/controller/piezo.ts b/src/controller/piezo.ts new file mode 100644 index 0000000..a0c42b1 --- /dev/null +++ b/src/controller/piezo.ts @@ -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}` + }); +} \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 4645227..5b92770 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,5 @@ import http from 'node:http'; -import express, { Request, Response } from 'express'; +import express from 'express'; import { Server } from 'socket.io'; import chalk from 'chalk'; @@ -8,6 +8,7 @@ import { board, suBoard, comport } from './setup'; import { isBoardConnected } from './middleware/connection'; import view from './routes/view'; import api from './routes/api'; +import { selectPort } from './ports'; const app = express(); const server = http.createServer(app); @@ -22,10 +23,11 @@ app.use(express.static('client')); app.use('/', view); app.use('/api-arduino', isBoardConnected, api); +console.log("\nRunning Server..."); app.listen(port, host, () => { console.log(`Server is connected and running in ${host} at port ${port} 🗣️🗣️🗣️`); - console.log(`Press CTRL+C to exit`); - console.log(`URL: http://${host}:${port}/\n`); + console.log(`* Press ${chalk.bold(chalk.yellow("CTRL+C"))} to exit`); + console.log(`* URL: ${chalk.bold(`http://${host}:${port}/\n`)}`); console.log(chalk.yellow(`Connecting to Board`)); board.on('ready', () => { diff --git a/src/melodies/index.ts b/src/melodies/index.ts new file mode 100644 index 0000000..788f0ff --- /dev/null +++ b/src/melodies/index.ts @@ -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 +} \ No newline at end of file diff --git a/src/ports/index.ts b/src/ports/index.ts new file mode 100644 index 0000000..fe9ccf7 --- /dev/null +++ b/src/ports/index.ts @@ -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(); +} \ No newline at end of file diff --git a/src/ports/read.ts b/src/ports/read.ts new file mode 100644 index 0000000..cafeaa7 --- /dev/null +++ b/src/ports/read.ts @@ -0,0 +1,8 @@ +import * as SerialPort from 'serialport'; + +export async function ReadPorts (): Promise { + const ports = await SerialPort.SerialPort.list(); + return ports + .filter(port => port.pnpId != null) + .map(port => port.path) +} \ No newline at end of file diff --git a/src/routes/api.ts b/src/routes/api.ts index 8b113c9..d358b6e 100644 --- a/src/routes/api.ts +++ b/src/routes/api.ts @@ -1,12 +1,14 @@ import { Response } from "express"; import { Router } from "express"; + import { readLed, readRgbLed, writeLed, writeRgbLed } from "../controller/led"; import { readPin, setPin } from "../controller/pin"; +import { piezoTone } from "../controller/piezo"; -const router = Router(); +const router: Router = Router(); -router.get('/hello', (req, res: Response) => { - res.status(200).send("Hello"); +router.get('/hello', (req, res: Response): Response => { + return res.status(200).send("Hello"); }) router.get('/pin/:p', readPin); @@ -18,4 +20,6 @@ router.patch('/led/:p/:a', writeLed); router.get('/rgb-led', readRgbLed); router.patch('/rgb-led/', writeRgbLed); +router.patch('/piezo/:p/:n', piezoTone); + export default router; \ No newline at end of file diff --git a/src/setup/index.ts b/src/setup/index.ts index 2f7bd4c..b030485 100644 --- a/src/setup/index.ts +++ b/src/setup/index.ts @@ -1,11 +1,14 @@ import { SuBoard } from './support'; import { Board } from 'johnny-five'; +import { config } from 'dotenv'; -export const comport: string = '/dev/ttyUSB0'; -export const board: Board = new Board({ +config(); + +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, repl: false, debug: false -}); - -export const suBoard: SuBoard = new SuBoard(); \ No newline at end of file +}); \ No newline at end of file diff --git a/src/setup/support.ts b/src/setup/support.ts index 10ea7ef..dde8154 100644 --- a/src/setup/support.ts +++ b/src/setup/support.ts @@ -1,4 +1,5 @@ export interface SupportBoard { + port: string | null, connected: boolean, PINS: { pwm: number[], @@ -8,6 +9,7 @@ export interface SupportBoard { } export class SuBoard implements SupportBoard { + public port = null; public connected = false; public PINS = { pwm: [],