micropython
micropython copied to clipboard
esp32: ESP-NOW support for ESP32 and ESP8266
ESP-NOW is a proprietary wireless communication protocol which supports communication between ESP32 and ESP8266 devices. See: https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/network/esp_now.html
ESP-NOW would be of particular interest for low-power wireless communication requirements, such as battery-powered operations.
This PR is based on the previous work by @nickzoic, @shawwwn and contributions from @zoland, including:
- Issue micropython/micropython-esp32#197
- Initial branch from @nickzoic: https://github.com/nickzoic/micropython-esp32/tree/esp32-espnow
- Updates from @shawwwn: #4115
- More progress from @nickzoic: https://github.com/nickzoic/micropython/tree/espnow-4115
This PR builds on the espnow-4115 branch by adding:
- Fixups to rebase against the main branch (also patches cleanly against v1.13).
- Add send and recv ring buffers to prevent buffer overwrites and minimise packet loss 1b372b2
- Dynamic allocation of ESPNow singleton so that gc will not reclaim buffer memory. adbdc32
- Eliminate heap allocation on send and recv callbacks (re-use static buffers) adbdc32
- Extend API to provide full control over peer_info parameters in 58bd9de
- Including some proposed API breaking changes!!
Docs API docs are provided in 3d4058b. See docs/library/espnow.rst. The latest API docs can be browsed at: https://micropython-glenn20.readthedocs.io/en/latest/library/espnow.html.
This is working reliably for my purposes as a low-level ESPNow interface. Testers and proposed improvements are welcome.
Caveat ~~I have only implemented this for the esp32. I don't have any ESP8266 devices.~~ ESP32 and ESP8266 is now supported.
Further development ~~It would be useful to extend this interface with a more robust I/O interface (rather than relying on the send and recv callbacks which run in the sched context and easily overflow the sched stack). Some form of uasyncio would be interesting.~~ Done
Comments, suggestion are welcome.
Thanks Glenn! I'll do some testing when I get a chance!
Has this been addressed?
I see a threading issue with the callbacks, from the esp32 docs:
The sending callback function runs from a high-priority WiFi task. So, do not do lengthy operations in the callback function. Instead, post necessary data to a queue and handle it from a lower priority task. The receiving callback function also runs from WiFi task.
To me "high priority Wifi task" has the smell of Pro CPU and mp_sched_schedule is not protected against that. Of course it would be nice if the Espressif docs were a bit more explicit, perhaps the esp-idf source sheds some light...
Has this been addressed?
Thanks for bringing this up again - it fell off my radar.
I see a threading issue with the callbacks, from the esp32 docs:
The sending callback function runs from a high-priority WiFi task. So, do not do lengthy operations in the callback function. Instead, post necessary data to a queue and handle it from a lower priority task. The receiving callback function also runs from WiFi task.
To me "high priority Wifi task" has the smell of Pro CPU and mp_sched_schedule is not protected against that. Of course it would be nice if the Espressif docs were a bit more explicit, perhaps the esp-idf source sheds some light...
I confess I've had no more success than any others in divining the mysteries behind the espressif docs. I think I am doing just what the docs recommend - handing data off to a queue (ring buffer) from the callback functions (I think of them as ISRs).
The ring buffer is thread safe so long as only one producer is pushing new data onto the head of the buffer (recv_cb() and send_cb() - which execute in the "high-priority Wifi Task") and only one consumer (callback_wrapper()) is pulling data off the tail of the ring buffer.
So, I don't believe it matters which CPU they are being executed on - so long as recv_cb() is never pre-empted by another recv_cb() (on the same or another CPU) and callback_wrapper() is never pre-empted by another callback_wrapper().
I am interested to understand better the concerns around the mp_sched_schedule vulnerability to Pro CPU invocations. If there is some further protection required I'd welcome any guidance. I'm keen to make this as robust as possible.
EDIT: I've done some small sustained transfer tests and haven't seen any buffer corruption yet - but that's no guarantee.
Tried to use espnow, using the documentation docs/library/espnow.rst. I can't init espnow, what argument is it expecting? see code below.
from esp import espnow
espnow.ESPNow.init()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: function takes 1 positional arguments but 0 were given
Hi Feiko,
Thanks for testing. ESPNow is a Class, you need to invoke a class instance first before calling the init() method. The errors is less than useful - I admit.
Typical usage (I'll put a short code snippet at the start of the docs in the next commit).
from esp import ESPNow
e = espnow.ESPNow()
e.init()
....
Furthermore, you need to initialise a WLAN interface before calling init() or your ESP32 may reset (on the todo list): eg.
import network
w0=network.WLAN(network.STA_IF) # Can be STA_IF or AP_IF
Additionally, you need to invoke w0.active(True) before you can send or receive any data.
Thanks Glenn20,
Of course! That did the trick. My Python is a bit rusty. A short code snippet would be very welcome.
@tve: I've done a little empirical testing, but I'm not sure if they are valid in micropython.
Using xPortGetCoreID() I tested the coreid for
- the espnow callbacks (recv_cb() and send_cb()),
- the callback_wrapper() scheduled by mp_sched_schedule() and
- the main thread running espnow_send(). In all cases the code reported back coreid=0. I ran the tests for about two hours.
I was a little surprised because I thought the micropython thread ran on coreid=1 (but I'm not sure why I had acquired that assumption). Is it possible that xPortGetCoreID() does not report correctly (eg. not initialised under micropython)?
Let me know if there are any specific effects I should be protecting against.
Ok - I've just checked and discovered that arising from #5489 micropython recently switched to pin everything to core 0, so the results of my test were not as unexpected as I thought.
Let me know if there are any specific effects I should be protecting against.
Code looks good to me!
Thanks @tve,
Actually, I am in the midst of a significant rewrite to remove the dependence on mp_sched_schedule to schedule callback functions. I have come to realise the fragility this introduces, and for these purposes it is not necessary now that we have a buffering solution.
I've also implemented asyncio and stream support (like uart). I'll post a new commit within 24 hours. It is a pretty substantial change. I understand that it is not ideal to make significant changes against a PR, but I do think it will be a much more robust solution.
On Sun, 11 Oct 2020 at 15:55, Thorsten von Eicken [email protected] wrote:
Let me know if there are any specific effects I should be protecting against.
Code looks good to me!
— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/micropython/micropython/pull/6515#issuecomment-706649986, or unsubscribe https://github.com/notifications/unsubscribe-auth/ABVEQRYPXQYQFXLUT5Y6GOTSKE3DBANCNFSM4SDXWCEA .
@glenn20
Is possible to do a like wlan.scan() to get all APs MAC and their signal? Actually, I'm working (ESP32) with wlan.scan() to get location-based on fixed APs. How is possible to do that with ESPNOW?
Thank you.
@beyonlo
By default, espnow devices respond to acknowledge receipt of packets, so it would be straightforward to scan status of a local network of known Mac addresses, but not to scan for unknown devices - as far as I am aware.
I'm not aware of any location based uses of espnow, but it might be an interesting experiment to see if a network of fixed esp32s could be used for location with rssi or round trip location. The max peer count might be a limitation.
Cheers, Glenn.
On Fri, 16 Oct 2020, 5:15 am beyonlo, [email protected] wrote:
@glenn20 https://github.com/glenn20
Is possible to do a like wlan.scan() to get all APs MAC and their signal? Actually, I'm working (ESP32) with wlan.scan() to get location-based on fixed APs. How is possible to do that with ESPNOW?
Thank you.
— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/micropython/micropython/pull/6515#issuecomment-709502979, or unsubscribe https://github.com/notifications/unsubscribe-auth/ABVEQR64EEEPGUITZNQZHVDSK432LANCNFSM4SDXWCEA .
Actually - scrap that. A little further checking and I see that ToF is not supported in IDF (though there are hints that the hardware may be capable). People seem to be more optimistic that the ESP32-S2 may get 802.11mc support at some point in the future.
On Fri, 16 Oct 2020 at 07:51, Glenn Moloney [email protected] wrote:
@beyonlo
By default, espnow devices respond to acknowledge receipt of packets, so it would be straightforward to scan status of a local network of known Mac addresses, but not to scan for unknown devices - as far as I am aware.
I'm not aware of any location based uses of espnow, but it might be an interesting experiment to see if a network of fixed esp32s could be used for location with rssi or round trip location. The max peer count might be a limitation.
Cheers, Glenn.
On Fri, 16 Oct 2020, 5:15 am beyonlo, [email protected] wrote:
@glenn20 https://github.com/glenn20
Is possible to do a like wlan.scan() to get all APs MAC and their signal? Actually, I'm working (ESP32) with wlan.scan() to get location-based on fixed APs. How is possible to do that with ESPNOW?
Thank you.
— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/micropython/micropython/pull/6515#issuecomment-709502979, or unsubscribe https://github.com/notifications/unsubscribe-auth/ABVEQR64EEEPGUITZNQZHVDSK432LANCNFSM4SDXWCEA .
As flagged, a substantial rewrite with breaking changes. Apologies for the delay - shoulder injury slowed me down for a while and I spent some time faffing about with various approaches to supporting more reliable IO, allocation-free reads and asyncio support. Learnt a lot more about micropython internals.
I know that a substantial change of direction in a pull request is less than ideal, but I do believe the new approach warrants the change. I'm happy to take on board feedback. I will say that I get more reliable and faster data transfers with the new approach.
Breaking Changes
- Remove on_send() and on_recv(): The mp_sched_schedule() stack is easily overflow-ed and can cause unfortunate reliability interactions when used along with other devices, such as bluetooth or other machine IO.
- Add recv([timeout_ms]): Wait for and return a tuple:
(peer_mac, message). Default timeout can be set withconfig(timeout=ms). New tuple and bytestring storage is alloced on each call. - Add irecv([timeout]): As for
recv()but supports alloc-free reads. Returns a callee-owned tuple. ie. the tuple and bytestring storage is alloc-ed once on first call and re-used across subsequent calls. - Change send(peer_mac, message[, sync=True]): Add
syncflag (default=True) to support syncronised writes to the peer. Ifsync=True: send() the message and wait for response (or not) of peer(s). Ifsync=False: send message to peer and return immediately. Responses from peers will be discarded. Ifsync=True: returnFalseif any peer fails to respond. Note that a 'broadcast' will send to all registered peers. Synchronised mode is much more reliable, whilesync=Falseis much faster. - Add clear(True): Clear any pending data in the recv buffer. Use this in case you get a "Buffer error" (should not happen). Will discard all data in the buffer.
- Add support for stream IO interface: read(size), write(), readinto(), readinto1(), read1(). I'll provide further docs and python code support for the data format used by these functions. Also supports poll.poll() which enables support for asyncio through the StreamReader() class.
- Add support for iterating over the ESPNow() interface (via
irecv()), eg:for i in espnow.ESPNow(): print i
Also a substantial internal rewrite to better consolidate the buffer and data packet logic.
Ok - I've squashed the commits to just two: original import from PR#4115 and my current HEAD.
I believe this is ready for review. It is working well for me but happy to hear from others.
Kudos for the uasyncio support :+1:
Thanks Peter - and thanks for all your great work on uasyncio and the magnificent support resources.... :)
On Mon, 19 Oct 2020 at 21:48, Peter Hinch [email protected] wrote:
Kudos for the uasyncio support 👍
— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/micropython/micropython/pull/6515#issuecomment-712034709, or unsubscribe https://github.com/notifications/unsubscribe-auth/ABVEQR527VDFIINDB5IJGJ3SLQKQ5ANCNFSM4SDXWCEA .
For those interested, I now have a heavily pared down version of the esp32 espnow support working on esp8266 available at: https://github.com/glenn20/micropython/tree/espnow-g20-8266. I am planning to make a separate PR for the esp8266 support after a little more testing, but I welcome any testers or users in the meantime.
Does not support:
- stream IO and asyncio
get_peer(),mod_peer()andget_peers()config()- ESPNow broadcast in
send()
However, it does support:
init([recv_bufsize]): Override the default recv_buffer size in init() (noconfig()method)deinit()irecv([timeout]): No recv() method.send(peer, message, [sync]): Sync, and non-sync send()s are supported.add_peer(peer[, lmk[, channel]])del_peer(peer)
With this build my compile builds to exactly the max .text size (32768 bytes) for the esp8266. Note: A number of defensive arg and consistency checks are also removed from the esp8266 espnow code.
Is there any indication when this will be merged and available for download as part of the main micropython binaries?
Sorry for the slow response (been away on holiday for a few weeks). I don't really know how to get an eye on the process for getting PRs reviewed and merged. I'm just hoping it will be soon-ish. Any suggestions for prodding the decision makers are welcome :-).
I have added some updates:
- Merged in the esp8266 support (is only compiled in for GENERIC_1M and GENERIC build targets).
- Some code simplifications and optimisations for the ESP32 (inspired by code reductions required for esp8266).
- Updates to the docs.
ESP32 and ESP8266 users can get their code from this one branch now.
Hello,
thank you for adding this feature to Micropython. I verified that code is working with actual master branch of MPY and I can send messages esp8266 -> esp32 and esp32 -> esp32. I hope it will be merged soon ;)
I have just applied a commit which:
- mod_peer(): bug fix - alignment of args was incorrect
- irecv(): on timeout, set length of callee-owned msg buffer to zero.
- Minor code and docs cleanups.
I have also rebased against micropython/master as of 28 Jan 2021.
Ok so @dpgeorge and I discussed this one and we just need to run some tests to get it over the line and merged in. I'm really excited about that! I've got heaps of this hardware on hand so should be able to get something done soon in the same general way as tests/multi_net.
Also, should we be closing #4115 in favour of this one? I'm not clear how they've diverged.
Thanks Nick. That's great news. I been working on a test framework for this code precisely to get it accepted more readily. It's different to standard upy test as you need a second device running a test-echo server. Thanks for the pointer to test/multi-net. I'll take a look - I had missed those.
I do think it best to close #4115 as this is a pretty substantial rewrite - and the API has changed.
I've squashed commits down to just two again (initial import of code from PR4115 and my uppdates) - for ease of review.
A new commit to address two bugs: espnow_singleton is not reset on soft reset and not sufficiently protected against gc. I have added the espnow_singleton to the micropython root pointers and added code to main.c to reset espnow_singleton to NULL on soft (or hard) reset. I've also added MICROPY_ESPNOW as a conditional flag in the Makefiles. This patch touches a lot more files.
@nickzoic, I have made up some draft tests for your consideration at https://github.com/glenn20/micropython/tree/espnow-g20-tests/tests/multi_espnow. Let me know if I'm barking up the wrong tree. The simple_data.py, send_echo.py and lmk_echo.py (tests encrypted transfers) tests should work for ESP32 or ESP8266 devices. The esp32_* tests require at least one of the devices to be an ESP32 and tests all the functionality not available on the esp8266.
It probably needs some tests to cover failure modes, let me know if you think so ;).
Ok so @dpgeorge and I discussed this one and we just need to run some tests to get it over the line and merged in. I'm really excited about that! I've got heaps of this hardware on hand so should be able to get something done soon in the same general way as
tests/multi_net.Also, should we be closing #4115 in favour of this one? I'm not clear how they've diverged.
@nickzoic just a poke to see if there is anything else I can do to get this PR merged in.
Hi, don't want to add to the noise, but I just needed to say GREAT WORK. I just followed the thread and different issues for an hour. Special kudos to @glenn20 who seems to have ground this for years, thanks.
This was just the thing I am looking for, so I can get 20-30+ devices talking without a star topology. I hope the 0.05% test coverage drop does not stop the powers that be from giving us access to this great feature.
Thanks