Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ESP-NOW error 0x306a if channel is not 1 #7903

Open
anecdata opened this issue Apr 24, 2023 · 4 comments
Open

ESP-NOW error 0x306a if channel is not 1 #7903

anecdata opened this issue Apr 24, 2023 · 4 comments
Assignees
Labels
bug espressif applies to multiple Espressif chips network
Milestone

Comments

@anecdata
Copy link
Member

anecdata commented Apr 24, 2023

CircuitPython version

Adafruit CircuitPython 8.1.0-beta.0-80-g22636e056 on 2023-03-29; Adafruit Feather ESP32-S2 TFT with ESP32S2

Code/REPL

Code and initial testing commentary here (also some on Discord):
https://gist.github.com/anecdata/f46a1d07add5fc60cfbcf42dc7be6528

Behavior

The most limiting issue right now I think is that if peer channel is set to anything except 0 (default; becomes channel 1) or 1, send will result in espidf.IDFError: ESP-NOW error 0x306a. ESP-NOW should operate on any channel. This also prevents running wifi and ESP-NOW simultaneously unless the AP is on channel 1.

A couple of other initial questions or issues after testing ESP-NOW... we can put them into separate issues if warranted:

  • 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 to be set, but 24 is the highest that seems to work between sender and receiver.

  • using broadcast peer address results in ESP-NOW error 0x3066 on send, not sure if this is intended to be supported right now

  • ESP-NOW error 0x306b if a peer is added more than once

  • packets are limited to 250 bytes, trying to send 251 results in espidf.IDFError: ESP-NOW error 0x306a but perhaps there's a friendlier input validation error that can be raised?

  • e = espnow.ESPNow() results in RuntimeError: Already running if done more than once, but RTD says it's a Singleton:

Allocate and initialize ESPNow instance as a singleton.

...also unexpected:

>>> import espnow
>>> 
>>> with espnow.ESPNow() as e:
...     pass
>>> e
<ESPNow>
>>> 
>>> e.deinit()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: Object has been deinitialized and can no longer be used. Create a new object.

Addendum: Two additional nice-to-have features:

  • Give user code a way to get the number of messages in the buffer. On PSRAM boards, large buffers can be allocated to accommodate high ESP-NOW traffic. Would be nice for user code to see how well it's keeping up. And how many messages have been lost. Monitor might be a good example for these.

  • allow configuring more than the default 7 encrypted pairs (can be up to 17, but trades off with SoftAP). Update: An issue has been filed with Espressif to allow runtime configuration of max encrypted pairs.

Addendum 2: Another issue:

  • ESP-Now receivers and senders will spontaneously reset with microcontroller.ResetReason.WATCHDOG numerous times per day.

Description

No response

Additional information

No response

@anecdata anecdata added the bug label Apr 24, 2023
@tannewt tannewt added network espressif applies to multiple Espressif chips labels Apr 24, 2023
@tannewt tannewt added this to the Long term milestone Apr 24, 2023
@aziascreations
Copy link

aziascreations commented Jun 4, 2023

Hello, I was recently playing around with the espnow module and encountered most of these issues.

However, I found some workarounds and made some examples that are might be worth sharing for anyone who will need to work on these issues or want to use the module thoroughly.

Broadcast messages

While the send method may return a 0x3066 or 0x3069 error when broadcasting, it is only does so under some specific conditions:

  • If you only have the broadcast peer registered, you must to pass it as the peer parameter in send for the message to be sent and received without any error being raised.

  • If you have one broadcast peer and one or more valid non-broadcast peers registered, one of the non-broadcast peers must acknowledge the message. Otherwise an ESP-IDF error will be raised.

Here is an example for these conditions:

Demo sender's code
import espidf
import espnow
import time
import wifi

MAC_SOURCE = b'\xf4\x12\xfa\xcb\xe8\x94'  # Somewhat irrelevant
MAC_BROADCAST = b'\xFF\xFF\xFF\xFF\xFF\xFF'
MAC_TARGET = b'\xdcTueX\xb8'  # You need to have a listener with this MAC  (Can be changed)
MAC_NOBODY = b'\x01\x02\x03\x04\x05\x06'

# Preparing the Wi-Fi radio
wifi.radio.enabled = True
wifi.radio.mac_address = MAC_SOURCE

# Preparing ESP-NOW
e = espnow.ESPNow()
peer_broadcast = espnow.Peer(mac=MAC_BROADCAST)
e.peers.append(peer_broadcast)


# Sending messages with only 1 registered peer
print("Sending message without specific target...")
try:
    e.send("Hello world :)")
    print("> Success")
except espidf.IDFError:  # Should be "ESP-NOW error 0x3069"
    print("> Failure")

