-
-
Save anecdata/f46a1d07add5fc60cfbcf42dc7be6528 to your computer and use it in GitHub Desktop.
# SPDX-FileCopyrightText: 2023 anecdata | |
# | |
# SPDX-License-Identifier: MIT | |
import time | |
import traceback | |
import supervisor | |
import os | |
import rtc | |
import espnow | |
import espidf | |
import wifi | |
import socketpool | |
import adafruit_ntp | |
from sekrets import * | |
#### ESPNOW Receiver | |
TZ_DEFAULT = -5 | |
SNDR_CH = 0 # channel 1 (unless connected to an AP or acting as an AP) | |
def struct_time_to_iso_time(): | |
st = time.localtime() | |
tz = TZ_DEFAULT | |
return f"{st[0]:04d}-{st[1]:02d}-{st[2]:02d}T{st[3]:02d}:{st[4]:02d}:{st[5]:02d}{tz:+03}:00" | |
def connect(): | |
try: | |
wifi.radio.connect(os.getenv("WIFI_SSID"), os.getenv("WIFI_PASSWORD"), bssid=AP_BSSID) # use AP on channel <> 1 | |
time.sleep(1) # wait for ap_info | |
print(f"{struct_time_to_iso_time()} ipv4={wifi.radio.ipv4_address} channel={wifi.radio.ap_info.channel} rssi={wifi.radio.ap_info.rssi}") | |
except ConnectionError as ex: | |
traceback.print_exception(ex, ex, ex.__traceback__) | |
def ntp_to_rtc(): | |
wifi.radio.enabled = True | |
connect() | |
try: | |
ntp = adafruit_ntp.NTP(pool, tz_offset=TZ_DEFAULT) | |
rtc.RTC().datetime = ntp.datetime | |
print(f"{struct_time_to_iso_time()} RTC time set with NTP time") | |
except Exception as e: | |
traceback.print_exception(e, e, e.__traceback__) | |
wifi.radio.enabled = False # lose the wifi channel | |
time.sleep(3) # wait for serial | |
print(f"{'='*25}") | |
pool = socketpool.SocketPool(wifi.radio) | |
ntp_to_rtc() | |
peers = [espnow.Peer(mac=SNDR_MAC, lmk=SNDR_LMK, encrypted=True, channel=SNDR_CH),] | |
while True: | |
with espnow.ESPNow() as e: | |
e.set_pmk(RCVR_PMK) | |
peers_report = "" | |
for peer in peers: | |
e.peers.append(peer) | |
peers_report += f"mac={peer.mac} lmk={peer.lmk} ch={peer.channel} if={peer.interface} enc={peer.encrypted}\n" | |
print(f"{'-'*25}\n{struct_time_to_iso_time()} Receiving...", end=" ") | |
while True: | |
if e: | |
try: | |
packet = e.read() | |
print(f"{packet}") | |
break | |
except ValueError as ex: # Invalid buffer | |
traceback.print_exception(ex, ex, ex.__traceback__) | |
supervisor.reload() | |
break | |
print(f"send=[{e.send_success} {e.send_failure}] read=[{e.read_success} {e.read_failure}] buf={e.buffer_size} phy={e.phy_rate} peers={peers_report}", end="") |
Above code tested on:
Adafruit CircuitPython 8.1.0-beta.0-80-g22636e056 on 2023-03-29; Adafruit Feather ESP32-S2 TFT with ESP32S2
General comments on use of espnow and wifi:
- Best results when wifi is disabled (
wifi.radio.enabled = False
) when not in use (requiring re-connection), and ESP-NOW isdeinit
ed when not in use (but see below). ESP-NOW docs suggest that wifi and ESP-NOW sending should work simultaneously on the same channel. ESP-NOW receiving may suffer from wifi power save modes (see https://micropython-glenn20.readthedocs.io/en/latest/library/espnow.html#espnow-and-wifi-operation thanks, @hopkapi). Sender and receiver must use the samephy_rate
, PMK, LMK, channel, and encryption.
Open questions:
e = espnow.ESPNow()
results inRuntimeError: Already running
if it was already done, but it's a Singleton?- is this the proper way to use the PMK and LMK (PMK is the same for all nodes; LMK can be unique per Sender-Receiver pair)?
- trying to send on a channel other than the default
0
(becomes1
) results inESP-NOW error 0x306a
- when encryption is used, RSSI is always returned as
0
ValueError: phy_rate must be 0-42
but should allow up to 54Mbps? 36 is allowed, but 24 is the highest that seems to get received.- using broadcast peer address results in
ESP-NOW error 0x3066
on send ESP-NOW error 0x306b
if peer is added more than once?
It can typically take up to several milliseconds for e.send_success
and e.send_failure
to become valid. One way to handle that:
try:
e.send(f"{struct_time_to_iso_time().encode()}")
delivery_time = 0
start = time.monotonic_ns()
while not e.send_success and not e.send_failure:
if (delivery_time := time.monotonic_ns() - start) > SEND_TIMEOUT:
break
Needs some parameter validation?
Adafruit CircuitPython 8.1.0-beta.0-80-g22636e056 on 2023-03-29; Adafruit Feather ESP32-S2 TFT with ESP32S2
>>> import time
>>> import espnow
>>> from sekrets import *
>>>
>>> RCVR_CH = 0
>>>
>>> e = espnow.ESPNow()
>>> e.set_pmk(SYS_PMK)
>>> peer = espnow.Peer(mac=RCVR_MAC, lmk=LMK_181, encrypted=True, channel=RCVR_CH)
>>> e.peers.append(peer)
>>>
>>> e.send(f"{'#'*250}")
>>> time.sleep(0.1) # delivery confirmation takes non-zero time
>>> print(f"send=[{e.send_success} {e.send_failure}] read=[{e.read_success} {e.read_failure}]")
send=[1 0] read=[1 0]
>>>
>>> e.send(f"{'#'*251}")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
espidf.IDFError: ESP-NOW error 0x306a
>>> time.sleep(0.1) # delivery confirmation takes non-zero time
>>> print(f"send=[{e.send_success} {e.send_failure}] read=[{e.read_success} {e.read_failure}]")
send=[1 0] read=[1 0]
>>>
>>> e.send(f"{'#'*250}")
>>> time.sleep(0.1) # delivery confirmation takes non-zero time
>>> print(f"send=[{e.send_success} {e.send_failure}] read=[{e.read_success} {e.read_failure}]")
send=[2 0] read=[1 0]
>>>
For ease of navigation...
Original Issue: adafruit/circuitpython#3999
Original PR: adafruit/circuitpython#7470
Issue filed for above comments: adafruit/circuitpython#7903
Thanks for compiling/sharing this @anecdata 🥇 I hadn't seen the micropython notes on esp-now.
- Best results when wifi is disabled (
wifi.radio.enabled = False
) when not in use (requiring re-connection), and ESP-NOW isdeinit
ed when not in use (but see below). ESP-NOW docs suggest that wifi and ESP-NOW sending should work simultaneously on the same channel. ESP-NOW receiving may suffer from wifi power save modes (see https://micropython-glenn20.readthedocs.io/en/latest/library/espnow.html#espnow-and-wifi-operation thanks, @hopkapi). Sender and receiver must use the samephy_rate
, PMK, LMK, channel, and encryption.
Updated link from Archive.org for micropython-espnow power related operation notes:
https://web.archive.org/web/20230520021624/https://micropython-glenn20.readthedocs.io/en/latest/library/espnow.html#espnow-and-wifi-operation
Thanks for the updated link @tyeth. CircuitPython does power management slightly differently adafruit/circuitpython#6976 which I think reduces complexity for ESP-Now. Channel I think is still a challenge to manage for now.
The sender example does not run on circuitpython 9.1.4 on a Waveshare ESP32-S3-Zero. I get several error messages (example : "ImportError: no module named 'sekrets'"). Is this expected behavior? Should I try this on circuitpython 8 instead?
@stanelie That was a file I used but didn't publish for the encryption keys, etc. (see the all-caps constants in the code, like RCVR_MAC
and RCVR_LMK
). You can put them anywhere you like and import them how you like.
Do you have examples of how this data should be formatted? For example, the mac address should be AB:CD:EF:GH:IJ or something like ['0x24', '0xec', '0x4a', '0x26', '0x8d', '0xb0'] ?
oh yeah, good question, PMK and LMK are bytes (or, ReadableBuffer), e.g., b'shufyrgetsfdtfyg'
https://docs.circuitpython.org/en/latest/shared-bindings/espnow/index.html#espnow.Peer
https://docs.circuitpython.org/en/latest/shared-bindings/espnow/index.html#espnow.ESPNow.set_pmk
Similarly, MAC address like b'\x00\x1A\xDD\x17\xC2\xA5'
(or any equivalent form)
https://docs.circuitpython.org/en/latest/shared-bindings/espnow/index.html#espnow.ESPNowPacket.mac
Thanks!
How about setting the data rate for long range?
The doc says "espnow.ESPNow(buffer_size: = 526, phy_rate: int = 0), but I don't see what 0 means (1mbps? 56mbps?)
The values in the reference https://docs.espressif.com/projects/esp-idf/en/release-v4.4/esp32/api-reference/network/esp_wifi.html#_CPPv415wifi_phy_rate_t are not integer values, as expected by the Circuitpython library.
@stanelie it's an enumeration, so increasing integer values
https://github.com/espressif/esp-idf/blob/a9d0f22193acdf47a5a4db36832ae7068818962b/components/esp_wifi/include/esp_wifi_types.h#L594
the default is 0 (1Mbps), but you can change it
Thanks. Is there a better place for my questions than this thread?
I've tried all the phy_rate values between 0 and 32, and 16 and up do not work. So, I am unable to select the 31 or 32nd values (lora 250 and lora 500) that I want to enable long range. Where should I file a bug report about this issue?
espnow_sender.py
Alternate between:
monitor
(random channels)wifi
withrequests
(not channel 1 here, but can be any channel)