Skip to content

Instantly share code, notes, and snippets.

@amigus
Last active September 28, 2024 00:15
Show Gist options
  • Save amigus/7db521b4ec0766bb1351a68c75046fa4 to your computer and use it in GitHub Desktop.
Save amigus/7db521b4ec0766bb1351a68c75046fa4 to your computer and use it in GitHub Desktop.
Functions to mess with Philips WiZ Smart Bulbs
<#
.SYNOPSIS
Controls WiZ lightbulbs.
.DESCRIPTION
I want to control my lightbulbs. These functions allow me to do that.
.EXAMPLE
# Get the current state of all bulbs on the first network with a gateway
Get-WiZBulb
.EXAMPLE
# Get the current state of all bulbs on the specified network interface
Get-WiZBulb -InterfaceAlias 'Ethernet 2'
.EXAMPLE
# Get the current state of all bulbs on the given interface
Get-NetIPInterface -InterfaceIndex 25 | Get-WiZBulb
.EXAMPLE
# Get the current state of two particular bulbs
Get-WiZBulb 192.168.0.5, wiz_abc123
.EXAMPLE
# Get the state and dimming value of a bulb
Get-WiZBulb -Parameters @('state', 'dimming') wiz_456def
.EXAMPLE
# Get the dimming value of a bulb as an integer
gwiz -Parameters dimming 192.168.0.10 | Select-Object -ExpandProperty Output
.EXAMPLE
# Get all the bulbs set on the "Candlelight (Id=29)" scene
gwiz | where { $_.Output.sceneId -eq 29 }
.EXAMPLE
# Set a bulb to 'Daylight'
Set-WiZBulb @{temp=4200;dimming=90} 192.168.0.7
.EXAMPLE
# Copy the scene and dimming settings from one bulb to another
gwiz 192.168.0.10 | Set-WiZBulb 192.168.0.11
.EXAMPLE
#>
if (-not (Test-Path -Path Variable:WizBulbPort)) {
Set-Variable -Name WizBulbPort -Option Constant -Scope Global -Value 38899
}
function New-WiZBulbMessage {
[CmdletBinding()]
param(
[Parameter()]
[ValidateSet('registration', 'getPilot', 'setPilot', 'syncPilot')]
$Method = 'getPilot',
[Parameter()]$Parameters = @{}
)
[Text.ASCIIEncoding]::new().GetBytes(
'{{"method":"{1}","params":{2}}}' -f @(
$Operation, $Method, ($Parameters | ConvertTo-Json -Compress)
)
)
}
function Send-WiZBulbMessage {
param([byte[]]$Message, [string]$Recipient, [switch]$Broadcast)
$Udp = New-Object Net.Sockets.UdpClient
$Udp.EnableBroadcast = $Broadcast.IsPresent
$Sent = $Udp.Send($Message, $Message.Count, $Recipient, $WizBulbPort)
Write-Debug (
"Sent {0} bytes to {1}: '{2}'" -f (
$Sent, $Recipient, [Text.ASCIIEncoding]::new().GetString($Message)
)
)
$Udp
}
function Receive-WiZBulbResponse {
param([Parameter(Mandatory, ValueFromPipeline)][Net.Sockets.UdpClient]$Udp)
begin {
$Remote = [Net.IPEndPoint]::new([Net.IPAddress]::Any, $WizBulbPort)
}
process {
do {
$Received = [Text.ASCIIEncoding]::new().GetString(
$Udp.Receive([ref] $Remote)
)
Write-Debug (
"Received {0} bytes from {1}: '{2}'" -f (
$Received.Length, $Remote, $Received
)
)
New-Object PSObject -Property @{
Bulb = $Remote.Address
Response = ConvertFrom-Json $Received
}
Start-Sleep -Milliseconds 666
} until ($Udp.Available -eq 0)
}
end {
$Udp.Close()
}
}
function Format-WizBulbResponse {
[CmdletBinding()]
param(
[Parameter(Mandatory, ValueFromPipeline)][object]$InputObject,
[Parameter()][switch]$SkipNameResolution,
[Parameter(ValueFromRemainingArguments)][string[]]$Parameters
)
process {
Select-Object -InputObject $InputObject @{
Name = $(
if ($Parameters.Count -eq 1) {
$Parameters[0]
}
else {
'Parameters'
}
)
Expression = {
if ($Parameters) {
if ($Parameters.Count -eq 1) {
$_.Response.result |
Select-Object -ExpandProperty $Parameters[0]
}
else {
$_.Response.result |
Select-Object -Property $Parameters |
Select-Object -ExcludeProperty 'mac', 'src'
}
}
else {
$_.Response.result |
Select-Object -ExcludeProperty 'mac', 'src'
}
}
}, @{
Name = 'Bulb'
Expression = {
if ($SkipNameResolution.IsPresent) {
$_.Bulb
}
else {
Resolve-DnsName -Name $_.Bulb -DnsOnly -QuickTimeout |
Select-Object -ExpandProperty NameHost
}
}
}
}
}
function Get-WizBulbBroadcastIPAddress {
param([int]$InterfaceIndex)
$NetIPAddressParameters = @{ AddressFamily = 'IPv4' }
if ($InterfaceIndex) {
$NetIPAddressParameters.InterfaceIndex = $InterfaceIndex
}
else {
$NetIPAddressParameters.InterfaceIndex = (
Get-NetRoute | Where-Object DestinationPrefix -eq '0.0.0.0/0' |
Select-Object -ExpandProperty InterfaceIndex -First 1
)
}
$NetIPAddress = Get-NetIPAddress @NetIPAddressParameters
Write-Debug (
'Calculating the broadcast address from {0}/{1}' -f (
$NetIPAddress.IPAddress, $NetIPAddress.PrefixLength
)
)
[Net.IPAddress]::new((
(
[Net.IPAddress]::Parse($NetIPAddress.IPAddress).Address -band
[uint]::MaxValue -shr (32 - $NetIPAddress.PrefixLength)
) -bor (
[uint]::MaxValue -shl $NetIPAddress.PrefixLength
)
)).IPAddressToString
}
function Get-WiZBulb {
[CmdletBinding(DefaultParameterSetName = 'Bulb')]
param(
[Parameter(ParameterSetName = 'Bulb', Position)][string[]]$Bulb,
[Parameter(Mandatory, ParameterSetName = 'Alias')
][string]$InterfaceAlias,
[Parameter(ParameterSetName = 'Index',
ValueFromPipelineByPropertyName)][int]$InterfaceIndex
)
begin {
if ($Bulb) {
Write-Debug (
'Sending unicast messages to {0}' -f $(
if ($Bulb.Count -eq 1) { $Bulb }
else { $Bulb.Join(', ') }
)
)
}
elseif ($InterfaceIndex) {
Write-Debug "Broadcasting on the interface with index ${InterfaceIndex}"
$Bulb = Get-WiZBulbBroadcastIPAddress -InterfaceIndex $InterfaceIndex
}
elseif ($InterfaceAlias) {
Write-Debug "Broadcasting on $InterfaceAlias"
$Bulb = Get-WiZBulbBroadcastIPAddress -InterfaceIndex (
Get-NetIPInterface -InterfaceAlias $InterfaceAlias |
Select-Object -ExpandProperty InterfaceIndex
)
}
else {
Write-Debug 'Broadcasting on the first interface with a gateway'
$Bulb = Get-WiZBulbBroadcastIPAddress
}
}
process {
foreach ($b in $Bulb) {
Send-WiZBulbMessage (New-WiZBulbMessage) $b |
Receive-WiZBulbResponse
}
}
}
Set-Alias -Name 'gwiz' -Scope Global -Value Get-WiZBulb
function Set-WiZBulb {
param(
[Parameter(Position)][string[]]$Bulb,
[Parameter(ParameterSetName='Parameters', ValueFromPipelineByPropertyName)]
$Parameters,
[Parameter(ParameterSetName='Response', ValueFromPipelineByPropertyName)]
$Response
)
process {
if ($PSCmdlet.ParameterSetName -eq 'Response') {
$Parameters = $Response.result | Select-Object -ExcludeProperty 'mac'
}
foreach ($b in $Bulb) {
Send-WiZBulbMessage (New-WiZBulbMessage 'setPilot' $Parameters) $b |
Receive-WiZBulbResponse
}
}
}
@amigus
Copy link
Author

amigus commented Oct 30, 2023

Credit where it is due. I got a start by reading Aleksandr Rogozin's Blog! 🙏

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment