Aircoookie / WLED

Control WS2812B and many more types of digital RGB LEDs with an ESP8266 or ESP32 over WiFi!
https://kno.wled.ge
MIT License
14.54k stars 3.11k forks source link

Restore previous state after disconnecting hyperion.ng #1308

Closed BeMacized closed 2 years ago

BeMacized commented 3 years ago

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

Currently, when hyperion "disconnects" from WLED (for example, by turning hyperion off through its home assistant integration), the lights controlled by WLED are always turned off.

Because of this, I run into an issue when using hyperion and wled with scenes in home assistant: When the scene turns off hyperion but turns on the wled light, the wled light often still turns off anyways.

Describe the solution you'd like An option for WLED to resume its previous state after hyperion disconnects.

Describe alternatives you've considered I set up a workaround with an automation that turns on the WLED led strip when it detects hyperion being turned off.

Additional context I use a NodeMCU with WLED flashed to it, with home assistant and hyperion both running together in a VM.

ProtoxiDe22 commented 3 years ago

Dropping in to follow and say that i'm looking for this too :) In the meantime would you share the details of your workaround?

BeMacized commented 3 years ago

@ProtoxiDe22 It's an automation in node-red that gets triggered by home assistant when a. hyperion is turned off, and b. the wled strip is still on while it happens. When this occurs, it tells home assistant to turn on the wled strip (even though it is already on). There is just enough delay for this to happen right after wled turns off the ledstrip, so that it turns back on. I'd imagine you wouldn't need node-red for it though, you could probably do the same with a home assistant automation alone.

Aircoookie commented 3 years ago

Interesting, thank you for the report!

WLED should not turn off after the Hyperion.ng stream ends, instead it should resume whatever state it had before the stream started. Could it be that there is an automation configured that turns WLED off before or during receiving data from Hyperion?

ProtoxiDe22 commented 3 years ago

@Aircoookie it happened to me with a brand new installation of both wled and hyperion, no special settings. I haven't checked that explicitly, but it might be that hyperion purposedly turns off the led strip? After i close hyperion on the computer, wled reports the status as "off"

Aircoookie commented 3 years ago

You can try disabling the three checkmarks Receive Brightness, Color, and Effects in the WLED broadcast section of Sync settings and rebooting. Perhaps WLED mistakes some hyperion data packet for a WLED sync packet and turns itself off. Shouldn't be possible as it is a different network port, but it is the only reasonable hunch I have about this behavior... What device config are you using in Hyperion.ng by the way? UDP 0 on port 19446?

ProtoxiDe22 commented 3 years ago

Will do some testing tomorrow and report back, I'm just using the default hyperion config when you select wled as the led hardware

Aircoookie commented 3 years ago

Just looked into it and they indeed have a device type for WLED now which even interfaces with my JSON API and has the ability to turn off the device: https://github.com/hyperion-project/hyperion.ng/blob/efc2046ab561a04cfff703b54cbb67ee15c6ed21/libsrc/leddevice/dev_net/LedDeviceWled.cpp#L148

Seems like the HyperionNG devs thought it would be a good idea to turn WLED off when turning the stream off (which it can be depending on your use case!), but maybe you need to open an issue with the HyperionNG repo as it is nothing I can change on the WLED side :) Maybe the power behavior is user configurable somehow.

ProtoxiDe22 commented 3 years ago

As suspected, it is hyperion that implemented something that explicitly turns off the led strip when the program is shut down, it's easy to verify this because if you terminate hyperion forcedly (eg via task manager) this behaviour does not occur, unfortunately this does not seems configurable on hyperion side. Nonetheless, this is a problem (feature?) on the hyperion side of things, not WLED's

EDIT: i submitted this to hyperion-project/hyperion.ng#1095

ProtoxiDe22 commented 3 years ago

However, i'm also noticing that if WLED was off when hyperion is opened, and then you close hyperion this way, WLED will now be on, and won't remeber it's previous state. i don't know if that's really possible (you would need to be able to know that WLED was turned on by Hyperion), but maybe it would be nice to add this?

Aircoookie commented 3 years ago

That is to be expected, since the Hyperion code will likely also set an on command when beginning the stream, thus changing the WLED state to on. That could be quite handy if you don't want to turn on WLED manually (It will only turn on automatically if the Force max bri option in sync settings is enabled) but it should do it in a way that restores the previous state after the stream is finished.

Pseudocode for that would be:

bool wledPreviousState = device.getOnOffState(); //"on" from json/state
if (!wledPreviousState) device.turnOn();
stream();
[...]
endStream();
if (!wledPreviousState) device.turnOff();

