[ESP32×React] 在M5stack上轻松地创建React应用体验一下

一开始

做好的东西 (zuò de

Animation.gif

在计算机屏幕上显示着”Counter Value”,但这个数字是由React应用程序驱动的。
由于是使用React编写的,所以无需不断刷新浏览器,JavaScript会动态地更改值。
为了测试延迟,也在微控制器的显示屏上显示相同的数字。

所制作的系统图

image.png

我的计算机作为Web服务器分发HTML文件,接收到这些HTML文件的浏览器会运行其中嵌入的React程序。
这个React程序会接收来自计算机的WebSocket传输的数据,并根据这些数据更新浏览器的DOM(即浏览器的渲染结果)。

稍后我会详细解释。

“簡單的”意思是

我决定提供一个简单的方法,将React应用程序在ESP32上运行起来。

我尝试通过嵌入React到一个HTML文件中来进行实现。

基本的な原理は、Reactを使用してHTMLファイルを提供する方法は変わりません。

环境

单一选择:
M5Stack TOUGH 是我的单片机。
我使用的开发环境是 PlatformIO IDE。
我使用的电脑是 Windows11。
我在Google Chrome(Windows11)上运行了React。

代码解释

提供HTML页面

首先,我们将开始提供HTML页面的传送。
关于Web服务器的说明,我们将在本文中解释。

 

这次我使用了这样的代码进行实现。
我使用了名为ESPAsyncWebserver的模块。

#include <Arduino.h>
#include <M5Unified.h>
#include <WiFi.h>
#include "ESPAsyncWebServer.h"
#include <SPIFFS.h> // SPIFFS(ファイルシステム)用のライブラリをインクルード

// WebServer server(80); // ポート80でWebサーバーを開始
AsyncWebServer server(80);

void setup()
{
  M5.begin();
  M5.Power.begin();
  int i = 1;
  Serial.begin(115200);
  Serial.print("Connecting");
  while (WiFi.status() != WL_CONNECTED)
  {
    if (i++ > 1)
    { // 10 seconds
      Serial.println("Smart Config Start!");
      WiFi.beginSmartConfig();
      while (!WiFi.smartConfigDone())
      {
        delay(500);
        Serial.print(".");
      }
      Serial.println("Smart Config Done!");
    }
    delay(500);
    Serial.print(".");
  }

  Serial.println("WiFi Connected.");

  Serial.print("IP Address: ");
  Serial.println(WiFi.localIP());

  // SPIFFS(ファイルシステム)を初期化
  if (SPIFFS.begin())
  {
    Serial.println("SPIFFS initialized.");
  }
  else
  {
    Serial.println("Error initializing SPIFFS.");
  }
  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request)
            { request->send(SPIFFS, "/index.html", String(), false); });
  server.begin();
}

void loop()
{
}

HTML文件可以随便写,甚至一行也可以。

<h1>Hello World!</h1>

为了希望看到其他文件的人们,我在这里放置了源代码。

 

我把HTML文件放在ESP32内部的闪存中。

使用PlatformIO时,如果要使用闪存,请阅读以下文章。

 

顺便一提,”Upload Filesystem Image”指的是在打开VSCode的Ant标志时出现的窗口的上部,在PROJECT TASKS/m5stack-core2/Platform/Upload Filesystem Image路径下。

我們使用 SmartConfig 來進行 Wi-Fi 連接。

如果不清楚SmartConfig是什么,请查看以下的URL链接。

 

image.png

当你将该IP地址后面显示的值输入到与同一WiFi连接的手机或电脑的浏览器中进行访问时,你可以查看HTML文件。

以React应用程序的发布

我想要将React应用嵌入到HTML文件中进行分发。

(Note: This sentence may vary depending on the context. The translated version provided here is a general option.)

普通的React应用程序是通过使用express、Next.js或者node.js来进行服务器分发的,但实际上也可以通过嵌入HTML来进行分发。

代替嵌入的同时还会限制功能,不过。

请参考下面的页面。

 

抱歉,这个情报有点过时,但还是可用的。

这次使用了TypeScript,但也可以用Javascript。

我写了一个随便的代码,这个方法不需要修改main.cpp,只需编辑在闪存中写入的html文件。

<!DOCTYPE html>
<head>
  <meta charset="utf-8">
  <title>React tsx-esm-standalone</title>
  <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
  <script>
    // typescript用のpresetを登録する
    Babel.registerPreset('tsx', {
      presets: [
        [Babel.availablePresets['typescript'], // preset-typescriptを指定
          {allExtensions: true, isTSX: true}   // allExtensionsは、isTSX利用時に必要なためセット
        ]],
      },
    );
  </script>
  <script type="text/babel" data-type="module" data-presets="tsx,react">
    import React from "https://cdn.skypack.dev/react@17";
    import ReactDOM from "https://cdn.skypack.dev/react-dom@17";
    const Button = () => {
      function handleClick() {
        alert('You clicked me!');
      }

      return (
        <button onClick={handleClick}>
          Click me
        </button>
      );
    }
    ReactDOM.render(<Button />, document.getElementById('app'));
  </script>
</head>
<body>
  <div id="app"></div>
</body>
</html>

这是React教程中的一个代码片段,它设定了一个按钮,当你点击按钮时会弹出一个“你点击了我!”的弹窗。

我会把全部的源代码都放在这里。

 

如果将这个内容像之前一样写入闪存中,即使不修改main.cpp文件,也可以运行react应用。

