Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle supported 'state' changes via FC03 for supported Philips Hue lights #7950

Open
wants to merge 14 commits into
base: master
Choose a base branch
from

Conversation

hanskroner
Copy link
Contributor

@hanskroner hanskroner commented Sep 29, 2024

The Philips Hue Bridge uses the manufacturer-specific 0xfc03 cluster to control lights that support the cluster. The 0x00 command of this cluster provides a mechanism for changing multiple state properties of a light with a single command. This PR changes the way the REST API processes PUTs that would change selected 'state' keys of these lights to mimic the Hue Bridge's behavior.

The 0x00 command begins with a 2-byte encoding of the command's payload. This PR addresses only some of the state changes supported by the command. Using the nomenclature of the CLIP v2 API, they are:

| 15 | 14 | 13 | 12 | 11 | 10 | 09 | 08 | 07 | 06 | 05 | 04 | 03 | 02 | 01 | 00 |
| -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- |
   0    0    0    0    0    0    0    0    1    0    1    1    1    1    1    1
                                           -         -    -    -    -    -    -
                                           ^         ^    ^    ^    ^    ^    ^
                                           |         |    |    |    |    |    |
                                           |         |    |    |    |    |    +--- [on on]
                                           |         |    |    |    |    +-------- [dimming brightness]
                                           |         |    |    |    +------------- [color_temperature mirek]
                                           |         |    |    +------------------ [color xy]
                                           |         |    +----------------------- [dynamics duration]
                                           |         +---------------------------- [effects]
                                           |      
                                           +-------------------------------------- [timed_effects duration]

The PR avoids introducing additional complexity to setLightState() by processing only supported state changes of supported lights separately, in setHueLightState(). The functionality introduced in the this PR also overlaps with functionality that addTaskHueEffect() provides today - this could be removed at a later stage.

Bit 6 of the 0x00 command is very likely gradient, judging by the structure of the command used by addTaskHueGradient() - I unfortunately don't have a gradient light to confirm this.

Copy link
Collaborator

@ebaauw ebaauw left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this. Not too happy about the reformatting (makes it difficult to identify the real changes), but the logic looking very good to me.

bool addTaskHueEffect(TaskItem &task, QString &effect);
bool validateHueGradient(const ApiRequest &req, ApiResponse &rsp, QVariantMap &gradient, quint16 styleBitmap);
bool addTaskHueGradient(TaskItem &task, QVariantMap &gradient);
bool addTaskHueManufacturerSpecific(TaskItem &task, HueManufacturerSpecificPayloads &payloadItems, QVariantMap &items);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this routine going to be invoked outside hue.cpp? If not, better define it locally in hue.cpp rather than including it in DeRestPluginPrivate. In that case, also define HueManufacturerSpecificPayload locally.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I decided on having it here cause I noticed a couple of places in rest_lights.cpp that call out to addTaskHueEffect() and figured addTaskHueManufacturerSpecific() would eventually succeed them.

@@ -1432,9 +1449,13 @@ public Q_SLOTS:
// Advanced features of Hue lights.
QStringList getHueEffectNames(quint64 effectBitmap, bool colorloop);
QStringList getHueGradientStyleNames(quint16 styleBitmap);
bool isHueEffectLight(const LightNode *lightNode);
bool isMapableToManufacturerSpecific(const QVariantMap &map);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this, making sure we don't break anything. I would like to go through all non-mappable items and see if we really cannot handle them from setHueLightState(). I don't care about hue and sat, but bri_inc and ct_inc would be nice (and I kind of expect the Hue bridge to handle these though FC03). And ontime to trigger an equivalent of On with Timed Off.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bri_inc and ct_inc are now dimming_delta and color_temperature_delta in the v2 API. The Hue Bridge sends out Step and ColorTemperatureStep commands for them - it doesn't seem to use 0xfc03 (yet?). I can't find any documentation for ontime, but including it as an additional key in the v2 API commands doesn't actually do anything.

@@ -1432,9 +1449,13 @@ public Q_SLOTS:
// Advanced features of Hue lights.
QStringList getHueEffectNames(quint64 effectBitmap, bool colorloop);
QStringList getHueGradientStyleNames(quint16 styleBitmap);
bool isHueEffectLight(const LightNode *lightNode);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's probably some more places in rest_light.cpp where this could be leveraged.

hue.cpp Outdated Show resolved Hide resolved
\return true - on success
false - on error
*/
bool DeRestPluginPrivate::addTaskHueManufacturerSpecific(TaskItem &task, HueManufacturerSpecificPayloads &payloadItems, QVariantMap &items)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addTaskHueEffect() could probably be refactored simply to call addTaskHueManufacturerSpecific().

