ESP-MESH 网络协议 – ESP32 和 ESP8266使用(painlessMesh 库)

释放双眼,带上耳机,听听看~!

了解如何使用 ESP-MESH 网络协议,通过 ESP32 和 ESP8266 NodeMCU 开发板构建 Mesh 网络。ESP-MESH 允许多个设备(节点)在单个无线局域网下相互通信。ESP32 和 ESP8266 开发板均支持此功能。在本教程中,我们将向您展示如何使用 Arduino 内核开始使用 ESP-MESH。

ESP-MESH with ESP32 and ESP8266: Getting Started

本文涵盖以下主题:

  •  ESP-MESH 简介
  • ESP-MESH Basic 示例(广播消息)
  • 使用 ESP-MESH 交换传感器读数(广播)

 Arduino集成开发环境

如果您想使用 Arduino IDE 对 ESP32 和 ESP8266 开发板进行编程,您IDE应该安装 ESP32 或 ESP8266 插件。

 ESP-MESH 简介

根据乐鑫文档:

“ESP-MESH 是一种建立在 Wi-Fi 协议之上的网络协议。ESP-MESH 允许分布在大型物理区域(室内和室外)的众多设备(称为节点)在单个 WLAN(无线局域网)下互连。

ESP-MESH 具有自组织和自修复功能,这意味着网络可以自主构建和维护。更多信息,请访问 ESP-MESH 官方文档

传统Wi-Fi网络架构

在传统的 Wi-Fi 网络架构中,单个节点(接入点 – 通常是路由器)连接到所有其他节点(站点)。每个节点都可以使用接入点相互通信。但是,这仅限于接入点 Wi-Fi 覆盖范围。每个站点都必须在范围内才能直接连接到接入点。ESP-MESH 不会发生这种情况。

Traditional Wi-Fi Network ESP32 ESP8266

ESP-MESH 网络架构

使用 ESP-MESH,节点无需连接到中心节点。节点负责中继彼此的传输。这允许多个设备分布在较大的物理区域。节点可以自组织并动态地相互通信,以确保数据包到达其最终节点目的地。如果从网络中删除任何节点,它能够自组织以确保数据包到达目的地。

ESP-MESH Network ESP32 ESP8266i

 painlessMesh 库

painlessMesh 库允许我们以简单的方式使用 ESP8266 或/和 ESP32 开发板创建网状网络。

“painlessMesh是一个真正的自组织网络,这意味着不需要规划、中央控制器或路由器。任何包含 1 个或多个节点的系统都将自组织成功能齐全的网格。网格的最大大小(我们认为)受到堆中可分配给子连接缓冲区的内存量的限制,因此应该非常高。有关 painlessMesh 库的更多信息。

安装 painlessMesh 库

此库需要一些其他库依赖项。应该会弹出一个新窗口,要求您安装任何缺少的依赖项。选择“全部安装”。

install-painlessmes-library-dependencies

如果未显示此窗口,则需要安装以下库依赖项:

如果您使用的是 PlatformIO,请将以下行添加到 platformio.ini 文件以添加库并更改监视器速度。

 对于 ESP32:

monitor_speed = 115200
lib_deps = painlessmesh/painlessMesh @ ^1.4.5
    ArduinoJson
    arduinoUnity
    TaskScheduler
    AsyncTCP

 对于ESP8266:

monitor_speed = 115200
lib_deps = painlessmesh/painlessMesh @ ^1.4.5
    ArduinoJson
    TaskScheduler
    ESPAsyncTCP

ESP-MESH Basic 示例(广播消息)

要开始使用 ESP-MESH,我们首先会尝试使用该库的基本示例。此示例创建一个网状网络,其中所有板将消息广播到所有其他板。

我们用四块开发板(两块 ESP32 和两块 ESP8266)对这个示例进行了实验。您可以添加或删除看板。该代码与 ESP32 和 ESP8266 开发板兼容。

ESP-MESH-painlessMesh-basic-example-ESP32-ESP8266

代码 – painlessMesh 库基本示例

将以下代码复制到 Arduino IDE(库示例中的代码)。该代码与 ESP32 和 ESP8266 开发板兼容。

/*
  
  更多Arduino/ESP8266/ESP32等教程请访问: https://www.qutaojiao.com
  
  This is a simple example that uses the painlessMesh library: https://github.com/gmag11/painlessMesh/blob/master/examples/basic/basic.ino
*/

