Skip to content

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

Group assignment page.

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.

  1. Download Sublime.
  2. Install Package Control (in the Tools section)
  3. Install recommended (by ChatGPT) packages:
  4. Open Command Palette (command-shift-p)
  5. Package Control: Advanced Install Package
  6. TypeScript, Babel, ESLint, Prettier, Emmet, SublimeLinter, SublimeLinter-contrib-eslint.

  7. 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 a Response &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

  1. 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.

  1. App.js: ```ts import React from 'react'; import logo from './logo.svg'; import './App.css';

    function App() { return (

    logo

    Click on the button to change pattern.

    ); }

    function toggleLED(){ fetch('/toggle') .then(response => console.log(response.text)) }

    export default App; ```

  2. Run the app locally with npm start from the root folder

  3. 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:

  1. User clicks on button.
  2. Server running on hubunit sends "nextPattern" message through I2C to the spoke unit.
  3. Spoke unit executes its nextPattern() function.

References