22浏览
查看: 22|回复: 2

[项目] 【Arduino 动手做】ESP32 到 ESP32 的 Internet 通信

[复制链接]
本帖最后由 驴友花雕 于 2025-6-23 17:02 编辑

在设备之间建立安全、P2P、低延迟的连接。连接到第一个 ESP32 的按钮控制连接到第二个 ESP32 的 LED。一个简单的项目模板,演示如何在两个基于 ESP32 的设备之间建立连接。在 LAN 和 Internet 中均可工作。

【编辑:2021 年 7 月 9 日】该项目已得到改进、修复并从 ArduinoIDE 移植到 platformio。下面评论中的问题应该已经修复。

通常,互联项目具有某种 Web 或移动 UI。如果您想通过另一个事物控制一件事,尤其是通过低延迟和互联网,这很难实现。这就是我创建这个项目的原因。这是一个 Arduino 框架模板,向您展示如何通过 Internet 连接两个基于 ESP32 的开发板,在 Wi-Fi 连接断开或其中一个连接的开发板临时断电的情况下,通过自动恢复功能最大限度地减少延迟。很酷的是,如果 ESP32 板位于同一 Wi-Fi 网络中,并且位于不同的网络中,它就可以正常工作。即使在不同的大陆。

我们在这里描述的模板可以作为基于 ESP32 的项目的各种酷炫接口的基础,例如:

智能手套控制您的 RC 汽车
远程控制您的智能家居设备
安全且私密的 Wi-Fi 密钥到您家(当连接为 P2P 时,任何第三方都无法访问加密密钥)
一个非常快速的 Internet 按钮,可访问您的物品
以及更多、更多。

该模板的默认功能是通过 ESP32 板的按钮对 LED 进行双向控制。您还可以将此模板视为摩斯电码 Internet 通信器:)。请随意将代码替换为您需要的任何输入/输出作来控制按钮和 LED。

运作方式
ESP32 既可以用作 HTTP 服务器(基于 库),也可以用作 HTTP 客户端(基于 ESPAsyncWebServerAsyncTCP)
ESP32 会自动检测同一 Husarnet VPN 网络中的所有对等体
按下该按钮时,HTTP 请求将发送到所有其他对等体并打开 LED
释放按钮后,HTTP 请求将发送到所有其他对等体,并关闭 LED

Wi-Fi 任务
如果当前连接断开,则写入 Wi-Fi 任务以自动切换到另一个 Wi-Fi 网络。在配置部分,您可以对多个 Wi-Fi 网络凭证进行硬编码 - 这是一个舒适的解决方案,因为如果您在不同位置打开电路板,则无需重新编程电路板。

基本上,ESP32 设备之间的虚拟 LAN 网络是通过以下两条线路创建的:

  1. Husarnet.join(husarnetJoinCode, hostNameX);
  2. Husarnet.start();
复制代码

Connection 也是完全加密、安全和私有的。它不仅可以在 LAN 中运行,还可以通过 Internet 运行,因为连接由 Husarnet 提供支持 - 一个开源 P2P VPN 客户端,它非常轻量级,不仅适用于普通计算机,也适用于 ESP32 微控制器。Husarnet 仅有助于通过 Internet 建立连接,用户数据不会由其服务器转发。因此延迟更低。

HTTP 服务器

  1. // A dummy web server (see index.html)
  2. server.on("/", HTTP_GET, [](AsyncWebServerRequest* request) {
  3.     request->send(200, "text/html", html);
  4. });
  5. // Send a GET request to <IP>/led/<number>/state/<0 or 1>
  6. server.on("^\\/led\\/([0-9]+)\\/state\\/([0-9]+)$", HTTP_GET,
  7. [] (AsyncWebServerRequest *request) {
  8.     String ledNumber = request->pathArg(0);
  9.     String state = request->pathArg(1);
  10.     digitalWrite(LED_PIN, state.toInt());
  11.     request->send(200, "text/plain", "LED: " + ledNumber + ", with state: " + state);
  12. });
复制代码

