Last active
September 28, 2024 00:15
-
-
Save amigus/7db521b4ec0766bb1351a68c75046fa4 to your computer and use it in GitHub Desktop.
Functions to mess with Philips WiZ Smart Bulbs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<# | |
.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 | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Credit where it is due. I got a start by reading Aleksandr Rogozin's Blog! 🙏