Interface and Application Programming
Task
Group assignment:
- Send a message between two projects
- Document your work to the group work page and reflect on your individual page what you learned
Individual assignment:
- Design, build and connect wired or wireless node(s) with network or bus addresses and a local interface
Framework?
Flutter is Dart-based and compiled.
React Native is JS-based (you can use TypeScript) and interpreted.
What have other participants done?
I've checked a few assignments. They mostly use processing (specifically P5.js). Some write their apps in vanilla html + jss. One used streamlit! which is intriguing because it's super easy, but I don't think it can run in the ESP32 (or can it?). Another used QT.
https://fabacademy.org/2024/labs/waag/students/joe-wallace/Assignments/week14/#individual-assignment
In the end I asked Tony for his opinion and I went for React.
Setup and creation of the basic React app
For this, I'm following instructions by ChatGPT until they break.
1. Install node.js
I'm installing it with conda in order not to mess with my system.
$> conda create -n react
$> conda activate react && conda install nodejs -c conda-forge
$> npm --version
10.5.0
2. Create React project
First, navigate to the mother folder and then create the project with:
$> npx create-react-app react-gui --template typescript
$> cd react-gui
3. Start the Development Server
npm start
4. Install additional modules
Recommended by ChatGPT
$> npm install react-router-dom
$> npm install @types/react-router-dom
$> npm install styled-components
$> npm install @types/styled-components
$> npm install axios
5. Set up develompment environment
I'm installing Sublime, as recommended by Tony.
- Download Sublime.
- Install Package Control (in the
Tools
section) - Install recommended (by ChatGPT) packages:
- Open Command Palette (command-shift-p)
- Package Control: Advanced Install Package
-
TypeScript, Babel, ESLint, Prettier, Emmet, SublimeLinter, SublimeLinter-contrib-eslint.
-
Open
react-gui/src/App.tsx
.
Done!
6. Play around, check it's working
I've run the default app by executing:
conda activate react && npm start
After playing with it and touching some things up, this is what I get:
Seems like I've got everything set up!
Running a React app from an ESP32
Plain text html served from the ESP32
My first step has been to basically copy the code from the networking class:
Basic web server. Click to show code.
#include "WiFi.h"
#include "ESPAsyncWebServer.h"
// Set your access point network credentials
const char* ssid = "bonobo-access-point";
const char* password = "123456789";
// Create AsyncWebServer object on port 80
AsyncWebServer server(80);
void setup(){
// Serial port for debugging purposes
Serial.begin(115200);
Serial.println();
// Setting the ESP as an access point
Serial.print("Setting AP (Access Point)…");
// Remove the password parameter, if you want the AP (Access Point) to be open
WiFi.softAP(ssid, password);
IPAddress IP = WiFi.softAPIP();
Serial.print("AP IP address: ");
Serial.println(IP);
server.on("/button", HTTP_GET, [](AsyncWebServerRequest *request){
request->send_P(200, "text/plain", "hey there");
});
// Start server
server.begin();
}
void loop() {
digitalWrite(LED_BUILTIN, HIGH);
delay(250);
digitalWrite(LED_BUILTIN, LOW);
delay(250);
}
It can be connected to from my phone!
I've removed a button that was there in the original example because React will allow me to grow the app farther.
Next step, deploying the React app!
React on ESP32
I'm following the following Medium post: Arduino Full Stack Tutorial, by the author of aWOT, a "fast, unopinionated, minimalist web server library for Arduino.". Others that seem like they could be very interesting is React on the ESP32 with PlatformIO and Building a live data feed BLE application with React Native and ESP32, but I'm not following them for now.
To deploy the React app:
Create the .ino sketch
Run from the React project root folder:
$> npm install awot-scripts -g
$> npx awot-create BonoboServer
This creates a BonoboServer
folder inside with auto-generated code:
$> ll BonoboServer
total 120
-rw-r--r-- 1 dani staff 529B 14 jun 14:15 BonoboServer.ino
-rw-r--r-- 1 dani staff 421B 14 jun 14:15 StaticFiles.h
-rw-r--r-- 1 dani staff 37K 14 jun 14:15 aWOT.cpp
-rw-r--r-- 1 dani staff 10K 14 jun 14:15 aWOT.h
`StaticFiles.h` contains the html source and a description of the routes. Click to show code.
void static_index(Request &req, Response &res) {
P(index) =
"<html>\n"
"<head>\n"
"<title>Hello World!</title>\n"
"</head>\n"
"<body>\n"
"<h1>Greetings middle earth!</h1>\n"
"</body>\n"
"</html>";
res.set("Content-Type", "text/html");
res.printP(index);
}
Router staticFileRouter;
Router * staticFiles() {
staticFileRouter.get("/", &static_index);
return &staticFileRouter;
}
After uploading the code, accessing from my laptop the IP shown in the Serial monitor will show:
Then, it's a matter of porting the React app into that skeleton
Api endpoints
From "Back-end Development" in the Arduino Full Stack Tutorial: to create an api endpoint we need to include in the .ino
:
- A function that takes a
Request &req
and aResponse &res
. It can then read or write like this:req.read()
,res.write()
. - A line associating those to the app:
c app.get("/led", &readLed); app.put("/led", &updateLed);
app.use(staticFiles());
My server will send a message through I2C to the spoke unit. That is, I need a nextPattern
API endpoint that will do that. For now, I'll simulate that with a toggleLED
function and a /toggle
endpoint:
void setup() {
pinMode(LED_PIN, OUTPUT);
Serial.begin(115200);
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println(WiFi.localIP());
app.use(staticFiles());
app.get("/toggle", &toggleLED);
server.begin();
}
void toggleLED(Request &req, Response &res){
digitalWrite(LED_PIN, !digitalRead(LED_PIN));
Serial.println("Toggle");
}
So far, so good:
Done!
Front-end
- Dev frontend locally, route API calls to the board: change proxy for the API calls. In
package.json
, set "proxy" to the board IP:
`package.json`. Click to show code.
{
"name": "react-gui",
"version": "0.1.0",
"private": true,
"dependencies": {
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"@types/jest": "^27.5.2",
"@types/node": "^16.18.98",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-scripts": "5.0.1",
"typescript": "^4.9.5",
"web-vitals": "^2.1.4"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"proxy": "http://172.16.21.147/"
}
I'm going to skip installing react-toggle-button
from the Arduino Full Stack Tutorial. It uses state, which I don't want ot to use until I am ready to fully understand it. I'll use a regular button.
-
App.js
: ```ts import React from 'react'; import logo from './logo.svg'; import './App.css';function App() { return (
Click on the button to change pattern.
function toggleLED(){ fetch('/toggle') .then(response => console.log(response.text)) }
export default App; ```
-
Run the app locally with
npm start
from the root folder - Profit!
This corresponds to this commit in the bonobo-lights repo.
Building and deploying on the board itself
Now I need to integrate the React app into the generated BonoboServer.ino
code, so that clicking the button will exercise the toggle
API endpoint and everything will be running in the board.
Execute npm run build
in the root folder of the React project:
$> npm run build
> react-gui@0.1.0 build
> react-scripts build
Creating an optimized production build...
Compiled successfully.
File sizes after gzip:
46.55 kB (+39 B) build/static/js/main.be0d81ad.js
1.78 kB build/static/js/453.bcf1b5ed.chunk.js
464 B build/static/css/main.0aed41c0.css
The project was built assuming it is hosted at /.
You can control this with the homepage field in your package.json.
The build folder is ready to be deployed.
You may serve it with a static server:
npm install -g serve
serve -s build
Arduino Full Stack Tutorial says: "To convert the files in the build directory to a Arduino compatible format we need first add some configurations to the package.json".
package.json
: add this to the "scripts" object
"dist": "awot-static"
and this to the root, after "proxy":
"awot-static": {
"sources": "./build",
"indexFile": "index.html",
"sketchDir": "./BonoboServer",
"exclude": [
"*.map",
"service-worker.js"]
}
BonoboServer
is the name of the folder I created earlier with npx awot-create BonoboServer
. It will be different for diferent projects.
Now, we're ready to do:
npm run dist
This integrates the React project into the StaticFiles.h
, so it's ready to be uploaded with the Arduino IDE and run.
IT WORKS!!!
Next step: integrate into bonobo-lights.ino
For my project, I'm going to implement an API endpoint that will change to the next pattern. The process will be:
- User clicks on button.
- Server running on hubunit sends "nextPattern" message through I2C to the spoke unit.
- Spoke unit executes its
nextPattern()
function.