Maybe i can make server app

This commit is contained in:
Nomi Nonsense (Nonszy) 2024-03-24 19:16:08 +07:00
parent b9671dbf21
commit 5deaf0f600
24 changed files with 216 additions and 99 deletions

2
.env
View File

@ -1 +1 @@
SERIAL_PORT=/dev/ttyUSB0
SERIAL_PORT=COM3

View File

@ -1,5 +1,4 @@
{
"watch": ["src"],
"ext": ".ts,.js",
"exec": "ts-node ./src/index.ts"
"ext": ".ts,.js"
}

15
package-lock.json generated
View File

@ -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",

View File

@ -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",

40
src/app.ts Normal file
View File

@ -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();
}

View File

@ -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<string | any> {
const { board } = suBoard;
const { pin, value }: AnalogState = req.body;
try {
@ -27,6 +29,7 @@ export function analogWrite (req: Request, res: Response): Response<string | any
}
export async function analogRead (req: Request, res: Response) {
const { board } = suBoard;
let { pin } = req.params;
pin = pin[0] == "A" ? pin.slice(1, pin.length) : pin;

View File

@ -1,5 +1,5 @@
import { Request, Response } from "express";
import { board } from "../../setup";
import { suBoard } from "../../setup";
interface DigitalState {
pin: number | string,
@ -7,6 +7,7 @@ interface DigitalState {
}
export function digitalWrite (req: Request, res: Response): Response<string | any> {
const { board } = suBoard;
const { pin, state }: DigitalState = req.body;
let value: number;
@ -38,6 +39,7 @@ export function digitalWrite (req: Request, res: Response): Response<string | an
}
export function digitalRead (req: Request, res: Response): Response<string | any> {
const { board } = suBoard;
try {
const { pin } = req.params;
const value = board.pins[pin].value == 1 ? 'HIGH' : 'LOW';

View File

@ -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<string | any> {
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<string | any> {
}
export function readPins (req: Request, res: Response): Response<string | any> {
const { board } = suBoard;
const pins: string[] = req.body.p;
const pinModes = [];
@ -39,12 +42,13 @@ export function readPins (req: Request, res: Response): Response<string | any> {
}
export function setPin (req: Request, res: Response): Response<string | any> {
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<string | any> {
}
export function setPins (req: Request, res: Response): Response<string | any> {
const { board } = suBoard;
type PinModes = {
pin: string,
mode: sPinModes | string
@ -71,7 +76,7 @@ export function setPins (req: Request, res: Response): Response<string | any> {
const { pin, mode } = p;
board.pinMode(pin, Pin[mode]);
suBoard.PINS.pwm.push(pin);
suBoard.PINS.digital.push(pin);
suBoard.sort();
});

View File

@ -1,10 +1,10 @@
import { Request, Response } from "express";
import { board } from "../../setup";
import { ChannelPins, digitalValue, voltage } from "..";
import { suBoard } from "../../setup";
import { ChannelPins, voltage } from "..";
import { Led } from "johnny-five";
export function readLed (req: Request, res: Response): Response<string | any> {
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<string | any> {
}
export function writeLed (req: Request, res: Response): Response<string | any> {
const { board } = suBoard;
const { p, a } = req.params;
const act: string = a.toLocaleLowerCase();
const pin: number = Number.parseInt(p);

View File

@ -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<Response<string, any>> {
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;

View File

@ -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<string, any> {
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);

7
src/dev.ts Normal file
View File

@ -0,0 +1,7 @@
import { Board } from "johnny-five";
import { run } from "./server";
import { defaultBoardOption, suBoard } from "./setup";
suBoard.board = new Board(defaultBoardOption);
run();

View File

@ -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`);

View File

@ -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");
})
});
main();

View File

@ -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) {

View File

@ -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`

View File

@ -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<string | any> {
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;
}

View File

@ -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}`);
})
})

View File

@ -1,8 +1,14 @@
import * as SerialPort from 'serialport';
export async function ReadPorts (): Promise<string[]> {
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
}
})
}

73
src/server.ts Normal file
View File

@ -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);
}
}

12
src/setup/config.ts Normal file
View File

@ -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: "*"
}
}

View File

@ -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
});
};

View File

@ -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);
}
}

5
src/utils/network.ts Normal file
View File

@ -0,0 +1,5 @@
import { address } from 'ip';
export function getPublicIp () {
return address("public", "ipv4") || '127.0.0.1';
}