[Edge] Virtual UDP Sender. Eases sending WiZ lights control commands

I made a UDP sender able to send text messages and hex data to the local network from automations. Includes some tweaks for specific use cases like string interpolation to insert attributes from other devices. It is not a server or receiver.

While it is generic, it is made to facilitate local control of certain WiZ features not available through SmartThings integrations that require using their UDP protocol like dynamic colours, dim to warm mode or independent control of dual-zone lights. For instance, this routine activates the Deep dive dynamic colour in a couple lights :dolphin::man_surfing:

Installation and use :joystick:

  • Install the driver from mocelet-shared channel at https://bestow-regional.api.smartthings.com/invite/Kr2zNDg0Wr2A
  • In SmartThings app go to add new devices and Scan nearby so the UDP Sender device appears.
  • To send UDP messages create automations with the device. Write the input in the Advanced input action or, from the advanced website, in the runCommands action. SmartThings app has an arbitrary character limit for the input, for some long commands you may need to use the Rules API or AWA instead.
  • There is a generic ip:port message input format and syntactic sugar to craft WiZ setPilot commands as well as other types of payloads you can find in the cheat sheet at the end.

Sending WiZ control commands :light_bulb:

:warning: Make sure your lights have static IPs! You can achieve that by setting static reservations in the DHCP configuration of your router.

You may be familiar with the WiZ UDP code generator that tells you the JSON text message to send. Well, you can use those codes from automations now.

For instance, to activate the Deep dive dynamic scene at 100% brightness in a WiZ bulb, this generic syntax will work:

192.168.1.44:38899 {"id":1,"method":"setPilot","params":{"sceneId":23,"speed":145,"dimming":100}}

Syntactic sugar for the setPilot command avoids boilerplate. This is equivalent, you only have to write the magic keyword, the IP and the value of the params attribute:

pilot 192.168.1.44 {"sceneId":23,"speed":145,"dimming":100}

Supports multiple addresses, comma separated, no spaces. They are sent as close in time as possible to avoid popcorn effect:

pilot 192.168.1.44,192.168.1.45,192.168.1.46 {"sceneId":23,"speed":145,"dimming":100}

There are two variations of the pilot keyword with automatic retransmission for added reliability, you can read more about them here.

Some more examples

Turn on: pilot 192.168.1.44 {"state":true}

Turn off: pilot 192.168.1.44 {"state":false}

Set 3000K 75% at once: pilot 192.168.1.44 {"temp":3000,"dimming":75}

Extra-low brightness orange-ish: pilot 192.168.1.44 {"r":1,"g":0,"b":0,"w":1,"c":0,"dimming": 1}

Activate the new Dim to Warm mode: pilot 192.168.1.44 {"sceneId":40}

Turn on zone 2 of dual-zone light: pilot 192.168.1.44 {"devices":2,"state":true}

WiZ protocol documentation

WiZ has no official documentation (well, partially) but you can find information by inspecting the source code of pywizlight, here is the list of dynamic scene ids and some info of dual-zone lamps. Comments at https://www.reddit.com/r/wiz/ are also useful, especially from user wiz-dude like the insights on the TV sync modes.

Syntax cheat sheet :eyes:

The UDP Sender is generic even if most examples in the thread are WiZ focused. Here is a concise feature list that should be more or less up to date, check the comments for more insights.

  • Send a text to single address: ip:port message
  • Send a text to multiple addresses: ip:port,ip:port,ip:port message
  • Send a text to broadcast address: bcast ip:port message
  • Send a WiZ setPilot command: pilot ip params_attribute
  • Send a WiZ setPilot with automatic retransmission: see here.
  • Insert in the input integer attributes from devices like dimming levels and on/off switches as true/false: see string interpolation.
  • Send hex data: hex ip:port hex_data
  • Send hex data to broadcast address: bhex ip:port hex_data
  • Send a Wake On LAN packet: wol broadcast_ip mac_address
2 Likes

Apparently the scene will remain on for now.

