zigpy / zha-device-handlers

ZHA device handlers bridge the functionality gap created when manufacturers deviate from the ZCL specification, handling deviations and exceptions by parsing custom messages to and from Zigbee devices.
Apache License 2.0
735 stars 674 forks source link

[Device Support Request] Hue bulbs missing Candle Effect #2517

Open phbasler opened 1 year ago

phbasler commented 1 year ago

Problem description

I have two Hue Go here which have a built in canlde effect. According to https://github.com/Koenkk/zigbee2mqtt/issues/15699 it is possible to start this by sending certain commands to the cluster 0xfc03.

This cluster supports the Canlde effect, the fireplace effect and also the color loop. On my device page for the light I can only see the color loop effect.

If I go to manage zigbee device from the device page, then I don't have this cluster, only 0xfc01 in that range. Is there a possibility to add these effects to zha so that we can call them from Home Assistant?

Solution description

Some possibility to trigger the candle effect of the hue go through a script.

Screenshots/Video

Screenshots/Video ![image](https://github.com/zigpy/zha-device-handlers/assets/28863303/d2c3cf8c-40ac-43c3-931a-9f5c70e2f5f0)

Device signature

Device signature ```json { "node_descriptor": "NodeDescriptor(logical_type=, complex_descriptor_available=0, user_descriptor_available=0, reserved=0, aps_flags=0, frequency_band=, mac_capability_flags=, manufacturer_code=4107, maximum_buffer_size=82, maximum_incoming_transfer_size=128, server_mask=11264, maximum_outgoing_transfer_size=128, descriptor_capability_field=, *allocate_address=True, *is_alternate_pan_coordinator=False, *is_coordinator=False, *is_end_device=False, *is_full_function_device=True, *is_mains_powered=True, *is_receiver_on_when_idle=True, *is_router=True, *is_security_capable=False)", "endpoints": { "11": { "profile_id": "0x0104", "device_type": "0x010d", "input_clusters": [ "0x0000", "0x0003", "0x0004", "0x0005", "0x0006", "0x0008", "0x0300", "0x1000", "0xfc01" ], "output_clusters": [ "0x0019" ] }, "242": { "profile_id": "0xa1e0", "device_type": "0x0061", "input_clusters": [], "output_clusters": [ "0x0021" ] } }, "manufacturer": "Philips", "model": "LCT026", "class": "zigpy.device.Device" } ```

Diagnostic information

Diagnostic information ```json { "home_assistant": { "installation_type": "Home Assistant Container", "version": "2023.8.1", "dev": false, "hassio": false, "virtualenv": false, "python_version": "3.11.4", "docker": true, "arch": "aarch64", "timezone": "Europe/Berlin", "os_name": "Linux", "os_version": "6.1.21-v8+", "run_as_root": true }, "custom_components": { "cryptoinfo": { "version": "0.1.7", "requirements": [] }, "hacs": { "version": "1.32.1", "requirements": [ "aiogithubapi>=22.10.1" ] }, "thermal_comfort": { "version": "2.1.1", "requirements": [] }, "xiaomi_miio_airpurifier": { "version": "2023.6.0.0", "requirements": [ "construct==2.10.56", "python-miio>=0.5.12" ] }, "blitzortung": { "version": "1.0.1", "requirements": [ "paho-mqtt>=1.5.0" ] }, "babybuddy": { "version": "v0.0.0", "requirements": [] }, "philips_airpurifier_coap": { "version": "0.10.8", "requirements": [ "aioairctrl==0.2.4" ] }, "ytube_music_player": { "version": "20230321.01", "requirements": [ "ytmusicapi==0.25.0", "pytube==10.5.1", "integrationhelper==0.2.2" ] }, "powercalc": { "version": "v1.8.8", "requirements": [ "numpy>=1.21.1" ] } }, "integration_manifest": { "domain": "zha", "name": "Zigbee Home Automation", "after_dependencies": [ "onboarding", "usb" ], "codeowners": [ "@dmulcahey", "@adminiuga", "@puddly" ], "config_flow": true, "dependencies": [ "file_upload" ], "documentation": "https://www.home-assistant.io/integrations/zha", "iot_class": "local_polling", "loggers": [ "aiosqlite", "bellows", "crccheck", "pure_pcapy3", "zhaquirks", "zigpy", "zigpy_deconz", "zigpy_xbee", "zigpy_zigate", "zigpy_znp" ], "requirements": [ "bellows==0.35.8", "pyserial==3.5", "pyserial-asyncio==0.6", "zha-quirks==0.0.102", "zigpy-deconz==0.21.0", "zigpy==0.56.4", "zigpy-xbee==0.18.1", "zigpy-zigate==0.11.0", "zigpy-znp==0.11.4" ], "usb": [ { "vid": "10C4", "pid": "EA60", "description": "*2652*", "known_devices": [ "slae.sh cc2652rb stick" ] }, { "vid": "1A86", "pid": "55D4", "description": "*sonoff*plus*", "known_devices": [ "sonoff zigbee dongle plus v2" ] }, { "vid": "10C4", "pid": "EA60", "description": "*sonoff*plus*", "known_devices": [ "sonoff zigbee dongle plus" ] }, { "vid": "10C4", "pid": "EA60", "description": "*tubeszb*", "known_devices": [ "TubesZB Coordinator" ] }, { "vid": "1A86", "pid": "7523", "description": "*tubeszb*", "known_devices": [ "TubesZB Coordinator" ] }, { "vid": "1A86", "pid": "7523", "description": "*zigstar*", "known_devices": [ "ZigStar Coordinators" ] }, { "vid": "1CF1", "pid": "0030", "description": "*conbee*", "known_devices": [ "Conbee II" ] }, { "vid": "10C4", "pid": "8A2A", "description": "*zigbee*", "known_devices": [ "Nortek HUSBZB-1" ] }, { "vid": "0403", "pid": "6015", "description": "*zigate*", "known_devices": [ "ZiGate+" ] }, { "vid": "10C4", "pid": "EA60", "description": "*zigate*", "known_devices": [ "ZiGate" ] }, { "vid": "10C4", "pid": "8B34", "description": "*bv 2010/10*", "known_devices": [ "Bitron Video AV2010/10" ] } ], "zeroconf": [ { "type": "_esphomelib._tcp.local.", "name": "tube*" }, { "type": "_zigate-zigbee-gateway._tcp.local.", "name": "*zigate*" }, { "type": "_zigstar_gw._tcp.local.", "name": "*zigstar*" }, { "type": "_slzb-06._tcp.local.", "name": "slzb-06*" } ], "is_built_in": true }, "data": { "ieee": "**REDACTED**", "nwk": 14994, "manufacturer": "Philips", "model": "LCT026", "name": "Philips LCT026", "quirk_applied": false, "quirk_class": "zigpy.device.Device", "manufacturer_code": 4107, "power_source": "Mains", "lqi": 136, "rssi": -66, "last_seen": "2023-08-09T10:44:37", "available": true, "device_type": "Router", "signature": { "node_descriptor": "NodeDescriptor(logical_type=, complex_descriptor_available=0, user_descriptor_available=0, reserved=0, aps_flags=0, frequency_band=, mac_capability_flags=, manufacturer_code=4107, maximum_buffer_size=82, maximum_incoming_transfer_size=128, server_mask=11264, maximum_outgoing_transfer_size=128, descriptor_capability_field=, *allocate_address=True, *is_alternate_pan_coordinator=False, *is_coordinator=False, *is_end_device=False, *is_full_function_device=True, *is_mains_powered=True, *is_receiver_on_when_idle=True, *is_router=True, *is_security_capable=False)", "endpoints": { "11": { "profile_id": "0x0104", "device_type": "0x010d", "input_clusters": [ "0x0000", "0x0003", "0x0004", "0x0005", "0x0006", "0x0008", "0x0300", "0x1000", "0xfc01" ], "output_clusters": [ "0x0019" ] }, "242": { "profile_id": "0xa1e0", "device_type": "0x0061", "input_clusters": [], "output_clusters": [ "0x0021" ] } }, "manufacturer": "Philips", "model": "LCT026" }, "active_coordinator": false, "entities": [ { "entity_id": "button.hugo_identify", "name": "Philips LCT026" }, { "entity_id": "light.zha_hugo_huelight", "name": "Philips LCT026" }, { "entity_id": "number.hugo_start_up_current_level", "name": "Philips LCT026" }, { "entity_id": "number.hugo_start_up_color_temperature", "name": "Philips LCT026" }, { "entity_id": "select.hugo_start_up_behavior", "name": "Philips LCT026" } ], "neighbors": [ { "device_type": "Coordinator", "rx_on_when_idle": "On", "relationship": "Sibling", "extended_pan_id": "**REDACTED**", "ieee": "**REDACTED**", "nwk": "0x0000", "permit_joining": "Unknown", "depth": "0", "lqi": "140" }, { "device_type": "Router", "rx_on_when_idle": "On", "relationship": "Sibling", "extended_pan_id": "**REDACTED**", "ieee": "**REDACTED**", "nwk": "0x14DA", "permit_joining": "Unknown", "depth": "15", "lqi": "151" }, { "device_type": "Router", "rx_on_when_idle": "On", "relationship": "Sibling", "extended_pan_id": "**REDACTED**", "ieee": "**REDACTED**", "nwk": "0x2D3F", "permit_joining": "Unknown", "depth": "15", "lqi": "193" }, { "device_type": "Router", "rx_on_when_idle": "On", "relationship": "Sibling", "extended_pan_id": "**REDACTED**", "ieee": "**REDACTED**", "nwk": "0x2FAA", "permit_joining": "Unknown", "depth": "15", "lqi": "217" }, { "device_type": "Router", "rx_on_when_idle": "On", "relationship": "Sibling", "extended_pan_id": "**REDACTED**", "ieee": "**REDACTED**", "nwk": "0x3955", "permit_joining": "Unknown", "depth": "15", "lqi": "210" }, { "device_type": "Router", "rx_on_when_idle": "On", "relationship": "Sibling", "extended_pan_id": "**REDACTED**", "ieee": "**REDACTED**", "nwk": "0x52D3", "permit_joining": "Unknown", "depth": "15", "lqi": "163" }, { "device_type": "Router", "rx_on_when_idle": "On", "relationship": "Sibling", "extended_pan_id": "**REDACTED**", "ieee": "**REDACTED**", "nwk": "0x53F0", "permit_joining": "Unknown", "depth": "15", "lqi": "189" }, { "device_type": "Router", "rx_on_when_idle": "On", "relationship": "Sibling", "extended_pan_id": "**REDACTED**", "ieee": "**REDACTED**", "nwk": "0x5C1B", "permit_joining": "Unknown", "depth": "15", "lqi": "163" }, { "device_type": "Router", "rx_on_when_idle": "On", "relationship": "Sibling", "extended_pan_id": "**REDACTED**", "ieee": "**REDACTED**", "nwk": "0x5CA7", "permit_joining": "Unknown", "depth": "15", "lqi": "132" }, { "device_type": "Router", "rx_on_when_idle": "On", "relationship": "Sibling", "extended_pan_id": "**REDACTED**", "ieee": "**REDACTED**", "nwk": "0x6779", "permit_joining": "Unknown", "depth": "15", "lqi": "200" }, { "device_type": "Router", "rx_on_when_idle": "On", "relationship": "Sibling", "extended_pan_id": "**REDACTED**", "ieee": "**REDACTED**", "nwk": "0x726C", "permit_joining": "Unknown", "depth": "15", "lqi": "116" }, { "device_type": "Router", "rx_on_when_idle": "On", "relationship": "Sibling", "extended_pan_id": "**REDACTED**", "ieee": "**REDACTED**", "nwk": "0x8E7F", "permit_joining": "Unknown", "depth": "15", "lqi": "127" }, { "device_type": "Router", "rx_on_when_idle": "On", "relationship": "Sibling", "extended_pan_id": "**REDACTED**", "ieee": "**REDACTED**", "nwk": "0x9211", "permit_joining": "Unknown", "depth": "15", "lqi": "168" }, { "device_type": "Router", "rx_on_when_idle": "On", "relationship": "Sibling", "extended_pan_id": "**REDACTED**", "ieee": "**REDACTED**", "nwk": "0x9BAC", "permit_joining": "Unknown", "depth": "15", "lqi": "152" }, { "device_type": "Router", "rx_on_when_idle": "On", "relationship": "Sibling", "extended_pan_id": "**REDACTED**", "ieee": "**REDACTED**", "nwk": "0xA995", "permit_joining": "Unknown", "depth": "15", "lqi": "151" }, { "device_type": "Router", "rx_on_when_idle": "On", "relationship": "Sibling", "extended_pan_id": "**REDACTED**", "ieee": "**REDACTED**", "nwk": "0xAA40", "permit_joining": "Unknown", "depth": "15", "lqi": "125" }, { "device_type": "Router", "rx_on_when_idle": "On", "relationship": "Sibling", "extended_pan_id": "**REDACTED**", "ieee": "**REDACTED**", "nwk": "0xAE75", "permit_joining": "Unknown", "depth": "15", "lqi": "182" }, { "device_type": "Router", "rx_on_when_idle": "On", "relationship": "Sibling", "extended_pan_id": "**REDACTED**", "ieee": "**REDACTED**", "nwk": "0xCB69", "permit_joining": "Unknown", "depth": "15", "lqi": "150" }, { "device_type": "Router", "rx_on_when_idle": "On", "relationship": "Sibling", "extended_pan_id": "**REDACTED**", "ieee": "**REDACTED**", "nwk": "0xCF87", "permit_joining": "Unknown", "depth": "15", "lqi": "126" }, { "device_type": "Router", "rx_on_when_idle": "On", "relationship": "Sibling", "extended_pan_id": "**REDACTED**", "ieee": "**REDACTED**", "nwk": "0xDBF5", "permit_joining": "Unknown", "depth": "15", "lqi": "159" }, { "device_type": "Router", "rx_on_when_idle": "On", "relationship": "Sibling", "extended_pan_id": "**REDACTED**", "ieee": "**REDACTED**", "nwk": "0xDF9A", "permit_joining": "Unknown", "depth": "15", "lqi": "189" }, { "device_type": "Router", "rx_on_when_idle": "On", "relationship": "Sibling", "extended_pan_id": "**REDACTED**", "ieee": "**REDACTED**", "nwk": "0xE2A4", "permit_joining": "Unknown", "depth": "15", "lqi": "179" }, { "device_type": "EndDevice", "rx_on_when_idle": "Off", "relationship": "Child", "extended_pan_id": "**REDACTED**", "ieee": "**REDACTED**", "nwk": "0x8E35", "permit_joining": "NotAccepting", "depth": "2", "lqi": "255" }, { "device_type": "EndDevice", "rx_on_when_idle": "Off", "relationship": "Child", "extended_pan_id": "**REDACTED**", "ieee": "**REDACTED**", "nwk": "0x60BA", "permit_joining": "NotAccepting", "depth": "2", "lqi": "213" }, { "device_type": "EndDevice", "rx_on_when_idle": "Off", "relationship": "Child", "extended_pan_id": "**REDACTED**", "ieee": "**REDACTED**", "nwk": "0x8A93", "permit_joining": "NotAccepting", "depth": "2", "lqi": "130" }, { "device_type": "EndDevice", "rx_on_when_idle": "Off", "relationship": "Child", "extended_pan_id": "**REDACTED**", "ieee": "**REDACTED**", "nwk": "0xBBCC", "permit_joining": "NotAccepting", "depth": "2", "lqi": "214" }, { "device_type": "EndDevice", "rx_on_when_idle": "Off", "relationship": "Child", "extended_pan_id": "**REDACTED**", "ieee": "**REDACTED**", "nwk": "0x9978", "permit_joining": "NotAccepting", "depth": "2", "lqi": "0" }, { "device_type": "EndDevice", "rx_on_when_idle": "Off", "relationship": "Child", "extended_pan_id": "**REDACTED**", "ieee": "**REDACTED**", "nwk": "0x4C93", "permit_joining": "NotAccepting", "depth": "2", "lqi": "193" }, { "device_type": "EndDevice", "rx_on_when_idle": "Off", "relationship": "Child", "extended_pan_id": "**REDACTED**", "ieee": "**REDACTED**", "nwk": "0xF0C3", "permit_joining": "NotAccepting", "depth": "2", "lqi": "253" } ], "routes": [ { "dest_nwk": "0x0000", "route_status": "Active", "memory_constrained": true, "many_to_one": true, "route_record_required": false, "next_hop": "0x0000" } ], "endpoint_names": [ { "name": "EXTENDED_COLOR_LIGHT" }, { "name": "PROXY_BASIC" } ], "user_given_name": "Hugo", "device_reg_id": "4a44e44704bc9a20dca93adc7ede69d3", "area_id": "bad", "cluster_details": { "11": { "device_type": { "name": "EXTENDED_COLOR_LIGHT", "id": 269 }, "profile_id": 260, "in_clusters": { "0x0000": { "endpoint_attribute": "basic", "attributes": { "0x0004": { "attribute_name": "manufacturer", "value": "Philips" }, "0x0005": { "attribute_name": "model", "value": "LCT026" } }, "unsupported_attributes": {} }, "0x0003": { "endpoint_attribute": "identify", "attributes": {}, "unsupported_attributes": {} }, "0x0004": { "endpoint_attribute": "groups", "attributes": {}, "unsupported_attributes": {} }, "0x0005": { "endpoint_attribute": "scenes", "attributes": { "0x0001": { "attribute_name": "current_scene", "value": 0 }, "0x0004": { "attribute_name": "name_support", "value": 0 } }, "unsupported_attributes": { "0xfffe": { "attribute_name": "reporting_status" } } }, "0x0006": { "endpoint_attribute": "on_off", "attributes": { "0x0000": { "attribute_name": "on_off", "value": 0 }, "0x4003": { "attribute_name": "start_up_on_off", "value": 1 } }, "unsupported_attributes": {} }, "0x0008": { "endpoint_attribute": "level", "attributes": { "0x0000": { "attribute_name": "current_level", "value": 67 }, "0x4000": { "attribute_name": "start_up_current_level", "value": 254 } }, "unsupported_attributes": { "0x0014": { "attribute_name": "default_move_rate" }, "0x0010": { "attribute_name": "on_off_transition_time" }, "0x0011": { "attribute_name": "on_level" }, "0x0012": { "attribute_name": "on_transition_time" }, "0x0013": { "attribute_name": "off_transition_time" } } }, "0x1000": { "endpoint_attribute": "lightlink", "attributes": {}, "unsupported_attributes": {} }, "0x0300": { "endpoint_attribute": "light_color", "attributes": { "0x400a": { "attribute_name": "color_capabilities", "value": 31 }, "0x4002": { "attribute_name": "color_loop_active", "value": 0 }, "0x0008": { "attribute_name": "color_mode", "value": 1 }, "0x400c": { "attribute_name": "color_temp_physical_max", "value": 500 }, "0x400b": { "attribute_name": "color_temp_physical_min", "value": 153 }, "0x0007": { "attribute_name": "color_temperature", "value": 500 }, "0x0000": { "attribute_name": "current_hue", "value": 21 }, "0x0001": { "attribute_name": "current_saturation", "value": 233 }, "0x0003": { "attribute_name": "current_x", "value": 36958 }, "0x0004": { "attribute_name": "current_y", "value": 25586 }, "0x4000": { "attribute_name": "enhanced_current_hue", "value": 4044 }, "0x000f": { "attribute_name": "options", "value": 0 }, "0x4010": { "attribute_name": "start_up_color_temperature", "value": 366 } }, "unsupported_attributes": {} }, "0xfc01": { "endpoint_attribute": "manufacturer_specific", "attributes": {}, "unsupported_attributes": {} } }, "out_clusters": { "0x0019": { "endpoint_attribute": "ota", "attributes": {}, "unsupported_attributes": {} } } }, "242": { "device_type": { "name": "PROXY_BASIC", "id": 97 }, "profile_id": 41440, "in_clusters": {}, "out_clusters": { "0x0021": { "endpoint_attribute": "green_power", "attributes": {}, "unsupported_attributes": {} } } } } } } ```

Logs

Logs ```python [Paste the logs here] ```

Custom quirk

Custom quirk ```python [Paste your custom quirk here] ```

Additional information

No response

nilsreiter commented 9 months ago

I managed to activatee the candle effect using the ZCL command documented on here, on the following bulbs:

The following light does not seem to support the effect (tested with Hue app):

I could give it a go to develop a quirk, but it would be helpful if someone could point me to one (for other things) that also add an effect.

(the firmware versions are actually also documented here ... )

BenJamesAndo commented 8 months ago

Perhaps these issues would contain code that could be a good start? https://github.com/zigpy/zha-device-handlers/pull/993 https://github.com/zigpy/zha-device-handlers/issues/769 I've never written a quirk so I'm not really sure. I did test out the ZCL command on my LCA006 bulb with firmware 1.104.2 and it worked great. It would be nice to also have fireplace. I believe there's a number of other ones as well.

nilsreiter commented 8 months ago

Ah cool, thanks, will have a look at it.

Next to the candle effect, there is fireplace and "prism", which is basically color loop. They are documented here as well: https://github.com/Koenkk/zigbee2mqtt/issues/15699. Others are not available even in the Hue app.

Regarding the ZCL command, @BenJamesAndo, did you use the built-in zha.issue_zigbee_cluster_command or zha_toolkit.zcl_cmd, provided by zha_toolkit? Because the former didn't work for me.

BenJamesAndo commented 8 months ago

I used zha_toolkit.zcl_cmd. I tried zha.issue_zigbee_cluster_command just now but I got an Unknown error.

BetaRavener commented 8 months ago

I used zha_toolkit.zcl_cmd. I tried zha.issue_zigbee_cluster_command just now but I got an Unknown error.

I also got an "Unknown error" using the zha.issue_zigbee_cluster_command, but I was able to issue an identify command for the light using the same, so I wonder if I got some parameters wrong. I've used:

IEEE: Copied from Device page Endpoint ID: 11 (Copied from Manage zigbee device dialog) Cluster ID: 64515 (decimal for 0xfc03) Cluster Type: in Command: 0 (can be seen on the capture here) Command Type: Server (this is the correct one that worked with identify)

And then I couldn't figure out Args or Params. I've used couple thing in Params, such as {multicolor: 0x21000101}, {multicolor: 21000101} or {multicolor: "21000101"}, trying to emulate what people did with zigbee2mqtt, but none of those worked.

I was wondering if you (@BenJamesAndo) have figured out what to put there, since you say you tested the ZCL command and it worked for you, even though using zha_toolkit.

BenJamesAndo commented 8 months ago

I unfortunately didn't make any progress beyond what anyone else has done. I only got zha_toolkit.zcl_cmd to work as others have, and never got anything out of zha.issue_zigbee_cluster_command.

BetaRavener commented 8 months ago

I only got zha_toolkit.zcl_cmd to work as others have

Could you point me to where this was described originally then? Because I either can't find it or am overlooking it.

BenJamesAndo commented 8 months ago

https://community.home-assistant.io/t/a-simple-single-automation-candle-effect-for-hue-white-ambience-and-other-bulbs/235023/16

service: zha_toolkit.zcl_cmd
data:
  manf: 4107
  ieee: light.philips_go_light
  endpoint: 11
  cluster: 0xfc03
  dir: 0
  cmd: 0x00
  args:
  - 0x21
  - 0x00
  - 0x01
  - 0x01

Change IEEE to your device. The below code is what I used to active Candle effect.

service: zha_toolkit.zcl_cmd
data:
  ieee: light.signify_netherlands_b_v_lca006_light
  manf: 4107
  endpoint: 11
  cluster: 64515
  dir: 0
  cmd: 0
  args:
    - 33
    - 0
    - 1
    - 1
nilsreiter commented 8 months ago

It doesn't seem to work with zha.issue_zigbee_cluster_command. My wild guess is that it has to do with the manf argument. Using zha_toolkit.zcl_cmd, it only works with this argument, and not without. If I use zha.issue_zigbee_cluster_command in the development page, it always converts the manf value to a string, which may not be what's expected.

BetaRavener commented 8 months ago

I've tracked down why this doesn't work with zha.issue_zigbee_cluster_command with help of logs. The thing tries to use the command as index to the commands hash-table but it doesn't know about any commands (otherwise I'd just use the "Manage Zigbee Device" dialog to issue a command). So it fails with error:

File "/usr/src/homeassistant/homeassistant/components/zha/core/device.py", line 824, in issue_cluster_command
    [field.name for field in commands[command].schema.fields],
                             ~~~~~~~~^^^^^^^^^
KeyError: 1

Unfortunately unless ZHA is made aware of the possibility to send command to that cluster, it is not possible to use it. The difference with zha_toolkit is that it probably just fires away and hopes for the best.

nilsreiter commented 8 months ago

Ah cool, interesting. This probably brings us back to the quirk we need ...

BetaRavener commented 8 months ago

I've successfully created a quirk that works with Philips Go to set the candlelight and fireplace effects, though I still have to find how to make it more user friendly (preset values, even getting it to the effect dropdown). For now I was testing it just by editing files in my docker deployment under /usr/local/lib/python3.11/site-packages/zhaquirks/philips (requires HA restart afterwards).

In __init.py__ goes:

class PhilipsEffectCluster(CustomCluster):
    """PhilipsEffectCluster."""

    cluster_id = 0xFC03
    ep_attribute = "philips_effect"
    name = "PhilipsEffect"

    server_commands = {
        0x00: foundation.ZCLCommandDef(
            "set_effect", 
            schema={
                "param1": t.uint8_t,
                "param2": t.uint8_t,
                "param3": t.uint8_t,
                "param4": t.uint8_t,
            },
            direction=foundation.Direction.Client_to_Server,
            is_manufacturer_specific=True,
        )
    }

