前言
ESP8266有很多好玩的技術,比如sniffer、smartconfig。這一次就介紹的是ESP-NOW。
那麼什麼是ESP-NOW呢?根據官方文檔介紹
ESP-NOW 是一種短數據傳輸、無連接的快速通信技術,適用於智能燈、遙控控制、傳感器數據回傳等場景。
下面會簡單介紹ESP-NOW,並放出使用示例代碼(基於ESP8266 SDK 2.0),想更深入研究請參考官方文檔。
面向的讀者
本文主要面向ESP8266開發者,閱讀本文需要有一定的ESP8266開發基礎,比如開發環境的搭建、SDK的開發等。
開發平臺和工具
- Windows 10 x64
- ESP8266 IDE 2.0
- NodeMCU(4MB Flash)
- ESP8266_NONOS_SDK 2.0.0
ESP-NOW
特性
ESP-NOW 支持下面特性:
- 單播包加密/不加密通信
- 加密和非加密配對設備混合
- 可攜帶最長250字節的用戶數據(payload)
- 支持設置發送回調函數
限制
- 暫時不支持廣播包
- 加密配對有數量限制(具體參考文檔)
- 用戶字節限制爲250字節
ESP-NOW 的 Role
在配置 ESP-NOW 的時候需要給設備配置role,ESP-NOW有如下role:
- IDLE。不設置角色,不允許發送數據。
- CONTROLLER。控制方。
- SLAVE。被控制方。
- COMBO。控制方&被控制方。
其中,ESP8266的Wi-Fi模式有station和softAP。當ESP-NOW作爲CONTROLLER時,數據優先從station接口發出。當作爲SLAVE時,數據優先從softAP接口發出
示例代碼
博主自己編寫的示例代碼user_esp_now.c
如下:
/*
* user_esp_now.c
*
* Created on: 2017年7月8日
* Author: Administrator
*/
#include "ets_sys.h"
#include "osapi.h"
#include "user_interface.h"
#include "espnow.h"
#define CONTROLLER
//#define SLAVE
// 是否使用加密
#define SECURITY 0
#if SECURITY
u8 g_key[16]= {0x12, 0x34, 0x56, 0x78,
0x12, 0x34, 0x56, 0x78,
0x12, 0x34, 0x56, 0x78,
0x12, 0x34, 0x56, 0x78};
#endif
// 使用虛擬的MAC地址,station設置MAC似乎不行
u8 controller_mac[6] = {0xA0, 0x20, 0xA6, 0xAA, 0xAA, 0xAA};
u8 slave_mac[6] = {0xA2, 0x20, 0xA6, 0x55, 0x55, 0x55};
#define ESP_NOW_SEND_INTERVAL 3000
static os_timer_t g_esp_now_timer;
/*
* 函數:user_esp_now_recv_cb
* 說明:ESP-NOW接收回調函數
*/
static void ICACHE_FLASH_ATTR
user_esp_now_recv_cb(u8 *macaddr, u8 *data, u8 len)
{
int i;
static u16 ack_count=0;
u8 ack_buf[16];
u8 recv_buf[17];
os_printf("now from[");
for(i = 0; i < 6; i++){
os_printf("%02X ", macaddr[i]);
}
os_printf(" len: %d]:", len);
os_bzero(recv_buf, 17);
os_memcpy(recv_buf, data, len<17?len:16);
os_printf(recv_buf);
os_printf("\r\n");
if (os_strncmp(data, "ACK", 3) == 0){
return;
}else{
}
os_sprintf(ack_buf, "ACK[%08x]", ack_count++);
esp_now_send(macaddr, ack_buf, os_strlen(ack_buf));
}
/*
* 函數:user_esp_now_send_cb
* 說明:ESP-NOW發送回調函數
*/
void ICACHE_FLASH_ATTR
user_esp_now_send_cb(u8 *mac_addr, u8 status)
{
int i;
for(i = 0; i < 6; i++){
os_printf("%02X ", mac_addr[i]);
}
if(1==status){
os_printf("SEND FAIL!\r\n");
}else if(0==status){
os_printf("SEND SUCCESSFUL!\r\n");
}
}
/*
* 函數:user_esp_now_send
* 說明:ESP-NOW數據發送
*/
void ICACHE_FLASH_ATTR
user_esp_now_send(u8 *mac_addr, u8 *data, u8 len)
{
/* the demo will send to two devices which added by esp_now_add_peer() */
//u8 result = esp_now_send(NULL, data, len);
/* send to the specified mac_addr */
u8 result = esp_now_send(mac_addr, data, len);
}
/*
* 函數:user_esp_now_timer_cb
* 說明:定時器回調函數
*/
static void ICACHE_FLASH_ATTR
user_esp_now_timer_cb(void* arg)
{
u8 *mac = arg;
u8* send_data = "Hello World!";
int result = esp_now_is_peer_exist(mac);
//os_printf("peer_exist = %d\r\n", result);
user_esp_now_send(mac, send_data, os_strlen(send_data));
}
/*
* 函數:user_esp_now_timer_init
* 說明:定時發送 ESP_NOW 數據包
*/
void ICACHE_FLASH_ATTR
user_esp_now_timer_init(u8 *mac)
{
os_timer_disarm(&g_esp_now_timer);
os_timer_setfn(&g_esp_now_timer, (os_timer_func_t *)user_esp_now_timer_cb, mac);
os_timer_arm(&g_esp_now_timer, ESP_NOW_SEND_INTERVAL, 1);
}
/*
* 函數:user_esp_now_init
* 說明:ESP-NOW初始化
*/
void ICACHE_FLASH_ATTR
user_esp_now_init(void)
{
int result;
if (esp_now_init()==0) {
os_printf("esp_now init ok\n");
// 註冊 ESP-NOW 收包的回調函數
esp_now_register_recv_cb(user_esp_now_recv_cb);
// 註冊發包回調函數
esp_now_register_send_cb(user_esp_now_send_cb);
#if SECURITY
// 設置主密鑰
//esp_now_set_kok(g_key, 16);
#endif
/* role
* ESP_NOW_ROLE_IDLE - 空閒
* ESP_NOW_ROLE_CONTROLLER - 主機
* ESP_NOW_ROLE_SLAVE - 從機
* ESP_NOW_ROLE_COMBO - 主/從機
*/
#if defined(SLAVE)
os_printf("==================\r\n");
os_printf("SLAVE\r\n");
os_printf("==================\r\n");
esp_now_set_self_role(ESP_NOW_ROLE_SLAVE);
//esp_now_add_peer(controller_mac, ESP_NOW_ROLE_CONTROLLER, 1, NULL, 16);
#if SECURITY
esp_now_set_peer_key(controller_mac, g_key, 16);
#endif
#elif defined(CONTROLLER)
os_printf("==================\r\n");
os_printf("CONTROLLER\r\n");
os_printf("==================\r\n");
esp_now_set_self_role(ESP_NOW_ROLE_CONTROLLER);
esp_now_add_peer(slave_mac, ESP_NOW_ROLE_SLAVE, 1, NULL, 16);
#if SECURITY
esp_now_set_peer_key(slave_mac, g_key, 16);
#endif
// 不需要連接Wi-Fi
wifi_station_disconnect();
//wifi_station_connect();
user_esp_now_timer_init(slave_mac);
#endif
} else {
os_printf("esp_now init failed\n");
}
}
/************************************************************************/
/*
* 函數:user_esp_now_set_mac_current
* 說明:設置虛擬MAC地址,必須在 user_init() 函數裏調用
*/
void ICACHE_FLASH_ATTR
user_esp_now_set_mac_current(void)
{
#if defined(SLAVE)
wifi_set_macaddr(SOFTAP_IF, slave_mac);
wifi_set_opmode_current(SOFTAP_MODE);
#elif defined(CONTROLLER)
// 設置station MAC地址
wifi_set_macaddr(STATION_IF, controller_mac);
// 設置爲station模式
wifi_set_opmode_current(STATION_MODE);
#endif
}
以上代碼的功能分爲兩部分:CONTROLLER
和SLAVE
。CONTROLLER是控制器代碼,類似於Client/Server的Client,需要設置Wi-FI爲station模式,CONTROLLER每個3秒中向SLAVE發送Hello World!
字符串。SLAVE爲被控制方,類似於server,需要打開softAP模式,這裏爲了方便使用了虛擬的MAC地址(0xA220A6555555)。另外,softAP和station使用的MAC不同,一般來說station硬件MAC爲0xA0開頭,softAP的爲0xA2開頭,如果是使用硬件MAC這一點要注意。
其他說明代碼裏面的註釋都寫得很清楚。
其中user_esp_now_set_mac_current()
函數必須在user_init()
函數裏調用。
user_esp_now_init()
爲 ESP-NOW 初始化函數,可以放在system_init_done_cb()
的回調函數裏調用。
user_main.c
主要代碼
#include "ets_sys.h"
#include "osapi.h"
#include "user_interface.h"
#include "driver/uart.h"
#include "espnow.h"
#include "user_esp_now.h"
// 省略……
void ICACHE_FLASH_ATTR
init_done_cb_init(void)
{
user_esp_now_init();
}
void ICACHE_FLASH_ATTR
user_init(void)
{
//uart_init(BIT_RATE_115200, BIT_RATE_115200);
user_esp_now_set_mac_current();
os_printf("SDK version:%s\n", system_get_sdk_version());
// 系統初始化後回調
system_init_done_cb(init_done_cb_init);
}
使用步驟
準備兩個ESP8266,分別燒錄CONTROLLER
和SLAVE
的代碼。爲了方便,SLAVE使用了虛擬了MAC地址(softAP),所以不用在CONTROLLER裏修改爲硬件MAC地址。
重啓上電,波特率76800,可以看到下面的打印信息。CONTROLLER的station並不需要連接SLAVE的softAP。
controller打印信息:
slave打印信息:
其他
加密發送消息暫時還沒調試好,發送消息對方會收不到。
本教程相關代碼
- Github:https://github.com/AngelLiang/ESP8266-Demos (esp-now_app文件夾)
- CSDN:http://download.csdn.net/detail/u012163234/9893267
備註:CSDN版本上傳後不再更新。