Is there a way to end the scene or do I turn off the lamp and the scene will stop running?

1 Like

The dynamic scene when activated is the current mode, unless you change the colour or temperature will remain there.

It’s exactly like if you used the WiZ app to pick that dynamic mode.

BTW in Matter there’s kind of a bug, if the light was in, let’s say, 2700K, you change to a dynamic mode and then want to go back to 2700K, the Matter part of the light thinks it’s still in 2700K and won’t do anything :smiley: I have some routines that change the temperature to something like 2750K and 2700K just to trigger an actual change in case it was in a dynamic mode.

1 Like

Now this works for me

The Wiz bulb can be turned on and off via UDP Sender.

This is a good backup connection. The main connection is using Matter WiFi.

1 Like

Also using Rules API rule (AWA)

  {
    "if": {
      "equals": {
        "left": {
          "device": {
            "devices": [
              "-- ID Virtual Switch --"
            ],
            "component": "main",
            "capability": "switch",
            "attribute": "switch",
            "trigger": "Always"
          }
        },
        "right": {
          "string": "on"
        }
      },
      "then": [
        {
          "command": {
            "devices": [
              "-- ID Virtual UDP Sender --"
            ],
            "commands": [
              {
                "component": "main",
                "capability": "panelorange55982.advancedControl",
                "command": "runCommands",
                "arguments": [
                  {
                    "string": "pilot 192.168.1.44 {\"state\":true}"
                  }
                ]
              }
            ]
          }
        }
      ],
      "else": [
        {
          "command": {
            "devices": [
              "-- ID Virtual UDP Sender --"
            ],
            "commands": [
              {
                "component": "main",
                "capability": "panelorange55982.advancedControl",
                "command": "runCommands",
                "arguments": [
                  {
                    "string": "pilot 192.168.1.44 {\"state\":false}"
                  }
                ]
              }
            ]
          }
        }
      ]
    }
  }
2 Likes

The rule example showcasing how to escape the JSON is a great addition, thanks!

Some notes regarding switching on/off the lights with UDP messages instead of the integrations:

  • Compared to the cloud integration for non-Matter bulbs, it is local obviously. Plus you can control lights not even added to SmartThings.
  • The ā€œonā€ is compatible with WiZ rhythms since the UDPs are the same sent by WiZ app. If you turn it on from Matter it uses the OnLevel attribute of Matter but not the rhythms.
  • The UDP doesn’t have the ā€œOff with effectā€ of Matter that I looove for the motion controlled lights.
  • A single UDP command can set both brightness and colour, avoiding ā€œflashesā€ of the last state. In Matter they are two commands since brightness control and colour control are independent features.
  • The UDP packet could get lost, Matter has retransmission mechanisms.

While I use the UDP Sender mostly to avoid a cloud automation invoking Google Home to recall a dynamic mode, I believe it’s going to be useful in more cases. The example of low brightness for instance achieves a low brightness that I believe cannot be achieved via Matter.

I have an older Wiz bulb that does not support Matter communication.

@mocelet
Do you have any information on whether this UDP communication also works with older bulbs?

It does with every WiZ bulb, you don’t even need to have it added to SmartThings as long as you have the IP address. But I didn’t really want to create a WiZ integration :rofl:

1 Like

New version 2026-03-25

My WiZ lights are Matter so there’s already local control but, reading the comments and since you may have older bulbs around, I’ve added a nice little perk: now you can link @TapioX ā€˜s Virtual Dimmer to this UDP Sender so it locally controls brightness and on/off state of those non-Matter WiZ lights where the only SmartThings integration is cloud-based.

The new capability panelorange55982.wizDimmerMirror is meant to be used by mirroring rules to pass the switch and level values of the Virtual Dimmer. Internally it just creates the pilot strings to send the ā€œstateā€ true or false or the ā€œdimmingā€ params to the IPs you provide in the rule (one or more, comma separated with no spaces just like in the input strings).

Rule to copy on/off state

