Compare commits

..

19 Commits

Author SHA1 Message Date
d401a7b0b0 Preparing build 2024-03-24 23:14:08 +07:00
5deaf0f600 Maybe i can make server app 2024-03-24 19:16:08 +07:00
b9671dbf21 Error event handler 2024-03-18 08:20:09 +07:00
Norman Andrians
e80b080ed4
Create LICENSE 2024-03-17 22:26:53 +07:00
d7eec00ba8 Update client build: Piezo music editor 2024-03-17 22:06:32 +07:00
2e205ec1c1 Complete API Docs with examples 2024-03-17 11:18:37 +07:00
9470845bad Allows to set pin modes more than one 2024-03-17 11:17:56 +07:00
b176f6108e Update docs 2024-03-17 00:02:53 +07:00
3d1b9bf3ac Add rooms as 'parallel' in socket handler and add digital analog api 2024-03-16 23:38:55 +07:00
23e35cdd2c First client view builded 2024-03-10 21:03:53 +07:00
4bc42d6ae9 Open API with clent 2024-03-10 19:13:28 +07:00
f1ccdb35bf The code type is DOTENV! 2024-03-10 14:02:08 +07:00
e064e198a3 Refact json response and fixed piezo tone 2024-03-10 13:56:38 +07:00
76e9ae14e5 Update API docs 2024-03-06 10:09:14 +07:00
737a2e2fd3 servo and photoresistor in http routes 2024-03-05 00:50:49 +07:00
80bedd3cb3 working servo and photoresistor with websocket 2024-03-05 00:26:06 +07:00
f897b3bf50 Piezo music, setup socket events 2024-03-04 20:34:35 +07:00
f90387bab9 Piezo buzzer, portlist and update documentation 2024-03-03 18:47:55 +07:00
71c4ec5072 johnny-five is very promising 2024-03-03 15:22:51 +07:00
63 changed files with 3261 additions and 393 deletions

1
.env
View File

@ -1 +0,0 @@
SERIAL_PORT=ttyUSB0

3
.gitignore vendored
View File

@ -1,3 +1,6 @@
example-client/
build
# Logs # Logs
logs logs
*.log *.log

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 Norman Andriansyah
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

771
README.md
View File