#include "painlessMesh.h"

#define   MESH_PREFIX     "whateverYouLike"
#define   MESH_PASSWORD   "somethingSneaky"
#define   MESH_PORT       5555

Scheduler userScheduler; // to control your personal task
painlessMesh  mesh;

// User stub
void sendMessage() ; // Prototype so PlatformIO doesn't complain

Task taskSendMessage( TASK_SECOND * 1 , TASK_FOREVER, &sendMessage );

void sendMessage() {
  String msg = "Hi from node1";
  msg += mesh.getNodeId();
  mesh.sendBroadcast( msg );
  taskSendMessage.setInterval( random( TASK_SECOND * 1, TASK_SECOND * 5 ));
}

// Needed for painless library
void receivedCallback( uint32_t from, String &msg ) {
  Serial.printf("startHere: Received from %u msg=%sn", from, msg.c_str());
}

void newConnectionCallback(uint32_t nodeId) {
    Serial.printf("--> startHere: New Connection, nodeId = %un", nodeId);
}

void changedConnectionCallback() {
  Serial.printf("Changed connectionsn");
}

void nodeTimeAdjustedCallback(int32_t offset) {
    Serial.printf("Adjusted time %u. Offset = %dn", mesh.getNodeTime(),offset);
}

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

//mesh.setDebugMsgTypes( ERROR | MESH_STATUS | CONNECTION | SYNC | COMMUNICATION | GENERAL | MSG_TYPES | REMOTE ); // all types on
  mesh.setDebugMsgTypes( ERROR | STARTUP );  // set before init() so that you can see startup messages

  mesh.init( MESH_PREFIX, MESH_PASSWORD, &userScheduler, MESH_PORT );
  mesh.onReceive(&receivedCallback);
  mesh.onNewConnection(&newConnectionCallback);
  mesh.onChangedConnections(&changedConnectionCallback);
  mesh.onNodeTimeAdjusted(&nodeTimeAdjustedCallback);

  userScheduler.addTask( taskSendMessage );
  taskSendMessage.enable();
}

void loop() {
  // it will run the user scheduler as well
  mesh.update();
}

 

在上传代码之前,您可以设置MESH_PREFIX(就像 MESH 网络的名称)和MESH_PASSWORD变量(您可以将其设置为您喜欢的任何内容)。

然后,我们建议您更改每个板的以下行,以便轻松识别发送消息的节点。例如,对于节点 1,更改消息,如下所示:

String msg = "Hi from node 1 ";

代码的工作原理

首先包括 painlessMesh 库。

#include "painlessMesh.h"

 网格详细信息

然后,添加网格详细信息。MESH_PREFIX是指网格的名称。您可以将其更改为您喜欢的任何内容。

#define MESH_PREFIX "whateverYouLike"

顾名思义,MESH_PASSWORD是网状密码。您可以将其更改为您喜欢的任何内容。

#define MESH_PASSWORD "somethingSneaky"

网格中的所有节点都应使用相同的MESH_PREFIX和MESH_PASSWORD。

MESH_PORT是指您希望运行网状服务器的 TCP 端口。默认值为 5555。

#define MESH_PORT 5555

 调度

建议避免在网状网络代码中使用 delay()。为了维护网格,需要在后台执行一些任务。使用 delay() 将阻止这些任务的发生,并可能导致网格失去稳定性/分崩离析。

相反,建议使用 TaskScheduler 来运行在 painlessMesh 本身中使用的任务。

以下行创建一个名为 userScheduler 的新调度程序。

Scheduler userScheduler; // to control your personal task

painlessMesh

创建一个名为 mesh 的 painlessMesh 对象来处理网格网络。

 taskSendMessage

创建一个名为 taskSendMessage 的任务,只要程序正在运行,它就负责每秒调用一次 sendMessage() 函数。

Task taskSendMessage(TASK_SECOND * 1 , TASK_FOREVER, &sendMessage);

sendMessage

sendMessage() 函数将消息发送到消息网络(广播)中的所有节点。

void sendMessage() {
  String msg = "Hi from node 1";
  msg += mesh.getNodeId();
  mesh.sendBroadcast( msg );
  taskSendMessage.setInterval(random(TASK_SECOND * 1, TASK_SECOND * 5));
}

