diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md deleted file mode 100644 index 2d105f9..0000000 --- a/.github/CONTRIBUTING.md +++ /dev/null @@ -1,62 +0,0 @@ -# How to contribute - -Contributions to awtrix are highly encouraged and desired. -Below are some guidelines that will help make the process as smooth as possible. - -## Getting Started - -- Make sure you have a [GitHub account](https://github.com/signup/free) -- Submit a new issue, assuming one does not already exist. - - Clearly describe the issue including steps to reproduce when it is a bug. - - Make sure you fill in the earliest version that you know has the issue. -- Fork the repository on GitHub - -## Suggesting Enhancements - -I want to know what you think is missing from awtrix and how it can be made better. - -- When submitting an issue for an enhancement, please be as clear as possible about why you think the enhancement is needed and what the benefit of it would be. - -## Making Changes - -- From your fork of the repository, create a topic branch where work on your change will take place. -- To quickly create a topic branch based on master; `git checkout -b my_contribution master`. - Please avoid working directly on the `master` branch. -- Make commits of logical units. -- Check for unnecessary whitespace with `git diff --check` before committing. -- Please follow the prevailing code conventions in the repository. - Differences in style make the code harder to understand for everyone. -- Make sure your commit messages are in the proper format. - -``` - Add more cowbell to Get-Something.ps1 - - The functionality of Get-Something would be greatly improved if there was a little - more 'pizzazz' added to it. I propose a cowbell. Adding more cowbell has been - shown in studies to both increase one's mojo, and cement one's status - as a rock legend. -``` - -- Make sure you have added all the necessary Pester tests for your changes. -- Run _all_ Pester tests in the module to assure nothing else was accidentally broken. - -## Documentation - -I am infallible and as such my documenation needs no corectoin. -In the highly unlikely event that that is _not_ the case, commits to update or add documentation are highly apprecaited. - -## Submitting Changes - -- Push your changes to a topic branch in your fork of the repository. -- Submit a pull request to the main repository. -- Once the pull request has been reviewed and accepted, it will be merged with the master branch. -- Celebrate - -## Additional Resources - -- [General GitHub documentation](https://help.github.com/) -- [GitHub forking documentation](https://guides.github.com/activities/forking/) -- [GitHub pull request documentation](https://help.github.com/send-pull-requests/) -- [GitHub Flow guide](https://guides.github.com/introduction/flow/) -- [GitHub's guide to contributing to open source projects](https://guides.github.com/activities/contributing-to-open-source/) - diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md deleted file mode 100644 index 6b95148..0000000 --- a/.github/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,31 +0,0 @@ - - -## Expected Behavior - - - -## Current Behavior - - - -## Possible Solution - - - -## Steps to Reproduce (for bugs) - - -1. -2. -3. -4. - -## Context - - - -## Your Environment - -* Module version used: -* Operating System and PowerShell version: - diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md deleted file mode 100644 index 78d0659..0000000 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ /dev/null @@ -1,37 +0,0 @@ - - -## Description - - -## Related Issue - - - - - -## Motivation and Context - - -## How Has This Been Tested? - - - - -## Screenshots (if appropriate): - -## Types of changes - -- [ ] Bug fix (non-breaking change which fixes an issue) -- [ ] New feature (non-breaking change which adds functionality) -- [ ] Breaking change (fix or feature that would cause existing functionality to change) - -## Checklist: - - -- [ ] My code follows the code style of this project. -- [ ] My change requires a change to the documentation. -- [ ] I have updated the documentation accordingly. -- [ ] I have read the **CONTRIBUTING** document. -- [ ] I have added tests to cover my changes. -- [ ] All new and existing tests passed. - diff --git a/.vscode/settings.json b/.vscode/settings.json index ec55855..619df75 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -6,5 +6,14 @@ "powershell.codeFormatting.preset": "OTBS", "cSpell.words": [ "AWTRIX" - ] + ], + "files.exclude": { + "**/bin": true, + "**/obj": true, + "**/.vscode": true, + "output/**": true + }, + "files.readonlyInclude": { + "output/**": true + } } diff --git a/CHANGELOG.md b/CHANGELOG.md index da0c283..f8aa0c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,51 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +## [0.4.0] 2026-03-14 + +### Added + +- **First-Class App Objects** — Apps and notifications are now full-fledged + stateful PowerShell objects with methods, properties, and lifecycle + management, enabling sophisticated scenarios: + - `AwtrixApp` class — Represents a persistent custom app with state + management, dirty tracking, and serialization + - `AwtrixNotification` class — Represents a one-time notification with + hold, sound, wakeup, and stacking support + - `AwtrixAppBase` base class — Shared infrastructure for 24 display + properties (text, colors, effects, charts, drawing, overlays, etc.) + - `AwtrixAppCollection` class — Multi-page app batching via single API + call (device-assigned suffixes: BaseName0, BaseName1, etc.) +- `New-AwtrixApp` cmdlet — Create and optionally push `[AwtrixApp]` + objects. Returns object for property mutation, template cloning, and + serialization workflows +- `New-AwtrixNotification` cmdlet — Create `[AwtrixNotification]` objects + for deferred or immediate dispatch +- `Update-AwtrixApp` cmdlet — Pipeline cmdlet to update app properties and + push. Supports `-DirtyOnly` for incremental updates (only changed + properties sent to device) +- `New-AwtrixAppCollection` cmdlet — Batch multi-page apps with unified + push/remove operations +- Object methods: `Push()`, `Push($dirtyOnly)`, `Remove()`, `SwitchTo()`, + `Send()` for imperative app lifecycle +- Object methods: `ToJson()`, `FromJson()` for config serialization and + restoration +- Object method: `Clone($newName)` for template/variant patterns +- Object method: `GetDirtyPayload()` for incremental updates +- Type accelerators for new classes: `[AwtrixApp]`, `[AwtrixNotification]`, + `[AwtrixAppBase]`, `[AwtrixAppCollection]` + +### Changed + +- `Set-AwtrixApp` now internally uses `[AwtrixApp]` class and supports + `-PassThru` to return the object. Fully backward-compatible — omit + `-PassThru` for fire-and-forget behavior (default) +- `Send-AwtrixNotification` now internally uses `[AwtrixNotification]` + class and supports `-PassThru`. Fully backward-compatible +- Module loading now establishes static delegate + `[AwtrixAppBase]::InvokeApi` during initialization to enable class + methods to call module-scoped `InvokeAwtrixApi` function + ## [0.3.0] 2026-03-13 ### Added diff --git a/awtrix/Class/AwtrixApp.ps1 b/awtrix/Class/AwtrixApp.ps1 new file mode 100644 index 0000000..fd180d6 --- /dev/null +++ b/awtrix/Class/AwtrixApp.ps1 @@ -0,0 +1,432 @@ +# Base class shared by AwtrixApp and AwtrixNotification. +# Classes defined in ScriptsToProcess run outside the module scope, so they +# cannot call InvokeAwtrixApi directly. The module wires up the static delegate +# [AwtrixAppBase]::InvokeApi during module load (see awtrix.psm1). +class AwtrixAppBase { + + # ── Shared API properties ──────────────────────────────────────────────── + + # Text to display. String or array of colored fragment hashtables. + $Text + + # 0 = global setting, 1 = force uppercase, 2 = show as sent. + [ValidateRange(0, 2)] + [System.Nullable[int]] $TextCase + + # Draw text on top of display. + [System.Nullable[bool]] $TopText + + # X-axis offset for the starting text position. + [System.Nullable[int]] $TextOffset + + # Centers a short, non-scrollable text. + [System.Nullable[bool]] $Center + + # Text/bar/line color. Hex string or RGB array. + $Color + + # Two-color gradient for text. Array of two color values. + $Gradient + + # Blink interval in milliseconds. Not compatible with gradient/rainbow. + [System.Nullable[int]] $BlinkTextMilliseconds + + # Fade-on/off interval in milliseconds. Not compatible with gradient/rainbow. + [System.Nullable[int]] $FadeTextMilliseconds + + # Background color. Hex string or RGB array. + $Background + + # Fade each letter through the full RGB spectrum. + [System.Nullable[bool]] $Rainbow + + # Icon ID, filename (no extension), or Base64-encoded 8×8 JPG. + [string] $Icon + + # 0 = static, 1 = moves with text once, 2 = moves with text repeatedly. + [ValidateRange(0, 2)] + [System.Nullable[int]] $PushIcon + + # Scroll count before app/notification ends. -1 = indefinite. + [System.Nullable[int]] $Repeat + + # Display duration in seconds. + [System.Nullable[int]] $DurationSeconds + + # Disables text scrolling. + [System.Nullable[bool]] $NoScroll + + # Scroll speed as a percentage of normal speed (e.g. 200 = 2×). + [System.Nullable[int]] $ScrollSpeed + + # Background effect name. Empty string clears an existing effect. + [string] $Effect + + # Color/speed overrides for the background effect. + [hashtable] $EffectSettings + + # Bar chart data. Max 16 values without icon, 11 with icon. + [int[]] $Bar + + # Line chart data. Max 16 values without icon, 11 with icon. + [int[]] $Line + + # Auto-scale bar/line chart axes. + [System.Nullable[bool]] $Autoscale + + # Background color of bar chart bars. + $BarBackgroundColor + + # Progress bar value 0-100. -1 disables. + [ValidateRange(-1, 100)] + [System.Nullable[int]] $Progress + + # Progress bar foreground color. + $ProgressColor + + # Progress bar background color. + $ProgressBackgroundColor + + # Drawing instructions. Use New-AwtrixDrawing to build entries. + [array] $Draw + + # Effect overlay: clear, snow, rain, drizzle, storm, thunder, frost. + [ValidateSet('', 'clear', 'snow', 'rain', 'drizzle', 'storm', 'thunder', 'frost')] + [string] $Overlay + + # ── Module API delegate (set by awtrix.psm1 during module load) ────────── + static [scriptblock] $InvokeApi + + # Per-object BaseUri override. $null = use module-level connection. + hidden [string] $_baseUri + + # Payload snapshot taken after last Push/Send, used for dirty tracking. + hidden [hashtable] $_lastPushed + + # ── Canonical property → API-key mapping ──────────────────────────────── + static [hashtable] GetPropertyMap() { + return @{ + Text = 'text' + TextCase = 'textCase' + TopText = 'topText' + TextOffset = 'textOffset' + Center = 'center' + Color = 'color' + Gradient = 'gradient' + BlinkTextMilliseconds = 'blinkText' + FadeTextMilliseconds = 'fadeText' + Background = 'background' + Rainbow = 'rainbow' + Icon = 'icon' + PushIcon = 'pushIcon' + Repeat = 'repeat' + DurationSeconds = 'duration' + NoScroll = 'noScroll' + ScrollSpeed = 'scrollSpeed' + Effect = 'effect' + EffectSettings = 'effectSettings' + Bar = 'bar' + Line = 'line' + Autoscale = 'autoscale' + BarBackgroundColor = 'barBC' + Progress = 'progress' + ProgressColor = 'progressC' + ProgressBackgroundColor = 'progressBC' + Draw = 'draw' + Overlay = 'overlay' + } + } + + # ── ToPayload ──────────────────────────────────────────────────────────── + # Returns an API-ready hashtable containing only properties with non-null, + # non-empty values so we never send noise to the device. + [hashtable] ToPayload() { + $map = [AwtrixAppBase]::GetPropertyMap() + $body = @{} + foreach ($prop in $map.Keys) { + $val = $this.$prop + if ($null -eq $val) { continue } + # Skip empty strings unless the property is Effect (empty string is + # a documented signal meaning "remove the current effect"). + if ($val -is [string] -and $val -eq '' -and $prop -ne 'Effect') { continue } + # Skip arrays and hashtables that are empty. + if ($val -is [array] -and $val.Count -eq 0) { continue } + if ($val -is [hashtable] -and $val.Count -eq 0) { continue } + $body[$map[$prop]] = $val + } + return $body + } + + # ── ToJson ─────────────────────────────────────────────────────────────── + [string] ToJson() { + return $this.ToPayload() | ConvertTo-Json -Depth 10 -Compress + } + + # ── Dirty Tracking ─────────────────────────────────────────────────────── + # Snapshot current payload, marking it as "clean". + [void] ResetDirtyState() { + $this._lastPushed = $this.ToPayload() + } + + # Returns only the keys whose values differ from the last push snapshot. + # On first call (no snapshot), returns the full payload. + [hashtable] GetDirtyPayload() { + if ($null -eq $this._lastPushed) { + return $this.ToPayload() + } + $current = $this.ToPayload() + $dirty = @{} + # Keys present in current that are new or changed. + foreach ($key in $current.Keys) { + $prev = $this._lastPushed[$key] + $curr = $current[$key] + if ($null -eq $prev) { + $dirty[$key] = $curr + } elseif ($curr -is [array] -or $curr -is [hashtable]) { + # Deep compare via JSON serialization. + if (($curr | ConvertTo-Json -Depth 10 -Compress) -ne + ($prev | ConvertTo-Json -Depth 10 -Compress)) { + $dirty[$key] = $curr + } + } elseif ($curr -ne $prev) { + $dirty[$key] = $curr + } + } + # Keys that existed before but are now absent (set to null/empty) — send + # the current (absent) value so callers can detect the removal if needed. + # For simplicity we leave removed keys out; callers can always do a full push. + return $dirty + } + + # ── Clone (base - subclasses override to carry their extra properties) ── + [AwtrixAppBase] CloneBase() { + $copy = [AwtrixAppBase]::new() + $copy._baseUri = $this._baseUri + foreach ($prop in [AwtrixAppBase]::GetPropertyMap().Keys) { + # Guard against null to avoid [ValidateRange] rejecting null assignments. + if ($null -ne $this.$prop) { $copy.$prop = $this.$prop } + } + return $copy + } +} + +# ── AwtrixApp ──────────────────────────────────────────────────────────────── +# Represents a persistent custom app in the AWTRIX display loop. +class AwtrixApp : AwtrixAppBase { + + # Unique app name used to identify and update the app. + [string] $Name + + # Remove app after no update within this many seconds. 0 = disabled. + [System.Nullable[int]] $LifetimeSeconds + + # 0 = delete app on expiry, 1 = mark as stale with red border. + [ValidateRange(0, 1)] + [System.Nullable[int]] $LifetimeMode + + # Loop position (0-based). Only applied on first push. Experimental. + [System.Nullable[int]] $Position + + # Persist app across reboots. Avoid for high-frequency updates. + [System.Nullable[bool]] $Save + + # ── Constructors ───────────────────────────────────────────────────────── + AwtrixApp() {} + + AwtrixApp([string]$name) { + $this.Name = $name + } + + # ── ToPayload override — merges app-only fields ────────────────────────── + [hashtable] ToPayload() { + $body = ([AwtrixAppBase]$this).ToPayload() + if ($null -ne $this.LifetimeSeconds) { $body['lifetime'] = $this.LifetimeSeconds } + if ($null -ne $this.LifetimeMode) { $body['lifetimeMode'] = $this.LifetimeMode } + if ($null -ne $this.Position) { $body['pos'] = $this.Position } + if ($null -ne $this.Save) { $body['save'] = $this.Save } + return $body + } + + # ── Push — sends the full payload (or only dirty keys) to the device ───── + [void] Push() { + $this.Push($false) + } + + [void] Push([bool]$dirtyOnly) { + if ([string]::IsNullOrWhiteSpace($this.Name)) { + throw 'AwtrixApp.Name must be set before calling Push().' + } + $body = if ($dirtyOnly) { $this.GetDirtyPayload() } else { $this.ToPayload() } + & ([AwtrixAppBase]::InvokeApi) -Endpoint 'custom' -Method POST -Body $body ` + -QueryString "name=$($this.Name)" -BaseUri $this._baseUri + $this.ResetDirtyState() + } + + # ── Remove — deletes the app from the device ───────────────────────────── + [void] Remove() { + if ([string]::IsNullOrWhiteSpace($this.Name)) { + throw 'AwtrixApp.Name must be set before calling Remove().' + } + & ([AwtrixAppBase]::InvokeApi) -Endpoint 'custom' -Method POST ` + -QueryString "name=$($this.Name)" -BaseUri $this._baseUri + } + + # ── SwitchTo — navigates the device to this app ────────────────────────── + [void] SwitchTo() { + if ([string]::IsNullOrWhiteSpace($this.Name)) { + throw 'AwtrixApp.Name must be set before calling SwitchTo().' + } + & ([AwtrixAppBase]::InvokeApi) -Endpoint 'switch' -Method POST ` + -Body @{ name = $this.Name } -BaseUri $this._baseUri + } + + # ── Clone ──────────────────────────────────────────────────────────────── + [AwtrixApp] Clone() { + return $this.Clone($this.Name) + } + + [AwtrixApp] Clone([string]$newName) { + $copy = [AwtrixApp]::new($newName) + $copy._baseUri = $this._baseUri + # Guard against null to avoid [ValidateRange] rejecting null assignments. + if ($null -ne $this.LifetimeSeconds) { $copy.LifetimeSeconds = $this.LifetimeSeconds } + if ($null -ne $this.LifetimeMode) { $copy.LifetimeMode = $this.LifetimeMode } + if ($null -ne $this.Position) { $copy.Position = $this.Position } + if ($null -ne $this.Save) { $copy.Save = $this.Save } + foreach ($prop in [AwtrixAppBase]::GetPropertyMap().Keys) { + if ($null -ne $this.$prop) { $copy.$prop = $this.$prop } + } + return $copy + } + + # ── Serialization ──────────────────────────────────────────────────────── + [string] ToJson() { + $payload = $this.ToPayload() + $payload['_name'] = $this.Name + if ($null -ne $this._baseUri) { $payload['_baseUri'] = $this._baseUri } + return $payload | ConvertTo-Json -Depth 10 -Compress + } + + static [AwtrixApp] FromJson([string]$json) { + $data = $json | ConvertFrom-Json -AsHashtable + $reverseMap = @{} + foreach ($kv in [AwtrixAppBase]::GetPropertyMap().GetEnumerator()) { + $reverseMap[$kv.Value] = $kv.Key + } + # App-only reverse mapping + $appKeys = @{ lifetime = 'LifetimeSeconds'; lifetimeMode = 'LifetimeMode' + pos = 'Position'; save = 'Save' + } + + $app = [AwtrixApp]::new() + foreach ($key in $data.Keys) { + if ($key -eq '_name') { $app.Name = $data[$key]; continue } + if ($key -eq '_baseUri') { $app._baseUri = $data[$key]; continue } + if ($reverseMap.ContainsKey($key)) { $app.($reverseMap[$key]) = $data[$key]; continue } + if ($appKeys.ContainsKey($key)) { $app.($appKeys[$key]) = $data[$key]; continue } + } + return $app + } +} + +# ── AwtrixNotification ─────────────────────────────────────────────────────── +# Represents a one-time notification that interrupts the app loop. +class AwtrixNotification : AwtrixAppBase { + + # Hold notification until middle-button press or API dismiss. + [System.Nullable[bool]] $Hold + + # RTTTL ringtone filename (no extension) or DFplayer 4-digit MP3 number. + [string] $Sound + + # Inline RTTTL sound string played with the notification. + [string] $Rtttl + + # Loop sound/RTTTL for the duration of the notification. + [System.Nullable[bool]] $LoopSound + + # Stack notification (true) or replace current notification (false). + [System.Nullable[bool]] $Stack + + # Wake the matrix for this notification if the display is off. + [System.Nullable[bool]] $Wakeup + + # Forward notification to additional AWTRIX devices by IP. + [string[]] $Clients + + # ── Constructors ───────────────────────────────────────────────────────── + AwtrixNotification() {} + + AwtrixNotification([string]$text) { + $this.Text = $text + } + + # ── ToPayload override — merges notification-only fields ───────────────── + [hashtable] ToPayload() { + $body = ([AwtrixAppBase]$this).ToPayload() + if ($null -ne $this.Hold) { $body['hold'] = $this.Hold } + if ($null -ne $this.LoopSound) { $body['loopSound'] = $this.LoopSound } + if ($null -ne $this.Stack) { $body['stack'] = $this.Stack } + if ($null -ne $this.Wakeup) { $body['wakeup'] = $this.Wakeup } + if (-not [string]::IsNullOrEmpty($this.Sound)) { $body['sound'] = $this.Sound } + if (-not [string]::IsNullOrEmpty($this.Rtttl)) { $body['rtttl'] = $this.Rtttl } + if ($null -ne $this.Clients -and $this.Clients.Count -gt 0) { + $body['clients'] = $this.Clients + } + return $body + } + + # ── Send — dispatches the notification to the device ───────────────────── + [void] Send() { + $body = $this.ToPayload() + & ([AwtrixAppBase]::InvokeApi) -Endpoint 'notify' -Method POST ` + -Body $body -BaseUri $this._baseUri + $this.ResetDirtyState() + } + + # ── Clone ──────────────────────────────────────────────────────────────── + [AwtrixNotification] Clone() { + $copy = [AwtrixNotification]::new() + $copy._baseUri = $this._baseUri + $copy.Hold = $this.Hold + $copy.Sound = $this.Sound + $copy.Rtttl = $this.Rtttl + $copy.LoopSound = $this.LoopSound + $copy.Stack = $this.Stack + $copy.Wakeup = $this.Wakeup + $copy.Clients = $this.Clients + foreach ($prop in [AwtrixAppBase]::GetPropertyMap().Keys) { + if ($this.$prop -ne $null) { + $copy.$prop = $this.$prop + } + } + return $copy + } + + # ── Serialization ──────────────────────────────────────────────────────── + [string] ToJson() { + $payload = $this.ToPayload() + if ($null -ne $this._baseUri) { $payload['_baseUri'] = $this._baseUri } + return $payload | ConvertTo-Json -Depth 10 -Compress + } + + static [AwtrixNotification] FromJson([string]$json) { + $data = $json | ConvertFrom-Json -AsHashtable + $reverseMap = @{} + foreach ($kv in [AwtrixAppBase]::GetPropertyMap().GetEnumerator()) { + $reverseMap[$kv.Value] = $kv.Key + } + $notifKeys = @{ hold = 'Hold'; sound = 'Sound'; rtttl = 'Rtttl' + loopSound = 'LoopSound'; stack = 'Stack' + wakeup = 'Wakeup'; clients = 'Clients' + } + + $notif = [AwtrixNotification]::new() + foreach ($key in $data.Keys) { + if ($key -eq '_baseUri') { $notif._baseUri = $data[$key]; continue } + if ($reverseMap.ContainsKey($key)) { $notif.($reverseMap[$key]) = $data[$key]; continue } + if ($notifKeys.ContainsKey($key)) { $notif.($notifKeys[$key]) = $data[$key]; continue } + } + return $notif + } +} diff --git a/awtrix/Class/AwtrixAppCollection.ps1 b/awtrix/Class/AwtrixAppCollection.ps1 new file mode 100644 index 0000000..08c0a57 --- /dev/null +++ b/awtrix/Class/AwtrixAppCollection.ps1 @@ -0,0 +1,48 @@ +# Represents a multi-page custom app group sent as an array to AWTRIX. +# The API assigns suffixes automatically: BaseName0, BaseName1, etc. +# Removing the BaseName prefix removes all pages in the group. +class AwtrixAppCollection { + + # Base name shared by all pages (no numeric suffix needed here). + [string] $BaseName + + # Ordered array of AwtrixApp pages. Each page's Name property is ignored + # on push — the device assigns suffixes from this array's order. + [AwtrixApp[]] $Apps + + # Per-collection BaseUri override. + hidden [string] $_baseUri + + # ── Constructors ───────────────────────────────────────────────────────── + AwtrixAppCollection() {} + + AwtrixAppCollection([string]$baseName, [AwtrixApp[]]$apps) { + $this.BaseName = $baseName + $this.Apps = $apps + } + + # ── Push — sends the array payload in one request ───────────────────────── + # AWTRIX handles suffix assignment internally when an array is POSTed to + # /api/custom?name=. + [void] Push() { + if ([string]::IsNullOrWhiteSpace($this.BaseName)) { + throw 'AwtrixAppCollection.BaseName must be set before calling Push().' + } + if ($null -eq $this.Apps -or $this.Apps.Count -eq 0) { + throw 'AwtrixAppCollection.Apps must contain at least one page.' + } + # Build an array of payload hashtables — one per page. + $pages = @($this.Apps | ForEach-Object { $_.ToPayload() }) + & ([AwtrixAppBase]::InvokeApi) -Endpoint 'custom' -Method POST ` + -Body $pages -QueryString "name=$($this.BaseName)" -BaseUri $this._baseUri + } + + # ── Remove — deletes all pages by sending an empty payload ──────────────── + [void] Remove() { + if ([string]::IsNullOrWhiteSpace($this.BaseName)) { + throw 'AwtrixAppCollection.BaseName must be set before calling Remove().' + } + & ([AwtrixAppBase]::InvokeApi) -Endpoint 'custom' -Method POST ` + -QueryString "name=$($this.BaseName)" -BaseUri $this._baseUri + } +} diff --git a/awtrix/Private/InvokeAwtrixApi.ps1 b/awtrix/Private/InvokeAwtrixApi.ps1 index 5b4bc05..22651bb 100644 --- a/awtrix/Private/InvokeAwtrixApi.ps1 +++ b/awtrix/Private/InvokeAwtrixApi.ps1 @@ -13,7 +13,7 @@ function InvokeAwtrixApi { [string]$Method = 'GET', [Parameter()] - [hashtable]$Body, + $Body, [Parameter()] [string]$RawBody, diff --git a/awtrix/Public/New-AwtrixApp.ps1 b/awtrix/Public/New-AwtrixApp.ps1 new file mode 100644 index 0000000..86a302c --- /dev/null +++ b/awtrix/Public/New-AwtrixApp.ps1 @@ -0,0 +1,259 @@ +function New-AwtrixApp { + <# + .SYNOPSIS + Creates an AwtrixApp object, optionally pushing it to the device immediately. + .DESCRIPTION + Returns an [AwtrixApp] object that holds the full state of a custom AWTRIX app. + The object can be modified, pushed to the device, cloned into templates, and + serialized to/from JSON — all without additional API calls until you're ready. + + Use -Push to send the app to the device in the same call. Omit -Push to build + the object locally first, set properties, then call $app.Push() when ready. + .PARAMETER Name + The unique app name used to identify and update the app on the device. + .PARAMETER Text + The text to display. A simple string or an array of colored fragment objects + created by New-AwtrixTextFragment. + .PARAMETER TextCase + 0 = global setting, 1 = force uppercase, 2 = show as sent. + .PARAMETER TopText + Draw text on top of the display. + .PARAMETER TextOffset + X-axis offset for the starting text position. + .PARAMETER Center + Centers a short, non-scrollable text. + .PARAMETER Color + Text, bar, or line color. Accepts a named color, hex string, or RGB array. + .PARAMETER Gradient + Colorizes text in a gradient of two colors. + .PARAMETER BlinkTextMilliseconds + Blinks the text at the given interval in ms. Not compatible with gradient or rainbow. + .PARAMETER FadeTextMilliseconds + Fades the text on and off at the given interval in ms. Not compatible with gradient or rainbow. + .PARAMETER Background + Background color. Accepts a named color, hex string, or RGB array. + .PARAMETER Rainbow + Fades each letter through the entire RGB spectrum. + .PARAMETER Icon + Icon ID, filename (without extension), or Base64-encoded 8x8 JPG. + .PARAMETER PushIcon + 0 = static, 1 = moves with text once, 2 = moves with text repeatedly. + .PARAMETER Repeat + Number of times the text scrolls before the app ends. -1 = indefinite. + .PARAMETER DurationSeconds + How long the app is displayed in seconds. + .PARAMETER NoScroll + Disables text scrolling. + .PARAMETER ScrollSpeed + Scroll speed as a percentage of the original speed. + .PARAMETER Effect + Background effect name. Empty string removes an existing effect. + .PARAMETER EffectSettings + Hashtable to change color and speed of the background effect. + .PARAMETER Bar + Bar chart data. Max 16 values without icon, 11 with icon. + .PARAMETER Line + Line chart data. Max 16 values without icon, 11 with icon. + .PARAMETER Autoscale + Enables or disables auto-scaling for bar and line charts. + .PARAMETER BarBackgroundColor + Background color of bar chart bars. + .PARAMETER Progress + Progress bar value 0–100. + .PARAMETER ProgressColor + Progress bar foreground color. + .PARAMETER ProgressBackgroundColor + Progress bar background color. + .PARAMETER Draw + Array of drawing instruction objects. Use New-AwtrixDrawing to create them. + .PARAMETER Overlay + Effect overlay: clear, snow, rain, drizzle, storm, thunder, frost. + .PARAMETER LifetimeSeconds + Removes the app if no update is received within this many seconds. 0 = disabled. + .PARAMETER LifetimeMode + 0 = delete app on expiry, 1 = mark as stale with red border. + .PARAMETER Position + 0-based loop position. Applied only on first push. Experimental. + .PARAMETER Save + Persist app to flash memory across reboots. Avoid for frequently updated apps. + .PARAMETER Push + Send the app to the device immediately after creating the object. + .PARAMETER BaseUri + Base URI of the AWTRIX device. Overrides the module-level connection for this app. + .EXAMPLE + PS> $app = New-AwtrixApp -Name 'weather' -Icon 'temperature' -Color '#FF6600' + PS> $app.Text = '72°F' + PS> $app.Push() + + Creates an app object locally, sets text, then pushes to the device. + .EXAMPLE + PS> $app = New-AwtrixApp -Name 'greeting' -Text 'Hello!' -Rainbow -DurationSeconds 10 -Push + + Creates and immediately pushes an app with rainbow text. + .EXAMPLE + PS> $base = New-AwtrixApp -Icon 'temperature' -Color '#FF6600' -DurationSeconds 10 + PS> $indoor = $base.Clone('temp_indoor'); $indoor.Text = '72°F'; $indoor.Push() + PS> $outdoor = $base.Clone('temp_outdoor'); $outdoor.Text = '45°F'; $outdoor.Push() + + Template pattern: clone a base configuration and push two variants. + .EXAMPLE + PS> $app = New-AwtrixApp -Name 'status' -Text 'OK' -Push + PS> $app.ToJson() | Set-Content 'status.json' + PS> $restored = [AwtrixApp]::FromJson((Get-Content 'status.json' -Raw)) + + Serialize and restore an app configuration. + #> + [Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSReviewUnusedParameter', '', + Justification = 'Parameters are applied via PSBoundParameters loop' + )] + [CmdletBinding()] + [OutputType([AwtrixApp])] + param( + [Parameter(Position = 0)] + [string]$Name, + + [Parameter(Position = 1)] + $Text, + + [Parameter()] + [ValidateSet(0, 1, 2)] + [int]$TextCase, + + [Parameter()] + [switch]$TopText, + + [Parameter()] + [int]$TextOffset, + + [Parameter()] + [switch]$Center, + + [Parameter()] + [AwtrixColorTransform()] + $Color, + + [Parameter()] + [AwtrixColorTransform()] + [array]$Gradient, + + [Parameter()] + [Alias('BlinkTextMs')] + [int]$BlinkTextMilliseconds, + + [Parameter()] + [Alias('FadeTextMs')] + [int]$FadeTextMilliseconds, + + [Parameter()] + [AwtrixColorTransform()] + $Background, + + [Parameter()] + [switch]$Rainbow, + + [Parameter()] + [string]$Icon, + + [Parameter()] + [ValidateSet(0, 1, 2)] + [int]$PushIcon, + + [Parameter()] + [int]$Repeat, + + [Parameter()] + [Alias('DurationSec')] + [int]$DurationSeconds, + + [Parameter()] + [switch]$NoScroll, + + [Parameter()] + [int]$ScrollSpeed, + + [Parameter()] + [string]$Effect, + + [Parameter()] + [hashtable]$EffectSettings, + + [Parameter()] + [int[]]$Bar, + + [Parameter()] + [int[]]$Line, + + [Parameter()] + [switch]$Autoscale, + + [Parameter()] + [AwtrixColorTransform()] + $BarBackgroundColor, + + [Parameter()] + [ValidateRange(0, 100)] + [int]$Progress, + + [Parameter()] + [AwtrixColorTransform()] + $ProgressColor, + + [Parameter()] + [AwtrixColorTransform()] + $ProgressBackgroundColor, + + [Parameter()] + [array]$Draw, + + [Parameter()] + [ValidateSet('clear', 'snow', 'rain', 'drizzle', 'storm', 'thunder', 'frost', '')] + [string]$Overlay, + + [Parameter()] + [Alias('LifetimeSec')] + [int]$LifetimeSeconds, + + [Parameter()] + [ValidateSet(0, 1)] + [int]$LifetimeMode, + + [Parameter()] + [int]$Position, + + [Parameter()] + [switch]$Save, + + [Parameter()] + [switch]$Push, + + [Parameter()] + [string]$BaseUri + ) + + $app = [AwtrixApp]::new() + + $skip = @('Push', 'BaseUri') + $colorParams = @('Color', 'Gradient', 'Background', 'BarBackgroundColor', 'ProgressColor', 'ProgressBackgroundColor') + foreach ($key in $PSBoundParameters.Keys) { + if ($key -in $skip) { continue } + $val = $PSBoundParameters[$key] + if ($val -is [switch]) { $val = [bool]$val } + # The color transform attribute uses unary comma (", $val") to preserve arrays + # through parameter binding. Unwrap the outer single-element array before storing. + if ($key -in $colorParams -and $val -is [array] -and $val.Count -eq 1 -and $val[0] -is [array]) { + $val = $val[0] + } + $app.$key = $val + } + + if ($PSBoundParameters.ContainsKey('BaseUri')) { + $app._baseUri = $BaseUri + } + + if ($Push) { + $app.Push() + } + + $app +} diff --git a/awtrix/Public/New-AwtrixAppCollection.ps1 b/awtrix/Public/New-AwtrixAppCollection.ps1 new file mode 100644 index 0000000..981e1b4 --- /dev/null +++ b/awtrix/Public/New-AwtrixAppCollection.ps1 @@ -0,0 +1,61 @@ +function New-AwtrixAppCollection { + <# + .SYNOPSIS + Creates an AwtrixAppCollection for multi-page custom app groups. + .DESCRIPTION + Wraps multiple AwtrixApp pages under a shared base name. AWTRIX 3 automatically + assigns numeric suffixes (BaseName0, BaseName1, …) when an array is sent to + the API. Use Push() to send all pages at once, or Remove() to delete the group. + .PARAMETER BaseName + The shared base name for all pages in the collection. + .PARAMETER Apps + Ordered array of AwtrixApp objects representing each page. + .PARAMETER Push + Send the collection to the device immediately after creating the object. + .PARAMETER BaseUri + Base URI of the AWTRIX device. Overrides the module-level connection. + .EXAMPLE + PS> $p1 = New-AwtrixApp -Text 'Page 1' -DurationSeconds 5 + PS> $p2 = New-AwtrixApp -Text 'Page 2' -Color Red -DurationSeconds 5 + PS> $c = New-AwtrixAppCollection -BaseName 'dashboard' -Apps @($p1, $p2) -Push + + Creates a two-page dashboard group and immediately pushes it. + .EXAMPLE + PS> $collection = New-AwtrixAppCollection -BaseName 'report' -Apps $pages + PS> # ... later, after data changes ... + PS> $collection.Push() + + Builds a collection, then pushes when you're ready. + .EXAMPLE + PS> $collection.Remove() + + Removes all pages in the group from the device. + #> + [CmdletBinding()] + [OutputType([AwtrixAppCollection])] + param( + [Parameter(Mandatory, Position = 0)] + [string]$BaseName, + + [Parameter(Mandatory, Position = 1)] + [AwtrixApp[]]$Apps, + + [Parameter()] + [switch]$Push, + + [Parameter()] + [string]$BaseUri + ) + + $collection = [AwtrixAppCollection]::new($BaseName, $Apps) + + if ($PSBoundParameters.ContainsKey('BaseUri')) { + $collection._baseUri = $BaseUri + } + + if ($Push) { + $collection.Push() + } + + $collection +} diff --git a/awtrix/Public/New-AwtrixNotification.ps1 b/awtrix/Public/New-AwtrixNotification.ps1 new file mode 100644 index 0000000..ba931d0 --- /dev/null +++ b/awtrix/Public/New-AwtrixNotification.ps1 @@ -0,0 +1,262 @@ +function New-AwtrixNotification { + <# + .SYNOPSIS + Creates an AwtrixNotification object for deferred or reusable dispatch. + .DESCRIPTION + Returns an [AwtrixNotification] object that holds all properties of a one-time + AWTRIX notification. Call .Send() when you're ready to dispatch it, or pass + -Send to dispatch immediately. + + Storing the object lets you build reusable notification templates that can be + cloned and customized without reconstructing every property each time. + .PARAMETER Text + The text to display. A simple string or an array of colored fragment objects + created by New-AwtrixTextFragment. + .PARAMETER TextCase + 0 = global setting, 1 = force uppercase, 2 = show as sent. + .PARAMETER TopText + Draw text on top of the display. + .PARAMETER TextOffset + X-axis offset for the starting text position. + .PARAMETER Center + Centers a short, non-scrollable text. + .PARAMETER Color + Text, bar, or line color. Accepts a named color, hex string, or RGB array. + .PARAMETER Gradient + Colorizes text in a gradient of two colors. + .PARAMETER BlinkTextMilliseconds + Blinks the text at the given interval in ms. + .PARAMETER FadeTextMilliseconds + Fades the text on and off at the given interval in ms. + .PARAMETER Background + Background color. + .PARAMETER Rainbow + Fades each letter through the entire RGB spectrum. + .PARAMETER Icon + Icon ID, filename (without extension), or Base64-encoded 8x8 JPG. + .PARAMETER PushIcon + 0 = static, 1 = moves with text once, 2 = moves with text repeatedly. + .PARAMETER Repeat + Number of times the text scrolls before the notification ends. + .PARAMETER DurationSeconds + How long the notification is displayed in seconds. + .PARAMETER Hold + Keep the notification on screen until dismissed via the middle button or + Clear-AwtrixNotification. + .PARAMETER Sound + RTTTL ringtone filename (no extension) from the MELODIES folder, or a + 4-digit DFplayer MP3 number. + .PARAMETER Rtttl + Inline RTTTL sound string played with the notification. + .PARAMETER LoopSound + Loop the sound or RTTTL for the duration of the notification. + .PARAMETER Stack + Stack this notification (true) or immediately replace the current one (false). + .PARAMETER Wakeup + Wake the matrix if it is off for the duration of this notification. + .PARAMETER Clients + Additional AWTRIX device IP addresses to forward this notification to. + .PARAMETER NoScroll + Disables text scrolling. + .PARAMETER ScrollSpeed + Scroll speed as a percentage of the original speed. + .PARAMETER Effect + Background effect name. + .PARAMETER EffectSettings + Hashtable to change color and speed of the background effect. + .PARAMETER Bar + Bar chart data. + .PARAMETER Line + Line chart data. + .PARAMETER Autoscale + Auto-scale bar and line chart axes. + .PARAMETER BarBackgroundColor + Background color of bar chart bars. + .PARAMETER Progress + Progress bar value 0–100. + .PARAMETER ProgressColor + Progress bar foreground color. + .PARAMETER ProgressBackgroundColor + Progress bar background color. + .PARAMETER Draw + Array of drawing instruction objects. + .PARAMETER Overlay + Effect overlay: clear, snow, rain, drizzle, storm, thunder, frost. + .PARAMETER Send + Dispatch the notification to the device immediately after creating the object. + .PARAMETER BaseUri + Base URI of the AWTRIX device. Overrides the module-level connection. + .EXAMPLE + PS> $alert = New-AwtrixNotification -Text 'Alert!' -Color Red -Sound 'alarm' -Hold + PS> $alert.Send() + + Builds a reusable alert notification and sends it on demand. + .EXAMPLE + PS> $template = New-AwtrixNotification -Icon 'warning' -Color '#FF0000' -DurationSeconds 5 + PS> $disk = $template.Clone(); $disk.Text = 'Disk full!'; $disk.Send() + PS> $net = $template.Clone(); $net.Text = 'Network down!'; $net.Send() + + Template pattern: clone a base notification, customize text, send. + .EXAMPLE + PS> New-AwtrixNotification -Text 'Done!' -Rainbow -Send + + Inline: create and immediately send. + #> + [Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSReviewUnusedParameter', '', + Justification = 'Parameters are applied via PSBoundParameters loop' + )] + [CmdletBinding()] + [OutputType([AwtrixNotification])] + param( + [Parameter(Position = 0)] + $Text, + + [Parameter()] + [ValidateSet(0, 1, 2)] + [int]$TextCase, + + [Parameter()] + [switch]$TopText, + + [Parameter()] + [int]$TextOffset, + + [Parameter()] + [switch]$Center, + + [Parameter()] + [AwtrixColorTransform()] + $Color, + + [Parameter()] + [AwtrixColorTransform()] + [array]$Gradient, + + [Parameter()] + [Alias('BlinkTextMs')] + [int]$BlinkTextMilliseconds, + + [Parameter()] + [Alias('FadeTextMs')] + [int]$FadeTextMilliseconds, + + [Parameter()] + [AwtrixColorTransform()] + $Background, + + [Parameter()] + [switch]$Rainbow, + + [Parameter()] + [string]$Icon, + + [Parameter()] + [ValidateSet(0, 1, 2)] + [int]$PushIcon, + + [Parameter()] + [int]$Repeat, + + [Parameter()] + [Alias('DurationSec')] + [int]$DurationSeconds, + + [Parameter()] + [switch]$Hold, + + [Parameter()] + [string]$Sound, + + [Parameter()] + [string]$Rtttl, + + [Parameter()] + [switch]$LoopSound, + + [Parameter()] + [switch]$Stack, + + [Parameter()] + [switch]$Wakeup, + + [Parameter()] + [string[]]$Clients, + + [Parameter()] + [switch]$NoScroll, + + [Parameter()] + [int]$ScrollSpeed, + + [Parameter()] + [string]$Effect, + + [Parameter()] + [hashtable]$EffectSettings, + + [Parameter()] + [int[]]$Bar, + + [Parameter()] + [int[]]$Line, + + [Parameter()] + [switch]$Autoscale, + + [Parameter()] + [AwtrixColorTransform()] + $BarBackgroundColor, + + [Parameter()] + [ValidateRange(0, 100)] + [int]$Progress, + + [Parameter()] + [AwtrixColorTransform()] + $ProgressColor, + + [Parameter()] + [AwtrixColorTransform()] + $ProgressBackgroundColor, + + [Parameter()] + [array]$Draw, + + [Parameter()] + [ValidateSet('clear', 'snow', 'rain', 'drizzle', 'storm', 'thunder', 'frost', '')] + [string]$Overlay, + + [Parameter()] + [switch]$Send, + + [Parameter()] + [string]$BaseUri + ) + + $notif = [AwtrixNotification]::new() + + $skip = @('Send', 'BaseUri') + $colorParams = @('Color', 'Gradient', 'Background', 'BarBackgroundColor', 'ProgressColor', 'ProgressBackgroundColor') + foreach ($key in $PSBoundParameters.Keys) { + if ($key -in $skip) { continue } + $val = $PSBoundParameters[$key] + if ($val -is [switch]) { $val = [bool]$val } + # The color transform attribute uses unary comma (", $val") to preserve arrays + # through parameter binding. Unwrap the outer single-element array before storing. + if ($key -in $colorParams -and $val -is [array] -and $val.Count -eq 1 -and $val[0] -is [array]) { + $val = $val[0] + } + $notif.$key = $val + } + + if ($PSBoundParameters.ContainsKey('BaseUri')) { + $notif._baseUri = $BaseUri + } + + if ($Send) { + $notif.Send() + } + + $notif +} diff --git a/awtrix/Public/Send-AwtrixNotification.ps1 b/awtrix/Public/Send-AwtrixNotification.ps1 index e5c3fbc..858c19a 100644 --- a/awtrix/Public/Send-AwtrixNotification.ps1 +++ b/awtrix/Public/Send-AwtrixNotification.ps1 @@ -79,6 +79,8 @@ function Send-AwtrixNotification { Array of drawing instruction objects. Use New-AwtrixDrawing to create them. .PARAMETER Overlay Sets an effect overlay. Options: clear, snow, rain, drizzle, storm, thunder, frost. + .PARAMETER PassThru + Returns the [AwtrixNotification] object after sending. By default Send-AwtrixNotification produces no output. .PARAMETER BaseUri The base URI of the AWTRIX device. If not specified, uses the connection from Connect-Awtrix. .EXAMPLE @@ -223,9 +225,36 @@ function Send-AwtrixNotification { [string]$Overlay, [Parameter()] - [string]$BaseUri + [switch]$PassThru, + + [Parameter()] + [string]$BaseUri ) - $body = NewAppPayload -BoundParameters $PSBoundParameters - InvokeAwtrixApi -Endpoint 'notify' -Method POST -Body $body -BaseUri $BaseUri + $notif = [AwtrixNotification]::new() + + $skip = @('BaseUri', 'PassThru') + $colorParams = @('Color', 'Gradient', 'Background', 'BarBackgroundColor', 'ProgressColor', 'ProgressBackgroundColor') + foreach ($key in $PSBoundParameters.Keys) { + if ($key -in $skip) { continue } + $val = $PSBoundParameters[$key] + if ($val -is [switch]) { $val = [bool]$val } + # The color transform attribute uses unary comma (", $val") to preserve arrays + # through parameter binding. Unwrap the outer single-element array before storing. + if ($key -in $colorParams -and $val -is [array] -and $val.Count -eq 1 -and $val[0] -is [array]) { + $val = $val[0] + } + $notif.$key = $val + } + + if ($PSBoundParameters.ContainsKey('BaseUri')) { + $notif._baseUri = $BaseUri + } + + $notif.Send() + + if ($PassThru) { + $notif + } } + diff --git a/awtrix/Public/Set-AwtrixApp.ps1 b/awtrix/Public/Set-AwtrixApp.ps1 index 3176a31..529e80a 100644 --- a/awtrix/Public/Set-AwtrixApp.ps1 +++ b/awtrix/Public/Set-AwtrixApp.ps1 @@ -74,6 +74,8 @@ function Set-AwtrixApp { Position of the app in the loop (0-based). Only applies on first push. Experimental. .PARAMETER Save Saves the app to flash memory, persisting across reboots. Avoid for frequently updated apps. + .PARAMETER PassThru + Returns the [AwtrixApp] object after pushing. By default Set-AwtrixApp produces no output. .PARAMETER BaseUri The base URI of the AWTRIX device. If not specified, uses the connection from Connect-Awtrix. .EXAMPLE @@ -219,9 +221,36 @@ function Set-AwtrixApp { [switch]$Save, [Parameter()] - [string]$BaseUri + [switch]$PassThru, + + [Parameter()] + [string]$BaseUri ) - $body = NewAppPayload -BoundParameters $PSBoundParameters - InvokeAwtrixApi -Endpoint 'custom' -Method POST -Body $body -QueryString "name=$Name" -BaseUri $BaseUri + $app = [AwtrixApp]::new($Name) + + $skip = @('Name', 'BaseUri', 'PassThru') + $colorParams = @('Color', 'Gradient', 'Background', 'BarBackgroundColor', 'ProgressColor', 'ProgressBackgroundColor') + foreach ($key in $PSBoundParameters.Keys) { + if ($key -in $skip) { continue } + $val = $PSBoundParameters[$key] + if ($val -is [switch]) { $val = [bool]$val } + # The color transform attribute uses unary comma (", $val") to preserve arrays + # through parameter binding. Unwrap the outer single-element array before storing. + if ($key -in $colorParams -and $val -is [array] -and $val.Count -eq 1 -and $val[0] -is [array]) { + $val = $val[0] + } + $app.$key = $val + } + + if ($PSBoundParameters.ContainsKey('BaseUri')) { + $app._baseUri = $BaseUri + } + + $app.Push() + + if ($PassThru) { + $app + } } + diff --git a/awtrix/Public/Update-AwtrixApp.ps1 b/awtrix/Public/Update-AwtrixApp.ps1 new file mode 100644 index 0000000..3bffc58 --- /dev/null +++ b/awtrix/Public/Update-AwtrixApp.ps1 @@ -0,0 +1,248 @@ +function Update-AwtrixApp { + <# + .SYNOPSIS + Updates properties of an AwtrixApp object and pushes the changes to the device. + .DESCRIPTION + Accepts an [AwtrixApp] object from the pipeline (or directly), applies any + specified property overrides, then calls Push() to send the update to the device. + + Use -DirtyOnly to send only the properties that changed since the last push, + minimising the payload sent over the network. Use -PassThru to get the updated + object back for further chaining. + + The InputObject is mutated in place so the caller's variable reflects the + latest state after the update. + .PARAMETER InputObject + The AwtrixApp object to update. Accepts pipeline input. + .PARAMETER Text + New text value. + .PARAMETER TextCase + 0 = global setting, 1 = force uppercase, 2 = show as sent. + .PARAMETER TopText + Draw text on top of the display. + .PARAMETER TextOffset + X-axis offset for the starting text position. + .PARAMETER Center + Centers a short, non-scrollable text. + .PARAMETER Color + Text, bar, or line color. + .PARAMETER Gradient + Two-color text gradient. + .PARAMETER BlinkTextMilliseconds + Blink interval in ms. + .PARAMETER FadeTextMilliseconds + Fade interval in ms. + .PARAMETER Background + Background color. + .PARAMETER Rainbow + Fade each letter through the RGB spectrum. + .PARAMETER Icon + Icon ID, filename, or Base64 8x8 JPG. + .PARAMETER PushIcon + 0 = static, 1 = moves once, 2 = moves repeatedly. + .PARAMETER Repeat + Scroll count before app ends. + .PARAMETER DurationSeconds + Display duration in seconds. + .PARAMETER NoScroll + Disable text scrolling. + .PARAMETER ScrollSpeed + Scroll speed percentage. + .PARAMETER Effect + Background effect name. + .PARAMETER EffectSettings + Effect color/speed overrides. + .PARAMETER Bar + Bar chart data. + .PARAMETER Line + Line chart data. + .PARAMETER Autoscale + Auto-scale chart axes. + .PARAMETER BarBackgroundColor + Bar background color. + .PARAMETER Progress + Progress bar value 0–100. + .PARAMETER ProgressColor + Progress bar foreground color. + .PARAMETER ProgressBackgroundColor + Progress bar background color. + .PARAMETER Draw + Drawing instructions array. + .PARAMETER Overlay + Effect overlay. + .PARAMETER LifetimeSeconds + Auto-remove timeout in seconds. + .PARAMETER LifetimeMode + 0 = delete on expiry, 1 = stale indicator. + .PARAMETER Save + Persist to flash. + .PARAMETER DirtyOnly + Send only properties that changed since the last Push(). If this is the first + push since the object was created, the full payload is sent. + .PARAMETER PassThru + Return the updated AwtrixApp object. + .PARAMETER BaseUri + Override the device URI for this push. Does not persist on the object. + .EXAMPLE + PS> $app | Update-AwtrixApp -Text '68°F' + + Pipes an existing app object, updates its text, and pushes. + .EXAMPLE + PS> $app | Update-AwtrixApp -Text '68°F' -DirtyOnly -PassThru | Select-Object Name, Text + + Updates text, pushes dirty payload only, returns updated object. + .EXAMPLE + PS> Get-Variable -Name 'app*' -ValueOnly | Update-AwtrixApp -DurationSeconds 5 + + Update duration on multiple app objects at once via pipeline. + #> + [Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSReviewUnusedParameter', '', + Justification = 'Parameters are applied via PSBoundParameters loop' + )] + [CmdletBinding()] + [OutputType([AwtrixApp])] + param( + [Parameter(Mandatory, ValueFromPipeline)] + [AwtrixApp]$InputObject, + + [Parameter()] + $Text, + + [Parameter()] + [ValidateSet(0, 1, 2)] + [int]$TextCase, + + [Parameter()] + [switch]$TopText, + + [Parameter()] + [int]$TextOffset, + + [Parameter()] + [switch]$Center, + + [Parameter()] + [AwtrixColorTransform()] + $Color, + + [Parameter()] + [AwtrixColorTransform()] + [array]$Gradient, + + [Parameter()] + [Alias('BlinkTextMs')] + [int]$BlinkTextMilliseconds, + + [Parameter()] + [Alias('FadeTextMs')] + [int]$FadeTextMilliseconds, + + [Parameter()] + [AwtrixColorTransform()] + $Background, + + [Parameter()] + [switch]$Rainbow, + + [Parameter()] + [string]$Icon, + + [Parameter()] + [ValidateSet(0, 1, 2)] + [int]$PushIcon, + + [Parameter()] + [int]$Repeat, + + [Parameter()] + [Alias('DurationSec')] + [int]$DurationSeconds, + + [Parameter()] + [switch]$NoScroll, + + [Parameter()] + [int]$ScrollSpeed, + + [Parameter()] + [string]$Effect, + + [Parameter()] + [hashtable]$EffectSettings, + + [Parameter()] + [int[]]$Bar, + + [Parameter()] + [int[]]$Line, + + [Parameter()] + [switch]$Autoscale, + + [Parameter()] + [AwtrixColorTransform()] + $BarBackgroundColor, + + [Parameter()] + [ValidateRange(0, 100)] + [int]$Progress, + + [Parameter()] + [AwtrixColorTransform()] + $ProgressColor, + + [Parameter()] + [AwtrixColorTransform()] + $ProgressBackgroundColor, + + [Parameter()] + [array]$Draw, + + [Parameter()] + [ValidateSet('clear', 'snow', 'rain', 'drizzle', 'storm', 'thunder', 'frost', '')] + [string]$Overlay, + + [Parameter()] + [Alias('LifetimeSec')] + [int]$LifetimeSeconds, + + [Parameter()] + [ValidateSet(0, 1)] + [int]$LifetimeMode, + + [Parameter()] + [switch]$Save, + + [Parameter()] + [switch]$DirtyOnly, + + [Parameter()] + [switch]$PassThru, + + [Parameter()] + [string]$BaseUri + ) + + process { + $skip = @('InputObject', 'DirtyOnly', 'PassThru', 'BaseUri') + foreach ($key in $PSBoundParameters.Keys) { + if ($key -in $skip) { continue } + $val = $PSBoundParameters[$key] + if ($val -is [switch]) { $val = [bool]$val } + $InputObject.$key = $val + } + + # Resolve BaseUri: use explicit override for this call, or fall back to object's stored URI. + $resolvedUri = if ($PSBoundParameters.ContainsKey('BaseUri')) { $BaseUri } else { $InputObject._baseUri } + + $InputObject.Push($DirtyOnly.IsPresent) + + # Restore the original _baseUri in case we applied a one-time override above. + $InputObject._baseUri = $resolvedUri + + if ($PassThru) { + $InputObject + } + } +} diff --git a/awtrix/awtrix.psd1 b/awtrix/awtrix.psd1 index 6939e00..14a72f6 100644 --- a/awtrix/awtrix.psd1 +++ b/awtrix/awtrix.psd1 @@ -12,7 +12,7 @@ RootModule = 'awtrix.psm1' # Version number of this module. - ModuleVersion = '0.3.0' + ModuleVersion = '0.4.0' # Supported PSEditions # CompatiblePSEditions = @() @@ -82,7 +82,10 @@ 'Get-AwtrixSetting' 'Get-AwtrixStats' 'Get-AwtrixTransition' + 'New-AwtrixApp' + 'New-AwtrixAppCollection' 'New-AwtrixDrawing' + 'New-AwtrixNotification' 'New-AwtrixTextFragment' 'Remove-AwtrixApp' 'Reset-Awtrix' @@ -99,6 +102,7 @@ 'Show-AwtrixScreen' 'Start-AwtrixSleep' 'Switch-AwtrixApp' + 'Update-AwtrixApp' 'Update-AwtrixFirmware' ) diff --git a/awtrix/awtrix.psm1 b/awtrix/awtrix.psm1 index 8b905f3..e38d000 100644 --- a/awtrix/awtrix.psm1 +++ b/awtrix/awtrix.psm1 @@ -13,6 +13,22 @@ foreach ($import in @($classes + $public + $private)) { } } +# Wire the InvokeApi static delegate so AwtrixApp/AwtrixNotification/AwtrixAppCollection +# class methods can call InvokeAwtrixApi (which is module-scoped and not visible from +# ScriptsToProcess class definitions). This scriptblock closes over the module scope. +[AwtrixAppBase]::InvokeApi = { + param( + [string]$Endpoint, + [string]$Method = 'GET', + $Body, + [string]$RawBody, + [string]$QueryString, + [string]$BaseUri + ) + InvokeAwtrixApi -Endpoint $Endpoint -Method $Method -Body $Body ` + -RawBody $RawBody -QueryString $QueryString -BaseUri $BaseUri +} + if ( Get-Module -Name PwshSpectreConsole -ListAvailable) { Import-Module PwshSpectreConsole -ErrorAction Stop } @@ -23,7 +39,11 @@ Export-ModuleMember -Function $public.Basename $ExportableTypes = @( [AwtrixColor], [AwtrixIndicatorPosition], - [AwtrixColorTransformAttribute] + [AwtrixColorTransformAttribute], + [AwtrixAppBase], + [AwtrixApp], + [AwtrixNotification], + [AwtrixAppCollection] ) # Get the internal TypeAccelerators class to use its static methods. $TypeAcceleratorsClass = [psobject].Assembly.GetType( diff --git a/docs/en-US/New-AwtrixApp.md b/docs/en-US/New-AwtrixApp.md new file mode 100644 index 0000000..12501f9 --- /dev/null +++ b/docs/en-US/New-AwtrixApp.md @@ -0,0 +1,639 @@ +--- +external help file: awtrix-help.xml +Module Name: awtrix +online version: +schema: 2.0.0 +--- + +# New-AwtrixApp + +## SYNOPSIS +Creates an AwtrixApp object, optionally pushing it to the device immediately. + +## SYNTAX + +``` +New-AwtrixApp [[-Name] ] [[-Text] ] [-TextCase ] [-TopText] [-TextOffset ] + [-Center] [-Color ] [-Gradient ] [-BlinkTextMilliseconds ] + [-FadeTextMilliseconds ] [-Background ] [-Rainbow] [-Icon ] [-PushIcon ] + [-Repeat ] [-DurationSeconds ] [-NoScroll] [-ScrollSpeed ] [-Effect ] + [-EffectSettings ] [-Bar ] [-Line ] [-Autoscale] [-BarBackgroundColor ] + [-Progress ] [-ProgressColor ] [-ProgressBackgroundColor ] [-Draw ] + [-Overlay ] [-LifetimeSeconds ] [-LifetimeMode ] [-Position ] [-Save] [-Push] + [-BaseUri ] [-ProgressAction ] [] +``` + +## DESCRIPTION +Returns an \[AwtrixApp\] object that holds the full state of a custom AWTRIX app. +The object can be modified, pushed to the device, cloned into templates, and +serialized to/from JSON - all without additional API calls until you're ready. + +Use -Push to send the app to the device in the same call. +Omit -Push to build +the object locally first, set properties, then call $app.Push() when ready. + +## EXAMPLES + +### EXAMPLE 1 +``` +$app = New-AwtrixApp -Name 'weather' -Icon 'temperature' -Color '#FF6600' +PS> $app.Text = '72°F' +PS> $app.Push() +``` + +Creates an app object locally, sets text, then pushes to the device. + +### EXAMPLE 2 +``` +$app = New-AwtrixApp -Name 'greeting' -Text 'Hello!' -Rainbow -DurationSeconds 10 -Push +``` + +Creates and immediately pushes an app with rainbow text. + +### EXAMPLE 3 +``` +$base = New-AwtrixApp -Icon 'temperature' -Color '#FF6600' -DurationSeconds 10 +PS> $indoor = $base.Clone('temp_indoor'); $indoor.Text = '72°F'; $indoor.Push() +PS> $outdoor = $base.Clone('temp_outdoor'); $outdoor.Text = '45°F'; $outdoor.Push() +``` + +Template pattern: clone a base configuration and push two variants. + +### EXAMPLE 4 +``` +$app = New-AwtrixApp -Name 'status' -Text 'OK' -Push +PS> $app.ToJson() | Set-Content 'status.json' +PS> $restored = [AwtrixApp]::FromJson((Get-Content 'status.json' -Raw)) +``` + +Serialize and restore an app configuration. + +## PARAMETERS + +### -Name +The unique app name used to identify and update the app on the device. + +```yaml +Type: String +Parameter Sets: (All) +Aliases: + +Required: False +Position: 1 +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Text +The text to display. +A simple string or an array of colored fragment objects +created by New-AwtrixTextFragment. + +```yaml +Type: Object +Parameter Sets: (All) +Aliases: + +Required: False +Position: 2 +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -TextCase +0 = global setting, 1 = force uppercase, 2 = show as sent. + +```yaml +Type: Int32 +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: 0 +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -TopText +Draw text on top of the display. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: False +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -TextOffset +X-axis offset for the starting text position. + +```yaml +Type: Int32 +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: 0 +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Center +Centers a short, non-scrollable text. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: False +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Color +Text, bar, or line color. +Accepts a named color, hex string, or RGB array. + +```yaml +Type: Object +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Gradient +Colorizes text in a gradient of two colors. + +```yaml +Type: Array +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -BlinkTextMilliseconds +Blinks the text at the given interval in ms. +Not compatible with gradient or rainbow. + +```yaml +Type: Int32 +Parameter Sets: (All) +Aliases: BlinkTextMs + +Required: False +Position: Named +Default value: 0 +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -FadeTextMilliseconds +Fades the text on and off at the given interval in ms. +Not compatible with gradient or rainbow. + +```yaml +Type: Int32 +Parameter Sets: (All) +Aliases: FadeTextMs + +Required: False +Position: Named +Default value: 0 +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Background +Background color. +Accepts a named color, hex string, or RGB array. + +```yaml +Type: Object +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Rainbow +Fades each letter through the entire RGB spectrum. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: False +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Icon +Icon ID, filename (without extension), or Base64-encoded 8x8 JPG. + +```yaml +Type: String +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -PushIcon +0 = static, 1 = moves with text once, 2 = moves with text repeatedly. + +```yaml +Type: Int32 +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: 0 +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Repeat +Number of times the text scrolls before the app ends. +-1 = indefinite. + +```yaml +Type: Int32 +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: 0 +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -DurationSeconds +How long the app is displayed in seconds. + +```yaml +Type: Int32 +Parameter Sets: (All) +Aliases: DurationSec + +Required: False +Position: Named +Default value: 0 +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -NoScroll +Disables text scrolling. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: False +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -ScrollSpeed +Scroll speed as a percentage of the original speed. + +```yaml +Type: Int32 +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: 0 +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Effect +Background effect name. +Empty string removes an existing effect. + +```yaml +Type: String +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -EffectSettings +Hashtable to change color and speed of the background effect. + +```yaml +Type: Hashtable +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Bar +Bar chart data. +Max 16 values without icon, 11 with icon. + +```yaml +Type: Int32[] +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Line +Line chart data. +Max 16 values without icon, 11 with icon. + +```yaml +Type: Int32[] +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Autoscale +Enables or disables auto-scaling for bar and line charts. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: False +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -BarBackgroundColor +Background color of bar chart bars. + +```yaml +Type: Object +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Progress +Progress bar value 0-100. + +```yaml +Type: Int32 +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: 0 +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -ProgressColor +Progress bar foreground color. + +```yaml +Type: Object +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -ProgressBackgroundColor +Progress bar background color. + +```yaml +Type: Object +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Draw +Array of drawing instruction objects. +Use New-AwtrixDrawing to create them. + +```yaml +Type: Array +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Overlay +Effect overlay: clear, snow, rain, drizzle, storm, thunder, frost. + +```yaml +Type: String +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -LifetimeSeconds +Removes the app if no update is received within this many seconds. +0 = disabled. + +```yaml +Type: Int32 +Parameter Sets: (All) +Aliases: LifetimeSec + +Required: False +Position: Named +Default value: 0 +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -LifetimeMode +0 = delete app on expiry, 1 = mark as stale with red border. + +```yaml +Type: Int32 +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: 0 +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Position +0-based loop position. +Applied only on first push. +Experimental. + +```yaml +Type: Int32 +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: 0 +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Save +Persist app to flash memory across reboots. +Avoid for frequently updated apps. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: False +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Push +Send the app to the device immediately after creating the object. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: False +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -BaseUri +Base URI of the AWTRIX device. +Overrides the module-level connection for this app. + +```yaml +Type: String +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -ProgressAction +{{ Fill ProgressAction Description }} + +```yaml +Type: ActionPreference +Parameter Sets: (All) +Aliases: proga + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### CommonParameters +This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). + +## INPUTS + +## OUTPUTS + +### AwtrixApp +## NOTES + +## RELATED LINKS diff --git a/docs/en-US/New-AwtrixAppCollection.md b/docs/en-US/New-AwtrixAppCollection.md new file mode 100644 index 0000000..e6f54cd --- /dev/null +++ b/docs/en-US/New-AwtrixAppCollection.md @@ -0,0 +1,142 @@ +--- +external help file: awtrix-help.xml +Module Name: awtrix +online version: +schema: 2.0.0 +--- + +# New-AwtrixAppCollection + +## SYNOPSIS +Creates an AwtrixAppCollection for multi-page custom app groups. + +## SYNTAX + +``` +New-AwtrixAppCollection [-BaseName] [-Apps] [-Push] [-BaseUri ] + [-ProgressAction ] [] +``` + +## DESCRIPTION +Wraps multiple AwtrixApp pages under a shared base name. +AWTRIX 3 automatically +assigns numeric suffixes (BaseName0, BaseName1, …) when an array is sent to +the API. +Use Push() to send all pages at once, or Remove() to delete the group. + +## EXAMPLES + +### EXAMPLE 1 +``` +$p1 = New-AwtrixApp -Text 'Page 1' -DurationSeconds 5 +PS> $p2 = New-AwtrixApp -Text 'Page 2' -Color Red -DurationSeconds 5 +PS> $c = New-AwtrixAppCollection -BaseName 'dashboard' -Apps @($p1, $p2) -Push +``` + +Creates a two-page dashboard group and immediately pushes it. + +### EXAMPLE 2 +``` +$collection = New-AwtrixAppCollection -BaseName 'report' -Apps $pages +PS> # ... later, after data changes ... +PS> $collection.Push() +``` + +Builds a collection, then pushes when you're ready. + +### EXAMPLE 3 +``` +$collection.Remove() +``` + +Removes all pages in the group from the device. + +## PARAMETERS + +### -BaseName +The shared base name for all pages in the collection. + +```yaml +Type: String +Parameter Sets: (All) +Aliases: + +Required: True +Position: 1 +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Apps +Ordered array of AwtrixApp objects representing each page. + +```yaml +Type: AwtrixApp[] +Parameter Sets: (All) +Aliases: + +Required: True +Position: 2 +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Push +Send the collection to the device immediately after creating the object. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: False +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -BaseUri +Base URI of the AWTRIX device. +Overrides the module-level connection. + +```yaml +Type: String +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -ProgressAction +{{ Fill ProgressAction Description }} + +```yaml +Type: ActionPreference +Parameter Sets: (All) +Aliases: proga + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### CommonParameters +This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). + +## INPUTS + +## OUTPUTS + +### AwtrixAppCollection +## NOTES + +## RELATED LINKS diff --git a/docs/en-US/New-AwtrixNotification.md b/docs/en-US/New-AwtrixNotification.md new file mode 100644 index 0000000..f75ee6f --- /dev/null +++ b/docs/en-US/New-AwtrixNotification.md @@ -0,0 +1,649 @@ +--- +external help file: awtrix-help.xml +Module Name: awtrix +online version: +schema: 2.0.0 +--- + +# New-AwtrixNotification + +## SYNOPSIS +Creates an AwtrixNotification object for deferred or reusable dispatch. + +## SYNTAX + +``` +New-AwtrixNotification [[-Text] ] [-TextCase ] [-TopText] [-TextOffset ] [-Center] + [-Color ] [-Gradient ] [-BlinkTextMilliseconds ] [-FadeTextMilliseconds ] + [-Background ] [-Rainbow] [-Icon ] [-PushIcon ] [-Repeat ] + [-DurationSeconds ] [-Hold] [-Sound ] [-Rtttl ] [-LoopSound] [-Stack] [-Wakeup] + [-Clients ] [-NoScroll] [-ScrollSpeed ] [-Effect ] [-EffectSettings ] + [-Bar ] [-Line ] [-Autoscale] [-BarBackgroundColor ] [-Progress ] + [-ProgressColor ] [-ProgressBackgroundColor ] [-Draw ] [-Overlay ] [-Send] + [-BaseUri ] [-ProgressAction ] [] +``` + +## DESCRIPTION +Returns an \[AwtrixNotification\] object that holds all properties of a one-time +AWTRIX notification. +Call .Send() when you're ready to dispatch it, or pass +-Send to dispatch immediately. + +Storing the object lets you build reusable notification templates that can be +cloned and customized without reconstructing every property each time. + +## EXAMPLES + +### EXAMPLE 1 +``` +$alert = New-AwtrixNotification -Text 'Alert!' -Color Red -Sound 'alarm' -Hold +PS> $alert.Send() +``` + +Builds a reusable alert notification and sends it on demand. + +### EXAMPLE 2 +``` +$template = New-AwtrixNotification -Icon 'warning' -Color '#FF0000' -DurationSeconds 5 +PS> $disk = $template.Clone(); $disk.Text = 'Disk full!'; $disk.Send() +PS> $net = $template.Clone(); $net.Text = 'Network down!'; $net.Send() +``` + +Template pattern: clone a base notification, customize text, send. + +### EXAMPLE 3 +``` +New-AwtrixNotification -Text 'Done!' -Rainbow -Send +``` + +Inline: create and immediately send. + +## PARAMETERS + +### -Text +The text to display. +A simple string or an array of colored fragment objects +created by New-AwtrixTextFragment. + +```yaml +Type: Object +Parameter Sets: (All) +Aliases: + +Required: False +Position: 1 +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -TextCase +0 = global setting, 1 = force uppercase, 2 = show as sent. + +```yaml +Type: Int32 +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: 0 +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -TopText +Draw text on top of the display. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: False +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -TextOffset +X-axis offset for the starting text position. + +```yaml +Type: Int32 +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: 0 +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Center +Centers a short, non-scrollable text. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: False +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Color +Text, bar, or line color. +Accepts a named color, hex string, or RGB array. + +```yaml +Type: Object +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Gradient +Colorizes text in a gradient of two colors. + +```yaml +Type: Array +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -BlinkTextMilliseconds +Blinks the text at the given interval in ms. + +```yaml +Type: Int32 +Parameter Sets: (All) +Aliases: BlinkTextMs + +Required: False +Position: Named +Default value: 0 +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -FadeTextMilliseconds +Fades the text on and off at the given interval in ms. + +```yaml +Type: Int32 +Parameter Sets: (All) +Aliases: FadeTextMs + +Required: False +Position: Named +Default value: 0 +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Background +Background color. + +```yaml +Type: Object +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Rainbow +Fades each letter through the entire RGB spectrum. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: False +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Icon +Icon ID, filename (without extension), or Base64-encoded 8x8 JPG. + +```yaml +Type: String +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -PushIcon +0 = static, 1 = moves with text once, 2 = moves with text repeatedly. + +```yaml +Type: Int32 +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: 0 +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Repeat +Number of times the text scrolls before the notification ends. + +```yaml +Type: Int32 +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: 0 +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -DurationSeconds +How long the notification is displayed in seconds. + +```yaml +Type: Int32 +Parameter Sets: (All) +Aliases: DurationSec + +Required: False +Position: Named +Default value: 0 +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Hold +Keep the notification on screen until dismissed via the middle button or +Clear-AwtrixNotification. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: False +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Sound +RTTTL ringtone filename (no extension) from the MELODIES folder, or a +4-digit DFplayer MP3 number. + +```yaml +Type: String +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Rtttl +Inline RTTTL sound string played with the notification. + +```yaml +Type: String +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -LoopSound +Loop the sound or RTTTL for the duration of the notification. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: False +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Stack +Stack this notification (true) or immediately replace the current one (false). + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: False +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Wakeup +Wake the matrix if it is off for the duration of this notification. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: False +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Clients +Additional AWTRIX device IP addresses to forward this notification to. + +```yaml +Type: String[] +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -NoScroll +Disables text scrolling. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: False +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -ScrollSpeed +Scroll speed as a percentage of the original speed. + +```yaml +Type: Int32 +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: 0 +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Effect +Background effect name. + +```yaml +Type: String +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -EffectSettings +Hashtable to change color and speed of the background effect. + +```yaml +Type: Hashtable +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Bar +Bar chart data. + +```yaml +Type: Int32[] +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Line +Line chart data. + +```yaml +Type: Int32[] +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Autoscale +Auto-scale bar and line chart axes. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: False +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -BarBackgroundColor +Background color of bar chart bars. + +```yaml +Type: Object +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Progress +Progress bar value 0-100. + +```yaml +Type: Int32 +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: 0 +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -ProgressColor +Progress bar foreground color. + +```yaml +Type: Object +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -ProgressBackgroundColor +Progress bar background color. + +```yaml +Type: Object +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Draw +Array of drawing instruction objects. + +```yaml +Type: Array +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Overlay +Effect overlay: clear, snow, rain, drizzle, storm, thunder, frost. + +```yaml +Type: String +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Send +Dispatch the notification to the device immediately after creating the object. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: False +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -BaseUri +Base URI of the AWTRIX device. +Overrides the module-level connection. + +```yaml +Type: String +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -ProgressAction +{{ Fill ProgressAction Description }} + +```yaml +Type: ActionPreference +Parameter Sets: (All) +Aliases: proga + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### CommonParameters +This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). + +## INPUTS + +## OUTPUTS + +### AwtrixNotification +## NOTES + +## RELATED LINKS diff --git a/docs/en-US/Send-AwtrixNotification.md b/docs/en-US/Send-AwtrixNotification.md index 465b09e..b2a8bfd 100644 --- a/docs/en-US/Send-AwtrixNotification.md +++ b/docs/en-US/Send-AwtrixNotification.md @@ -19,7 +19,7 @@ Send-AwtrixNotification [[-Text] ] [-TextCase ] [-TopText] [-Text [-DurationSeconds ] [-Hold] [-Sound ] [-Rtttl ] [-LoopSound] [-Stack] [-Wakeup] [-Clients ] [-NoScroll] [-ScrollSpeed ] [-Effect ] [-EffectSettings ] [-Bar ] [-Line ] [-Autoscale] [-BarBackgroundColor ] [-Progress ] - [-ProgressColor ] [-ProgressBackgroundColor ] [-Draw ] [-Overlay ] + [-ProgressColor ] [-ProgressBackgroundColor ] [-Draw ] [-Overlay ] [-PassThru] [-BaseUri ] [-ProgressAction ] [] ``` @@ -605,6 +605,21 @@ Accept pipeline input: False Accept wildcard characters: False ``` +### -PassThru +{{ Fill PassThru Description }} + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: False +Accept pipeline input: False +Accept wildcard characters: False +``` + ### -BaseUri The base URI of the AWTRIX device. If not specified, uses the connection from Connect-Awtrix. diff --git a/docs/en-US/Set-AwtrixApp.md b/docs/en-US/Set-AwtrixApp.md index 8a3a02a..3b8cfd1 100644 --- a/docs/en-US/Set-AwtrixApp.md +++ b/docs/en-US/Set-AwtrixApp.md @@ -19,7 +19,7 @@ Set-AwtrixApp [-Name] [[-Text] ] [-TextCase ] [-TopText] [-Repeat ] [-DurationSeconds ] [-NoScroll] [-ScrollSpeed ] [-Effect ] [-EffectSettings ] [-Bar ] [-Line ] [-Autoscale] [-BarBackgroundColor ] [-Progress ] [-ProgressColor ] [-ProgressBackgroundColor ] [-Draw ] - [-Overlay ] [-LifetimeSeconds ] [-LifetimeMode ] [-Position ] [-Save] + [-Overlay ] [-LifetimeSeconds ] [-LifetimeMode ] [-Position ] [-Save] [-PassThru] [-BaseUri ] [-ProgressAction ] [] ``` @@ -580,6 +580,21 @@ Accept pipeline input: False Accept wildcard characters: False ``` +### -PassThru +{{ Fill PassThru Description }} + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: False +Accept pipeline input: False +Accept wildcard characters: False +``` + ### -BaseUri The base URI of the AWTRIX device. If not specified, uses the connection from Connect-Awtrix. diff --git a/docs/en-US/Update-AwtrixApp.md b/docs/en-US/Update-AwtrixApp.md new file mode 100644 index 0000000..df19f37 --- /dev/null +++ b/docs/en-US/Update-AwtrixApp.md @@ -0,0 +1,618 @@ +--- +external help file: awtrix-help.xml +Module Name: awtrix +online version: +schema: 2.0.0 +--- + +# Update-AwtrixApp + +## SYNOPSIS +Updates properties of an AwtrixApp object and pushes the changes to the device. + +## SYNTAX + +``` +Update-AwtrixApp [-InputObject] [[-Text] ] [[-TextCase] ] [-TopText] + [[-TextOffset] ] [-Center] [[-Color] ] [[-Gradient] ] [[-BlinkTextMilliseconds] ] + [[-FadeTextMilliseconds] ] [[-Background] ] [-Rainbow] [[-Icon] ] [[-PushIcon] ] + [[-Repeat] ] [[-DurationSeconds] ] [-NoScroll] [[-ScrollSpeed] ] [[-Effect] ] + [[-EffectSettings] ] [[-Bar] ] [[-Line] ] [-Autoscale] + [[-BarBackgroundColor] ] [[-Progress] ] [[-ProgressColor] ] + [[-ProgressBackgroundColor] ] [[-Draw] ] [[-Overlay] ] [[-LifetimeSeconds] ] + [[-LifetimeMode] ] [-Save] [-DirtyOnly] [-PassThru] [[-BaseUri] ] + [-ProgressAction ] [] +``` + +## DESCRIPTION +Accepts an \[AwtrixApp\] object from the pipeline (or directly), applies any +specified property overrides, then calls Push() to send the update to the device. + +Use -DirtyOnly to send only the properties that changed since the last push, +minimising the payload sent over the network. +Use -PassThru to get the updated +object back for further chaining. + +The InputObject is mutated in place so the caller's variable reflects the +latest state after the update. + +## EXAMPLES + +### EXAMPLE 1 +``` +$app | Update-AwtrixApp -Text '68°F' +``` + +Pipes an existing app object, updates its text, and pushes. + +### EXAMPLE 2 +``` +$app | Update-AwtrixApp -Text '68°F' -DirtyOnly -PassThru | Select-Object Name, Text +``` + +Updates text, pushes dirty payload only, returns updated object. + +### EXAMPLE 3 +``` +Get-Variable -Name 'app*' -ValueOnly | Update-AwtrixApp -DurationSeconds 5 +``` + +Update duration on multiple app objects at once via pipeline. + +## PARAMETERS + +### -InputObject +The AwtrixApp object to update. +Accepts pipeline input. + +```yaml +Type: AwtrixApp +Parameter Sets: (All) +Aliases: + +Required: True +Position: 1 +Default value: None +Accept pipeline input: True (ByValue) +Accept wildcard characters: False +``` + +### -Text +New text value. + +```yaml +Type: Object +Parameter Sets: (All) +Aliases: + +Required: False +Position: 2 +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -TextCase +0 = global setting, 1 = force uppercase, 2 = show as sent. + +```yaml +Type: Int32 +Parameter Sets: (All) +Aliases: + +Required: False +Position: 3 +Default value: 0 +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -TopText +Draw text on top of the display. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: False +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -TextOffset +X-axis offset for the starting text position. + +```yaml +Type: Int32 +Parameter Sets: (All) +Aliases: + +Required: False +Position: 4 +Default value: 0 +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Center +Centers a short, non-scrollable text. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: False +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Color +Text, bar, or line color. + +```yaml +Type: Object +Parameter Sets: (All) +Aliases: + +Required: False +Position: 5 +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Gradient +Two-color text gradient. + +```yaml +Type: Array +Parameter Sets: (All) +Aliases: + +Required: False +Position: 6 +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -BlinkTextMilliseconds +Blink interval in ms. + +```yaml +Type: Int32 +Parameter Sets: (All) +Aliases: BlinkTextMs + +Required: False +Position: 7 +Default value: 0 +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -FadeTextMilliseconds +Fade interval in ms. + +```yaml +Type: Int32 +Parameter Sets: (All) +Aliases: FadeTextMs + +Required: False +Position: 8 +Default value: 0 +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Background +Background color. + +```yaml +Type: Object +Parameter Sets: (All) +Aliases: + +Required: False +Position: 9 +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Rainbow +Fade each letter through the RGB spectrum. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: False +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Icon +Icon ID, filename, or Base64 8x8 JPG. + +```yaml +Type: String +Parameter Sets: (All) +Aliases: + +Required: False +Position: 10 +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -PushIcon +0 = static, 1 = moves once, 2 = moves repeatedly. + +```yaml +Type: Int32 +Parameter Sets: (All) +Aliases: + +Required: False +Position: 11 +Default value: 0 +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Repeat +Scroll count before app ends. + +```yaml +Type: Int32 +Parameter Sets: (All) +Aliases: + +Required: False +Position: 12 +Default value: 0 +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -DurationSeconds +Display duration in seconds. + +```yaml +Type: Int32 +Parameter Sets: (All) +Aliases: DurationSec + +Required: False +Position: 13 +Default value: 0 +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -NoScroll +Disable text scrolling. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: False +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -ScrollSpeed +Scroll speed percentage. + +```yaml +Type: Int32 +Parameter Sets: (All) +Aliases: + +Required: False +Position: 14 +Default value: 0 +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Effect +Background effect name. + +```yaml +Type: String +Parameter Sets: (All) +Aliases: + +Required: False +Position: 15 +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -EffectSettings +Effect color/speed overrides. + +```yaml +Type: Hashtable +Parameter Sets: (All) +Aliases: + +Required: False +Position: 16 +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Bar +Bar chart data. + +```yaml +Type: Int32[] +Parameter Sets: (All) +Aliases: + +Required: False +Position: 17 +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Line +Line chart data. + +```yaml +Type: Int32[] +Parameter Sets: (All) +Aliases: + +Required: False +Position: 18 +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Autoscale +Auto-scale chart axes. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: False +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -BarBackgroundColor +Bar background color. + +```yaml +Type: Object +Parameter Sets: (All) +Aliases: + +Required: False +Position: 19 +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Progress +Progress bar value 0-100. + +```yaml +Type: Int32 +Parameter Sets: (All) +Aliases: + +Required: False +Position: 20 +Default value: 0 +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -ProgressColor +Progress bar foreground color. + +```yaml +Type: Object +Parameter Sets: (All) +Aliases: + +Required: False +Position: 21 +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -ProgressBackgroundColor +Progress bar background color. + +```yaml +Type: Object +Parameter Sets: (All) +Aliases: + +Required: False +Position: 22 +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Draw +Drawing instructions array. + +```yaml +Type: Array +Parameter Sets: (All) +Aliases: + +Required: False +Position: 23 +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Overlay +Effect overlay. + +```yaml +Type: String +Parameter Sets: (All) +Aliases: + +Required: False +Position: 24 +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -LifetimeSeconds +Auto-remove timeout in seconds. + +```yaml +Type: Int32 +Parameter Sets: (All) +Aliases: LifetimeSec + +Required: False +Position: 25 +Default value: 0 +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -LifetimeMode +0 = delete on expiry, 1 = stale indicator. + +```yaml +Type: Int32 +Parameter Sets: (All) +Aliases: + +Required: False +Position: 26 +Default value: 0 +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Save +Persist to flash. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: False +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -DirtyOnly +Send only properties that changed since the last Push(). +If this is the first +push since the object was created, the full payload is sent. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: False +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -PassThru +Return the updated AwtrixApp object. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: False +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -BaseUri +Override the device URI for this push. +Does not persist on the object. + +```yaml +Type: String +Parameter Sets: (All) +Aliases: + +Required: False +Position: 27 +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -ProgressAction +{{ Fill ProgressAction Description }} + +```yaml +Type: ActionPreference +Parameter Sets: (All) +Aliases: proga + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### CommonParameters +This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). + +## INPUTS + +## OUTPUTS + +### AwtrixApp +## NOTES + +## RELATED LINKS diff --git a/tests/AwtrixApp.tests.ps1 b/tests/AwtrixApp.tests.ps1 new file mode 100644 index 0000000..0bddc2d --- /dev/null +++ b/tests/AwtrixApp.tests.ps1 @@ -0,0 +1,427 @@ +BeforeAll { + if ($null -eq $env:BHPSModuleManifest) { + & "$PSScriptRoot/../Build.ps1" -Task Init + } + $manifest = Import-PowerShellDataFile -Path $env:BHPSModuleManifest + $outputDir = Join-Path -Path $env:BHProjectPath -ChildPath 'Output' + $outputModDir = Join-Path -Path $outputDir -ChildPath $env:BHProjectName + $outputModVerDir = Join-Path -Path $outputModDir -ChildPath $manifest.ModuleVersion + $outputModVerManifest = Join-Path -Path $outputModVerDir -ChildPath "$($env:BHProjectName).psd1" + + Get-Module $env:BHProjectName | Remove-Module -Force -ErrorAction Ignore + Import-Module -Name $outputModVerManifest -Verbose:$false -ErrorAction Stop + + & (Get-Module $env:BHProjectName) { $script:AwtrixConnection = @{ BaseUri = 'http://192.168.1.100' } } +} + +Describe 'AwtrixApp class' { + + BeforeAll { + Mock Invoke-RestMethod {} -ModuleName awtrix + } + + Context 'Construction' { + + It 'Creates an empty instance' { + $app = [AwtrixApp]::new() + $app | Should -Not -BeNullOrEmpty + $app.Name | Should -BeNullOrEmpty + } + + It 'Accepts a name via the constructor overload' { + $app = [AwtrixApp]::new('myapp') + $app.Name | Should -Be 'myapp' + } + + It 'All base properties default to null' { + $app = [AwtrixApp]::new() + $app.Text | Should -BeNullOrEmpty + $app.Color | Should -BeNullOrEmpty + $app.Icon | Should -BeNullOrEmpty + $app.DurationSeconds | Should -BeNullOrEmpty + $app.LifetimeSeconds | Should -BeNullOrEmpty + } + } + + Context 'ToPayload' { + + It 'Returns an empty hashtable when no properties are set' { + $app = [AwtrixApp]::new() + $app.ToPayload().Count | Should -Be 0 + } + + It 'Maps Text to "text"' { + $app = [AwtrixApp]::new() + $app.Text = 'Hello' + $app.ToPayload()['text'] | Should -Be 'Hello' + } + + It 'Maps Color to "color"' { + $app = [AwtrixApp]::new() + $app.Color = '#FF0000' + $app.ToPayload()['color'] | Should -Be '#FF0000' + } + + It 'Maps DurationSeconds to "duration"' { + $app = [AwtrixApp]::new() + $app.DurationSeconds = 10 + $app.ToPayload()['duration'] | Should -Be 10 + } + + It 'Maps LifetimeSeconds to "lifetime"' { + $app = [AwtrixApp]::new() + $app.LifetimeSeconds = 300 + $app.ToPayload()['lifetime'] | Should -Be 300 + } + + It 'Maps LifetimeMode to "lifetimeMode"' { + $app = [AwtrixApp]::new() + $app.LifetimeMode = 1 + $app.ToPayload()['lifetimeMode'] | Should -Be 1 + } + + It 'Maps Position to "pos"' { + $app = [AwtrixApp]::new() + $app.Position = 2 + $app.ToPayload()['pos'] | Should -Be 2 + } + + It 'Maps Save to "save"' { + $app = [AwtrixApp]::new() + $app.Save = $true + $app.ToPayload()['save'] | Should -Be $true + } + + It 'Maps BlinkTextMilliseconds to "blinkText"' { + $app = [AwtrixApp]::new() + $app.BlinkTextMilliseconds = 500 + $app.ToPayload()['blinkText'] | Should -Be 500 + } + + It 'Maps Progress to "progress"' { + $app = [AwtrixApp]::new() + $app.Progress = 75 + $app.ToPayload()['progress'] | Should -Be 75 + } + + It 'Maps Overlay to "overlay"' { + $app = [AwtrixApp]::new() + $app.Overlay = 'snow' + $app.ToPayload()['overlay'] | Should -Be 'snow' + } + + It 'Maps NoScroll to "noScroll"' { + $app = [AwtrixApp]::new() + $app.NoScroll = $true + $app.ToPayload()['noScroll'] | Should -Be $true + } + + It 'Maps Rainbow to "rainbow"' { + $app = [AwtrixApp]::new() + $app.Rainbow = $true + $app.ToPayload()['rainbow'] | Should -Be $true + } + + It 'Maps Bar to "bar"' { + $app = [AwtrixApp]::new() + $app.Bar = @(1, 5, 3, 8) + $app.ToPayload()['bar'] | Should -Be @(1, 5, 3, 8) + } + + It 'Does not include null properties in payload' { + $app = [AwtrixApp]::new() + $app.Text = 'Hi' + $payload = $app.ToPayload() + $payload.ContainsKey('duration') | Should -Be $false + $payload.ContainsKey('color') | Should -Be $false + } + + It 'Includes Effect empty string (API signal to remove effect)' { + $app = [AwtrixApp]::new() + $app.Effect = '' + $app.ToPayload().ContainsKey('effect') | Should -Be $true + } + + It 'Excludes empty string for non-Effect properties' { + $app = [AwtrixApp]::new() + $app.Icon = '' + $app.ToPayload().ContainsKey('icon') | Should -Be $false + } + } + + Context 'ToJson and FromJson round-trip' { + + It 'Round-trips Name, Text, Color, and Duration' { + $app = [AwtrixApp]::new('roundtrip') + $app.Text = 'Test' + $app.Color = '#00FF00' + $app.DurationSeconds = 7 + + $json = $app.ToJson() + $restored = [AwtrixApp]::FromJson($json) + + $restored.Name | Should -Be 'roundtrip' + $restored.Text | Should -Be 'Test' + $restored.Color | Should -Be '#00FF00' + $restored.DurationSeconds | Should -Be 7 + } + + It 'Round-trips app-only properties' { + $app = [AwtrixApp]::new('lt') + $app.LifetimeSeconds = 120 + $app.LifetimeMode = 1 + $app.Position = 3 + $app.Save = $true + + $restored = [AwtrixApp]::FromJson($app.ToJson()) + + $restored.LifetimeSeconds | Should -Be 120 + $restored.LifetimeMode | Should -Be 1 + $restored.Position | Should -Be 3 + $restored.Save | Should -Be $true + } + } + + Context 'Clone' { + + It 'Returns a new AwtrixApp with the same properties' { + $original = [AwtrixApp]::new('orig') + $original.Text = 'Hello' + $original.Color = '#FF0000' + + $clone = $original.Clone('copy') + $clone.Name | Should -Be 'copy' + $clone.Text | Should -Be 'Hello' + $clone.Color | Should -Be '#FF0000' + } + + It 'Mutating the clone does not affect the original' { + $original = [AwtrixApp]::new('orig') + $original.Text = 'Hello' + + $clone = $original.Clone('copy') + $clone.Text = 'Changed' + + $original.Text | Should -Be 'Hello' + } + + It 'Clone without argument keeps the same Name' { + $original = [AwtrixApp]::new('same') + $clone = $original.Clone() + $clone.Name | Should -Be 'same' + } + } + + Context 'Dirty Tracking' { + + It 'GetDirtyPayload returns full payload before any Push' { + $app = [AwtrixApp]::new('dirty') + $app.Text = 'Initial' + $dirty = $app.GetDirtyPayload() + $dirty['text'] | Should -Be 'Initial' + } + + It 'GetDirtyPayload returns only changed keys after ResetDirtyState' { + $app = [AwtrixApp]::new('dirty') + $app.Text = 'Initial' + $app.ResetDirtyState() + + $app.Text = 'Updated' + $dirty = $app.GetDirtyPayload() + + $dirty.ContainsKey('text') | Should -Be $true + $dirty.ContainsKey('duration') | Should -Be $false + } + + It 'GetDirtyPayload returns empty when nothing changed since reset' { + $app = [AwtrixApp]::new('dirty') + $app.Text = 'Same' + $app.ResetDirtyState() + + $app.GetDirtyPayload().Count | Should -Be 0 + } + + It 'ResetDirtyState is called after Push' { + $app = [AwtrixApp]::new('dirty') + $app.Text = 'First' + $app.Push() + + $app.Text = 'Second' + $dirty = $app.GetDirtyPayload() + + $dirty.Keys | Should -Contain 'text' + $dirty.Values | Should -Contain 'Second' + } + } + + Context 'Push' { + + It 'POSTs to custom endpoint with app name as query string' { + $app = [AwtrixApp]::new('pushtest') + $app.Text = 'Hi' + $app.Push() + Should -Invoke Invoke-RestMethod -ModuleName awtrix -ParameterFilter { + $Uri -eq 'http://192.168.1.100/api/custom?name=pushtest' -and + $Method -eq 'POST' + } + } + + It 'Includes text in the POST body' { + $app = [AwtrixApp]::new('bodytest') + $app.Text = 'Hello' + $app.Push() + Should -Invoke Invoke-RestMethod -ModuleName awtrix -ParameterFilter { + ($Body | ConvertFrom-Json).text -eq 'Hello' + } + } + + It 'DirtyOnly sends only changed properties' { + $app = [AwtrixApp]::new('dirtyonly') + $app.Text = 'Init' + $app.DurationSeconds = 5 + $app.Push() + + $app.Text = 'Updated' + $app.Push($true) # dirty only + + Should -Invoke Invoke-RestMethod -ModuleName awtrix -ParameterFilter { + $parsed = $Body | ConvertFrom-Json + $parsed.text -eq 'Updated' -and $null -eq $parsed.duration + } + } + + It 'Throws when Name is not set' { + $app = [AwtrixApp]::new() + { $app.Push() } | Should -Throw '*Name must be set*' + } + } + + Context 'Remove' { + + It 'POSTs an empty body to the custom endpoint' { + $app = [AwtrixApp]::new('removetest') + $app.Remove() + Should -Invoke Invoke-RestMethod -ModuleName awtrix -ParameterFilter { + $Uri -eq 'http://192.168.1.100/api/custom?name=removetest' -and + $Method -eq 'POST' + } + } + + It 'Throws when Name is not set' { + { [AwtrixApp]::new().Remove() } | Should -Throw '*Name must be set*' + } + } + + Context 'SwitchTo' { + + It 'POSTs to switch endpoint with app name' { + $app = [AwtrixApp]::new('switchtest') + $app.SwitchTo() + Should -Invoke Invoke-RestMethod -ModuleName awtrix -ParameterFilter { + $Uri -eq 'http://192.168.1.100/api/switch' -and + ($Body | ConvertFrom-Json).name -eq 'switchtest' + } + } + } +} + +Describe 'AwtrixNotification class' { + + BeforeAll { + Mock Invoke-RestMethod {} -ModuleName awtrix + } + + Context 'Construction' { + + It 'Creates an empty instance' { + $n = [AwtrixNotification]::new() + $n | Should -Not -BeNullOrEmpty + } + + It 'Accepts initial text via constructor overload' { + $n = [AwtrixNotification]::new('Alert!') + $n.Text | Should -Be 'Alert!' + } + } + + Context 'ToPayload' { + + It 'Maps Hold to "hold"' { + $n = [AwtrixNotification]::new() + $n.Hold = $true + $n.ToPayload()['hold'] | Should -Be $true + } + + It 'Maps Sound to "sound"' { + $n = [AwtrixNotification]::new() + $n.Sound = 'alarm' + $n.ToPayload()['sound'] | Should -Be 'alarm' + } + + It 'Maps Rtttl to "rtttl"' { + $n = [AwtrixNotification]::new() + $n.Rtttl = 'Scale:d=4,o=5,b=120:c,d,e,f' + $n.ToPayload()['rtttl'] | Should -Be 'Scale:d=4,o=5,b=120:c,d,e,f' + } + + It 'Maps Stack to "stack"' { + $n = [AwtrixNotification]::new() + $n.Stack = $false + $n.ToPayload()['stack'] | Should -Be $false + } + + It 'Maps Wakeup to "wakeup"' { + $n = [AwtrixNotification]::new() + $n.Wakeup = $true + $n.ToPayload()['wakeup'] | Should -Be $true + } + + It 'Maps Clients to "clients"' { + $n = [AwtrixNotification]::new() + $n.Clients = @('10.0.0.2', '10.0.0.3') + $payload = $n.ToPayload() + $payload['clients'].Count | Should -Be 2 + } + + It 'Does not include notification-only keys that are null' { + $n = [AwtrixNotification]::new() + $n.Text = 'Hi' + $payload = $n.ToPayload() + $payload.ContainsKey('hold') | Should -Be $false + $payload.ContainsKey('sound') | Should -Be $false + } + } + + Context 'Send' { + + It 'POSTs to notify endpoint' { + $n = [AwtrixNotification]::new() + $n.Text = 'Test' + $n.Send() + Should -Invoke Invoke-RestMethod -ModuleName awtrix -ParameterFilter { + $Uri -eq 'http://192.168.1.100/api/notify' -and + $Method -eq 'POST' + } + } + + It 'Includes text in the POST body' { + $n = [AwtrixNotification]::new() + $n.Text = 'Hello' + $n.Send() + Should -Invoke Invoke-RestMethod -ModuleName awtrix -ParameterFilter { + ($Body | ConvertFrom-Json).text -eq 'Hello' + } + } + } + + Context 'Clone' { + + It 'Produces an independent copy' { + $original = [AwtrixNotification]::new('Original') + $original.Sound = 'alarm' + $clone = $original.Clone() + $clone.Text = 'Changed' + $original.Text | Should -Be 'Original' + } + } +} diff --git a/tests/New-AwtrixApp.tests.ps1 b/tests/New-AwtrixApp.tests.ps1 new file mode 100644 index 0000000..a37e04e --- /dev/null +++ b/tests/New-AwtrixApp.tests.ps1 @@ -0,0 +1,169 @@ +BeforeAll { + if ($null -eq $env:BHPSModuleManifest) { + & "$PSScriptRoot/../Build.ps1" -Task Init + } + $manifest = Import-PowerShellDataFile -Path $env:BHPSModuleManifest + $outputDir = Join-Path -Path $env:BHProjectPath -ChildPath 'Output' + $outputModDir = Join-Path -Path $outputDir -ChildPath $env:BHProjectName + $outputModVerDir = Join-Path -Path $outputModDir -ChildPath $manifest.ModuleVersion + $outputModVerManifest = Join-Path -Path $outputModVerDir -ChildPath "$($env:BHProjectName).psd1" + + Get-Module $env:BHProjectName | Remove-Module -Force -ErrorAction Ignore + Import-Module -Name $outputModVerManifest -Verbose:$false -ErrorAction Stop + + & (Get-Module $env:BHProjectName) { $script:AwtrixConnection = @{ BaseUri = 'http://192.168.1.100' } } +} + +Describe 'New-AwtrixApp' { + + BeforeAll { + Mock Invoke-RestMethod {} -ModuleName awtrix + } + + It 'Returns an [AwtrixApp] object' { + $app = New-AwtrixApp -Name 'test' + $app | Should -BeOfType ([AwtrixApp]) + } + + It 'Sets Name from parameter' { + $app = New-AwtrixApp -Name 'myapp' + $app.Name | Should -Be 'myapp' + } + + It 'Sets Text and Color from parameters' { + $app = New-AwtrixApp -Name 'test' -Text 'Hello' -Color '#FF0000' + $app.Text | Should -Be 'Hello' + $app.Color | Should -Be '#FF0000' + } + + It 'Converts switch parameters to bool on the object' { + $app = New-AwtrixApp -Name 'test' -Rainbow -NoScroll + $app.Rainbow | Should -Be $true + $app.NoScroll | Should -Be $true + } + + It 'Does not push to device when -Push is omitted' { + New-AwtrixApp -Name 'nopush' -Text 'Hi' + Should -Not -Invoke Invoke-RestMethod -ModuleName awtrix + } + + It 'Pushes to device when -Push is specified' { + New-AwtrixApp -Name 'dopush' -Text 'Hi' -Push + Should -Invoke Invoke-RestMethod -ModuleName awtrix -ParameterFilter { + $Uri -eq 'http://192.168.1.100/api/custom?name=dopush' -and + $Method -eq 'POST' + } + } + + It 'Includes all specified properties in the pushed payload' { + New-AwtrixApp -Name 'props' -Text 'Test' -DurationSeconds 10 -Icon 'info' -Push + Should -Invoke Invoke-RestMethod -ModuleName awtrix -ParameterFilter { + $parsed = $Body | ConvertFrom-Json + $parsed.text -eq 'Test' -and $parsed.duration -eq 10 -and $parsed.icon -eq 'info' + } + } + + It 'Accepts a named color and transforms it to hex' { + $app = New-AwtrixApp -Name 'c' -Color Red + $app.Color | Should -Be '#FF0000' + } + + It 'Accepts an RGB array color' { + $app = New-AwtrixApp -Name 'c' -Color @(255, 128, 0) + $colors = $app.Color + $colors[0] | Should -Be 255 + $colors[1] | Should -Be 128 + $colors[2] | Should -Be 0 + } + + It 'Returns an object that can be pushed later' { + $app = New-AwtrixApp -Name 'late' -Text 'Later' + $app.Text = 'Modified' + $app.Push() + Should -Invoke Invoke-RestMethod -ModuleName awtrix -ParameterFilter { + ($Body | ConvertFrom-Json).text -eq 'Modified' + } + } + + It 'Sets LifetimeSeconds and LifetimeMode' { + $app = New-AwtrixApp -Name 'lt' -LifetimeSeconds 300 -LifetimeMode 1 + $app.LifetimeSeconds | Should -Be 300 + $app.LifetimeMode | Should -Be 1 + } + + It 'Does not include Name or BaseUri in the pushed payload' { + New-AwtrixApp -Name 'sanity' -Text 'Hi' -BaseUri 'http://10.0.0.1' -Push + Should -Invoke Invoke-RestMethod -ModuleName awtrix -ParameterFilter { + $parsed = $Body | ConvertFrom-Json + $null -eq $parsed.Name -and $null -eq $parsed.BaseUri -and $null -eq $parsed.Push + } + } +} + +Describe 'New-AwtrixNotification' { + + BeforeAll { + Mock Invoke-RestMethod {} -ModuleName awtrix + } + + It 'Returns an [AwtrixNotification] object' { + $n = New-AwtrixNotification -Text 'Alert!' + $n | Should -BeOfType ([AwtrixNotification]) + } + + It 'Sets notification-specific properties' { + $n = New-AwtrixNotification -Text 'Test' -Hold -Sound 'alarm' -Wakeup + $n.Hold | Should -Be $true + $n.Sound | Should -Be 'alarm' + $n.Wakeup | Should -Be $true + } + + It 'Does not send when -Send is omitted' { + New-AwtrixNotification -Text 'Quiet' + Should -Not -Invoke Invoke-RestMethod -ModuleName awtrix + } + + It 'Sends to notify endpoint when -Send is specified' { + New-AwtrixNotification -Text 'Alert!' -Send + Should -Invoke Invoke-RestMethod -ModuleName awtrix -ParameterFilter { + $Uri -eq 'http://192.168.1.100/api/notify' -and + $Method -eq 'POST' + } + } +} + +Describe 'New-AwtrixAppCollection' { + + BeforeAll { + Mock Invoke-RestMethod {} -ModuleName awtrix + } + + It 'Returns an [AwtrixAppCollection] object' { + $p1 = New-AwtrixApp -Name 'p1' -Text 'Page1' + $c = New-AwtrixAppCollection -BaseName 'dashboard' -Apps @($p1) + $c | Should -BeOfType ([AwtrixAppCollection]) + } + + It 'Stores BaseName and Apps' { + $p1 = New-AwtrixApp -Name 'p1' -Text 'A' + $p2 = New-AwtrixApp -Name 'p2' -Text 'B' + $c = New-AwtrixAppCollection -BaseName 'multi' -Apps @($p1, $p2) + $c.BaseName | Should -Be 'multi' + $c.Apps.Count | Should -Be 2 + } + + It 'Does not push when -Push is omitted' { + $p1 = New-AwtrixApp -Name 'p1' -Text 'A' + New-AwtrixAppCollection -BaseName 'x' -Apps @($p1) + Should -Not -Invoke Invoke-RestMethod -ModuleName awtrix + } + + It 'Pushes to device when -Push is specified' { + $p1 = New-AwtrixApp -Name 'p1' -Text 'A' + New-AwtrixAppCollection -BaseName 'col' -Apps @($p1) -Push + Should -Invoke Invoke-RestMethod -ModuleName awtrix -ParameterFilter { + $Uri -eq 'http://192.168.1.100/api/custom?name=col' -and + $Method -eq 'POST' + } + } +} diff --git a/tests/Send-AwtrixNotification.tests.ps1 b/tests/Send-AwtrixNotification.tests.ps1 index 9a07e96..860f547 100644 --- a/tests/Send-AwtrixNotification.tests.ps1 +++ b/tests/Send-AwtrixNotification.tests.ps1 @@ -78,4 +78,21 @@ Describe 'Send-AwtrixNotification' { $null -eq ($Body | ConvertFrom-Json).BaseUri } } + + It '-PassThru returns an AwtrixNotification object' { + $result = Send-AwtrixNotification -Text 'Alert!' -PassThru + $result | Should -BeOfType ([AwtrixNotification]) + } + + It '-PassThru object has correct properties' { + $result = Send-AwtrixNotification -Text 'Alert!' -Sound 'alarm' -Hold -PassThru + $result.Text | Should -Be 'Alert!' + $result.Sound | Should -Be 'alarm' + $result.Hold | Should -Be $true + } + + It 'Returns nothing when -PassThru is omitted' { + $result = Send-AwtrixNotification -Text 'Quiet' + $result | Should -BeNullOrEmpty + } } diff --git a/tests/Set-AwtrixApp.tests.ps1 b/tests/Set-AwtrixApp.tests.ps1 index 7e78683..0dd498a 100644 --- a/tests/Set-AwtrixApp.tests.ps1 +++ b/tests/Set-AwtrixApp.tests.ps1 @@ -112,4 +112,21 @@ Describe 'Set-AwtrixApp' { ($Body | ConvertFrom-Json).draw.Count -eq 2 } } + + It '-PassThru returns an AwtrixApp object' { + $result = Set-AwtrixApp -Name 'pt' -Text 'Hello' -PassThru + $result | Should -BeOfType ([AwtrixApp]) + } + + It '-PassThru object has correct Name and properties' { + $result = Set-AwtrixApp -Name 'ptprops' -Text 'World' -DurationSeconds 8 -PassThru + $result.Name | Should -Be 'ptprops' + $result.Text | Should -Be 'World' + $result.DurationSeconds | Should -Be 8 + } + + It 'Returns nothing when -PassThru is omitted' { + $result = Set-AwtrixApp -Name 'nopt' -Text 'Hi' + $result | Should -BeNullOrEmpty + } } diff --git a/tests/Show-AwtrixScreen.tests.ps1 b/tests/Show-AwtrixScreen.tests.ps1 index 09f37f7..aeeb796 100644 --- a/tests/Show-AwtrixScreen.tests.ps1 +++ b/tests/Show-AwtrixScreen.tests.ps1 @@ -40,7 +40,7 @@ Describe 'Show-AwtrixScreen' -Skip:$script:SkipAll { It 'Throws when PwshSpectreConsole module is not installed' { Mock Get-Module { $null } -ModuleName awtrix -ParameterFilter { - $Name -eq 'PwshSpectreConsole' -and $ListAvailable + $Name -like 'PwshSpectreConsole*' -and $ListAvailable } { Show-AwtrixScreen -ScreenData $script:blackScreen } | Should -Throw '*PwshSpectreConsole*' diff --git a/tests/Update-AwtrixApp.tests.ps1 b/tests/Update-AwtrixApp.tests.ps1 new file mode 100644 index 0000000..5cf6319 --- /dev/null +++ b/tests/Update-AwtrixApp.tests.ps1 @@ -0,0 +1,89 @@ +BeforeAll { + if ($null -eq $env:BHPSModuleManifest) { + & "$PSScriptRoot/../Build.ps1" -Task Init + } + $manifest = Import-PowerShellDataFile -Path $env:BHPSModuleManifest + $outputDir = Join-Path -Path $env:BHProjectPath -ChildPath 'Output' + $outputModDir = Join-Path -Path $outputDir -ChildPath $env:BHProjectName + $outputModVerDir = Join-Path -Path $outputModDir -ChildPath $manifest.ModuleVersion + $outputModVerManifest = Join-Path -Path $outputModVerDir -ChildPath "$($env:BHProjectName).psd1" + + Get-Module $env:BHProjectName | Remove-Module -Force -ErrorAction Ignore + Import-Module -Name $outputModVerManifest -Verbose:$false -ErrorAction Stop + + & (Get-Module $env:BHProjectName) { $script:AwtrixConnection = @{ BaseUri = 'http://192.168.1.100' } } +} + +Describe 'Update-AwtrixApp' { + + BeforeAll { + Mock Invoke-RestMethod {} -ModuleName awtrix + } + + It 'Accepts an AwtrixApp from the pipeline and pushes it' { + $app = New-AwtrixApp -Name 'uptest' -Text 'Before' + $app | Update-AwtrixApp + Should -Invoke Invoke-RestMethod -ModuleName awtrix -ParameterFilter { + $Uri -eq 'http://192.168.1.100/api/custom?name=uptest' -and + $Method -eq 'POST' + } + } + + It 'Updates a property on the object before pushing' { + $app = New-AwtrixApp -Name 'textup' -Text 'Before' + $app | Update-AwtrixApp -Text 'After' + $app.Text | Should -Be 'After' + Should -Invoke Invoke-RestMethod -ModuleName awtrix -ParameterFilter { + ($Body | ConvertFrom-Json).text -eq 'After' + } + } + + It 'Updates multiple properties in one call' { + $app = New-AwtrixApp -Name 'multi' -Text 'X' + $app | Update-AwtrixApp -Text 'New' -Color '#00FF00' -DurationSeconds 15 + $app.Text | Should -Be 'New' + $app.Color | Should -Be '#00FF00' + $app.DurationSeconds | Should -Be 15 + } + + It 'Converts switch parameters to bool on the object' { + $app = New-AwtrixApp -Name 'sw' + $app | Update-AwtrixApp -Rainbow + $app.Rainbow | Should -Be $true + } + + It '-DirtyOnly sends only changed properties' { + $app = New-AwtrixApp -Name 'donly' -Text 'Init' -DurationSeconds 5 + $app.Push() # snapshot clean state + + $app | Update-AwtrixApp -Text 'Changed' -DirtyOnly + + Should -Invoke Invoke-RestMethod -ModuleName awtrix -ParameterFilter { + $parsed = $Body | ConvertFrom-Json + $parsed.text -eq 'Changed' -and $null -eq $parsed.duration + } + } + + It '-PassThru returns the mutated AwtrixApp object' { + $app = New-AwtrixApp -Name 'pt' -Text 'Before' + $result = $app | Update-AwtrixApp -Text 'After' -PassThru + $result | Should -BeOfType ([AwtrixApp]) + $result.Text | Should -Be 'After' + } + + It '-PassThru returns the same object reference (mutates in place)' { + $app = New-AwtrixApp -Name 'ref' -Text 'X' + $result = $app | Update-AwtrixApp -Text 'Y' -PassThru + # Same object — they're the identical reference + [object]::ReferenceEquals($app, $result) | Should -Be $true + } + + It 'Handles multiple pipeline objects' { + $a = New-AwtrixApp -Name 'batch1' -Text 'A' + $b = New-AwtrixApp -Name 'batch2' -Text 'B' + @($a, $b) | Update-AwtrixApp -DurationSeconds 3 + $a.DurationSeconds | Should -Be 3 + $b.DurationSeconds | Should -Be 3 + Should -Invoke Invoke-RestMethod -ModuleName awtrix -Exactly -Times 2 + } +}