espnow — support for the ESP-NOW wireless protocol — MicroPython latest documentation (original) (raw)

This module provides an interface to the ESP-NOW protocol provided by Espressif on ESP32 and ESP8266 devices (API docs).

Table of Contents:

Introduction

ESP-NOW is a connection-less wireless communication protocol supporting:

It is especially useful for small IoT networks, latency sensitive or power sensitive applications (such as battery operated devices) and for long-range communication between devices (hundreds of metres).

This module also supports tracking the Wifi signal strength (RSSI) of peer devices.

A simple example would be:

Sender:

import network import espnow

A WLAN interface must be active to send()/recv()

sta = network.WLAN(network.WLAN.IF_STA) # Or network.WLAN.IF_AP sta.active(True) sta.disconnect() # For ESP8266

e = espnow.ESPNow() e.active(True) peer = b'\xbb\xbb\xbb\xbb\xbb\xbb' # MAC address of peer's wifi interface e.add_peer(peer) # Must add_peer() before send()

e.send(peer, "Starting...") for i in range(100): e.send(peer, str(i)*20, True) e.send(peer, b'end')

Receiver:

import network import espnow

A WLAN interface must be active to send()/recv()

sta = network.WLAN(network.WLAN.IF_STA) sta.active(True) sta.disconnect() # Because ESP8266 auto-connects to last Access Point

e = espnow.ESPNow() e.active(True)

while True: host, msg = e.recv() if msg: # msg == None if timeout in recv() print(host, msg) if msg == b'end': break

class ESPNow

Constructor

class espnow.ESPNow

Returns the singleton ESPNow object. As this is a singleton, all calls toespnow.ESPNow() return a reference to the same object.

Note

Some methods are available only on the ESP32 due to code size restrictions on the ESP8266 and differences in the Espressif API.

Configuration

ESPNow.active([_flag_])

Initialise or de-initialise the ESP-NOW communication protocol depending on the value of the flag optional argument.

Arguments:

If flag is not provided, return the current status of the ESPNow interface.

Returns:

True if interface is currently active, else False.

ESPNow.config(param=value, ...)

ESPNow.config('param') (ESP32 only)

Set or get configuration values of the ESPNow interface. To set values, use the keyword syntax, and one or more parameters can be set at a time. To get a value the parameter name should be quoted as a string, and just one parameter is queried at a time.

Note: Getting parameters is not supported on the ESP8266.

Options:

rxbuf: (default=526) Get/set the size in bytes of the internal buffer used to store incoming ESPNow packet data. The default size is selected to fit two max-sized ESPNow packets (250 bytes) with associated mac_address (6 bytes), a message byte count (1 byte) and RSSI data plus buffer overhead. Increase this if you expect to receive a lot of large packets or expect bursty incoming traffic.

Note: The recv buffer is allocated by ESPNow.active(). Changing this value will have no effect until the next call ofESPNow.active(True).

timeout_ms: (default=300,000) Default timeout (in milliseconds) for receiving ESPNow messages. If timeout_ms is less than zero, then wait forever. The timeout can also be provided as arg torecv()/irecv()/recvinto().

rate: (ESP32 only) Set the transmission speed for ESPNow packets. Must be set to a number from the allowed numeric values in enum wifi_phy_rate_t. This parameter is actually write-only due to ESP-IDF not providing any means for querying the radio interface’s rate parameter.

Returns:

None or the value of the parameter being queried.

Raises:

Sending and Receiving Data

A wifi interface (network.WLAN.IF_STA or network.WLAN.IF_AP) must beactive() before messages can be sent or received, but it is not necessary to connect or configure the WLAN interface. For example:

import network

sta = network.WLAN(network.WLAN.IF_STA) sta.active(True) sta.disconnect() # For ESP8266

Note: The ESP8266 has a feature that causes it to automatically reconnect to the last wifi Access Point when set active(True) (even after reboot/reset). This reduces the reliability of receiving ESP-NOW messages (see ESPNow and Wifi Operation). You can avoid this by callingdisconnect() afteractive(True).

ESPNow.send(mac, _msg_[, _sync_])

ESPNow.send(msg) (ESP32 only)

Send the data contained in msg to the peer with given network macaddress. In the second form, mac=None and sync=True. The peer must be registered with ESPNow.add_peer() before the message can be sent.

Arguments:

Returns:

True if sync=False or if sync=True and all peers respond, else False.

Raises:

Note: A peer will respond with success if its wifi interface isactive() and set to the same channel as the sender, regardless of whether it has initialised it’s ESP-NOW system or is actively listening for ESP-NOW traffic (see the Espressif ESP-NOW docs).

ESPNow.recv([_timeout_ms_])

Wait for an incoming message and return the mac address of the peer and the message. Note: It is not necessary to register a peer (usingadd_peer()) to receive a message from that peer.