print("Sending message with specific target...")
try:
    e.send("Hello world :)", peer_broadcast)
    print("> Success")
except espidf.IDFError:  # Should not trigger
    print("> Failure")


# Adding peer whose MAC isn't used by anyone.
peer_nobody = espnow.Peer(mac=MAC_NOBODY)
e.peers.append(peer_nobody)

# Sending messages with 2 registered peers
print("Sending message without specific target...")
try:
    e.send("Hello world :)")
    print("> Success")
except espidf.IDFError:  # Should be triggerred
    print("> Failure")


# Adding peer whose MAC is used and should receive messages.
peer_valid = espnow.Peer(mac=MAC_TARGET)
e.peers.append(peer_valid)

# Sending messages with 3 registered peers
print("Sending message without specific target...")
try:
    e.send("Hello world :)")
    print("> Success")
except espidf.IDFError:  # Should not trigger
    print("> Failure")


# Finishing
print("Done !")
e.deinit()

For the receiver, use the one below.

Channel switching

It appears that you can change the interface's channel by quickly creating an AP with the desired channel number and stopping it directly after.

The only issue with this approach is that you have to wait for any messages sent to be acknowledged or marked as a failed delivery in the internal callback.
If you don't, the peer's target channel appear to be ignored and all messages will be sent on the initial channel.

See espnow.ESPNow.send_success and espnow.ESPNow.send_failure.

Timing issue examples

All these examples have two devices:

  1. A listener on channel 1. (None on channel 9)
  2. A sender switching between channel 1 and 9.

No waiting

  1. Switch to channel 1
  2. Add peer, send message, arrives on channel 1, unregister peer
  3. Switch to channel 9
  4. Add peer and send message
  5. Message arrives on channel 1
  6. All future message will arrive on channel 1, even if alternating between 1 and 9

Waiting for 100ms

  1. Switch to channel 1
  2. Add peer, send message, arrives on channel 1, unregister peer
  3. Switch to channel 9
  4. Add peer, send message, arrives on channel 9, unregister peer
  5. Rince and repeat
Sender's code
import espnow
import time
import wifi

MAC_SOURCE = b'\xf4\x12\xfa\xcb\xe8\x94'
MAC_TARGET = b'\xdcTueX\xb8'

# Preparing the Wi-Fi radio
wifi.radio.enabled = True
wifi.radio.mac_address = MAC_SOURCE

# Preparing ESP-NOW
e = espnow.ESPNow()

# Iterating over the desired channels
message_count = 0

for channel in [1, 9, 1, 9, 1, 9, 1]:
    print("Sending a message on channel " + str(channel))
    
    # Moving to the correct channel.
    wifi.radio.start_ap(ssid="Moving channels...", channel=channel)
    wifi.radio.stop_ap()
    
    # Re-creating the peer to share the target MAC.
    peer = espnow.Peer(mac=MAC_TARGET, channel=channel, interface=0)
    e.peers.append(peer)
    
    message = "Hello channel {}, this is message {} !".format(channel, message_count).encode("utf-8")
    
    # Sending data directly  (Not working)
    #e.send(message, peer)
    
    # Sending data in a loop  (Works just fine)
    for i in range(5):
        e.send(message, peer)
        time.sleep(0.1)
    
    # Freeing the peer and ESP-NOW
    e.peers.remove(peer)
    
    message_count = message_count + 1

# Finishing
print("Done !")
e.deinit()
Receiver's code

The receiver's code is pretty much lifted as-is from the documentation and only has a bit to change the channel
before initializing ESP-NOW and the main loop.

import espnow
import wifi

TARGET_CHANNEL = 9

wifi.radio.enabled = True
wifi.radio.start_ap(ssid="NO_SSID", channel=TARGET_CHANNEL)
wifi.radio.stop_ap()

e = espnow.ESPNow()
packets = []

print("Waiting for packets...")

while True:
    if e:
        packet = e.read()
        print(packet)
        if packet.msg == b'end':
            break

@casainho
Copy link

casainho commented Jul 11, 2023

I was also getting issues when using ESPNow to communicate between a ESP32-S2 and a S3. In all this, I found that the initialization need to be a bit different between them.

For ESP32-S2, the initialization need to be this (see that I am communicating with 2 different ESP32 boards, one with ESP32-S3 and other with ESP32-S2):

# MAC Address value needed for the wireless communication
my_mac_address = [0x68, 0xb6, 0xb3, 0x01, 0xf7, 0xf3]
mac_address_power_switch_board = [0x68, 0xb6, 0xb3, 0x01, 0xf7, 0xf1]
mac_address_motor_board = [0x68, 0xb6, 0xb3, 0x01, 0xf7, 0xf2]

