# 使用ESP-NOW协议

## 1. 功能简介

ESP-NOW 是由乐鑫开发的另一款无线通信协议，可以使多个设备在没有或不使用 Wi-Fi 的情况下进行通信。这种协议类似常见于无线鼠标中的低功耗 2.4GHz 无线连接——设备在进行通信之前要进行配对。配对之后，设备之间的连接是持续的、点对点的，并且不需要握手协议。它是一种短数据传输、无连接的快速通信技术，可以让低功耗控制器直接控制所有智能设备而无需连接路由器，适用于智能灯、遥控控制、传感器数据回传等场景。

使用了 ESP-NOW 通信之后，如果某一个设备突然断电之后，只要它一旦重启，就是自动连接到对应的节点中重新进行通信。

ESP-NOW 的通信模式支持如下：

* 一对一通信
* 一对多通信
* 多对一通信
* 多对多通信

ESP-NOW 支持如下特性：

* 单播包加密或单播包不加密通信；
* 加密配对设备和非加密配对设备混合使用；
* 可携带最长为 250 字节的有效 payload 数据；
* 支持设置发送回调函数以通知应用层帧发送失败或成功。

同时，ESP-NOW 也存在一些限制：

* 暂时不支持广播包；
* 加密配对设备有以下限制：
  * Station 模式下最多支持10 个加密配对设备；
  * SoftAP 或 SoftAP + Station 混合模式下最多支持 6 个加密配对设备；
  * 非加密配对设备支持若干，与加密设备总数和不超过 20 个；
* 有效 payload 限制为 250 字节。

Petoi群控完全可以采用ESP8266模块的ESP-NOW通信功能。

## 2. 准备工作

### 2.1 硬件准备

本案例准备2只Bittle（配备WiFi模块），一台连接有ESP模块的计算机。

<figure><img src="https://201656985-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MQ6a951Q6Jn1Zzt5Ajr-3369173170%2Fuploads%2FBJWLqrMPp8heJGiN543r%2Fstructure.png?alt=media&#x26;token=b461c6d4-63b9-4f17-807f-a63086b30ab5" alt=""><figcaption></figcaption></figure>

图中模块的程序烧录和MAC地址的获取见下文。

### 2.2 软件准备

电脑安装Thonny，方便进行8266模块的MicroPython的调试。 使用ESP-NOW协议时，需要特殊的MicroPython固件（见[Github](https://github.com/glenn20/micropython-espnow-images)）。因为普通版本的8266-MicroPython固件会提示找不到库。

打开Thonny并使用USB上载器连接ESP8266模块，在shelll界面中输入：

```python
import espnow
```

如果出现错误提示`如“找不到espnow”模块`，表示固件烧录有问题；若没有任何提示，表示固件烧录正常。

{% hint style="info" %}
如果烧录完ESP-NOW固件后，在shelll界面未出现Python标志性&#x7684;**`>>>`**&#x7B26;号，表示固件烧录失败。可以尝试使用Flash烧录工具 [NodeMCU-PyFlasher.exe](https://github.com/marcelstoer/nodemcu-pyflasher/releases/tag/v5.0.0)，烧录配置如下图所示：![](https://201656985-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MQ6a951Q6Jn1Zzt5Ajr-3369173170%2Fuploads%2Fy7iqvKz7K01rKZ411EoB%2FNodeMCU_config_espnow.png?alt=media\&token=e4a45971-e098-46b1-9884-2c88241d801f)
{% endhint %}

## 3. 代码简介

群控代码分为3部分：

* 查询模块的MAC地址
* 发射端程序
* 接收端程序

### 3.1 查询模块的MAC地址

MAC地址是一个用来确认网络设备位置的位址，由OSI网络模型第二层（数据链路层）负责。 MAC地址也叫物理地址、硬件地址，由网络设备制造商生产时烧录在网卡的非易失存储器（如EEPROM）中。

MAC地址的长度为48位(6个字节)，通常表示为12个16进制数。 其中前3个字节代表网络硬件制造商的编号，它由IEEE(电气与电子工程师协会)分配， 而后3个字节代表该制造商所制造的某个网络产品(如网卡)的系列号。 只要不更改自己的MAC地址，MAC地址在世界是唯一的。形象地说，MAC地址就如同身份证上的身份证号码，具有唯一性。

ESPNOW最简单的使用方式是通过MAC地址发送。我们使用一个小的程序来查询模块的MAC地址。

```python
import ubinascii
import network

wlan_sta = network.WLAN(network.STA_IF)
wlan_sta.active(True)
wlan_mac = wlan_sta.config('mac')
print(ubinascii.hexlify(wlan_mac).decode())
```

在Thonny中运行后，在终端打印出MAC地址。此时可以用一个不干胶贴写上模块的MAC地址并贴在模块上。

### 3.2 发射端程序

发射端程序由以下几个部分组成：

* 启用模块的WiFi功能
* 配置ESP-NOW协议并启用
* 添加需要通信的节点（peer）
* 发送消息

具体程序代码如下：

```python
import network
import espnow
import time

sta = network.WLAN(network.STA_IF)    # Enable station mode for ESP
sta.active(True)
sta.disconnect()        # Disconnect from last connected WiFi SSID

e = espnow.ESPNow()     # Enable ESP-NOW
e.active(True)

peer1 = b'\xe8\x68\xe7\x4e\xbb\x19'   # MAC address of peer1's wifi interface
e.add_peer(peer1)                     # add peer1 (receiver1)

peer2 = b'\x60\x01\x94\x5a\x9c\xf0'   # MAC address of peer2's wifi interface
e.add_peer(peer2)                     # add peer2 (receiver2)

print("Starting...")            # Send to all peers

e.send(peer1, "walk", True)     # send commands to pear 1
e.send(peer2, "walk", True)     # send commands to pear 2
time.sleep_ms(2000)
e.send(peer1, "walk", True)
e.send(peer2, "back", True)
time.sleep_ms(2000)
```

### 3.3 接收端程序

接收端程序主要由以下几个部分组成

* 启用模块的WiFi功能
* 配置ESP-NOW协议并启用
* 添加需要通信的节点（peer）
* 接收消息并解码，通过串口给NyBoard发送指令

具体程序代码如下：

```python
import network
import espnow
from machine import UART

def espnow_rx():
    #config UART
    uart = UART(0, baudrate=115200)

    # A WLAN interface must be active to send()/recv()
    sta = network.WLAN(network.STA_IF)
    sta.active(True)
    sta.disconnect()                # Disconnect from last connected WiFi SSID

    e = espnow.ESPNow()                  # Enable ESP-NOW
    e.active(True)

    peer = b'\x5c\xcf\x7f\xf0\x06\xda'   # MAC address of peer's wifi interface
    e.add_peer(peer)                     # Sender's MAC registration

    while True:
        host, msg = e.recv()
        if msg:                          # wait for message
            if msg == b'walk':           # decode message and translate
                uart.write("kwkF")       # to the NyBoard's command
            elif msg == b'back':
                uart.write('kbk')
            elif msg == b'stop':
                uart.write('d')

if __name__ == "__main__":
    espnow_rx()
    
```

此代码封装于一个名为`espnow_rx()`的函数中是为了方便上电后自动启动程序。

实现上电自动启动的方法有以下两种：

* 将代码文件改名为`main.py`；
* `修改boot.py`文件；

&#x20;对于新手而言，我们更推荐第一种方法。

### 3.4 通信-指令转换程序

将串口命令转换写在接收端会使程序过于复杂且不易维护。我们可以新建一个函数在其中进行指令转换并输出命令。
