zmkfirmware / zmk

ZMK Firmware Repository
https://zmk.dev/
MIT License
2.48k stars 2.55k forks source link

Feature request: Soft shutdown of keyboard #1292

Closed dxmh closed 2 months ago

dxmh commented 2 years ago

Lots of users request the ability to power off their keyboard (in the absence of a physical power switch).

The reason behind most of the requests is so that the keyboard can be transported in a bag without accidentally sending keystrokes and/or draining battery. However, it's also just nice to be able to turn things off when they're not being used. 🙂

R3XP commented 2 years ago

How would that work in theory? Would it shut down until a certain keycombo is pressed? If yes, how often does the mcu scan for the combo? If no, is there another way to start it again? Pluging it into something maybe? Or a designated button to restart it? Maybe a reset button?

{just brainstorming out loud here ^^}

dxmh commented 2 years ago

Good question. There was some discussion about this on Discord a few days ago. Here are some relevant snippets to aid the brainstorming:

hxy: How would you power-on the keyboard after a soft shutdown?

nef: reset switch would work...

…

petejohanson: We could have a software shutdown, that stops BLE and reduces scan frequency. So we could still detect key presses, but much more slowly. Or we set a flag and go into deep sleep, so if a key press does wake us, still stay in "software lockout mode"

R3XP commented 2 years ago

Hmm I see. What do you think about a user defined combo where the mcu would only ever scan one of the columns of the combo so that there wouldnt be as much activity as usally but when theres any key pressed in that column it would start scanning for the whole combo again?

grasegger commented 2 years ago

Hmm I see. What do you think about a user defined combo where the mcu would only ever scan one of the columns of the combo so that there wouldnt be as much activity as usally but when theres any key pressed in that column it would start scanning for the whole combo again?

Not every board uses a matrix, some use direct pin assignments (but I guess you could make that configurable at compile time).

What about split keyboards? I guess since the slave side already operates very power efficient it could be ignored, but maybe some users would like to shut down both sides.

manna-harbour commented 2 years ago

Also see https://github.com/zmkfirmware/zmk/issues/405.

freqmod commented 1 year ago

Hi, i did a 2 hour stab at this (instead of figuring how to solder a power switch to my corne helix), and found a solution that seems to work for me on nice nano v2. I haven't measured the current draw, but i do not get any presses until reset i pressed. Use the &suspend key behaviour (and include the reset header if necessary) in the keyboard layout. For split keyboard add the key on both halves and suspend the right half before the left (if you want both to be off).

The following patches are needed (if someone wants to make this into a proper upstream feature it would be great. feel free, and use the MIT license. It would be great if you'd mention my name in the commit message): (Based on zmk revision fc511e40cc1a274473a753c959f8d7e5fcc317d0)

commit 726a482165964d67f1f8d4cd1dd3dcde67e4de57 (HEAD -> nrf_suspend)
Author: Frederik M.J.V <freqmod@gmail.com>
Date:   Sun Oct 2 12:05:30 2022 +0200

    Support for supend for nrf52

diff --git a/app/dts/behaviors/reset.dtsi b/app/dts/behaviors/reset.dtsi
index cb246814..f1192023 100644
--- a/app/dts/behaviors/reset.dtsi
+++ b/app/dts/behaviors/reset.dtsi
@@ -20,5 +20,11 @@
                        type = <RST_UF2>;
                        #binding-cells = <0>;
                };
+               suspend: behavior_reset_suspend {
+                       compatible = "zmk,behavior-reset";
+                       label = "SUSPEND";
+                       type = <RST_SUSPEND>;
+                       #binding-cells = <0>;
+               };
        };
 };
diff --git a/app/include/dt-bindings/zmk/reset.h b/app/include/dt-bindings/zmk/reset.h
index 2b3d8760..4ebb8d0f 100644
--- a/app/include/dt-bindings/zmk/reset.h
+++ b/app/include/dt-bindings/zmk/reset.h
@@ -10,4 +10,5 @@
 // AdaFruit nrf52 Bootloader Specific. See
 // https://github.com/adafruit/Adafruit_nRF52_Bootloader/blob/d6b28e66053eea467166f44875e3c7ec741cb471/src/main.c#L107

-#define RST_UF2 0x57
\ No newline at end of file
+#define RST_UF2 0x57
+#define RST_SUSPEND 0xFE
\ No newline at end of file

