glenn20 / micropython

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

OTA over ESPNOW #7

Closed beyonlo closed 1 year ago

beyonlo commented 1 year ago

Hello @glenn20

As exposed here I would like to request if is a good idea to implement the OTA over ESPNOW, or if is better just to change the protocol, from ESPNOW to regular WiFi, execute normal OTA over WiFi and when OTA is finished, back to works in the ESPNOW protocol. My fear is just about in applications that use just battery, where using the WiFi can to be use so much energy at the OTA over WiFi process.

Thank you!

AmirHmZz commented 1 year ago

A proper firmware update is sized at least 1.4MB. An ideal approach can be prefixing messages with their order number as an 'H' (unsigned short) which will takes 2 bytes. So considering that, we have 248 bytes in each message which will leads us to something about 6K messages per each OTA update. This can keep your esp32 awake for a long time. If everything goes well (no packet loss, firmware update checksum match, no need to resend packets) update will be done successfully. But according to my personal experience in my projects, ESP-NOW can consume much energy (about 80mA on 3.3V after initiated) which is not ideal for battery based systems if used improperly. I recommend to take a look at this issue on esp-idf project repository. The issue reveals that ESP-NOW can be used in battery based environments only if gets deinitiated after sending each message (which cannot be done in OTA over ESP-NOW scenario). Conclusion: OTA over ESP-NOW needs you to develope some mechanisms (such as packet ordering and corruption) which TCP over WiFi already has. So I guess sending OTA update over WiFi consumes less time and energy (both in development and execution). You can also play with PHY_RATE of esp32 which can change power consumption, WiFi range, transfer rate.

glenn20 commented 1 year ago

Thanks @AmirHmZz. My own prior investigations came to much the same conclusions. For several reasons (overlapping with those made above):

It is possible that these limitations may be overcome by implementing more robust re-transmission operations when the receiving device is in MIN_MODEM power saving mode and implementing a retransmission protocol, but it is unlikely to provide any power advantages over update over wifi (happy to be proved wrong on this :-).

@tve did some prior work on OTA updates over MQTT which might be useful if you would like to investigate further (https://github.com/tve/mqboard).

@beyonlo - Thanks for raising this issue - it's very helpful for getting perspectives on the challenges. I'm happy to leave this issue open for some weeks or months in case it sparks some interesting ideas.

beyonlo commented 1 year ago

All right. Maybe for now to continue using the OTA over WiFi is the best option.

But according to my personal experience in my projects, ESP-NOW can consume much energy (about 80mA on 3.3V after initiated) which is not ideal for battery based systems if used improperly. I recommend to take a look at this issue on esp-idf project repository. The issue reveals that ESP-NOW can be used in battery based environments only if gets deinitiated after sending each message (which cannot be done in OTA over ESP-NOW scenario).

That's the point that I was as well looking for. How much is the low consumption using ESP-NOW.

Acordly with that issue: " But I have made good progress now, and found that the key is to de-initialize ESP-NOW at the transmitter side as soon as the message is sent. This cuts the consumption down to 21/28 mA (CPU at 80/160 MHz), measured on a barebone ESP32-WROOM module. I measured the total transmit duration (including initialization and de-initialization) to be 31-33 ms (mostly 32 ms). " @glenn20 Is possible to do that (de-initialize ESP-NOW at the transmitter side as soon as the message is sent) on the MicroPython as well? If yes:

  1. Could you to provide us an example?
  2. Did you tried to mensure how much is the consumption using the MicroPython? Just to compare with that results aboved that was done directly in the esp-idf.

Maybe this is a bit off-topic with the OTA, but I think that is the mostly important information about ESP-NOW when used on battery applications. If you you want I can to open a new issue for this.

Thank you very much!

glenn20 commented 1 year ago

@beyonlo The main advantage of espnow for battery usage is that you can turn on the radio, send the message and turn off in much less time than it takes to turn on the radio, connect to an access point, send the message and shutdown the radio. ie. you get to avoid the time it takes to negotiate a connection with an access point. When the radio is on the power consumption rises dramatically (100s of mA - depending on setup).

The radio is turned on/off whenever you call network.WLAN().active(True/False).

  1. Could you to provide us an example?

A simple example for deepsleep would be (beware - this simple example will put your device into a deepsleep boot loop if you run it from main.py or boot.py without some additional logic):

import network, machine, espnow

peer = b'0\xaa\xaa\xaa\xaa\xaa'   # MAC address of peer
sta = network.WLAN(network.STA_IF)
e = espnow.ESPNow()
e.active(True)
e.add_peer(peer)                  # Register peer on STA_IF
sta.active(true)                      # Turn on the radio
e.send(peer, b'ping')
sta.active(False)                   # Turn off the radio before sleep
e.active(False)
machine.deepsleep(10000)          # Sleep for 10 seconds then reboot

and for lightsleep:

import network, machine, espnow

sta = network.WLAN(network.STA_IF)
peer = b'0\xaa\xaa\xaa\xaa\xaa'   # MAC address of peer
e = espnow.ESPNow()
e.active(True)
e.add_peer(peer)                  # Register peer on STA_IF
while True:
    sta.active(True)                 # Turn on the radio
    e.send(peer, b'ping')
    sta.active(False)               # Disable the wifi before sleep
    print('Going to sleep...')
    machine.lightsleep(10000)       # Sleep for 10 seconds

(Adapted from my docs on light/deep sleep).

  1. Did you tried to mensure how much is the consumption using the MicroPython? Just to compare with that results aboved that was done directly in the esp-idf.

I haven't done this yet as it is difficult to measure, but I have very recently purchased a Nordic Power Profiler Kit II device so I can optimise my sensor devices. When I get a chance, I'll post the results of my experiments.

Additional Notes:

beyonlo commented 1 year ago

@glenn20 Thank you very much for the detailed explanation and examples. The Additional Notes is very useful!

Will be great when you will post the consumption results using the Nordic Power Profiler Kit II device :)