使用WebSocket进行数据传递

写解说文章觉得有点丢人,实际上我对WebSocket并不是很了解。
只是在用Node.js制作某个工具时,稍微接触了一次。
更不用说在ESP32上,从来没有接触过。
所以我请ChatGPT小伙伴给我写了个规格说明,请原谅它的代码可能不够优雅。

以下是我写的代码。

#include <Arduino.h>
#include <M5Unified.h>
#include <WiFi.h>
#include <WebSocketsServer.h>
#include "ESPAsyncWebServer.h"
#include <SPIFFS.h> // SPIFFS(ファイルシステム)用のライブラリをインクルード

const int webSocketPort = 81;
WebSocketsServer webSocket = WebSocketsServer(webSocketPort);

// WebServer server(80); // ポート80でWebサーバーを開始
AsyncWebServer server(80);

void webSocketEvent(uint8_t num, WStype_t type, uint8_t *payload, size_t length)
{
  switch (type)
  {
  case WStype_CONNECTED:
    Serial.printf("WebSocket client connected: %u\n", num);
    break;
  case WStype_TEXT:
    // Handle text messages from WebSocket clients if needed
    break;
  case WStype_DISCONNECTED:
    Serial.printf("WebSocket client disconnected: %u\n", num);
    break;
  }
}

void setup()
{
  M5.begin();
  M5.Power.begin();
  int i = 1;
  Serial.begin(115200);
  Serial.print("Connecting");
  while (WiFi.status() != WL_CONNECTED)
  {
    if (i++ > 1)
    { // 10 seconds
      Serial.println("Smart Config Start!");
      WiFi.beginSmartConfig();
      while (!WiFi.smartConfigDone())
      {
        delay(500);
        Serial.print(".");
      }
      Serial.println("Smart Config Done!");
    }
    delay(500);
    Serial.print(".");
  }

  Serial.println("WiFi Connected.");

  Serial.print("IP Address: ");
  Serial.println(WiFi.localIP());

  // SPIFFS(ファイルシステム)を初期化
  if (SPIFFS.begin())
  {
    Serial.println("SPIFFS initialized.");
  }
  else
  {
    Serial.println("Error initializing SPIFFS.");
  }
  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request)
            { request->send(SPIFFS, "/index.html", String(), false); });
  server.begin();

  webSocket.begin();
  webSocket.onEvent(webSocketEvent);
}

void loop()
{
  webSocket.loop();
  if (millis() % 1000 == 0)
  {
    String message = String(millis() / 1000);
    webSocket.broadcastTXT(message);
  }
}

本次是通过函数mills()获取程序执行的时间,并且每秒通过WebSocket将该数字发送到浏览器上。

接收WebSocket通信的HTML位于此处。

<!DOCTYPE html>
<head>
  <meta charset="utf-8">
  <title>React tsx-esm-standalone</title>
  <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
  <script>
    const WebSocketUrl = window.location.href.replace("http", "ws").slice(0,-1) + ":81";
  </script>
  <script>
    // typescript用のpresetを登録する
    Babel.registerPreset('tsx', {
      presets: [
        [Babel.availablePresets['typescript'], // preset-typescriptを指定
          {allExtensions: true, isTSX: true}   // allExtensionsは、isTSX利用時に必要なためセット
        ]],
      },
    );
  </script>
  <script type="text/babel" data-type="module" data-presets="tsx,react">
    import React, { useState } from "https://cdn.skypack.dev/react@17";
    import ReactDOM from "https://cdn.skypack.dev/react-dom@17";
    const Count = () => {
      const [counter, setCounter] = React.useState(0);
    
      React.useEffect(() => {
        const socket = new WebSocket(WebSocketUrl); // M5StackのIPアドレスを指定
    
        socket.onmessage = (event) => {
          const data = event.data;
          setCounter(data);
        };
    
        return () => {
          socket.close();
        };
      }, []);
    
      return (
        <div>
          <h1>Counter Value: {counter}</h1>
        </div>
      );
    };
    
    ReactDOM.render(<Count />, document.getElementById('app'));
  </script>
</head>
<body>
  <div id="app"></div>
</body>
</html>

WebSocket的URL是ws://{microcontroller的IP地址}:{main.cpp中设置的端口号}。
为了动态地获取它而不受环境的影响,我们使用变量window.location.href来获取页面的URL。具体来说,可以用window.location.href来获取http://{microcontroller的IP地址}/的信息。然后,为了将其更改为WebSocket的URL,我们将http替换为ws,删除末尾的/,并添加:81。
这就是源代码中的

const WebSocketUrl = window.location.href.replace("http", "ws").slice(0,-1) + ":81";

是的。

然后,使用React来动态修改WebSocket传来的值的代码。此部分解释将被省略。

以下是源代码,其中包括了在微控制器上显示处理以及对WiFi连接代码的微小更改。

 

将HTML文件写入闪存,将main.cpp写入微控制器,连接WiFi并将显示的IP地址输入浏览器,值将每秒更新一次。

关于地毯

Animation.gif

总结

能够从自己的手机或电脑上远程查看传感器信息真是一个美好的梦想啊。

推广

现在,新泻大学学生方程式项目正在招募有意成为赞助商的企业和个人。

在我们学校里有许多具备从编程到焊接等多种技能的学生。

请您直接联系下面的电子邮件地址:next-fp@eng.niigata-u.ac.jp。

广告
将在 10 秒后关闭
bannerAds