And for zephyr (on revision 8adeab429a2480198d22df9847933cfe3f9ea410 ):

commit d1eee565d5c3aab021839aa77743a48b32575815 (HEAD -> nrf52_suspend)
Author: Frederik M.J.V <freqmod@gmail.com>
Date:   Sun Oct 2 12:09:46 2022 +0200

    Allow soft power off of nrf52 boards

diff --git a/soc/arm/nordic_nrf/nrf52/soc.c b/soc/arm/nordic_nrf/nrf52/soc.c
index e1e63e9f8..51e4665bb 100644
--- a/soc/arm/nordic_nrf/nrf52/soc.c
+++ b/soc/arm/nordic_nrf/nrf52/soc.c
@@ -18,6 +18,7 @@
 #include <hal/nrf_power.h>
 #include <soc/nrfx_coredep.h>
 #include <logging/log.h>
+#include <pm/state.h>

 #ifdef CONFIG_RUNTIME_NMI
 extern void z_arm_nmi_init(void);
@@ -51,8 +52,13 @@ LOG_MODULE_REGISTER(soc);
    Set general purpose retention register and reboot */
 void sys_arch_reboot(int type)
 {
-       nrf_power_gpregret_set(NRF_POWER, (uint8_t)type);
-       NVIC_SystemReset();
+       if (type == 0xFE){
+               nrf_power_system_off(NRF_POWER);
+       }
+       else{
+               nrf_power_gpregret_set(NRF_POWER, (uint8_t)type);
+               NVIC_SystemReset();
+       }
 }

 static int nordicsemi_nrf52_init(const struct device *arg)
manna-harbour commented 1 year ago

@freqmod To make your changes easier to test, here they are in separate branches, including a change to the zmk manifest to use the zephyr fork.

Support is also included in Miryoku ZMK.

manna-harbour commented 1 year ago

@freqmod Could this be implemented using BEHAVIOR_LOCALITY_GLOBAL?

freqmod commented 1 year ago

Initially i tried to do global behaviour. As far as i could see from the implementation the global behaviour first sends a command to the peripheral (right side) and then executes the command itself. I was a bit concerned that the command to the peripheral side seems to be put in a queue. If the right side executes before the queue element is processed then it will power down the right side before the left side gets the command to power of. I guess there are ways to handle this e.g. by putting the command to power down the left side in the queue after the other command. Another option may be to sleep, but if you block the queue processing it does not help. I just wanted something that worked within a reasonable implementation time, so i haven't investigated this, and rather went with the local behaviour.

Btw i have been testing this for a week without any issues (except sometimes reconnecting after wakeup, but i guess that is present if you use a hard power off switch too). It seems my battery stays charged too.

Also it may be useful see https://github.com/freqmod/zmk where i have applied the zmk (but not zephyr) patch, in addition to other patches i use. I will rebase this now and then, whenever I want new zmk firmware on my keyboards.

jacobsamlarose commented 1 year ago

@freqmod Question (and please excuse my noobiness): I'm testing this on a Corne with OLED displays; I usually keep the OLEDs off unless diagnosing a fault, but kept them on to test this soft shutdown. I have a "travel layer" with a keymap full of &nones that I'd usually toggle on before packing the board; I've now added &suspend keys to each side in that layer. When I press the &suspend keys, the Bluetooth connection shuts down and the keys are non-responsive (as I'd expect for a shutdown) but the OLEDs continue to display whatever was showing when I pressed the suspend keys— is that expected?

frederik-vestre-pexip commented 1 year ago

I am not sure what the nrf52 suspend does. Have you considered seeing if the external power off behaviour turns off the displays, maybe they can be combined with this in a macro or something?

If so it may be sensible to add external power down etc. in the code pre shutdown. I am not sure what is required on startup though, if you need specific code to power it up at that time.

jacobsamlarose commented 1 year ago

Something like: ZMK_MACRO(shutdown, wait-ms = <30>; tap-ms = <40>; bindings = <&ext_power EP_OFF &suspend>; )

? I'll give this a go and see.

MartianH commented 1 year ago

Any update on this?

freqmod commented 1 year ago

At least for my side I am not intending to try to spend effort to try to upstream this(I. e. get this merged). Especially since it requires changes to zephyr too (to reset the device).

If anybody else wants to do the work that would be great though.

caksoylar commented 6 months ago

Would be resolved by #1942.

elpekenin commented 2 months ago

Closed by #2085