Compare commits
No commits in common. "client" and "main" have entirely different histories.
@ -1,18 +0,0 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
env: { browser: true, es2020: true },
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:react-hooks/recommended',
|
||||
],
|
||||
ignorePatterns: ['dist', '.eslintrc.cjs'],
|
||||
parser: '@typescript-eslint/parser',
|
||||
plugins: ['react-refresh'],
|
||||
rules: {
|
||||
'react-refresh/only-export-components': [
|
||||
'warn',
|
||||
{ allowConstantExport: true },
|
||||
],
|
||||
},
|
||||
}
|
137
.gitignore
vendored
@ -1,25 +1,132 @@
|
||||
example-client/
|
||||
build
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
node_modules
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Snowpack dependency directory (https://snowpack.dev/)
|
||||
web_modules/
|
||||
|
||||
# TypeScript cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional stylelint cache
|
||||
.stylelintcache
|
||||
|
||||
# Microbundle cache
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variable files
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.env.local
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
.parcel-cache
|
||||
|
||||
# Next.js build output
|
||||
.next
|
||||
out
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
.nuxt
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
yarn.lock
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
# Gatsby files
|
||||
.cache/
|
||||
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||
# public
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# vuepress v2.x temp and cache directory
|
||||
.temp
|
||||
.cache
|
||||
|
||||
# Docusaurus cache and generated files
|
||||
.docusaurus
|
||||
|
||||
# Serverless directories
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
.dynamodb/
|
||||
|
||||
# TernJS port file
|
||||
.tern-port
|
||||
|
||||
# Stores VSCode versions used for testing VSCode extensions
|
||||
.vscode-test
|
||||
|
||||
# yarn v2
|
||||
.yarn/cache
|
||||
.yarn/unplugged
|
||||
.yarn/build-state.yml
|
||||
.yarn/install-state.gz
|
||||
.pnp.*
|
21
LICENSE
Normal 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.
|
773
README.md
@ -1,13 +1,772 @@
|
||||
# Lunar Vein: Arduino Client
|
||||
# Lunar Vein: Arduino
|
||||
|
||||
Client version of the [lunar vein arduino](https://github.com/norman-andrians/lunar-vein-arduino) project, This is the source code of the serial communication API testing example, built with React, Typescript and Vite. The example web client code in the [main branch](https://github.com/norman-andrians/lunar-vein-arduino/tree/main/src/client) is only a **build version** of this code.
|
||||
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 (づ ̄ ³ ̄)づ
|
||||
|
||||

|
||||
|
||||
<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/)
|
||||
|
||||

|
||||
|
||||
#### 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
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## React + TypeScript + Vite
|
||||
|
||||
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
|
||||
|
||||
Currently, two official plugins are available:
|
||||
## PIN
|
||||
|
||||
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
|
||||
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
|
||||
### 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
After Width: | Height: | Size: 114 KiB |
4
nodemon.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"watch": ["src"],
|
||||
"ext": ".ts,.js"
|
||||
}
|
3972
package-lock.json
generated
Normal file
57
package.json
@ -1,34 +1,41 @@
|
||||
{
|
||||
"name": "lunar-vein-arduino-client",
|
||||
"name": "lunar-vein-arduino",
|
||||
"version": "0.0.6",
|
||||
"description": "A test to arduino",
|
||||
"main": "index.ts",
|
||||
"author": "Norman Andrians",
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc && vite build",
|
||||
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
||||
"preview": "vite preview"
|
||||
"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": {
|
||||
"axios": "^1.6.7",
|
||||
"bootstrap-icons": "^1.11.3",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"socket.io-client": "^4.7.4"
|
||||
"@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",
|
||||
"pkg": "^5.8.1",
|
||||
"serialport": "^12.0.0",
|
||||
"socket.io": "^4.7.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.2.56",
|
||||
"@types/react-dom": "^18.2.19",
|
||||
"@typescript-eslint/eslint-plugin": "^7.0.2",
|
||||
"@typescript-eslint/parser": "^7.0.2",
|
||||
"@vitejs/plugin-react": "^4.2.1",
|
||||
"autoprefixer": "^10.4.18",
|
||||
"eslint": "^8.56.0",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.5",
|
||||
"postcss": "^8.4.35",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"typescript": "^5.2.2",
|
||||
"vite": "^5.1.4"
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/firmata": "^0.19.8",
|
||||
"@types/johnny-five": "^2.1.9",
|
||||
"@types/nodemon": "^1.19.6",
|
||||
"@types/serialport": "^8.0.5",
|
||||
"@types/socket.io": "^3.0.2",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.3.3"
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +0,0 @@
|
||||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
13
src/App.tsx
@ -1,13 +0,0 @@
|
||||
import { BoardControllerProvider } from "./contexts/BoardController";
|
||||
import MainPage from "./pages/MainPage";
|
||||
import "bootstrap-icons/font/bootstrap-icons.min.css";
|
||||
|
||||
function App () {
|
||||
return (
|
||||
<BoardControllerProvider>
|
||||
<MainPage />
|
||||
</BoardControllerProvider>
|
||||
)
|
||||
}
|
||||
|
||||
export default App;
|
40
src/app.ts
Normal 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();
|
||||
}
|
@ -1 +0,0 @@
|
||||
<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="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>
|
Before Width: | Height: | Size: 4.0 KiB |
Before Width: | Height: | Size: 855 KiB After Width: | Height: | Size: 855 KiB |
Before Width: | Height: | Size: 4.8 MiB After Width: | Height: | Size: 4.8 MiB |
Before Width: | Height: | Size: 486 KiB After Width: | Height: | Size: 486 KiB |
BIN
src/client/assets/bootstrap-icons-BOrJxbIo.woff
Normal file
BIN
src/client/assets/bootstrap-icons-BtvjY1KL.woff2
Normal file
45
src/client/assets/index-CfbleRJs.js
Normal file
5
src/client/assets/index-jsM6q3pf.css
Normal file
Before Width: | Height: | Size: 365 KiB After Width: | Height: | Size: 365 KiB |
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 4.8 KiB |
@ -5,9 +5,10 @@
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Lunar Vein: Arduino</title>
|
||||
<script type="module" crossorigin src="/assets/index-CfbleRJs.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-jsM6q3pf.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
@ -1,35 +0,0 @@
|
||||
import { ReactNode, Ref } from "react";
|
||||
|
||||
interface ControlSectionProps {
|
||||
title: string;
|
||||
description?: string | ReactNode;
|
||||
id?: string;
|
||||
refto?: Ref<HTMLDivElement>
|
||||
stack: ReactNode;
|
||||
colSpan?: number;
|
||||
stackType?: 'grid' | 'flex';
|
||||
}
|
||||
|
||||
export default function ControlSection ({
|
||||
title,
|
||||
description,
|
||||
id,
|
||||
refto,
|
||||
stack
|
||||
}: ControlSectionProps) {
|
||||
return (
|
||||
<div className="container py-16" id={id} ref={refto}>
|
||||
<div className="container-grid items-center relative">
|
||||
<div className={`col-span-6`}>
|
||||
<h2 className="text-4xl font-poppins font-bold leading-normal mb-4">
|
||||
{title}
|
||||
</h2>
|
||||
{description && <p className="mb-8">{description}</p>}
|
||||
<div className={`flex flex-wrap gap-6`}>
|
||||
{stack}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
import MadeBy from "./info/MadeBy";
|
||||
import Social from "./info/Social";
|
||||
|
||||
export default function Footer () {
|
||||
return (
|
||||
<footer className="bg-black bg-opacity-80 border-t border-border absolute bottom-0 left-0 w-full">
|
||||
<div className="container py-12 text-center">
|
||||
<MadeBy className="font-poppins" />
|
||||
<div className="mt-8 flex flex-row justify-center gap-3">
|
||||
<Social
|
||||
name="X/Twitter"
|
||||
link="https://twitter.com/NormanAndrians"
|
||||
icon={<i className="bi bi-twitter-x text-3xl"></i>}
|
||||
/>
|
||||
<Social
|
||||
name="Instagram"
|
||||
link="https://www.instagram.com/normanandrians25/"
|
||||
icon={<i className="bi bi-instagram text-3xl"></i>}
|
||||
/>
|
||||
<Social
|
||||
name="Facebook"
|
||||
link="https://www.facebook.com/profile.php?id=100076314820736"
|
||||
icon={<i className="bi bi-facebook text-3xl"></i>}
|
||||
/>
|
||||
<Social
|
||||
name="Github"
|
||||
link="https://github.com/norman-andrians"
|
||||
icon={<i className="bi bi-github text-3xl"></i>}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
)
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
import { ReactNode } from "react";
|
||||
import BackroundImg from "../assets/img/background.png";
|
||||
|
||||
export default function MainBody({ children }: { children?: ReactNode }) {
|
||||
return (
|
||||
<div
|
||||
className="bg-background text-white bg-repeat-y bg-cover relative"
|
||||
style={{
|
||||
backgroundImage: `url(${BackroundImg})`
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
@ -1,75 +0,0 @@
|
||||
import { CSSProperties, MouseEventHandler, ReactNode } from "react";
|
||||
|
||||
interface ButtonProps {
|
||||
className?: string;
|
||||
style?: CSSProperties,
|
||||
onClick?: MouseEventHandler
|
||||
children?: ReactNode
|
||||
}
|
||||
|
||||
function Primary ({
|
||||
className,
|
||||
style,
|
||||
onClick,
|
||||
children
|
||||
}: ButtonProps) {
|
||||
return (
|
||||
<button
|
||||
className={`btn bg-primary bg-opacity-100 hover:bg-opacity-80 ${className}`}
|
||||
style={style}
|
||||
onClick={onClick}
|
||||
>{children}</button>
|
||||
)
|
||||
}
|
||||
|
||||
function Secondary ({
|
||||
className,
|
||||
style,
|
||||
onClick,
|
||||
children
|
||||
}: ButtonProps) {
|
||||
return (
|
||||
<button
|
||||
className={`btn bg-finn border hover:bg-secondary border-border ${className}`}
|
||||
style={style}
|
||||
onClick={onClick}
|
||||
>{children}</button>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
function Danger ({
|
||||
className,
|
||||
style,
|
||||
onClick,
|
||||
children
|
||||
}: ButtonProps) {
|
||||
return (
|
||||
<button
|
||||
className={`btn bg-transparent border text-danger hover:bg-danger hover:text-white border-danger ${className}`}
|
||||
style={style}
|
||||
onClick={onClick}
|
||||
>{children}</button>
|
||||
)
|
||||
}
|
||||
|
||||
function Button ({
|
||||
className,
|
||||
style,
|
||||
onClick,
|
||||
children
|
||||
}: ButtonProps) {
|
||||
return (
|
||||
<button
|
||||
className={`btn bg-finn border hover:bg-secondary border-border ${className}`}
|
||||
style={style}
|
||||
onClick={onClick}
|
||||
>{children}</button>
|
||||
)
|
||||
}
|
||||
|
||||
Button.Primary = Primary;
|
||||
Button.Secondary = Secondary;
|
||||
Button.Danger = Danger;
|
||||
|
||||
export default Button;
|
@ -1,83 +0,0 @@
|
||||
import { FocusEvent, useState } from "react";
|
||||
|
||||
type EvoDropDownItem = {
|
||||
name: string,
|
||||
value: any
|
||||
}
|
||||
|
||||
interface EvoDropDownProps {
|
||||
className?: string;
|
||||
name?: string;
|
||||
items: EvoDropDownItem[];
|
||||
initItem?: EvoDropDownItem;
|
||||
onValueChange?: (item: EvoDropDownItem) => void;
|
||||
onBlur?: (e: FocusEvent) => void
|
||||
}
|
||||
|
||||
interface MenuProps extends EvoDropDownProps {
|
||||
appear: boolean;
|
||||
}
|
||||
|
||||
function Menu ({ className, items, initItem, appear, onValueChange, onBlur }: MenuProps) {
|
||||
const [currentItem, setItem] = useState<EvoDropDownItem>(initItem || items[0]);
|
||||
|
||||
return (
|
||||
<div className={`absolute p-2 flex flex-col w-full bg-black border mt-3 border-border rounded-lg ${appear ? "block" : "hidden"} ${className}`} onBlur={onBlur}>
|
||||
{items.map((item) => (
|
||||
<button
|
||||
className="py-2 px-3 flex justify-between items-center bg-transparent bg-opacity-100 hover:bg-indigo-300 hover:bg-opacity-20 rounded-md"
|
||||
onClick={() => {
|
||||
setItem(item);
|
||||
if (onValueChange) onValueChange(item)}
|
||||
}
|
||||
>
|
||||
<div className="text-left text-sm">
|
||||
{item.name}
|
||||
</div>
|
||||
{currentItem.value == item.value && <div className="rounded-full bg-white w-2 h-2"></div>}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function EvoDropDown ({ className, name, items, initItem, onValueChange }: EvoDropDownProps) {
|
||||
const [appear, setAppear] = useState<boolean>(false);
|
||||
const [currentItem, setItem] = useState<EvoDropDownItem>(initItem || items[0]);
|
||||
|
||||
const handleClick = () => {
|
||||
setAppear(!appear);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<button className={`flex items-center justify-between cursor-pointer bg-finn border border-border rounded-lg ${className}`} onClick={handleClick}>
|
||||
<div className="w-fit p-2 text-sm ps-4">{name}</div>
|
||||
<div className="w-1/2 h-full p-2 text-right pe-4 text-sm font-roboto-mono bg-transparent">
|
||||
{currentItem.name}
|
||||
</div>
|
||||
</button>
|
||||
<div className={`absolute p-2 flex flex-col w-full bg-black border mt-3 border-border rounded-lg ${appear ? "block" : "hidden"}`}>
|
||||
{items.map((item) => (
|
||||
<button
|
||||
className="py-2 px-3 flex justify-between items-center bg-transparent bg-opacity-100 hover:bg-indigo-300 hover:bg-opacity-20 rounded-md"
|
||||
onClick={() => {
|
||||
setAppear(false);
|
||||
setItem(item);
|
||||
if (onValueChange) onValueChange(item)}
|
||||
}
|
||||
>
|
||||
<div className="text-left text-sm">
|
||||
{item.name}
|
||||
</div>
|
||||
{currentItem.value == item.value && <div className="rounded-full bg-white w-2 h-2"></div>}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
EvoDropDown.Menu = Menu;
|
||||
|
||||
export default EvoDropDown;
|
@ -1,38 +0,0 @@
|
||||
import { ChangeEventHandler, HTMLInputTypeAttribute, useRef } from "react";
|
||||
|
||||
interface EvoInputProps {
|
||||
className?: string;
|
||||
name: string;
|
||||
value: string;
|
||||
type?: HTMLInputTypeAttribute
|
||||
onChange?: ChangeEventHandler<HTMLInputElement>
|
||||
}
|
||||
|
||||
export default function EvoInput ({
|
||||
className,
|
||||
name,
|
||||
value,
|
||||
type,
|
||||
onChange
|
||||
}: EvoInputProps) {
|
||||
const labelRef = useRef<HTMLInputElement | null>(null);
|
||||
|
||||
const handleClick = () => {
|
||||
labelRef.current?.focus();
|
||||
}
|
||||
|
||||
return (
|
||||
<label className={`flex items-center justify-between cursor-pointer bg-finn border border-border rounded-lg ${className}`} onClick={handleClick}>
|
||||
<div className="w-fit p-2 text-sm ps-4">{name}</div>
|
||||
<input
|
||||
className="w-1/2 h-full p-2 text-right pe-4 text-sm font-roboto-mono bg-transparent"
|
||||
type={type}
|
||||
name={name}
|
||||
id=""
|
||||
value={value}
|
||||
ref={labelRef}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</label>
|
||||
)
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
import { ChangeEventHandler, HTMLInputTypeAttribute } from "react";
|
||||
|
||||
interface InputProps {
|
||||
className?: string;
|
||||
name?: string;
|
||||
placeholder?: string;
|
||||
value?: string;
|
||||
type?: HTMLInputTypeAttribute
|
||||
onChange?: ChangeEventHandler<HTMLInputElement>
|
||||
}
|
||||
|
||||
export default function Input ({
|
||||
className,
|
||||
name,
|
||||
placeholder,
|
||||
value,
|
||||
type,
|
||||
onChange
|
||||
}: InputProps) {
|
||||
return (
|
||||
<input
|
||||
className={`px-4 py-2 text-left text-sm font-roboto-mono bg-finn border border-border rounded-lg ${className}`}
|
||||
placeholder={placeholder}
|
||||
type={type}
|
||||
name={name}
|
||||
id=""
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
/>
|
||||
)
|
||||
}
|
@ -1,92 +0,0 @@
|
||||
import { ChangeEventHandler, FormEventHandler, MouseEventHandler } from "react";
|
||||
import Switch from "./Switch";
|
||||
|
||||
interface PinBoxProps {
|
||||
className?: string;
|
||||
value: number | string;
|
||||
onValueChange?: ChangeEventHandler<HTMLInputElement | null>;
|
||||
onDelete?: FormEventHandler<HTMLButtonElement | null>;
|
||||
minusBtn?: boolean;
|
||||
}
|
||||
|
||||
interface ToggleProps extends PinBoxProps {
|
||||
state: boolean;
|
||||
onStateChange?: FormEventHandler<HTMLButtonElement | null>;
|
||||
}
|
||||
|
||||
function PinBox ({
|
||||
className,
|
||||
value,
|
||||
onValueChange,
|
||||
onDelete,
|
||||
minusBtn
|
||||
}: PinBoxProps) {
|
||||
return (
|
||||
<div className={`flex flex-col gap-3 ${className}`}>
|
||||
{minusBtn && <button
|
||||
className="border bg-transparent ms-auto hover:bg-secondary border-border rounded px-2"
|
||||
onClick={onDelete}
|
||||
|
||||
>
|
||||
<i className="bi bi-dash"></i>
|
||||
</button>}
|
||||
<div className="bg-secondary border border-border rounded-lg flex flex-col h-36 animate-size-in relative">
|
||||
<div className="font-roboto-mono py-4 text-center">
|
||||
Pin
|
||||
</div>
|
||||
<input
|
||||
className="flex-grow bg-transparent pb-6 border-none text-center text-3xl font-roboto-mono"
|
||||
type="number"
|
||||
name="pinbox"
|
||||
id=""
|
||||
value={value}
|
||||
onChange={onValueChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function Toggle ({
|
||||
className,
|
||||
value,
|
||||
minusBtn,
|
||||
onDelete,
|
||||
state,
|
||||
onStateChange,
|
||||
onValueChange
|
||||
}: ToggleProps) {
|
||||
return (
|
||||
<div className={`flex flex-col gap-6 ${className}`}>
|
||||
<PinBox
|
||||
value={value}
|
||||
minusBtn={minusBtn}
|
||||
onDelete={onDelete}
|
||||
onValueChange={onValueChange}
|
||||
/>
|
||||
<Switch
|
||||
state={state}
|
||||
onChange={onStateChange}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function Add ({
|
||||
className,
|
||||
onClick
|
||||
}: {
|
||||
className?: string,
|
||||
onClick?: MouseEventHandler<HTMLButtonElement>
|
||||
}) {
|
||||
return (
|
||||
<button className={`bg-finn hover:bg-secondary transition border border-border rounded-lg flex items-center justify-center h-36 ${className}`} onClick={onClick}>
|
||||
<i className="bi bi-plus text-border text-7xl"></i>
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
PinBox.Add = Add;
|
||||
PinBox.Toggle = Toggle;
|
||||
|
||||
export default PinBox;
|
@ -1,28 +0,0 @@
|
||||
import { ChangeEventHandler } from "react";
|
||||
|
||||
interface SliderProps {
|
||||
className?: string;
|
||||
name?: string;
|
||||
value?: number;
|
||||
onChange?: ChangeEventHandler<HTMLInputElement | null>
|
||||
}
|
||||
|
||||
export function Slider ({ className, name, value, onChange }: SliderProps) {
|
||||
const percent = value ? value/10 : 0
|
||||
|
||||
return (
|
||||
<input
|
||||
className={`slider ${className}`}
|
||||
style={{
|
||||
background: `linear-gradient(to right, #3a3affcc ${percent}%, #3a3aff4d ${percent}%)`
|
||||
}}
|
||||
type="range"
|
||||
min={0}
|
||||
max={1000}
|
||||
name={name}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
id=""
|
||||
/>
|
||||
)
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
import { FormEventHandler } from "react";
|
||||
|
||||
export default function Switch ({ state, onChange }: { state: boolean, onChange?: FormEventHandler<HTMLButtonElement> }) {
|
||||
return (
|
||||
<button
|
||||
className={`border switch ${state ? 'switch-on' : 'switch-off'}`}
|
||||
onClick={onChange}
|
||||
>
|
||||
<div className="handler w-1/2 font-roboto-mono">OFF</div>
|
||||
<input type="hidden" name="" className="hidden" />
|
||||
</button>
|
||||
)
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
import NolaImg from "../../assets/img/ocs/nola_3.png";
|
||||
|
||||
export default function MadeBy ({ className }: { className?: string }) {
|
||||
return (
|
||||
<div className={`group ${className}`}>
|
||||
Made By
|
||||
<a href="https://github.com/norman-andrians" target="_blank">
|
||||
<img className="inline group-hover:animate-wewew" src={NolaImg} />
|
||||
</a>
|
||||
Norman Andrians
|
||||
</div>
|
||||
)
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
import { ReactNode } from "react";
|
||||
|
||||
interface SocialPops {
|
||||
className?: string;
|
||||
name: string;
|
||||
link: string;
|
||||
icon: ReactNode | string;
|
||||
}
|
||||
|
||||
export default function Social({ className, name, link, icon }: SocialPops) {
|
||||
return (
|
||||
<a
|
||||
href={link}
|
||||
title={name}
|
||||
target="_blank"
|
||||
className={`opacity-80 transition bg-transparent hover:bg-black w-14 h-14 flex justify-center items-center rounded-full ${className}`}
|
||||
>
|
||||
{icon}
|
||||
</a>
|
||||
);
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
interface TwoRowTab {
|
||||
className?: string;
|
||||
prop?: string;
|
||||
value?: string | number;
|
||||
}
|
||||
|
||||
export default function TwoRowTab ({ className, prop, value }: TwoRowTab) {
|
||||
return (
|
||||
<div className={`flex justify-between font-roboto-mono ${className}`}>
|
||||
<div>{prop}</div>
|
||||
<div>{value}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
import Img from "../../assets/img/absurd-closing.png";
|
||||
import Bg from "../../assets/img/bg-closing.png";
|
||||
|
||||
export default function Closing () {
|
||||
return (
|
||||
<div className="bg-gradient-to-t from-background via-background to-transparent">
|
||||
<div className="pt-64 pb-96 bg-no-repeat bg-cover bg-bottom" style={{
|
||||
backgroundImage: `url(${Bg})`,
|
||||
}}>
|
||||
<div className="container">
|
||||
<header className="text-center font-poppins">
|
||||
<h1 className="text-5xl font-bold leading-normal">That's All {":)"}</h1>
|
||||
<div>Other features are still under development</div>
|
||||
</header>
|
||||
<picture className="">
|
||||
<img className="mt-16 w-full h-auto" src={Img} alt="Goofy ahh closing" />
|
||||
</picture>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
@ -1,81 +0,0 @@
|
||||
import { ChangeEvent, Ref, useEffect } from "react";
|
||||
import PinBox from "../forms/PinBox";
|
||||
import { useLed } from "../../hooks";
|
||||
import ControlSection from "../ControlSection";
|
||||
import { PinState } from "../../types/board";
|
||||
import { PatchLed } from "../../controllers/BoardController";
|
||||
|
||||
function LedItem ({ led, index }: { led: PinState, index: number }) {
|
||||
const { removeLed, setLed, setLedPin } = useLed();
|
||||
|
||||
const handleChange = (e: ChangeEvent<HTMLInputElement | null>) => {
|
||||
const pin = e.target?.value;
|
||||
setLedPin(index, pin);
|
||||
}
|
||||
|
||||
const toggleLed = () => {
|
||||
setLed(led.pin, !led.state);
|
||||
}
|
||||
|
||||
const handleRemove = () => {
|
||||
removeLed(index);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const pin: number = typeof led.pin == "string" ? Number.parseInt(led.pin) : led.pin;
|
||||
PatchLed(pin, led.state);
|
||||
}, [led.state]);
|
||||
|
||||
return (
|
||||
<PinBox.Toggle
|
||||
className="w-36 animate-fade-in"
|
||||
minusBtn={true}
|
||||
value={led.pin}
|
||||
state={led.state}
|
||||
onValueChange={handleChange}
|
||||
onStateChange={toggleLed}
|
||||
onDelete={handleRemove}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function ControlLED ({ refto }: { refto?: Ref<HTMLDivElement> }) {
|
||||
const { addLed, leds } = useLed();
|
||||
|
||||
const handleAdd = (): void => {
|
||||
let anopin = 13;
|
||||
for (let i = 0; i < leds.length; i++) {
|
||||
if (leds.filter(led => led.pin == anopin).length > 0) {
|
||||
anopin--;
|
||||
}
|
||||
else break;
|
||||
}
|
||||
addLed(anopin, false);
|
||||
}
|
||||
|
||||
return (
|
||||
<ControlSection
|
||||
title="LED"
|
||||
id="led"
|
||||
description={<>
|
||||
Light Emitting Diode {"("}LED{")"} is one of the simplest Arduino electronics components, it is a type of semiconductor diode that produces light when an electric current flows through it.
|
||||
</>}
|
||||
refto={refto}
|
||||
stack={(<>
|
||||
{leds.map((led, i) => (
|
||||
<LedItem
|
||||
led={led}
|
||||
index={i}
|
||||
key={i}
|
||||
/>
|
||||
))}
|
||||
{leds.length < 14 && <PinBox.Add
|
||||
className="w-36 mt-10"
|
||||
onClick={handleAdd}
|
||||
/>}
|
||||
</>)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default ControlLED;
|
@ -1,142 +0,0 @@
|
||||
import { ChangeEvent, MouseEventHandler, Ref, useEffect, useState } from "react";
|
||||
import { usePhotoresistor } from "../../hooks";
|
||||
import { DynamicPinState } from "../../types/board";
|
||||
import CircleResistance from "../shapes/CircleResistance";
|
||||
import TwoRowTab from "../info/TwoRowTab";
|
||||
import EvoInput from "../forms/EvoInput";
|
||||
import Button from "../forms/Button";
|
||||
import { io } from "../../socket/socket.io";
|
||||
|
||||
|
||||
function Card ({ index, resistor }: { index: number, resistor: DynamicPinState }) {
|
||||
const { setResistorPin, setResistance, removeResistor } = usePhotoresistor();
|
||||
|
||||
const [isListen, setListen] = useState<boolean>(false);
|
||||
|
||||
const resistance: number = Math.floor(resistor.state);
|
||||
const intensity: number = Math.floor(100 - (resistor.state / 1023 * 100));
|
||||
|
||||
const toggleListen = () => {
|
||||
setListen(!isListen);
|
||||
}
|
||||
|
||||
const handleChange = (e: ChangeEvent<HTMLInputElement | null>) => {
|
||||
setResistorPin(index, e.target.value);
|
||||
}
|
||||
|
||||
const handleDelete = () => {
|
||||
removeResistor(index);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const pin = typeof resistor.pin == "string" && resistor.pin[0] == "A" ?
|
||||
resistor.pin[1] : resistor.pin;
|
||||
|
||||
if (isListen) {
|
||||
io.emit("set-photoresistor", pin);
|
||||
|
||||
const handler = (value: string) => {
|
||||
const res: number = Number.parseInt(value);
|
||||
setResistance(resistor.pin, res);
|
||||
}
|
||||
|
||||
io.on("photoresistor", handler);
|
||||
|
||||
return () => {
|
||||
io.off("photoresistor", handler);
|
||||
}
|
||||
}
|
||||
}, [io, isListen]);
|
||||
|
||||
return (
|
||||
<div className="border border-border bg-secondary col-span-2 rounded-lg p-6 animate-size-fade-in">
|
||||
<div className="flex justify-items-end">
|
||||
<button className="ms-auto bg-finn hover:bg-secondary transition border border-border rounded-lg px-5" onClick={handleDelete}>
|
||||
<i className="bi bi-dash text-xl"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div className="h-52 flex items-center justify-center">
|
||||
<CircleResistance
|
||||
intensity={intensity}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col mt-3 gap-3">
|
||||
<TwoRowTab
|
||||
prop="Resistance"
|
||||
value={resistance}
|
||||
/>
|
||||
<TwoRowTab
|
||||
prop="Light Intensity"
|
||||
value={`${intensity}%`}
|
||||
/>
|
||||
<EvoInput
|
||||
className="h-10"
|
||||
name="Pin"
|
||||
type="text"
|
||||
value={resistor.pin.toString()}
|
||||
onChange={(handleChange)}
|
||||
/>
|
||||
{!isListen ? (
|
||||
<Button.Primary onClick={toggleListen} className="text-sm !p-0 h-11">Start</Button.Primary>
|
||||
) : (
|
||||
<Button.Danger onClick={toggleListen} className="text-sm !p-0 h-11">Stop</Button.Danger>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function CardPlus ({ onClick }: { onClick?: MouseEventHandler<HTMLButtonElement> }) {
|
||||
return (
|
||||
<button
|
||||
className="bg-finn h-[470px] hover:bg-secondary transition col-span-2 rounded-lg border border-border flex items-center justify-center"
|
||||
onClick={onClick}
|
||||
>
|
||||
<i className="bi bi-plus text-6xl text-border"></i>
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
function ControlPhotoresistor ({ refto }: { refto?: Ref<HTMLDivElement> }) {
|
||||
const { photoresistor: resistors, addResistor } = usePhotoresistor();
|
||||
|
||||
const handleAdd = (): void => {
|
||||
let anopin = 5;
|
||||
for (let i = 0; i < resistors.length; i++) {
|
||||
if (resistors.filter(resistor => (resistor.pin == `A${anopin}` || resistor.pin == anopin)).length > 0) {
|
||||
anopin--;
|
||||
}
|
||||
else break;
|
||||
}
|
||||
addResistor(`A${anopin}`, 0);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="container py-16" id="rgb-led" ref={refto}>
|
||||
<div className="container-grid items-center relative">
|
||||
<div className={`col-span-8`}>
|
||||
<h2 className="text-4xl font-poppins font-bold leading-normal mb-4">
|
||||
Photoresistor
|
||||
</h2>
|
||||
<div className="grid grid-cols-8 mb-8">
|
||||
<p className="col-span-6">Also known as LDR {"("}Light Dependent Resistor{")"}, it is an electronic component whose resistance changes based on the intensity of light it receives. The higher the light intensity, the lower the resistance, When exposed to intense light, a photoresistor experiences a decrease in resistance due to photoconduction. The molecules in the photoresistor material become more active and allow electric current to flow through the material more easily.</p>
|
||||
</div>
|
||||
<div className={`grid grid-cols-8 gap-6`}>
|
||||
{resistors.map((resistor, i) => (
|
||||
<Card
|
||||
key={i}
|
||||
resistor={resistor}
|
||||
index={i}
|
||||
/>
|
||||
))}
|
||||
{resistors.length < 6 && <CardPlus
|
||||
onClick={handleAdd}
|
||||
/>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ControlPhotoresistor;
|
@ -1,98 +0,0 @@
|
||||
import PinBox from "../forms/PinBox";
|
||||
import ControlSection from "../ControlSection";
|
||||
import { PatchPiezo } from "../../controllers/BoardController";
|
||||
import { ChangeEvent, Ref, useEffect, useState } from "react";
|
||||
import { usePiezo } from "../../hooks";
|
||||
import { DynamicPinState } from "../../types/board";
|
||||
import EvoInput from "../forms/EvoInput";
|
||||
import Button from "../forms/Button";
|
||||
|
||||
function PiezoItem ({ piezo, index }: { piezo: DynamicPinState, index: number }) {
|
||||
const { setFrequency, setPiezoPin, removePiezo } = usePiezo();
|
||||
|
||||
const [freq, setFreq] = useState<number | string>(piezo.state);
|
||||
|
||||
const handlePinChange = (e: ChangeEvent<HTMLInputElement | null>) => {
|
||||
const pin = e.target?.value;
|
||||
setPiezoPin(index, pin);
|
||||
}
|
||||
|
||||
const handleFreqChange = (e: ChangeEvent<HTMLInputElement | null>) => {
|
||||
setFreq(e.target.value);
|
||||
}
|
||||
|
||||
const handleDelete = () => {
|
||||
removePiezo(index);
|
||||
}
|
||||
|
||||
const handlePatch = () => {
|
||||
const pin: number = typeof piezo.pin == "string" ? Number.parseInt(piezo.pin) : piezo.pin;
|
||||
PatchPiezo(pin, piezo.state);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const f: number = typeof freq == "string" ? Number.parseInt(freq) : freq;
|
||||
if (!Number.isNaN(freq)) setFrequency(index, f);
|
||||
}, [freq])
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-3">
|
||||
<PinBox
|
||||
className="w-36 animate-fade-in"
|
||||
minusBtn={true}
|
||||
value={piezo.pin}
|
||||
onValueChange={handlePinChange}
|
||||
onDelete={handleDelete}
|
||||
/>
|
||||
<EvoInput
|
||||
className="w-36 h-11"
|
||||
name="Frequency"
|
||||
type="number"
|
||||
value={piezo.state.toString()}
|
||||
onChange={handleFreqChange}
|
||||
/>
|
||||
<Button.Primary onClick={handlePatch} className="text-sm !py-0 h-11">Play</Button.Primary>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function ControlPiezo ({ refto }: { refto?: Ref<HTMLDivElement> }) {
|
||||
const { piezo: piezos, addPiezo } = usePiezo();
|
||||
|
||||
const handleAdd = (): void => {
|
||||
let anopin = 13;
|
||||
for (let i = 0; i < piezos.length; i++) {
|
||||
if (piezos.filter(piezo => piezo.pin == anopin).length > 0) {
|
||||
anopin--;
|
||||
}
|
||||
else break;
|
||||
}
|
||||
addPiezo(anopin, 247);
|
||||
}
|
||||
|
||||
return (
|
||||
<ControlSection
|
||||
title="Piezo Buzzer"
|
||||
id="piezo"
|
||||
description={<>
|
||||
Piezo buzzer is a type of transducer that converts electrical signals into sound. It uses piezoelectric elements to generate mechanical vibrations that then produce sound. Piezo buzzer can produce sound with a given frequency depending on its characteristics
|
||||
</>}
|
||||
refto={refto}
|
||||
stack={(<>
|
||||
{piezos.map((piezo, i) => (
|
||||
<PiezoItem
|
||||
index={i}
|
||||
piezo={piezo}
|
||||
key={i}
|
||||
/>
|
||||
))}
|
||||
{piezos.length < 14 && <PinBox.Add
|
||||
className="w-36 mt-10"
|
||||
onClick={handleAdd}
|
||||
/>}
|
||||
</>)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default ControlPiezo;
|
@ -1,332 +0,0 @@
|
||||
import { ChangeEvent, useState } from "react";
|
||||
import { usePiezoMusic } from "../../hooks";
|
||||
import { PiezoMusic } from "../../types/board";
|
||||
import EvoInput from "../forms/EvoInput";
|
||||
import Input from "../forms/Input";
|
||||
import EvoDropDown from "../forms/EvoDropDown";
|
||||
import Button from "../forms/Button";
|
||||
import { pitch } from "../../data/melodies";
|
||||
import { PatchPiezoMusic } from "../../controllers/BoardController";
|
||||
import SearchItemModal from "../modals/SearchItemModal";
|
||||
|
||||
function getNoteItems () {
|
||||
const pitches = Object.keys(pitch);
|
||||
const notes = pitches.map(n => {
|
||||
const note = n.replace("S", "#");
|
||||
return {name: note, value: note}
|
||||
})
|
||||
const additional = [
|
||||
{
|
||||
name: ". (Jump note)",
|
||||
value: "."
|
||||
},
|
||||
{
|
||||
name: "- (Hold note)",
|
||||
value: "-"
|
||||
}
|
||||
]
|
||||
return [...additional, ...notes];
|
||||
}
|
||||
|
||||
function NoteItem ({note, index, parentIndex}: { note: string, index: number, parentIndex: number }) {
|
||||
const { changeNote, removeNote } = usePiezoMusic();
|
||||
|
||||
const [dropAppear, setApper] = useState<boolean>(false);
|
||||
|
||||
const noteItems = getNoteItems();
|
||||
const toggleDropdown = () => { setApper(!dropAppear) };
|
||||
|
||||
const handleChange = (item: { name: string, value: any }) => {
|
||||
toggleDropdown();
|
||||
changeNote(parentIndex, index, item.value);
|
||||
}
|
||||
|
||||
const handleRemove = () => {
|
||||
removeNote(parentIndex, index);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
className="min-w-32 h-32 transition border group border-border rounded-lg animate-size-fade-in relative"
|
||||
>
|
||||
<div className="absolute right-2 top-2 flex gap-1">
|
||||
<button
|
||||
className="border w-8 h-8 border-border transition hover:bg-finn rounded-md opacity-0 group-hover:opacity-100 flex items-center justify-center"
|
||||
onClick={toggleDropdown}
|
||||
>
|
||||
<i className="bi bi-pencil text-xs"></i>
|
||||
</button>
|
||||
<button
|
||||
className="border w-8 h-8 border-border transition hover:bg-finn rounded-md opacity-0 group-hover:opacity-100 flex items-center justify-center"
|
||||
onClick={handleRemove}
|
||||
>
|
||||
<i className="bi bi-dash"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div className="text-xl">
|
||||
{note}
|
||||
</div>
|
||||
{/* <EvoDropDown.Menu
|
||||
className="!w-44 top-0 h-40 z-30 overflow-y-scroll v-scrollbar left-2"
|
||||
appear={dropAppear}
|
||||
items={noteItems}
|
||||
onValueChange={handleChange}
|
||||
/> */}
|
||||
</button>
|
||||
|
||||
{dropAppear == true && <SearchItemModal
|
||||
name="Edit Piezo Note"
|
||||
items={noteItems}
|
||||
initItem={{ name: note.replace("S", "#"), value: note.replace("S", "#") }}
|
||||
onValueChange={handleChange}
|
||||
onClose={toggleDropdown}
|
||||
/>}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
function NotePlus ({ parentIndex }: { parentIndex: number }) {
|
||||
const { addNote } = usePiezoMusic();
|
||||
const [dropAppear, setApper] = useState<boolean>(false);
|
||||
|
||||
const noteItems = getNoteItems();
|
||||
const toggleDropdown = () => { setApper(!dropAppear) };
|
||||
|
||||
const handleAddNote = (item: { name: string, value: any }) => {
|
||||
addNote(parentIndex, item.value);
|
||||
setApper(false);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<button className="border border-border bg-secondary-solid rounded-lg min-w-32 h-32" onClick={toggleDropdown}>
|
||||
<i className="bi bi-plus text-2xl"></i>
|
||||
{/* <EvoDropDown.Menu
|
||||
className="!w-32 top-0 h-56 z-30 overflow-y-scroll v-scrollbar"
|
||||
appear={dropAppear}
|
||||
items={noteItems}
|
||||
onValueChange={handleAddNote}
|
||||
/> */}
|
||||
</button>
|
||||
{dropAppear == true && <SearchItemModal
|
||||
name="Add Piezo Note"
|
||||
items={noteItems}
|
||||
onValueChange={handleAddNote}
|
||||
onClose={toggleDropdown}
|
||||
/>}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
function PiezoEditor ({ piezo, index }: { piezo: PiezoMusic, index: number }) {
|
||||
const { setName, setPin, setBeat, setTempo, removePiezo } = usePiezoMusic();
|
||||
|
||||
const beatsItem = [
|
||||
{
|
||||
name: "1/2",
|
||||
value: 1/2
|
||||
},
|
||||
{
|
||||
name: "1/4",
|
||||
value: 1/4
|
||||
},
|
||||
{
|
||||
name: "1/8",
|
||||
value: 1/8
|
||||
},
|
||||
{
|
||||
name: "1/16",
|
||||
value: 1/16
|
||||
}
|
||||
];
|
||||
|
||||
const initBeatItem = beatsItem.find((item) => item.value == piezo.beats);
|
||||
|
||||
const exportJson = () => {
|
||||
const filename = "piezo-music_" + piezo.name
|
||||
.toLocaleLowerCase()
|
||||
.split("")
|
||||
.map(char => char == " " ? "-" : char)
|
||||
.join("") + ".json"
|
||||
|
||||
const json = JSON.stringify(piezo, null, 4);
|
||||
const blob = new Blob([json], {
|
||||
type: "application/json; charset=utf-8"
|
||||
});
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
|
||||
link.setAttribute("href", url);
|
||||
link.setAttribute("download", filename);
|
||||
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
}
|
||||
|
||||
const handle = {
|
||||
changeName: (e: ChangeEvent<HTMLInputElement | null>) => {
|
||||
setName(index, e.target.value);
|
||||
},
|
||||
pinChange: (e: ChangeEvent<HTMLInputElement | null>) => {
|
||||
setPin(index, e.target.value);
|
||||
},
|
||||
beatChange: (item: { name: string, value: any }) => {
|
||||
setBeat(index, item.value);
|
||||
},
|
||||
tempoChange: (e: ChangeEvent<HTMLInputElement | null>) => {
|
||||
const tempo = Number.parseInt(e.target.value);
|
||||
setTempo(index, tempo);
|
||||
},
|
||||
remove: () => {
|
||||
removePiezo(index);
|
||||
},
|
||||
play: () => {
|
||||
PatchPiezoMusic(piezo);
|
||||
},
|
||||
export: exportJson
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-5 bg-secondary border border-border rounded-lg p-5 animate-size-fade-in relative">
|
||||
<button className="ms-auto absolute -right-24 top-1/2 -translate-y-1/2 bg-finn hover:bg-secondary transition border border-border rounded-lg px-5" onClick={handle.remove}>
|
||||
<i className="bi bi-dash text-3xl"></i>
|
||||
</button>
|
||||
<div className="flex flex-row justify-between">
|
||||
<div className="flex flex-row gap-3">
|
||||
<Input
|
||||
className="w-64"
|
||||
placeholder="Enter name"
|
||||
value={piezo.name}
|
||||
onChange={handle.changeName}
|
||||
/>
|
||||
<EvoInput
|
||||
className="w-36"
|
||||
name="Pin"
|
||||
type="number"
|
||||
value={piezo.pin.toString()}
|
||||
onChange={handle.pinChange}
|
||||
/>
|
||||
<EvoDropDown
|
||||
className="w-36"
|
||||
name="Beats"
|
||||
items={beatsItem}
|
||||
initItem={initBeatItem}
|
||||
/>
|
||||
<EvoInput
|
||||
className="w-36"
|
||||
name="Tempo"
|
||||
type="number"
|
||||
value={piezo.tempo.toString()}
|
||||
onChange={handle.tempoChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-row gap-3">
|
||||
<Button.Secondary className="!py-0 !px-6 flex items-center gap-3" onClick={exportJson}>
|
||||
<div className="text-sm">Export</div>
|
||||
<i className="bi bi-upload text-sm"></i>
|
||||
</Button.Secondary>
|
||||
<Button.Primary className="!py-1 !px-8" onClick={handle.play}>
|
||||
<i className="bi bi-play-fill"></i>
|
||||
</Button.Primary>
|
||||
</div>
|
||||
</div>
|
||||
<div className="relative">
|
||||
<div className="v-scrollbar pb-4 flex flex-row flex-nowrap gap-4 overflow-x-scroll overflow-y-hidden relative">
|
||||
{piezo.notes.map((note, i) => (
|
||||
<NoteItem
|
||||
note={note}
|
||||
index={i}
|
||||
parentIndex={index}
|
||||
key={i}
|
||||
/>
|
||||
))}
|
||||
<NotePlus parentIndex={index} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function PiezoBarPlus () {
|
||||
const { addPiezo } = usePiezoMusic();
|
||||
|
||||
const handleAdd = (): void => {
|
||||
addPiezo({
|
||||
name: "",
|
||||
pin: 11,
|
||||
notes: [],
|
||||
beats: 1/4,
|
||||
tempo: 100
|
||||
});
|
||||
}
|
||||
|
||||
const handleImport = (e: ChangeEvent<HTMLInputElement | null>): void => {
|
||||
const file: File | null = e.target.files ? e.target.files[0] : null;
|
||||
|
||||
if (!file) return;
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.onload = (event: ProgressEvent<FileReader>) => {
|
||||
const result = event.target ? event.target.result : null;
|
||||
|
||||
if (result && typeof result == "string") {
|
||||
const data: PiezoMusic = JSON.parse(result);
|
||||
const isValidFormat: boolean = data.name != null && data.pin != null && data.notes != null && data.beats != null && data.tempo != null;
|
||||
|
||||
if (isValidFormat) addPiezo(data);
|
||||
}
|
||||
}
|
||||
reader.readAsText(file);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-row gap-5">
|
||||
<button
|
||||
className="bg-finn h-36 flex-1 hover:bg-secondary transition col-span-2 rounded-lg border text-border border-border hover:border-slate-400 hover:text-slate-400 flex items-center justify-center"
|
||||
onClick={handleAdd}
|
||||
>
|
||||
<i className="bi bi-plus text-5xl"></i>
|
||||
<div className="font-roboto-mono text-lg">Add New</div>
|
||||
</button>
|
||||
<label
|
||||
htmlFor="file"
|
||||
className="bg-finn h-36 flex-1 hover:bg-secondary transition col-span-2 rounded-lg border text-border border-border hover:border-slate-400 hover:text-slate-400 flex items-center justify-center cursor-pointer"
|
||||
>
|
||||
<input type="file" id="file" className="hidden" onChange={handleImport} />
|
||||
<i className="bi bi-download text-3xl me-3"></i>
|
||||
<div className="font-roboto-mono text-lg">Import</div>
|
||||
</label>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function ControlPiezoMusicEditor () {
|
||||
const { piezeNotes } = usePiezoMusic();
|
||||
|
||||
return (
|
||||
<div className="container grid items-center relative">
|
||||
<div className={`col-span-8 w-[inherit]`}>
|
||||
<h2 className="text-4xl font-poppins font-bold leading-normal mb-4">
|
||||
Piezo Music Editor
|
||||
</h2>
|
||||
<div className="grid grid-cols-8 mb-8">
|
||||
<p className="col-span-6">Lorem, ipsum dolor sit amet consectetur adipisicing elit. Omnis rem, perspiciatis voluptatibus dolor officiis voluptas asperiores reiciendis quisquam qui numquam quas illum velit in id, est expedita ipsa voluptatum eligendi?</p>
|
||||
</div>
|
||||
<div className={`flex flex-col gap-6`}>
|
||||
{piezeNotes.map((piezo, i) => (
|
||||
<PiezoEditor
|
||||
piezo={piezo}
|
||||
index={i}
|
||||
key={i}
|
||||
/>
|
||||
))}
|
||||
<PiezoBarPlus />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ControlPiezoMusicEditor;
|
@ -1,190 +0,0 @@
|
||||
import { PatchRgbLed } from "../../controllers/BoardController";
|
||||
import { useRgbLed } from "../../hooks";
|
||||
import { ChannelPinState, PinState } from "../../types/board";
|
||||
import EvoInput from "../forms/EvoInput";
|
||||
import Switch from "../forms/Switch";
|
||||
import { MouseEventHandler, Ref, useEffect, useState } from "react";
|
||||
|
||||
interface HorizontalBarProps {
|
||||
rgbLed: ChannelPinState;
|
||||
index: number;
|
||||
}
|
||||
|
||||
function HorizontalBar ({ rgbLed, index }: HorizontalBarProps) {
|
||||
const { removeLed, setLed } = useRgbLed();
|
||||
|
||||
const handleUpdate = (led: ChannelPinState) => {
|
||||
setLed(index, led);
|
||||
}
|
||||
|
||||
const handleRemove = () => {
|
||||
removeLed(index);
|
||||
}
|
||||
|
||||
const [red, setRed] = useState<PinState>({
|
||||
pin: rgbLed.red.pin.toString(),
|
||||
state: rgbLed.red.state
|
||||
});
|
||||
|
||||
const [green, setGreen] = useState<PinState>({
|
||||
pin: rgbLed.green.pin.toString(),
|
||||
state: rgbLed.green.state
|
||||
});
|
||||
|
||||
const [blue, setBlue] = useState<PinState>({
|
||||
pin: rgbLed.blue.pin.toString(),
|
||||
state: rgbLed.blue.state
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
handleUpdate({red, green, blue});
|
||||
}, [red, green, blue]);
|
||||
|
||||
useEffect(() => {
|
||||
PatchRgbLed(rgbLed);
|
||||
}, [
|
||||
rgbLed.red.state,
|
||||
rgbLed.green.state,
|
||||
rgbLed.blue.state
|
||||
]);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col col-span-3 gap-2 animate-fade-in font-roboto-mono">
|
||||
<button className="ms-auto bg-finn hover:bg-secondary transition border border-border rounded-lg px-5" onClick={handleRemove}>
|
||||
<i className="bi bi-dash text-xl"></i>
|
||||
</button>
|
||||
<div className="bg-secondary animate-size-in rounded-lg border border-border p-4 flex gap-3">
|
||||
<div className="flex-1 text-center flex flex-col gap-3">
|
||||
<div className="text-[#FF4444]">Red</div>
|
||||
<EvoInput
|
||||
name="Pin"
|
||||
value={red.pin.toString()}
|
||||
type="number"
|
||||
onChange={(e) => {
|
||||
setRed({
|
||||
pin: e.target.value,
|
||||
state: red.state
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<Switch
|
||||
state={red.state}
|
||||
onChange={() => {
|
||||
setRed({
|
||||
pin: red.pin,
|
||||
state: !red.state
|
||||
})
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex-1 text-center flex flex-col gap-3">
|
||||
<div className="text-[#48FF44]">Green</div>
|
||||
<EvoInput
|
||||
name="Pin"
|
||||
value={green.pin.toString()}
|
||||
type="number"
|
||||
onChange={(e) => {
|
||||
setGreen({
|
||||
pin: e.target.value,
|
||||
state: green.state
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<Switch
|
||||
state={green.state}
|
||||
onChange={() => {
|
||||
setGreen({
|
||||
pin: green.pin,
|
||||
state: !green.state
|
||||
})
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex-1 text-center flex flex-col gap-3">
|
||||
<div className="text-[#4844FF]">Blue</div>
|
||||
<EvoInput
|
||||
name="Pin"
|
||||
value={blue.pin.toString()}
|
||||
type="number"
|
||||
onChange={(e) => {
|
||||
setBlue({
|
||||
pin: e.target.value,
|
||||
state: blue.state
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<Switch
|
||||
state={blue.state}
|
||||
onChange={() => {
|
||||
setBlue({
|
||||
pin: blue.pin,
|
||||
state: !blue.state
|
||||
})
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function HorizontalBarPlus ({ onClick }: { onClick?: MouseEventHandler<HTMLButtonElement> }) {
|
||||
return (
|
||||
<button
|
||||
className="bg-finn h-40 hover:bg-secondary transition col-span-3 rounded-lg border border-border flex items-center justify-center mt-10"
|
||||
onClick={onClick}
|
||||
>
|
||||
<i className="bi bi-plus text-6xl text-border"></i>
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
function ControlRgbLed ({ refto }: { refto?: Ref<HTMLDivElement> }) {
|
||||
const { addLed, rgbLed } = useRgbLed();
|
||||
|
||||
const handleAdd = (): void => {
|
||||
addLed({
|
||||
red: {
|
||||
pin: 13,
|
||||
state: false
|
||||
},
|
||||
green: {
|
||||
pin: 12,
|
||||
state: false
|
||||
},
|
||||
blue: {
|
||||
pin: 11,
|
||||
state: false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="container py-16" id="rgb-led" ref={refto}>
|
||||
<div className="container-grid items-center relative">
|
||||
<div className={`col-span-8`}>
|
||||
<h2 className="text-4xl font-poppins font-bold leading-normal mb-4">
|
||||
RGB LED
|
||||
</h2>
|
||||
<div className="grid grid-cols-8 mb-8">
|
||||
<p className="col-span-6">RGB LED is a type of Light Emitting Diode that has the ability to produce light in three basic colors, red {"("}R{")"}, green {"("}G{")"}, and blue {"("}B{")"}. By controlling the intensity of these three colors separately, RGB LEDs allow the creation of a variety of different colors</p>
|
||||
</div>
|
||||
<div className={`grid grid-cols-9 gap-6`}>
|
||||
{rgbLed.map((led, i) => (
|
||||
<HorizontalBar
|
||||
key={i}
|
||||
index={i}
|
||||
rgbLed={led}
|
||||
/>
|
||||
))}
|
||||
{rgbLed.length < 5 && <HorizontalBarPlus
|
||||
onClick={handleAdd}
|
||||
/>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ControlRgbLed;
|
@ -1,127 +0,0 @@
|
||||
import { ChangeEvent, MouseEventHandler, Ref, useEffect, useState } from "react";
|
||||
import { useServo } from "../../hooks";
|
||||
import { DynamicPinState } from "../../types/board";
|
||||
import EvoInput from "../forms/EvoInput";
|
||||
import Button from "../forms/Button";
|
||||
import { Slider } from "../forms/Slider";
|
||||
import { PatchServo } from "../../controllers/BoardController";
|
||||
|
||||
|
||||
function Bar ({ index, servo }: { index: number, servo: DynamicPinState }) {
|
||||
const { setServoPin, setDegree, removeServo } = useServo();
|
||||
|
||||
const [percentage, setPercent] = useState(0);
|
||||
const [isListen, setListen] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const deg = Math.floor((percentage / 1000) * 180);
|
||||
setDegree(servo.pin, deg);
|
||||
}, [percentage]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isListen) PatchServo(servo.pin, servo.state);
|
||||
}, [isListen, servo.state])
|
||||
|
||||
const toggleListen = () => {
|
||||
setListen(!isListen);
|
||||
}
|
||||
|
||||
const handleChange = (e: ChangeEvent<HTMLInputElement | null>) => {
|
||||
const percent = Number.parseInt(e.target.value);
|
||||
if (!Number.isNaN(percent)) setPercent(percent);
|
||||
}
|
||||
|
||||
const handleChangePin = (e: ChangeEvent<HTMLInputElement | null>) => {
|
||||
setServoPin(index, e.target.value);
|
||||
}
|
||||
|
||||
const handleDelete = () => {
|
||||
removeServo(index);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="border border-border bg-secondary rounded-lg p-6 animate-size-fade-in relative">
|
||||
<button className="ms-auto absolute -right-24 top-1/2 -translate-y-1/2 bg-finn hover:bg-secondary transition border border-border rounded-lg px-5" onClick={handleDelete}>
|
||||
<i className="bi bi-dash text-3xl"></i>
|
||||
</button>
|
||||
<div className="flex justify-between mb-8">
|
||||
<div className="flex gap-3">
|
||||
<EvoInput
|
||||
name="Pin"
|
||||
value={servo.pin.toString()}
|
||||
onChange={handleChangePin}
|
||||
type="text"
|
||||
/>
|
||||
{!isListen ? (
|
||||
<Button.Primary onClick={toggleListen} className="text-sm !py-0 h-11">Start</Button.Primary>
|
||||
) : (
|
||||
<Button.Danger onClick={toggleListen} className="text-sm !py-0 h-11">Stop</Button.Danger>
|
||||
)}
|
||||
</div>
|
||||
<div className="">{servo.state+"°"}</div>
|
||||
</div>
|
||||
<div className="">
|
||||
<Slider
|
||||
name={`Servo ${servo.pin}`}
|
||||
value={percentage}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function BarPlus ({ onClick }: { onClick?: MouseEventHandler<HTMLButtonElement> }) {
|
||||
return (
|
||||
<button
|
||||
className="bg-finn h-36 hover:bg-secondary transition col-span-2 rounded-lg border border-border flex items-center justify-center"
|
||||
onClick={onClick}
|
||||
>
|
||||
<i className="bi bi-plus text-6xl text-border"></i>
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
function ControlServo ({ refto }: { refto?: Ref<HTMLDivElement> }) {
|
||||
const { motoServo, addServo } = useServo();
|
||||
|
||||
const handleAdd = (): void => {
|
||||
let anopin = 5;
|
||||
for (let i = 0; i < motoServo.length; i++) {
|
||||
if (motoServo.filter(servo => (servo.pin == `A${anopin}` || servo.pin == anopin)).length > 0) {
|
||||
anopin--;
|
||||
}
|
||||
else break;
|
||||
}
|
||||
addServo(`A${anopin}`, 0);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="container py-16" id="rgb-led" ref={refto}>
|
||||
<div className="container-grid items-center relative">
|
||||
<div className={`col-span-8`}>
|
||||
<h2 className="text-4xl font-poppins font-bold leading-normal mb-4">
|
||||
Servo
|
||||
</h2>
|
||||
<div className="grid grid-cols-8 mb-8">
|
||||
<p className="col-span-6">Servo motors is a type of electric motor designed to provide precise motion and accurate control. Servo motors use a feedback mechanism to control their shaft position and rotational speed.</p>
|
||||
</div>
|
||||
<div className={`flex flex-col gap-6`}>
|
||||
{motoServo.map((servo, i) => (
|
||||
<Bar
|
||||
key={i}
|
||||
servo={servo}
|
||||
index={i}
|
||||
/>
|
||||
))}
|
||||
{motoServo.length < 6 && <BarPlus
|
||||
onClick={handleAdd}
|
||||
/>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ControlServo;
|
@ -1,57 +0,0 @@
|
||||
import Button from "../forms/Button";
|
||||
import { RefObject } from "react";
|
||||
import MadeBy from "../info/MadeBy";
|
||||
|
||||
type ShortNav = {
|
||||
name: string,
|
||||
target: string
|
||||
}
|
||||
|
||||
interface InHero {
|
||||
img: string;
|
||||
shortNav: ShortNav[];
|
||||
refs?: RefObject<HTMLElement>[]
|
||||
}
|
||||
|
||||
function Hero ({ img, shortNav, refs }: InHero) {
|
||||
return (
|
||||
<div className="h-screen container flex items-center">
|
||||
<div className="container-grid items-center relative">
|
||||
<div className="col-span-4">
|
||||
<h2 className="font-poppins text-6xl font-bold leading-normal">
|
||||
Lunar Vein: Arduino Client
|
||||
</h2>
|
||||
<p className="font-poppins text-xl">
|
||||
Open-source API Based serial communication, it helps you testing the arduino board and some components without any code.
|
||||
<small className="text-xs"> i don't even know anything about electronics and some IoT stuff lol</small>
|
||||
</p>
|
||||
<div className="flex flex-wrap gap-3 mt-5">
|
||||
{shortNav.map(({ name }, i) => (
|
||||
<Button
|
||||
key={i}
|
||||
onClick={() => {
|
||||
if (refs && refs.length >= i)
|
||||
refs[i].current?.scrollIntoView({ behavior: "smooth" });
|
||||
}}
|
||||
>{name}</Button>
|
||||
))}
|
||||
</div>
|
||||
<MadeBy className="absolute left-0 -bottom-24" />
|
||||
</div>
|
||||
<div className="col-span-1"></div>
|
||||
<div className="col-span-3">
|
||||
<picture title="Lunar">
|
||||
<img
|
||||
className="w-full h-auto"
|
||||
src={img}
|
||||
alt="Lunar"
|
||||
srcSet={img}
|
||||
/>
|
||||
</picture>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Hero;
|
@ -1,73 +0,0 @@
|
||||
import { ChangeEvent, useEffect, useState } from "react";
|
||||
import Input from "../forms/Input";
|
||||
|
||||
type EvoDropDownItem = {
|
||||
name: string,
|
||||
value: any
|
||||
}
|
||||
|
||||
interface SearchItemModalProps {
|
||||
className?: string;
|
||||
name?: string;
|
||||
items: EvoDropDownItem[];
|
||||
initItem?: EvoDropDownItem;
|
||||
onValueChange?: (item: EvoDropDownItem) => void;
|
||||
onClose?: () => void
|
||||
}
|
||||
|
||||
export default function SearchItemModal ({ className, items, name, initItem, onClose, onValueChange }: SearchItemModalProps) {
|
||||
const [filteredItems, setItems] = useState<EvoDropDownItem[]>(items);
|
||||
const [currentItem, setItem] = useState<EvoDropDownItem>(initItem || items[0]);
|
||||
|
||||
const [searchVal, setSearch] = useState<string>("");
|
||||
|
||||
const handleSearch = (e: ChangeEvent<HTMLInputElement | null>) => {
|
||||
setSearch(e.target.value);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (searchVal.length > 0) {
|
||||
const filter = items.filter(item => item.name.toLocaleLowerCase().includes(searchVal.toLowerCase()));
|
||||
setItems(filter);
|
||||
}
|
||||
else {
|
||||
setItems(items);
|
||||
}
|
||||
}, [searchVal])
|
||||
|
||||
return (
|
||||
<div className={`fixed left-0 top-0 bg-finn w-screen h-screen z-50 ${className}`}>
|
||||
<div className="bg-black border border-border rounded-lg absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 h-96 w-96 p-3 flex flex-col justify-items-start">
|
||||
<button className="p-1 absolute right-2 top-2" onClick={onClose}>
|
||||
<i className="bi bi-x text-3xl"></i>
|
||||
</button>
|
||||
<header className="text-left">
|
||||
<h1 className="text-2xl font-bold font-poppins px-2 py-4">{name}</h1>
|
||||
<Input
|
||||
className="w-full mb-3"
|
||||
name="Search"
|
||||
placeholder="Search..."
|
||||
value={searchVal}
|
||||
onChange={handleSearch}
|
||||
/>
|
||||
</header>
|
||||
<div className="overflow-y-scroll flex-grow flex flex-col">
|
||||
{filteredItems.map((item) => (
|
||||
<button
|
||||
className="py-2 px-3 flex justify-between items-center bg-transparent bg-opacity-100 hover:bg-indigo-300 hover:bg-opacity-20 rounded-md"
|
||||
onClick={() => {
|
||||
setItem(item);
|
||||
if (onValueChange) onValueChange(item)}
|
||||
}
|
||||
>
|
||||
<div className="text-left text-sm">
|
||||
{item.name}
|
||||
</div>
|
||||
{currentItem.value == item.value && <div className="rounded-full bg-white w-2 h-2"></div>}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
@ -1,40 +0,0 @@
|
||||
interface CircleResistanceProps {
|
||||
className?: string;
|
||||
width?: number;
|
||||
height?: number;
|
||||
blur?: number;
|
||||
intensity?: number;
|
||||
}
|
||||
|
||||
export default function CircleResistance({
|
||||
className,
|
||||
width,
|
||||
height,
|
||||
blur,
|
||||
intensity,
|
||||
}: CircleResistanceProps) {
|
||||
return (
|
||||
<div
|
||||
className={className}
|
||||
style={{
|
||||
borderRadius: 999,
|
||||
width: width || 122,
|
||||
height: height || 122,
|
||||
backgroundColor: "rgba(255, 255, 255, 0.20)",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="transition-opacity duration-75"
|
||||
style={{
|
||||
backgroundColor: "white",
|
||||
borderRadius: 999,
|
||||
width: width || 122,
|
||||
height: height || 122,
|
||||
filter: `blur(${blur || 12}px)`,
|
||||
boxShadow: '0 0 52px 22px #FF2929',
|
||||
opacity: intensity+"%"
|
||||
}}
|
||||
></div>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -1,97 +0,0 @@
|
||||
import { Dispatch, ReactNode, SetStateAction, createContext, useState } from "react";
|
||||
import { ChannelPinState, DynamicPinState, PiezoMusic, PinMode, PinState } from "../types/board";
|
||||
|
||||
import ExampleMusic1 from "../data/music/ode-to-joy.json";
|
||||
import ExampleMusic2 from "../data/music/fallen-down.json";
|
||||
|
||||
interface ControllerContextProps {
|
||||
pinModes: PinMode[];
|
||||
leds: PinState[];
|
||||
rgbLed: ChannelPinState[];
|
||||
piezo: DynamicPinState[];
|
||||
piezeNotes: PiezoMusic[];
|
||||
motoServo: DynamicPinState[];
|
||||
photoresistor: DynamicPinState[];
|
||||
|
||||
setPinModes?: Dispatch<SetStateAction<PinMode[]>>;
|
||||
setLeds?: Dispatch<SetStateAction<PinState[]>>;
|
||||
setRgbLed?: Dispatch<SetStateAction<ChannelPinState[]>>;
|
||||
setPiezo?: Dispatch<SetStateAction<DynamicPinState[]>>;
|
||||
setNotes?: Dispatch<SetStateAction<PiezoMusic[]>>;
|
||||
setMotoServo?: Dispatch<SetStateAction<DynamicPinState[]>>;
|
||||
setPhotoresistor?: Dispatch<SetStateAction<DynamicPinState[]>>;
|
||||
}
|
||||
|
||||
const INIT_VALUES: ControllerContextProps = {
|
||||
pinModes: [],
|
||||
leds: [
|
||||
{
|
||||
pin: 13,
|
||||
state: false
|
||||
}
|
||||
],
|
||||
rgbLed: [
|
||||
{
|
||||
red: {
|
||||
pin: 13,
|
||||
state: false
|
||||
},
|
||||
green: {
|
||||
pin: 12,
|
||||
state: false
|
||||
},
|
||||
blue: {
|
||||
pin: 11,
|
||||
state: false
|
||||
}
|
||||
}
|
||||
],
|
||||
piezo: [
|
||||
{
|
||||
pin: 6,
|
||||
state: 262
|
||||
}
|
||||
],
|
||||
piezeNotes: [
|
||||
ExampleMusic1,
|
||||
ExampleMusic2
|
||||
],
|
||||
motoServo: [
|
||||
{
|
||||
pin: "9",
|
||||
state: 0
|
||||
}
|
||||
],
|
||||
photoresistor: [
|
||||
{
|
||||
pin: "A0",
|
||||
state: 0
|
||||
}
|
||||
],
|
||||
}
|
||||
|
||||
export const BoardControllerContext = createContext<ControllerContextProps>(INIT_VALUES);
|
||||
|
||||
export function BoardControllerProvider ({ children }: { children: ReactNode }) {
|
||||
const [pinModes, setPinModes] = useState<PinMode[]>(INIT_VALUES.pinModes);
|
||||
const [leds, setLeds] = useState<PinState[]>(INIT_VALUES.leds);
|
||||
const [rgbLed, setRgbLed] = useState<ChannelPinState[]>(INIT_VALUES.rgbLed);
|
||||
const [piezo, setPiezo] = useState<DynamicPinState[]>(INIT_VALUES.piezo);
|
||||
const [piezeNotes, setNotes] = useState<PiezoMusic[]>(INIT_VALUES.piezeNotes);
|
||||
const [motoServo, setMotoServo] = useState<DynamicPinState[]>(INIT_VALUES.motoServo);
|
||||
const [photoresistor, setPhotoresistor] = useState<DynamicPinState[]>(INIT_VALUES.photoresistor);
|
||||
|
||||
const contextValue: ControllerContextProps = {
|
||||
pinModes, setPinModes,
|
||||
leds, setLeds,
|
||||
rgbLed, setRgbLed,
|
||||
piezo, setPiezo,
|
||||
piezeNotes, setNotes,
|
||||
motoServo, setMotoServo,
|
||||
photoresistor, setPhotoresistor
|
||||
};
|
||||
|
||||
return <BoardControllerContext.Provider value={contextValue}>
|
||||
{children}
|
||||
</BoardControllerContext.Provider>
|
||||
}
|
50
src/controller/basic/analog.ts
Normal 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);
|
||||
}
|
||||
}
|
56
src/controller/basic/digital.ts
Normal 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);
|
||||
}
|
||||
}
|
87
src/controller/basic/pin.ts
Normal 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 ]
|
||||
});
|
||||
}
|
185
src/controller/components/led.ts
Normal 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"
|
||||
});
|
||||
}
|
||||
};
|
17
src/controller/components/photoresistor.ts
Normal 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}°`
|
||||
});
|
||||
}
|
92
src/controller/components/piezo.ts
Normal 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}`
|
||||
});
|
||||
}
|
23
src/controller/components/servo.ts
Normal 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}°`
|
||||
});
|
||||
}
|
15
src/controller/index.d.ts
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
|
||||
export type digitalValue = 'ON' | 'OFF';
|
||||
export type voltage = 'HIGH' | 'LOW' | 'high' | 'low' | 1 | 0;
|
||||
export type sPinModes = 'INPUT' | 'OUTPUT' | 'ANALOG' | 'PWM' | 'SERVO';
|
||||
|
||||
export interface ChannelPins {
|
||||
pin: number,
|
||||
value: boolean
|
||||
}
|
||||
|
||||
export interface ColorChannel {
|
||||
r: string,
|
||||
g: string,
|
||||
b: string
|
||||
}
|
@ -1,63 +0,0 @@
|
||||
import axios from "axios";
|
||||
import { ChannelPinState, PiezoMusic } from "../types/board";
|
||||
import { io } from "../socket/socket.io";
|
||||
|
||||
const url = "http://localhost:3000/api-arduino";
|
||||
|
||||
export async function PatchLed (pin: number, state: boolean) {
|
||||
const act = state == true ? 'on' : 'off';
|
||||
|
||||
const res = await axios.patch(`${url}/led/${pin}/${act}`);
|
||||
const data = res.data;
|
||||
|
||||
console.log("LED Response: ", data);
|
||||
}
|
||||
|
||||
export async function PatchRgbLed (state: ChannelPinState) {
|
||||
const { red, green, blue } = state;
|
||||
const body = {
|
||||
r: {
|
||||
pin: red.pin,
|
||||
value: red.state
|
||||
},
|
||||
g: {
|
||||
pin: green.pin,
|
||||
value: green.state
|
||||
},
|
||||
b: {
|
||||
pin: blue.pin,
|
||||
value: blue.state
|
||||
}
|
||||
}
|
||||
const res = await axios.patch(`${url}/rgb-led`, body, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
const data = res.data;
|
||||
|
||||
console.log("RGB LED Response: ", data);
|
||||
}
|
||||
|
||||
export async function PatchPiezo (pin: number, freq: number) {
|
||||
const res = await axios.patch(`${url}/piezo/${pin}/${freq}`);
|
||||
const data = res.data;
|
||||
|
||||
console.log("Piezo Response: ", data);
|
||||
}
|
||||
|
||||
export async function PatchPiezoMusic (music: PiezoMusic) {
|
||||
const res = await axios.patch(`${url}/piezo/music`, music, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
const data = res.data;
|
||||
|
||||
console.log("Piezo Response: ", data);
|
||||
}
|
||||
|
||||
|
||||
export async function PatchServo(pin: string | number, value: number) {
|
||||
io.emit("servo", pin, value);
|
||||
}
|
@ -1,123 +0,0 @@
|
||||
@import url('https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&family=Roboto+Mono:ital,wght@0,100..700;1,100..700&display=swap');
|
||||
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer base {
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
input::-webkit-outer-spin-button,
|
||||
input::-webkit-inner-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Firefox */
|
||||
input[type=number] {
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
|
||||
.v-scrollbar::-webkit-scrollbar {
|
||||
width: 3px;
|
||||
}
|
||||
.v-scrollbar::-webkit-scrollbar-track {
|
||||
@apply bg-transparent;
|
||||
}
|
||||
.v-scrollbar::-webkit-scrollbar-thumb {
|
||||
@apply bg-primary cursor-pointer rounded-full;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes size-in {
|
||||
from { transform: scale(0); }
|
||||
to { transform: scale(1); }
|
||||
}
|
||||
|
||||
@keyframes fade-in {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
|
||||
@keyframes size-fade-in {
|
||||
from {
|
||||
transform: scale(0);
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes wewew {
|
||||
0% { transform: scale(1, 1); }
|
||||
12% { transform: scale(1.1, .9); }
|
||||
25% { transform: scale(.9, 1.1); }
|
||||
37% { transform: scale(1.1, .9); }
|
||||
50% { transform: scale(1, 1); }
|
||||
100% { transform: scale(1, 1); }
|
||||
}
|
||||
|
||||
.container {
|
||||
@apply w-[1280px] mx-auto;
|
||||
}
|
||||
|
||||
.container-grid {
|
||||
@apply grid grid-cols-8 gap-6;
|
||||
}
|
||||
|
||||
@layer components {
|
||||
.slider {
|
||||
-webkit-appearance: none;
|
||||
-webkit-transition: .15s;
|
||||
color: #3a3affcc;
|
||||
color: #3a3aff4d;
|
||||
@apply appearance-none rounded-full w-full h-3 bg-primary bg-opacity-30 transition;
|
||||
}
|
||||
.slider::-webkit-slider-thumb {
|
||||
-webkit-appearance: none;
|
||||
@apply appearance-none transition-all rounded-full w-5 h-5 bg-primary cursor-pointer;
|
||||
}
|
||||
.slider::-webkit-slider-thumb:hover {
|
||||
-webkit-appearance: none;
|
||||
box-shadow: 0 0 0 8px #3838ff66;
|
||||
}
|
||||
.slider::-webkit-slider-thumb:active {
|
||||
-webkit-appearance: none;
|
||||
box-shadow: 0 0 0 12px #3838ff66;
|
||||
@apply w-6 h-6;
|
||||
}
|
||||
.slider::-moz-range-thumb {
|
||||
@apply w-5 h-5 bg-primary cursor-pointer;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.btn {
|
||||
@apply px-5 py-4 transition font-roboto-mono rounded-lg transition-[transform] duration-75 scale-100 active:scale-90;
|
||||
}
|
||||
|
||||
|
||||
.switch {
|
||||
@apply rounded-lg transition;
|
||||
}
|
||||
.switch > .handler {
|
||||
@apply rounded-md p-[6px] transition-[margin];
|
||||
}
|
||||
|
||||
.switch-on {
|
||||
@apply border-primary bg-primary bg-opacity-40;
|
||||
}
|
||||
.switch-on > .handler {
|
||||
@apply bg-primary ms-[50%];
|
||||
}
|
||||
|
||||
.switch-off {
|
||||
@apply border-disabled bg-transparent;
|
||||
}
|
||||
.switch-off > .handler {
|
||||
@apply bg-disabled ms-0;
|
||||
}
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
[
|
||||
{
|
||||
"name": "LED",
|
||||
"target": "#led"
|
||||
},
|
||||
{
|
||||
"name": "RGB LED",
|
||||
"target": "#rgb-led"
|
||||
},
|
||||
{
|
||||
"name": "Piezo",
|
||||
"target": "#piezo"
|
||||
},
|
||||
{
|
||||
"name": "Moto Servo",
|
||||
"target": "#moto-servo"
|
||||
},
|
||||
{
|
||||
"name": "Photoresistor",
|
||||
"target": "#photoresistor"
|
||||
},
|
||||
{
|
||||
"name": "Ultrasonic sensor",
|
||||
"target": "#ultrasonic-sensor"
|
||||
}
|
||||
]
|
@ -1,12 +0,0 @@
|
||||
{
|
||||
"name": "Fallen Down",
|
||||
"pin": 11,
|
||||
"notes": [
|
||||
"F#5", "C#5", "F#5", "C#5", "F#5", "C#5", "F#5", "C#5", "F#5", "C#5", "F#5", "C#5",
|
||||
"B4", "A4", "C#5", ".", "A4", "B4", "E5", "D#5", "E5", "F#5", "D#5", "B4",
|
||||
"F#5", "B4", "F#5", "B4", "F#5", "B4", "F#5", "A#4", "F#5", "A#4", "G5", ".",
|
||||
"F#5", "D5", "F#5", "D5", "E5", "F#5", "E5", "-", "D5", "-", "C#5", "-"
|
||||
],
|
||||
"beats": 0.5,
|
||||
"tempo": 100
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
{
|
||||
"name": "Ode to joy",
|
||||
"pin": 11,
|
||||
"notes": [
|
||||
"E4", "E4", "F4", "G4", "G4", "F4", "E4", "D4", "C4", "C4", "D4", "E4", "D4", "-", "C4", "C4"
|
||||
],
|
||||
"beats": 0.5,
|
||||
"tempo": 100
|
||||
}
|
7
src/dev.ts
Normal 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();
|
57
src/handlers/socketHandler.ts
Normal 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}`);
|
||||
}
|
||||
})
|
||||
}
|
@ -1,297 +0,0 @@
|
||||
import { useContext } from "react";
|
||||
import { BoardControllerContext } from "../contexts/BoardController";
|
||||
import { ChannelPinState, PiezoMusic } from "../types/board";
|
||||
|
||||
|
||||
// * -----------------------------------
|
||||
// * I'm never use OOP in react lol
|
||||
// * -----------------------------------
|
||||
|
||||
export function usePin () {
|
||||
return useContext(BoardControllerContext);
|
||||
}
|
||||
|
||||
|
||||
export function useLed () {
|
||||
const { leds, setLeds } = useContext(BoardControllerContext);
|
||||
|
||||
const getLed = (pin: number | string) => {
|
||||
return leds.find(val => val.pin == pin);
|
||||
}
|
||||
|
||||
const addLed = (pin: number | string, state?: boolean) => {
|
||||
setLeds!(led => [...led, { pin, state: state || false }]);
|
||||
}
|
||||
|
||||
const removeLed = (index: number) => {
|
||||
const newLed = leds.filter((_led, i) => i != index);
|
||||
setLeds!(newLed);
|
||||
}
|
||||
|
||||
const setLed = (pin: number | string, state: boolean) => {
|
||||
const newLed = leds.map(led => {
|
||||
if (led.pin == pin) return { pin, state };
|
||||
return led;
|
||||
})
|
||||
setLeds!(newLed);
|
||||
}
|
||||
|
||||
const setLedPin = (index: number | string, newPin: number | string) => {
|
||||
const newLed = leds.map((led, i) => {
|
||||
if (i == index) return { pin: newPin, state: led.state };
|
||||
return led;
|
||||
})
|
||||
setLeds!(newLed);
|
||||
}
|
||||
|
||||
return { leds, getLed, setLed, setLedPin, addLed, removeLed };
|
||||
}
|
||||
|
||||
|
||||
|
||||
export function useRgbLed () {
|
||||
const { rgbLed, setRgbLed } = useContext(BoardControllerContext);
|
||||
|
||||
const getLed = (index: number) => {
|
||||
const led = rgbLed.find((_val, i) => i == index);
|
||||
return led;
|
||||
}
|
||||
|
||||
const addLed = (channelPin: ChannelPinState) => {
|
||||
const newLed: ChannelPinState[] = [
|
||||
...rgbLed,
|
||||
{
|
||||
red: channelPin.red,
|
||||
green: channelPin.green,
|
||||
blue: channelPin.blue
|
||||
}
|
||||
];
|
||||
setRgbLed!(newLed);
|
||||
}
|
||||
|
||||
const removeLed = (index: number) => {
|
||||
const newLed: ChannelPinState[] = rgbLed.filter((_led, i) => i != index);
|
||||
setRgbLed!(newLed);
|
||||
}
|
||||
|
||||
const setLed = (index: number, channelPin: ChannelPinState) => {
|
||||
const newLed: ChannelPinState[] = rgbLed.map((led, i) => {
|
||||
if (i == index) return channelPin;
|
||||
return led;
|
||||
})
|
||||
setRgbLed!(newLed);
|
||||
}
|
||||
|
||||
return { rgbLed, getLed, setLed, addLed, removeLed };
|
||||
}
|
||||
|
||||
|
||||
|
||||
export function usePiezo () {
|
||||
const { piezo, setPiezo } = useContext(BoardControllerContext);
|
||||
|
||||
const getPiezo = (pin: number | string) => {
|
||||
return piezo.find(val => val.pin == pin);
|
||||
}
|
||||
|
||||
const addPiezo = (pin: number | string, state: number) => {
|
||||
const newPiezo = [...piezo, { pin, state }];
|
||||
setPiezo!(newPiezo);
|
||||
}
|
||||
|
||||
const removePiezo = (index: number) => {
|
||||
const newPiezo = piezo.filter((_piezo, i) => i != index);
|
||||
setPiezo!(newPiezo);
|
||||
}
|
||||
|
||||
const setFrequency = (index: number, state: number) => {
|
||||
setPiezo!(piezos => piezos.map((piezo, i) => {
|
||||
if (i == index) return { pin: piezo.pin, state: state };
|
||||
return piezo;
|
||||
}));
|
||||
}
|
||||
|
||||
const setPiezoPin = (index: number | string, newPin: number | string) => {
|
||||
const newPiezo = piezo.map((piezo, i) => {
|
||||
if (i == index) return { pin: newPin, state: piezo.state };
|
||||
return piezo;
|
||||
})
|
||||
setPiezo!(newPiezo);
|
||||
}
|
||||
|
||||
return { piezo, getPiezo, addPiezo, removePiezo, setFrequency, setPiezoPin };
|
||||
}
|
||||
|
||||
|
||||
|
||||
export function usePiezoMusic () {
|
||||
const { piezeNotes, setNotes } = useContext(BoardControllerContext);
|
||||
|
||||
const getPiezo = (pin: number | string) => {
|
||||
return piezeNotes.find(val => val.pin == pin);
|
||||
}
|
||||
|
||||
const addPiezo = (notes: PiezoMusic) => {
|
||||
const newPiezo = [...piezeNotes, notes];
|
||||
setNotes!(newPiezo);
|
||||
}
|
||||
|
||||
const setPiezo = (index: number, piezo: PiezoMusic) => {
|
||||
setNotes!(piezos => piezos.map((_piezo, i) => {
|
||||
if (i == index) return piezo;
|
||||
return _piezo;
|
||||
}));
|
||||
}
|
||||
|
||||
const removePiezo = (index: number) => {
|
||||
const newPiezo = piezeNotes.filter((_piezo, i) => i != index);
|
||||
setNotes!(newPiezo);
|
||||
}
|
||||
|
||||
const addNote = (piezoIndex: number, note: string) => {
|
||||
setNotes!(piezos => piezos.map((piezo, i) => {
|
||||
if (piezoIndex == i) {
|
||||
piezo.notes.push(note);
|
||||
}
|
||||
return piezo;
|
||||
}));
|
||||
}
|
||||
|
||||
const changeNote = (piezoIndex: number, noteIndex: number, note: string) => {
|
||||
setNotes!(piezos => piezos.map((piezo, i) => {
|
||||
if (piezoIndex == i) {
|
||||
piezo.notes[noteIndex] = note;
|
||||
}
|
||||
return piezo;
|
||||
}));
|
||||
}
|
||||
|
||||
const removeNote = (piezoIndex: number, noteIndex: number) => {
|
||||
setNotes!(piezos => piezos.map((piezo, i) => {
|
||||
if (piezoIndex == i) {
|
||||
piezo.notes = piezo.notes.filter((_p, i) => i != noteIndex);
|
||||
}
|
||||
return piezo;
|
||||
}));
|
||||
}
|
||||
|
||||
const setPin = (index: number, newPin: string | number) => {
|
||||
const newPiezo: PiezoMusic[] = piezeNotes.map((piezo, i) => {
|
||||
if (i == index) {
|
||||
piezo.pin = newPin;
|
||||
}
|
||||
return piezo;
|
||||
})
|
||||
setNotes!(newPiezo);
|
||||
}
|
||||
|
||||
const setName = (index: number, name: string) => {
|
||||
const newPiezo: PiezoMusic[] = piezeNotes.map((piezo, i) => {
|
||||
if (i == index) {
|
||||
piezo.name = name;
|
||||
}
|
||||
return piezo;
|
||||
})
|
||||
setNotes!(newPiezo);
|
||||
}
|
||||
|
||||
const setBeat = (index: number, beats: number) => {
|
||||
const newPiezo: PiezoMusic[] = piezeNotes.map((piezo, i) => {
|
||||
if (i == index) {
|
||||
piezo.beats = beats;
|
||||
}
|
||||
return piezo;
|
||||
})
|
||||
setNotes!(newPiezo);
|
||||
}
|
||||
|
||||
const setTempo = (index: number, tempo: number) => {
|
||||
const newPiezo: PiezoMusic[] = piezeNotes.map((piezo, i) => {
|
||||
if (i == index) {
|
||||
piezo.tempo = tempo;
|
||||
}
|
||||
return piezo;
|
||||
})
|
||||
setNotes!(newPiezo);
|
||||
}
|
||||
|
||||
return {
|
||||
piezeNotes, setNotes, getPiezo, addPiezo, removePiezo, setPiezo,
|
||||
setPin, setName, setBeat, setTempo,
|
||||
addNote, changeNote, removeNote
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
export function useServo () {
|
||||
const { motoServo, setMotoServo } = useContext(BoardControllerContext);
|
||||
|
||||
const getServo = (pin: number | string) => {
|
||||
return motoServo.find(val => val.pin == pin);
|
||||
}
|
||||
|
||||
const addServo = (pin: number | string, state: number) => {
|
||||
const newPesistor = [...motoServo, { pin, state }];
|
||||
setMotoServo!(newPesistor);
|
||||
}
|
||||
|
||||
const removeServo = (index: number) => {
|
||||
const newPesistor = motoServo.filter((_servo, i) => i != index);
|
||||
setMotoServo!(newPesistor);
|
||||
}
|
||||
|
||||
const setDegree = (pin: number | string, state: number) => {
|
||||
const newPiezo = motoServo.map(servo => {
|
||||
if (servo.pin == pin) return { pin, state };
|
||||
return servo;
|
||||
})
|
||||
setMotoServo!(newPiezo);
|
||||
}
|
||||
|
||||
const setServoPin = (index: number, newPin: number | string) => {
|
||||
const newPesistor = motoServo.map((servo, i) => {
|
||||
if (i == index) return { pin: newPin, state: servo.state };
|
||||
return servo;
|
||||
})
|
||||
setMotoServo!(newPesistor);
|
||||
}
|
||||
|
||||
return { motoServo, getServo, addServo, removeServo, setDegree, setServoPin };
|
||||
}
|
||||
|
||||
|
||||
export function usePhotoresistor () {
|
||||
const { photoresistor, setPhotoresistor } = useContext(BoardControllerContext);
|
||||
|
||||
const getResistor = (pin: number | string) => {
|
||||
return photoresistor.find(val => val.pin == pin);
|
||||
}
|
||||
|
||||
const addResistor = (pin: number | string, state: number) => {
|
||||
const newPesistor = [...photoresistor, { pin, state }];
|
||||
setPhotoresistor!(newPesistor);
|
||||
}
|
||||
|
||||
const removeResistor = (index: number) => {
|
||||
const newPesistor = photoresistor.filter((_resist, i) => i != index);
|
||||
setPhotoresistor!(newPesistor);
|
||||
}
|
||||
|
||||
const setResistance = (pin: number | string, state: number) => {
|
||||
const resistor = photoresistor.map(resist => {
|
||||
if (resist.pin == pin) return { pin: resist.pin, state };
|
||||
return resist;
|
||||
})
|
||||
setPhotoresistor!(resistor);
|
||||
}
|
||||
|
||||
const setResistorPin = (index: number, newPin: number | string) => {
|
||||
const newPesistor = photoresistor.map((resist, i) => {
|
||||
if (i == index) return { pin: newPin, state: resist.state };
|
||||
return resist;
|
||||
})
|
||||
setPhotoresistor!(newPesistor);
|
||||
}
|
||||
|
||||
return { photoresistor, getResistor, addResistor, removeResistor, setResistance, setResistorPin };
|
||||
}
|
3
src/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import main from "./app";
|
||||
|
||||
main();
|
10
src/main.tsx
@ -1,10 +0,0 @@
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom/client'
|
||||
import App from './App.tsx'
|
||||
import './css/index.css'
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>,
|
||||
)
|
@ -1,4 +1,4 @@
|
||||
export const pitch = {
|
||||
export const Pitch = {
|
||||
B0: 31,
|
||||
C1: 33,
|
||||
CS1: 35,
|
16
src/middleware/connection.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { NextFunction, Request, Response } from "express";
|
||||
import { suBoard } from "../setup";
|
||||
|
||||
const { board } = suBoard;
|
||||
|
||||
export function isBoardConnected (req: Request, res: Response, next: NextFunction) {
|
||||
if (suBoard.connected) {
|
||||
next();
|
||||
}
|
||||
else {
|
||||
return res.status(500).json({
|
||||
status: 500,
|
||||
message: "Internal server error: No Boards connected"
|
||||
})
|
||||
}
|
||||
}
|
35
src/middleware/pin.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import { NextFunction, Request, Response } from "express";
|
||||
import { suBoard } from "../setup";
|
||||
|
||||
export function isPinNumeric (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'
|
||||
});
|
||||
}
|
||||
|
||||
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({
|
||||
status: 400,
|
||||
message: `Pin ${pin}, is used`
|
||||
});
|
||||
}
|
||||
|
||||
next();
|
||||
}
|
@ -1,45 +0,0 @@
|
||||
import { useRef } from "react";
|
||||
import MainBody from "../components/MainBody";
|
||||
|
||||
// Section components
|
||||
import Hero from "../components/landing/Hero";
|
||||
import ControlLED from "../components/landing/ControlLED";
|
||||
|
||||
import LunarImg from "../assets/img/ocs/lunar-oc.png";
|
||||
|
||||
import ControlNav from "../data/control-navigation.json";
|
||||
import ControlRgbLed from "../components/landing/ControlRgbLed";
|
||||
import ControlPiezo from "../components/landing/ControlPiezo";
|
||||
import ControlPhotoresistor from "../components/landing/ControlPhotoresistor";
|
||||
import ControlServo from "../components/landing/ControlServo";
|
||||
import Closing from "../components/landing/Closing";
|
||||
import Footer from "../components/Footer";
|
||||
import ControlPiezoMusicEditor from "../components/landing/ControlPiezoMusicEditor";
|
||||
|
||||
function MainPage () {
|
||||
const led = useRef<HTMLDivElement | null>(null);
|
||||
const rgbLed = useRef<HTMLDivElement | null>(null);
|
||||
const piezo = useRef<HTMLDivElement | null>(null);
|
||||
const servo = useRef<HTMLDivElement | null>(null);
|
||||
const photoresistor = useRef<HTMLDivElement | null>(null)
|
||||
|
||||
return (<>
|
||||
<MainBody>
|
||||
<Hero
|
||||
img={LunarImg}
|
||||
refs={[led, rgbLed, piezo, servo, photoresistor]}
|
||||
shortNav={ControlNav}
|
||||
/>
|
||||
<ControlLED refto={led} />
|
||||
<ControlRgbLed refto={rgbLed} />
|
||||
<ControlPiezo refto={piezo} />
|
||||
<ControlPiezoMusicEditor />
|
||||
<ControlServo refto={servo} />
|
||||
<ControlPhotoresistor refto={photoresistor} />
|
||||
<Closing />
|
||||
<Footer />
|
||||
</MainBody>
|
||||
</>)
|
||||
}
|
||||
|
||||
export default MainPage;
|
23
src/ports/index.ts
Normal 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
@ -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
@ -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
|
||||
}
|
||||
})
|
||||
}
|
23
src/promises/index.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { Board } from "johnny-five";
|
||||
|
||||
export function digitalRead (board: Board, pin: number): Promise<number> {
|
||||
return new Promise((resolve, reject) => {
|
||||
board.digitalRead(pin, (val) => {
|
||||
resolve(val);
|
||||
});
|
||||
setTimeout(() => {
|
||||
reject(new Error("Reading digital timeout"));
|
||||
}, 5000);
|
||||
});
|
||||
}
|
||||
|
||||
export function analogRead (board: Board, pin: string): Promise<number> {
|
||||
return new Promise((resolve, reject) => {
|
||||
board.analogRead(pin, (val) => {
|
||||
resolve(val);
|
||||
})
|
||||
setTimeout(() => {
|
||||
reject(new Error("Reading analog timeout"));
|
||||
}, 5000);
|
||||
})
|
||||
}
|
55
src/routes/api.ts
Normal file
@ -0,0 +1,55 @@
|
||||
import { Response } from "express";
|
||||
import { Router } from "express";
|
||||
|
||||
import { readPin, readPins, setPin, setPins } from "../controller/basic/pin";
|
||||
import { digitalRead, digitalWrite } from "../controller/basic/digital";
|
||||
import { analogRead, analogWrite } from "../controller/basic/analog";
|
||||
|
||||
import { readLed, readRgbLed, writeLed, writeRgbLed } from "../controller/components/led";
|
||||
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('/pins', readPins);
|
||||
router.patch('/pin/:p/:m', setPin);
|
||||
router.patch('/pins', setPins);
|
||||
|
||||
// Digital read/write
|
||||
router.get('/digital/:pin', digitalRead);
|
||||
router.patch('/digital', digitalWrite);
|
||||
|
||||
// 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);
|
||||
|
||||
// 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;
|
9
src/routes/view.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { Request, Response, Router } from "express";
|
||||
|
||||
const router = Router();
|
||||
|
||||
router.get('/', (req: Request, res: Response) => {
|
||||
res.sendFile('./client/index.html', { root: __dirname + "../../" });
|
||||
})
|
||||
|
||||
export default router;
|
73
src/server.ts
Normal 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
@ -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: "*"
|
||||
}
|
||||
}
|
14
src/setup/index.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { SuBoard } from './support';
|
||||
import { Board, BoardOption } from 'johnny-five';
|
||||
import { config } from 'dotenv';
|
||||
|
||||
config();
|
||||
|
||||
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,
|
||||
};
|
25
src/setup/support.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import { Board } from "johnny-five";
|
||||
|
||||
export interface SupportBoard {
|
||||
port: string | null,
|
||||
connected: boolean,
|
||||
PINS: {
|
||||
digital: number[],
|
||||
analog: number[],
|
||||
}
|
||||
sort: () => void
|
||||
}
|
||||
|
||||
export class SuBoard implements SupportBoard {
|
||||
public board: Board | null;
|
||||
public port = null;
|
||||
public connected = false;
|
||||
public PINS = {
|
||||
digital: [],
|
||||
analog: []
|
||||
};
|
||||
public sort() {
|
||||
this.PINS.digital = this.PINS.digital.sort((a, b) => b - a);
|
||||
this.PINS.analog = this.PINS.analog.sort((a, b) => b - a);
|
||||
}
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
import { connect } from "socket.io-client";
|
||||
|
||||
export const io = connect("http://localhost:3000");
|
@ -1,28 +0,0 @@
|
||||
export type PinMode = {
|
||||
pin: string | number,
|
||||
mode: 'INPUT' | 'OUTPUT' | 'SERVO'
|
||||
}
|
||||
|
||||
export type PinState = {
|
||||
pin: number | string,
|
||||
state: boolean
|
||||
}
|
||||
|
||||
export type DynamicPinState = {
|
||||
pin: string | number,
|
||||
state: number
|
||||
}
|
||||
|
||||
export interface ChannelPinState {
|
||||
red: PinState,
|
||||
green: PinState,
|
||||
blue: PinState
|
||||
}
|
||||
|
||||
export interface PiezoMusic {
|
||||
name: string,
|
||||
pin: number | string;
|
||||
notes: string[];
|
||||
beats: number;
|
||||
tempo: number;
|
||||
}
|
5
src/utils/network.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { address } from 'ip';
|
||||
|
||||
export function getPublicIp () {
|
||||
return address("public", "ipv4") || '127.0.0.1';
|
||||
}
|
1
src/vite-env.d.ts
vendored
@ -1 +0,0 @@
|
||||
/// <reference types="vite/client" />
|
@ -1,34 +0,0 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
export default {
|
||||
content: [
|
||||
"./index.html",
|
||||
"./src/**/*.{ts,tsx,js,jsx}"
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
'primary': '#3A3AFF',
|
||||
'secondary': '#0B0B1D66',
|
||||
'secondary-solid': '#0B0B1D',
|
||||
'finn': '#00000066',
|
||||
'finn-solid': '#000000',
|
||||
'danger': '#FF016C',
|
||||
'disabled': '#7373B0',
|
||||
'border': '#FFFFFF66',
|
||||
'background': '#06060C'
|
||||
},
|
||||
fontFamily: {
|
||||
'poppins': '"Poppins", sans-serif',
|
||||
'roboto-mono': '"Roboto Mono", monospace',
|
||||
},
|
||||
animation: {
|
||||
'size-in': 'size-in .3s ease-in-out',
|
||||
'fade-in': 'fade-in .3s ease-in-out',
|
||||
'size-fade-in': 'size-fade-in .3s ease-in-out',
|
||||
'wewew': 'wewew .9s ease-in-out infinite',
|
||||
}
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
}
|
||||
|
13
test/serial/blink.ts
Normal 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
@ -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
@ -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");
|
||||
})
|
15
test/serial/photoresistor.ts
Normal 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
@ -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);
|
||||
})
|
||||
})
|
25
test/serial/rgbLed.js
Normal 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
@ -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;
|
||||
}
|
||||
})
|
||||
});
|
19
test/serial/servo.ts
Normal 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
@ -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");
|
||||
})
|
@ -1,25 +1,11 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"include": ["src"],
|
||||
"references": [{ "path": "./tsconfig.node.json" }]
|
||||
}
|
||||
"compilerOptions": {
|
||||
"module": "NodeNext",
|
||||
"moduleResolution": "NodeNext",
|
||||
"target": "ES2020",
|
||||
"sourceMap": true,
|
||||
"outDir": "dist"
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["test/**/*"]
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"skipLibCheck": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
})
|