回到首页 返回首页
回到顶部 回到顶部
返回上一页 返回上一页

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

头像 驴友花雕 2025.06.23 19 0

在设备之间建立安全、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 网络是通过以下两条线路创建的:

Husarnet.join(husarnetJoinCode, hostNameX);
Husarnet.start();

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

HTTP 服务器

// A dummy web server (see index.html)
server.on("/", HTTP_GET, [](AsyncWebServerRequest* request) {
   request->send(200, "text/html", html);
});
// Send a GET request to /led//state/<0 or 1>
server.on("^\\/led\\/([0-9]+)\\/state\\/([0-9]+)$", HTTP_GET, 
[] (AsyncWebServerRequest *request) {
   String ledNumber = request->pathArg(0); 
   String state = request->pathArg(1);
   digitalWrite(LED_PIN, state.toInt());
   request->send(200, "text/plain", "LED: " + ledNumber + ", with state: " + state);
});

装配
在引脚 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 加入代码放在这里:

const char *husarnetJoinCode = "fc94:b01d:1803:8dd8:b293:5c7d:7639:932a/xxxxxxxxxxxxxxxxxxxxxx

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

// WiFi credentials
const char* wifiNetworks[][2] = {
 {"wifi-ssid-one", "wifi-pass-one"},
 {"wifi-ssid-two", "wifi-pass-two"},
}

如果您的 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 中均可工作)。
就这样!我希望你会喜欢它。很高兴看到您的反馈。

 

00.jpg
01.jpg
02.jpg

项目代码

 

代码
#include <WiFi.h>
#include <WiFiMulti.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <Husarnet.h>
#include <AceButton.h>

#include <SPI.h>
#include <TFT_eSPI.h>

#define ENABLE_TFT 1  // tested on TTGO T Display

#if ENABLE_TFT == 1

TFT_eSPI tft = TFT_eSPI(); 

