Device/Set-LifxDeviceColor.ps1

<#
    .SYNOPSIS
        Controls the color of a Lifx device
    .DESCRIPTION
        This cmdlet allows you to set the brightness, saturation, hue, and kelvin of a Lifx device based on different parameters. It also
        supports SecondsToTransition to control how fast the change occurs.
    .EXAMPLE
        $devices = Get-LifxDevice | Initialize-LifxDevice
        $devices | Where-Object {$_.Group -eq "Living Room"} | Set-LifxDeviceColor -Red 200 -Blue 13 -Brightness 75 -Saturation 100
    .EXAMPLE
        $devices = Get-LifxDevice | Initialize-LifxDevice
        $devices | Where-Object {$_.Group -eq "Living Room"} | Set-LifxDeviceColor -Brightness 100 -White "Sunset" -SecondsToTransition 1.5
    .EXAMPLE
        $devices = Get-LifxDevice | Initialize-LifxDevice
        $devices | Where-Object {$_.Group -eq "Living Room"} | Set-LifxDeviceColor -Brightness 100 -White "Noon Daylight" -SecondsToTransition 4
    .EXAMPLE
        $devices = Get-LifxDevice | Initialize-LifxDevice
        $devices | Where-Object {$_.Group -eq "Living Room"} | Set-LifxDeviceColor -Kelvin 12000 -Brightness 10000
    .EXAMPLE
        $devices = Get-LifxDevice | Initialize-LifxDevice
        $devices | Where-Object {$_.Group -eq "Living Room"} | Set-LifxDeviceColor -White 'Cloudy Daylight' -Brightness 90
#>


function Set-LifxDeviceColor
{
    param(
        #Discovered Lifx device. Accepts input from Get-LifxDevice or Initialize-LifxDevice
        [parameter(
            Position          = 0,
            Mandatory         = $true,
            ValueFromPipeline = $true)]
        [PSCustomObject[]]$Device,

        #Hue: The section of the color spectrum that represents the color of your device. This is taken as RGB values and converted to HSB
        [ValidateRange(0, 255)]
        [Parameter(ParameterSetName="Color")]
        [decimal]$Red = 0,
        
        [ValidateRange(0, 255)]
        [Parameter(ParameterSetName="Color")]
        [decimal]$Green = 0,
        
        [ValidateRange(0, 255)]
        [Parameter(ParameterSetName="Color")]
        [decimal]$Blue = 0,
        
        #Brightness: How bright the light is. Zero brightness is the same as the device is off, while full brightness be just that.
        #This value is taken as a percent value i.e. 1, 27, 43, 100
        [ValidateRange(0, 100)]
        [Parameter(ParameterSetName="Color")]
        [Parameter(ParameterSetName="KelvinByNumber")]
        [Parameter(ParameterSetName="KelvinByName")]
        [decimal]$Brightness,

        #Saturation: How strong the color is. So a Zero saturation is completely white, whilst full saturation is the full color
        [ValidateRange(0, 100)]
        [Parameter(ParameterSetName="Color")]
        [decimal]$Saturation = 0,

        #the number of seconds it will take the light to change color
        [Parameter(ParameterSetName="Color")]
        [Parameter(ParameterSetName="KelvinByNumber")]
        [Parameter(ParameterSetName="KelvinByName")]
        [decimal]$SecondsToTransition = 0,
        
        #Kelvin: Allowed range aka the "temperature" when the device has zero saturation. A higher value is a
        #cooler white (more blue) whereas a lower value is a warmer white (more yellow)
        [ValidateRange(1000, 12000)]
        [Parameter(ParameterSetName="KelvinByNumber")]
        [decimal]$Kelvin,
        
        #kelvin values by name as defined by Lifx
        [Parameter(ParameterSetName="KelvinByName")]
        [ValidateSet("Candlelight", "Sunset", "Ultra Warm", "Incandescent", "Warm", "Neutral", "Cool", "Cool Daylight", "Soft Daylight",
            "Daylight", "Noon Daylight", "Bright Daylight", "Cloudy Daylight", "Blue Daylight", "Blue Overcast", "Blue Ice")]
        [string]$White
    )

    #declare the counters
    [int]$Total = $Input.Count
    [int]$Count = 0

    #convert the White value to Kelvin
    switch ($White) {
        "Candlelight" { $Kelvin = 1500 }
        "Sunset" { $Kelvin = 2000 }
        "Ultra Warm" { $Kelvin = 2500 }
        "Incandescent" { $Kelvin = 2700 }
        "Warm" { $Kelvin = 3000 }
        "Neutral" { $Kelvin = 3500 }
        "Cool" { $Kelvin = 4000 }
        "Cool Daylight" { $Kelvin = 4500 }
        "Soft Daylight" { $Kelvin = 5000 }
        "Daylight" { $Kelvin = 5600 }
        "Noon Daylight" { $Kelvin = 6000 }
        "Bright Daylight" { $Kelvin = 6500 }
        "Cloudy Daylight" { $Kelvin = 7000 }
        "Blue Daylight" { $Kelvin = 7500 }
        "Blue Overcast" { $Kelvin = 8000 }
        "Blue Ice" { $Kelvin = 9000 }
    }

    #convert inputs
    #https://lan.developer.lifx.com/docs/representing-color-with-hsbk
    $Brightness = $Brightness/100
    $Saturation = $Saturation/100
    $hscolor = [System.Drawing.Color]::FromArgb($red, $green, $blue)
    $hsbrightness = [int]([Math]::Round(0xFFFF * $Brightness))
    $hshue = [int](([Math]::Round(0x10000 * $hscolor.GetHue()) / 360) % 0x10000)
    $hsSaturation = [int]([Math]::Round(0xFFFF * $Saturation))

    #build the packet
    $packet = [PSCustomObject]@{
        size       = 49;
        hue        = [uint16]$hshue
        saturation = [uint16]$hsSaturation #0 #65535
        brightness = [uint16]$hsbrightness
        kelvin     = [uint16]$Kelvin
        duration   = [uint32]($SecondsToTransition * 1000)
        packet_type        = [uint16]102;
        protocol           = [uint16]21504;
        reserved1          = [uint32]0;
        reserved2          = [uint32]0;
        reserved3          = [uint32]0;
        reserved4          = [uint32]0;
        reserved5          = [uint32]0;
        reserved6          = [uint32]0;
        site               = New-Object byte[] 8
        target_mac_address = New-Object byte[] 8
        timestamp          = [uint64]0
    }

    #convert the packet to a byte array
    [Byte[]]$buffer = New-Object byte[] $packet.size;
    [System.Array]::Copy([System.BitConverter]::GetBytes($packet.size), 0, $buffer, 0, 2);
    [System.Array]::Copy([System.BitConverter]::GetBytes($packet.protocol), 0, $buffer, 2, 2);
    [System.Array]::Copy([System.BitConverter]::GetBytes($packet.reserved1), 0, $buffer, 4, 4);
    [System.Array]::Copy($packet.target_mac_address, 0, $buffer, 8, 6);
    [System.Array]::Copy([System.BitConverter]::GetBytes($packet.reserved2), 0, $buffer, 14, 2);
    [System.Array]::Copy($packet.site, 0, $buffer, 16, 6);
    [System.Array]::Copy([System.BitConverter]::GetBytes($packet.reserved3), 0, $buffer, 22, 2);
    [System.Array]::Copy([System.BitConverter]::GetBytes($packet.timestamp), 0, $buffer, 24, 8);
    [System.Array]::Copy([System.BitConverter]::GetBytes($packet.packet_type), 0, $buffer, 32, 2);
    [System.Array]::Copy([System.BitConverter]::GetBytes($packet.reserved4), 0, $buffer, 30, 2);
    [System.Array]::Copy([System.BitConverter]::GetBytes($packet.reserved6), 0, $buffer, 34, 2);
    [System.Array]::Copy([System.BitConverter]::GetBytes($packet.hue), 0, $buffer, 37, 2);
    [System.Array]::Copy([System.BitConverter]::GetBytes($packet.saturation), 0, $buffer, 39, 2);
    [System.Array]::Copy([System.BitConverter]::GetBytes($packet.brightness), 0, $buffer, 41, 2);
    [System.Array]::Copy([System.BitConverter]::GetBytes($packet.kelvin), 0, $buffer, 43, 2);
    [System.Array]::Copy([System.BitConverter]::GetBytes($packet.duration), 0, $buffer, 45, 4);
    [Byte[]]$payload = New-Object byte[] 0 #$packet.GetPayloadBuffer();
    [System.Array]::Copy($payload, 0, $buffer, 36, $payload.Length);

    #Device packets
    [Byte[]]$changeColorPacket = $buffer

    #process
    $Input | ForEach-Object {
        #constants
        $Port = "56700"
        $localIP = [System.Net.IPAddress]::Parse([System.Net.IPAddress]::Any)
        $RemoteIpEndPoint = New-Object System.Net.IPEndpoint($localIP, $Port)
        $receivingUdpClient = $null
        $receivingUdpClient = New-Object System.Net.Sockets.UDPClient($RemoteIpEndPoint)
        $receivingUdpClient.Client.Blocking = $false
        $receivingUdpClient.DontFragment = $true
        $receivingUdpClient.Client.SetSocketOption([System.Net.Sockets.SocketOptionLevel]::Socket, [System.Net.Sockets.SocketOptionName]::ReuseAddress, $true)

        #send the SetColor packet to the device
        $send = $receivingUdpClient.SendAsync($changeColorPacket, $changeColorPacket.Length, $_.IPAddress.Address, $_.IPAddress.Port)
    
        #shut the udp client down
        $receivingUdpClient.Dispose()
        $receivingUdpClient.Close()

        $Count++
        [int]$percentComplete = ($Count/$Total* 100)
        Write-Progress -Activity "Changing Lifx Device Color" -PercentComplete $percentComplete -Status ("$($_.Name) color changed - " + $percentComplete + "%")
    }
}