and then created new file 7602031P7.py:

"""Philips Go 7602031P7 device."""
from zigpy.profiles import zha
from zigpy.quirks import CustomDevice
from zigpy.zcl.clusters.general import (
    Basic,
    GreenPowerProxy,
    Groups,
    Identify,
    LevelControl,
    OnOff,
    Ota,
    Scenes,
)
from zigpy.zcl.clusters.lighting import Color
from zigpy.zcl.clusters.lightlink import LightLink

from zhaquirks.const import (
    DEVICE_TYPE,
    ENDPOINTS,
    INPUT_CLUSTERS,
    MODELS_INFO,
    OUTPUT_CLUSTERS,
    PROFILE_ID,
)
from zhaquirks.philips import (
    PHILIPS,
    PhilipsEffectCluster,
)

class Philips7602031P7(CustomDevice):
    """Philips 7602031P7 device."""

    signature = {
        MODELS_INFO: [
            (PHILIPS, "7602031P7"),
        ],
        ENDPOINTS: {
            11: {
                # SizePrefixedSimpleDescriptor(endpoint=11, profile=260, device_type=268, device_version=1, input_clusters=[0, 3, 4, 5, 6, 8, 768, 4096, 64513, 64515], output_clusters=[25])
                PROFILE_ID: zha.PROFILE_ID,
                DEVICE_TYPE: zha.DeviceType.EXTENDED_COLOR_LIGHT,
                INPUT_CLUSTERS: [
                    Basic.cluster_id,
                    Identify.cluster_id,
                    Groups.cluster_id,
                    Scenes.cluster_id,
                    OnOff.cluster_id,
                    LevelControl.cluster_id,
                    Color.cluster_id,
                    LightLink.cluster_id,
                    64513,
                    64515,
                ],
                OUTPUT_CLUSTERS: [Ota.cluster_id],
            },
            # SizePrefixedSimpleDescriptor(endpoint=242, profile=41440, device_type=97, device_version=0, input_clusters=[], output_clusters=[33])
            242: {
                PROFILE_ID: 41440,
                DEVICE_TYPE: 97,
                INPUT_CLUSTERS: [],
                OUTPUT_CLUSTERS: [GreenPowerProxy.cluster_id],
            },
        },
    }

    replacement = {
        ENDPOINTS: {
            11: {
                PROFILE_ID: zha.PROFILE_ID,
                DEVICE_TYPE: zha.DeviceType.EXTENDED_COLOR_LIGHT,
                INPUT_CLUSTERS: [
                    Basic.cluster_id,
                    Identify.cluster_id,
                    Groups.cluster_id,
                    Scenes.cluster_id,
                    OnOff.cluster_id,
                    LevelControl.cluster_id,
                    Color.cluster_id,
                    LightLink.cluster_id,
                    64513,
                    PhilipsEffectCluster,
                ],
                OUTPUT_CLUSTERS: [Ota.cluster_id],
            },
            242: {
                PROFILE_ID: 41440,
                DEVICE_TYPE: 97,
                INPUT_CLUSTERS: [],
                OUTPUT_CLUSTERS: [GreenPowerProxy.cluster_id],
            },
        }
    }