[
  {
    "if": {
      "changes": {
        "operand": {
          "device": {
            "devices": [
              "VIRTUAL-DIMMER-ID-HERE"
            ],
            "component": "main",
            "capability": "switch",
            "attribute": "switch"
          }
        }
      },
      "then": [
        {
          "command": {
            "devices": [
              "UDP-SENDER-ID-HERE"
            ],
            "commands": [
              {
                "component": "main",
                "capability": "panelorange55982.wizDimmerMirror",
                "command": "setSwitch",
                "arguments": [
                  {
                    "string": "192.X.X.X"
                  },
                  {
                    "device": {
                      "devices": [
                        "VIRTUAL-DIMMER-ID-HERE"
                      ],
                      "component": "main",
                      "capability": "switch",
                      "attribute": "switch"
                    }
                  }
                ]
              }
            ]
          }
        }
      ]
    }
  }
]

Rule to copy dimmer level when changes or turns on

[
  {
    "if": {
      "or": [
        {
          "changes": {
            "operand": {
              "device": {
                "devices": [
                  "VIRTUAL-DIMMER-ID-HERE"
                ],
                "component": "main",
                "capability": "switchLevel",
                "attribute": "level",
                "trigger": "Always"
              }
            }
          }
        },
        {
          "equals": {
            "left": {
              "device": {
                "devices": [
                  "VIRTUAL-DIMMER-ID-HERE"
                ],
                "component": "main",
                "capability": "switch",
                "attribute": "switch",
                "trigger": "Always"
              }
            },
            "right": {
              "string": "on"
            }
          }
        }
      ],
      "then": [
        {
          "command": {
            "devices": [
              "UDP-SENDER-ID-HERE"
            ],
            "commands": [
              {
                "component": "main",
                "capability": "panelorange55982.wizDimmerMirror",
                "command": "setLevel",
                "arguments": [
                  {
                    "string": "192.X.X.X"
                  },
                  {
                    "device": {
                      "devices": [
                        "VIRTUAL-DIMMER-ID-HERE"
                      ],
                      "component": "main",
                      "capability": "switchLevel",
                      "attribute": "level"
                    }
                  }
                ]
              }
            ]
          }
        }
      ]
    }
  }
]
1 Like

My Wiz bulb (not Matter) that I bought about 3 years ago also works. The bulb may be much older.

I first installed the bulb in the Wiz V2 app and then I got the IP address from the router.

1 Like

WiZ v2 app also has the IP information but it’s well hidden: in the device screen, three dots menu, Settings, click the arrow pointing down near the name, Device info, scroll down to see IP address and MAC address.

BTW if you bought it 3 years ago must be really old stock to not be Matter enabled, have you double checked in Settings - Integrations section of WiZ app that Matter is not available for that bulb?

1 Like

Yes That’s probably why they sold the bulbs at a discount.

I didn’t understand enough about Matter compatibility back then.


Old bulb’s Model 27147 is different

Matter support is not mentioned for the old bulb in the Integration list.

1 Like

New version 2026-03-26 introduces integer placeholders for inputs. Thanks to @TapioX for the idea!

For Rules API users, there is a new capability panelorange55982.integerInterpolator and interpolate command that allows you to write the input just like you would do normally but being able to include up to five optional integer fields that you refer in the input like $int1 up to $int5.

Example and use cases

A typical input like pilot 192.168.1.44 {"sceneId":23,"speed":145} to set a dynamic colour can be expressed as:

pilot 192.168.1.44 {"sceneId":$int1,"speed":145}

Then you can pass the id as the int1 argument of the interpolate command. The benefit is being able to pass the value from another device using a rule to copy it.