装配
在引脚 P0 和 GND 之间连接按钮
在引脚 27 和 GND 之间串联 LED 二极管和电阻器
将电池连接到基于 ESP32 的开发板。在该项目中,我们使用带有内置 LDO 的 ESP32 开发套件。查看基于 ESP32 的开发板时的最大输入电压电平,以避免损坏。
准备固件
从 GitHub 存储库克隆项目,然后按照以下步骤作:

1. 打开项目
从安装了 Platformio 扩展的 Visual Studio Code 打开项目文件夹
2. 配置您的项目(ESP32-to-ESP32.ino 文件)
获取您的 Husarnet VPN 加入代码(允许您将设备连接到同一个 VPN 网络)
您将在 https://app.husarnet.com
中找到您的加入代码 - > 单击所需的网络
- > 添加元素 按钮
- > 加入代码 选项卡
将您的 Husarnet 加入代码放在这里:

  1. const char *husarnetJoinCode = "fc94:b01d:1803:8dd8:b293:5c7d:7639:932a/xxxxxxxxxxxxxxxxxxxxxx
复制代码


在此处添加您的 Wi-Fi 网络凭证:

  1. // WiFi credentials
  2. const char* wifiNetworks[][2] = {
  3.   {"wifi-ssid-one", "wifi-pass-one"},
  4.   {"wifi-ssid-two", "wifi-pass-two"},
  5. }
复制代码


如果您的 ESP32 开发板是 ESP32 TTGO T Display,那么您可以通过第 14 行启用 LCD/TFT 显示:
#define ENABLE_TFT 1  //tested on TTGO T Display
将项目上传到 ESP32 开发板(所有开发板的代码相同)。
为两个 ESP32 模块供电并等待约 15 秒,让您的 ESP32 设备连接到 Wi-Fi 网络并建立 P2P 连接(在 LAN 和 Internet 中均可工作)。
就这样!我希望你会喜欢它。很高兴看到您的反馈。

【Arduino 动手做】ESP32 到 ESP32 的 Internet 通信图2

【Arduino 动手做】ESP32 到 ESP32 的 Internet 通信图1

【Arduino 动手做】ESP32 到 ESP32 的 Internet 通信图3

驴友花雕  中级技神
 楼主|

发表于 4 小时前

【Arduino 动手做】使用Arduino框架的ESP32到ESP32通信示例