which gets you something like this, where you can set the 4 numbers from args per parameter (33, 0, 1, 1) and send the command. image

BetaRavener commented 7 months ago

So if I understand this correctly, making HA understand that there's a command in the cluster is as far as zigpy can take things. To be able to set the effect via dropdown in the HA UI, one would need to implement special class of LightEntity for HA (link).

Currently, Philips Go (and I'm not sure there's example of a Zigbee bulb that does so differently) will register as Light class defined here, which is a subclass of BaseLight defined here, which finally implements the LightEntity mentioned above.

The Light class currently doesn't support any other effects other than colorloop added here.

But what may be interesting is few lines of code almost obscured by all the other code. This looks like a further subclass of the Light class that is applied only based on some exact match of clusters and manufacturers. I wonder if we would be able to implement another such subclass, that would only match when PhilipsEffectCluster implemented in previous comment is present. There we could insert the additional effects to effect_list. That would take care of at least the UI side, now just missing the linking piece where setting the effect would trigger ZHA command with exact arguments 🤔

BetaRavener commented 7 months ago

Think I found the missing link. Just going to write it all out here in case I forget, or somebody wants to do this before I have time :).

For a second, I'll use occupancy sensor as cluster example since it's much easier to search for across repositories than light bulb. In this repo, with zhaquirks, custom cluster PhilipsOccupancySensing is defined. Important is what's stored in ep_attribute = "philips_occupancy". This is then matched on the HA side with CLUSTER_HANDLER constant. Finally, this can be then used to implement the new class matching the cluster.

Then, back to light as example, one can get the cluster handler like this. The cluster handler is another class, specifically for the CLUSTER_HANDLER_COLOR it's this one. I've linked the color_loop_supported on purpose, since it is exactly the condition I've linked in previous comment where colorloop is added, confirming we are looking at the correct thing. But now a big assumption - I think we don't need to write our own cluster handler, it should exist based on the quirk we defined earlier, otherwise it's a strange duplication.

The last step is figuring out what's the interface to issue the set_effect command we defined on our cluster and pass the arguments, but I think I'll have to do some live debugging to answer that.

BetaRavener commented 7 months ago

The assumption regarding cluster being automatically created for us is correct. But the cluster is only passed in cluster_handlers when it is explicitly being matched against. Also thanks to the existing HueLight class, we need to specify the manufacturer, otherwise the HueLight match gets precedence (the logic is here).

So far I have hacked this into my HA zha/light.py:

@STRICT_MATCH(
    cluster_handler_names=CLUSTER_HANDLER_ON_OFF,
    aux_cluster_handlers={CLUSTER_HANDLER_COLOR, CLUSTER_HANDLER_LEVEL, "philips_effect"},
    manufacturers={"Philips", "Signify Netherlands B.V."},
)
class EffectHueLight(Light):
    """Representation of a HUE light with effects."""
    def __init__(
        self, unique_id, zha_device: ZHADevice, cluster_handlers, **kwargs
    ) -> None:
        """Initialize the ZHA light."""
        super().__init__(unique_id, zha_device, cluster_handlers, **kwargs)
        h = self.cluster_handlers.get("philips_effect")
        self.error("Effect Handler: %s, %s", str(h), dir(h))

