ESP32使用ESP-NOW协议实现一对多通信和MAC地址存储

news2025/1/16 1:39:41

目录

  • 介绍
  • 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 特点如下:

  1. 低延迟:ESP-NOW协议不需要进行TCP/IP协议栈处理,因此具有很低的延迟,适合实时性要求较高的应用场景。
  2. 高吞吐量:ESP-NOW协议使用帧广播方式发送数据,传输效率高,可以同时向多个节点发送相同的信息。
  3. 低功耗:ESP-NOW协议在数据传输过程中使用的功耗非常低,适合电池供电场景。
  4. 简单易用:ESP-NOW协议配置简单,只需定义通道、加密密钥和数据结构等基本参数即可实现无线通信。
  5. 支持多播:ESP-NOW协议支持一对多或一对所有节点的通信方式,提高了网络的灵活性。
  6. 可扩展性:ESP-NOW协议支持多种数据类型的传输,可以通过自定义数据结构来实现更复杂的通信功能。

在 ESP32 上配置 ESP-NOW

本次开发IDE:arduino2.0.4
要在 ESP32 上配置 ESP-NOW,通过以下几步:

  1. 安装ESP32开发环境:在Arduino
    IDE中,依次选择“文件”->“首选项”,在“附加开发板管理器网址”中添加以下链接,并单击“确定”按钮:
    https://dl.espressif.com/dl/package_esp32_index.json
  2. 然后打开“工具”->“开发板”->“开发板管理器”,搜索“esp32”,安装ESP32开发板支持。
  3. 引入ESP-NOW库:在Arduino IDE中,添加esp-now协议头文件。
  4. 编写代码(只是简单使用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 Monitor
  Serial.begin(115200);
 
  // Set device as a Wi-Fi Station
  WiFi.mode(WIFI_STA);

  // Init ESP-NOW
  if (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 packet
  esp_now_register_send_cb(OnDataSent);
  
  // Register peer
  memcpy(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 send
  strcpy(myData.a, "THIS IS A CHAR");
  myData.b = random(1,20);
  myData.c = 1.2;
  myData.d = false;
  
  // Send message via ESP-NOW
  esp_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(){

}
  1. 上传代码:将代码上传到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 string
  snprintf(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 NVS
  String 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 list
      NVS.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 list
      NVS.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-NOW
  WiFi.useStaticBuffers(true);  //非常重要!强制 Arduino 使用默认的 sdkconfig!
  WiFi.mode(WIFI_STA);
  // 初始化 ESP-NOW
  if (esp_now_init() == ESP_OK) {
    Serial.println("ESPNow Init Success");
  } else {
    Serial.println("ESPNow Init Failed");
    ESP.restart();
  }
  // Initialize NVS
  NVS.begin("my-app");
  // Print the stored MAC addresses
  int 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 模组的高效运行。

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/443845.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【数据分析实战】基于python对酒店预订需求进行分析

文章目录 &#x1f4da;引言&#x1f4d6;数据加载以及基本观察&#x1f4d1;缺失值观察及处理&#x1f516;缺失值观察以及可视化&#x1f516;缺失值处理 &#x1f4d6;用户数据探索&#x1f4d1;什么时间预定酒店将会更经济实惠&#xff1f;&#x1f4d1;哪个月份的酒店预订…

centos8 mysql 主从复制

♥️作者:小刘在C站 ♥️个人主页:小刘主页 ♥️每天分享云计算网络运维课堂笔记,努力不一定有收获,但一定会有收获加油!一起努力,共赴美好人生! ♥️夕阳下,是最美的绽放,树高千尺,落叶归根人生不易,人间真情 目录 Linux centos8

机器学习入门实例-MNIST手写数据集-多分分类错误分析多标签分类多输出分类

多分类 随机梯度下降、随机森林和朴素贝叶斯都可以处理多分类问题&#xff0c;而logistic回归、支持向量机是严格的二分类分类器&#xff0c;但是可以用一些方法将多个二分类分类器组合在一起完成多分类任务。 1. OvR&#xff08;one-versus-the-rest、one-versus-all&#x…

webp格式怎么转换成jpg,3种常用工具方法

在日常办公中&#xff0c;我们经常需要在网上找到一些图片进行编辑。但有时候我们会遇到Webp格式的图片&#xff0c;而有些软件无法直接编辑它们。Webp是一种由谷歌开发的图像文件格式&#xff0c;它提供了有损压缩和无损压缩(可逆压缩)的功能&#xff0c;近年来&#xff0c;它…

Kafka基础篇学习笔记整理

Kafka基础篇学习笔记整理 生产者数据发送流程批量与定时发送缓冲区大小send发送消息消息可靠性发布确认机制重试机制 消息顺序性问题如何避免重试导致消息顺序错乱自定义拦截器自定义序列化器自定义分区器幂等与事务kafka实现幂等kafka实现事务事务的隔离级别使用演示 消费者重…

【状态估计】用于描述符 LTI 和 LPV 系统的分析、状态估计和故障检测的算法(Matlab代码实现)

&#x1f4a5; &#x1f4a5; &#x1f49e; &#x1f49e; 欢迎来到本博客 ❤️ ❤️ &#x1f4a5; &#x1f4a5; &#x1f3c6; 博主优势&#xff1a; &#x1f31e; &#x1f31e; &#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 …

免费文案生成器-免费文案改写神器

推荐一款高效免费自动写作软件&#xff0c;让你的写作效率飞升&#xff01; 写作&#xff0c;对于众多的从业者或学生来说都是必不可少的工作内容。然而&#xff0c;许多人在写作时遇到了各种各样的困难&#xff0c;例如缺乏灵感、引用不足、缺乏逻辑性等等。为了解决这些问题…

Linux DHCP服务

DHCP 作用 DHCP动态主机配置协议作为服务端负责集中给客户端分配各种网络地址参数(主要包括IP地址、子网掩码、广播地址、默认网关地址、DNS服务器地址) 传输协议端口 服务端 UDP 67端口 客户端 UDP 68端口 工作原理 1) 客户端广播发送DISCOVER报文寻找服务端 2) 服务端广播发…

Unity - 带耗时 begin ... end 的耗时统计的Log - TSLog

CSharp Code // jave.lin 2023/04/21 带 timespan 的日志 &#xff08;不帶 log hierarchy 结构要求&#xff0c;即&#xff1a; 不带 stack 要求&#xff09;using System; using System.Collections.Generic; using System.IO; using UnityEditor; using UnityEngine;public…

Qt 学生信息数据库管理

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

Qt模型视图结构

一.模型视图介绍 1.Model/View(模型/视图结构) 视图(View)是显示和编辑数据的界面组件&#xff0c; 模型(Model)是视图和原始数据之间的接口 2.视图组件有:QListView QTreeView QTableView&#xff0c;QColumnView&#xff0c;QHeaderView 模型组件有:QStringListM…

MyBatis详解(2)

8、自定义映射resultMap 8.1、resultMap处理字段和属性的映射关系 若字段名和实体类中的属性名不一致&#xff0c;则可以通过resultMap设置自定义映射 <!--resultMap&#xff1a;设置自定义映射属性&#xff1a;id&#xff1a;表示自定义映射的唯一标识type&#xff1a;查询…

PCIE_DMA实例二:xapp1052的EDK仿真

目录 一&#xff1a;前言 二&#xff1a;前期准备 三&#xff1a;操作步骤 四&#xff1a;仿真结果 五&#xff1a;总结 一&#xff1a;前言 对于有的同学&#xff0c;想要学习基于FPGA的PCIe DMA控制器设计&#xff0c;但是手上没有合适的Xilinx开发板&#xff0c;而且xap…

ETCD(五)写请求执行过程

写请求过程 客户端执行写请求指令 etcdctl put hello world —endpoints 192.168.1.1:12379执行流程&#xff1a; 首先客户端通过负载均衡选择一个etcd节点发起gRPC put方法调用&#xff1b;服务器收到请求后经过gRPC拦截器、Quota模块校验&#xff0c;进入KV Server模块&am…

「物联网时代的新选择」漫途科技推出装配式物联网服务,轻松实现项目落地

随着物联网技术的不断发展&#xff0c;越来越多的企业开始重视物联网系统的应用。然而&#xff0c;在物联网时代&#xff0c;鱼龙混杂&#xff0c;小品牌厂商层出不穷&#xff0c;质量参差不齐&#xff0c;这为系统集成商寻找靠谱的供应商伙伴带来了极大的挑战。 一、如何找靠谱…

Nacos配置中心的配置是怎么加载到spring容器的?

首先看到 org.springframework.boot.SpringApplication#applyInitializers 这个方法。 protected void applyInitializers(ConfigurableApplicationContext context) {for (ApplicationContextInitializer initializer : getInitializers()) {Class<?> requiredType G…

单链表C语言实现

链表就是许多节点在逻辑上串起来的数据存储方式 是通过结构体中的指针将后续的节点串联起来 typedef int SLTDataType;//数据类型 typedef struct SListNode//节点 {SLTDataType data;//存储的数据struct SListNode* next;//指向下一个节点地址的指针 }SLTNode;//结构体类型的…

设计模式(GOF)之我见(0)——UML

这里直接梳理画类图时的几个类关系。 类图的语法和功能 关系说明举例依赖&#xff08;Dependency) 偶然的&#xff0c;陌生的。 对类B进行修改会影响到A。 例如&#xff1a;问路时&#xff0c;路人甲给路人乙带路&#xff0c;路人甲的指引必然会影响到路人乙&#xff0c;但是…

在ROS2中使用奥比中光(ORBBEC)的AstraPro深度相机

0.效果演示 1.下载SDK 到官网下载OpenNI2_SDK 记得是下载这个OpenNI2_SDK,而不是下载那个Orbbec_SDK. 2.拷贝至自定义目录 拷贝到你的ubuntu的一个文件夹中&#xff0c;并解压得到 ros2_astra_camera 文件夹 然后新建一个ros2_ws文件夹&#xff0c;再在ros2_ws文件夹中新建…

矩阵链相乘的乘法次数(动态规划)

Description 设 A1, A2, …, An 为矩阵序列&#xff0c;Ai 是阶为 Pi − 1 * Pi 的矩阵 i  1, 2, …, n.试确定矩阵的乘法顺序&#xff0c;使得计算 A1A2…An 过程中元素相乘的总次数最少.Input 多组数据第一行一个整数 n(1≤n≤300) &#xff0c;表示一共有 n 个矩…