Set-Twinkly.ps1

function Set-Twinkly
{
    <#
    .Synopsis
        Sets Twinkly Lights
    .Description
        Sets Twinkly Lights. Changes colors, mode of operation
    .Example
        Set-Twinkly -Hue 360 -Saturation 100 -Luminance 1
    .Link
        Get-Twinkly
    .Link
        Connect-Twinkly
    #>

    [OutputType([PSObject])]
    param(
    # One or more IP Addresses of Twinkly devices.
    [Parameter(ValueFromPipelineByPropertyName)]
    [Alias('TwinklyIPAddress')]
    [IPAddress[]]
    $IPAddress,

    # Sets the hue of all lights in a fixture
    [Parameter(ValueFromPipelineByPropertyName)]
    [double]
    $Hue,

    # Sets the saturation of all lights in a fixture
    [Parameter(ValueFromPipelineByPropertyName)]
    [ValidateRange(0,1)]
    [double]
    $Saturation,

    # Sets the brightness of all lights in a fixture
    # When passed with -Hue and -Saturation, sets the color
    # When passed with no other parameters, adjusts the absolute brightness
    [Parameter(ValueFromPipelineByPropertyName)]
    [Alias('Luminance')]
    [ValidateRange(0,1)]
    [double]
    $Brightness,

    # Sets the red part of a color
    [Parameter(ValueFromPipelineByPropertyName)]
    [byte]
    $Red,

    # Sets the green part of a color
    [Parameter(ValueFromPipelineByPropertyName)]
    [byte]
    $Green,

    # Sets the blue part of a color
    [Parameter(ValueFromPipelineByPropertyName)]
    [byte]
    $Blue,

    # Sets lights to an RGB color.
    # If one color is provided, this will set the Twinkly color mode
    # If more than one RGB Color is provided, this will create a movie where each light alternates between each color
    # If -MovieFrameRate and
    [Parameter(ValueFromPipelineByPropertyName)]
    [string[]]
    $RGBColor,

    <#

    Will change the mode. Can be:

    * Off
    * Color
    * Demo
    * Effect
    * Movie
    * Playlist
    * RT

    Many operations require the mode to be set before they take effect.

    For instance, if you use -RGBColor, you will not see the changes if the mode is not set to 'color'.
    #>

    [Parameter(ValueFromPipelineByPropertyName)]
    [ValidateSet('off','color', 'demo','effect','movie','playlist','rt')]
    [string]
    $Mode,

    # If provided, will set the device name.
    [Parameter(ValueFromPipelineByPropertyName)]
    [string]
    $DeviceName,

    # If provided, will restart the playlist
    [Parameter(ValueFromPipelineByPropertyName)]
    [switch]
    $RestartPlaylist,

    # The name of a movie.
    [Parameter(ValueFromPipelineByPropertyName)]
    [string]
    $MovieName,

    # The movie framerate
    [Parameter(ValueFromPipelineByPropertyName)]
    [Alias('MovieFPS')]
    [int]
    $MovieFramerate,

    # The movie LED
    [Parameter(ValueFromPipelineByPropertyName)]
    [Alias('MovieLEDsPerFrame')]
    [int]
    $MovieLEDCount,

    # The number of frames in the movie.
    [Parameter(ValueFromPipelineByPropertyName)]
    [int]
    $MovieFrameCount,

    # A byte array of movie data.
    # Each sequence of 3-4 bytes represents a light color
    # Each sequence of colors represents a frame
    # Each sequence of frames represents a movie.
    [Parameter(ValueFromPipelineByPropertyName)]
    [Alias('MovieFrame','MovieFrames')]
    [byte[]]
    $MovieData,

    # The size of each block within a movie. By default, 3.
    [Parameter(ValueFromPipelineByPropertyName)]
    [byte]
    $MovieBlockSize = 3
    )
    begin {
        if (-not $script:TwinklyCache) {
            $script:TwinklyCache = @{}
        }
        if ($home) {
            $lightScriptRoot = Join-Path $home -ChildPath LightScript
        }
    }

    process {
        if (-not $IPAddress) {
            if ($home) {
                Get-ChildItem -Path $lightScriptRoot -ErrorAction SilentlyContinue -Filter *.twinkly.clixml -Force |
                    Import-Clixml |
                    ForEach-Object {
                        if (-not $_) { return }
                        $twinklyConnection = $_
                        if ([DateTime]::Now -ge $twinklyConnection.Authentication_ExpiresAt) {
                            $twinklyConnection = Connect-Twinkly -IPAddress $twinklyConnection.IPAddress -PassThru
                        }
                        $script:TwinklyCache["$($twinklyConnection.IPAddress)"] = $twinklyConnection
                    }

                $IPAddress = $script:TwinklyCache.Keys
            }
            if (-not $IPAddress) {
                Write-Warning "No -IPAddress provided and no cached devices found"
                return
            }
        }


        $myParams = [Ordered]@{} + $PSBoundParameters

        foreach ($ip in $IPAddress) {
            $Splat = @{
                Headers= @{"X-Auth-Token" =$script:TwinklyCache["$ip"].Authentication_Token}
                Method = 'POST'
            }
            #region Set Light Color
            if ($myParams.Contains('Red') -and $myParams.Contains('Blue') -and $myParams.Contains('Green'))
            {
                Invoke-RestMethod -Uri "http://$ip/xled/v1/led/color" @Splat -Body (
                    @{
                        red        = $red
                        blue       = $blue
                        green      = $green
                    } | ConvertTo-Json
                )
            }
            elseif ($myParams.Contains('RGBColor') -and $RGBColor.Count -eq 1)
            {
                $fromRgb = ([PSCustomObject]@{PSTypeName='LightScript.Color'}).FromRGB($RGBColor)
                Invoke-RestMethod -Uri "http://$ip/xled/v1/led/color" @Splat -Body (
                    @{
                        red        = $fromRgb.red
                        blue       = $fromRgb.blue
                        green      = $fromRgb.green
                    } | ConvertTo-Json
                )
            }
            elseif ($myParams.Contains('Hue') -and $myParams.Contains('Saturation') -and $myParams.Contains('Brightness'))
            {
                Invoke-RestMethod -Uri "http://$ip/xled/v1/led/color" @Splat -Body (
                    @{
                        hue        = $hue % 360
                        saturation = [byte][Math]::Ceiling([Byte]::MaxValue * $Saturation)
                        value      = [byte][Math]::Ceiling([Byte]::MaxValue * $Brightness)
                    } | ConvertTo-Json
                )
            }
            #endregion Set Light Color
            elseif ($myParams.Contains("Brightness"))
            {
                Invoke-RestMethod -Uri "http://$ip/xled/v1/led/out/brightness" @Splat -Body (
                    @{
                        mode = 'enabled'
                        type = 'A'
                        value = [int]($Brightness * 100)
                    } | ConvertTo-Json
                )
            }

            if ($FrameDelay) {
                Invoke-RestMethod -Uri "http://$ip/xled/v1/led/movie/config" @Splat -Body (
                    @{
                        frame_delay = $FrameDelay
                    } | ConvertTo-Json
                )
            }

            #region Set Movie
            if ($RGBColor.Count -gt 1 -and -not $MovieData) {
                $MovieData =
                    foreach ($hexCol in $RGBColor) {
                        $Rgb = ([PSCustomObject]@{PSTypeName='LightScript.Color'}).FromRGB($hexCol)
                        $Rgb.Red
                        $Rgb.Green
                        $Rgb.Blue
                    }
            }

            if ($MovieData) {
                if (-not $myParams.Contains("MovieLEDCount")) {
                    $MovieLEDCount = Get-Twinkly -IPAddress $ip -Configuration |
                        Select-Object -ExpandProperty Strings |
                        Select-Object -ExpandProperty Length |
                        Measure-Object -Sum |
                        Select-Object -ExpandProperty Sum
                }
                if (-not $myParams.Contains("MovieFrameCount")) {
                    $MovieFrameCount =
                        if ($MovieData.Length -lt $MovieLEDCount) {
                            1
                        } else {
                            [Math]::Floor($MovieData.Length / $MovieLEDCount)
                        }
                }
                if (-not $myParams.Contains("MovieFrameRate")) {
                    $MovieFramerate = 1
                }

                if ($MovieData.Length -lt $MovieLEDCount) {
                    # Movie too short, assume it is a sequence of colors
                    $newMovieData = @(
                        foreach ($movieFrameNumber in 0..($MovieFrameCount - 1)) {
                            for ($b = 0; $b -lt $MovieLEDCount; $b++) {
                                $ptr = ($b  + $movieFrameNumber)* $MovieBlockSize
                                if ($ptr -ge $MovieData.Length) {
                                    $ptr = $ptr % $MovieData.Length
                                }
                                foreach ($n in 0..($MovieBlockSize- 1)) {
                                    $MovieData[$ptr  + $n]
                                }
                            }
                        }) -as [byte[]]
                    $MovieData = $newMovieData
                }

                if (-not $myParams.Contains("MovieName")) {
                    $MovieName = "Movie " + [DateTime]::Now.ToString() -replace '[:/]', '-'
                }

                Invoke-RestMethod -Uri "http://$ip/xled/v1/movies/new" @Splat -Body (
                    @{
                        name = $MovieName
                        unique_id = ([GUID]::NewGuid()).ToString().ToUpper()
                        leds_per_frame = $MovieLEDCount
                        frames_number = $MovieFrameCount
                        fps = $MovieFramerate
                    } | ConvertTo-Json
                )

                Invoke-RestMethod -Uri "$twinklyUrl/xled/v1/movies/full" @Splat -Body $MovieData -ContentType application/octect-stream
            }
            #endregion Set Movie

            #region Reset Playlist
            if ($RestartPlaylist) {
                Invoke-RestMethod -Uri "http://$ip/xled/v1/playlist/current" @Splat -Body (
                    @{
                        id = 0
                    } | ConvertTo-Json
                )
            }
            #endregion Reset Playlist

            #region Set Mode
            if ($Off) {
                $Mode = 'off'
            }

            if ($Mode) {
                Invoke-RestMethod -Uri "http://$ip/xled/v1/led/mode" @Splat -Body (
                    @{
                        mode = $Mode.ToLower()
                    } | ConvertTo-Json
                )
            }
            #endregion Set Mode
        }
    }
}