项目代码

  1. #include <WiFi.h>
  2. #include <WiFiMulti.h>
  3. #include <AsyncTCP.h>
  4. #include <ESPAsyncWebServer.h>
  5. #include <Husarnet.h>
  6. #include <AceButton.h>
  7. #include <SPI.h>
  8. #include <TFT_eSPI.h>
  9. #define ENABLE_TFT 1  // tested on TTGO T Display
  10. #if ENABLE_TFT == 1
  11. TFT_eSPI tft = TFT_eSPI();
  12. #define LOG(f_, ...)                                                         \
  13.   {                                                                          \
  14.     if (tft.getCursorY() >= tft.height() || tft.getCursorY() == 0) {         \
  15.       tft.fillScreen(TFT_BLACK);                                             \
  16.       tft.setCursor(0, 0);                                                   \
  17.       IPAddress myip = WiFi.localIP();                                       \
  18.       tft.printf("IP: %u.%u.%u.%u\r\n", myip[0], myip[1], myip[2], myip[3]); \
  19.       tft.printf("Hostname: %s\r\n--\r\n", Husarnet.getHostname().c_str());  \
  20.     }                                                                        \
  21.     tft.printf((f_), ##__VA_ARGS__);                                         \
  22.     Serial.printf((f_), ##__VA_ARGS__);                                      \
  23.   }
  24. #else
  25. #define LOG(f_, ...) \
  26.   { Serial.printf((f_), ##__VA_ARGS__); }
  27. #endif
  28. /* =============== config section start =============== */
  29. #if __has_include("credentials.h")
  30. #include "credentials.h"
  31. #else
  32. /* to get your join code go to https://app.husarnet.com
  33.    -> select network
  34.    -> click "Add element"
  35.    -> select "join code" tab
  36.    Keep it secret!
  37. */
  38. const char *husarnetJoinCode = "xxxxxxxxxxxxxxxxxxxxxx";
  39. const char *dashboardURL = "default";
  40. // WiFi credentials
  41. const char* wifiNetworks[][2] = {
  42.   {"wifi-ssid-one", "wifi-pass-one"},
  43.   {"wifi-ssid-two", "wifi-pass-two"},
  44. };
  45. const char *hostname = "random";
  46. #endif
  47. /* =============== config section end =============== */
  48. using namespace ace_button;
  49. const int BUTTON_PIN = 0;
  50. const int LED_PIN = 27;
  51. const int PORT = 8001;
  52. int ledState = 0;
  53. // Push button
  54. AceButton btn(BUTTON_PIN);
  55. void handleButtonEvent(AceButton *, uint8_t, uint8_t);
  56. // you can provide credentials to multiple WiFi networks
  57. WiFiMulti wifiMulti;
  58. // store index.html content in html constant variable (platformio feature)
  59. extern const char index_html_start[] asm("_binary_src_index_html_start");
  60. const String html = String((const char*)index_html_start);
  61. AsyncWebServer server(PORT);
  62. // Task functions
  63. void taskWifi(void *parameter);
  64. void setup() {
  65.   Serial.begin(115200);
  66. #if ENABLE_TFT == 1
  67.   tft.init();
  68.   tft.setRotation(0);
  69.   tft.fillScreen(TFT_BLACK);
  70.   tft.setTextColor(TFT_WHITE, TFT_BLACK);
  71.   tft.setTextSize(1);
  72. #endif
  73.   // LED and Button config
  74.   pinMode(BUTTON_PIN, INPUT_PULLUP);
  75.   btn.setEventHandler(handleButtonEvent);
  76.   pinMode(LED_PIN, OUTPUT);
  77.   digitalWrite(LED_PIN, LOW);
  78.   // Save Wi-Fi credentials
  79.   for (int i = 0; i < (sizeof(wifiNetworks)/sizeof(wifiNetworks[0])); i++) {
  80.     wifiMulti.addAP(wifiNetworks[i][0], wifiNetworks[i][1]);
  81.     Serial.printf("WiFi %d: SSID: "%s" ; PASS: "%s"\r\n", i, wifiNetworks[i][0], wifiNetworks[i][1]);
  82.   }
  83.   // Husarnet VPN configuration
  84.   Husarnet.selfHostedSetup(dashboardURL);
  85.   Husarnet.join(husarnetJoinCode, hostname);
  86.   // A dummy web server (see index.html)
  87.   server.on("/", HTTP_GET, [](AsyncWebServerRequest* request) {
  88.     request->send(200, "text/html", html);
  89.   });
  90.   // Send a GET request to <IP>/led/<number>/state/<0 or 1>
  91.   server.on("^\\/led\\/([0-9]+)\\/state\\/([0-9]+)$", HTTP_GET, [] (AsyncWebServerRequest *request) {
  92.     String ledNumber = request->pathArg(0); // currently unused - we use only a predefined LED number
  93.     String state = request->pathArg(1);
  94.     digitalWrite(LED_PIN, state.toInt());
  95.     request->send(200, "text/plain", "LED: " + ledNumber + ", with state: " + state);
  96.   });
  97.   xTaskCreate(taskWifi,   /* Task function. */
  98.               "taskWifi", /* String with name of task. */
  99.               10000,      /* Stack size in bytes. */
  100.               NULL,       /* Parameter passed as input of the task */
  101.               1,          /* Priority of the task. */
  102.               NULL);      /* Task handle. */
  103. }
  104. void loop() {
  105.   while (1) {
  106.     btn.check();
  107.     delay(1);
  108.   }
  109. }
  110. void taskWifi(void *parameter) {
  111.   uint8_t stat = WL_DISCONNECTED;
  112.   while (stat != WL_CONNECTED) {
  113.     stat = wifiMulti.run();
  114.     Serial.printf("WiFi status: %d\r\n", (int)stat);
  115.     delay(100);
  116.   }
  117.   Serial.printf("WiFi connected\r\n");
  118.   // Start Husarnet VPN Client
  119.   Husarnet.start();
  120.   // Start HTTP server
  121.   server.begin();
  122.   LOG("READY!\r\n");
  123.   while (1) {
  124.     while (WiFi.status() == WL_CONNECTED) {
  125.       delay(500);
  126.     }
  127.     LOG("WiFi disconnected, reconnecting\r\n");
  128.     delay(500);
  129.     stat = wifiMulti.run();
  130.     LOG("WiFi status: %d\r\n", (int)stat);
  131.   }
  132. }
  133. void handleButtonEvent(AceButton *button, uint8_t eventType, uint8_t buttonState) {
  134.   ledState = (buttonState==1?0:1);
  135.   for (auto const &host : Husarnet.listPeers()) {
  136.     IPv6Address peerAddr = host.first;
  137.     if(host.second == "master") {
  138.       ;
  139.     } else {
  140.       AsyncClient* client_tcp = new AsyncClient;
  141.       
  142.       client_tcp->onConnect([](void *arg, AsyncClient *client) {
  143.         String requestURL = "/led/1/state/" + String(ledState);
  144.         String GETreq = String("GET ") + requestURL + " HTTP/1.1\r\n" + "Host: esp32\r\n" + "Connection: close\r\n\r\n";
  145.         if ( client->canSend() && (client->space() > GETreq.length())){
  146.           client->add(GETreq.c_str(), strlen(GETreq.c_str()));
  147.                 client->send();
  148.         } else {
  149.           Serial.printf("\r\nSENDING ERROR!\r\n");
  150.         }
  151.       }, client_tcp);
  152.       client_tcp->onData([](void *arg, AsyncClient *client, void *data, size_t len) {
  153.         Serial.printf("\r\nResponse from %s\r\n", client->remoteIP().toString().c_str());
  154.               Serial.write((uint8_t *)data, len);
  155.         client->close();
  156.       }, client_tcp);
  157.       client_tcp->onDisconnect([](void* arg, AsyncClient* client) {
  158.         Serial.println("[CALLBACK] discconnected");
  159.         delete client;
  160.       }, client_tcp);
  161.       
  162.       client_tcp->onError([](void* arg, AsyncClient* client, int8_t error) {
  163.         Serial.printf("[CALLBACK] error: %d\r\n", error);
  164.       }, NULL);
  165.       client_tcp->onTimeout([](void* arg, AsyncClient* client, uint32_t time) {
  166.         Serial.println("[CALLBACK] ACK timeout");
  167.       }, NULL);
  168.       
  169.       client_tcp->connect(peerAddr, PORT);
  170.       LOG("Sending HTTP req to:\r\n%s:\r\n%s\r\n\r\n", host.second.c_str(), host.first.toString().c_str());
  171.     }
  172.   }
  173. }
复制代码


回复

使用道具 举报

驴友花雕  中级技神
 楼主|

发表于 4 小时前

【Arduino 动手做】使用Arduino框架的ESP32到ESP32通信示例

【Arduino 动手做】使用Arduino框架的ESP32到ESP32通信示例
项目链接:https://www.hackster.io/donowak/ ... the-internet-9799df
项目作者:多米尼克

项目视频 :https://www.youtube.com/watch?v=DRsfMmTfyeo
项目代码:https://github.com/DominikN/ESP32-to-ESP32

【Arduino 动手做】ESP32 到 ESP32 的 Internet 通信图1

回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

为本项目制作心愿单
购买心愿单
心愿单 编辑
[[wsData.name]]

硬件清单

  • [[d.name]]
btnicon
我也要做!
点击进入购买页面
上海智位机器人股份有限公司 沪ICP备09038501号-4 备案 沪公网安备31011502402448

© 2013-2025 Comsenz Inc. Powered by Discuz! X3.4 Licensed

mail