glenn20 / micropython

MicroPython - a lean and efficient Python implementation for microcontrollers and constrained systems
https://micropython.org
Other
38 stars 9 forks source link

Sorted `ESPNOW.peers_table` #12

Closed AmirHmZz closed 1 year ago

AmirHmZz commented 1 year ago

My feature request comes with two questions:

  1. Are ESPNOW.peers_table records sorted by any parameter like latest message timestamp or rssi?
  2. Which parameter is better to sort the table by? rssi or latest message timestamp?
glenn20 commented 1 year ago

The ESPNow.peers_table is a python dict, which doesn't have an intrinsic notion of order. Since 3.7, python guarantees that insertion order is preserved in dictionaries (eg. when calling dict.items()), but I don't think that micropython makes such guarantees.

So, the peers_table dict is indexed by peer MAC address and the values are a list: [rssi, timestamp]. Only the very latest rssi and timestamp for each peer is saved - ie. each new message from a peer overwrites the [rssi, timestamp] values.

If you have a specific use case that is not well-supported by this mechanism, let me know and I'll look at supporting that in future versions.

I envisaged several possible modes for using the rssi values:

  1. Periodically monitoring and responding to changes in RSSI values.
    • For example, updating a display or periodically monitoring the rssi values.
    • In this case, the app workflow for rssi monitoring runs independently of the app worflow for processing messages
  2. Responding to the rssi values for each and every message received from a peer.
    • In this case the app workflow for monitoring rssi values is synchronised with the app logic for reading and processing messages.

(NOTE: the peers_table dict is also handy for monitoring when you last heard from a peer as it includes the timestamp for the last received message.)

Example 1: Periodic monitoring of rssi values of peers

def rssi_check(e, period):
    now = time.ticks_ms()
    for peer, (rssi, rssi_ms) in e.peers_table.items():
        if time.ticks_diff(now, rssi_ms) < period and rssi < rssi_threshold:
            print(f"Warning: Signal strength ({rssi}) is below threshold for peer {peer}.")

then call rssi_check(e, period) every period milliseconds from your app loop (or from a periodic async task). You would read the actual messages in some other part of your app workflow.

Example 2: Monitoring rssi for every message received

for peer, msg in e.irecv():
    rssi, rssi_ms = e.peers_table[peer]
    # process message
    ...
    # process rssi
    if rssi < rssi_threshold:
        print(f"Warning: Signal strength ({rssi}) is below threshold for peer {peer}.")

If you want to store and monitor changes in rssi values over time (eg. graph changes over time or analyse movements within a network), you need to store the rssi values into some other data structure.

AmirHmZz commented 1 year ago

@glenn20 Sorry for the delay. Do you have any statistics about how much RAM does peers_table takes? Does peers_table has any size limitation? There's also another recommendation: peers_table name is not much suitable as long as it contains broadcast message senders too.

glenn20 commented 1 year ago

I haven't measured it precisely, but it should be about (1 mp_obj_str_t object + 6 bytes + 1 mp_obj_list_t + 2 x mp_obj_t = 16 + 6 + 16 + 8 = 46 bytes) per entry in the dict and there is one entry for each peer from whom we have received a message (remember, it only stores rssi for the last message received from each peer). The use of the peers table also saves some memory elsewhere because recv() returns a reference to the bytes object containing the MAC address in the dict keys instead of allocating a new MAC address bytes object for each packet received from the peer.

@AmirHmZz Regarding the name, I'm very open to suggestions. It is a table of data (rssi and timestamp of last received message) about all the peers from whom we have received a message.

AmirHmZz commented 1 year ago

If you have a specific use case that is not well-supported by this mechanism, let me know and I'll look at supporting that in future versions.

I use peers_table in order to find nearby devices. I've developed a MESH network on top of ESP-NOW and each device sends something like ping messages to broadcast address. So each device finds nearby devices by iterating over peers_table and filtering them by two parameters:

  1. RSSI >= _MIN_RSSILIMIT
  2. Latest message timestamp >= time.time() - _PINGSTIMEOUT

When I opened this issue, I needed peers_table to be sorted by RSSI. But after that I've decided to change my algorithm and after you described other use cases of peers_table, I think it's one of the best choices for its implementation.

The reason that I insist on renaming peers_table is not only peers are included in that table. I mean device A will be found in device B's peers_table if device A sends a message to broadcast MAC address even If device B has not added device A as a peer. What about renaming it to table? Or maybe nopeers_table (Inspired by NoSQL, just kiddin)! Anyway, I'll keep thinking about it to suggest better names and I will share them with you.

AmirHmZz commented 1 year ago

I think it's better to update documentation and mention that broadcast message senders are also included in peers_table. I remember that I've done a test to discover this ability because I needed it In my project.

glenn20 commented 1 year ago

I think it's better to update documentation and mention that broadcast message senders are also included in peers_table. I remember that I've done a test to discover this ability because I needed it In my project.

Ah - I get where the ambiguity is now :). I think of a peer as any ESP-NOW capable device operating on the same channel (ie. any device which can send me a message), rather than just those that we have registered with add_peer() (which I think of as "add the peer to the list of peers to whom we will send messages", rather than "make this device a new peer"). By my interpretation, the docs are pretty clear, but I see that peer is not a well defined term (the Espressif API docs are not explicit on this either). According to my implicit definition, the name makes sense, but not according to yours. I'll look at clarifying that in the docs.