The best option would really be to have a config option to be able to choose between this behavior, the current "turn on always, turn off always" behavior, and not using the WLED API at all (although you could probably achieve that by configuring the device with the "traditional" UDP type 0 protocol on port 19446)

ProtoxiDe22 commented 3 years ago

Thanks, i hoped this could be also done from WLED side, but as i suspected, it's not really possible, sice WLED doesn't know the "reason" it was turned on in the first place, in this case i'll add this suggestion to the Hyperion issue

edit: nevermind, you already did that, thanks!

BeMacized commented 3 years ago

Thank you both for figuring it out so quickly! I can indeed confirm that this only happens when using the wled device config in hyperion. Having switched it to udpraw instead, it behaves the way I would expect it to, restoring the state after hyperion is turned off.

Lord-Grey commented 3 years ago

@Aircoookie Thanks for bringing this up. The hyperion LED-device protocol already considers storing & restoring state in general, but it has not been implemented for WLED yet.

This is a good opportunity to tackle it now and the behaviour will be configurable as for other devices.

As a start, I am storing the full "state json" before hyperion start streaming (WLED is on displaying a color):

2020-11-17T10:15:22.407 hyperiond LEDDEVICE    : <DEBUG> LedDeviceWled.cpp:198:storeState() | state: [{"bri":128,"ccnf":{"max":5,"min":1,"time":12},"lor":0,"mainseg":0,"nl":{"dur":60,"fade":true,"mode":1,"on":false,"tbri":0},"on":true,"pl":-1,"ps":-1,"pss":1,"seg":[{"bri":255,"col":[[3,108,255],[0,0,0],[0,0,0]],"fx":0,"grp":1,"id":0,"ix":128,"len":24,"mi":false,"on":true,"pal":0,"rev":false,"sel":true,"spc":0,"start":0,"stop":24,"sx":128}],"transition":7,"udpn":{"recv":true,"send":false}}]

when restoring the state is required after streaming, I was under the assumption that I could just "play-back" the state json and WLED acts on it.... I perfectly get an API-Success, but WLED does not react on the command.

