From 5deaf0f600e62ab061971c8da5eb28e55bba4267 Mon Sep 17 00:00:00 2001 From: norman-andrians Date: Sun, 24 Mar 2024 19:16:08 +0700 Subject: [PATCH] Maybe i can make server app --- .env | 2 +- nodemon.json | 3 +- package-lock.json | 15 +++++ package.json | 5 +- src/app.ts | 40 ++++++++++++ src/controller/basic/analog.ts | 5 +- src/controller/basic/digital.ts | 4 +- src/controller/basic/pin.ts | 11 +++- src/controller/components/led.ts | 9 +-- src/controller/components/photoresistor.ts | 4 +- src/controller/components/servo.ts | 3 +- src/dev.ts | 7 +++ src/handlers/socketHandler.ts | 4 +- src/index.ts | 62 +----------------- src/middleware/connection.ts | 4 +- src/middleware/pin.ts | 2 +- src/ports/index.ts | 16 ++--- src/ports/print.ts | 2 +- src/ports/read.ts | 10 ++- src/server.ts | 73 ++++++++++++++++++++++ src/setup/config.ts | 12 ++++ src/setup/index.ts | 8 +-- src/setup/support.ts | 9 ++- src/utils/network.ts | 5 ++ 24 files changed, 216 insertions(+), 99 deletions(-) create mode 100644 src/app.ts create mode 100644 src/dev.ts create mode 100644 src/server.ts create mode 100644 src/setup/config.ts create mode 100644 src/utils/network.ts diff --git a/.env b/.env index ea5b7eb..959abe0 100644 --- a/.env +++ b/.env @@ -1 +1 @@ -SERIAL_PORT=/dev/ttyUSB0 \ No newline at end of file +SERIAL_PORT=COM3 \ No newline at end of file diff --git a/nodemon.json b/nodemon.json index 1f9c899..7e0bb38 100644 --- a/nodemon.json +++ b/nodemon.json @@ -1,5 +1,4 @@ { "watch": ["src"], - "ext": ".ts,.js", - "exec": "ts-node ./src/index.ts" + "ext": ".ts,.js" } \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 2c4d932..0159e06 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,11 +11,13 @@ "dependencies": { "@serialport/bindings": "^9.2.9", "@serialport/list": "^12.0.0", + "@types/ip": "^1.1.3", "chalk": "^4.1.2", "cors": "^2.8.5", "dotenv": "^16.4.5", "express": "^4.18.3", "firmata": "^2.3.0", + "ip": "^2.0.1", "johnny-five": "^2.1.0", "nodemon": "^3.1.0", "serialport": "^12.0.0", @@ -458,6 +460,14 @@ "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", "dev": true }, + "node_modules/@types/ip": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@types/ip/-/ip-1.1.3.tgz", + "integrity": "sha512-64waoJgkXFTYnCYDUWgSATJ/dXEBanVkaP5d4Sbk7P6U7cTTMhxVyROTckc6JKdwCrgnAjZMn0k3177aQxtDEA==", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/johnny-five": { "version": "2.1.9", "resolved": "https://registry.npmjs.org/@types/johnny-five/-/johnny-five-2.1.9.tgz", @@ -1739,6 +1749,11 @@ "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" }, + "node_modules/ip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.1.tgz", + "integrity": "sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ==" + }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", diff --git a/package.json b/package.json index 1fb8716..600dbc4 100644 --- a/package.json +++ b/package.json @@ -7,18 +7,21 @@ "license": "MIT", "private": true, "scripts": { - "start": "nodemon", + "start": "nodemon --exec ts-node ./src", + "dev": "nodemon --exec ts-node ./src/dev.ts", "ports": "ts-node src/ports/print.ts", "test-blink": "nodemon test/blink.js" }, "dependencies": { "@serialport/bindings": "^9.2.9", "@serialport/list": "^12.0.0", + "@types/ip": "^1.1.3", "chalk": "^4.1.2", "cors": "^2.8.5", "dotenv": "^16.4.5", "express": "^4.18.3", "firmata": "^2.3.0", + "ip": "^2.0.1", "johnny-five": "^2.1.0", "nodemon": "^3.1.0", "serialport": "^12.0.0", diff --git a/src/app.ts b/src/app.ts new file mode 100644 index 0000000..aed69bf --- /dev/null +++ b/src/app.ts @@ -0,0 +1,40 @@ +import readline from "node:readline/promises"; +import chalk from "chalk"; +import { Board } from "johnny-five"; + +import { selectPort } from "./ports"; +import { run } from "./server"; +import { suBoard } from "./setup"; +import { cfg } from "./setup/config"; +import { getPublicIp } from "./utils/network"; + +async function questions () { + const line = readline.createInterface({ + input: process.stdin, + output: process.stdout + }) + + const exposeHost = await line.question("Run in public network? (y/n) "); + + if (exposeHost.toLowerCase() == "y") { + console.warn(chalk.yellow("Running a server on a public host may not be able to use the socket.io api")); + cfg.server.host = getPublicIp(); + } + + line.close(); +} + +export default async function main () { + const port = await selectPort(); + await questions(); + + console.log(`Selected port ${port}`); + + cfg.port = port; + suBoard.board = new Board({ + port: port, + debug: false, + repl: false + }); + run(); +} \ No newline at end of file diff --git a/src/controller/basic/analog.ts b/src/controller/basic/analog.ts index e986a27..d1965ad 100644 --- a/src/controller/basic/analog.ts +++ b/src/controller/basic/analog.ts @@ -1,13 +1,15 @@ import { Request, Response } from "express"; -import { board } from "../../setup"; +import { suBoard } from "../../setup"; import * as Promises from "../../promises"; + interface AnalogState { pin: string, value: number } export function analogWrite (req: Request, res: Response): Response { + const { board } = suBoard; const { pin, value }: AnalogState = req.body; try { @@ -27,6 +29,7 @@ export function analogWrite (req: Request, res: Response): Response { + const { board } = suBoard; const { pin, state }: DigitalState = req.body; let value: number; @@ -38,6 +39,7 @@ export function digitalWrite (req: Request, res: Response): Response { + const { board } = suBoard; try { const { pin } = req.params; const value = board.pins[pin].value == 1 ? 'HIGH' : 'LOW'; diff --git a/src/controller/basic/pin.ts b/src/controller/basic/pin.ts index fe71f4b..1f3a729 100644 --- a/src/controller/basic/pin.ts +++ b/src/controller/basic/pin.ts @@ -1,9 +1,11 @@ import { Request, Response } from "express"; -import { board, suBoard } from "../../setup"; +import { suBoard } from "../../setup"; import { sPinModes } from ".."; import { Pin } from "johnny-five"; + export function readPin (req: Request, res: Response): Response { + const { board } = suBoard; const pin: string = req.params.p; const { mode } = board.pins[pin]; @@ -21,6 +23,7 @@ export function readPin (req: Request, res: Response): Response { } export function readPins (req: Request, res: Response): Response { + const { board } = suBoard; const pins: string[] = req.body.p; const pinModes = []; @@ -39,12 +42,13 @@ export function readPins (req: Request, res: Response): Response { } export function setPin (req: Request, res: Response): Response { + const { board } = suBoard; const pin: string = req.params.p; const mode: sPinModes | string = req.params.m.toUpperCase(); board.pinMode(pin, Pin[mode]); - suBoard.PINS.pwm.push(pin); + suBoard.PINS.digital.push(pin); suBoard.sort(); return res.status(200).json({ @@ -60,6 +64,7 @@ export function setPin (req: Request, res: Response): Response { } export function setPins (req: Request, res: Response): Response { + const { board } = suBoard; type PinModes = { pin: string, mode: sPinModes | string @@ -71,7 +76,7 @@ export function setPins (req: Request, res: Response): Response { const { pin, mode } = p; board.pinMode(pin, Pin[mode]); - suBoard.PINS.pwm.push(pin); + suBoard.PINS.digital.push(pin); suBoard.sort(); }); diff --git a/src/controller/components/led.ts b/src/controller/components/led.ts index 9ab24bd..c09adaa 100644 --- a/src/controller/components/led.ts +++ b/src/controller/components/led.ts @@ -1,10 +1,10 @@ import { Request, Response } from "express"; -import { board } from "../../setup"; -import { ChannelPins, digitalValue, voltage } from ".."; -import { Led } from "johnny-five"; - +import { suBoard } from "../../setup"; +import { ChannelPins, voltage } from ".."; +import { Led } from "johnny-five"; export function readLed (req: Request, res: Response): Response { + const { board } = suBoard; try { const { p } = req.params; const pin: number = Number.parseInt(p); @@ -29,6 +29,7 @@ export function readLed (req: Request, res: Response): Response { } export function writeLed (req: Request, res: Response): Response { + const { board } = suBoard; const { p, a } = req.params; const act: string = a.toLocaleLowerCase(); const pin: number = Number.parseInt(p); diff --git a/src/controller/components/photoresistor.ts b/src/controller/components/photoresistor.ts index 3bfe66f..a4c84d4 100644 --- a/src/controller/components/photoresistor.ts +++ b/src/controller/components/photoresistor.ts @@ -1,8 +1,10 @@ import { Request, Response } from "express"; -import { board } from "../../setup"; +import { suBoard } from "../../setup"; import { analogRead } from "../../promises"; + export async function readResistor (req: Request, res: Response): Promise> { + const { board } = suBoard; const { p } = req.params; const pin: string = req.params.p.startsWith("A") ? req.params.p.slice(0, p.length) : req.params.p; diff --git a/src/controller/components/servo.ts b/src/controller/components/servo.ts index 54e66cf..962cad4 100644 --- a/src/controller/components/servo.ts +++ b/src/controller/components/servo.ts @@ -1,7 +1,8 @@ import { Request, Response } from "express"; -import { board } from "../../setup"; +import { suBoard } from "../../setup"; export function rotateServo (req: Request, res: Response): Response { + const { board } = suBoard; const { p } = req.params; const pin: string = req.params.p.startsWith("A") ? req.params.p.slice(0, p.length) : req.params.p; const angle: number = Number.parseInt(req.params.ang); diff --git a/src/dev.ts b/src/dev.ts new file mode 100644 index 0000000..9d3e283 --- /dev/null +++ b/src/dev.ts @@ -0,0 +1,7 @@ +import { Board } from "johnny-five"; +import { run } from "./server"; +import { defaultBoardOption, suBoard } from "./setup"; + +suBoard.board = new Board(defaultBoardOption); + +run(); \ No newline at end of file diff --git a/src/handlers/socketHandler.ts b/src/handlers/socketHandler.ts index 5b9cc3e..d25eb43 100644 --- a/src/handlers/socketHandler.ts +++ b/src/handlers/socketHandler.ts @@ -1,6 +1,8 @@ import { Socket } from "socket.io"; import { Pin, Sensor } from "johnny-five"; -import { board } from "../setup"; +import { suBoard } from "../setup"; + +const { board } = suBoard; export default (socket: Socket) => { console.log(`${socket.id} | ${socket.client.request.headers.host} | Joined`); diff --git a/src/index.ts b/src/index.ts index b946239..a0a7baf 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,61 +1,3 @@ -import http from 'node:http'; -import path from 'node:path'; -import express from 'express'; -import cors from 'cors'; -import { Server } from 'socket.io'; -import chalk from 'chalk'; +import main from "./app"; -import { board, suBoard, comport } from './setup'; - -import { isBoardConnected } from './middleware/connection'; -import view from './routes/view'; -import api from './routes/api'; -import socketHandler from './handlers/socketHandler'; - -const app = express(); -const server: http.Server = http.createServer(app); -const io: Server = new Server(server, { - cors: { - origin: "*" - } -}); // I have no experience at WebSocket, so.. forgive me :) - -// Server configuration -const host: string = 'localhost'; -const port: number = 3000; - -// Express middleware -app.use(cors({ origin: "*" })); -app.use(express.json()); -app.use(express.static(path.join(__dirname, 'client'))); - -// Socket.io event handlers -io.on('connection', socketHandler); - -// HTTP Routes -app.use('/', view); -app.use('/api-arduino', isBoardConnected, api); // Board API Controllers - -// Run server -console.log("\nRunning Server..."); -server.listen(port, () => { - console.log(`Server is connected and running in ${host} at port ${port} 🗣️🗣️🗣️`); - 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', () => { - console.log(chalk.green(`Board at port ${comport} Connected!! \⁠(⁠^⁠o⁠^⁠)⁠/`)); - suBoard.connected = true; - }) - - board.on('error', (err) => { - console.error(chalk.red("\nError while connecting to Board")); - console.error(chalk.red(err.message)); - console.log("Enter 'rs' to try again"); - }) - - board.on('exit', () => { - console.log("Bye bye"); - }) -}); \ No newline at end of file +main(); \ No newline at end of file diff --git a/src/middleware/connection.ts b/src/middleware/connection.ts index 14842fd..5b99907 100644 --- a/src/middleware/connection.ts +++ b/src/middleware/connection.ts @@ -1,5 +1,7 @@ import { NextFunction, Request, Response } from "express"; -import { board, suBoard } from "../setup"; +import { suBoard } from "../setup"; + +const { board } = suBoard; export function isBoardConnected (req: Request, res: Response, next: NextFunction) { if (suBoard.connected) { diff --git a/src/middleware/pin.ts b/src/middleware/pin.ts index 85af50c..f89eb65 100644 --- a/src/middleware/pin.ts +++ b/src/middleware/pin.ts @@ -24,7 +24,7 @@ export function isPinBeingUsed (req: Request, res: Response, next: NextFunction) }); } - if (suBoard.PINS.pwm.indexOf(pin) != -1) { + if (suBoard.PINS.digital.indexOf(pin) != -1) { return res.status(400).json({ status: 400, message: `Pin ${pin}, is used` diff --git a/src/ports/index.ts b/src/ports/index.ts index fe9ccf7..0aec664 100644 --- a/src/ports/index.ts +++ b/src/ports/index.ts @@ -1,29 +1,23 @@ import readline from 'node:readline/promises'; import { ReadPorts } from "./read"; -import { board, suBoard } from '../setup'; -export async function selectPort (cb: () => void) { +export async function selectPort (): Promise { const ports = await ReadPorts(); - const line =readline.createInterface({ + 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}`); + console.log(`${i+1}. ${port.path} - ${port.name}${port.serialNumber ? " - Recomended" : ""}`); }) const selectedInput: string = await line.question("Enter number: "); const selectedNumber: number = Number.parseInt(selectedInput); - const selectedPort: string = ports[selectedNumber-1]; + const selectedPort: { name?: string, path?: string } = ports[selectedNumber-1]; - suBoard.port = selectedPort; - board.port = selectedPort; - - console.log(`Selected port: ${selectedPort}`); line.close(); - - cb(); + return selectedPort.path; } \ No newline at end of file diff --git a/src/ports/print.ts b/src/ports/print.ts index 8b45b8c..77037ea 100644 --- a/src/ports/print.ts +++ b/src/ports/print.ts @@ -3,6 +3,6 @@ import { ReadPorts } from "./read"; ReadPorts().then((ports) => { console.log(`${ports.length} Ports available`); ports.forEach((port, i) => { - console.log((i+1) + ", " + port); + console.log(`${i+1}. ${port.path} - ${port.name}`); }) }) \ No newline at end of file diff --git a/src/ports/read.ts b/src/ports/read.ts index cafeaa7..8933fe8 100644 --- a/src/ports/read.ts +++ b/src/ports/read.ts @@ -1,8 +1,14 @@ import * as SerialPort from 'serialport'; -export async function ReadPorts (): Promise { +export async function ReadPorts (): Promise<{ name?: string, path?: string, serialNumber?: string | number }[]> { const ports = await SerialPort.SerialPort.list(); return ports .filter(port => port.pnpId != null) - .map(port => port.path) + .map(port => { + return { + name: port['friendlyName'], + path: port.path, + serialNumber: port.serialNumber + } + }) } \ No newline at end of file diff --git a/src/server.ts b/src/server.ts new file mode 100644 index 0000000..91cc0f4 --- /dev/null +++ b/src/server.ts @@ -0,0 +1,73 @@ +import http from 'node:http'; +import path from 'node:path'; +import express from 'express'; +import cors from 'cors'; +import { Server } from 'socket.io'; +import chalk from 'chalk'; + +import { suBoard, comport } from './setup'; +import { cfg } from './setup/config'; + +import { isBoardConnected } from './middleware/connection'; +import view from './routes/view'; +import api from './routes/api'; +import socketHandler from './handlers/socketHandler'; + +const app = express(); +const server: http.Server = http.createServer(app); +const io: Server = new Server(server, { + cors: { + origin: cfg.server.cors + } +}); // I have no experience at WebSocket, so.. forgive me :) + +// Express middleware +app.use(cors({ origin: "*" })); +app.use(express.json()); +app.use(express.static(path.join(__dirname, 'client'))); + +// Socket.io event handlers +io.on('connection', socketHandler); + +// HTTP Routes +app.use('/', view); +app.use('/api-arduino', isBoardConnected, api); // Board API Controllers + +// Run server +export function run () { + const { board } = suBoard; + const { port, host } = cfg.server; + + try { + + if (!board) { + throw new Error("Failed to use API, process canceled. No new API boards have been declared as of yet"); + } + + console.log("\nRunning Server..."); + server.listen(port, () => { + console.log(`Server is connected and running in ${host} at port ${port} 🗣️🗣️🗣️`); + console.log(`* Enter ${chalk.bold(chalk.yellow("rs"))} to restart`); + 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', () => { + console.log(chalk.green(`Board at port ${comport} Connected!! \⁠(⁠^⁠o⁠^⁠)⁠/`)); + suBoard.connected = true; + }) + + board.on('error', (err) => { + console.error(chalk.red("\nError while connecting to Board")); + console.error(chalk.red(err.message)); + console.log("Enter 'rs' to try again"); + }) + + board.on('exit', () => { + console.log("Bye bye"); + }) + }); + } catch (err) { + console.error("Fatal error. ", err); + } +} \ No newline at end of file diff --git a/src/setup/config.ts b/src/setup/config.ts new file mode 100644 index 0000000..7c0b854 --- /dev/null +++ b/src/setup/config.ts @@ -0,0 +1,12 @@ +import { config } from 'dotenv'; + +config(); + +export const cfg = { + port: process.env.SERIAL_PORT || '/dev/ttyUSB0', + server: { + port: 3000, + host: 'localhost', + cors: "*" + } +} \ No newline at end of file diff --git a/src/setup/index.ts b/src/setup/index.ts index b030485..e8d7319 100644 --- a/src/setup/index.ts +++ b/src/setup/index.ts @@ -1,5 +1,5 @@ import { SuBoard } from './support'; -import { Board } from 'johnny-five'; +import { Board, BoardOption } from 'johnny-five'; import { config } from 'dotenv'; config(); @@ -7,8 +7,8 @@ config(); export const comport: string = process.env.SERIAL_PORT || '/dev/ttyUSB0'; export const suBoard: SuBoard = new SuBoard(); -export const board: Board | null = new Board({ +export const defaultBoardOption: BoardOption = { port: comport, + debug: false, repl: false, - debug: false -}); \ 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 dde8154..178b47e 100644 --- a/src/setup/support.ts +++ b/src/setup/support.ts @@ -1,22 +1,25 @@ +import { Board } from "johnny-five"; + export interface SupportBoard { port: string | null, connected: boolean, PINS: { - pwm: number[], + digital: number[], analog: number[], } sort: () => void } export class SuBoard implements SupportBoard { + public board: Board | null; public port = null; public connected = false; public PINS = { - pwm: [], + digital: [], analog: [] }; public sort() { - this.PINS.pwm = this.PINS.pwm.sort((a, b) => b - a); + this.PINS.digital = this.PINS.digital.sort((a, b) => b - a); this.PINS.analog = this.PINS.analog.sort((a, b) => b - a); } } \ No newline at end of file diff --git a/src/utils/network.ts b/src/utils/network.ts new file mode 100644 index 0000000..11de044 --- /dev/null +++ b/src/utils/network.ts @@ -0,0 +1,5 @@ +import { address } from 'ip'; + +export function getPublicIp () { + return address("public", "ipv4") || '127.0.0.1'; +} \ No newline at end of file