glenn20 commented 1 year ago

@beyonlo I have posted some of my experiments with my new Nordic Power Profiler Kit II at https://github.com/glenn20/upy-esp32-experiments.

This includes an investigation into:

There is also an investigation into using the ESP32 wake stubs. I am planning to develop these further to be more useful for micropython users.

I was a bit time constrained during these measurements, so they are not as systematic or consistent as I would prefer, but there are still some useful conclusions to be drawn. I welcome any comments, suggestions or requests via the Issues or Discussions tabs on that repo.

beyonlo commented 1 year ago

@glenn20 Sorry for the delay, there are many information in your experiments! Thank you very much to share that and for all that incridible optimizations that you did in MicroPython for speed, to use less energy. I would love to use MicroPython with just batteries for sensors aplications using ESPNOW!

Well, wake ESP32-S3 from deepsleep, boot micropython, Send ESPNOW message and return to deepsleep use 10.7 microWh with time of 119ms - that is very good to me. That report with C ESPNOW implementation do not report about the all process (wake, send message, go to deepsleep), it report just 31-33 ms of time to send message.

  1. Could we suppose, even with all Micropython optimizations that you did, that MicroPython use ~100% more energy than that C implementation, to wake from deep sleep, send message and back to deepsleep? Something like as MicroPython ~10.7 microWh and C ~5.3 microWh.
  2. Is possible to optimise still more to have in MicroPython near of energy use used in C Implementation, or you think that already is in the limits, with natural MicroPython limitation?
  3. Do you think to create PRs on the official MicroPython for all that optimizations that you did? Would be amazing!
glenn20 commented 1 year ago

Well, wake ESP32-S3 from deepsleep, boot micropython, Send ESPNOW message and return to deepsleep use 10.7 microWh with time of 119ms - that is very good to me.

Just to clarify - that measurement was done with an ESP32. I should have been clearer about which tests are with ESP32 and which with ESP32-S3 (For some tests I found it easier to code for ESP32 first - then port to the S3 as there were sometimes subtle differences).

That report with C ESPNOW implementation do not report about the all process (wake, send message, go to deepsleep), it report just 31-33 ms of time to send message.

Yes. They report only for the time that the ESPNow radio is on. According to my test, the Wifi radio is on for about 8ms - which is shorter than the 30ms claimed in the test above.