hue.cpp Outdated Show resolved Hide resolved
@hanskroner
Copy link
Contributor Author

I like this. Not too happy about the reformatting (makes it difficult to identify the real changes), but the logic looking very good to me.

Thanks. Noticed the formatting a little too late - but I've corrected that now to make the changes clear.

@hanskroner hanskroner marked this pull request as ready for review October 7, 2024 11:52
@manup
Copy link
Member

manup commented Oct 7, 2024

Hi thanks for the PR. @hanskroner @ebaauw is this ready to be merged or wait for next beta?

@hanskroner
Copy link
Contributor Author

I've been running it for a few days on a small test network (3 supported lights) with no surprises. I'm unsure if @ebaauw has had the chance to test this, but if I'm the only one so far it might be more sensible to wait for the next beta.

@ebaauw
Copy link
Collaborator

ebaauw commented Oct 7, 2024

Not yet.

@hanskroner hanskroner force-pushed the hue-light-state branch 3 times, most recently from 1e7a854 to dac50b2 Compare October 11, 2024 17:18
'setLightState()' calls out to this new helper method when dealing
with a Hue light that supports effects with a request that contains
only items that can be handled by the manufacturer-specific command.

The new method will eventually build a '0x00' command from the
'0xfc03' cluster to control the light, mimicking what the Hue Bridge.
The new method will be responsible for building a '0x00' command from the '0xfc03' cluster and queuing a task for it to be sent out the radio.

The method signature is expected to change to allow passing payload items and their content descriptor easily.
Use a quint16 enum, wrapped in QFlags, to track which items are
present in the payload of a ‘’0xfc03’ 0x00’ command.
For Philips Hue lights that support the ‘0xfc03’ cluster, handle changes
to their “on” state using the manufacturer-specific cluster.
@Thomas-Vos
Copy link
Contributor

The Philips Hue API now also allows customising effect speed/colour for all the effects. When this is supported in deCONZ, please reuse existing effectSpeed and effectColours attributes in the deCONZ API if possible (see xmas.cpp).

@hanskroner
Copy link
Contributor Author

hanskroner commented Oct 17, 2024

For some effects (e.g., prism, sunrise, and sunset) customizing the color doesn't seem to be possible. For the other effects, including the xy key along with the effect key in the request will already change the effect's color with this PR - as long as the light in question has firmware (at least) v1.222.2.

Modifying an effect's speed also requires firmware (at least) v1.222.2. The CLIP v2 API exposes it through dynamics/speed and including the effect_duration key in the request will already change the effect's speed with this PR as it uses the same byte in the FC03 command. Modifying the effect's speed this way through the API is awkward, because effect_duration's value is duration is in tenths of a second. I was thinking I'd introduce effect_speed which would take a float value between 0 and 1 to match what the CLIP v2 API does.

Using snake case for the key names seems more consistent with both the deCONZ REST and the CLIP v2 APIs to me.

@ebaauw
Copy link
Collaborator

ebaauw commented Oct 17, 2024

What happens after you turn the effect off? Does the light remain on the effect colour, or does it revert to the previous colour from before the effect? In the latter case, we don’t need effectColors, just effect and xy. Can you change the colour of a running effect through only specifying xy? And the speed?

Note that effectColors (plural !) was introduced for the LIDL Xmas lightstrip, which does support up to six colours to be specified for effects.

The camel case is my bad; at some time I had hoped we’d be moving to that, but since many new snake case or simple lowercase items have been introduced (even by me).

@hanskroner
Copy link
Contributor Author

What happens after you turn the effect off? Does the light remain on the effect colour, or does it revert to the previous colour from before the effect? In the latter case, we don’t need effectColors, just effect and xy.

After turning the effect off, the light will revert to the effect's color. Starting the underwater effect and then setting "effect": "none" will make the light stay in a light shade of blue. Starting the effect while passing in a purple color through xy and then turning the effect off will make the light stay in the passed-in shade of purple. The FC03 command seems to affect the light's state - not just the effect's state when that is passed. Indeed, starting an effect on a light, turning the light off, and then turning the light back on again will result in the light turning back on to the effect (and whatever color, brightness, speed, etc. attributes it had).

Can you change the colour of a running effect through only specifying xy? And the speed?

It doesn't seem to be possible, no. Passing in only xy while a light is running an effect will stop the effect and set the light to the passed-in color. Passing both effect and xy will cause the effect to (visibly) restart with the new color.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants