ESP32使用ESP-NOW协议实现一对多通信和MAC地址存储
目录
- 介绍
- ESP-NOW 协议概述
- 在 ESP32 上配置 ESP-NOW
- 使用 ESP-NOW 进行一对多通信
- 在 ESP32 上存储发件人的 MAC 地址
- 代码
- 结论
介绍
ESP32 是一款功能强大的 Wi-Fi 和蓝牙双模模块,可用于使用 ESP-NOW 协议实现低功耗、高效率的一对多通信。本文将介绍如何使用ESP-NOW协议进行一对多通信,并在接收端存储发送方的MAC地址。
本文主要实现在使用ESP-NOW协议进行一对多通信以及接收端存储发送方的MAC地址,当esp32上电后检测有无存储MAC地址,有则将MAC添加到对等点peer,当接收到now信息时将发送端的MAC存储到flash中方便下次直接发送信息。
看本次代码直接划到最后即可!
ESP-NOW 协议概述
ESP-NOW 协议是乐鑫开发的一种快速高效的无线通信协议,专门用于 ESP8266 和 ESP32 模块之间的点对点或点对多点通信。它支持单播和组播两种传输模式,能够在网状网络中支持多达 20 个节点。与 Wi-Fi 和蓝牙等其他无线通信协议相比,ESP-NOW 特点如下:
- 低延迟:ESP-NOW协议不需要进行TCP/IP协议栈处理,因此具有很低的延迟,适合实时性要求较高的应用场景。
- 高吞吐量:ESP-NOW协议使用帧广播方式发送数据,传输效率高,可以同时向多个节点发送相同的信息。
- 低功耗:ESP-NOW协议在数据传输过程中使用的功耗非常低,适合电池供电场景。
- 简单易用:ESP-NOW协议配置简单,只需定义通道、加密密钥和数据结构等基本参数即可实现无线通信。
- 支持多播:ESP-NOW协议支持一对多或一对所有节点的通信方式,提高了网络的灵活性。
- 可扩展性:ESP-NOW协议支持多种数据类型的传输,可以通过自定义数据结构来实现更复杂的通信功能。
在 ESP32 上配置 ESP-NOW
本次开发IDE:arduino2.0.4
要在 ESP32 上配置 ESP-NOW,通过以下几步:
- 安装ESP32开发环境:在Arduino
IDE中,依次选择“文件”->“首选项”,在“附加开发板管理器网址”中添加以下链接,并单击“确定”按钮:
https://dl.espressif.com/dl/package_esp32_index.json - 然后打开“工具”->“开发板”->“开发板管理器”,搜索“esp32”,安装ESP32开发板支持。
- 引入ESP-NOW库:在Arduino IDE中,添加esp-now协议头文件。
- 编写代码(只是简单使用now,不是本次重点):
#include <esp_now.h>
#include <WiFi.h>// REPLACE WITH YOUR RECEIVER MAC Address
uint8_t broadcastAddress[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};// Structure example to send data
// Must match the receiver structure
typedef struct struct_message {char a[32];int b;float c;bool d;
} struct_message;// Create a struct_message called myData
struct_message myData;esp_now_peer_info_t peerInfo;// callback when data is sent
void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) {Serial.print("\r\nLast Packet Send Status:\t");Serial.println(status == ESP_NOW_SEND_SUCCESS ? "Delivery Success" : "Delivery Fail");
}void setup() {// Init Serial MonitorSerial.begin(115200);// Set device as a Wi-Fi StationWiFi.mode(WIFI_STA);// Init ESP-NOWif (esp_now_init() != ESP_OK) {Serial.println("Error initializing ESP-NOW");return;}// Once ESPNow is successfully Init, we will register for Send CB to// get the status of Trasnmitted packetesp_now_register_send_cb(OnDataSent);// Register peermemcpy(peerInfo.peer_addr, broadcastAddress, 6);peerInfo.channel = 0; peerInfo.encrypt = false;// Add peer if (esp_now_add_peer(&peerInfo) != ESP_OK){Serial.println("Failed to add peer");return;}
}void loop() {// Set values to sendstrcpy(myData.a, "THIS IS A CHAR");myData.b = random(1,20);myData.c = 1.2;myData.d = false;// Send message via ESP-NOWesp_err_t result = esp_now_send(broadcastAddress, (uint8_t *) &myData, sizeof(myData));if (result == ESP_OK) {Serial.println("Sent with success");}else {Serial.println("Error sending the data");}delay(2000);
}
不知道如何获取MAC地址?
#include "WiFi.h"void setup(){Serial.begin(115200);WiFi.mode(WIFI_MODE_STA);Serial.println(WiFi.macAddress());
}void loop(){}
- 上传代码:将代码上传到ESP32板子上。打开串口监视器,可以看到ESP32发送的数据。
使用 ESP-NOW 进行一对多通信
要使用 ESP-NOW 协议实现一对多通信,首先需要配置发送端和接收端。在发送端只需要读取有无存储到flash的MAC信息,如果有就添加到peer中,如果没有就等待接收到now信息然后添加到flash以及peer中。通过now信息发送将已添加到now里的对等点(peer)发送now信息。
esp_err_t result = esp_now_send(0, (uint8_t *)&test, sizeof(test_struct));
- uint8_t* dest_mac: 目标设备的MAC地址。如果传递NULL,则消息将广播到所有已配对的设备。
- uint8_t* data: 指向要发送的数据缓冲区的指针。
- size_t len: 要发送的数据字节数。
此外,esp_now_send()函数返回一个esp_err_t类型的错误代码,以指示发送操作是否成功。如果返回值为ESP_OK,则表明发送操作成功;否则,将返回对应的错误代码。
一般情况下使用arduino IDE开启esp-now模式一次只能同时发送3到4个消息,然后就会报错ESP_ERR_ESPNOW_INTERNAL提示发送失败
这是因为arduino上配置WiFi的原因,你需要做的就是强制 Arduino 使用默认的 sdkconfig WiFi 设置。
WiFi.useStaticBuffers(true);
在 ESP32 上存储发件人的 MAC 地址
在一对多通信场景中,跟踪发件人的 MAC 地址通常很有用。可以通过在接收方创建一个列表来存储所有发送方的 MAC 地址。每当收到新消息时,都可以从消息中提取发件人的 MAC 地址并将其添加到列表中。这样,就可以跟踪所有发件人及其相应的 MAC 地址。
在这里将MAC地址存储进入flash中,使用库是ArduinoNvs.h
,该库可以使存储信息到flash中简单化!
库地址如下
https://github.com/rpolitex/ArduinoNvs
打开不了?
来资源里下载(点击这里):https://download.csdn.net/download/hongyun1221/87712695
代码
附上代码
#include <WiFi.h>
#include <esp_now.h>
#include "ArduinoNvs.h"#define MAX_MAC_ADDRESSES 10 //最大20个esp_now_peer_info_t peerInfo;typedef struct test_struct {int x;int y;
} test_struct;
test_struct test;void printMacAddress(const uint8_t *macBytes) {for (int i = 0; i < 6; i++) {Serial.print(macBytes[i], HEX);if (i < 5) {Serial.print(":");}}Serial.println();
}// Helper function to convert a MAC address array to a string
String macToString(const uint8_t *mac) {String address = "";for (int i = 0; i < 6; ++i) {address += String(mac[i], HEX);if (i < 5) {address += ":";}}return address;
}bool addPeer(const String &macAddress) {uint8_t macBytes[6];peerInfo.channel = 0;peerInfo.encrypt = false;if (sscanf(macAddress.c_str(), "%02X:%02X:%02X:%02X:%02X:%02X", &macBytes[0], &macBytes[1], &macBytes[2], &macBytes[3], &macBytes[4], &macBytes[5]) != 6) {Serial.println("Failed to parse MAC address");return false;}memcpy(peerInfo.peer_addr, macBytes, 6);if (esp_now_add_peer(&peerInfo) != ESP_OK) {Serial.println("Failed to add peer");return false;}return true;
}// callback when data is sent
void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) {char macStr[18];Serial.print("Packet to: ");// Copies the sender mac address to a stringsnprintf(macStr, sizeof(macStr), "%02x:%02x:%02x:%02x:%02x:%02x",mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5]);Serial.print(macStr);Serial.print(" send status:\t");Serial.println(status == ESP_NOW_SEND_SUCCESS ? "Delivery Success" : "Delivery Fail");
}void OnDataRecv(const uint8_t *mac, const uint8_t *incomingData, int len) {Serial.print("received MAC: ");Serial.print(macToString(mac));Serial.print("Bytes received: ");Serial.println(len);// Check if MAC already exists in NVSString macAddress = macToString(mac);int count = NVS.getInt("count", 0);bool macExists = false;for (int i = 0; i < count; i++) {String storedMac = NVS.getString("mac" + String(i));if (storedMac.equals(macAddress)) {macExists = true;Serial.println("macExists");break;}}if (!macExists) {if (count >= MAX_MAC_ADDRESSES) {for (int i = 0; i < count; i++) {String nextMac = NVS.getString("mac" + String(i + 1));NVS.setString("mac" + String(i), nextMac);}// Store the MAC address in NVS and add it to ESP-NOW peer node listNVS.setString("mac" + String(count-1), macToString(mac));NVS.setInt("count", count);Serial.println("打印已存储的mac");for (int i = 0; i < count; i++) {String macAddress = NVS.getString("mac" + String(i));Serial.print(i);Serial.print(". ");Serial.println(macAddress);}ESP.restart();} else {// Store the MAC address in NVS and add it to ESP-NOW peer node listNVS.setString("mac" + String(count), macToString(mac));NVS.setInt("count", count + 1);memcpy(peerInfo.peer_addr, mac, 6);peerInfo.channel = 1;peerInfo.encrypt = false;if (esp_now_add_peer(&peerInfo) != ESP_OK) {Serial.println("Failed to add peer");ESP.restart();}}}
}void setup() {Serial.begin(115200);// Initialize WiFi and ESP-NOWWiFi.useStaticBuffers(true); //非常重要!强制 Arduino 使用默认的 sdkconfig!WiFi.mode(WIFI_STA);// 初始化 ESP-NOWif (esp_now_init() == ESP_OK) {Serial.println("ESPNow Init Success");} else {Serial.println("ESPNow Init Failed");ESP.restart();}// Initialize NVSNVS.begin("my-app");// Print the stored MAC addressesint count = NVS.getInt("count", 0);Serial.printf("Stored %d MAC addresses:\n", count);for (int i = 0; i < count; i++) {String macAddress = NVS.getString("mac" + String(i));Serial.print(i);Serial.print(". ");Serial.println(macAddress);if (addPeer(macAddress)) {// 成功添加对等节点 -- 处理} else {// 添加对等节点失败 -- 处理}}esp_now_register_send_cb(OnDataSent);esp_now_register_recv_cb(OnDataRecv);//信息初始化test.x = 5123;test.y = 12345;
}void loop() {delay(5000);int count = NVS.getInt("count", 0);//向已存在的同等体发送信息if (0 == count) {Serial.println("No peer");} else {esp_err_t result = esp_now_send(0, (uint8_t *)&test, sizeof(test_struct));if (result == ESP_OK) {Serial.println("Sent with success");} else {Serial.println("Error sending the data");}Serial.print("Peer count: ");Serial.println(count);}
}
输出展示:
以下是函数的讲解:
-
printMacAddress(const uint8_t *macBytes):打印 MAC 地址数组。
-
macToString(const uint8_t *mac):将 MAC 地址数组转换为字符串类型。
-
addPeer(const String &macAddress):将设备添加到 ESP NOW 对等节点列表中。
-
OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status):发送数据回调函数。如果数据发送成功,将打印“Delivery Success”;否则将打印“Delivery Fail”。
-
OnDataRecv(const uint8_t *mac, const uint8_t *incomingData, int len):接收数据回调函数。将检查是否已经存在该设备的 MAC 地址,并根据需要将其添加到 ESP NOW 对等节点列表中。
-
setup():初始化 Wi-Fi、ESP NOW 和 NVS。该函数还会打印存储的 MAC 地址,并注册了发送和接收数据的回调函数。
-
loop():每隔五秒钟向已配对的设备发送测试结构体 test 中的数据。该函数还会显示当前已存储的对等节点数量。
#define MAX_MAC_ADDRESSES 10 // 最大允许存储的 MAC 地址数量esp_now_peer_info_t peerInfo; // 定义 ESP-NOW 的对等节点信息结构体
部分函数讲解
定义两个辅助函数,一个将MAC地址数组打印为十六进制字符串,另一个将MAC地址数组转换为字符串类型。
void printMacAddress(const uint8_t *macBytes) {for (int i = 0; i < 6; i++) {Serial.print(macBytes[i], HEX);if (i < 5) {Serial.print(":");}}Serial.println();
}// Helper function to convert a MAC address array to a string
String macToString(const uint8_t *mac) {String address = "";for (int i = 0; i < 6; ++i) {address += String(mac[i], HEX);if (i < 5) {address += ":";}}return address;
}
定义将MAC地址添加到ESP-NOW对等体结构体变量中的函数。
bool addPeer(const String &macAddress) {uint8_t macBytes[6];peerInfo.channel = 0;peerInfo.encrypt = false;if (sscanf(macAddress.c_str(), "%02X:%02X:%02X:%02X:%02X:%02X", &macBytes[0], &macBytes[1], &macBytes[2], &macBytes[3], &macBytes[4], &macBytes[5]) != 6) {Serial.println("Failed to parse MAC address");return false;}memcpy(peerInfo.peer_addr, macBytes, 6);if (esp_now_add_peer(&peerInfo) != ESP_OK) {Serial.println("Failed to add peer");return false;}return true;
}
结论
总之,ESP-NOW 协议是一种强大的无线通信协议,可用于使用 ESP32 模组实现一对多通信。通过将发射器和接收器配置为使用 ESP-NOW 协议,您可以实现与多个节点的低延迟、高吞吐量和低功耗通信。此外,通过存储发送方的 MAC 地址,您可以跟踪网络中的所有节点。通过限制存储的 MAC 地址数量,可以防止内存溢出问题并确保 ESP32 模组的高效运行。
相关文章:

ESP32使用ESP-NOW协议实现一对多通信和MAC地址存储
目录 介绍ESP-NOW 协议概述在 ESP32 上配置 ESP-NOW使用 ESP-NOW 进行一对多通信在 ESP32 上存储发件人的 MAC 地址代码结论 介绍 ESP32 是一款功能强大的 Wi-Fi 和蓝牙双模模块,可用于使用 ESP-NOW 协议实现低功耗、高效率的一对多通信。本文将介绍如何使用ESP-NO…...

Qt 学生信息数据库管理
1 添加样式表 我们采用了样式表 通过添加Qt resources文件 添加前缀 添加文件,将我们的图标进行添加 2 拖动部件 用到的部件 Label 标签Pushbutton 按钮table view 视图LineEdit 输入框 3 程序编写 1 配置sql环境 在 pro文件中 添加 连接数据库跟访问数据…...

相量的加减乘除计算
相量的加减乘除计算 矢量是物理学中的术语,是指具有大小(magnitude)和方向的量。如速度、加速度、力等等就是这样的量。向量是数学中的术语,也称为欧几里得向量、几何向量、矢量。与向量对应的量叫做数量,在物理学中称…...

JavaScript 代码整洁之道
文章目录 概述篇变量篇函数篇注释篇异常处理篇复杂判断函数篇重构篇代码风格常量大写先声明后调用注释 参考资料 概述篇 书写能让人读懂的代码使用英语编写代码团队协作 制定通用的规则,依靠工具让团队的代码风格保持统一,要让代码看起来是由一个人编写…...

socket 及 字节序转换(嵌入式学习)
socket 及 字节序转换 socket简介Socket为什么需要Socket?socket类型Socket通信模型 字节序主机字节序到网络字节序网络字节序到主机字节序IP地址转换 socket简介 1、1982 - Berkeley Software Distributions 操作系统引入了socket作为本地进程之间通信的接口 2、1…...

Java之~ Aop自定义注解日志
大纲步骤: 一,创建需要记录的日志表,创建基础方法。(省略) 二,在需要加记录日志的方法上加Aop注解1,创建一个注解类,Aop中定义一个注解import java.lang.annotation.*; /*** http 请…...
编译原理个人作业--第四章
构造FIRST和FOLLOW的大白话网站 第四章 1 考虑文法 G 1 G_1 G1: S → a ∣ ∧ ∣ ( T ) T → T , S ∣ S S \rightarrow a|\land|(T) \\ T\rightarrow T,S|S S→a∣∧∣(T)T→T,S∣S 先复习左递归如何消除 原书p69页 类似于 P → P a ∣ b P\rightarrow Pa|b P→Pa∣b的…...
学习笔记:数据库简介
数据库是一系列可以方便的访问和修改的数据的集合。 所有数据库管理系统的主要工作都是可靠的存储数据并使其对用户可用。 目前最常见的数据库模型主要是两种,即关系型数据库和非关系型数据库。 一、按数据的组织方式 数据从组织的角度上,主要分为结…...

day18_集合
今日内容 零、 复习昨日 一、集合框架体系 二、Collection 三、泛型 四、迭代 五、List 六、ArrayList 七、LinkedList 零、 复习昨日 晨考 一、集合框架体系 数组: 是一个容器,用来存放数据的 定长只能存储同一种数据类型的数据int[] 可以存储int值,Student[] 可以存储引用类型…...
Go面试必会基础题
文章目录 1.下面代码有什么错误?2.下面代码有什么问题?3.下面代码输出什么?4.下面这段代码输出什么? 1.下面代码有什么错误? func main() {one : 0one : 1 }参考答案及解析:变量重复声明。不能在单独的声…...

发送封包协议实现XXZ批量秒分解装备
通过发送封包,我们可以让一些反复的枯燥的行为变的简单,高效。 比如XXZ的萃取装备,我们可以一瞬间萃取大量的装备,而省去读条的过程。 我们来萃取一下看看效果 手动萃取是有读条的,那么如果很多装备的话,…...

Spring学习——Nginx
Nginx概述 Nginx介绍 Nginx是一款轻量级的web 服务器/反向代理服务器及电子邮件(IMAP/POP3)代理服务器。其特点是占有内存少,并发能力强,事实上nginx的并发能力在同类型的网页服务器中表现较好,中国大陆使用nginx的网…...

记录 vue-cli 安装过程
1. VueCli CLI 是 Commond-Line Interface 的缩写 如果开发大型项目,肯定需要考虑代码目录结构、项目结构和部署、热加载、代码单元测试等事情,那么你必然需要使用 VueCLI,使用 VueCLI 可以快速搭建 vue 开发环境以及对应的 webpack 配置。 …...

含氢微网优化调度模型matlab
目录 1 主要内容 模型示意图 目标函数 2 部分程序 3 程序结果 4 下载链接 1 主要内容 最近咨询含氢微网优化调度模型的同学较多,本次就分享一个高质量的源码资源。该程序方法复现《Simulation of design and operation of hydrogen energy utilization syste…...

【springcloud开发教程】路由网关——zuul
官方资料:https://github.com/Netflix/zuul/ 什么是Zuul? Zuul包含了两个主要的功能:路由和过滤 路由功能将外部请求转发到具体的微服务实例上,是实现外部访问统一入口的基础,而过滤器功能则负责对请求的处理过程进行干预&#…...

DF竞赛平台携手嬴彻科技与清华大学智能产业研究院,助力自动驾驶挑战赛圆满落幕!
由DataFountain竞赛平台(简称DF平台)提供办赛支持的「首届“嬴彻-清华AIR杯”自动驾驶挑战赛:决策规划算法」已圆满落幕。作为一场前沿性自动驾驶类比赛,本次大赛立足“高速道路”和“城市道路”两大真实场景,选择“半…...

234:vue+openlayers 加载本地shp数据,在map上显示图形
第234个 点击查看专栏目录 本示例的目的是介绍演示如何在vue+openlayers中利用shapefile读取本地的shp数据,并在地图上显示图形。 直接复制下面的 vue+openlayers源代码,操作2分钟即可运行实现效果 文章目录 示例效果安装引用配置方式示例源代码(共143行)相关API参考:专栏…...

网络模型-网络体系结构(OSI、TCP/IP)
网络模型(网络体系结构) 网络模型网络的体系结构OSI模型TCP/IP模型OSI和TCP/IP模型对应关系图 常见网络协议 网络模型 网络的体系结构 1、网络采用分而治之的方法设计,将网络的功能划分为不同的模块,以分层的形式有机组合在一起…...

园区智慧导览地图软件,智慧工厂导航定位怎么解决方案的
智慧工厂导航定位怎么解决方案的地图新基建是行业的核心数字基础需求之一,行业内中已构建了较为完整的城市级地理信息系统。园区管理涉及众多方面,因此园区的智慧信息化建设至关重要,需求越来越广泛。在智慧园区中,基于园区的电子…...

Redis高可用之3种集群方案对比
Redis集群方案使用建议: Redis cluster:除非是1000个节点以上的超大规模集群,优先考虑使用Redis clustercodis:旧项目如果仍在使用codis,可继续使用,但也推荐迁移到Redis clustertwemproxy:不建…...
synchronized 学习
学习源: https://www.bilibili.com/video/BV1aJ411V763?spm_id_from333.788.videopod.episodes&vd_source32e1c41a9370911ab06d12fbc36c4ebc 1.应用场景 不超卖,也要考虑性能问题(场景) 2.常见面试问题: sync出…...

智慧医疗能源事业线深度画像分析(上)
引言 医疗行业作为现代社会的关键基础设施,其能源消耗与环境影响正日益受到关注。随着全球"双碳"目标的推进和可持续发展理念的深入,智慧医疗能源事业线应运而生,致力于通过创新技术与管理方案,重构医疗领域的能源使用模式。这一事业线融合了能源管理、可持续发…...

(十)学生端搭建
本次旨在将之前的已完成的部分功能进行拼装到学生端,同时完善学生端的构建。本次工作主要包括: 1.学生端整体界面布局 2.模拟考场与部分个人画像流程的串联 3.整体学生端逻辑 一、学生端 在主界面可以选择自己的用户角色 选择学生则进入学生登录界面…...
OkHttp 中实现断点续传 demo
在 OkHttp 中实现断点续传主要通过以下步骤完成,核心是利用 HTTP 协议的 Range 请求头指定下载范围: 实现原理 Range 请求头:向服务器请求文件的特定字节范围(如 Range: bytes1024-) 本地文件记录:保存已…...

Java-41 深入浅出 Spring - 声明式事务的支持 事务配置 XML模式 XML+注解模式
点一下关注吧!!!非常感谢!!持续更新!!! 🚀 AI篇持续更新中!(长期更新) 目前2025年06月05日更新到: AI炼丹日志-28 - Aud…...
uniapp中使用aixos 报错
问题: 在uniapp中使用aixos,运行后报如下错误: AxiosError: There is no suitable adapter to dispatch the request since : - adapter xhr is not supported by the environment - adapter http is not available in the build 解决方案&…...

Aspose.PDF 限制绕过方案:Java 字节码技术实战分享(仅供学习)
Aspose.PDF 限制绕过方案:Java 字节码技术实战分享(仅供学习) 一、Aspose.PDF 简介二、说明(⚠️仅供学习与研究使用)三、技术流程总览四、准备工作1. 下载 Jar 包2. Maven 项目依赖配置 五、字节码修改实现代码&#…...
【Android】Android 开发 ADB 常用指令
查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...
django blank 与 null的区别
1.blank blank控制表单验证时是否允许字段为空 2.null null控制数据库层面是否为空 但是,要注意以下几点: Django的表单验证与null无关:null参数控制的是数据库层面字段是否可以为NULL,而blank参数控制的是Django表单验证时字…...

解析奥地利 XARION激光超声检测系统:无膜光学麦克风 + 无耦合剂的技术协同优势及多元应用
在工业制造领域,无损检测(NDT)的精度与效率直接影响产品质量与生产安全。奥地利 XARION开发的激光超声精密检测系统,以非接触式光学麦克风技术为核心,打破传统检测瓶颈,为半导体、航空航天、汽车制造等行业提供了高灵敏…...