Philips Wiz (not Hue) Bulbs: how can we advocate for an API

I’ve recently bought 8 Philips Wiz bulbs because their color temperature is much warmer than other available products and they cost about 30% of their Hue equivalents.

The bulbs are configured via an iPhone app, and are WiFi based. There’s a Smartthings Cloud integration, which is pretty bad, if I’m honest.

These types of device are crying out for a native integration with Homeassistant, but some form of API is going to be necessary. When I reached out to the company to ask if there were plans to release one, I got a form letter back basically indicating that without an NDA, such an API was not forthcoming.

I’m a strong believer in what Homeassistant is trying to accomplish, and I’m frustrated by the fact that the manufacturer of these bulbs isn’t willing to work with the developer community. Does anyone have any advice for how we can effectively advocate to the manufacturer to include folks like us in their product support plans?

1 Like

Hey just got some of these bulbs for Christmas myself, was really disappointed that Home Assistant doesn’t support them. They don’t seem to be cloud reliant as I tested changing the lights while having my wifi router disconnected from the internet, hopefully that means there’s hope that an API can be reverse engineered. Fingers crossed!

1 Like

Someone in the openHab community has figured out a way in:

And someone in the security community figured out that these bulbs are horribly insecure. Anybody with access to the home network can change them. While I don’t currently have the time to write a high quality API for homeassistant, it would seem to me that someone in the community could use this information to get something hacked together pretty quickly.

That’s good to see! thanks for the links!

Disappointed in Home Assistant? You know this is a free open-source project?

1 Like

I am aware. I didn’t mean it in any way to denigrate Home Assistant.

1 Like

So, based on the information in the security analysis, I was able to send a UDP packet that activated one of my lights. Sending commands seems like a matter, then, of configuring each device (IP, MAC, and bulb type for proper parameters). Where I’m a bit confused is how monitoring state would work. Would love to learn more from someone more experienced if it’s possible to configure HA to listen to UDP traffic on the network, or whether setting up some sort of regular “ping” to each bulb to advertise its state would be required.

Well that’s some exciting progress!

Here’s a node-red node that can capture mqtt data (i.e. create mqtt lights and send them to your mqtt server) and translates them into tripled-up UDP calls that the Wiz bulbs will respond to. For the commands the bulbs expect, look at the documentation linked higher in this thread.

The calls to the UDP node is tripled up because I’ve noticed that the bulbs will often “miss” commands if they’re sent rapidly, but tripling up seems to fix this problem.

I haven’t been as successful in writing code to update HASS when the bulbs are changed elsewhere (Alexa, the Wiz app). While I can register the bulbs to send UDP commands to my server (as thus node-red), the 5 second ongoing updates are somewhat overwhelming, and I haven’t been able to suss out an API call that responds with bulb state.

Hope this helps someone.

[
    {
        "id": "3d109b6e.9aeda4",
        "type": "tab",
        "label": "Demo Flow",
        "disabled": false,
        "info": ""
    },
    {
        "id": "34d9e40a.f75bcc",
        "type": "json",
        "z": "3d109b6e.9aeda4",
        "name": "",
        "property": "payload",
        "action": "",
        "pretty": false,
        "x": 310,
        "y": 340,
        "wires": [
            [
                "15fbe611.434b8a"
            ]
        ]
    },
    {
        "id": "290eaf47.9580f",
        "type": "comment",
        "z": "3d109b6e.9aeda4",
        "name": "Relay MQTT to UDP",
        "info": "",
        "x": 170,
        "y": 260,
        "wires": []
    },
    {
        "id": "15fbe611.434b8a",
        "type": "function",
        "z": "3d109b6e.9aeda4",
        "name": "relay to udp",
        "func": "// expects the mqtt topic to be wizlight/ip/command\n\nvar command = msg.topic.split(\"/\")[2];\n\nmsg.ip = msg.topic.split(\"/\")[1];\n\nvar obj = {}; \nobj[command] = msg.payload;\n            \nvar p = {};\n\nvar pl = {method:\"setPilot\", \n          id:527, \n          env:\"pro\", \n          params : obj};\n\nmsg.payload =  pl\n\nmsg.port = 38899\n\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "x": 490,
        "y": 340,
        "wires": [
            [
                "ef2ef336.20cd1"
            ]
        ]
    },
    {
        "id": "d99585ca.e81038",
        "type": "mqtt in",
        "z": "3d109b6e.9aeda4",
        "name": "Wizlight In",
        "topic": "wizlight/#",
        "qos": "0",
        "datatype": "auto",
        "broker": "",
        "x": 160,
        "y": 340,
        "wires": [
            [
                "34d9e40a.f75bcc"
            ]
        ]
    },
    {
        "id": "adbcb2c3.f7b18",
        "type": "udp out",
        "z": "3d109b6e.9aeda4",
        "name": "UDP auto port/ip",
        "addr": "",
        "iface": "",
        "port": "",
        "ipv": "udp4",
        "outport": "38899",
        "base64": false,
        "multicast": "false",
        "x": 1070,
        "y": 200,
        "wires": []
    },
    {
        "id": "ef2ef336.20cd1",
        "type": "json",
        "z": "3d109b6e.9aeda4",
        "name": "",
        "property": "payload",
        "action": "",
        "pretty": false,
        "x": 690,
        "y": 340,
        "wires": [
            [
                "5212efd2.fd1b9"
            ]
        ]
    },
    {
        "id": "541bd62d.bf5178",
        "type": "repeat",
        "z": "3d109b6e.9aeda4",
        "name": "Triple It Up",
        "repetitions": "3",
        "elseOutput": false,
        "outputs": 1,
        "x": 810,
        "y": 200,
        "wires": [
            [
                "adbcb2c3.f7b18",
                "ef2ef336.20cd1"
            ]
        ]
    },
    {
        "id": "5212efd2.fd1b9",
        "type": "delay",
        "z": "3d109b6e.9aeda4",
        "name": "",
        "pauseType": "delay",
        "timeout": "25",
        "timeoutUnits": "milliseconds",
        "rate": "1",
        "nbRateUnits": "1",
        "rateUnits": "second",
        "randomFirst": "1",
        "randomLast": "5",
        "randomUnits": "seconds",
        "drop": false,
        "x": 870,
        "y": 340,
        "wires": [
            [
                "541bd62d.bf5178"
            ]
        ]
    }
]

It appears you can poll the bulbs with method getPilot:

echo '{"method":"getPilot","params":{}}' | nc -u -w 1 [ip] 38899
returns:
{"method":"getPilot","env":"pro","result":{"mac":"[mac]","rssi":-53,"src":"","state":false,"sceneId":0,"r":0,"g":255,"b":0,"c":0,"w":0,"dimming":100}}

{"method":"getPilot","env":"pro","result":{"mac":"[mac]","rssi":-53,"src":"","state":true,"sceneId":0,"r":0,"g":255,"b":0,"c":0,"w":0,"dimming":100}}
etc…

Actually, if I wasn’t allergic to Java and looked at the openhab binding more I would have noticed the author apparently did reverse engineer all the local commands:

He posted in Ability to control Philips/Wiz Connected WIFI Smart LED lights? a few days ago.

Anyway, I modified [wilvancleve]'s nodeRed flow a bit to just ignore the command passed in the topic so I can send the full params object and use the MQTT template schema to configure the light. For now I think I’ll just do template lights using the smartthings entities I already have setup for these bulbs for state, I mostly wanted the faster response time on the commands. (light.template apparently doesn’t support RGB :()

[
    {
        "id": "3bfa4c0a.55a6e4",
        "type": "tab",
        "label": "MQTT to Wiz",
        "disabled": false,
        "info": ""
    },
    {
        "id": "f8e03d02.b904a",
        "type": "json",
        "z": "3bfa4c0a.55a6e4",
        "name": "",
        "property": "payload",
        "action": "",
        "pretty": false,
        "x": 310,
        "y": 340,
        "wires": [
            [
                "5b5538a.6f768c8"
            ]
        ]
    },
    {
        "id": "39ef9bf6.b126f4",
        "type": "comment",
        "z": "3bfa4c0a.55a6e4",
        "name": "Relay MQTT to UDP",
        "info": "",
        "x": 170,
        "y": 260,
        "wires": []
    },
    {
        "id": "5b5538a.6f768c8",
        "type": "function",
        "z": "3bfa4c0a.55a6e4",
        "name": "relay to udp",
        "func": "// expects the mqtt topic to be wizlight/ip/command\n\nvar command = msg.topic.split(\"/\")[2];\n\nmsg.ip = msg.topic.split(\"/\")[1];\n\nvar obj = {}; \nobj = msg.payload;\n            \nvar p = {};\n\nvar pl = {method:\"setPilot\", \n          id:527, \n          env:\"pro\", \n          params : obj};\n\nmsg.payload =  pl\n\nmsg.port = 38899\n\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "x": 490,
        "y": 340,
        "wires": [
            [
                "eb39d4e7.006d18"
            ]
        ]
    },
    {
        "id": "62afb6be.881218",
        "type": "mqtt in",
        "z": "3bfa4c0a.55a6e4",
        "name": "Wizlight In",
        "topic": "wizlight/#",
        "qos": "0",
        "datatype": "auto",
        "broker": "",
        "x": 160,
        "y": 340,
        "wires": [
            [
                "f8e03d02.b904a"
            ]
        ]
    },
    {
        "id": "fae3fd3b.f7733",
        "type": "udp out",
        "z": "3bfa4c0a.55a6e4",
        "name": "UDP auto port/ip",
        "addr": "",
        "iface": "",
        "port": "",
        "ipv": "udp4",
        "outport": "38899",
        "base64": false,
        "multicast": "false",
        "x": 1070,
        "y": 200,
        "wires": []
    },
    {
        "id": "eb39d4e7.006d18",
        "type": "json",
        "z": "3bfa4c0a.55a6e4",
        "name": "",
        "property": "payload",
        "action": "",
        "pretty": false,
        "x": 690,
        "y": 340,
        "wires": [
            [
                "6840d6e1.111ea8"
            ]
        ]
    },
    {
        "id": "f345a1c5.4bbbf",
        "type": "repeat",
        "z": "3bfa4c0a.55a6e4",
        "name": "Triple It Up",
        "repetitions": "3",
        "elseOutput": false,
        "outputs": 1,
        "x": 810,
        "y": 200,
        "wires": [
            [
                "fae3fd3b.f7733",
                "eb39d4e7.006d18"
            ]
        ]
    },
    {
        "id": "6840d6e1.111ea8",
        "type": "delay",
        "z": "3bfa4c0a.55a6e4",
        "name": "",
        "pauseType": "delay",
        "timeout": "25",
        "timeoutUnits": "milliseconds",
        "rate": "1",
        "nbRateUnits": "1",
        "rateUnits": "second",
        "randomFirst": "1",
        "randomLast": "5",
        "randomUnits": "seconds",
        "drop": false,
        "x": 870,
        "y": 340,
        "wires": [
            [
                "f345a1c5.4bbbf"
            ]
        ]
    }
]
light:
  - platform: mqtt
    name: "wizbulb MQTT"
    schema: template
    command_topic: "wizlight/ip_address/set"
    command_on_template: >
      {"state": true
      {%- if brightness is defined -%}
      ,"dimming": {{ (brightness/254*100)| int }}
      {%- endif -%}
      {%- if red is defined and green is defined and blue is defined -%}
      ,"r":{{ red }},"g":{{ green }},"b":{{ blue }}
      {%- endif -%}
      {%- if color_temp is defined -%}
      ,"temp":{{ (1000000/color_temp)| int}}
      {%- endif -%}
      {%- if white_value is defined -%}
      ,"w":{{ white_value }}
      {%- endif -%}
      }
    command_off_template: '{"state":false}'
#note these are just populated to get the MQTT platform to enable the relevant supported_features, not currently reading state from the bulb
    brightness_template: '{{ value_json.dimming }}'
    red_template: '{{ value_json.r }}'
    green_template: '{{ value_json.g }}'
    blue_template: '{{ value_json.b }}'
    color_temp_template: '{{ value_json.temp }}'
    white_value_template: '{{ value_json.w }}'

Would it be possible to use HA and OpenHAB on the same system, that way you could use HA to send messages to OpenHAB in order to turn the bulbs on and off.