wifi.radio.enabled = True
wifi.radio.mac_address = bytearray(my_mac_address)
wifi.radio.start_ap(ssid="NO_SSID", channel=1)
wifi.radio.stop_ap()

_espnow = ESPNow.ESPNow()
motor = motor_board_espnow.MotorBoard(_espnow, mac_address_motor_board, system_data) # System data object to hold the EBike data
power_switch = power_switch_espnow.PowerSwitch(_espnow, mac_address_power_switch_board, system_data) # System data object to hold the EBike data
The code to send and receive
class MotorBoard(object):
    def __init__(self, _espnow, mac_address, system_data):
        self._motor_board_espnow = _espnow
        peer = ESPNow.Peer(mac=bytes(mac_address), channel=1)
        self._motor_board_espnow.peers.append(peer)

        self._packets = []
        self._system_data = system_data
        self.motor_board_espnow_id = 1
        
    def process_data(self):
        try:
            data = self._motor_board_espnow.read()
            if data is not None:
                data = [n for n in data.msg.split()]
                self._system_data.battery_voltage_x10 = int(data[0])
                self._system_data.battery_current_x100 = int(data[1]) * -1.0
                self._system_data.motor_current_x100 = int(data[2]) * -1.0
                self._system_data.motor_speed_erpm = int(data[3])
                self._system_data.brakes_are_active = True if int(data[4]) == 1 else False
        except:
            supervisor.reload()

    def send_data(self):
        try:
            system_power_state = 1 if self._system_data.system_power_state else 0
            self._motor_board_espnow.send(f"{int(self.motor_board_espnow_id)} {system_power_state}")
        except:
            supervisor.reload()
class PowerSwitch(object):

    def __init__(self, _espnow, mac_address, system_data):
        self._system_data = system_data
        self.power_switch_id = 4 # power switch ESPNow messages ID

        self._espnow = _espnow
        self._peer = ESPNow.Peer(mac=bytes(mac_address), channel=1)
        self._espnow.peers.append(self._peer)
        
    def update(self):
        try:
            self._espnow.send(f"{self.power_switch_id} {int(self._system_data.display_communication_counter)} {int(self._system_data.turn_off_relay)}")
        except:
            supervisor.reload()

For ESP32-S3, the initialization need to be this (see that I am communicating only with a ESP32-S2):

wifi.radio.enabled = True
my_mac_address = [0x68, 0xb6, 0xb3, 0x01, 0xf7, 0xf2]
wifi.radio.mac_address = bytearray(my_mac_address)

# MAC Address value needed for the wireless communication with the display
display_mac_address = [0x68, 0xb6, 0xb3, 0x01, 0xf7, 0xf3]
display = display_espnow.Display(display_mac_address, system_data)
The code to send and receive
class Display(object):
    """Display"""

    def __init__(self, display_mac_address, system_data):
        self._system_data = system_data
        self.my_espnow_id = 1

        self._espnow = ESPNow.ESPNow()
        peer = ESPNow.Peer(mac=bytes(display_mac_address), channel=1)
        self._espnow.peers.append(peer)

    def process_data(self):
        try:
            data = self._espnow.read()
            if data is not None:
                data = [n for n in data.msg.split()]
                # only process packages for us
                if int(data[0]) == self.my_espnow_id:
                    self._system_data.motor_enable_state = True if int(data[1]) != 0 else False
        except:
            supervisor.reload()

    def update(self):
        try:
            brakes_are_active = 1 if self._system_data.brakes_are_active else 0
            self._espnow.send(f"{int(self._system_data.battery_voltage_x10)} {int(self._system_data.battery_current_x100)} {int(self._system_data.motor_current_x100)} {self._system_data.motor_speed_erpm} {brakes_are_active}")
        except:
            supervisor.reload()

@anecdata
Copy link
Member Author

anecdata commented Jul 11, 2023

I'm not entirely clear on the distinction, other than the shared _espnow for the (ESP32-S2) device with two peers. Does this setup not work if the ESP32-S2 and ESP32-S3 are swapped?

Everything is channel=1, so the AP workaround to set the channel may not be strictly necessary in this specific case.

@casainho
Copy link

I'm not entirely clear on the distinction, other than the shared _espnow for the (ESP32-S2) device with two peers. Does this setup not work if the ESP32-S2 and ESP32-S3 are swapped?

The only difference is that this code is needed on S2:

wifi.radio.start_ap(ssid="NO_SSID", channel=1)
wifi.radio.stop_ap()

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug espressif applies to multiple Espressif chips network
Projects
None yet
Development

No branches or pull requests

5 participants