2020-11-17T10:15:24.025 hyperiond LEDDEVICE    : <DEBUG> ProviderRestApi.cpp:150:put() | PUT: [http://wled-00581d/json/state] [{"bri":128,"ccnf":{"max":5,"min":1,"time":12},"lor":0,"mainseg":0,"nl":{"dur":60,"fade":true,"mode":1,"on":false,"tbri":0},"on":true,"pl":-1,"ps":-1,"pss":1,"seg":[{"bri":255,"col":[[3,108,255],[0,0,0],[0,0,0]],"fx":0,"grp":1,"id":0,"ix":128,"len":24,"mi":false,"on":true,"pal":0,"rev":false,"sel":true,"spc":0,"start":0,"stop":24,"sx":128}],"transition":7,"udpn":{"recv":true,"send":false}}]
2020-11-17T10:15:24.150 hyperiond LEDDEVICE    : <DEBUG> ProviderRestApi.cpp:179:getResponse() | Reply.httpStatusCode [200]

plus WLED stays in "WLED is receiving live hyperion data" mode, even if streaming stopped. This might be due to the fact I set "Timeout: 65000 ms" to overcome that WLED turn orange in scenes without updates for a longer period (we discussed this "issue" before).

Further testing showed, that when I sent a "pure"power-off before restoring the state, it perfectly works. If I sent a "pure" power-on before the command it does not.

It looks like that when sending a power-on state, WLED ignores the update as it is already on.

Is there any other command that would tell WLED to disable realtime streaming immediatly and I can restore the overall state successfully? In case you store the overall state before streaming, I am happy to send selected commands only, if that helps. (Just playing-back the json looked appealing).

Thank you!

Ping me, if you have further questions.

Aircoookie commented 3 years ago

@Lord-Grey thank you for looking into that!

I believe I have found out what is going on here. You are getting and setting the state absolutely correctly, there is nothing wrong with that. The problem is that WLED does not consider receiving live data as a change of state. Thus the state stays the same the entire time and when you apply it after ending the stream, it has no effect.

On the other hand, if you send a power off, the state will be changed and WLED will exit realtime mode, even with the indefinite timeout set by 65000. Alternatively, changing the color or effect would also work.

What we are really lacking is a way to exit realtime mode via the API without changing the state. The reason that is not yet possible is based on two (now proven incorrect) assumptions by me:

What we'd really need is a command like {"live":false} to exit realtime mode without a state change. I could easily add that, but of course it wouldn't work in a backwards compatible way. Another great option would be adding an override for the timeout setting to the API as 65000 is not practical for most other UDP sources (E1.31, Artnet, ...) anyways and setting it to 0 would exit realtime mode immediately and reset the timeout to the default set in settings. What do you think?

One way to achieve the desired result with all WLED versions would most likely be changing the state slightly (e.g. incresing the brightness by one), waiting a second, then setting it back to the original value. Quite the hack :(

I do have the lor (live override) property, which when set to 1, essentially ignores the received UDP data and displays the WLED state instead. Because the realtime mode never properly exits though until the state is changed, this will prevent other streaming sources from working. For Hyperion to work again when the stream is next started, you'd just need to send {"lor":0} again.

That's all quite involved though and has pitfalls like that it will get stuck on the last frame when connection is lost... The easiest way by far would be not using the API at all and instead sending an UDP refresh once a second - even a 3-byte payload to set the first LED is enough to prevent a timeout. But I suspect that's not easily possible from the LED-device protocol. (maybe an feature request for a property minimum_device_refresh_rate, but I disgress). I'm not a Hyperion user myself, but I would suspect this timeout is only really an issue with the equivalent of the WLED Solid mode, setting a static color. Maybe the color could be set via the API instead, thus not relying on realtime mode for this edge case. Certainly not a trivial issue.

Thank you for reading and sorry for the long reply!

ProtoxiDe22 commented 3 years ago

I'll chime in and say that even if it's not backwards compatible, adding an api command to disable realtime mode seems like a good idea, and if becomes too much of an hack to disable it otherwise, i think it would be ok to have this feature just from now on

Lord-Grey commented 3 years ago

@Aircoookie Thanks for you elaborate answer exploring the different options. In the meantime, I played certain scenarios through and design wise, I would agree that a "stream stop/-start" command would be the cleanest design, as it clearly differentiates between streaming state and power state. Similar I do in hyperion where devices can is enabled/disabled (similar to WLED power on/off) and where I start/stop updating the output of an LED device (the later might result in switching on/off devices or simulating on/off state writing "black" depending on the device type). My assumption is that a user might want to disable a device in hyperion and make use of it for something else (e.g. effects via APP, etc.) and later enable hyperion again for streaming. With the latest LED device protocol this is possible.

Given that WLED is an "active" device type, like Hue, Nanoleaf, Yeelight, Cololight, etc., the best way from my opinion is to "open and close streaming" as an alternative way (API) to the User's interaction via the App/Web page... This will allow further automation scenarios, too. In addition, it will make the timeout workaround scenario obsolete for hyperion.

In response to your options:

I do have the lor (live override) property

Using "lor" works, but consequently I would need to send a lor=1 when restoring/closing streaming, as well as lor=0 when opening the stream. This might override a manual set lor and the whole lor idea gets absurd.

The easiest way by far would be not using the API at all and instead sending an UDP refresh once a second - even a 3-byte payload to set the first LED is enough to prevent a timeout. But I suspect that's not easily possible from the LED-device protocol.

That would be easily possible using a rewrite time > 0 in hyperion. Then the last update would be refreshed. While this is the easiest option, it is the worst design from my perspective. It will

  1. waste resources unnecessarily,
  2. force the user to do a manual override to use WLED in interim
  3. confuse the users, as there will be effects happening which they do not control

On the other hand, if you send a power off, the state will be changed and WLED will exit real-time mode,

Sending power-off before restoring works. Nevertheless, to be on the save side I would need also do power-off before powering on and start streaming to have WLED in a stable state.

Given the lor capability and that WLED does treat streaming as a change in state, there would be multiple commands required during start and stop streaming just to get the expected outcome. I might implement such workarounds for downward comparability (if WLED version < x), if mandatory required, but would like to opt for a new command (mirroring @ProtoxiDe22 feedback).

Hoping the above provide a good rationale and outlines well the motivation.

PS: Seems I managed to do a long reply, too :-)

Aircoookie commented 3 years ago

Thank you very much @Lord-Grey ! I do agree with all your points, especially with that lor is meant for overriding the stream in WLED. I was willing to make an exception, but it is best if it is kept to its intended purpose, let users override any live source.

I've added a new property, live to the JSON API in version 2011260 (will be in 0.11.0 release). This is not part of the state response, so it is set-only. No need to retrieve the state before starting the stream. Sending a {"live":true} POST will enter realtime mode and blank the LEDs. The realtime timeout option does not have an effect when this command is used, WLED will stay in realtime mode until the state is changed. It is expected that a {"live":false} is sent once the stream is terminated.

Hope you find this useful, do not worry too much about compatibility, the {"on":false} will be sufficient for versions < 2011260. However, you'd need to fetch the info object to get the version ID, which complicates the logic a bit again. It's up to you :)