I don’t know much about OpenHAB, but given how easily node-red and mqtt coexist with HA, I think it’d be overkill. Now that someone has posted information about to query the bulbs, I think this is a really easy node-red solution. I’m going to work to implement the query API call tonight and I’ll post an updated node-red flow in the next little while…

Ok, give this a whirl. It queries all bulbs for which IP addresses are given every 10 seconds, and sends this to MQTT, and queries again on a change to a bulb (via UDP).

This works very nicely for me, and faster than the Wiz app. I struggled a little bit with the templating for the RGB bulbs, but otherwise I think this is pretty tight.

[{"id":"21cbe739.15d9c8","type":"tab","label":"Flow 1","disabled":false,"info":""},{"id":"b579249.841f4d8","type":"udp in","z":"21cbe739.15d9c8","name":"UDP2MQTT <gw listening UDP port>","iface":"","port":"38900","ipv":"udp4","multicast":"false","group":"","datatype":"utf8","x":210,"y":660,"wires":[["2ae4d01b.79f26"]]},{"id":"4e9174c8.cb986c","type":"comment","z":"21cbe739.15d9c8","name":"Relay UDP to MQTT","info":"","x":130,"y":600,"wires":[]},{"id":"fa19997f.c4a078","type":"function","z":"21cbe739.15d9c8","name":"relay to udp","func":"var ipArr = [\n\"192.168.164.x\",\n\"192.168.164.x\",\n\"192.168.164.x\",\n\"192.168.164.x\"\n    ];\n\nmultiMsg = {};\n\nipArr.forEach(myFunction); \n\nfunction myFunction(item, index) \n{ \n    var msg = {}\n    msg.ip = item;\n    msg.port = 38899;\n    \n    var pl = {method:\"getPilot\", \n          params : {}};\n          \n    msg.payload = pl;\n    multiMsg[item] = msg.payload;\n}\n\nmsg.payload = multiMsg;\nreturn msg;","outputs":1,"noerr":0,"x":330,"y":240,"wires":[["4a40c54.eb1663c"]]},{"id":"9738fe47.c95a1","type":"inject","z":"21cbe739.15d9c8","name":"Repeat q 10 s","topic":"","payload":"","payloadType":"date","repeat":"10","crontab":"","once":true,"onceDelay":"5","x":100,"y":240,"wires":[["fa19997f.c4a078"]]},{"id":"ba6b39ae.da6508","type":"udp out","z":"21cbe739.15d9c8","name":"UDP auto port/ip","addr":"","iface":"","port":"38899","ipv":"udp4","outport":"38900","base64":false,"multicast":"false","x":810,"y":240,"wires":[]},{"id":"4a40c54.eb1663c","type":"split","z":"21cbe739.15d9c8","name":"","splt":"\\n","spltType":"str","arraySplt":1,"arraySpltType":"len","stream":false,"addname":"ip","x":490,"y":240,"wires":[["c5c478f6.506058"]]},{"id":"c5c478f6.506058","type":"json","z":"21cbe739.15d9c8","name":"","property":"payload","action":"","pretty":false,"x":630,"y":240,"wires":[["ba6b39ae.da6508"]]},{"id":"645e1d4b.17b4b4","type":"comment","z":"21cbe739.15d9c8","name":"Query All Bulbs Every 10 Seconds","info":"","x":160,"y":180,"wires":[]},{"id":"4c6937dc.999c28","type":"json","z":"21cbe739.15d9c8","name":"","property":"payload","action":"","pretty":false,"x":230,"y":500,"wires":[["c4e6fab9.e40268"]]},{"id":"d45bf5f9.053ac8","type":"comment","z":"21cbe739.15d9c8","name":"Relay MQTT to UDP","info":"","x":110,"y":360,"wires":[]},{"id":"c4e6fab9.e40268","type":"function","z":"21cbe739.15d9c8","name":"relay to udp","func":"// expects the mqtt topic to be wizlight/ip/command\n\nmsg.ip = msg.topic.split(\"/\")[1];\n\nvar obj = {}; \nobj = msg.payload;\n            \nvar p = {};\n\nvar pl = {method:\"setPilot\", \n          id:527, \n          env:\"pro\", \n          params : obj};\n\nmsg.payload =  pl;\nmsg.command = msg.topic.split(\"/\")[2];\n\nmsg.port = 38899\n\nreturn msg;","outputs":1,"noerr":0,"x":390,"y":500,"wires":[["d3371846.5b01a8"]]},{"id":"1ea8ec1d.0d2d74","type":"mqtt in","z":"21cbe739.15d9c8","name":"Wizlight In","topic":"wizlight/#","qos":"0","datatype":"auto","broker":"7ca43ccf.bcea44","x":100,"y":440,"wires":[["4c6937dc.999c28"]]},{"id":"b98d58c7.ccb928","type":"udp out","z":"21cbe739.15d9c8","name":"UDP auto port/ip","addr":"","iface":"","port":"","ipv":"udp4","outport":"38899","base64":false,"multicast":"false","x":1010,"y":400,"wires":[]},{"id":"47549dc1.9ba0a4","type":"json","z":"21cbe739.15d9c8","name":"","property":"payload","action":"","pretty":false,"x":650,"y":500,"wires":[["8764c6c2.3d27d8"]]},{"id":"a0d26ebc.76d71","type":"repeat","z":"21cbe739.15d9c8","name":"Triple It Up","repetitions":"3","elseOutput":false,"outputs":1,"x":730,"y":400,"wires":[["b98d58c7.ccb928","47549dc1.9ba0a4","f086575b.7055a8"]]},{"id":"8764c6c2.3d27d8","type":"delay","z":"21cbe739.15d9c8","name":"","pauseType":"delay","timeout":"25","timeoutUnits":"milliseconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"x":830,"y":500,"wires":[["a0d26ebc.76d71"]]},{"id":"2ae4d01b.79f26","type":"json","z":"21cbe739.15d9c8","name":"","property":"payload","action":"","pretty":false,"x":470,"y":660,"wires":[["7e2173a7.c445ec"]]},{"id":"7e2173a7.c445ec","type":"function","z":"21cbe739.15d9c8","name":"Create MQTT message","func":"// expects the mqtt topic to be wizlight/ip/command\n\n// convert state to on/off\n\n\n\nmsg.payload =  msg.payload.result;\nmsg.topic = \"wizlight/\" + msg.ip + \"/status\"\n\nif (msg.payload.state === true) {\n    msg.payload.state = \"on\"\n} else {\n    msg.payload.state = \"off\"\n}\n\n\nreturn msg;","outputs":1,"noerr":0,"x":670,"y":660,"wires":[["8e010fb5.fb0bd"]]},{"id":"8e010fb5.fb0bd","type":"mqtt out","z":"21cbe739.15d9c8","name":"Send to MQTT","topic":"","qos":"0","retain":"","broker":"a6852ac6.173b78","x":900,"y":660,"wires":[]},{"id":"d3371846.5b01a8","type":"switch","z":"21cbe739.15d9c8","name":"","property":"command","propertyType":"msg","rules":[{"t":"eq","v":"set","vt":"str"}],"checkall":"true","repair":false,"outputs":1,"x":530,"y":560,"wires":[["47549dc1.9ba0a4"]]},{"id":"f086575b.7055a8","type":"repeat","z":"21cbe739.15d9c8","name":"Only Allow Once","repetitions":"1","elseOutput":false,"outputs":1,"x":710,"y":340,"wires":[["22de1803.bbafd8"]]},{"id":"22de1803.bbafd8","type":"delay","z":"21cbe739.15d9c8","name":"Wait One Second","pauseType":"delay","timeout":"1","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"x":930,"y":340,"wires":[["fa19997f.c4a078"]]},{"id":"7ca43ccf.bcea44","type":"mqtt-broker","z":"","name":"<IP of your MQTT broker>","broker":"<IP of your MQTT broker>","port":"1883","clientid":"","usetls":false,"compatmode":true,"keepalive":"60","cleansession":true,"birthTopic":"","birthQos":"0","birthPayload":"","willTopic":"","willQos":"0","willPayload":""},{"id":"a6852ac6.173b78","type":"mqtt-broker","z":"","name":"<IP of your MQTT broker>","broker":"<IP of your MQTT broker>","port":"1883","clientid":"","usetls":false,"compatmode":true,"keepalive":"60","cleansession":true,"birthTopic":"","birthQos":"0","birthPayload":"","willTopic":"","willQos":"0","willPayload":""}]
2 Likes

Here is my light.mqtt config I am using with the updated NodeRed flow. The color_temp handling when in RGB mode is kind of a hack, probably a better way to handle that. In theory the NodeRed flow wouldn’t need to actually poll the bulbs as you can use the registration command to have them send updates automatically to a given IP. This is easier though, since I don’t know what the persistence on that registration is, and making sure it stays maintained might be more of a pain that it is worth. Another improvement could be made to have the NodeRed convert all the json to comply with the mqtt json schema, as that would make the config a lot less ugly than all the templates needed to match what the bulb expects/replies with. And if the flow is doing the work to comply with a standard schema, could go one step further and use the correct format and topics for autodiscovery so you could just put the ips in the node red flow and be done with it. That is assuming someone doesn’t just write a custom component (and potentially eventual official integration) for these bulbs now that we know how to talk to them. Hopefully Wiz doesn’t tighten local security in response to that guys CVE he submitted.

light:
  - platform: mqtt
    name: "A Wiz Light"
    schema: template
    command_topic: "wizlight/<ip>/set"
    state_topic: "wizlight/<ip>/status"
    command_on_template: >
      {"state": true
      {%- if brightness is defined -%}
      ,"dimming": {{ (brightness/254*100)| int }}
      {%- endif -%}
      {%- if red is defined and green is defined and blue is defined -%}
      ,"r":{{ red }},"g":{{ green }},"b":{{ blue }}
      {%- endif -%}
      {%- if color_temp is defined -%}
      ,"temp":{{ (1000000/color_temp)| int}}
      {%- endif -%}
      {%- if white_value is defined -%}
      ,"w":{{ white_value }}
      {%- endif -%}
      }
    command_off_template: '{"state":false}'
    state_template: '{{ value_json.state}}'
    brightness_template: '{{ (value_json.dimming/100*254)| int }}'
    red_template: >
      {%- if value_json.r is defined -%}
      {{ value_json.r }}
      {%- else -%}
      255
      {%- endif -%}
    green_template: >
      {%- if value_json.g is defined -%}
      {{ value_json.g }}
      {%- else -%}
      255
      {%- endif -%}
    blue_template: >
      {%- if value_json.b is defined -%}
      {{ value_json.b }}
      {%- else -%}
      255
      {%- endif -%}
    color_temp_template: >
      {%- if value_json.temp is defined -%}
      {{ (1000000/value_json.temp)| int }}
      {%- else -%}
      370
      {%- endif -%}
    white_value_template: >
      {%- if value_json.w is defined -%}
      {{ value_json.w }}
      {%- else -%}
      {{ (value_json.dimming/100*254)| int }}
      {%- endif -%}

Thank you @ogremustcrush for this. It mostly works for me…curious if the UI should report back if its on? Currently if I click on, it turns the LED on, but the UI indicator flips back to off a second later…any thoughts?

If you look at my Node Red code, you can see my Node Red flows query the bulbs after a command to change state, and this updates the UI if you configure your MQTT settings for the bulb correctly.

Did you make sure you have all the IPs you are querying in the NodeRed flow as well as make sure the correct ip is in the state_topic? This is what I believe would happen if HA never receives a state update, or received a state update for another bulb (that is off in this case.) You wont have that behavior if you comment out the state_topic line, but then you won’t have state updates at all (HA will just maintain its internal state for the entity, but it won’t respond to changing the bulb through the Wiz app etc.)

Thank you for the replies. I think my conflict is I had two red flows, probably an earlier one and your newer one. Ultimately I deleted everything, reimported your yaml/red, configured IP’s and voila, worked like it should. Good job!

If you’re interested, I noticed in packet captures that every once in a while they beacon out a broadcast, in theory could be used to auto-find them? May be too much work when you could just input IP’s. Regardless, if you want that json, let me know.

1 Like