adafruit / circuitpython

CircuitPython - a Python implementation for teaching coding with microcontrollers
https://circuitpython.org
Other
4.12k stars 1.22k forks source link

espnow cannot send to a broadcast address #9380

Open gitcnd opened 5 months ago

gitcnd commented 5 months ago

CircuitPython version

Adafruit CircuitPython 9.1.0-beta.1-18-g781c577745 on 2024-05-11; sunton_esp32_2432S028 with ESP32

Code/REPL

import wifi
import espnow
import time

# Disable WiFi
wifi.radio.enabled = False

# Initialize ESPNOW
esp = espnow.ESPNow()
esp.phy_rate = 0  # Default physical layer rate

if 0: # enabling this line makes the problem go away
    peer = espnow.Peer(b'\x24\xdc\xc3\x8b\xe1\xc8')
    esp.peers.append(peer)

# Add a broadcast peer 
peer = espnow.Peer(b'\xff\xff\xff\xff\xff\xff')
esp.peers.append(peer)

print("Broadcasting message...")

while True:
    try:
        esp.send(b'hello world')
        print("Sent.")
    except Exception as e:
        print(f"esp.read() error: {e}")
    time.sleep(5)  # Send the message every 5 seconds

# exec(open("espnowsender.py").read())

Behavior

Adafruit CircuitPython 9.1.0-beta.1-18-g781c577745 on 2024-05-11; sunton_esp32_2432S028 with ESP32
>>> 
>>> exec(open("estnowtst1.py").read())
Broadcasting message...
esp.read() error: ESP-NOW error 0x3069
esp.read() error: ESP-NOW error 0x3069
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<string>", line 28, in <module>
KeyboardInterrupt: 
>>> 

Description

Change the "if 0:" to "if 1:" and this is what we see:-

>>> exec(open("estnowtst2.py").read())
Broadcasting message...
Sent.
Sent.
Sent.
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<string>", line 28, in <module>
KeyboardInterrupt: 
>>> 

The logically identical code in micropython works fine

MicroPython version:

import network
import espnow

sta = network.WLAN(network.STA_IF)
sta.active(True)
sta.disconnect()
e = espnow.ESPNow()
e.active(True)
peer = b'\xff\xff\xff\xff\xff\xff' # broadcast mac address to all esp32's (not esp8266)
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) # Supposedly capable of 89250bytes/sec
e.send(peer, b'end')

Additional information

It also does not allow channel numbers - the below errors out:- peer = espnow.Peer(b'\x24\xdc\xc3\x8b\xe1\xc8', channel=9) as mentioned in https://github.com/adafruit/circuitpython/issues/7903

The "receiver" side of the code does seem to semi-work (gets packets from my micro-python version) but experiences a very high number of read error exceptions

espnowclient.py

import wifi
import espnow
import binascii

# Disable WiFi
#wifi.radio.enabled = False

# Initialize ESPNOW
esp = espnow.ESPNow()
esp.phy_rate = 0  # Default physical layer rate

# Add a broadcast peer
#peer = espnow.Peer(b'\xff\xff\xff\xff\xff\xff', channel=9) # bug - crashes it
peer = espnow.Peer(b'\xff\xff\xff\xff\xff\xff') 
esp.peers.append(peer)

def packet_to_hex_and_ascii(packet):
    hex_data = binascii.hexlify(packet.msg)
    ascii_data = ''.join(chr(b) if 32 <= b <= 126 else '.' for b in packet.msg)
    return hex_data, ascii_data

#print("\033[2J\033[HListening for ESPNOW packets on channel 9...")
print("\033[2J\033[HListening for ESPNOW packets on wifi channel...")

while True:
    try:
        packet = esp.read()
    except Exception as e:
        print(f"esp.read() error: {e}")
    if packet:
        hex_data, ascii_data = packet_to_hex_and_ascii(packet)
        print("Received packet:")
        print("Hex: ", hex_data)
        print("ASCII: ", ascii_data)
        print("sender mac: ",  binascii.hexlify(packet.mac)  )
        print("rssi: ", packet.rssi )
        print("time: ", packet.time )

# exec(open("espnowclient.py").read())
gitcnd commented 5 months ago

Update; while adding b'\xff\xff\xff\xff\xff\xff' as a peer seems to stop the error - the corresponding receiver listening to broadcast traffic does not receive anything.

gitcnd commented 4 months ago

I'm planning to take a look at this and https://github.com/adafruit/circuitpython/issues/7903. For reference: ( https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/error-codes.html )

ESP_ERR_ESPNOW_INTERNAL (0x306a): Internal error

ESP_ERR_ESPNOW_NOT_FOUND (0x3069): ESPNOW peer is not found

leave a note here if you've done (or are doing) any work on it, so no effort gets duplicated.

gitcnd commented 4 months ago

I was unable to get circuitpython to reliably (or in most cases - at all) communicate with micropython over espnow (packet corruption and loss mostly when it semi-worked, or nothing at all most of the time probably due to code issue in the peer address setup/handling). I was unable to work out where/how in the code to fix this, or where the issue might be (I used "printf()" in the C to emit debug info - couldn't work it out still). For now - I'm giving up on this.

stanelie commented 2 weeks ago

Adding b'\xff\xff\xff\xff\xff\xff' as a peer does not work for me : peer = espnow.Peer(b'\xff\xff\xff\xff\xff\xff') I get error message espidf.IDFError: ESP-NOW error 0x3069 ((0x3069): ESPNOW peer is not found)

Without the possibility of sending gratuitous broadcast packets and receiving them, it is not possible to pair ESPNow devices.

Is there any update on this?

Sola85 commented 2 weeks ago

I am using circuitpython + ESPNow for a custom sensor and this has been working reliably for the past few months - also using the broadcast adress.

My receiver is additionally using wifi, so I had to fix the Wifi channel in my router settings (in this case to channel 6). One then has to force the transmitter to also use this channel. As far as I know, this can only be done using the wifi api and not using the ESPNow api.

Here are snippets of my code: Receiver:

async def handle_esp_now():
    e = espnow.ESPNow()
    while True:
        await asyncio.sleep(0.1)
        if not e: continue

        packet = e.read()
        last_received_data = json.loads(packet.msg.decode())
        if "topic" in last_received_data:
            mqtt_client.publish(last_received_data["topic"], json.dumps(last_received_data))

Transmitter:

# hack to switch channel that is used for ESPNow
# this takes just a few milliseconds, so doesn't waste a lot of power
wifi.radio.start_ap(" ", "", channel=6, max_connections=0)
wifi.radio.stop_ap()

e = espnow.ESPNow()
peer = espnow.Peer(mac=b'\xff\xff\xff\xff\xff\xff', channel=6)
e.peers.append(peer)

...

data = {
    "voltage": voltage,
    "topic": "state/circuitpy-sensor3"
}
message = json.dumps(data)
e.send(message, peer)

If I remember correctly, the seeming redundancy (specifying the channel twice, i.e. once in the wifi settings as well as in the Peer) and the redundancy of specifying the peer both in e.peers as well as in e.send was both necessary. I had briefly looked into CircuitPython's source but came to the conclusion that its not CircuitPython that is causing the friction here, but that we are instead seeing quirks of the underlying esp-idf.

stanelie commented 2 weeks ago

Well, this is precious information! I wish this were documented in the circuitpython documentation. Thanks @Sola85 !

gitcnd commented 2 weeks ago

Switch to MicroPython - it properly supports channel selection and broadcast addresses.

stanelie commented 2 weeks ago

Sadly, MicroPython has other issues that make it even less desirable for me. For example, the default hardware transmission mode of Circuitpython gives me twice the range of MicroPython's default mode (neither can be switched, it's another bug see https://github.com/adafruit/circuitpython/issues/9790 and https://github.com/micropython/micropython/issues/16179).