Arguments:

Returns:

Raises:

ESPNow.recv() will allocate new storage for the returned list and thepeer and msg bytestrings. This can lead to memory fragmentation if the data rate is high. See ESPNow.irecv() for a memory-friendly alternative.

ESPNow.irecv([_timeout_ms_])

Works like ESPNow.recv() but will reuse internal bytearrays to store the return values: [mac, msg], so that no new memory is allocated on each call.

Arguments:

timeout_ms: (Optional) Timeout in milliseconds (see ESPNow.recv()).

Returns:

Raises:

Note: You may also read messages by iterating over the ESPNow object, which will use the irecv() method for alloc-free reads, eg:

import espnow e = espnow.ESPNow(); e.active(True) for mac, msg in e: print(mac, msg) if mac is None: # mac, msg will equal (None, None) on timeout break

ESPNow.recvinto(_data_[, _timeout_ms_])

Wait for an incoming message and return the length of the message in bytes. This is the low-level method used by both recv() andirecv() to read messages.

Arguments:

data: A list of at least two elements, [peer, msg]. msg must be a bytearray large enough to hold the message (250 bytes). On the ESP8266, peer should be a bytearray of 6 bytes. The MAC address of the sender and the message will be stored in these bytearrays (see Note on ESP32 below).

timeout_ms: (Optional) Timeout in milliseconds (see ESPNow.recv()).

Returns:

Raises:

Note: On the ESP32:

ESPNow.any()

Check if data is available to be read with ESPNow.recv().

For more sophisticated querying of available characters use select.poll():

import select import espnow

e = espnow.ESPNow() poll = select.poll() poll.register(e, select.POLLIN) poll.poll(timeout)

Returns:

True if data is available to be read, else False.

ESPNow.stats() (ESP32 only)

Returns:

A 5-tuple containing the number of packets sent/received/lost:

(tx_pkts, tx_responses, tx_failures, rx_packets, rx_dropped_packets)

Incoming packets are dropped when the recv buffers are full. To reduce packet loss, increase the rxbuf config parameters and ensure you are reading messages as quickly as possible.

Note: Dropped packets will still be acknowledged to the sender as received.

Peer Management

On ESP32 devices, the Espressif ESP-NOW software requires that other devices (peers) must be registered using add_peer() before we cansend() them messages (this is not enforced on ESP8266 devices). It is not necessary to register a peer to receive an un-encrypted message from that peer.