@ -1,7 +1,772 @@
# 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 **SIMPLE** 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 make some IoT stuff. Enjoy (⁠づ⁠ ̄⁠ ⁠³⁠ ̄⁠)⁠づ
### Setup ![ENA BBQ](https://media1.tenor.com/m/Nl2_tsGV-qkAAAAC/enadbbq-dreambbq.gif)
- Install all packages with `npm i` or `yarn` <small>*the animation is from ENA BBQ series by JoelG</small>
## Setup
Tools that are required
- Arduino Board
- [Arduino IDE](https://www.arduino.cc/en/software)
- [Node.js](https://nodejs.org/en) v16.0.0^ or other javascript runtime
### 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. Plug in your Board via USB
2. Open Arduino IDE then select your Board
3. Go to `File > Examples > Firmata` Select `StandarFirmataPlus`
4. 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
```dotenv
SERIAL_PORT=/dev/ttyUSB0
```
3. Run the project
```bash
npm start
```
4. If the board is connected try to open the client page in [https://localhost:3000/](https://localhost:3000/)
![Client page](./docs/client.png)
#### Other scripts
- You can see what ports are connected to the device by running `npm run ports` script.
```bash
npm run ports
```
```
> lunar-vein-arduino@1.0.0 ports
> ts-node src/ports/print.ts
3 Ports available
1. /dev/ttyUSB0
2. /dev/ttyUSB1
3. /dev/ttyACM0
```
# REST API Documentation
## Table of contents
- [Common HTTP Responses](#common-http-responses)
- [JSON Response](#json-response)
- [Example Request](#example-request)
- [Digital](#digital)
- [Digital Read](#digital-read)
- [Digital Write](#digital-write)
- [Analog](#analog)
- [Analog Read](#analog-read)
- [Analog Write](#analog-write)
- [PIN](#pin)
- [Read PIN Mode](#read-pin-mode)
- [Set PIN Mode](#set-pin-mode)
- [LED](#led)
- [Read LED state](#read-led-state)
- [Set LED state](#set-led-state)
- [RGB LED](#rgb-led)
- [Read RGB state](#read-rgb-state)
- [Set RGB state](#set-rgb-state)
- [Piezo](#piezo)
- [Piezo Tone](#piezo-tone)
- [Piezo Note](#piezo-note)
- [Piezo Play Music](#piezo-play-music)
## Common HTTP Responses
Some HTTP responses are sent with JSON, otherwise an HTML body will be sent which is the default express.js response
| HTTP Status | Marks |
|-------------|-------|
| 200 | The request `act` was successful |
| 400 | You may be making an invalid request, try to check the payload or recheck the documentation. |
| 404 | The resource wass not found |
| 405 | Method not allowed, servers usually only accept `GET` and `PATCH` methods |
| 500 | An error in the app server, if it continues please raise an issue or ask to contribute. |
## JSON Response
```json
{
"status": 200,
"pin_state": {
"13": "HIGH"
},
"message": "Changed pin state 13 to HIGH"
}
```
| Property | Description |
|----------|-------------|
| status | HTTP Status code |
| pin_state | The state value of the pin that has been changed |
| message | Descriptive message of the changed state |
## Example Request
This is an example of sending a request to turn on the LED light, [See LED API](#set-led-state)
**Using curl**
```bash
curl -X PATCH http://localhost:3000/api-arduino/led/13/on
```
**Using fetch async/await javascript**
```javascript
async function turnOnLed() {
const res = await fetch("http://localhost:3000/api-arduino/led/13/on", { method: 'PATCH' }); // Set LED to HIGH
const data = await res.json();
console.log(data); // { status: 200, pin_state: { 13: high }, message: "Pin 13 Set to HIGH" }
}
```
## Digital
### Digital Read
- **URL Endpoint**
`/api-arduino/digital/:pin`
- **URL Params**
| Params | Mark | Type | Required | Description |
|--------|------|------|----------|-------------|
| pin | pin | `string` | true | Seleced pin |
- **Method**
`GET`
- **Example Request**
```javascript
fetch("http://localhost:3000/api-arduino/digital/13")
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error('error', err));
```
- **Example Response**
```json
{
"status": 200,
"pin_state": {
"13": "HIGH"
}
}
```
### Digital Write
- **URL Endpoint**
`/api-arduino/digital`
- **Body**
```typescript
{
pin: number,
state: 'HIGH' | 'LOW' | 1 | 0 | string
}
```
- **Method**
`PATCH`
- **Example Request**
```javascript
var data = {
pin: 13,
state: 'LOW'
}
var options = {
method: 'PATCH',
body: JSON.stringify(data),
headers: {
'Content-Type': 'application/json'
},
redirect: 'follow'
}
fetch("http://localhost:3000/api-arduino/digital", options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error('error', err));
```
- **Example Response**
```json
{
"status": 200,
"pin_state": {
"13": "HIGH"
}
}
```
## Analog
### Analog Read
- **URL Endpoint**
`/api-arduino/analog/:p`
- **URL Params**
| Params | Mark | Type | Required | Description |
|--------|------|------|----------|-------------|
| pin | pin | `string` | true | Seleced pin |
- **Method**
`GET`
- **Example Request**
```javascript
fetch("http://localhost:3000/api-arduino/analog/A0")
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error('error', err));
```
- **Example Response**
```json
{
"status": 200,
"analog_state": {
"A0": 1023
}
}
```
### Analog Write
- **URL Endpoint**
`/api-arduino/analog`
- **Body**
```typescript
{
pin: string | number,
value: number
}
```
- **Method**
`PATCH`
- **Example Request**
```javascript
var data = {
pin: 'A0',
value: 1023
}
var options = {
method: 'PATCH',
body: JSON.stringify(data),
headers: {
'Content-Type': 'application/json'
},
redirect: 'follow'
}
fetch("http://localhost:3000/api-arduino/analog", options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error('error', err));
```
- **Example Response**
```json
{
"status": 200,
"pin_state": {
"13": 1023
}
}
```
## PIN
### Read PIN Mode
- **URL Endpoint**
`/api-arduino/pin/:p/`
- **URL Params**
| Params | Mark | Type | Required | Description |
|--------|------|------|----------|-------------|
| p | pin | `string` | true | Seleced pin |
- **Method**
`GET`
- **Example Request**
```javascript
fetch("http://localhost:3000/api-arduino/pin/13")
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error('error', err));
```
- **Example Response**
```json
{
"status": 200,
"pins": [
{
"pin": 13,
"mode": "OUTPUT"
}
],
"message": "Pin 13 is OUTPUT"
}
```
### Set PIN Mode
- **URL Endpoint**
`/api-arduino/pin/:p/:m`
- **URL Params**
| Params | Mark | Type | Required | Description |
|--------|------|------|----------|-------------|
| p | pin | `string` | true | Seleced pin |
| m | mode | `'input'`, `'output'`, `'servo'` | true | Pin Mode |
- **Method**
`PATCH`
- **Example Request**
```javascript
fetch("http://localhost:3000/api-arduino/pin/13/output", { method: 'PATCH' })
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error('error', err));
```
- **Example Response**
```json
{
"status": 200,
"pins": [
{
"pin": 13,
"mode": "OUTPUT"
}
],
"message": "Pin 13 setted as OUTPUT"
}
```
## LED
### Read LED state
- **URL Endpoint**
`/api-arduino/led/:p/`
- **URL Params**
| Params | Mark | Type | Required | Description |
|--------|------|------|----------|-------------|
| p | pin | `string` | true | Seleced pin |
- **Method**
`GET`
- **Example Request**
```javascript
fetch("http://localhost:3000/api-arduino/led/13")
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error('error', err));
```
- **Example Response**
```json
{
"status": 200,
"pin_state": {
"13": "HIGH"
},
"message": "Led pin 13 is HIGH"
}
```
### Set LED state
- **URL Endpoint**
`/api-arduino/led/:p/:a`
- **URL Params**
| Params | Mark | Type | Required | Description |
|--------|------|------|----------|-------------|
| p | pin | `string` | true | Seleced pin |
| a | act | `'on'`, `'off'`, `'high'`, `'low'` | true | Action |
- **Method**
`PATCH`
- **Example Request**
```javascript
fetch("http://localhost:3000/api-arduino/led/13/output", { method: 'PATCH' })
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error('error', err));
```
- **Example Response**
```json
{
"status": 200,
"pin_state": {
"13": "HIGH"
},
"message": "Changed pin state 13 to HIGH"
}
```
## RGB LED
### Read RGB state
- **URL Endpoint**
`/api-arduino/rgb-led`
- **Body**
```typescript
{
r: number,
g: number,
b: number
}
```
- **Method**
`POST`
- **Example Request**
```javascript
var data = {
r: 7,
g: 6,
b: 5
}
var options = {
method: 'POST',
body: JSON.stringify(data),
headers: {
'Content-Type': 'application/json'
},
redirect: 'follow'
}
fetch("http://localhost:3000/api-arduino/rgb-led", options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error('error', err));
```
- **Example Response**
```json
{
"status": 200,
"pin_state": {
"7": true,
"6": true,
"5": false
}
}
```
### Set RGB state
- **URL Endpoint**
`/api-arduino/rgb-led`
- **Body**
```typescript
{
r: {
pin: number,
value: boolean
},
g: {
pin: number,
value: boolean
},
b: {
pin: number,
value: boolean
}
}
```
- **Method**
`PATCH`
- **Example Request**
```javascript
var data = {
r: {
pin: 7,
value: true
},
g: {
pin: 6,
value: true
},
b: {
pin: 5,
value: false
}
}
var options = {
method: 'PATCH',
body: JSON.stringify(data),
headers: {
'Content-Type': 'application/json'
},
redirect: 'follow'
}
fetch("http://localhost:3000/api-arduino/rgb-led", options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error('error', err));
```
- **Example Response**
```json
{
"status": 200,
"pin_state": {
"7": true,
"6": true,
"5": false
},
"message": "Success changed pins 7, 6, 5 to state HIGH, HIGH, LOW"
}
```
## Piezo
### Piezo Tone
- **URL Endpoint**
`/api-arduino/piezo/:p/:f`
- **URL Params**
| Params | Mark | Type | Required | Description |
|--------|------|------|----------|-------------|
| p | pin | `string` | true | Seleced pin |
| f | frequency | `number` | true | Frequency |
- **Method**
`PATCH`
- **Example Request**
```javascript
fetch("http://localhost:3000/api-arduino/piezo/6/300", { method: 'PATCH' })
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error('error', err));
```
- **Example Response**
```json
{
"status": 200,
"pin_tone": {
"6": 300
},
"message": "Piezo 6 tone 300"
}
```
### Piezo Note
- **URL Endpoint**
`/api-arduino/piezo/note`
- **Body**
```typescript
{
pin: number,
note: string
}
```
- **Method**
`PATCH`
- **Example Request**
```javascript
var data = {
pin: 6,
note: "B3"
}
var options = {
method: 'PATCH',
body: JSON.stringify(data),
headers: {
'Content-Type': 'application/json'
},
redirect: 'follow'
}
fetch("http://localhost:3000/api-arduino/piezo/note", options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error('error', err));
```
- **Example Response**
```json
{
"status": 200,
"pin_tone": {
"6": 247
},
"pin_note": {
"6": "B3"
},
"message": "Piezo 6 tone note B3"
}
```
### Piezo Play Music
- **URL Endpoint**
`/api-arduino/piezo/music`
- **Body**
```typescript
{
pin: number,
notes: string[],
beats: number,
tempo: number
}
```
Example
```javascript
{
pin: 6,
notes: ["F4", "G4", "F4", "G4"],
beats: 1/4,
tempo: 100
}
```
- **Method**
`PATCH`
- **Example Request**
```javascript
var data = {
pin: 6,
notes: ["E4", "E4", "F4", "G4", "G4", "F4", "E4", "D4", "C4", "C4", "D4", "E4", "D4", "-", "C4", "C4"], // ode to joy notes
beats: 1/2,
tempo: 100
}
var options = {
method: 'PATCH',
body: JSON.stringify(data),
headers: {
'Content-Type': 'application/json'
},
redirect: 'follow'
}
fetch("http://localhost:3000/api-arduino/piezo/music", options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error('error', err));
```
- **Example Response**
```json
{
"status": 200,
"pin_notes": {
"6": [ "E4", "E4", "F4", "G4", "G4", "F4", "E4", "D4", "C4", "C4", "D4", "E4", "D4", "-", "C4", "C4" ]
},
"message": "Piezo 6 play notes E4, E4, F4, G4..."
}
```

BIN
docs/client.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

View File

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

1289
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,28 +1,39 @@
{ {
"name": "lunar-vein-arduino", "name": "lunar-vein-arduino",
"version": "1.0.0", "version": "0.0.6",
"description": "A test to arduino", "description": "A test to arduino",
"main": "index.ts", "main": "index.ts",
"author": "Norman Andrians", "author": "Norman Andrians",
"license": "MIT", "license": "MIT",
"private": true, "private": true,
"scripts": { "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" "test-blink": "nodemon test/blink.js"
}, },
"dependencies": { "dependencies": {
"@serialport/bindings": "^9.2.9", "@serialport/bindings": "^9.2.9",
"@serialport/list": "^12.0.0",
"@types/ip": "^1.1.3",
"chalk": "^4.1.2", "chalk": "^4.1.2",
"cors": "^2.8.5",
"dotenv": "^16.4.5",
"express": "^4.18.3", "express": "^4.18.3",
"firmata": "^2.3.0", "firmata": "^2.3.0",
"ip": "^2.0.1",
"johnny-five": "^2.1.0",
"nodemon": "^3.1.0", "nodemon": "^3.1.0",
"pkg": "^5.8.1",
"serialport": "^12.0.0", "serialport": "^12.0.0",
"socket.io": "^4.7.4" "socket.io": "^4.7.4"
}, },
"devDependencies": { "devDependencies": {
"@types/express": "^4.17.21", "@types/express": "^4.17.21",
"@types/firmata": "^0.19.8", "@types/firmata": "^0.19.8",
"@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"

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 855 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 486 KiB

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 365 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

View File

@ -1,16 +1,14 @@
<!DOCTYPE html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <link rel="icon" type="image/svg+xml" href="/vite.svg" />
<title>Firmata Arduino Client</title> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="style/main.css"> <title>Lunar Vein: Arduino</title>
</head> <script type="module" crossorigin src="/assets/index-CfbleRJs.js"></script>
<body> <link rel="stylesheet" crossorigin href="/assets/index-jsM6q3pf.css">
<section> </head>
<h2>LED</h2> <body>
<button id="toggleLed1">ON/OFF</button> <div id="root"></div>
</section> </body>
<script src="./scripts/main.js"></script> </html>
</body>
</html>

View File

@ -1,17 +0,0 @@
document.getElementById('toggleLed1').addEventListener('mousedown', async () => {
const res = await fetch('/api-arduino/led/13/on', {
method: 'PATCH'
});
const data = await res.json();
console.log(data);
})
document.getElementById('toggleLed1').addEventListener('mouseup', async () => {
const res = await fetch('/api-arduino/led/13/off', {
method: 'PATCH'
});
const data = await res.json();
console.log(data);
})

1
src/client/vite.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -0,0 +1,50 @@
import { Request, Response } from "express";
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 {
board.analogWrite(pin[0] == "A" ? pin.slice(1, pin.length) : pin, value);
return res.status(200).json({
status: 200,
analog_state: {
[pin]: value
}
});
}
catch (err) {
console.error(err);
res.sendStatus(500);
}
}
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;
try {
const value = await Promises.analogRead(board, pin);
return res.status(200).json({
status: 200,
analog_state: {
[pin]: value
}
})
}
catch (err) {
console.error(err);
res.sendStatus(500);
}
}

View File

@ -0,0 +1,56 @@
import { Request, Response } from "express";
import { suBoard } from "../../setup";
interface DigitalState {
pin: number | string,
state: string
}
export function digitalWrite (req: Request, res: Response): Response<string | any> {
const { board } = suBoard;
const { pin, state }: DigitalState = req.body;
let value: number;
try {
switch (state) {
case 'LOW' || 0: value = 0; break;
case 'HIGH' || 1: value = 1; break;
default:
return res.status(400).json({
status: 400,
message: `Invalid state ${state}, read the documentation`
})
}
const val = value == 1 ? "HIGH" : "LOW";
board.digitalWrite(pin, value);
return res.status(200).json({
status: 200,
pin_state: {
[pin]: val
}
});
}
catch (err) {
console.error(err);
res.sendStatus(500);
}
}
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';
return res.status(200).json({
pin_state: {
[pin]: value
}
});
}
catch (err) {
console.error(err);
res.sendStatus(500);
}
}

View File

@ -0,0 +1,87 @@
import { Request, Response } from "express";
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];
return res.status(200).json({
status: 200,
pins: [
{
pin: pin,
mode: mode
}
],
message: `Pin ${pin} is ${mode}`
});
}
export function readPins (req: Request, res: Response): Response<string | any> {
const { board } = suBoard;
const pins: string[] = req.body.p;
const pinModes = [];
pins.forEach((pin) => {
const { mode } = board.pins[pin];
pinModes.push({
pin: pin,
mode: mode
});
})
return res.status(200).json({
status: 200,
pins: [ ...pinModes ]
});
}
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.digital.push(pin);
suBoard.sort();
return res.status(200).json({
status: 200,
pins: [
{
pin: pin,
mode: mode
}
],
message: `Pin ${pin} setted as ${mode}`
});
}
export function setPins (req: Request, res: Response): Response<string | any> {
const { board } = suBoard;
type PinModes = {
pin: string,
mode: sPinModes | string
}
const { pinModes }: { pinModes: PinModes[] } = req.body;
pinModes.forEach((p) => {
const { pin, mode } = p;
board.pinMode(pin, Pin[mode]);
suBoard.PINS.digital.push(pin);
suBoard.sort();
});
return res.status(200).json({
status: 200,
pins: [ ...pinModes ]
});
}

View File

@ -0,0 +1,185 @@
import { Request, Response } from "express";
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);
const pinState: voltage = board.pins[pin].value == 1 ? 'HIGH' : 'LOW';
return res.status(200).json({
status: 200,
pin_state: {
[pin]: pinState
},
message: `Led pin ${pin} is ${pinState}`
});
}
catch (err) {
console.error(err);
return res.status(500).json({
status: 200,
message: "Internal server error"
})
}
}
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);
let state: voltage;
let volt: voltage;
try {
switch (act) {
case 'on' || 'high':
state = 'HIGH';
volt = 1;
break;
case 'off' || 'low':
state = 'LOW';
volt = 0;
break;
default:
console.log(`${req.hostname} | ${pin} | LED: INVALID ACT`);
return res.status(400).json({
status: 400,
message: `Invalid act ${act}`
});
}
board.digitalWrite(pin, volt);
console.log(`${req.hostname} | ${pin} | LED: ${state}`);
res.status(200).json({
status: 200,
pin_state: {
[pin]: state
},
message: `Changed pin state ${pin} to ${state}`
});
}
catch (err) {
console.error(err);
res.status(500).json({
status: 500,
message: "Internal Server Error"
});
}
};
export function readRgbLed (req: Request, res: Response): Response<string | any> {
const r: number = Number.parseInt(req.body.r);
const g: number = Number.parseInt(req.body.g);
const b: number = Number.parseInt(req.body.b);
try {
const rgbPins: number[] = Object.values({ r, g, b });
for (let i = 0; i < rgbPins.length; i++) {
const pin = rgbPins[i];
if (Number.isNaN(pin)) {
return res.status(400).json({
status: 400,
message: `Invalid pin ${pin} param, it should be integer`
});
}
}
const led = new Led.RGB({
pins: {
red: r,
green: g,
blue: b
},
isAnode: true
})
led.red = new Led(r);
led.green = new Led(g);
led.blue = new Led(b);
return res.status(200).json({
status: 200,
pin_state: {
[r]: led.red.isOn,
[g]: led.green.isOn,
[b]: led.blue.isOn
}
});
}
catch (err) {
console.error(err);
return res.status(500).json({
status: 200,
message: "Internal server error"
})
}
}
export function writeRgbLed (req: Request, res: Response): Response<string | any> {
const r: ChannelPins = req.body.r;
const g: ChannelPins = req.body.g;
const b: ChannelPins = req.body.b;
const rgbLeds: ChannelPins[] = Object.values({ r, g, b });
try {
rgbLeds.forEach(led => {
if (Number.isNaN(led.pin)) {
return res.status(400).json({
status: 400,
message: `Invalid pin ${led.pin} param, it should be integer`
});
}
})
const led = new Led.RGB({
pins: {
red: r.pin,
green: g.pin,
blue: b.pin
},
isAnode: true
})
const isHigh: boolean | string = true || 'HIGH' || 'high';
led.red = new Led(r.pin);
led.green = new Led(g.pin);
led.blue = new Led(b.pin);
if (r.value == isHigh) led.red.on(); else led.red.off();
if (g.value == isHigh) led.green.on(); else led.green.off();
if (b.value == isHigh) led.blue.on(); else led.blue.off();
const pins: string = rgbLeds.map(c => c.pin.toString()).join(", ");
const values: string = rgbLeds.map(c => `${c.value}`).join(", ");
return res.status(200).json({
status: 200,
pin_state: {
[r.pin]: led.red.isOn,
[g.pin]: led.green.isOn,
[b.pin]: led.blue.isOn
},
message: `Success changed pins ${pins} to state ${values}`
});
}
catch (err) {
console.error(err);
return res.status(500).json({
status: 500,
message: "Internal Server Error"
});
}
};

View File

@ -0,0 +1,17 @@
import { Request, Response } from "express";
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;
const resistance = await analogRead(board, pin);
return res.status(200).json({
status: 200,
message: `Analog pin ${p} resistance as ${resistance}°`
});
}

View File

@ -0,0 +1,92 @@
import { Request, Response } from "express";
import { Piezo } from "johnny-five";
import { Pitch } from "../../melodies";
export function piezoTone (req: Request, res: Response): Response {
const pin: number = Number.parseInt(req.params.p);
const frequency: number = Number.parseInt(req.params.f);
const piezo: Piezo = new Piezo(pin);
piezo.tone(frequency * 7.3, 100);
return res.status(200).json({
status: 200,
pin_tone: {
[pin]: frequency
},
message: `Piezo ${pin} tone ${frequency}`
});
}
export function piezoNote (req: Request, res: Response): Response {
const { pin, note }: { pin: number, note: string } = req.body;
const notePitch = Pitch[note.toUpperCase()];
if (notePitch == null) {
return res.status(400).json({
status: 400,
message: `Invalid note ${note}`
});
}
const piezo: Piezo = new Piezo(pin);
piezo.play({
song: note.toUpperCase(),
beats: 1/2,
tempo: 100
})
return res.status(200).json({
status: 200,
pin_tone: {
[pin]: notePitch
},
pin_note: {
[pin]: note.toUpperCase()
},
message: `Piezo ${pin} tone note ${note}`
});
}
export function piezoNoTone (req: Request, res: Response): Response {
const { pin }: { pin: number } = req.body;
new Piezo(pin).noTone();
return res.sendStatus(200);
}
interface MusicSheet {
notes: string[];
beats: number;
tempo: 100
}
export function piezoPlayNotes (req: Request, res: Response): Response {
const { pin }: { pin: number } = req.body;
const { notes, beats, tempo }: MusicSheet = req.body;
const piezo: Piezo = new Piezo(pin);
const song: [frequency: string, duration: number][] = notes
.map((note, i): [frequency: string, duration: number] => {
const beat = notes[i+1] && notes[i+1].trim() == "-" ? beats*2 : beats;
return [note, beat]
})
.filter((note) => note[0].trim() != "-");
const notesS: string = notes.length > 4 ?
notes.slice(0, 4).join(", ")+"..." :
notes.join(", ");
piezo.play({ song, tempo });
return res.status(200).json({
status: 200,
pin_notes: {
[pin]: notes
},
message: `Piezo ${pin} play notes ${notesS}`
});
}

View File

@ -0,0 +1,23 @@
import { Request, Response } from "express";
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);
if (Number.isNaN(angle)) {
return res.status(400).json({
status: 400,
message: 'Invalid angle param, it should be integer'
});
}
board.servoWrite(pin, angle);
return res.status(200).json({
status: 200,
message: `Pin ${p} rotate as ${angle}°`
});
}

View File

@ -1,11 +1,11 @@
export type digitalValue = 'ON' | 'OFF'; export type digitalValue = 'ON' | 'OFF';
export type voltage = 'HIGH' | 'LOW'; export type voltage = 'HIGH' | 'LOW' | 'high' | 'low' | 1 | 0;
export type sPinModes = 'INPUT' | 'OUTPUT' | 'ANALOG' | 'PWM' | 'SERVO' |'SHIFT' |'I2C' |'ONEWIRE' |'STEPPER' |'SERIAL' |'PULLUP' |'IGNORE' |'PING_READ' | 'UNKOWN'; export type sPinModes = 'INPUT' | 'OUTPUT' | 'ANALOG' | 'PWM' | 'SERVO';
export interface ChannelPins { export interface ChannelPins {
pin: number, pin: number,
value: digitalValue value: boolean
} }
export interface ColorChannel { export interface ColorChannel {

View File

@ -1,157 +0,0 @@
import { Request, Response } from "express";
import { board } from "../setup";
import { ChannelPins, digitalValue, voltage } from ".";
import { digitalRead } from "../promises";
export async function readLed (req: Request, res: Response) {
const { p } = req.params;
const pin: number = Number.parseInt(p);
if (Number.isNaN(pin)) {
return res.status(400).json({
status: 400,
message: 'Invalid pin param, it should be integer'
});
}
const pinState: digitalValue = board.pins[pin].value == 1 ? 'ON' : 'OFF';
return res.status(200).json({
status: 200,
pin_state: {
[pin]: pinState
}
});
}
export async function writeLed (req: Request, res: Response) {
const { p, a } = req.params;
const act: string = a.toLocaleLowerCase();
const pin: number = Number.parseInt(p);
let state: digitalValue;
let volt: voltage;
try {
if (Number.isNaN(pin)) {
return res.status(400).json({
status: 400,
message: 'Invalid pin param, it should be integer'
});
}
switch (act) {
case 'on':
state = 'ON';
volt = 'HIGH';
console.log(`${req.hostname} | ${pin} | LED: ${state}`);
break;
case 'off':
state = 'OFF';
volt = 'LOW';
console.log(`${req.hostname} | ${pin} | LED: ${state}`);
break;
default:
console.log(`${req.hostname} | ${pin} | LED: INVALID ACT`);
return res.status(400).json({
status: 400,
message: `Invalid act ${act}`
});
}
board.digitalWrite(pin, board[volt]);
res.status(200).json({
status: 200,
message: `Success changed pin ${pin} to state ${state}`
});
}
catch (err) {
console.error(err);
res.status(500).json({
status: 500,
message: "Internal Server Error"
});
}
};
export async function readRgbLed (req: Request, res: Response) {
const r: number = Number.parseInt(req.body.r);
const g: number = Number.parseInt(req.body.b);
const b: number = Number.parseInt(req.body.b);
const rgbPins: number[] = Object.values({ r, g, b });
const pinStates: number[] = [];
for (let i = 0; i < rgbPins.length; i++) {
const pin = rgbPins[i];
if (Number.isNaN(pin)) {
return res.status(400).json({
status: 400,
message: `Invalid pin ${pin} param, it should be integer`
});
}
const state: number = await digitalRead(board, pin);
pinStates.push(state);
}
return res.status(200).json({
status: 200,
pin_state: {
[r]: pinStates[0],
[g]: pinStates[1],
[b]: pinStates[2]
}
});
}
export async function writeRgbLed (req: Request, res: Response) {
const r: ChannelPins = req.body.r;
const g: ChannelPins = req.body.b;
const b: ChannelPins = req.body.b;
const rgbLeds: ChannelPins[] = Object.values({ r, g, b });
try {
rgbLeds.forEach((led, i) => {
if (Number.isNaN(led.pin)) {
return res.status(400).json({
status: 400,
message: `Invalid pin ${led.pin} param, it should be integer`
});
}
switch (led.value) {
case 'ON':
board.analogWrite(led.pin, board.HIGH);
console.log(`${req.hostname} | ${led.pin} | LED: ${led.value}`);
break;
case 'OFF':
board.analogWrite(led.pin, board.LOW);
console.log(`${req.hostname} | ${led.pin} | LED: ${led.value}`);
break;
default:
console.log(`${req.hostname} | ${led.pin} | LED: INVALID VALUE`);
}
})
const pins: string[] = rgbLeds.map(c => `${c.pin}, `);
const values: string[] = rgbLeds.map(c => `${c.value}, `);
res.status(200).json({
status: 200,
message: `Success changed pins ${pins} to state ${values}`
});
}
catch (err) {
console.error(err);
res.status(500).json({
status: 500,
message: "Internal Server Error"
});
}
};

View File

@ -1,46 +0,0 @@
import { Request, Response } from "express";
import { board, suBoard } from "../setup";
import { sPinModes } from ".";
export function readPin (req: Request, res: Response) {
const pin: number = Number.parseInt(req.params.p);
if (Number.isNaN(pin)) {
return res.status(400).json({
status: 400,
message: 'Invalid pin param, it should be integer'
});
}
const { mode } = board.pins[pin];
return res.status(200).json({
status: 200,
state: {
used: true
},
message: `Pin ${pin} is ${mode}`
});
}
export function setPin (req: Request, res: Response) {
const pin: number = Number.parseInt(req.params.p);
const mode: sPinModes | string = req.params.m.toUpperCase();
if (Number.isNaN(pin)) {
return res.status(400).json({
status: 400,
message: 'Invalid pin param, it should be integer'
});
}
board.pinMode(pin, board.MODES[mode]);
suBoard.PINS.pwm.push(pin);
suBoard.sort();
return res.status(200).json({
status: 200,
message: `Pin ${pin} setted as ${mode}`
});
}

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

@ -0,0 +1,57 @@
import { Socket } from "socket.io";
import { Pin, Sensor } from "johnny-five";
import { suBoard } from "../setup";
const { board } = suBoard;
export default (socket: Socket) => {
console.log(`${socket.id} | ${socket.client.request.headers.host} | Joined`);
socket.on("servo", (p: string, ang: string, cb?: (msg?: string) => void) => {
const pin: number = Number.parseInt(p);
const angle: number = Number.parseInt(ang);
board.pinMode(pin, Pin.SERVO);
board.servoWrite(pin, angle);
if (cb) cb(`Set servo pin ${p} to ${angle} degrees`);
console.log(socket.id, pin, angle);
})
socket.on("set-photoresistor", (pin: string, cb?: (msg?: string) => void) => {
const room = `resistor-${pin}`;
if (!socket.rooms.has(room)) {
socket.join(room);
const sensor = new Sensor({
pin: pin,
board: board,
type: "analog",
freq: 250
});
sensor.on("change", () => {
socket.to(room).emit("photoresistor", sensor.value);
});
console.log(`New room: ${room}`);
console.log(`${socket.id} joined room ${room}`);
if (cb) cb(`Set pin resistor to pin ${pin}`);
}
else {
if (cb) cb(`Resistor pin ${pin} is already used, try to lisen to "photoresistor" room: ${room}`);
}
})
socket.on("join-photoresistor", (pin) => {
const room = `resistor-${pin}`;
if (!socket.rooms.has(room)) {
socket.join(room);
console.log(socket.rooms);
console.log(`${socket.id} Joined room ${room}`);
}
})
}

View File

@ -1,35 +1,3 @@
import http from 'node:http'; import main from "./app";
import express, { Request, Response } from 'express';
import { Server } from 'socket.io';
import chalk from 'chalk';
import { board, suBoard, comport } from './setup'; main();
import { isBoardConnected } from './middleware/connection';
import view from './routes/view';
import api from './routes/api';
const app = express();
const server = http.createServer(app);
const io = new Server(server); // I have no experience at WebSocket, so.. forgive me :)
const host = 'localhost';
const port = 3000;
app.use(express.json());
app.use(express.static('client'));
app.use('/', view);
app.use('/api-arduino', isBoardConnected, api);
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(chalk.yellow(`Connecting to Board`));
board.on('ready', () => {
console.log(chalk.green(`Board at port ${comport} Connected!! (^o^)`));
suBoard.connected = true;
})
});

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
}

View File

@ -1,6 +1,8 @@
import { NextFunction, Request, Response } from "express"; import { NextFunction, Request, Response } from "express";
import { suBoard } from "../setup"; import { suBoard } from "../setup";
const { board } = suBoard;
export function isBoardConnected (req: Request, res: Response, next: NextFunction) { export function isBoardConnected (req: Request, res: Response, next: NextFunction) {
if (suBoard.connected) { if (suBoard.connected) {
next(); next();

View File

@ -1,7 +1,7 @@
import { NextFunction, Request, Response } from "express"; import { NextFunction, Request, Response } from "express";
import { suBoard } from "../setup"; import { suBoard } from "../setup";
export function isPinBeingUsed(req: Request, res: Response, next: NextFunction) { export function isPinNumeric (req: Request, res: Response, next: NextFunction) {
const pin: number = Number.parseInt(req.params.p); const pin: number = Number.parseInt(req.params.p);
if (Number.isNaN(pin)) { if (Number.isNaN(pin)) {
@ -11,7 +11,20 @@ export function isPinBeingUsed(req: Request, res: Response, next: NextFunction)
}); });
} }
if (suBoard.PINS.pwm.indexOf(pin) != -1) { next();
}
export function isPinBeingUsed (req: Request, res: Response, next: NextFunction) {
const pin: number = Number.parseInt(req.params.p);
if (Number.isNaN(pin)) {
return res.status(400).json({
status: 400,
message: 'Invalid pin param, it should be integer'
});
}
if (suBoard.PINS.digital.indexOf(pin) != -1) {
return res.status(400).json({ return res.status(400).json({
status: 400, status: 400,
message: `Pin ${pin}, is used` message: `Pin ${pin}, is used`

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

@ -0,0 +1,23 @@
import readline from 'node:readline/promises';
import { ReadPorts } from "./read";
export async function selectPort (): Promise<string | any> {
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.path} - ${port.name}${port.serialNumber ? " - Recomended" : ""}`);
})
const selectedInput: string = await line.question("Enter number: ");
const selectedNumber: number = Number.parseInt(selectedInput);
const selectedPort: { name?: string, path?: string } = ports[selectedNumber-1];
line.close();
return selectedPort.path;
}

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

@ -0,0 +1,8 @@
import { ReadPorts } from "./read";
ReadPorts().then((ports) => {
console.log(`${ports.length} Ports available`);
ports.forEach((port, i) => {
console.log(`${i+1}. ${port.path} - ${port.name}`);
})
})

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

@ -0,0 +1,14 @@
import * as SerialPort from 'serialport';
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 => {
return {
name: port['friendlyName'],
path: port.path,
serialNumber: port.serialNumber
}
})
}

View File

@ -1,6 +1,6 @@
import Board, { PIN_MODE } from "firmata"; import { Board } from "johnny-five";
export function digitalRead (board: Board, pin: number): Promise<PIN_MODE | number> { export function digitalRead (board: Board, pin: number): Promise<number> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
board.digitalRead(pin, (val) => { board.digitalRead(pin, (val) => {
resolve(val); resolve(val);
@ -11,7 +11,7 @@ export function digitalRead (board: Board, pin: number): Promise<PIN_MODE | numb
}); });
} }
export function analogRead (board: Board, pin: number): Promise<number> { export function analogRead (board: Board, pin: string): Promise<number> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
board.analogRead(pin, (val) => { board.analogRead(pin, (val) => {
resolve(val); resolve(val);

View File

@ -1,21 +1,55 @@
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 { readPin, setPin } from "../controller/pin";
const router = Router(); import { readPin, readPins, setPin, setPins } from "../controller/basic/pin";
import { digitalRead, digitalWrite } from "../controller/basic/digital";
import { analogRead, analogWrite } from "../controller/basic/analog";
router.get('/hello', (req, res: Response) => { import { readLed, readRgbLed, writeLed, writeRgbLed } from "../controller/components/led";
res.status(200).send("Hello"); import { piezoNoTone, piezoNote, piezoPlayNotes, piezoTone } from "../controller/components/piezo";
import { rotateServo } from "../controller/components/servo";
import { readResistor } from "../controller/components/photoresistor";
import { isPinNumeric } from "../middleware/pin";
const router: Router = Router();
router.get('/hello', (req, res: Response): Response<string> => {
return res.status(200).send("Hello");
}) })
// PinMode
router.get('/pin/:p', readPin); router.get('/pin/:p', readPin);
router.get('/pins', readPins);
router.patch('/pin/:p/:m', setPin); router.patch('/pin/:p/:m', setPin);
router.patch('/pins', setPins);
router.get('/led/:p', readLed); // Digital read/write
router.patch('/led/:p/:a', writeLed); router.get('/digital/:pin', digitalRead);
router.patch('/digital', digitalWrite);
router.get('/rgb-led', readRgbLed); // Analog read/write
router.get('/analog/:pin', analogRead);
router.patch('/analog', analogWrite);
// LED
router.get('/led/:p', isPinNumeric, readLed);
router.patch('/led/:p/:a', isPinNumeric, writeLed);
// RGB LED
router.post('/rgb-led', readRgbLed);
router.patch('/rgb-led/', writeRgbLed); router.patch('/rgb-led/', writeRgbLed);
// Piezo
router.patch('/piezo/:p/:f', isPinNumeric, piezoTone);
router.patch('/piezo/note', piezoNote);
router.patch('/piezo/music', piezoPlayNotes);
router.patch('/piezo/stop', piezoNoTone);
// for real-time communication is deprecated and not recommended
// use other protocol like websocket instead, we're using socket.io
router.patch('/servo/:p/:m', rotateServo);
router.get('/photoresistor/:p', readResistor);
export default router; export default router;

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,7 +1,14 @@
import Firmata from 'firmata';
import { SuBoard } from './support'; import { SuBoard } from './support';
import { Board, BoardOption } from 'johnny-five';
import { config } from 'dotenv';
export const comport: string = '/dev/ttyUSB1'; config();
export const board: Firmata = new Firmata(comport);
export const suBoard: SuBoard = new SuBoard(); export const comport: string = process.env.SERIAL_PORT || '/dev/ttyUSB0';
export const suBoard: SuBoard = new SuBoard();
export const defaultBoardOption: BoardOption = {
port: comport,
debug: false,
repl: false,
};

View File

@ -1,20 +1,25 @@
import { Board } from "johnny-five";
export interface SupportBoard { export interface SupportBoard {
port: string | null,
connected: boolean, connected: boolean,
PINS: { PINS: {
pwm: number[], digital: number[],
analog: number[], analog: number[],
} }
sort: () => void sort: () => void
} }
export class SuBoard implements SupportBoard { export class SuBoard implements SupportBoard {
public board: Board | null;
public port = null;
public connected = false; public connected = false;
public PINS = { public PINS = {
pwm: [], digital: [],
analog: [] analog: []
}; };
public sort() { 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); 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';
}

View File

@ -1,17 +0,0 @@
import Firmata, { PIN_MODE, PIN_STATE } from 'firmata';
const board = new Firmata('/dev/ttyUSB2');
board.on('ready', async () => {
// board.analogWrite(0, 255);
// board.analogWrite(1, 255);
// board.analogWrite(2, 255);
board.analogRead(0, (val) => {
console.log(val);
})
while (true) {
await new Promise (resolve => setTimeout(resolve, 90));
}
})

View File

@ -1,17 +0,0 @@
const Firmata = require('firmata');
const board = new Firmata('/dev/ttyUSB0');
board.on('ready', async () => {
const led = 12;
board.pinMode(led, board.MODES.OUTPUT);
while (true) {
board.digitalWrite(led, board.HIGH);
console.log("ON");
await new Promise(resolve => setTimeout(resolve, 300));
board.digitalWrite(led, board.LOW);
console.log("OFF");
await new Promise(resolve => setTimeout(resolve, 300));
}
});

13
test/serial/blink.ts Normal file
View File

@ -0,0 +1,13 @@
import Firmata from 'firmata';
import { Board, Pin, PinMode } from "johnny-five";
const board = new Board({
port: '/dev/ttyUSB0',
repl: false
});
board.on('ready', () => {
board.pinMode(13, Pin.OUTPUT);
board.digitalWrite(13, 1);
})

11
test/serial/checkBoard.ts Normal file
View File

@ -0,0 +1,11 @@
import { Board } from "johnny-five";
const board: Board = new Board({
port: '/dev/ttyUSB0',
debug: false,
repl: false
});
setInterval(() => {
console.log(board.isReady);
}, 400);

27
test/serial/events.ts Normal file
View File

@ -0,0 +1,27 @@
import { Board } from "johnny-five";
const board: Board = new Board({
port: '/dev/ttyUSB1',
debug: false,
repl: false
});
board.on('ready', () => {
console.log("Board is ready");
})
board.on('close', () => {
console.log("Board is close");
})
board.on('connect', () => {
console.log("Board is connect");
})
board.on('exit', () => {
console.log("Board is exit");
})
board.on('fail', () => {
console.log("Board is fail");
})

View File

@ -1,18 +0,0 @@
import Firmata from 'firmata';
const board = new Firmata('/dev/ttyUSB2');
board.on('ready', async () => {
board.analogRead(2, (val) => {
console.log(board.analogPins[2]);
const output = board.pins.map((p, i) => {
return `Pin ${i}: ${p.value}`
}).join(", ");
console.log(output);
})
while (true) {
await new Promise(resolve => setTimeout(resolve, 200));
}
})

View File

@ -0,0 +1,15 @@
import { Board, Pin } from "johnny-five";
const board: Board = new Board({
port: '/dev/ttyUSB0',
debug: false,
repl: false
});
board.on("ready", async () => {
board.pinMode(14, Pin.INPUT);
board.analogRead(0, (val) => {
console.log(val);
})
});

24
test/serial/piezo.ts Normal file
View File

@ -0,0 +1,24 @@
import { Board, Piezo } from "johnny-five";
const board = new Board({
port: '/dev/ttyUSB0',
repl: false
});
board.on('ready', () => {
const piezo = new Piezo(11);
let ins = 0;
board.loop(500, async () => {
piezo.play({
song: "C4",
beats: 1/2,
tempo: 100
})
await new Promise(resolve => setTimeout(resolve, 100));
piezo.note("C4", 100);
await new Promise(resolve => setTimeout(resolve, 100));
piezo.tone(262 * 7.3, 100);
})
})

View File

@ -1,20 +0,0 @@
const Firmata = require('firmata');
const board = new Firmata('/dev/ttyUSB0');
board.on('ready', async () => {
const led = 12;
board.pinMode(led, board.MODES.OUTPUT);
let state = board.LOW;
board.digitalWrite(led, board.HIGH);
state = board.pins[led].value;
while (true) {
console.log(state);
await new Promise(resolve => setTimeout(resolve, 1000));
}
});

View File

@ -1,11 +0,0 @@
import Firmata from 'firmata';
const board = new Firmata('/dev/ttyUSB1');
board.on('ready', () => {
board.pinMode(0, Firmata.PIN_MODE.INPUT);
board.analogRead(0, (val) => {
console.log(val);
})
})

25
test/serial/rgbLed.js Normal file
View File

@ -0,0 +1,25 @@
const { Board, Led } = require("johnny-five");
const board = new Board({
port: '/dev/ttyUSB0',
repl: false,
debug: false
})
board.on('ready', () => {
const led = new Led.RGB([7, 6, 5]);
let ins = 252;
board.loop(10, () => {
led.color("#0000FF");
led.intensity(ins);
ins--;
if (ins < 1) {
ins = 255;
}
})
board.analogRead(0, (val) => {
console.log(val);
})
});

28
test/serial/rgbLed.ts Normal file
View File

@ -0,0 +1,28 @@
import { Board, Led } from "johnny-five";
const board = new Board({
port: '/dev/ttyUSB0',
repl: false,
debug: false
})
board.on('ready', () => {
const led = new Led.RGB({
pins: {
red: 7,
green: 6,
blue: 5,
},
isAnode: true
})
let ins = 100;
board.loop(10, () => {
led.intensity(ins);
ins--;
if (ins < 1) {
ins = 100;
}
})
});

View File

@ -0,0 +1,19 @@
import { Board, Pin, PinMode } from "johnny-five";
const board: Board = new Board({
port: '/dev/ttyUSB0',
debug: true,
repl: false
});
board.on("ready", async () => {
board.pinMode(9, Pin.SERVO);
board.servoWrite(9, 0);
while (true) {
board.servoWrite(9, 180);
await new Promise(resolve => setTimeout(resolve, 1000));
board.servoWrite(9, 0);
await new Promise(resolve => setTimeout(resolve, 1000));
}
});

19
test/websocket/common.ts Normal file
View File

@ -0,0 +1,19 @@
import http from 'node:http';
import express from 'express';
import { Server } from "socket.io";
const app = express();
const server = http.createServer(app);
const io = new Server(server);
io.on("connection", (socket) => {
console.log("Connected");
socket.on("message", (message) => {
console.log(message);
})
})
server.listen(3001, "localhost", () => {
console.log("Common server is running");
})

View File

@ -3,8 +3,9 @@
"module": "NodeNext", "module": "NodeNext",
"moduleResolution": "NodeNext", "moduleResolution": "NodeNext",
"target": "ES2020", "target": "ES2020",
"sourceMap": false, "sourceMap": true,
"outDir": "dist" "outDir": "dist"
}, },
"include": ["src/**/*", "test/**/*"] "include": ["src/**/*"],
"exclude": ["test/**/*"]
} }