Lord-Grey commented 3 years ago

@Aircoookie Great that you are in heavy agreement and thanks for making the change on short notice!

Is there any interim master binary available for download that I could test the change before release? I am looking for a WLED_0.x.x_ESP8266.bin to install on a NodeMCU....

ProtoxiDe22 commented 3 years ago

@Lord-Grey here, i compiled one for you, was built on commit ac010cd, tested uploading it on a nodemcu v2 and it works properly. only thing, the security and update page shows build 2011230 and not 2011260 but i'm pretty sure who bumped the version forgot to do so in wled.h (in fact in commit adbeb4a the build number gets bumped in changelog.md but not in wled.h)

d1_mini.zip

Lord-Grey commented 3 years ago

@ProtoxiDe22 Thank you! I will give it a try over the weekend.

Lord-Grey commented 3 years ago

@Aircoookie / @ProtoxiDe22 Thanks for providing the test binary. Looks like it works!

danyboy666 commented 3 years ago

According to this https://github.com/hyperion-project/hyperion.ng/pull/1204 the issue should be fixed but i'm experiencing this exact issue with build #2103300.

My infos:

Wled build 2103300

Home Assistant 2021.3.4 with latest hyperion integration

Hyperion Ambilight Deamon Version : 2.0.0-alpha.9 (master (Paulchen-Panther-32764375/dc0e953c-1616852804)) Build Time: Mar 31 2021 15:06:39

 2021-03-31T19:34:57.029Z [hyperiond SMOOTHING] (DEBUG) (LinearColorSmoothing.cpp:701:selectConfig()) cfg [0]:  Type: linear - Time: 200 ms, outputRate 25.000000 Hz, interpolationRate: 25.000000 Hz, timer: 40 ms, Dithering: 0, Decay: 1.000000 -> HalfTime: 100.000000 ms
 2021-03-31T19:34:57.029Z [hyperiond HYPERION] (DEBUG) (Hyperion.cpp:559:handlePriorityChangedLedDevice()) priority[255], previousPriority[128]
 2021-03-31T19:34:57.030Z [hyperiond HYPERION] (DEBUG) (Hyperion.cpp:562:handlePriorityChangedLedDevice()) No source left -> switch LED-Device off
 2021-03-31T19:34:57.031Z [hyperiond EFFECTENGINE] (INFO) Run effect "Cinema dim lights" on channel 128
 2021-03-31T19:34:57.031Z [hyperiond EFFECTENGINE] (DEBUG) (EffectEngine.cpp:181:runEffectScript()) Start the effect: name [Cinema dim lights], smoothCfg [2]
 ...
left -> switch LED-Device off
 2021-03-31T19:35:50.500Z [hyperiond EFFECTENGINE] (INFO) Run effect "Cinema dim lights" on channel 128
 2021-03-31T19:35:50.500Z [hyperiond EFFECTENGINE] (DEBUG) (EffectEngine.cpp:181:runEffectScript()) Start the effect: name [Cinema dim lights], smoothCfg [2]
 2021-03-31T19:35:50.501Z [hyperiond HYPERION] (DEBUG) (PriorityMuxer.cpp:163:registerInput()) Register new input 'Home Assistant@::ffff:192.168.121.195/EFFECT' with priority 128 as inactive
 2021-03-31T19:35:50.541Z [hyperiond HYPERION] (DEBUG) (PriorityMuxer.cpp:209:setInput()) Priority 128 is now active
 2021-03-31T19:35:50.542Z [hyperiond HYPERION] (DEBUG) (PriorityMuxer.cpp:353:setCurrentTime()) Set visible priority to 128
 2021-03-31T19:35:50.542Z [hyperiond SMOOTHING] (DEBUG) (LinearColorSmoothing.cpp:701:selectConfig()) cfg [2]:  Type: linear - Time: 200 ms, outputRate 25.000000 Hz, interpolationRate: 25.000000 Hz, timer: 40 ms, Dithering: 0, Decay: 1.000000 -> HalfTime: 100.000000 ms

But remains active in the status source list of hyperion.

edit 1000: Nevermind I think it's fixed. There is a new option when you setup wled device

Restore lights' original state when disabled

blazoncek commented 2 years ago

Due to no updates on this issue I deem it is fixed. Closing.