该消息包含“Hi from node 1”文本,后跟电路板芯片 ID。

String msg = "Hi from node 1";
msg += mesh.getNodeId();

要广播消息,只需在网格对象上使用 sendBroadcast() 方法,并将要发送的消息 (msg) 作为参数传递。

mesh.sendBroadcast(msg);

每次发送新消息时,代码都会更改消息之间的间隔(一到五秒)。

taskSendMessage.setInterval(random(TASK_SECOND * 1, TASK_SECOND * 5));

receivedCallback

接下来,创建几个回调函数,当网格上发生特定事件时,将调用这些函数。

receivedCallback() 函数打印消息发件人 (from) 和消息内容 (msg.c_str())。

void receivedCallback( uint32_t from, String &msg ) {
  Serial.printf("startHere: Received from %u msg=%sn", from, msg.c_str());
}

每当新节点加入网络时,newConnectionCallback() 函数就会运行。此函数仅打印新节点的芯片 ID。您可以修改该函数以执行任何其他任务。

void newConnectionCallback(uint32_t nodeId) {
  Serial.printf("--> startHere: New Connection, nodeId = %un", nodeId);
}

每当网络上的连接发生变化时(当节点加入或离开网络时),changedConnectionCallback() 函数就会运行。

void changedConnectionCallback() {
  Serial.printf("Changed connectionsn");
}

nodeTimeAdjustedCallback() 函数在网络调整时间时运行,以便所有节点同步。它打印偏移量。

void nodeTimeAdjustedCallback(int32_t offset) {
  Serial.printf("Adjusted time %u. Offset = %dn", mesh.getNodeTime(),offset);
}

 setup()

在 setup() 中,初始化串行监视器。

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

选择所需的调试消息类型:

//mesh.setDebugMsgTypes( ERROR | MESH_STATUS | CONNECTION | SYNC | COMMUNICATION | GENERAL | MSG_TYPES | REMOTE ); // all types on

mesh.setDebugMsgTypes( ERROR | STARTUP );  // set before init() so that you can see startup messages

使用前面定义的详细信息初始化网格。

mesh.init(MESH_PREFIX, MESH_PASSWORD, &userScheduler, MESH_PORT);

将所有回调函数分配给其对应的事件。

mesh.onReceive(&receivedCallback);
mesh.onNewConnection(&newConnectionCallback);
mesh.onChangedConnections(&changedConnectionCallback);
mesh.onNodeTimeAdjusted(&nodeTimeAdjustedCallback);

最后,将 taskSendMessage 函数添加到 userScheduler。调度程序负责在正确的时间处理和运行任务。

userScheduler.addTask(taskSendMessage);

最后,启用 taskSendMessage,以便程序开始向网格发送消息。

taskSendMessage.enable();

要保持网格运行,请将 mesh.update() 添加到 loop() 中。

void loop() {
  // it will run the user scheduler as well
  mesh.update();
}

 示范

将提供的代码上传到所有看板。不要忘记修改消息以轻松识别发送方节点

将主板连接到计算机后,打开与每个主板的串行连接。您可以使用串行监视器,也可以使用PuTTY等软件,并为所有板打开多个窗口。

您应该看到所有看板都收到彼此的消息。例如,这些是节点 1 接收的消息。它接收来自节点 2、3 和 4 的消息。

ESP-MESH-basic-example-4-boards-Serial-Monitor-ESP8266-ESP32

当网格发生变化时,您还应该看到其他消息:当板离开或加入网络时。

ESP-MESH-basic-example-Serial-Monitor-Changed-Connections

使用 ESP-MESH 交换传感器读数

在下一个示例中,我们将在 4 块板之间交换传感器读数(您可以使用不同数量的板)。每个板子都会收到其他板子的读数。

ESP-MESH-Exchnage-BME280-Sensor-Readings-ESP32-ESP8266

例如,我们将交换来自 BME280 传感器的传感器读数,但您可以使用任何其他传感器。

 所需零件

以下是此示例所需的部件:

  • 4 个 ESP 板(ESP32 或 ESP8266)
  •  4 个 BME280
  •  面包板
  •  连接线

 Arduino_JSON库

在此示例中,我们将以 JSON 格式交换传感器读数。

如果将 VS Code 与 PlatformIO 配合使用,请在 platformio.ini 文件中包含库,如下所示:

ESP32

monitor_speed = 115200
lib_deps = painlessmesh/painlessMesh @ ^1.4.5
    ArduinoJson
    arduinoUnity
    AsyncTCP
    TaskScheduler
    adafruit/Adafruit Unified Sensor @ ^1.1.4
    adafruit/Adafruit BME280 Library @ ^2.1.2
    arduino-libraries/Arduino_JSON @ ^0.1.0

ESP8266

monitor_speed = 115200
lib_deps = painlessmesh/painlessMesh @ ^1.4.5
    ArduinoJson
    TaskScheduler
    ESPAsyncTCP
    adafruit/Adafruit Unified Sensor @ ^1.1.4
    adafruit/Adafruit BME280 Library @ ^2.1.2
    arduino-libraries/Arduino_JSON @ ^0.1.0

 电路图

将 BME280 传感器连接到 ESP32 或ESP8266默认的 I2C 引脚,如下图所示。

ESP32

ESP32-BME280-Sensor-Temperature-Humidity-Pressure-Wiring-Diagram-Circuit_f

推荐阅读:ESP32 with BME280 Sensor using Arduino IDE(压力、温度、湿度)

ESP8266 NodeMCU

ESP8266-NodeMCU-BME280-Sensor-Temperature-Humidity-Pressure-Wiring-Diagram-Circuit

推荐阅读:使用 Arduino IDE ESP8266 BME280(压力、温度、湿度)

代码 – ESP-MESH 广播传感器读数

将以下代码上传到每个开发板。此代码读取当前温度、湿度和压力读数并将其广播到网状网络上的所有板。读数以 JSON 字符串的形式发送,该字符串还包含用于标识发送方板的节点号。

/*
  
  更多Arduino/ESP8266/ESP32等教程请访问: https://www.qutaojiao.com
  
  Permission is hereby granted, free of charge, to any person obtaining a copy
  of this software and associated documentation files.
  
  The above copyright notice and this permission notice shall be included in all
  copies or substantial portions of the Software.
*/

#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>
#include "painlessMesh.h"
#include <Arduino_JSON.h>

// MESH Details
#define   MESH_PREFIX     "RNTMESH" //name for your MESH
#define   MESH_PASSWORD   "MESHpassword" //password for your MESH
#define   MESH_PORT       5555 //default port

//BME object on the default I2C pins
Adafruit_BME280 bme;

//Number for this node
int nodeNumber = 2;

//String to send to other nodes with sensor readings
String readings;

Scheduler userScheduler; // to control your personal task
painlessMesh  mesh;

// User stub
void sendMessage() ; // Prototype so PlatformIO doesn't complain
String getReadings(); // Prototype for sending sensor readings

//Create tasks: to send messages and get readings;
Task taskSendMessage(TASK_SECOND * 5 , TASK_FOREVER, &sendMessage);

String getReadings () {
  JSONVar jsonReadings;
  jsonReadings["node"] = nodeNumber;
  jsonReadings["temp"] = bme.readTemperature();
  jsonReadings["hum"] = bme.readHumidity();
  jsonReadings["pres"] = bme.readPressure()/100.0F;
  readings = JSON.stringify(jsonReadings);
  return readings;
}

void sendMessage () {
  String msg = getReadings();
  mesh.sendBroadcast(msg);
}

//Init BME280
void initBME(){
  if (!bme.begin(0x76)) {
    Serial.println("Could not find a valid BME280 sensor, check wiring!");
    while (1);
  }  
}

// Needed for painless library
void receivedCallback( uint32_t from, String &msg ) {
  Serial.printf("Received from %u msg=%sn", from, msg.c_str());
  JSONVar myObject = JSON.parse(msg.c_str());
  int node = myObject["node"];
  double temp = myObject["temp"];
  double hum = myObject["hum"];
  double pres = myObject["pres"];
  Serial.print("Node: ");
  Serial.println(node);
  Serial.print("Temperature: ");
  Serial.print(temp);
  Serial.println(" C");
  Serial.print("Humidity: ");
  Serial.print(hum);
  Serial.println(" %");
  Serial.print("Pressure: ");
  Serial.print(pres);
  Serial.println(" hpa");
}

void newConnectionCallback(uint32_t nodeId) {
  Serial.printf("New Connection, nodeId = %un", nodeId);
}