#define LOG(f_, ...)                                                         \
  {                                                                          \
    if (tft.getCursorY() >= tft.height() || tft.getCursorY() == 0) {         \
      tft.fillScreen(TFT_BLACK);                                             \
      tft.setCursor(0, 0);                                                   \
      IPAddress myip = WiFi.localIP();                                       \
      tft.printf("IP: %u.%u.%u.%u\r\n", myip[0], myip[1], myip[2], myip[3]); \
      tft.printf("Hostname: %s\r\n--\r\n", Husarnet.getHostname().c_str());  \
    }                                                                        \
    tft.printf((f_), ##__VA_ARGS__);                                         \
    Serial.printf((f_), ##__VA_ARGS__);                                      \
  }
#else
#define LOG(f_, ...) \
  { Serial.printf((f_), ##__VA_ARGS__); }
#endif

/* =============== config section start =============== */
#if __has_include("credentials.h")
#include "credentials.h"
#else
/* to get your join code go to https://app.husarnet.com
   -> select network
   -> click "Add element"
   -> select "join code" tab

   Keep it secret!
*/
const char *husarnetJoinCode = "xxxxxxxxxxxxxxxxxxxxxx";
const char *dashboardURL = "default";

// WiFi credentials
const char* wifiNetworks[][2] = {
  {"wifi-ssid-one", "wifi-pass-one"},
  {"wifi-ssid-two", "wifi-pass-two"},
};

const char *hostname = "random";

#endif
/* =============== config section end =============== */

using namespace ace_button;

const int BUTTON_PIN = 0;
const int LED_PIN = 27;
const int PORT = 8001;

int ledState = 0;

// Push button
AceButton btn(BUTTON_PIN);
void handleButtonEvent(AceButton *, uint8_t, uint8_t);

// you can provide credentials to multiple WiFi networks
WiFiMulti wifiMulti;

// store index.html content in html constant variable (platformio feature)
extern const char index_html_start[] asm("_binary_src_index_html_start");
const String html = String((const char*)index_html_start);
AsyncWebServer server(PORT);

// Task functions
void taskWifi(void *parameter);

void setup() {
  Serial.begin(115200);

#if ENABLE_TFT == 1
  tft.init();
  tft.setRotation(0);
  tft.fillScreen(TFT_BLACK);
  tft.setTextColor(TFT_WHITE, TFT_BLACK);
  tft.setTextSize(1);
#endif

  // LED and Button config
  pinMode(BUTTON_PIN, INPUT_PULLUP);
  btn.setEventHandler(handleButtonEvent);

  pinMode(LED_PIN, OUTPUT);
  digitalWrite(LED_PIN, LOW);

  // Save Wi-Fi credentials
  for (int i = 0; i < (sizeof(wifiNetworks)/sizeof(wifiNetworks[0])); i++) {
    wifiMulti.addAP(wifiNetworks[i][0], wifiNetworks[i][1]);
    Serial.printf("WiFi %d: SSID: \"%s\" ; PASS: \"%s\"\r\n", i, wifiNetworks[i][0], wifiNetworks[i][1]);
  }

  // Husarnet VPN configuration 
  Husarnet.selfHostedSetup(dashboardURL);
  Husarnet.join(husarnetJoinCode, hostname);

  // A dummy web server (see index.html)
  server.on("/", HTTP_GET, [](AsyncWebServerRequest* request) {
    request->send(200, "text/html", html);
  });

  // Send a GET request to <IP>/led/<number>/state/<0 or 1>
  server.on("^\\/led\\/([0-9]+)\\/state\\/([0-9]+)$", HTTP_GET, [] (AsyncWebServerRequest *request) {
    String ledNumber = request->pathArg(0); // currently unused - we use only a predefined LED number
    String state = request->pathArg(1);

    digitalWrite(LED_PIN, state.toInt());
    request->send(200, "text/plain", "LED: " + ledNumber + ", with state: " + state);
  });

  xTaskCreate(taskWifi,   /* Task function. */
              "taskWifi", /* String with name of task. */
              10000,      /* Stack size in bytes. */
              NULL,       /* Parameter passed as input of the task */
              1,          /* Priority of the task. */
              NULL);      /* Task handle. */
}

void loop() {
  while (1) {
    btn.check();
    delay(1);
  }
}

void taskWifi(void *parameter) {
  uint8_t stat = WL_DISCONNECTED;

  while (stat != WL_CONNECTED) {
    stat = wifiMulti.run();
    Serial.printf("WiFi status: %d\r\n", (int)stat);
    delay(100);
  }

  Serial.printf("WiFi connected\r\n");

  // Start Husarnet VPN Client
  Husarnet.start();

  // Start HTTP server
  server.begin();

  LOG("READY!\r\n");

  while (1) {
    while (WiFi.status() == WL_CONNECTED) {
      delay(500);
    }
    LOG("WiFi disconnected, reconnecting\r\n");
    delay(500);
    stat = wifiMulti.run();
    LOG("WiFi status: %d\r\n", (int)stat);
  }
}

void handleButtonEvent(AceButton *button, uint8_t eventType, uint8_t buttonState) {
  ledState = (buttonState==1?0:1);

  for (auto const &host : Husarnet.listPeers()) {
    IPv6Address peerAddr = host.first;
    if(host.second == "master") {
      ;
    } else {
      AsyncClient* client_tcp = new AsyncClient;
      
      client_tcp->onConnect([](void *arg, AsyncClient *client) {
        String requestURL = "/led/1/state/" + String(ledState);
        String GETreq = String("GET ") + requestURL + " HTTP/1.1\r\n" + "Host: esp32\r\n" + "Connection: close\r\n\r\n";

        if ( client->canSend() && (client->space() > GETreq.length())){
          client->add(GETreq.c_str(), strlen(GETreq.c_str()));
	        client->send();
        } else {
          Serial.printf("\r\nSENDING ERROR!\r\n");
        }
      }, client_tcp);

      client_tcp->onData([](void *arg, AsyncClient *client, void *data, size_t len) {
        Serial.printf("\r\nResponse from %s\r\n", client->remoteIP().toString().c_str());
	      Serial.write((uint8_t *)data, len);
        client->close();
      }, client_tcp);

      client_tcp->onDisconnect([](void* arg, AsyncClient* client) {
        Serial.println("[CALLBACK] discconnected");
        delete client;
      }, client_tcp);
      
      client_tcp->onError([](void* arg, AsyncClient* client, int8_t error) {
        Serial.printf("[CALLBACK] error: %d\r\n", error);
      }, NULL);

      client_tcp->onTimeout([](void* arg, AsyncClient* client, uint32_t time) {
        Serial.println("[CALLBACK] ACK timeout");
      }, NULL);
      
      client_tcp->connect(peerAddr, PORT);

      LOG("Sending HTTP req to:\r\n%s:\r\n%s\r\n\r\n", host.second.c_str(), host.first.toString().c_str());
    }
  }
}

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

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

 

评论

user-avatar
icon 他的勋章
    展开更多