Encrypted messages: To receive an encrypted message, the receiving device must first register the sender and use the same encryption keys as the sender (PMK and LMK) (see set_pmk() and add_peer().

ESPNow.set_pmk(pmk)

Set the Primary Master Key (PMK) which is used to encrypt the Local Master Keys (LMK) for encrypting messages. If this is not set, a default PMK is used by the underlying Espressif ESP-NOW software stack.

Note: messages will only be encrypted if lmk is also set inESPNow.add_peer() (see Security in the Espressif API docs).

Arguments:

pmk: Must be a byte string, bytearray or string of lengthespnow.KEY_LEN (16 bytes).

Returns:

None

Raises:

ValueError() on invalid pmk values.

ESPNow.add_peer(_mac_[, _lmk_][, _channel_][, _ifidx_][, _encrypt_])

ESPNow.add_peer(mac, param=value, ...) (ESP32 only)

Add/register the provided mac address as a peer. Additional parameters may also be specified as positional or keyword arguments (any parameter set toNone will be set to it’s default value):

Arguments:

ESP8266: Keyword args may not be used on the ESP8266.

Note: The maximum number of peers which may be registered is 20 (espnow.MAX_TOTAL_PEER_NUM), with a maximum of 6 (espnow.MAX_ENCRYPT_PEER_NUM) of those peers with encryption enabled (see ESP_NOW_MAX_ENCRYPT_PEER_NUM in the Espressif API docs).

Raises:

ESPNow.del_peer(mac)

Deregister the peer associated with the provided mac address.

Returns:

None

Raises:

ESPNow.get_peer(mac) (ESP32 only)

Return information on a registered peer.

Returns:

(mac, lmk, channel, ifidx, encrypt): a tuple of the “peer info” associated with the given mac address.

Raises:

ESPNow.peer_count() (ESP32 only)

Return the number of registered peers:

ESPNow.get_peers() (ESP32 only)

Return the “peer info” parameters for all the registered peers (as a tuple of tuples).

ESPNow.mod_peer(mac, lmk, [channel], [ifidx], [encrypt]) (ESP32 only)

ESPNow.mod_peer(mac, 'param'=value, ...) (ESP32 only)

Modify the parameters of the peer associated with the provided _mac_address. Parameters may be provided as positional or keyword arguments (see ESPNow.add_peer()). Any parameter that is not set (or set toNone) will retain the existing value for that parameter.

Callback Methods

ESPNow.irq(callback) (ESP32 only)

Set a callback function to be called as soon as possible after a message has been received from another ESPNow device. The callback function will be called with the ESPNow instance object as an argument. For more reliable operation, it is recommended to read out as many messages as are available when the callback is invoked and to set the read timeout to zero, eg:

def recv_cb(e): while True: # Read out all messages waiting in the buffer mac, msg = e.irecv(0) # Don't wait if no messages left if mac is None: return print(mac, msg) e.irq(recv_cb)

The irq() callback method is an alternative method for processing incoming messages, especially if the data rate is moderate and the device is not too busy but there are some caveats:

Constants

espnow.MAX_DATA_LEN(=250)

espnow.KEY_LEN(=16)

espnow.ADDR_LEN(=6)

espnow.MAX_TOTAL_PEER_NUM(=20)

espnow.MAX_ENCRYPT_PEER_NUM(=6)

Exceptions

If the underlying Espressif ESP-NOW software stack returns an error code, the MicroPython espnow module will raise an OSError(errnum, errstring)exception where errstring is set to the name of one of the error codes identified in theEspressif ESP-NOW docs. For example:

try: e.send(peer, 'Hello') except OSError as err: if len(err.args) < 2: raise err if err.args[1] == 'ESP_ERR_ESPNOW_NOT_INIT': e.active(True) elif err.args[1] == 'ESP_ERR_ESPNOW_NOT_FOUND': e.add_peer(peer) elif err.args[1] == 'ESP_ERR_ESPNOW_IF': network.WLAN(network.WLAN.IF_STA).active(True) else: raise err

Supporting asyncio

A supplementary module (aioespnow) is available to provideasyncio support.

Note: Asyncio support is available on all ESP32 targets as well as those ESP8266 boards which include the asyncio module (ie. ESP8266 devices with at least 2MB flash memory).

A small async server example:

import network import aioespnow import asyncio

A WLAN interface must be active to send()/recv()

network.WLAN(network.WLAN.IF_STA).active(True)

e = aioespnow.AIOESPNow() # Returns AIOESPNow enhanced with async support e.active(True) peer = b'\xbb\xbb\xbb\xbb\xbb\xbb' e.add_peer(peer)

Send a periodic ping to a peer

async def heartbeat(e, peer, period=30): while True: if not await e.asend(peer, b'ping'): print("Heartbeat: peer not responding:", peer) else: print("Heartbeat: ping", peer) await asyncio.sleep(period)

Echo any received messages back to the sender

async def echo_server(e): async for mac, msg in e: print("Echo:", msg) try: await e.asend(mac, msg) except OSError as err: if len(err.args) > 1 and err.args[1] == 'ESP_ERR_ESPNOW_NOT_FOUND': e.add_peer(mac) await e.asend(mac, msg)

async def main(e, peer, timeout, period): asyncio.create_task(heartbeat(e, peer, period)) asyncio.create_task(echo_server(e)) await asyncio.sleep(timeout)

asyncio.run(main(e, peer, 120, 10))

class aioespnow.AIOESPNow

The AIOESPNow class inherits all the methods of ESPNowand extends the interface with the following async methods.

async AIOESPNow.arecv()

Asyncio support for ESPNow.recv(). Note that this method does not take a timeout value as argument.

async AIOESPNow.airecv()

Asyncio support for ESPNow.irecv(). Note that this method does not take a timeout value as argument.

async AIOESPNow.asend(mac, msg, sync=True)

async AIOESPNow.asend(msg)

Asyncio support for ESPNow.send().

AIOESPNow._aiter__() / async AIOESPNow.__anext__()

AIOESPNow also supports reading incoming messages by asynchronous iteration using async for; eg:

e = AIOESPNow() e.active(True) async def recv_till_halt(e): async for mac, msg in e: print(mac, msg) if msg == b'halt': break asyncio.run(recv_till_halt(e))

Broadcast and Multicast

All active ESPNow clients will receive messages sent to their MAC address and all devices (except ESP8266 devices) will also receive messages sent to the_broadcast_ MAC address (b'\xff\xff\xff\xff\xff\xff') or any multicast MAC address.

All ESPNow devices (including ESP8266 devices) can also send messages to the broadcast MAC address or any multicast MAC address.

To send() a broadcast message, the broadcast (or multicast) MAC address must first be registered usingadd_peer(). send() will always returnTrue for broadcasts, regardless of whether any devices receive the message. It is not permitted to encrypt messages sent to the broadcast address or any multicast address.

Note: ESPNow.send(None, msg) will send to all registered peers except the broadcast address. To send a broadcast or multicast message, you must specify the broadcast (or multicast) MAC address as the peer. For example:

bcast = b'\xff' * 6 e.add_peer(bcast) e.send(bcast, "Hello World!")

ESPNow and Wifi Operation

ESPNow messages may be sent and received on any active() WLAN interface (network.WLAN.IF_STA or network.WLAN.IF_AP), even if that interface is also connected to a wifi network or configured as an access point. When an ESP32 or ESP8266 device connects to a Wifi Access Point (seeESP32 Quickref) the following things happen which affect ESPNow communications:

  1. Wifi Power-saving Mode (network.WLAN.PM_PERFORMANCE) is automatically activated and
  2. The radio on the esp device changes wifi channel to match the channel used by the Access Point.

Wifi Power-saving Mode: (see Espressif Docs) The power saving mode causes the device to turn off the radio periodically (typically for hundreds of milliseconds), making it unreliable in receiving ESPNow messages. This can be resolved by either of:

  1. Disabling the power-saving mode on the STA_IF interface;
    • Use sta.config(pm=sta.PM_NONE)
  2. Turning on the AP_IF interface, which will disable the power saving mode. However, the device will then be advertising an active wifi access point.
    • You may also choose to send your messages via the AP_IF interface, but this is not necessary.
    • ESP8266 peers must send messages to this AP_IF interface (see below).
  3. Configuring ESPNow clients to retry sending messages.

Receiving messages from an ESP8266 device: Strangely, an ESP32 device connected to a wifi network using method 1 or 2 above, will receive ESPNow messages sent to the STA_IF MAC address from another ESP32 device, but willreject messages from an ESP8266 device!!!. To receive messages from an ESP8266 device, the AP_IF interface must be set to active(True) andmessages must be sent to the AP_IF MAC address.

Managing wifi channels: Any other ESPNow devices wishing to communicate with a device which is also connected to a Wifi Access Point MUST use the same channel. A common scenario is where one ESPNow device is connected to a wifi router and acts as a proxy for messages from a group of sensors connected via ESPNow:

Proxy:

import network, time, espnow

sta, ap = wifi_reset() # Reset wifi to AP off, STA on and disconnected sta.connect('myssid', 'mypassword') while not sta.isconnected(): # Wait until connected... time.sleep(0.1) sta.config(pm=sta.PM_NONE) # ..then disable power saving

Print the wifi channel used AFTER finished connecting to access point

print("Proxy running on channel:", sta.config("channel")) e = espnow.ESPNow(); e.active(True) for peer, msg in e: # Receive espnow messages and forward them to MQTT broker over wifi

Sensor:

import network, espnow

sta, ap = wifi_reset() # Reset wifi to AP off, STA on and disconnected sta.config(channel=6) # Change to the channel used by the proxy above. peer = b'0\xaa\xaa\xaa\xaa\xaa' # MAC address of proxy e = espnow.ESPNow(); e.active(True); e.add_peer(peer) while True: msg = read_sensor() e.send(peer, msg) time.sleep(1)

Other issues to take care with when using ESPNow with wifi are:

ESPNow and Sleep Modes

The machine.lightsleep([time_ms]) andmachine.deepsleep([time_ms]) functions can be used to put the ESP32 and peripherals (including the WiFi and Bluetooth radios) to sleep. This is useful in many applications to conserve battery power. However, applications must disable the WLAN peripheral (usingactive(False)) before entering light or deep sleep (seeSleep Modes). Otherwise the WiFi radio may not be initialised properly after wake from sleep. If the STA_IF and AP_IF interfaces have both been setactive(True) then both interfaces should be setactive(False) before entering any sleep mode.

Example: deep sleep:

import network, machine, espnow

sta, ap = wifi_reset() # Reset wifi to AP off, STA on and disconnected peer = b'0\xaa\xaa\xaa\xaa\xaa' # MAC address of peer e = espnow.ESPNow() e.active(True) e.add_peer(peer) # Register peer on STA_IF

print('Sending ping...') if not e.send(peer, b'ping'): print('Ping failed!') e.active(False) sta.active(False) # Disable the wifi before sleep print('Going to sleep...') machine.deepsleep(10000) # Sleep for 10 seconds then reboot

Example: light sleep:

import network, machine, espnow

sta, ap = wifi_reset() # Reset wifi to AP off, STA on and disconnected sta.config(channel=6) peer = b'0\xaa\xaa\xaa\xaa\xaa' # MAC address of peer e = espnow.ESPNow() e.active(True) e.add_peer(peer) # Register peer on STA_IF

while True: print('Sending ping...') if not e.send(peer, b'ping'): print('Ping failed!') sta.active(False) # Disable the wifi before sleep print('Going to sleep...') machine.lightsleep(10000) # Sleep for 10 seconds sta.active(True) sta.config(channel=6) # Wifi loses config after lightsleep()