The cluster definition looks like this:

2024-02-16 18:17:22.824 ERROR (MainThread) [homeassistant.components.zha.entity] None: Effect Handler: <homeassistant.components.zha.core.cluster_handlers.ClusterHandler object at 0x7f6c01a3e0d0>, ['BIND', 'REPORT_CONFIG', 'ZCL_INIT_ATTRS', '__annotations__', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattr__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_cluster', '_configure_reporting_status', '_endpoint', '_generic_id', '_get_attribute_name', '_get_attributes', '_id', '_status', '_unique_id', 'async_configure', 'async_initialize', 'async_send_signal', 'async_update', 'attribute_updated', 'bind', 'cluster', 'cluster_command', 'configure_reporting', 'data_cache', 'debug', 'error', 'generic_id', 'get_attribute_value', 'get_attributes', 'id', 'info', 'log', 'matches', 'name', 'status', 'unique_id', 'warning', 'write_attributes_safe', 'zdo_command', 'zha_send_event']

I'm thinking h.zha_send_event("set_effect", [33, 0, 1, 1]) should work. Not sure if it's correct though, as the function is decorated as callback.

BetaRavener commented 7 months ago

Got a working prototype, I'll try to open up PRs both on zigpy and HA sides to integrate this in: image

BetaRavener commented 7 months ago

Just found out interesting information playing with the new quirk.

miamilabs commented 5 months ago

I managed to activatee the candle effect using the ZCL command documented on here, on the following bulbs:

* LCT015, with bulb firmware 1.108.7, but not with 1.101.2

* LCT012, with bulb firmware 1.108.7, but not with 1.88.1

* LTO001, with bulb firmware 1.104.2

* 440400982842 (Playbar), with firmware 1.104.2

* LLC020, with firmware 67.101.2

* 1742830P7 (Lily), with 1.104.2, but not with 1.82.10

* LCL003 (Outdoor Lightstrip), with 1.93.7, but not 1.66.6

The following light does not seem to support the effect (tested with Hue app):

* LST002 (light strip), with firmware: 1.101.2

* LCT024 (Playbar), with firmware 1.108.5

I could give it a go to develop a quirk, but it would be helpful if someone could point me to one (for other things) that also add an effect.

(the firmware versions are actually also documented here ... )

Sorry for ping, facing same issue with fireplace after having 20 of the Playbars in my home. It is nice to have effect. I have Playbar with firmware "[1.104.2]" which support Fireplace..

Is there any way to downgrade the firmware ?

nilsreiter commented 4 months ago

Wouldn't that be an upgrade? From 1.104.2 to 1.108.5?