Two things to note:

  1. The report you cited adds a 10ms delay after sending the message.
  2. My example of a failed espnow transmission see turns on the radio for about 35ms (because it causes lots of retransmission attempts).
    • If you send a message, and call esp_now_deinit() (ESPNow.active(False)) before waiting for the response packet, then it triggers the retransmissions just like the failed transmission case - which takes about 35ms (I suspect this may be happening in the report you cited.)
  1. Could we suppose, even with all Micropython optimizations that you did, that MicroPython use ~100% more energy than that C implementation, to wake from deep sleep, send message and back to deepsleep? Something like as MicroPython ~10.7 microWh and C ~5.3 microWh.

That conclusion would not be supported by the evidence ;-). My test shows 8ms vs. their measurement of 33ms for the time that the radio is turned on to send a message. Their report does not count wake from deepsleep time.

  1. Is possible to optimise still more to have in MicroPython near of energy use used in C Implementation, or you think that already is in the limits, with natural MicroPython limitation?

In fact, IF you freeze your app into the image (so it is not necessary to mount the filesystem on the flash) Micropython adds very little overhead to the boot time from deepsleep. It is completely dominated by the native ESP32 boot process. So, in this case there should be little difference between a micropython and a C implementation.

  1. Do you think to create PRs on the official MicroPython for all that optimizations that you did? Would be amazing!

I am thinking about that. The two biggest impacts are:

  1. Freeze your app into the micropython image. You can do this now, but you have to compile your own image.
  2. Add CONFIG_BOOTLOADER_SKIP_VALIDATE_IN_DEEP_SLEEP=y to ports/esp32/boards/sdkconfig.base. I am guessing that it is unlikely this will be accepted in the micropython main due to the comments on the espressif docs page - but you never know :-).

I am working on a PR to enable a runtime flag to enable some of the other savings on wake from deepsleep and also another PR to provide some wake_stubs code which would handle common use cases (such as count pulses on a pin/pins or wake at fixed time intervals and sample the state of input pins, etc.). Once I have draft PRs for those, I'll seek further input.

glenn20 commented 1 year ago

@beyonlo

  1. Is possible to optimise still more to have in MicroPython near of energy use used in C Implementation, or you think that already is in the limits, with natural MicroPython limitation?

I have found a few other recommendations for saving time in the bootloader during wake from deepsleep. I will try them when I get a chance (eg. disable UART output from the bootloader).

beyonlo commented 1 year ago

Hello @glenn20

  1. Could we suppose, even with all Micropython optimizations that you did, that MicroPython use ~100% more energy than that C implementation, to wake from deep sleep, send message and back to deepsleep? Something like as MicroPython ~10.7 microWh and C ~5.3 microWh.

That conclusion would not be supported by the evidence ;-). My test shows 8ms vs. their measurement of 33ms for the time that the radio is turned on to send a message. Their report does not count wake from deepsleep time.

Excellent!

  1. Is possible to optimise still more to have in MicroPython near of energy use used in C Implementation, or you think that already is in the limits, with natural MicroPython limitation?

In fact, IF you freeze your app into the image (so it is not necessary to mount the filesystem on the flash) Micropython adds very little overhead to the boot time from deepsleep. It is completely dominated by the native ESP32 boot process. So, in this case there should be little difference between a micropython and a C implementation.

All right. I think that this is not a problem. Who want to have very little overhead need to freeze the code into the image.

  1. Do you think to create PRs on the official MicroPython for all that optimizations that you did? Would be amazing!

I am thinking about that. The two biggest impacts are:

  1. Freeze your app into the micropython image. You can do this now, but you have to compile your own image.

As I commented above, I think this will be just a condition - not a problem for a very specific scenario (power just for batteries)

  1. Add CONFIG_BOOTLOADER_SKIP_VALIDATE_IN_DEEP_SLEEP=y to ports/esp32/boards/sdkconfig.base. I am guessing that it is unlikely this will be accepted in the micropython main due to the comments on the espressif docs page - but you never know :-).

Understood! But if we think that at some point the ESPNOW PR will need to merged to mainstream, this can be a condition as well :) Or maybe, if need to change so much, to have two MicroPython official firmwares, one for normal use, and other for ESPNOW (power by batteries) :) But maybe to have two official firmwares is not a good idea!

Question: your tests use ESP32 and ESP32-S3, both has two cores. Did you tried with ESP32-C3 or ESP32-S2 to check if the power consumption is lower? I assume that two cores use more energy than one core, right?

Great works! Thanks!