Since it is generic you can use it for things other than WiZ lights but, for WiZ lights opens more automation options:

  • A fun example I used for testing is passing the dimmer level of a virtual dimmer to the previous input (mind the escaping in the JSON rule). The regular dynamic scenes go from 1 to thirty something, you can quickly pick let’s say, 23% brightness for the Deep Dive dynamic scene. And use smart buttons to step the brightness 1% more or less to go to the next or previous scene.
  • I don’t have WiZ dual-zone lamps so can’t test it but now you should be able to link virtual dimmers to the individual zones of the lamp. Apparently it’s just a matter of adding a "devices":1 or "devices":2 parameter so the input would be like pilot 192.168.1.44 {"devices":1,"dimming":$int1} and if it’s another format or they changed it doesn’t matter given it is generic.

This is the fun example of the dimmer level to change scenes, just the then part so you get the idea of how it would look. You can use a virtual counter, the scene switcher or anything that has an integer attribute:

      "then": [
        {
          "command": {
            "devices": [
              "UDP-SENDER-ID"
            ],
            "commands": [
              {
                "component": "main",
                "capability": "panelorange55982.integerInterpolator",
                "command": "interpolate",
                "arguments": [
                  {
                    "string": "pilot 192.168.1.44 {\"sceneId\":$int1}"
                  },
                  {
                    "device": {
                      "devices": [
                        "DEVICE-TO-COPY-ID"
                      ],
                      "component": "main",
                      "capability": "switchLevel",
                      "attribute": "level"
                    }
                  }
                ]
              }
            ]
          }
        }
      ]

Update: Version 2026-03-31 adds capability panelorange55982.switchBooleanInterpolator so you can pass the on/off state of a switch directly as a true/false boolean. No more ā€œif switch on then state true else state falseā€! The placeholders are called $switch1 to $switch5, e.g.:

"arguments": [
  {
    "string": "pilot 192.168.1.44 {\"state\":$switch1}"
  },

For WiZ dual zone lights you may need the following rules. Note I don’t have dual zone lights to test, first check that the pilot inputs work as expected and tweak them if necessary.

Rule so virtual dimmer controls zone 1 brightness
[
  {
    "if": {
      "or": [
        {
          "greaterThan": {
            "left": {
              "device": {
                "devices": [
                  "VIRTUAL-DIMMER-ID-HERE"
                ],
                "component": "main",
                "capability": "switchLevel",
                "attribute": "level",
                "trigger": "Always"
              }
            },
            "right": {
              "integer": 0
            },
            "changesOnly": false
          }
        },
        {
          "equals": {
            "left": {
              "device": {
                "devices": [
                  "VIRTUAL-DIMMER-ID-HERE"
                ],
                "component": "main",
                "capability": "switch",
                "attribute": "switch",
                "trigger": "Always"
              }
            },
            "right": {
              "string": "on"
            }
          }
        }
      ],
      "then": [
        {
          "command": {
            "devices": [
              "UDP-SENDER-ID-HERE"
            ],
            "commands": [
              {
                "component": "main",
                "capability": "panelorange55982.integerInterpolator",
                "command": "interpolate",
                "arguments": [
                  {
                    "string": "pilot 192.168.1.44 {\"devices\":1,\"dimming\":$int1,\"state\":true}"
                  },
                  {
                    "device": {
                      "devices": [
                        "VIRTUAL-DIMMER-ID-HERE"
                      ],
                      "component": "main",
                      "capability": "switchLevel",
                      "attribute": "level"
                    }
                  }
                ]
              }
            ]
          }
        }
      ]
    }
  }
]
Rule so virtual dimmer or switch controls zone 1 on/off state
[
  {
    "if": {
      "changes": {
        "operand": {
          "device": {
            "devices": [
              "VIRTUAL-DIMMER-ID-HERE"
            ],
            "component": "main",
            "capability": "switch",
            "attribute": "switch"
          }
        }
      },
      "then": [
        {
          "command": {
            "devices": [
              "UDP-SENDER-ID-HERE"
            ],
            "commands": [
              {
                "component": "main",
                "capability": "panelorange55982.switchBooleanInterpolator",
                "command": "interpolate",
                "arguments": [
                  {
                    "string": "pilot 192.168.1.44 {\"devices\":1,\"state\":$switch1}"
                  },
                  {
                    "device": {
                      "devices": [
                        "VIRTUAL-DIMMER-ID-HERE"
                      ],
                      "component": "main",
                      "capability": "switch",
                      "attribute": "switch"
                    }
                  }
                ]
              }
            ]
          }
        }
      ]
    }
  }
]
1 Like

New version 2026-03-28

The driver can send hex data packets now, the syntax is hex ip:port data with data being a hex string like FF or ab-f2-c1 or 00ffff. Use bhex keyword for broadcast packets. As usual, it allows multiple comma separated targets like ip:port,ip:port. The hex data can be written without separators, in upper case or with spaces.

:laptop: Wake On LAN (WoL) example

An example of binary payload is the magic packet to wake a device and, while you can build it yourself and send it with a generic command, there is some syntactic sugar for it too. Mind there are already drivers like PC Control if you look for more features, WoL included.

The syntax is wol broadcast_ip mac_address, for instance:

wol 255.255.255.255 bc-5f-f3-35-55-d9

For the curious, click here to reveal how to send a WoL packet without the syntactic sugar

Instead of the wol keyword that builds the magic packet payload and fills in the port for you, you can concatenate 6 FF blocks with 16 times the MAC and broadcast it (bhex) using the standard port 9. I’ve tested it so you don’t have to :sweat_smile:

bhex 255.255.255.255:9 ffffffffffffbc5ff33555d9bc5ff33555d9bc5ff33555d9bc5ff33555d9bc5ff33555d9bc5ff33555d9bc5ff33555d9bc5ff33555d9bc5ff33555d9bc5ff33555d9bc5ff33555d9bc5ff33555d9bc5ff33555d9bc5ff33555d9bc5ff33555d9bc5ff33555d9

I’ve revamped the first post too with a syntax cheat sheet at the end given the number of features has grown quickly in just few days :grinning_face_with_smiling_eyes:

Edit: There’s a new version 2026-03-30, just a internal refactor to be more efficient

1 Like

New version. With today’s update 2026-03-31 I believe the driver is where I wanted it to be.

Boolean interpolator for On/Off switches

I’ve updated the previous comment on string interpolation since the driver now supports passing the on/off state of a switch as true/false directly to the input. This simplifies rules (no need for if on then true else false), especially if you plan to link virtual dimmers to the individual zones of dual-zone WiZ lights.

Automatic retransmission of WiZ setPilot commands

Since the UDP Sender is not meant to receive data or be an integration for a particular device or brand, it just opens a new socket when it has to send something and closes it right away.

There is an exception though… Two special variations of the pilot keyword called pilotFastRetry and pilotRetry do keep the socket opened for a bit to receive any response from the light and send the message again if there was no reply. The fast flavour retries once within a second, the normal one keeps trying few times during a total of around 10 seconds.

Which flavour of pilot to use depends on the use case, I’ve never seen a UDP command get lost in my local network so the normal pilot is probably fine:

  • If you plan to use automations with buttons, retries are probably not really important if you are seeing the result and can press the button again. If any I woud use the pilotFastRetry. This is the one used by the wizDimmerMirror capability by the way (a capability that you can replicate with the new interpolators).
  • If you are using a scroll wheel or knob to control the brightness that sends lots of events while rotating, retransmitting is pointless: the light will be already busy with all the commands anyway and you may end up resending an old brightness value. If any I would use the pilotFastRetry here.
  • If you have an automation that runs once, for instance to activate some dynamic modes when the TV turns on, I’d go for the pilotRetry to ensure it is going to run unless the network is really struggling.

Basic arithmetic for the string interpolator

This starts in version 2026-04-07. The interpolator already supported replacing $int1 with the argument passed, let’s say 100. Now you can scale it and/or add an offset, for instance ${2 * $int1 + 5} would be converted to ${2 * 100 + 5}, perform the operation and replace everything with 205 instead of the 100 passed originally. Just enclose the operations in ${...}

1 Like