void changedConnectionCallback() {
  Serial.printf("Changed connectionsn");
}

void nodeTimeAdjustedCallback(int32_t offset) {
  Serial.printf("Adjusted time %u. Offset = %dn", mesh.getNodeTime(),offset);
}

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

  //mesh.setDebugMsgTypes( ERROR | MESH_STATUS | CONNECTION | SYNC | COMMUNICATION | GENERAL | MSG_TYPES | REMOTE ); // all types on
  mesh.setDebugMsgTypes( ERROR | STARTUP );  // set before init() so that you can see startup messages

  mesh.init( MESH_PREFIX, MESH_PASSWORD, &userScheduler, MESH_PORT );
  mesh.onReceive(&receivedCallback);
  mesh.onNewConnection(&newConnectionCallback);
  mesh.onChangedConnections(&changedConnectionCallback);
  mesh.onNodeTimeAdjusted(&nodeTimeAdjustedCallback);

  userScheduler.addTask(taskSendMessage);
  taskSendMessage.enable();
}

void loop() {
  // it will run the user scheduler as well
  mesh.update();
}

 

该代码与 ESP32 和 ESP8266 开发板兼容。

代码的工作原理

继续阅读本部分,了解代码的工作原理。

 

首先包括所需的库:与 BME280 传感器接口的Adafruit_Sensor和Adafruit_BME280;用于处理网状网络的 painlessMesh 库和用于轻松创建和处理 JSON 字符串的 Arduino_JSON。

#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>
#include "painlessMesh.h"
#include <Arduino_JSON.h>

 

Mesh详情

在以下行中插入网格详细信息。

#define MESH_PREFIX    "RNTMESH" //name for your MESH
#define MESH_PASSWORD  "MESHpassword" //password for your MESH
#define MESH_PORT      5555 //default port

MESH_PREFIX是指网格的名称。您可以将其更改为您喜欢的任何内容。顾名思义,MESH_PASSWORD是网状密码。您可以将其更改为您喜欢的任何内容。网格中的所有节点都应使用相同的MESH_PREFIX和MESH_PASSWORD。

MESH_PORT是指您希望运行网状服务器的 TCP 端口。默认值为 5555。

 BME280

在默认的 ESP32 或 ESP8266 引脚上创建一个名为 bme 的Adafruit_BME280对象。

Adafruit_BME280 bme;

在 nodeNumber 变量中,插入主板的节点号。每个板的编号必须不同。

int nodeNumber = 2;

readings 变量将用于保存要发送到其他板的读数。

String readings;

 userScheduler

以下行创建一个名为 userScheduler 的新调度程序。

Scheduler userScheduler; // to control your personal task

painlessMesh

创建一个名为 mesh 的 painlessMesh 对象来处理网格网络。

 创建任务

创建一个名为 taskSendMessage 的任务,只要程序正在运行,该任务就负责每 5 秒调用一次 sendMessage() 函数。

Task taskSendMessage(TASK_SECOND * 5 , TASK_FOREVER, &sendMessage);

 getReadings()

getReadings() 函数从 BME280 传感器获取温度、湿度和压力读数,并连接所有信息,包括名为 jsonReadings 的 JSON 变量上的节点号。

JSONVar jsonReadings;
jsonReadings["node"] = nodeNumber;
jsonReadings["temp"] = bme.readTemperature();
jsonReadings["hum"] = bme.readHumidity();
jsonReadings["pres"] = bme.readPressure()/100.0F;

以下行显示了具有任意值的 jsonReadings 变量的结构。

{
  "node":2,
  "temperature":24.51,
  "humidity":52.01,
  "pressure":1005.21
}

然后使用 stringify() 方法将 jsonReadings 变量转换为 JSON 字符串,并保存在 readings 变量中。

readings = JSON.stringify(jsonReadings);

然后,该函数返回此变量。

return readings;

sendMessage

sendMessage() 函数将带有读数和节点号 (getReadings()) 的 JSON 字符串发送到网络中的所有节点(广播)。

void sendMessage () {
  String msg = getReadings();
  mesh.sendBroadcast(msg);
}

initBME

initBME() 函数初始化 BME280 传感器。

void initBME(){
  if (!bme.begin(0x76)) {
    Serial.println("Could not find a valid BME280 sensor, check wiring!");
    while (1);
  }
}

receivedCallback

