microbit-foundation / micropython-microbit-v2

Temporary home for MicroPython for micro:bit v2 as we stablise it before pushing upstream
MIT License
42 stars 23 forks source link

New Feature: programmable power management #60

Closed microbit-mark closed 1 year ago

microbit-mark commented 3 years ago

Is your feature request related to a problem? Please describe.

User story: I need to make a sensing device that lasts a long time on batteries. Thus the device needs to power off under program control, and power up on an event, take a measurement, then go to sleep again.

Describe the solution you'd like CODAL offers a power management API https://github.com/lancaster-university/microbit-v2-samples/blob/master/source/samples/PowerManagementTest.cpp and there is a published spec https://github.com/microbit-foundation/spec-power-management. It would be useful to surface this in MicroPython

Describe alternatives you've considered None

Additional context Add any other context or screenshots about the feature request here.

microbit-carlos commented 2 years ago

Proposal:

dpgeorge commented 2 years ago

Initial power module has been implemented in 009fbd28c6c7e2e2f03ccd622e5087f05177a144.

It currently does not support the run_every argument to deep_sleep(). But everything else is there.

I noticed that deep_sleep() doesn't work very well. Some issues I noticed are:

microbit-carlos commented 2 years ago

Thanks Damien!

I've updated the PR with the discussion over ther: https://github.com/bbcmicrobit/micropython/pull/754

It currently does not support the run_every argument to deep_sleep().

Is that due to some technical limitation or barrier?

if ms and buttons are specified then only ms works (buttons do not wake it)-

I wasn't able to replicate this one in CODAL, but I have found a related issue:

When trying this in the REPL, power.deep_sleep() does block until time has elapsed, but the 1 appears on the display as soon as the button is pressed:

MicroPython b781e65 on 2022-08-23; micro:bit v2.0.0 with nRF52833
Type "help()" for more information.
>>> import power
>>> display.show("1")
>>> power.deep_sleep(ms=20000, buttons=button_a)

specifying buttons=button_b also allows waking it by button A

Is this a bug in MicroPython? I haven't been able to replicated in CODAL v0.2.40 with this example:

#include "MicroBit.h"

MicroBit uBit;

int main() {
    uBit.init();

    uBit.display.print("!");
    uBit.sleep(400);

    uBit.buttonB.wakeOnActive(true);
    uBit.power.deepSleep();

    uBit.display.print("Up");

    while (true) {
        uBit.sleep(100);
    }
}

I wasn't able to replicate this on the REPL either after a reset button press:

MicroPython b781e65 on 2022-08-23; micro:bit v2.0.0 with nRF52833
Type "help()" for more information.
>>> import power
>>> display.show("1")
>>> power.deep_sleep(buttons=button_a)

However, if I then called again power.deep_sleep() with a different buttons value, the old still woke up the micro:bit.

MicroPython b781e65 on 2022-08-23; micro:bit v2.0.0 with nRF52833
Type "help()" for more information.
>>> import power
>>> display.show("1")
>>> power.deep_sleep(buttons=button_a)
>>> power.deep_sleep(buttons=button_b)

the first time deep_sleep(1000) is called, the microphone LED comes on after it wakes

This is capture in the CODAL issue tracker and should be fixed by the next release:

microbit-carlos commented 2 years ago

Ah, I think we need to clear the wake up sources on every call to power.deep_sleep():

dpgeorge commented 2 years ago

The pins and buttons arguments have been merged together into the wake_on argument, see f0fe9823ebd428569acb1ebb2fa7be6972885c48

dpgeorge commented 2 years ago

The run_every parameter is implemented by 2233d79b8ab15bb769adaa44d53874ce80ebd3eb

Due to the issue with CODAL that a button/pin won't stop the timeout, run_every doesn't work as expected if wake_on is specified (the wake source will not wake the device, it will only wake when the next run_every event is scheduled).

microbit-carlos commented 2 years ago

Due to the issue with CODAL that a button/pin won't stop the timeout, run_every doesn't work as expected if wake_on is specified (the wake source will not wake the device, it will only wake when the next run_every event is scheduled).

Just to clarify, I think the wake source does wake up the target, but the function power.deep_sleep() is blocking until the "sleep time" elapses:

With this Python script, it goes to sleep when pressing A, and then when pressing B it wakes up showing the letter S in the display (which was set right before going to sleep). It then waits there until the 10 seconds of sleep elapse, and only at that point it shows the ! in the display and continues running:

from microbit import *
import power

@run_every(s=30)
def foo():
    display.show(Image.SURPRISED)
    sleep(150)

while True:
    display.show(Image.HAPPY)
    if button_a.is_pressed():
        display.show("S")
        sleep(100)
        power.deep_sleep(10000, wake_on=button_b, run_every=True)
        display.show("!")
        sleep(500)
    sleep(200)
dpgeorge commented 1 year ago

Just to clarify, I think the wake source does wake up the target, but the function power.deep_sleep() is blocking until the "sleep time" elapses

Yes, I agree, that looks to be the behaviour.

For MicroPython, I think we need a uBit.power.deepSleepAtMost(ms) (or similar) API. Essentially a non-blocking deep sleep. Does something like that already exist?

microbit-carlos commented 1 year ago

We've shipped this in its current state in v2.1.0-beta.1, so we can close this as part of that milestone, and any additional changes captured in new tickets.