接下来,创建几个回调函数,当网格上发生某些事件时,将调用这些函数。

receivedCallback() 函数打印消息发件人 (from) 和消息内容 (msg.c_str())。

void receivedCallback( uint32_t from, String &msg ) {
  Serial.printf("startHere: Received from %u msg=%sn", from, msg.c_str());

该消息采用 JSON 格式,因此,我们可以按如下方式访问变量:

JSONVar myObject = JSON.parse(msg.c_str());
int node = myObject["node"];
double temp = myObject["temp"];
double hum = myObject["hum"];
double pres = myObject["pres"];

最后,在串行监视器上打印所有信息。

Serial.print("Node: ");
Serial.println(node);
Serial.print("Temperature: ");
Serial.print(temp);
Serial.println(" C");
Serial.print("Humidity: ");
Serial.print(hum);
Serial.println(" %");
Serial.print("Pressure: ");
Serial.print(pres);
Serial.println(" hpa");

每当新节点加入网络时,newConnectionCallback() 函数就会运行。此函数仅打印新节点的芯片 ID。您可以修改该函数以执行任何其他任务。

void newConnectionCallback(uint32_t nodeId) {
  Serial.printf("--> startHere: New Connection, nodeId = %un", nodeId);
}

每当网络上的连接发生变化时(当节点加入或离开网络时),changedConnectionCallback() 函数就会运行。

void changedConnectionCallback() {
  Serial.printf("Changed connectionsn");
}

nodeTimeAdjustedCallback() 函数在网络调整时间时运行,以便所有节点同步。它打印偏移量。

void nodeTimeAdjustedCallback(int32_t offset) {
  Serial.printf("Adjusted time %u. Offset = %dn", mesh.getNodeTime(),offset);
}

 setup()

在 setup() 中,初始化串行监视器。

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

调用 initBME() 函数以初始化 BME280 传感器。

initBME();

选择所需的调试消息类型:

//mesh.setDebugMsgTypes( ERROR | MESH_STATUS | CONNECTION | SYNC | COMMUNICATION | GENERAL | MSG_TYPES | REMOTE ); // all types on

mesh.setDebugMsgTypes( ERROR | STARTUP );  // set before init() so that you can see startup messages

使用前面定义的详细信息初始化网格。

mesh.init(MESH_PREFIX, MESH_PASSWORD, &userScheduler, MESH_PORT);

将所有回调函数分配给其对应的事件。

mesh.onReceive(&receivedCallback);
mesh.onNewConnection(&newConnectionCallback);
mesh.onChangedConnections(&changedConnectionCallback);
mesh.onNodeTimeAdjusted(&nodeTimeAdjustedCallback);

最后,将 taskSendMessage 函数添加到 userScheduler。调度程序负责在正确的时间处理和运行任务。

userScheduler.addTask(taskSendMessage);

最后,启用 taskSendMessage,以便程序开始向网格发送消息。

taskSendMessage.enable();

要保持网格运行,请将 mesh.update() 添加到 loop() 中。

void loop() {
  // it will run the user scheduler as well
  mesh.update();
}

 示范

将代码上传到所有开发板(每个开发板具有不同的节点编号)后,您应该看到每个开发板正在接收其他开发板的消息。

以下屏幕截图显示了节点 1 收到的消息。它接收来自节点 2、3 和 4 的传感器读数。

ESP-MESH-Exchange-BME280-Sensor-Readings-ESP32-ESP8266-Serial-Monitor-f

 总结

希望您喜欢这篇 ESP-MESH 网络协议的快速介绍。

给TA打赏
共{{data.count}}人
人已打赏
Nodemcu/ESP8266Nodemcu/ESP8266-进阶动态

WebSocket 服务器:控制输出 - ESP8266 NodeMCU

2023-12-9 21:37:07

Nodemcu/ESP8266Nodemcu/ESP8266-进阶动态

在Arduino IDE中安装ESP8266 NodeMCU LittleFS文件系统上传器

2023-12-11 19:45:57

2 条回复 A文章作者 M管理员
  1. That’s a solid point about team dynamics! Seeing how quickly players adapt in-game is key, almost like the fast deposits at kn77 game – instant access makes all the difference! Good analysis.

  2. Strategic players need CardTime’s analytics for serious play. Visit cardtime com now to verify identity and unlock advanced tools before delays happen today.

个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索