From ea44315d77a3e83d82e80f2124fb87a6837e4f9b Mon Sep 17 00:00:00 2001 From: Thomas Lauterbach Date: Sat, 8 Feb 2025 21:28:42 +0100 Subject: [PATCH 01/16] Initial contribution Signed-off-by: Thomas Lauterbach --- bom/openhab-addons/pom.xml | 5 + .../NOTICE | 13 + .../README.md | 380 ++++++++++ .../pom.xml | 25 + .../src/main/feature/feature.xml | 12 + .../internal/AppConfigOptions.java | 26 + .../internal/AwtrixLightBindingConstants.java | 184 +++++ .../internal/AwtrixLightHandlerFactory.java | 65 ++ .../internal/BridgeConfigOptions.java | 30 + .../mqtt/awtrixlight/internal/Helper.java | 123 ++++ .../internal/action/AwtrixActions.java | 239 ++++++ .../awtrixlight/internal/app/AwtrixApp.java | 625 ++++++++++++++++ .../internal/app/AwtrixNotification.java | 127 ++++ .../AwtrixLightBridgeDiscoveryService.java | 97 +++ .../AwtrixLightDiscoveryService.java | 113 +++ .../handler/AwtrixLightAppHandler.java | 683 ++++++++++++++++++ .../handler/AwtrixLightBridgeHandler.java | 626 ++++++++++++++++ .../main/resources/OH-INF/config/config.xml | 47 ++ .../resources/OH-INF/thing/thing-types.xml | 593 +++++++++++++++ .../resources/OH-INF/update/app-updates.xml | 43 ++ .../OH-INF/update/bridge-updates.xml | 50 ++ .../mqtt/awtrixlight/internal/HelperTest.java | 146 ++++ bundles/pom.xml | 1 + 23 files changed, 4253 insertions(+) create mode 100644 bundles/org.openhab.binding.mqtt.awtrixlight/NOTICE create mode 100644 bundles/org.openhab.binding.mqtt.awtrixlight/README.md create mode 100644 bundles/org.openhab.binding.mqtt.awtrixlight/pom.xml create mode 100644 bundles/org.openhab.binding.mqtt.awtrixlight/src/main/feature/feature.xml create mode 100644 bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/AppConfigOptions.java create mode 100644 bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/AwtrixLightBindingConstants.java create mode 100644 bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/AwtrixLightHandlerFactory.java create mode 100644 bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/BridgeConfigOptions.java create mode 100644 bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/Helper.java create mode 100644 bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/action/AwtrixActions.java create mode 100644 bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/app/AwtrixApp.java create mode 100644 bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/app/AwtrixNotification.java create mode 100644 bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/discovery/AwtrixLightBridgeDiscoveryService.java create mode 100644 bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/discovery/AwtrixLightDiscoveryService.java create mode 100644 bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/handler/AwtrixLightAppHandler.java create mode 100644 bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/handler/AwtrixLightBridgeHandler.java create mode 100644 bundles/org.openhab.binding.mqtt.awtrixlight/src/main/resources/OH-INF/config/config.xml create mode 100644 bundles/org.openhab.binding.mqtt.awtrixlight/src/main/resources/OH-INF/thing/thing-types.xml create mode 100644 bundles/org.openhab.binding.mqtt.awtrixlight/src/main/resources/OH-INF/update/app-updates.xml create mode 100644 bundles/org.openhab.binding.mqtt.awtrixlight/src/main/resources/OH-INF/update/bridge-updates.xml create mode 100644 bundles/org.openhab.binding.mqtt.awtrixlight/src/test/java/org/openhab/binding/mqtt/awtrixlight/internal/HelperTest.java diff --git a/bom/openhab-addons/pom.xml b/bom/openhab-addons/pom.xml index ea798c3d2c6f5..bb0b318825c52 100644 --- a/bom/openhab-addons/pom.xml +++ b/bom/openhab-addons/pom.xml @@ -1201,6 +1201,11 @@ org.openhab.binding.mqtt ${project.version} + + org.openhab.addons.bundles + org.openhab.binding.mqtt.awtrixlight + ${project.version} + org.openhab.addons.bundles org.openhab.binding.mqtt.espmilighthub diff --git a/bundles/org.openhab.binding.mqtt.awtrixlight/NOTICE b/bundles/org.openhab.binding.mqtt.awtrixlight/NOTICE new file mode 100644 index 0000000000000..38d625e349232 --- /dev/null +++ b/bundles/org.openhab.binding.mqtt.awtrixlight/NOTICE @@ -0,0 +1,13 @@ +This content is produced and maintained by the openHAB project. + +* Project home: https://www.openhab.org + +== Declared Project Licenses + +This program and the accompanying materials are made available under the terms +of the Eclipse Public License 2.0 which is available at +https://www.eclipse.org/legal/epl-2.0/. + +== Source Code + +https://github.com/openhab/openhab-addons diff --git a/bundles/org.openhab.binding.mqtt.awtrixlight/README.md b/bundles/org.openhab.binding.mqtt.awtrixlight/README.md new file mode 100644 index 0000000000000..f97ef6b8d87d9 --- /dev/null +++ b/bundles/org.openhab.binding.mqtt.awtrixlight/README.md @@ -0,0 +1,380 @@ +# MQTT Awtrix 3 Binding + +This binding allows you to control Awtrix 3 (formerly Awtrix Light) LED matrix displays via MQTT. The Awtrix 3 is a customizable 32x8 LED matrix display that can show various information like time, weather, notifications and custom text/graphics. + +## Supported Things + +This binding supports two types of things: + +| Thing Type | Description | +|------------------------|-------------------------------------------------------------------------------------------------| +| `awtrixclock` (Bridge) | Represents an Awtrix 3 display device. Acts as a bridge for apps. | +| `awtrixapp` | Represents an app running on the Awtrix display. Apps can show text, icons, notifications, etc. | + +## Prerequisites + +- An MQTT broker (the MQTT binding must be installed and a broker configured) +- An Awtrix 3 LED matrix display configured to use MQTT + +## Thing Configuration + +### Bridge Configuration (`awtrixclock`) + +| Parameter | Description | Required | Default | +|-----------------------|--------------------------------------------------------------------------------------------------------------------------------------------|----------|----------| +| `basetopic` | The MQTT base topic for the Awtrix device | Yes | "awtrix" | +| `appLockTimeout` | Timeout in seconds before releasing the lock to a selected app and returning to normal app cycle (see App Configuration for more details). | No | 10 | +| `discoverDefaultApps` | Enable discovery of default apps. Since default apps cannot be controlled by this binding this should usually be disabled. | No | false | +| `lowBatteryThreshold` | Battery level threshold for low battery warning. | No | 25 | + +### App Configuration (`awtrixapp`) + +| Parameter | Description | Required | Default | +|--------------|------------------------------------|----------|---------| +| `appname` | Name of the app | Yes | - | +| `useButtons` | Enable button control for this app | No | false | + +When you enable the button control for an app, you can lock the app to the display by pushing the select button on the clock device. A red indicator will be shown while the app is locked and will start to blink shortly before the lock ends. The lock will last for the appLockTimeout set for the bridge. As long as the app is locked the normal app cycle is disabled and you can control the app by pressing the left and right buttons or the select button on the clock device. Pressing a button while the app is locked will reset the lock timeout to the value set for appLockTimeout. Left and right button presses will emit button events on the clock itself and the selected app. The button events can be used by rules to change the displayed app or perform any other actions (for example change the text color of the app or skip the current song playing on your audio device). + +## Channels + +### Bridge Channels (`awtrixclock`) + +| Channel | Type | Description | +|-------------------|----------------------|------------------------------------------------------------------------------------------------------------------------------------------------| +| `app` | String | Currently active app: Will show the name of the app that is currently shown on the display. | +| `autoBrightness` | Switch | Automatic brightness control: The clock will adjust the display brightness automatically based on ambient light. | +| `batteryLevel` | Number | Battery level: The battery level of the internal battery in percent. | +| `brightness` | Dimmer | Display brightness: The brightness of the display in percent. | +| `buttonLeft` | Trigger | Left button press event: Triggered when the left button is pressed/released (Event PRESSED or RELEASED). | +| `buttonRight` | Trigger | Right button press event: Triggered when the right button is pressed/released (Event PRESSED or RELEASED). | +| `buttonSelect` | Trigger | Select button press event: Triggered when the select button is pressed/released (Event PRESSED or RELEASED). | +| `display` | Switch | Display on/off: Switches the display on or off. The clock will still stay on while the display is off. | +| `humidity` | Number:Dimensionless | Relative humidity: Relative humidity in percent. For the Ulanzi clock values are usually very inaccurate. | +| `indicator1` | Switch | Control first indicator LED: Switches the first indicator LED on or off. The color of the LED will be green but can be customised by using thing actions (you can also use blinking/fading effects). | +| `indicator2` | Switch | Control second indicator LED: Switches the second indicator LED on or off.The color of the LED will be green but can be customised by using thing actions (you can also use blinking/fading effects). | +| `indicator3` | Switch | Control third indicator LED: Switches the third indicator LED on or off. The color of the LED will be green but can be customised by using thing actions (you can also use blinking/fading effects). | +| `lowBattery` | Switch | Low battery warning: Will be switched ON as soon as the battery level drops below the lowBatteryThreshold set for the bridge. | +| `lux` | Number:Illuminance | Ambient light level: Ambient light level in lux as measured by the built-in light sensor. | +| `rssi` | Number:Dimensionless | WiFi signal strength (RSSI): WiFi signal strength (RSSI) in dBm. | +| `rtttl` | String | Play RTTTL ringtone: Play a ringtone specified in RTTTL format (see https://de.wikipedia.org/wiki/Ring_Tones_Text_Transfer_Language) | +| `screen` | String | Screen image: Allows you to mirror the screen image from the clock. The screen image will be updated automatically when the app changes but can be updated manually by sending a RefreshType command to the channel. | +| `sound` | String | Play sound file: The sound file must be available on the clock device in the MELODIES folder. Save a file with a valid RTTTL string (e.g. melody.txt) in this folder and play it by sending a String command to the channel with the filename without file extension (e.g. "melody"). | +| `temperature` | Number:Temperature | Device temperature: Temperature in °C as measured by the built-in temperature sensor. For the Ulanzi clock values are usually very inaccurate. | + +### App Channels (`awtrixapp`) + +| Channel | Type | Description | +|----------------------|----------------------|---------------------------------------------------------------------------------------------------------------------------------------------| +| `active` | Switch | Enable/disable the app: Switches the app on or off. Note that channels of inactive apps will be reset to their default values during a restart of openHAB. | +| `autoscale` | Switch | Enable/disable autoscaling for bar and linechart. | +| `background` | Color | Sets a background color. | +| `bar` | String | Shows a bar chart: Send a string with values separated by commas (e.g. "value1,value2,value3"). Only the last 16 values will be displayed. | +| `blink` | Number:Time | Blink text: Blink the text in the specified interval. Ignored if gradientColor or rainbow are set. | +| `center` | Switch | Center short text horizontally and disable scrolling. | +| `color` | Color | Text, bar or line chart color. | +| `duration` | Number:Time | Display duration in seconds. | +| `effect` | String | Display effect (see https://blueforcer.github.io/awtrix3/#/effects for possible values). | +| `effectBlend` | Switch | Enable smoother effect transitions. Only to be used with effect. | +| `effectPalette` | String | Color palette for effects (see https://blueforcer.github.io/awtrix3/#/effects for possible values and how to create custom palettes). Only to be used with effect. | +| `effectSpeed` | Number:Dimensionless | Effect animation speed: Higher means faster (see https://blueforcer.github.io/awtrix3/#/effects). Only to be used with effect. | +| `fade` | Number:Time | Fade text: Fades the text in and out in the specified interval. Ignored if gradientColor or rainbow are set. | +| `gradientColor` | Color | Secondary color for gradient effects. Use color for setting the primary color. | +| `icon` | String | Icon name to display: Install icons on the clock device first. | +| `lifetime` | Number:Time | App lifetime: Define how long the app will remain active on the clock. | +| `lifetimeMode` | String | Lifetime mode: Define if the app should be deleted (Command DELETE) or marked as stale (Command STALE) after lifetime. | +| `line` | String | Shows a line chart: Send a string with values separated by commas (e.g. "value1,value2,value3"). Only the last 16 values will be displayed. | +| `overlay` | String | Enable overlay mode: Shows a weather overlay effect (can be any of clear, snow, rain, drizzle, storm, thunder, frost). | +| `progress` | Number:Dimensionless | Progress value: Shows a progress bar at the bottom of the app with the specified percentage value. | +| `progressBackground` | Color | Progress bar background color: Background color for the progress bar. | +| `progressColor` | Color | Progress bar color: Color for the progress bar. | +| `pushIcon` | String | Push icon animation (STATIC=Icon doesn't move, PUSHOUT=Icon moves with text and will not appear again, PUSHOUTRETURN=Icon moves with text but appears again when the text starts to scroll again). | +| `rainbow` | Switch | Enable rainbow effect: Uses a rainbow effect for the displayed text. | +| `reset` | Switch | Reset app to default state: All channels will be reset to their default values. | +| `scrollSpeed` | Number:Dimensionless | Text scrolling speed: Provide as percentage value. The original speed is 100%. Values above 100% will increase the scrolling speed, values below 100% will decrease it. Setting this value to 0 will disable scrolling completely. | +| `text` | String | Text to display. | +| `textCase` | Number:Dimensionless | Set text case (0=normal, 1=uppercase, 2=lowercase). | +| `textOffset` | Number:Dimensionless | Text offset position: Horizontal offset of the text in pixels. | +| `topText` | String | Draws the text on the top of the display. | + +## Full Example + +### Things + +``` +Bridge mqtt:broker:myBroker [ host="localhost", port=1883 ] +Bridge mqtt:awtrixclock:myBroker:myAwtrix "Living Room Display" (mqtt:broker:myBroker) [ basetopic="awtrix", appLockTimeout=10, lowBatteryThreshold=25 ] { + Thing awtrixapp clock "Clock App" [ appname="clock", useButtons=true ] + Thing awtrixapp weather "Weather App" [ appname="weather" ] + Thing awtrixapp calendar "Calendar App" [ appname="calendar" ] + Thing awtrixapp custom "Custom App" [ appname="custom" ] +} +``` + +### Items + +``` +// Bridge items (Living Room Display) +Group gAwtrix "Living Room Awtrix Display" +Dimmer Display_Brightness "Brightness [%d %%]" (gAwtrix) { channel="mqtt:awtrixclock:myBroker:myAwtrix:brightness" } +Switch Display_Power "Power" (gAwtrix) { channel="mqtt:awtrixclock:myBroker:myAwtrix:power" } +Switch Display_Screen "Screen" (gAwtrix) { channel="mqtt:awtrixclock:myBroker:myAwtrix:display" } +Switch Display_Sound "Sound" (gAwtrix) { channel="mqtt:awtrixclock:myBroker:myAwtrix:sound" } +Switch Display_AutoBrightness "Auto Brightness" (gAwtrix) { channel="mqtt:awtrixclock:myBroker:myAwtrix:autoBrightness" } +Number:Temperature Display_Temperature "Temperature [%.1f °C]" (gAwtrix) { channel="mqtt:awtrixclock:myBroker:myAwtrix:temperature" } +Number:Dimensionless Display_Humidity "Humidity [%d %%]" (gAwtrix) { channel="mqtt:awtrixclock:myBroker:myAwtrix:humidity" } +Number Display_Battery "Battery Level [%d %%]" (gAwtrix) { channel="mqtt:awtrixclock:myBroker:myAwtrix:batteryLevel" } +Switch Display_LowBattery "Low Battery" (gAwtrix) { channel="mqtt:awtrixclock:myBroker:myAwtrix:lowBattery" } +Number:Dimensionless Display_WiFi "WiFi Signal [%d %%]" (gAwtrix) { channel="mqtt:awtrixclock:myBroker:myAwtrix:rssi" } +String Display_CurrentApp "Active App [%s]" (gAwtrix) { channel="mqtt:awtrixclock:myBroker:myAwtrix:app" } + +// Clock App items +Group gAwtrixClock "Clock App" +Switch Clock_Active "Clock Active" (gAwtrixClock) { channel="mqtt:awtrixapp:myBroker:myAwtrix:clock:active" } +String Clock_Text "Clock Text" (gAwtrixClock) { channel="mqtt:awtrixapp:myBroker:myAwtrix:clock:text" } +Color Clock_Color "Clock Color" (gAwtrixClock) { channel="mqtt:awtrixapp:myBroker:myAwtrix:clock:color" } +Number Clock_Duration "Clock Duration" (gAwtrixClock) { channel="mqtt:awtrixapp:myBroker:myAwtrix:clock:duration" } + +// Weather App items +Group gAwtrixWeather "Weather App" +Switch Weather_Active "Weather Active" (gAwtrixWeather) { channel="mqtt:awtrixapp:myBroker:myAwtrix:weather:active" } +String Weather_Text "Weather Text" (gAwtrixWeather) { channel="mqtt:awtrixapp:myBroker:myAwtrix:weather:text" } +String Weather_Icon "Weather Icon" (gAwtrixWeather) { channel="mqtt:awtrixapp:myBroker:myAwtrix:weather:icon" } +Color Weather_Color "Weather Color" (gAwtrixWeather) { channel="mqtt:awtrixapp:myBroker:myAwtrix:weather:color" } +Switch Weather_Rainbow "Weather Rainbow Effect" (gAwtrixWeather) { channel="mqtt:awtrixapp:myBroker:myAwtrix:weather:rainbow" } + +// Custom App items with advanced features +Switch Custom_Active "Custom App Active" (gAwtrix) { channel="mqtt:awtrixapp:myBroker:myAwtrix:custom:active" } +String Custom_Text "Custom Text" (gAwtrix) { channel="mqtt:awtrixapp:myBroker:myAwtrix:custom:text" } +String Custom_Icon "Custom Icon" (gAwtrix) { channel="mqtt:awtrixapp:myBroker:myAwtrix:custom:icon" } +Color Custom_Color "Text Color" (gAwtrix) { channel="mqtt:awtrixapp:myBroker:myAwtrix:custom:color" } +Color Custom_Background "Background Color" (gAwtrix) { channel="mqtt:awtrixapp:myBroker:myAwtrix:custom:background" } +Number:Dimensionless Custom_ScrollSpeed "Scroll Speed" (gAwtrix) { channel="mqtt:awtrixapp:myBroker:myAwtrix:custom:scrollSpeed" } +Switch Custom_Center "Center Text" (gAwtrix) { channel="mqtt:awtrixapp:myBroker:myAwtrix:custom:center" } +Number:Dimensionless Custom_Progress "Progress [%d %%]" (gAwtrix) { channel="mqtt:awtrixapp:myBroker:myAwtrix:custom:progress" } +Color Custom_ProgressColor "Progress Color" (gAwtrix) { channel="mqtt:awtrixapp:myBroker:myAwtrix:custom:progressColor" } +``` + +### Sitemap + +``` + +sitemap awtrix label="Awtrix Display" { + Frame label="Display Control" { + Switch item=Display_Power + Slider item=Display_Brightness + Switch item=Display_Screen + Switch item=Display_Sound + Switch item=Display_AutoBrightness + Text item=Display_Temperature + Text item=Display_Humidity + Text item=Display_Battery visibility=[Display_LowBattery==ON] + Text item=Display_WiFi + Text item=Display_CurrentApp + } + + Frame label="Clock App" { + Switch item=Clock_Active + Text item=Clock_Text + Colorpicker item=Clock_Color + Slider item=Clock_Duration + } + + Frame label="Weather App" { + Switch item=Weather_Active + Text item=Weather_Text + Text item=Weather_Icon + Colorpicker item=Weather_Color + Switch item=Weather_Rainbow + } + + Frame label="Custom App" { + Switch item=Custom_Active + Text item=Custom_Text + Text item=Custom_Icon + Colorpicker item=Custom_Color + Colorpicker item=Custom_Background + Slider item=Custom_ScrollSpeed + Switch item=Custom_Center + Slider item=Custom_Progress + Colorpicker item=Custom_ProgressColor + } +} + +``` + +## Discovery + +The binding can automatically discover Awtrix devices that publish their status to the configured MQTT broker. Once a device is discovered, it will appear in the inbox. Default Awtrix apps can also be discovered if `discoverDefaultApps` is enabled on the bridge. This is however not recommended as the default apps cannot be controlled via this binding. + +## Actions + +The binding provides various actions that can be used in rules to control the Awtrix display. To use these actions, you need to import them in your rules. + +Rules DSL: + +``` +val awtrixActions = getActions("mqtt.awtrixlight", "mqtt:awtrixclock:myBroker:myAwtrix") +``` + +JS Scripting: + +```javascript +var awtrixActions = actions.thingActions("mqtt.awtrixlight", "mqtt:awtrixclock:myBroker:myAwtrix"); +``` + +### Indicator Control + +Control the three indicator LEDs on the Awtrix display (JS Scripting): + +```javascript +// Blink indicator 1 in red for 1 second +awtrixActions.blinkIndicator(1, [255,0,0], 1000) + +// Fade indicator 2 to blue over 2 seconds +awtrixActions.fadeIndicator(2, [0,0,255], 2000) + +// Turn on indicator 3 in green +awtrixActions.activateIndicator(3, [0,255,0]) + +// Turn off indicator 1 +awtrixActions.deactivateIndicator(1) +``` + +### Device Control + +Control basic device functions: + +```javascript +// Reboot the device +awtrixActions.reboot() + +// Put device to sleep for 60 seconds +awtrixActions.sleep(60) + +// Perform firmware upgrade +awtrixActions.upgrade() +``` + +### Sound Control + +Play sounds and melodies: + +```javascript +// Play a predefined sound file (without extension) +awtrixActions.playSound("notification") + +// Play an RTTTL melody +awtrixActions.playRtttl("Indiana:d=4,o=5,b=250:e,8p,8f,8g,8p,1c6,8p.,d,8p,8e,1f,p.,g,8p,8a,8b,8p,1f6,p,a,8p,8b,2c6,2d6,2e6,e,8p,8f,8g,8p,1c6,p,d6,8p,8e6,1f.6,g,8p,8g,e.6,8p,d6,8p,8g,e.6,8p,d6,8p,8g,f.6,8p,e6,8p,8d6,2c6") +``` + +### Notifications + +Display notifications on the screen: + +``` +// Show simple notification with icon +awtrixActions.showNotification("Hello World", "alert") + +// Show custom notification with advanced options +val params = newHashMap( + 'text' -> 'Custom Message', + 'icon' -> 'warning', + 'color' -> [255,165,0], // Orange color + 'rainbow' -> true, + 'duration' -> 10 +) +awtrixActions.showCustomNotification( + params, // Notification parameters + false, // hold: Keep notification until manually cleared + true, // wakeUp: Wake up from screen saver + true, // stack: Add to notification stack + "Indiana:d=4,o=5,b=250:e,8p,8f,8g,8p,1c6,8p.,d,8p,8e,1f,p.,g,8p,8a,8b,8p,1f6,p,a,8p,8b,2c6,2d6,2e6,e,8p,8f,8g,8p,1c6,p,d6,8p,8e6,1f.6,g,8p,8g,e.6,8p,d6,8p,8g,e.6,8p,d6,8p,8g,f.6,8p,e6,8p,8d6,2c6", // RTTTL sound to play (not both sound and rtttl) + "alert", // Sound file to play (not both sound and rtttl) + false // loopSound: Loop the sound until manually stopped +) +``` + +#### Custom Notification Parameters + +The action method parameters: + +| Parameter | Type | Description | +|-----------|------|-------------| +| `hold` | Boolean | Keep notification until manually cleared | +| `wakeUp` | Boolean | Wake up from screen saver | +| `stack` | Boolean | Add to notification stack | +| `rtttl` | String | RTTTL melody to play | +| `sound` | String | Sound file to play (without extension) | +| `loopSound` | Boolean | Loop the sound | +| `params` | Map | Notification parameters | + +The `showCustomNotification` action accepts all app channels as shown above as parameters in the params map. + +### Rule Examples + +Here are some example rules demonstrating various features: + +``` + +rule "Battery Status Indicator Demo" +when + Item Display_Battery changed +then + if (Display_Battery.state <= 20) { + // Show low battery warning with blinking red indicator + getActions("mqtt.awtrixlight", "mqtt:awtrixclock:myBroker:myAwtrix").blinkIndicator(1, [255,0,0], 500) + getActions("mqtt.awtrixlight", "mqtt:awtrixclock:myBroker:myAwtrix").showNotification("Low Battery!", "battery-alert") + } else if (Display_Battery.state <= 50) { + // Show yellow indicator for medium battery + getActions("mqtt.awtrixlight", "mqtt:awtrixclock:myBroker:myAwtrix").setIndicator(1, [255,255,0]) + } else { + // Show green indicator for good battery + getActions("mqtt.awtrixlight", "mqtt:awtrixclock:myBroker:myAwtrix").setIndicator(1, [0,255,0]) + } +end + +rule "Door Bell Demo" +when + Item Doorbell_Button changed to ON +then + // Play sound and show notification + getActions("mqtt.awtrixlight", "mqtt:awtrixclock:myBroker:myAwtrix").playRtttl("doorbell:d=4,o=6,b=100:8e,8g,8e,8c") + + var params = newHashMap( + 'text' -> "Doorbell", + 'icon' -> "bell-ring", + 'color' -> [0,255,255], // Cyan color + 'pushIcon' -> "PUSHOUT", + 'center' -> true + ) + getActions("mqtt.awtrixlight", "mqtt:awtrixclock:myBroker:myAwtrix").showCustomNotification( + params, false, true, true, "", "", false + ) +end + +rule "Progress Bar Demo" +when + Item Washing_Machine_Progress changed +then + var progress = (Washing_Machine_Progress.state as Number).intValue + + // Update custom app with progress bar + Custom_Text.sendCommand("Washing") + Custom_Icon.sendCommand("washing-machine") + Custom_Progress.sendCommand(progress) + Custom_ProgressColor.sendCommand("0,255,0") // Green progress bar + + if (progress == 100) { + // Play sound when done + getActions("mqtt.awtrixlight", "mqtt:awtrixclock:myBroker:myAwtrix").playSound("complete") + } +end +``` + +These rules demonstrate: + +- Using indicators to show battery status +- Creating custom notifications with icons and colors +- Playing RTTTL melodies and sound files +- Displaying progress bars diff --git a/bundles/org.openhab.binding.mqtt.awtrixlight/pom.xml b/bundles/org.openhab.binding.mqtt.awtrixlight/pom.xml new file mode 100644 index 0000000000000..5fa0c9edbf57c --- /dev/null +++ b/bundles/org.openhab.binding.mqtt.awtrixlight/pom.xml @@ -0,0 +1,25 @@ + + + + 4.0.0 + + + org.openhab.addons.bundles + org.openhab.addons.reactor.bundles + 5.0.0-SNAPSHOT + + + org.openhab.binding.mqtt.awtrixlight + openHAB Add-ons :: Bundles :: MQTT Awtrix Light + + + + org.openhab.addons.bundles + org.openhab.binding.mqtt + ${project.version} + provided + + + + diff --git a/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/feature/feature.xml b/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/feature/feature.xml new file mode 100644 index 0000000000000..b31dddabc0832 --- /dev/null +++ b/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/feature/feature.xml @@ -0,0 +1,12 @@ + + + mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features + + + openhab-runtime-base + openhab-transport-mqtt + mvn:org.openhab.addons.bundles/org.openhab.binding.mqtt/${project.version} + mvn:org.openhab.addons.bundles/org.openhab.binding.mqtt.awtrixlight/${project.version} + + + diff --git a/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/AppConfigOptions.java b/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/AppConfigOptions.java new file mode 100644 index 0000000000000..eafb302e34d2f --- /dev/null +++ b/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/AppConfigOptions.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2010-2025 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.mqtt.awtrixlight.internal; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link AppConfigOptions} Holds the config for the app settings. + * + * @author Thomas Lauterbach - Initial contribution + */ +@NonNullByDefault +public class AppConfigOptions { + public String appname = ""; + public boolean useButtons = false; +} diff --git a/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/AwtrixLightBindingConstants.java b/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/AwtrixLightBindingConstants.java new file mode 100644 index 0000000000000..de2cb6017c1c4 --- /dev/null +++ b/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/AwtrixLightBindingConstants.java @@ -0,0 +1,184 @@ +/* + * Copyright (c) 2010-2025 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ + +package org.openhab.binding.mqtt.awtrixlight.internal; + +import static org.openhab.binding.mqtt.MqttBindingConstants.BINDING_ID; + +import java.math.BigDecimal; +import java.util.Set; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.thing.ThingTypeUID; + +/** + * The {@link AwtrixLightBindingConstants} class defines common constants, which are + * used across the whole binding. + * + * @author Thomas Lauterbach - Initial contribution + */ +@NonNullByDefault +public class AwtrixLightBindingConstants { + + // List of all Thing Type UIDs + public static final ThingTypeUID THING_TYPE_APP = new ThingTypeUID(BINDING_ID, "awtrixapp"); + public static final ThingTypeUID THING_TYPE_BRIDGE = new ThingTypeUID(BINDING_ID, "awtrixclock"); + public static final Set SUPPORTED_THING_TYPES = Set.of(THING_TYPE_APP, THING_TYPE_BRIDGE); + + // Thing Type IDs + public static final String AWTRIX_APP = "awtrixapp"; + public static final String AWTRIX_CLOCK = "awtrixclock"; + + // Matrix Size + public static final int SCREEN_HEIGHT = 8; + public static final int SCREEN_WIDTH = 32; + + // Clock Properties + public static final String PROP_APPID = "appid"; + public static final String PROP_APPLOCKTIMEOUT = "appLockTimeout"; + public static final String PROP_APPNAME = "appname"; + public static final String PROP_APP_CONTROLLABLE = "useButtons"; + public static final String PROP_BASETOPIC = "basetopic"; + public static final String PROP_DISCOVERDEFAULT = "discoverDefaultApps"; + public static final String PROP_FIRMWARE = "firmware"; + public static final String PROP_UNIQUEID = "uniqueId"; + public static final String PROP_VENDOR = "vendor"; + + // Clock Topics + public static final String TOPIC_BASE = "awtrix"; + public static final String TOPIC_BUTLEFT = "/stats/buttonLeft"; + public static final String TOPIC_BUTRIGHT = "/stats/buttonRight"; + public static final String TOPIC_BUTSELECT = "/stats/buttonSelect"; + public static final String TOPIC_INDICATOR1 = "/indicator1"; + public static final String TOPIC_INDICATOR2 = "/indicator2"; + public static final String TOPIC_INDICATOR3 = "/indicator3"; + public static final String TOPIC_NOTIFY = "/notify"; + public static final String TOPIC_POWER = "/power"; + public static final String TOPIC_REBOOT = "/reboot"; + public static final String TOPIC_RTTTL = "/rtttl"; + public static final String TOPIC_SCREEN = "/screen"; + public static final String TOPIC_SEND_SCREEN = "/sendscreen"; + public static final String TOPIC_SETTINGS = "/settings"; + public static final String TOPIC_SLEEP = "/sleep"; + public static final String TOPIC_SOUND = "/sound"; + public static final String TOPIC_STATS = "/stats"; + public static final String TOPIC_STATS_CURRENT_APP = "/stats/currentApp"; + public static final String TOPIC_SWITCH = "/switch"; + public static final String TOPIC_UPGRADE = "/doupdate"; + + // Stats fields + public static final String FIELD_BRIDGE_APP = "app"; + public static final String FIELD_BRIDGE_BATTERY = "bat"; + public static final String FIELD_BRIDGE_BATTERY_RAW = "bat_raw"; + public static final String FIELD_BRIDGE_BRIGHTNESS = "bri"; + public static final String FIELD_BRIDGE_FIRMWARE = "version"; + public static final String FIELD_BRIDGE_HUMIDITY = "hum"; + public static final String FIELD_BRIDGE_INDICATOR1 = "indicator1"; + public static final String FIELD_BRIDGE_INDICATOR2 = "indicator2"; + public static final String FIELD_BRIDGE_INDICATOR3 = "indicator3"; + public static final String FIELD_BRIDGE_INDICATOR1_COLOR = "indicator1-color"; + public static final String FIELD_BRIDGE_INDICATOR2_COLOR = "indicator2-color"; + public static final String FIELD_BRIDGE_INDICATOR3_COLOR = "indicator3-color"; + public static final String FIELD_BRIDGE_LDR_RAW = "ldr_raw"; + public static final String FIELD_BRIDGE_LUX = "lux"; + public static final String FIELD_BRIDGE_MATRIX = "matrix"; + public static final String FIELD_BRIDGE_MESSAGES = "messages"; + public static final String FIELD_BRIDGE_RAM = "ram"; + public static final String FIELD_BRIDGE_TEMPERATURE = "temp"; + public static final String FIELD_BRIDGE_TYPE = "type"; + public static final String FIELD_BRIDGE_UID = "uid"; + public static final String FIELD_BRIDGE_UPTIME = "uptime"; + public static final String FIELD_BRIDGE_WIFI_SIGNAL = "wifi_signal"; + + // Settings fields + public static final String FIELD_BRIDGE_SET_APP_TIME = "ATIME"; + public static final String FIELD_BRIDGE_SET_AUTO_BRIGHTNESS = "ABRI"; + public static final String FIELD_BRIDGE_SET_AUTO_TRANSITION = "ATRANS"; + public static final String FIELD_BRIDGE_SET_BLOCK_KEYS = "BLOCKN"; + public static final String FIELD_BRIDGE_SET_BRIGHTNESS = "BRI"; + public static final String FIELD_BRIDGE_SET_DISPLAY = "MATP"; + public static final String FIELD_BRIDGE_SET_MUTE = "SOUND"; + public static final String FIELD_BRIDGE_SET_SCROLL_SPEED = "SSPEED"; + public static final String FIELD_BRIDGE_SET_TEXT_COLOR = "TCOL"; + public static final String FIELD_BRIDGE_SET_TRANS_EFFECT = "TEFF"; + public static final String FIELD_BRIDGE_SET_TRANS_SPEED = "TSPEED"; + + // Apps + public static final String BASE_APP_TOPIC = "/custom"; + public static final String[] DEFAULT_APPS = { "Time", "Date", "Temperature", "Humidity", "Battery" }; + + // Common Channels + public static final String CHANNEL_BUTLEFT = "buttonleft"; + public static final String CHANNEL_BUTRIGHT = "buttonright"; + public static final String CHANNEL_BUTSELECT = "buttonselect"; + + // Clock Channels + public static final String CHANNEL_APP = "app"; + public static final String CHANNEL_AUTO_BRIGHTNESS = "autoBrightness"; + public static final String CHANNEL_BATTERY = "batterylevel"; + public static final String CHANNEL_BRIGHTNESS = "brightness"; + public static final String CHANNEL_DISPLAY = "display"; + public static final String CHANNEL_HUMIDITY = "humidity"; + public static final String CHANNEL_INDICATOR1 = "indicator1"; + public static final String CHANNEL_INDICATOR2 = "indicator2"; + public static final String CHANNEL_INDICATOR3 = "indicator3"; + public static final String CHANNEL_LOW_BATTERY = "low-battery"; + public static final String CHANNEL_LUX = "lux"; + public static final String CHANNEL_RSSI = "rssi"; + public static final String CHANNEL_RTTTL = "rtttl"; + public static final String CHANNEL_SCREEN = "screen"; + public static final String CHANNEL_SOUND = "sound"; + public static final String CHANNEL_TEMPERATURE = "temperature"; + + // App Channels + public static final String CHANNEL_ACTIVE = "active"; + public static final String CHANNEL_AUTOSCALE = "autoscale"; + public static final String CHANNEL_BACKGROUND = "background"; + public static final String CHANNEL_BAR = "bar"; + public static final String CHANNEL_BLINK_TEXT = "blinkText"; + public static final String CHANNEL_CENTER = "center"; + public static final String CHANNEL_COLOR = "color"; + public static final String CHANNEL_DURATION = "duration"; + public static final String CHANNEL_EFFECT = "effect"; + public static final String CHANNEL_EFFECT_BLEND = "effectBlend"; + public static final String CHANNEL_EFFECT_PALETTE = "effectPalette"; + public static final String CHANNEL_EFFECT_SPEED = "effectSpeed"; + public static final String CHANNEL_FADE_TEXT = "fadeText"; + public static final String CHANNEL_GRADIENT_COLOR = "gradientColor"; + public static final String CHANNEL_ICON = "icon"; + public static final String CHANNEL_LIFETIME = "lifetime"; + public static final String CHANNEL_LIFETIME_MODE = "lifetimeMode"; + public static final String CHANNEL_LINE = "line"; + public static final String CHANNEL_OVERLAY = "overlay"; + public static final String CHANNEL_PROGRESS = "progress"; + public static final String CHANNEL_PROGRESSC = "progressColor"; + public static final String CHANNEL_PROGRESSBC = "progressBackground"; + public static final String CHANNEL_PUSH_ICON = "pushIcon"; + public static final String CHANNEL_RAINBOW = "rainbow"; + public static final String CHANNEL_REPEAT = "repeat"; + public static final String CHANNEL_RESET = "reset"; + public static final String CHANNEL_SCROLLSPEED = "scrollSpeed"; + public static final String CHANNEL_TEXT = "text"; + public static final String CHANNEL_TEXTCASE = "textCase"; + public static final String CHANNEL_TEXT_OFFSET = "textOffset"; + public static final String CHANNEL_TOP_TEXT = "topText"; + + public static final String PUSH_ICON_OPTION_0 = "STATIC"; + public static final String PUSH_ICON_OPTION_1 = "PUSHOUT"; + public static final String PUSH_ICON_OPTION_2 = "PUSHOUTRETURN"; + + // Just some little helpers... + public static final BigDecimal MINUSONE = new BigDecimal(-1); + public static final BigDecimal ONEHUNDRED = new BigDecimal(100); + public static final BigDecimal THOUSAND = new BigDecimal(1000); +} diff --git a/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/AwtrixLightHandlerFactory.java b/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/AwtrixLightHandlerFactory.java new file mode 100644 index 0000000000000..48522b13a7797 --- /dev/null +++ b/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/AwtrixLightHandlerFactory.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2010-2025 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ + +package org.openhab.binding.mqtt.awtrixlight.internal; + +import static org.openhab.binding.mqtt.awtrixlight.internal.AwtrixLightBindingConstants.SUPPORTED_THING_TYPES; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.mqtt.awtrixlight.internal.handler.AwtrixLightAppHandler; +import org.openhab.binding.mqtt.awtrixlight.internal.handler.AwtrixLightBridgeHandler; +import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingRegistry; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.binding.BaseThingHandlerFactory; +import org.openhab.core.thing.binding.ThingHandler; +import org.openhab.core.thing.binding.ThingHandlerFactory; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; + +/** + * The {@link AwtrixLightHandlerFactory} is responsible for creating things and thing + * handlers. + * + * @author Thomas Lauterbach - Initial contribution + */ +@Component(service = ThingHandlerFactory.class) +@NonNullByDefault +public class AwtrixLightHandlerFactory extends BaseThingHandlerFactory { + + @Activate + public AwtrixLightHandlerFactory(final @Reference ThingRegistry thingRegistry) { + } + + @Override + public boolean supportsThingType(ThingTypeUID thingTypeUID) { + return SUPPORTED_THING_TYPES.contains(thingTypeUID); + } + + @Override + protected @Nullable ThingHandler createHandler(Thing thing) { + ThingTypeUID thingTypeUID = thing.getThingTypeUID(); + if (AwtrixLightBridgeHandler.SUPPORTED_THING_TYPES.contains(thingTypeUID)) { + AwtrixLightBridgeHandler bridgeHandler = new AwtrixLightBridgeHandler((Bridge) thing); + return bridgeHandler; + } else if (AwtrixLightAppHandler.SUPPORTED_THING_TYPES.contains(thingTypeUID)) { + AwtrixLightAppHandler appHandler = new AwtrixLightAppHandler((Thing) thing); + return appHandler; + } + + return null; + } +} diff --git a/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/BridgeConfigOptions.java b/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/BridgeConfigOptions.java new file mode 100644 index 0000000000000..f3d7b2f53e516 --- /dev/null +++ b/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/BridgeConfigOptions.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2010-2025 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.mqtt.awtrixlight.internal; + +import java.math.BigDecimal; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link BridgeConfigOptions} Holds the config for the bridge settings. + * + * @author Thomas Lauterbach - Initial contribution + */ +@NonNullByDefault +public class BridgeConfigOptions { + public String basetopic = "awtrix"; + public int appLockTimeout = 10; + public boolean discoverDefaultApps = false; + public BigDecimal lowBatteryThreshold = new BigDecimal(25); +} diff --git a/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/Helper.java b/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/Helper.java new file mode 100644 index 0000000000000..4606eb17ad6c8 --- /dev/null +++ b/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/Helper.java @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2010-2025 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.mqtt.awtrixlight.internal; + +import static org.openhab.binding.mqtt.awtrixlight.internal.AwtrixLightBindingConstants.SCREEN_HEIGHT; +import static org.openhab.binding.mqtt.awtrixlight.internal.AwtrixLightBindingConstants.SCREEN_WIDTH; + +import java.awt.image.BufferedImage; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.math.BigDecimal; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +import javax.imageio.ImageIO; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.mqtt.awtrixlight.internal.app.AwtrixApp; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.ToNumberPolicy; +import com.google.gson.reflect.TypeToken; + +/** + * The {@link Helper} Various helper methods used througout the binding + * + * @author Thomas Lauterbach - Initial contribution + */ +@NonNullByDefault +public class Helper { + + private static final Logger LOGGER = LoggerFactory.getLogger(Helper.class); + + private static final Gson GSON = new GsonBuilder().setObjectToNumberStrategy(ToNumberPolicy.BIG_DECIMAL).create(); + + public static Map decodeStatsJson(String statsJson) { + Map stats = GSON.fromJson(statsJson, new TypeToken>() { + }.getType()); + return stats != null ? stats : new HashMap(); + } + + public static AwtrixApp decodeAppJson(String appJson) { + @Nullable + AwtrixApp app = GSON.fromJson(appJson, AwtrixApp.class); + return app != null ? app : new AwtrixApp(); + } + + public static String encodeJson(Map params) { + return GSON.toJson(params); + } + + public static byte[] decodeImage(String messageJSON) { + String cutBrackets = messageJSON.substring(1, messageJSON.length() - 1); + + String[] pixelStrings = cutBrackets.split(","); + int[] values = Arrays.stream(pixelStrings).mapToInt(Integer::parseInt).toArray(); + int[][] pixels = new int[SCREEN_HEIGHT][SCREEN_WIDTH]; + for (int i = 0; i < 256; i++) { + pixels[i / 32][i % 32] = values[i]; + } + + // Resize and add gaps between pixels + int factor = 10; + int resizedHeight = SCREEN_HEIGHT * factor + (SCREEN_HEIGHT - 1); + int resizedWidth = SCREEN_WIDTH * factor + (SCREEN_WIDTH - 1); + int[][] resizedPixels = new int[resizedHeight][resizedWidth]; + for (int y = 0; y < SCREEN_HEIGHT; y++) { + for (int x = 0; x < SCREEN_WIDTH; x++) { + int yOffset = y * factor + y; + int xOffset = x * factor + x; + for (int y2 = 0; y2 < factor; y2++) { + for (int x2 = 0; x2 < factor; x2++) { + resizedPixels[yOffset + y2][xOffset + x2] = pixels[y][x]; + } + } + } + } + + BufferedImage image = new BufferedImage(resizedWidth, resizedHeight, BufferedImage.TYPE_INT_RGB); + for (int y = 0; y < resizedHeight; y++) { + for (int x = 0; x < resizedWidth; x++) { + int rgb = resizedPixels[y][x]; + image.setRGB(x, y, rgb); + } + } + + byte[] bytes = new byte[256]; + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ImageIO.write(image, "png", baos); + bytes = baos.toByteArray(); + } catch (IOException e) { + LOGGER.error("Failed to decode image", e); + } + return bytes == null ? new byte[0] : bytes; + } + + public static BigDecimal[] leftTrim(BigDecimal[] data, int length) { + if (length < data.length) { + BigDecimal[] trimmed = new BigDecimal[length]; + for (int i = data.length - length; i < data.length; i++) { + trimmed[i - (data.length - length)] = data[i]; + } + return trimmed; + } + return data; + } +} diff --git a/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/action/AwtrixActions.java b/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/action/AwtrixActions.java new file mode 100644 index 0000000000000..0c9d4c91f7f8d --- /dev/null +++ b/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/action/AwtrixActions.java @@ -0,0 +1,239 @@ +/* + * Copyright (c) 2010-2025 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ + +package org.openhab.binding.mqtt.awtrixlight.internal.action; + +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.mqtt.awtrixlight.internal.handler.AwtrixLightBridgeHandler; +import org.openhab.core.automation.annotation.RuleAction; +import org.openhab.core.thing.binding.ThingActions; +import org.openhab.core.thing.binding.ThingActionsScope; +import org.openhab.core.thing.binding.ThingHandler; + +/** + * Actions for the Awtrix clock. + * + * @author Thomas Lauterbach - Initial contribution + */ +@ThingActionsScope(name = "mqtt.awtrixlight") +@NonNullByDefault +public class AwtrixActions implements ThingActions { + + private @Nullable AwtrixLightBridgeHandler handler; + + @Override + public void setThingHandler(ThingHandler handler) { + this.handler = (AwtrixLightBridgeHandler) handler; + } + + @Override + public @Nullable ThingHandler getThingHandler() { + return handler; + } + + @RuleAction(label = "Blink Indicator", description = "Blink indicator with indicatorId") + public void blinkIndicator(int indicatorId, int[] rgb, int blinkTimeInMs) { + AwtrixLightBridgeHandler localHandler = this.handler; + if (localHandler != null) { + localHandler.blinkIndicator(indicatorId, rgb, blinkTimeInMs); + } + } + + public static void blinkIndicator(@Nullable ThingActions actions, int indicatorId, int[] rgb, int blinkTimeInMs) { + if (actions instanceof AwtrixActions) { + ((AwtrixActions) actions).blinkIndicator(indicatorId, rgb, blinkTimeInMs); + } else { + throw new IllegalArgumentException("Instance is not an AwtrixActions class."); + } + } + + @RuleAction(label = "Fade Indicator", description = "Fade indicator with indicatorId") + public void fadeIndicator(int indicatorId, int[] rgb, int fadeTimeInMs) { + AwtrixLightBridgeHandler localHandler = this.handler; + if (localHandler != null) { + localHandler.fadeIndicator(indicatorId, rgb, fadeTimeInMs); + } + } + + public static void fadeIndicator(@Nullable ThingActions actions, int indicatorId, int[] rgb, int fadeTimeInMs) { + if (actions instanceof AwtrixActions) { + ((AwtrixActions) actions).fadeIndicator(indicatorId, rgb, fadeTimeInMs); + } else { + throw new IllegalArgumentException("Instance is not an AwtrixActions class."); + } + } + + @RuleAction(label = "Activate Indicator", description = "Turn on indicator with indicatorId") + public void activateIndicator(int indicatorId, int[] rgb) { + AwtrixLightBridgeHandler localHandler = this.handler; + if (localHandler != null) { + localHandler.activateIndicator(indicatorId, rgb); + } + } + + public static void activateIndicator(@Nullable ThingActions actions, int indicatorId, int[] rgb) { + if (actions instanceof AwtrixActions) { + ((AwtrixActions) actions).activateIndicator(indicatorId, rgb); + } else { + throw new IllegalArgumentException("Instance is not an AwtrixActions class."); + } + } + + @RuleAction(label = "Deactivate Indicator", description = "Turn off indicator with indicatorId") + public void deactivateIndicator(int indicatorId) { + AwtrixLightBridgeHandler localHandler = this.handler; + if (localHandler != null) { + localHandler.deactivateIndicator(indicatorId); + } + } + + public static void deactivateIndicator(@Nullable ThingActions actions, int indicatorId) { + if (actions instanceof AwtrixActions) { + ((AwtrixActions) actions).deactivateIndicator(indicatorId); + } else { + throw new IllegalArgumentException("Instance is not an AwtrixActions class."); + } + } + + @RuleAction(label = "Reboot", description = "Reboots the device") + public void reboot() { + AwtrixLightBridgeHandler localHandler = this.handler; + if (localHandler != null) { + localHandler.reboot(); + } + } + + public static void reboot(@Nullable ThingActions actions) { + if (actions instanceof AwtrixActions) { + ((AwtrixActions) actions).reboot(); + } else { + throw new IllegalArgumentException("Instance is not an AwtrixActions class."); + } + } + + @RuleAction(label = "Sleep", description = "Send device to deep sleep") + public void sleep(int seconds) { + AwtrixLightBridgeHandler localHandler = this.handler; + if (localHandler != null) { + localHandler.sleep(seconds); + } + } + + public static void sleep(@Nullable ThingActions actions, int seconds) { + if (actions instanceof AwtrixActions) { + ((AwtrixActions) actions).sleep(seconds); + } else { + throw new IllegalArgumentException("Instance is not an AwtrixActions class."); + } + } + + @RuleAction(label = "Upgrade", description = "Performs firmware upgrade") + public void upgrade() { + AwtrixLightBridgeHandler localHandler = this.handler; + if (localHandler != null) { + localHandler.upgrade(); + } + } + + public static void upgrade(@Nullable ThingActions actions) { + if (actions instanceof AwtrixActions) { + ((AwtrixActions) actions).upgrade(); + } else { + throw new IllegalArgumentException("Instance is not an AwtrixActions class."); + } + } + + @RuleAction(label = "Play Sound", description = "Plays the sound file with given name (without extension) if it exists") + public void playSound(String melody) { + AwtrixLightBridgeHandler localHandler = this.handler; + if (localHandler != null) { + localHandler.playSound(melody); + } + } + + public static void playSound(@Nullable ThingActions actions, @Nullable String melody) { + if (actions instanceof AwtrixActions) { + if (melody != null) { + ((AwtrixActions) actions).playSound(melody); + } + } else { + throw new IllegalArgumentException("Instance is not an AwtrixActions class."); + } + } + + @RuleAction(label = "Play RTTTL", description = "Plays the melody provided in RTTTL format") + public void playRtttl(String rtttl) { + AwtrixLightBridgeHandler localHandler = this.handler; + if (localHandler != null) { + localHandler.playRtttl(rtttl); + } + } + + public static void playRtttl(@Nullable ThingActions actions, @Nullable String rtttl) { + if (actions instanceof AwtrixActions) { + if (rtttl != null) { + ((AwtrixActions) actions).playRtttl(rtttl); + } + } else { + throw new IllegalArgumentException("Instance is not an AwtrixActions class."); + } + } + + @RuleAction(label = "Show Notification", description = "Shows a default notification with an icon") + public void showNotification(String message, String icon) { + AwtrixLightBridgeHandler localHandler = this.handler; + if (localHandler != null) { + Map params = new HashMap(); + params.put("text", message); + params.put("icon", icon); + localHandler.showNotification(false, false, true, "", "", false, params); + } + } + + public static void showNotification(@Nullable ThingActions actions, @Nullable String message, + @Nullable String icon) { + if (actions instanceof AwtrixActions) { + if (message != null && icon != null) { + ((AwtrixActions) actions).showNotification(message, icon); + } + } else { + throw new IllegalArgumentException("Instance is not an AwtrixActions class."); + } + } + + @RuleAction(label = "Show Custom Notification", description = "Shows a notification with specified options") + public void showCustomNotification(Map appParams, boolean hold, boolean wakeUp, boolean stack, + @Nullable String rtttl, @Nullable String sound, boolean loopSound) { + AwtrixLightBridgeHandler localHandler = this.handler; + if (localHandler != null) { + localHandler.showNotification(hold, wakeUp, stack, rtttl, sound, loopSound, appParams); + } + } + + public static void showCustomNotification(@Nullable ThingActions actions, @Nullable Map appParams, + boolean hold, boolean wakeUp, boolean stack, @Nullable String rtttl, @Nullable String sound, + boolean loopSound) { + if (actions instanceof AwtrixActions) { + if (appParams != null) { + ((AwtrixActions) actions).showCustomNotification(appParams, hold, wakeUp, stack, rtttl, sound, + loopSound); + } + } else { + throw new IllegalArgumentException("Instance is not an AwtrixActions class."); + } + } +} diff --git a/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/app/AwtrixApp.java b/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/app/AwtrixApp.java new file mode 100644 index 0000000000000..22018e3879007 --- /dev/null +++ b/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/app/AwtrixApp.java @@ -0,0 +1,625 @@ +/* + * Copyright (c) 2010-2025 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.mqtt.awtrixlight.internal.app; + +import static org.openhab.binding.mqtt.awtrixlight.internal.AwtrixLightBindingConstants.*; + +import java.math.BigDecimal; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.mqtt.awtrixlight.internal.Helper; + +/** + * The {@link AwtrixApp} is the representation of the current app configuration and provides a method to create a config + * string for the clock. + * + * @author Thomas Lauterbach - Initial contribution + */ +@NonNullByDefault +public class AwtrixApp { + + public static final String DEFAULT_TEXT = "New Awtrix App"; + public static final BigDecimal DEFAULT_TEXTCASE = BigDecimal.ZERO; + public static final boolean DEFAULT_TOPTEXT = false; + public static final BigDecimal DEFAULT_TEXTOFFSET = BigDecimal.ZERO; + public static final boolean DEFAULT_CENTER = true; + public static final BigDecimal[] DEFAULT_COLOR = {}; + public static final BigDecimal[] DEFAULT_GRADIENT = {}; + public static final BigDecimal DEFAULT_BLINKTEXT = BigDecimal.ZERO; + public static final BigDecimal DEFAULT_FADETEXT = BigDecimal.ZERO; + public static final BigDecimal[] DEFAULT_BACKGROUND = {}; + public static final boolean DEFAULT_RAINBOW = false; + public static final String DEFAULT_ICON = "None"; + public static final BigDecimal DEFAULT_PUSHICON = BigDecimal.ZERO; + public static final BigDecimal DEFAULT_DURATION = new BigDecimal(7); + public static final BigDecimal[] DEFAULT_LINE = {}; + public static final BigDecimal DEFAULT_LIFETIME = BigDecimal.ZERO; + public static final BigDecimal DEFAULT_LIFETIME_MODE = BigDecimal.ZERO; + public static final BigDecimal[] DEFAULT_BAR = {}; + public static final boolean DEFAULT_AUTOSCALE = true; + public static final String DEFAULT_OVERLAY = "Clear"; + public static final BigDecimal DEFAULT_PROGRESS = MINUSONE; + public static final BigDecimal[] DEFAULT_PROGRESSC = {}; + public static final BigDecimal[] DEFAULT_PROGRESSBC = {}; + public static final BigDecimal DEFAULT_SCROLLSPEED = ONEHUNDRED; + public static final String DEFAULT_EFFECT = "None"; + public static final BigDecimal DEFAULT_EFFECTSPEED = BigDecimal.ONE; + public static final String DEFAULT_EFFECTPALETTE = "None"; + public static final boolean DEFAULT_EFFECTBLEND = true; + + private String text = DEFAULT_TEXT; + private BigDecimal textCase = DEFAULT_TEXTCASE; + private boolean topText = DEFAULT_TOPTEXT; + private BigDecimal textOffset = DEFAULT_TEXTOFFSET; + private boolean center = DEFAULT_CENTER; + private BigDecimal[] color = DEFAULT_COLOR; + private BigDecimal[] gradient = DEFAULT_GRADIENT; + private BigDecimal blinkText = DEFAULT_BLINKTEXT; + private BigDecimal fadeText = DEFAULT_FADETEXT; + private BigDecimal[] background = DEFAULT_BACKGROUND; + private boolean rainbow = DEFAULT_RAINBOW; + private String icon = DEFAULT_ICON; + private BigDecimal pushIcon = DEFAULT_PUSHICON; + private BigDecimal duration = DEFAULT_DURATION; + private BigDecimal[] line = DEFAULT_LINE; + private BigDecimal lifetime = DEFAULT_LIFETIME; + private BigDecimal lifetimeMode = DEFAULT_LIFETIME_MODE; + private BigDecimal[] bar = DEFAULT_BAR; + private boolean autoscale = DEFAULT_AUTOSCALE; + private String overlay = DEFAULT_OVERLAY; + private BigDecimal progress = DEFAULT_PROGRESS; + private BigDecimal[] progressC = DEFAULT_PROGRESSC; + private BigDecimal[] progressBC = DEFAULT_PROGRESSBC; + private BigDecimal scrollSpeed = DEFAULT_SCROLLSPEED; + private String effect = DEFAULT_EFFECT; + + // effectSettings properties + private Map effectSettings; + + public AwtrixApp() { + this.effectSettings = new HashMap(); + this.effectSettings.put("speed", DEFAULT_EFFECTSPEED); + this.effectSettings.put("palette", DEFAULT_EFFECTPALETTE); + this.effectSettings.put("blend", DEFAULT_EFFECTBLEND); + } + + public void updateFields(Map params) { + this.text = getStringValue(params, "text", DEFAULT_TEXT); + this.textCase = getNumberValue(params, "textCase", DEFAULT_TEXTCASE); + this.topText = getBoolValue(params, "topText", DEFAULT_TOPTEXT); + this.textOffset = getNumberValue(params, "textOffset", DEFAULT_TEXTOFFSET); + this.center = getBoolValue(params, "center", DEFAULT_CENTER); + this.color = getNumberArrayValue(params, "color", DEFAULT_COLOR); + this.gradient = getGradientValue(params, DEFAULT_GRADIENT); + this.blinkText = getNumberValue(params, "blinkText", DEFAULT_BLINKTEXT); + this.fadeText = getNumberValue(params, "fadeText", DEFAULT_FADETEXT); + this.background = getNumberArrayValue(params, "background", DEFAULT_BACKGROUND); + this.rainbow = getBoolValue(params, "rainbow", DEFAULT_RAINBOW); + this.icon = getStringValue(params, "icon", DEFAULT_ICON); + this.pushIcon = getNumberValue(params, "pushIcon", DEFAULT_PUSHICON); + this.duration = getNumberValue(params, "duration", DEFAULT_DURATION); + this.line = getNumberArrayValue(params, "line", DEFAULT_LINE); + this.lifetime = getNumberValue(params, "lifetime", DEFAULT_LIFETIME); + this.lifetimeMode = getNumberValue(params, "lifetimeMode", DEFAULT_LIFETIME_MODE); + this.bar = getNumberArrayValue(params, "bar", DEFAULT_BAR); + this.autoscale = getBoolValue(params, "autoscale", DEFAULT_AUTOSCALE); + this.overlay = getStringValue(params, "overlay", DEFAULT_OVERLAY); + this.progress = getNumberValue(params, "progress", DEFAULT_PROGRESS); + this.progressC = getNumberArrayValue(params, "progressC", DEFAULT_PROGRESSC); + this.progressBC = getNumberArrayValue(params, "progressBC", DEFAULT_PROGRESSBC); + this.scrollSpeed = getNumberValue(params, "scrollSpeed", DEFAULT_SCROLLSPEED); + this.effect = getStringValue(params, "effect", DEFAULT_EFFECT); + + Map defaultEffectSettings = new HashMap(); + defaultEffectSettings.put("speed", DEFAULT_EFFECTSPEED); + defaultEffectSettings.put("palette", DEFAULT_EFFECTPALETTE); + defaultEffectSettings.put("blend", DEFAULT_EFFECTBLEND); + this.effectSettings = getEffectSettingsValues(params, defaultEffectSettings); + } + + public String getAppConfig() { + Map fields = getAppParams(); + return Helper.encodeJson(fields); + } + + public String getText() { + return this.text; + } + + public void setText(String text) { + this.text = text; + } + + public BigDecimal getTextCase() { + return this.textCase; + } + + public void setTextCase(BigDecimal textCase) { + this.textCase = textCase; + } + + public Boolean getTopText() { + return this.topText; + } + + public void setTopText(Boolean topText) { + this.topText = topText; + } + + public BigDecimal getTextOffset() { + return this.textOffset; + } + + public void setTextOffset(BigDecimal textOffset) { + this.textOffset = textOffset; + } + + public Boolean getCenter() { + return this.center; + } + + public void setCenter(Boolean center) { + this.center = center; + } + + public BigDecimal[] getColor() { + return this.color; + } + + public void setColor(BigDecimal[] color) { + this.color = color; + } + + public BigDecimal[] getGradient() { + return this.gradient; + } + + public void setGradient(BigDecimal[] gradient) { + this.gradient = gradient; + } + + public BigDecimal getBlinkText() { + return this.blinkText; + } + + public void setBlinkText(BigDecimal blinkText) { + this.blinkText = blinkText; + } + + public BigDecimal getFadeText() { + return this.fadeText; + } + + public void setFadeText(BigDecimal fadeText) { + this.fadeText = fadeText; + } + + public BigDecimal[] getBackground() { + return this.background; + } + + public void setBackground(BigDecimal[] background) { + this.background = background; + } + + public Boolean getRainbow() { + return this.rainbow; + } + + public void setRainbow(Boolean rainbow) { + this.rainbow = rainbow; + } + + public String getIcon() { + return this.icon; + } + + public void setIcon(String icon) { + this.icon = icon; + } + + public BigDecimal getPushIcon() { + return this.pushIcon; + } + + public void setPushIcon(BigDecimal pushIcon) { + this.pushIcon = pushIcon; + } + + public BigDecimal getDuration() { + return this.duration; + } + + public void setDuration(BigDecimal duration) { + this.duration = duration; + } + + public BigDecimal[] getLine() { + return this.line; + } + + public void setLine(BigDecimal[] line) { + this.line = line; + } + + public BigDecimal getLifetime() { + return this.lifetime; + } + + public void setLifetime(BigDecimal lifetime) { + this.lifetime = lifetime; + } + + public BigDecimal getLifetimeMode() { + return this.lifetimeMode; + } + + public void setLifetimeMode(BigDecimal lifetimeMode) { + this.lifetimeMode = lifetimeMode; + } + + public BigDecimal[] getBar() { + return this.bar; + } + + public void setBar(BigDecimal[] bar) { + this.bar = bar; + } + + public Boolean getAutoscale() { + return this.autoscale; + } + + public void setAutoscale(Boolean autoscale) { + this.autoscale = autoscale; + } + + public String getOverlay() { + return this.overlay; + } + + public void setOverlay(String overlay) { + this.overlay = overlay; + } + + public BigDecimal getProgress() { + return this.progress; + } + + public void setProgress(BigDecimal progress) { + this.progress = progress; + } + + public BigDecimal[] getProgressC() { + return this.progressC; + } + + public BigDecimal[] setProgressC() { + return this.progressC; + } + + public void setProgressC(BigDecimal[] progressC) { + this.progressC = progressC; + } + + public BigDecimal[] getProgressBC() { + return this.progressBC; + } + + public void setProgressBC(BigDecimal[] progressBC) { + this.progressBC = progressBC; + } + + public BigDecimal getScrollSpeed() { + return this.scrollSpeed; + } + + public void setScrollSpeed(BigDecimal scrollSpeed) { + this.scrollSpeed = scrollSpeed; + } + + public String getEffect() { + return this.effect; + } + + public void setEffect(String effect) { + this.effect = effect; + } + + public Map getEffectSettings() { + return this.effectSettings; + } + + public void setEffectSettings(Map effectSettings) { + this.effectSettings = effectSettings; + } + + protected String propertiesAsString() { + return "text=" + text + ", textCase=" + textCase + ", topText=" + topText + ", textOffset=" + textOffset + + ", center=" + center + ", color=" + Arrays.toString(color) + ", gradient=" + Arrays.toString(gradient) + + ", blinkText=" + blinkText + ", fadeText=" + fadeText + ", background=" + Arrays.toString(background) + + ", rainbow=" + rainbow + ", icon=" + icon + ", pushIcon=" + pushIcon + ", duration=" + duration + + ", line=" + Arrays.toString(line) + ", lifetime=" + lifetime + ", lifetimeMode=" + lifetimeMode + + ", bar=" + Arrays.toString(bar) + ", autoscale=" + autoscale + ", overlay=" + overlay + ", progress=" + + progress + ", progressC=" + Arrays.toString(progressC) + ", progressBC=" + Arrays.toString(progressBC) + + ", scrollSpeed=" + scrollSpeed + ", effect=" + effect + ", effectSpeed=" + getEffectSpeed() + + ", effectPalette=" + effectSettings.get("palette") + ", effectBlend=" + effectSettings.get("blend"); + } + + @Override + public String toString() { + return "AwtrixApp [" + propertiesAsString() + "]"; + } + + public Map getAppParams() { + Map fields = new HashMap(); + fields.put("text", this.text); + fields.put("textCase", this.textCase); + fields.put("topText", this.topText); + fields.put("textOffset", this.textOffset); + fields.put("center", this.center); + fields.put("lifetime", this.lifetime); + fields.put("lifetimeMode", this.lifetimeMode); + fields.put("overlay", this.overlay); + fields.putAll(getColorConfig()); + fields.putAll(getTextEffectConfig()); + fields.putAll(getBackgroundConfig()); + fields.putAll(getIconConfig()); + fields.put("duration", this.duration); + fields.putAll(getGraphConfig()); + fields.putAll(getProgressConfig()); + if (this.scrollSpeed.compareTo(BigDecimal.ZERO) == 0) { + fields.put("noScroll", true); + } else { + fields.put("scrollSpeed", this.scrollSpeed); + } + fields.putAll(getEffectConfig()); + return fields; + } + + private boolean getBoolValue(Map params, String key, boolean defaultValue) { + if (params.containsKey(key)) { + @Nullable + Object value = params.get(key); + if (value instanceof Boolean) { + return (Boolean) value; + } + } + return defaultValue; + } + + private BigDecimal getNumberValue(Map params, String key, BigDecimal defaultValue) { + if (params.containsKey(key)) { + @Nullable + Object value = params.get(key); + if (value instanceof BigDecimal) { + return (BigDecimal) value; + } + } + return defaultValue; + } + + private BigDecimal[] getNumberArrayValue(Map params, String key, BigDecimal[] defaultValue) { + if (params.containsKey(key)) { + @Nullable + Object value = params.get(key); + if (value instanceof BigDecimal[]) { + return (BigDecimal[]) value; + } + } + return defaultValue; + } + + private BigDecimal[] getGradientValue(Map params, BigDecimal[] defaultValue) { + if (params.containsKey("gradient")) { + @Nullable + Object value = params.get("gradient"); + if (value instanceof BigDecimal[][] && ((BigDecimal[][]) value).length == 2) { + BigDecimal[] gradientColor = ((BigDecimal[][]) value)[1]; + if (gradientColor.length == 3) { + return gradientColor; + } + } + } + return defaultValue; + } + + private Map getEffectSettingsValues(Map params, Map defaultValues) { + if (params.containsKey("effectSettings")) { + @Nullable + Object value = params.get("effectSettings"); + if (value instanceof Map) { + Map map = (Map) value; + if (map.containsKey("speed")) { + Object speed = map.get("speed"); + if (speed != null) { + defaultValues.put("speed", speed); + } + } + if (map.containsKey("palette")) { + Object palette = map.get("palette"); + if (palette != null) { + defaultValues.put("palette", palette); + } + } + if (map.containsKey("blend")) { + Object blend = map.get("blend"); + if (blend != null) { + defaultValues.put("blend", blend); + } + } + } + } + return defaultValues; + } + + private String getStringValue(Map params, String key, String defaultValue) { + if (params.containsKey(key)) { + @Nullable + Object value = params.get(key); + if (value instanceof String) { + return (String) value; + } + } + return defaultValue; + } + + private Map getColorConfig() { + Map fields = new HashMap(); + if (this.gradient.length != 3) { + if (this.color.length == 3) { + fields.put("color", this.color); + } + } else { + if (this.color.length == 3) { + BigDecimal[][] gradientColors = { this.color, this.gradient }; + fields.put("gradient", gradientColors); + } else { + fields.put("color", this.gradient); + } + } + return fields; + } + + private Map getTextEffectConfig() { + Map fields = new HashMap(); + if (this.color.length == 0 || this.gradient.length == 0) { + if (this.blinkText.compareTo(BigDecimal.ZERO) > 0) { + fields.put("blinkText", this.blinkText); + } else if (this.fadeText.compareTo(BigDecimal.ZERO) > 0) { + fields.put("fadeText", this.fadeText); + } else if (this.rainbow) { + fields.put("rainbow", this.rainbow); + } + } + return fields; + } + + private Map getBackgroundConfig() { + Map fields = new HashMap(); + if (this.background.length == 3) { + fields.put("background", this.background); + } + return fields; + } + + private Map getIconConfig() { + Map fields = new HashMap(); + if (!"None".equals(this.icon)) { + fields.put("icon", this.icon); + fields.put("pushIcon", this.pushIcon); + } + return fields; + } + + private Map getGraphConfig() { + Map fields = new HashMap(); + String graphType = null; + BigDecimal[] data = null; + if (this.bar.length > 0) { + graphType = "bar"; + if ("None".equals(this.icon)) { + data = Helper.leftTrim(this.bar, 16); + } else { + data = Helper.leftTrim(this.bar, 11); + } + } else if (this.line.length > 0) { + graphType = "line"; + if ("None".equals(this.icon)) { + data = Helper.leftTrim(this.line, 16); + } else { + data = Helper.leftTrim(this.line, 11); + } + } + if (graphType != null && data != null) { + fields.put(graphType, data); + fields.put("autoscale", this.autoscale); + } + return fields; + } + + private Map getProgressConfig() { + Map fields = new HashMap(); + if (progress.compareTo(MINUSONE) > 0 && progress.compareTo(ONEHUNDRED) <= 0) { + fields.put("progress", this.progress); + if (this.progressC.length == 3) { + fields.put("progressC", this.progressC); + } + if (this.progressBC.length == 3) { + fields.put("progressBC", this.progressBC); + } + } + return fields; + } + + private Map getEffectConfig() { + Map fields = new HashMap(); + Map effectSettings = new HashMap(); + fields.put("effect", this.effect); + if (!"None".equals(this.effect)) { + if (getEffectSpeed().compareTo(MINUSONE) > 0) { + effectSettings.put("speed", getEffectSpeed()); + } + effectSettings.put("palette", getEffectPalette()); + effectSettings.put("blend", getEffectBlend()); + fields.put("effectSettings", effectSettings); + } + return fields; + } + + public BigDecimal getEffectSpeed() { + @Nullable + Object effectSpeed = this.effectSettings.get("speed"); + if (effectSpeed instanceof BigDecimal) { + return (BigDecimal) effectSpeed; + } else { + return DEFAULT_EFFECTSPEED; + } + } + + public void setEffectSpeed(BigDecimal effectSpeed) { + this.effectSettings.put("speed", effectSpeed); + } + + public String getEffectPalette() { + @Nullable + Object effectPalette = this.effectSettings.get("palette"); + if (effectPalette instanceof String) { + return (String) effectPalette; + } else { + return DEFAULT_EFFECTPALETTE; + } + } + + public void setEffectPalette(String effectPalette) { + this.effectSettings.put("palette", effectPalette); + } + + public Boolean getEffectBlend() { + @Nullable + Object effectBlend = this.effectSettings.get("blend"); + if (effectBlend instanceof Boolean) { + return (Boolean) effectBlend; + } else { + return DEFAULT_EFFECTBLEND; + } + } + + public void setEffectBlend(Boolean effectBlend) { + this.effectSettings.put("blend", effectBlend); + } +} diff --git a/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/app/AwtrixNotification.java b/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/app/AwtrixNotification.java new file mode 100644 index 0000000000000..1fb9aaf1f229d --- /dev/null +++ b/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/app/AwtrixNotification.java @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2010-2025 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.mqtt.awtrixlight.internal.app; + +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.mqtt.awtrixlight.internal.Helper; + +/** + * The {@link AwtrixNotification} is the representation of a notification configuration and provides a method to create + * a config + * string for the clock. + * + * @author Thomas Lauterbach - Initial contribution + */ +@NonNullByDefault +public class AwtrixNotification extends AwtrixApp { + + public static final boolean DEFAULT_HOLD = false; + public static final boolean DEFAULT_WAKEUP = false; + public static final boolean DEFAULT_STACK = true; + public static final String DEFAULT_RTTTL = ""; + public static final String DEFAULT_SOUND = ""; + public static final boolean DEFAULT_SOUND_LOOP = false; + + private boolean hold = DEFAULT_HOLD; + private boolean wakeUp = DEFAULT_WAKEUP; + private boolean stack = DEFAULT_STACK; + private String rtttl = DEFAULT_RTTTL; + private String sound = DEFAULT_SOUND; + private boolean loopSound = DEFAULT_SOUND_LOOP; + + private Map getNotificationParams() { + Map fields = new HashMap(); + fields.put("hold", this.hold); + fields.put("wakeUp", this.wakeUp); + fields.put("stack", this.stack); + fields.put("rtttl", this.rtttl); + fields.put("sound", this.sound); + fields.put("loopSound", this.loopSound); + return fields; + } + + @Override + public String getAppConfig() { + Map fields = getAppParams(); + return Helper.encodeJson(fields); + } + + @Override + public Map getAppParams() { + Map params = super.getAppParams(); + params.putAll(getNotificationParams()); + return params; + } + + public boolean isHold() { + return hold; + } + + public void setHold(boolean hold) { + this.hold = hold; + } + + public boolean isWakeUp() { + return wakeUp; + } + + public void setWakeUp(boolean wakeUp) { + this.wakeUp = wakeUp; + } + + public boolean isStack() { + return stack; + } + + public void setStack(boolean stack) { + this.stack = stack; + } + + public String getRtttl() { + return rtttl; + } + + public void setRtttl(String rtttl) { + this.rtttl = rtttl; + } + + public String getSound() { + return sound; + } + + public void setSound(String sound) { + this.sound = sound; + } + + public boolean isLoopSound() { + return loopSound; + } + + public void setLoopSound(boolean loopSound) { + this.loopSound = loopSound; + } + + @Override + protected String propertiesAsString() { + return super.propertiesAsString() + ", wakeUp=" + wakeUp + ", stack=" + stack + ", rtttl=" + rtttl + ", sound=" + + sound + ", loopSound=" + loopSound; + } + + @Override + public String toString() { + return "AwtrixNotification [" + propertiesAsString() + "]"; + } +} diff --git a/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/discovery/AwtrixLightBridgeDiscoveryService.java b/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/discovery/AwtrixLightBridgeDiscoveryService.java new file mode 100644 index 0000000000000..494bcfbad0d60 --- /dev/null +++ b/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/discovery/AwtrixLightBridgeDiscoveryService.java @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2010-2025 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ + +package org.openhab.binding.mqtt.awtrixlight.internal.discovery; + +import static org.openhab.binding.mqtt.MqttBindingConstants.BINDING_ID; +import static org.openhab.binding.mqtt.awtrixlight.internal.AwtrixLightBindingConstants.*; + +import java.util.Map; +import java.util.Set; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.mqtt.awtrixlight.internal.handler.AwtrixLightBridgeHandler; +import org.openhab.core.config.discovery.AbstractDiscoveryService; +import org.openhab.core.config.discovery.DiscoveryResultBuilder; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.ThingUID; +import org.openhab.core.thing.binding.ThingHandler; +import org.openhab.core.thing.binding.ThingHandlerService; + +/** + * The {@link AwtrixLightBridgeDiscoveryService} is responsible for finding awtrix + * apps and setting them up for the handlers. + * + * @author Thomas Lauterbach - Initial contribution + */ +@NonNullByDefault +public class AwtrixLightBridgeDiscoveryService extends AbstractDiscoveryService implements ThingHandlerService { + + @Nullable + AwtrixLightBridgeHandler bridgeHandler = null; + + public AwtrixLightBridgeDiscoveryService() { + super(Set.of(THING_TYPE_APP), 3, true); + } + + public void appDiscovered(String baseTopic, String appName) { + AwtrixLightBridgeHandler localHandler = this.bridgeHandler; + if (localHandler != null) { + Map bridgeProperties = localHandler.getThing().getProperties(); + @Nullable + String bridgeHardwareId = bridgeProperties.get(PROP_UNIQUEID); + if (bridgeHardwareId != null) { + publishApp(localHandler.getThing().getUID(), bridgeHardwareId, baseTopic, appName); + } + } + } + + @Override + public void setThingHandler(ThingHandler handler) { + if (handler instanceof AwtrixLightBridgeHandler) { + this.bridgeHandler = (AwtrixLightBridgeHandler) handler; + ((AwtrixLightBridgeHandler) handler).setAppDiscoveryCallback(this); + } + } + + @Override + public @Nullable ThingHandler getThingHandler() { + return this.bridgeHandler; + } + + @Override + public void deactivate() { + AwtrixLightBridgeHandler localHandler = this.bridgeHandler; + if (localHandler != null) { + localHandler.removeAppDiscoveryCallback(); + } + super.deactivate(); + } + + @Override + protected void startScan() { + // Do nothing. We get results pushed in from the bridge as they come + } + + void publishApp(ThingUID connectionBridgeUid, String bridgeHardwareId, String basetopic, String appName) { + if (!"Notification".equals(appName)) { + String appId = bridgeHardwareId + "-" + appName; + thingDiscovered(DiscoveryResultBuilder + .create(new ThingUID(new ThingTypeUID(BINDING_ID, AWTRIX_APP), connectionBridgeUid, appName)) + .withBridge(connectionBridgeUid).withProperty(PROP_APPID, appId) + .withProperty(PROP_APP_CONTROLLABLE, false).withProperty(PROP_APPNAME, appName) + .withRepresentationProperty(PROP_APPID).withLabel("Awtrix App " + appName).build()); + } + } +} diff --git a/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/discovery/AwtrixLightDiscoveryService.java b/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/discovery/AwtrixLightDiscoveryService.java new file mode 100644 index 0000000000000..8914157708149 --- /dev/null +++ b/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/discovery/AwtrixLightDiscoveryService.java @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2010-2025 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ + +package org.openhab.binding.mqtt.awtrixlight.internal.discovery; + +import static org.openhab.binding.mqtt.MqttBindingConstants.BINDING_ID; +import static org.openhab.binding.mqtt.awtrixlight.internal.AwtrixLightBindingConstants.*; + +import java.nio.charset.StandardCharsets; +import java.util.Map; +import java.util.Set; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.mqtt.awtrixlight.internal.Helper; +import org.openhab.binding.mqtt.discovery.AbstractMQTTDiscovery; +import org.openhab.binding.mqtt.discovery.MQTTTopicDiscoveryService; +import org.openhab.core.config.discovery.DiscoveryResultBuilder; +import org.openhab.core.config.discovery.DiscoveryService; +import org.openhab.core.io.transport.mqtt.MqttBrokerConnection; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.ThingUID; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link AwtrixLightBridgeDiscoveryService} is responsible for finding awtrix + * clocks and setting them up for the handlers. + * + * @author Thomas Lauterbach - Initial contribution + */ + +@Component(service = DiscoveryService.class, configurationPid = "discovery.awtrixlight") +@NonNullByDefault +public class AwtrixLightDiscoveryService extends AbstractMQTTDiscovery { + protected final MQTTTopicDiscoveryService discoveryService; + + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + @Activate + public AwtrixLightDiscoveryService(@Reference MQTTTopicDiscoveryService discoveryService) { + super(Set.of(THING_TYPE_BRIDGE), 3, true, TOPIC_BASE + "/+" + TOPIC_STATS); + this.discoveryService = discoveryService; + } + + @Override + public void receivedMessage(ThingUID connectionBridge, MqttBrokerConnection connection, String topic, + byte[] payload) { + resetTimeout(); + String message = new String(payload, StandardCharsets.UTF_8); + if (topic.endsWith(TOPIC_STATS)) { + String baseTopic = topic.replace(TOPIC_STATS, ""); + Map messageParams = Helper.decodeStatsJson(message); + String vendorString = "Unknown"; + @Nullable + Object vendor = messageParams.get(FIELD_BRIDGE_TYPE); + if (vendor instanceof Integer) { + vendorString = vendor.equals(0) ? "Ulanzi" : "Generic"; + } + String firmwareString = "Unknown"; + @Nullable + Object firmware = messageParams.get(FIELD_BRIDGE_FIRMWARE); + if (firmware instanceof String) { + firmwareString = (String) firmware; + } + String hardwareUidString = "Unknown"; + @Nullable + Object hardwareUid = messageParams.get(FIELD_BRIDGE_UID); + if (hardwareUid instanceof String) { + hardwareUidString = (String) hardwareUid; + } + logger.trace("Publishing an Awtrix Clock with ID :{}", hardwareUidString); + publishClock(connectionBridge, baseTopic, vendorString, firmwareString, hardwareUidString); + } + } + + @Override + public void topicVanished(ThingUID connectionBridge, MqttBrokerConnection connection, String topic) { + } + + @Override + public void deactivate() { + super.deactivate(); + } + + @Override + protected MQTTTopicDiscoveryService getDiscoveryService() { + return discoveryService; + } + + void publishClock(ThingUID connectionBridgeUid, String baseTopic, String vendor, String firmware, + String hardwareUid) { + String name = baseTopic.replace(TOPIC_BASE + "/", ""); + thingDiscovered(DiscoveryResultBuilder + .create(new ThingUID(new ThingTypeUID(BINDING_ID, AWTRIX_CLOCK), connectionBridgeUid, hardwareUid)) + .withBridge(connectionBridgeUid).withProperty(PROP_VENDOR, vendor).withProperty(PROP_FIRMWARE, firmware) + .withProperty(PROP_UNIQUEID, hardwareUid).withProperty(PROP_BASETOPIC, baseTopic) + .withRepresentationProperty(PROP_UNIQUEID).withLabel("Awtrix Clock " + name).build()); + } +} diff --git a/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/handler/AwtrixLightAppHandler.java b/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/handler/AwtrixLightAppHandler.java new file mode 100644 index 0000000000000..2eb375527a958 --- /dev/null +++ b/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/handler/AwtrixLightAppHandler.java @@ -0,0 +1,683 @@ +/* + * Copyright (c) 2010-2025 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ + +package org.openhab.binding.mqtt.awtrixlight.internal.handler; + +import static org.openhab.binding.mqtt.awtrixlight.internal.AwtrixLightBindingConstants.*; + +import java.math.BigDecimal; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.Future; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.mqtt.awtrixlight.internal.AppConfigOptions; +import org.openhab.binding.mqtt.awtrixlight.internal.Helper; +import org.openhab.binding.mqtt.awtrixlight.internal.app.AwtrixApp; +import org.openhab.core.io.transport.mqtt.MqttMessageSubscriber; +import org.openhab.core.library.types.HSBType; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.library.types.QuantityType; +import org.openhab.core.library.types.StringType; +import org.openhab.core.library.unit.Units; +import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingStatus; +import org.openhab.core.thing.ThingStatusDetail; +import org.openhab.core.thing.ThingStatusInfo; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.binding.BaseThingHandler; +import org.openhab.core.thing.binding.BridgeHandler; +import org.openhab.core.thing.binding.ThingHandler; +import org.openhab.core.types.Command; +import org.openhab.core.types.RefreshType; +import org.openhab.core.util.ColorUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link AwtrixLightAppHandler} is responsible for handling commands for an app and will send mqtt messages to + * update the app configuration on the awtrix clock. It will also emit trigger events as long as the app is locked on + * the clock. + * + * @author Thomas Lauterbach - Initial contribution + */ +@NonNullByDefault +public class AwtrixLightAppHandler extends BaseThingHandler implements MqttMessageSubscriber { + + public static final Set SUPPORTED_THING_TYPES = Set.of(THING_TYPE_APP); + + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + private String channelPrefix = ""; + private String appName = ""; + private Boolean synchronizationRequired = true; + private Boolean buttonControlled = false; + private Boolean active = true; + + private AwtrixApp app = new AwtrixApp(); + + private final Object syncLock = new Object(); + private @Nullable ScheduledFuture finishInitJob; + + public AwtrixLightAppHandler(Thing thing) { + super(thing); + } + + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + logger.trace("Received command {} of type {} on channel {}", command.toString(), command.getClass(), + channelUID.getAsString()); + if (this.synchronizationRequired) { + // Don't accept any commands while we're synchronizing our settings + return; + } + + if (command instanceof RefreshType) { + updateApp(); + return; + } + switch (channelUID.getId()) { + case CHANNEL_ACTIVE: + // WARNING: Inactive Apps will return with default values after OH reboot + if (command instanceof OnOffType) { + if (OnOffType.OFF.equals(command)) { + this.active = false; + deleteApp(); + } else if (OnOffType.ON.equals(command)) { + this.active = true; + } + } + break; + case CHANNEL_RESET: + if (command instanceof OnOffType) { + if (OnOffType.ON.equals((OnOffType) command)) { + deleteApp(); + this.app = new AwtrixApp(); + updateApp(); + initStates(); + return; + } + } + break; + case CHANNEL_COLOR: + if (command instanceof HSBType) { + int[] hsbToRgb = ColorUtil.hsbToRgb((HSBType) command); + this.app.setColor(convertRgbArray(hsbToRgb)); + } + break; + case CHANNEL_GRADIENT_COLOR: + if (command instanceof HSBType) { + int[] hsbToRgb = ColorUtil.hsbToRgb((HSBType) command); + this.app.setGradient(convertRgbArray(hsbToRgb)); + } + break; + case CHANNEL_SCROLLSPEED: + if (command instanceof QuantityType) { + this.app.setScrollSpeed(((QuantityType) command).toBigDecimal()); + } + break; + case CHANNEL_DURATION: + if (command instanceof QuantityType) { + this.app.setDuration(((QuantityType) command).toBigDecimal()); + } + break; + case CHANNEL_EFFECT: + if (command instanceof StringType) { + this.app.setEffect(((StringType) command).toString()); + } + break; + case CHANNEL_EFFECT_SPEED: + if (command instanceof QuantityType) { + this.app.setEffectSpeed(((QuantityType) command).toBigDecimal()); + } + break; + case CHANNEL_EFFECT_PALETTE: + if (command instanceof StringType) { + this.app.setEffectPalette(((StringType) command).toString()); + } + break; + case CHANNEL_EFFECT_BLEND: + if (command instanceof OnOffType) { + this.app.setEffectBlend(command.equals(OnOffType.ON)); + } + break; + case CHANNEL_TEXT: + if (command instanceof StringType) { + this.app.setText(((StringType) command).toString()); + } + break; + case CHANNEL_TEXT_OFFSET: + if (command instanceof QuantityType) { + this.app.setTextOffset(((QuantityType) command).toBigDecimal()); + } + break; + case CHANNEL_TOP_TEXT: + if (command instanceof OnOffType) { + this.app.setTopText(command.equals(OnOffType.ON)); + } + break; + case CHANNEL_TEXTCASE: + if (command instanceof QuantityType) { + this.app.setTextCase(((QuantityType) command).toBigDecimal()); + } + break; + case CHANNEL_CENTER: + if (command instanceof OnOffType) { + this.app.setCenter(command.equals(OnOffType.ON)); + } + break; + case CHANNEL_BLINK_TEXT: + if (command instanceof QuantityType) { + QuantityType blinkInS = ((QuantityType) command).toUnit(Units.SECOND); + if (blinkInS != null) { + BigDecimal blinkInMs = blinkInS.toBigDecimal().multiply(THOUSAND); + this.app.setBlinkText(blinkInMs); + } + } + break; + case CHANNEL_FADE_TEXT: + if (command instanceof QuantityType) { + QuantityType fadeInS = ((QuantityType) command).toUnit(Units.SECOND); + if (fadeInS != null) { + BigDecimal fadeInMs = fadeInS.toBigDecimal().multiply(THOUSAND); + this.app.setFadeText(fadeInMs); + } + } + break; + case CHANNEL_RAINBOW: + if (command instanceof OnOffType) { + this.app.setRainbow(command.equals(OnOffType.ON)); + } + break; + case CHANNEL_ICON: + if (command instanceof StringType) { + this.app.setIcon(((StringType) command).toString()); + } + break; + case CHANNEL_PUSH_ICON: + if (command instanceof StringType) { + switch (((StringType) command).toString()) { + case PUSH_ICON_OPTION_0: + this.app.setPushIcon(new BigDecimal(0)); + break; + case PUSH_ICON_OPTION_1: + this.app.setPushIcon(new BigDecimal(1)); + break; + case PUSH_ICON_OPTION_2: + this.app.setPushIcon(new BigDecimal(2)); + break; + } + } + break; + case CHANNEL_BACKGROUND: + if (command instanceof HSBType) { + int[] hsbToRgb = ColorUtil.hsbToRgb((HSBType) command); + this.app.setBackground(convertRgbArray(hsbToRgb)); + } + break; + case CHANNEL_LINE: + if (command instanceof StringType) { + try { + String[] points = command.toString().split(","); + BigDecimal[] pointsAsNumber = new BigDecimal[points.length]; + for (int i = 0; i < points.length; i++) { + pointsAsNumber[i] = new BigDecimal(points[i]); + } + this.app.setLine(pointsAsNumber); + } catch (Exception e) { + logger.warn("Command {} cannot be parsed as line graph. Format should be: 1,2,3,4,5", + command.toString()); + } + } + break; + case CHANNEL_LIFETIME: + if (command instanceof QuantityType) { + this.app.setLifetime(((QuantityType) command).toBigDecimal()); + } + break; + case CHANNEL_LIFETIME_MODE: + if (command instanceof StringType) { + switch (command.toString()) { + case "DELETE": + this.app.setLifetimeMode(new BigDecimal(0)); + break; + case "STALE": + this.app.setLifetimeMode(new BigDecimal(1)); + break; + } + } + break; + case CHANNEL_BAR: + if (command instanceof StringType) { + try { + String[] points = command.toString().split(","); + BigDecimal[] pointsAsNumber = new BigDecimal[points.length]; + for (int i = 0; i < points.length; i++) { + pointsAsNumber[i] = new BigDecimal(points[i]); + } + this.app.setBar(pointsAsNumber); + } catch (Exception e) { + logger.warn("Command {} cannot be parsed as bar graph. Format should be: 1,2,3,4,5", + command.toString()); + } + } + break; + case CHANNEL_AUTOSCALE: + if (command instanceof OnOffType) { + this.app.setAutoscale(command.equals(OnOffType.ON)); + } + break; + case CHANNEL_OVERLAY: + if (command instanceof StringType) { + this.app.setOverlay(((StringType) command).toString()); + } + break; + case CHANNEL_PROGRESS: + if (command instanceof QuantityType) { + this.app.setProgress(((QuantityType) command).toBigDecimal()); + } + break; + case CHANNEL_PROGRESSC: + if (command instanceof HSBType) { + int[] hsbToRgb = ColorUtil.hsbToRgb((HSBType) command); + this.app.setProgressC(convertRgbArray(hsbToRgb)); + } + break; + case CHANNEL_PROGRESSBC: + if (command instanceof HSBType) { + int[] hsbToRgb = ColorUtil.hsbToRgb((HSBType) command); + this.app.setProgressBC(convertRgbArray(hsbToRgb)); + } + break; + } + logger.debug("Current app configuration: {}", this.app.toString()); + if (this.active) { + updateApp(); + } + } + + @Override + public void channelUnlinked(ChannelUID channelUID) { + switch (channelUID.getId()) { + case CHANNEL_COLOR: + this.app.setColor(AwtrixApp.DEFAULT_COLOR); + break; + case CHANNEL_GRADIENT_COLOR: + this.app.setGradient(AwtrixApp.DEFAULT_GRADIENT); + break; + case CHANNEL_SCROLLSPEED: + this.app.setScrollSpeed(AwtrixApp.DEFAULT_SCROLLSPEED); + break; + case CHANNEL_DURATION: + this.app.setDuration(AwtrixApp.DEFAULT_DURATION); + break; + case CHANNEL_EFFECT: + this.app.setEffect(AwtrixApp.DEFAULT_EFFECT); + break; + case CHANNEL_EFFECT_SPEED: + this.app.setEffectSpeed(AwtrixApp.DEFAULT_EFFECTSPEED); + break; + case CHANNEL_EFFECT_PALETTE: + this.app.setEffectPalette(AwtrixApp.DEFAULT_EFFECTPALETTE); + break; + case CHANNEL_EFFECT_BLEND: + this.app.setEffectBlend(AwtrixApp.DEFAULT_EFFECTBLEND); + break; + case CHANNEL_TEXT: + this.app.setText(AwtrixApp.DEFAULT_TEXT); + break; + case CHANNEL_TEXT_OFFSET: + this.app.setTextOffset(AwtrixApp.DEFAULT_TEXTOFFSET); + break; + case CHANNEL_TOP_TEXT: + this.app.setTopText(AwtrixApp.DEFAULT_TOPTEXT); + break; + case CHANNEL_TEXTCASE: + this.app.setTextCase(AwtrixApp.DEFAULT_TEXTCASE); + break; + case CHANNEL_CENTER: + this.app.setCenter(AwtrixApp.DEFAULT_CENTER); + break; + case CHANNEL_BLINK_TEXT: + this.app.setBlinkText(AwtrixApp.DEFAULT_BLINKTEXT); + break; + case CHANNEL_FADE_TEXT: + this.app.setFadeText(AwtrixApp.DEFAULT_FADETEXT); + break; + case CHANNEL_RAINBOW: + this.app.setRainbow(AwtrixApp.DEFAULT_RAINBOW); + break; + case CHANNEL_ICON: + this.app.setIcon(AwtrixApp.DEFAULT_ICON); + break; + case CHANNEL_PUSH_ICON: + this.app.setPushIcon(AwtrixApp.DEFAULT_PUSHICON); + break; + case CHANNEL_BACKGROUND: + this.app.setBackground(AwtrixApp.DEFAULT_BACKGROUND); + break; + case CHANNEL_LINE: + this.app.setLine(AwtrixApp.DEFAULT_LINE); + break; + case CHANNEL_LIFETIME: + this.app.setLifetime(AwtrixApp.DEFAULT_LIFETIME); + break; + case CHANNEL_LIFETIME_MODE: + this.app.setLifetimeMode(AwtrixApp.DEFAULT_LIFETIME_MODE); + break; + case CHANNEL_BAR: + this.app.setBar(AwtrixApp.DEFAULT_BAR); + break; + case CHANNEL_AUTOSCALE: + this.app.setAutoscale(AwtrixApp.DEFAULT_AUTOSCALE); + break; + case CHANNEL_OVERLAY: + this.app.setOverlay(AwtrixApp.DEFAULT_OVERLAY); + break; + case CHANNEL_PROGRESS: + this.app.setProgress(AwtrixApp.DEFAULT_PROGRESS); + break; + case CHANNEL_PROGRESSC: + this.app.setProgressC(AwtrixApp.DEFAULT_PROGRESSC); + break; + case CHANNEL_PROGRESSBC: + this.app.setProgressBC(AwtrixApp.DEFAULT_PROGRESSBC); + break; + } + logger.debug("Current app configuration: {}", this.app.toString()); + updateApp(); + } + + @Override + public void channelLinked(ChannelUID channelUID) { + // One might consider not updating all unaffected channels but it does not really hurt + initStates(); + } + + @Override + public void dispose() { + Future localFinishJob = this.finishInitJob; + if (localFinishJob != null && !localFinishJob.isCancelled() && !localFinishJob.isDone()) { + localFinishJob.cancel(true); + } + } + + @Override + public void handleRemoval() { + deleteApp(); + updateStatus(ThingStatus.REMOVED); + } + + @Override + public void initialize() { + this.synchronizationRequired = true; + AppConfigOptions config = getConfigAs(AppConfigOptions.class); + if (!this.appName.equals(config.appname)) { + // The app name has changed. Get rid of the old App first and init a new one + deleteApp(); + } + this.appName = config.appname; + this.buttonControlled = config.useButtons; + this.channelPrefix = getThing().getUID() + ":"; + thing.setProperty(PROP_APPID, this.appName); + logger.trace("Configured handler for app {} with channelPrefix {}", this.appName, this.channelPrefix); + bridgeStatusChanged(getBridgeStatus()); + } + + public ThingStatusInfo getBridgeStatus() { + Bridge b = getBridge(); + if (b != null) { + return b.getStatusInfo(); + } else { + return new ThingStatusInfo(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE, null); + } + } + + @Override + public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) { + if (bridgeStatusInfo.getStatus() == ThingStatus.OFFLINE) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE); + return; + } + if (bridgeStatusInfo.getStatus() != ThingStatus.ONLINE) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR); + return; + } + + Bridge localBridge = this.getBridge(); + if (localBridge == null) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED, "Bridge is missing or offline."); + return; + } + ThingHandler handler = localBridge.getHandler(); + if (handler instanceof AwtrixLightBridgeHandler) { + AwtrixLightBridgeHandler albh = (AwtrixLightBridgeHandler) handler; + Map bridgeProperties = albh.getThing().getProperties(); + @Nullable + String bridgeHardwareId = bridgeProperties.get(PROP_UNIQUEID); + if (bridgeHardwareId != null) { + thing.setProperty(PROP_APPID, bridgeHardwareId + "-" + this.appName); + } + if (this.synchronizationRequired) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NOT_YET_READY, "Synchronizing..."); + this.finishInitJob = scheduler.schedule(this::finishInit, 15, TimeUnit.SECONDS); + } else { + finishInit(); + } + } + } + + @Override + public void processMessage(String topic, byte[] payload) { + synchronized (syncLock) { + if (this.synchronizationRequired) { + this.synchronizationRequired = false; + String payloadString = new String(payload, StandardCharsets.UTF_8); + this.app = Helper.decodeAppJson(payloadString); + initStates(); + finishInit(); + } + } + } + + public String getAppName() { + return this.appName; + } + + public boolean isButtonControlled() { + return this.buttonControlled; + } + + void handleLeftButton(String event) { + triggerChannel(new ChannelUID(channelPrefix + CHANNEL_BUTLEFT), event); + } + + void handleRightButton(String event) { + triggerChannel(new ChannelUID(channelPrefix + CHANNEL_BUTRIGHT), event); + } + + void handleSelectButton(String event) { + triggerChannel(new ChannelUID(channelPrefix + CHANNEL_BUTSELECT), event); + } + + private BigDecimal[] convertRgbArray(int[] rgbIn) { + if (rgbIn.length == 3) { + BigDecimal[] rgb = Arrays.stream(rgbIn).mapToObj(BigDecimal::new).toArray(BigDecimal[]::new); + return rgb; + } else { + return new BigDecimal[0]; + } + } + + private void deleteApp() { + Bridge bridge = getBridge(); + if (bridge != null) { + BridgeHandler bridgeHandler = bridge.getHandler(); + if (bridgeHandler instanceof AwtrixLightBridgeHandler) { + AwtrixLightBridgeHandler albh = (AwtrixLightBridgeHandler) bridgeHandler; + albh.deleteApp(this.appName); + } + } + } + + private void updateApp() { + Bridge bridge = getBridge(); + if (bridge != null) { + BridgeHandler bridgeHandler = bridge.getHandler(); + if (bridgeHandler instanceof AwtrixLightBridgeHandler) { + AwtrixLightBridgeHandler albh = (AwtrixLightBridgeHandler) bridgeHandler; + albh.updateApp(this.appName, this.app.getAppConfig()); + } + } + } + + private void initStates() { + updateState(new ChannelUID(channelPrefix + CHANNEL_ACTIVE), this.active ? OnOffType.ON : OnOffType.OFF); + + BigDecimal[] color = this.app.getColor().length == 3 ? this.app.getColor() + : new BigDecimal[] { BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO }; + updateState(new ChannelUID(channelPrefix + CHANNEL_COLOR), + HSBType.fromRGB(color[0].intValue(), color[1].intValue(), color[2].intValue())); + + BigDecimal[] gradient = this.app.getGradient().length == 3 ? this.app.getGradient() + : new BigDecimal[] { BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO }; + updateState(new ChannelUID(channelPrefix + CHANNEL_GRADIENT_COLOR), + HSBType.fromRGB(gradient[0].intValue(), gradient[1].intValue(), gradient[2].intValue())); + + updateState(new ChannelUID(channelPrefix + CHANNEL_SCROLLSPEED), + new QuantityType<>(this.app.getScrollSpeed(), Units.PERCENT)); + + updateState(new ChannelUID(channelPrefix + CHANNEL_DURATION), + new QuantityType<>(this.app.getDuration(), Units.SECOND)); + + updateState(new ChannelUID(channelPrefix + CHANNEL_EFFECT), new StringType(this.app.getEffect())); + + updateState(new ChannelUID(channelPrefix + CHANNEL_EFFECT_SPEED), + new QuantityType<>(this.app.getEffectSpeed(), Units.ONE)); + + updateState(new ChannelUID(channelPrefix + CHANNEL_EFFECT_PALETTE), + new StringType(this.app.getEffectPalette())); + + updateState(new ChannelUID(channelPrefix + CHANNEL_EFFECT_BLEND), + this.app.getEffectBlend() ? OnOffType.ON : OnOffType.OFF); + + updateState(new ChannelUID(channelPrefix + CHANNEL_TEXT), new StringType(this.app.getText())); + + updateState(new ChannelUID(channelPrefix + CHANNEL_TEXT_OFFSET), + new QuantityType<>(this.app.getTextOffset(), Units.ONE)); + + updateState(new ChannelUID(channelPrefix + CHANNEL_TOP_TEXT), + this.app.getTopText() ? OnOffType.ON : OnOffType.OFF); + + updateState(new ChannelUID(channelPrefix + CHANNEL_CENTER), + this.app.getCenter() ? OnOffType.ON : OnOffType.OFF); + + BigDecimal blinkTextInSeconds = this.app.getBlinkText().divide(THOUSAND); + if (blinkTextInSeconds != null) { + updateState(new ChannelUID(channelPrefix + CHANNEL_BLINK_TEXT), + new QuantityType<>(blinkTextInSeconds, Units.SECOND)); + } + + BigDecimal fadeTextInSeconds = this.app.getFadeText().divide(THOUSAND); + if (fadeTextInSeconds != null) { + updateState(new ChannelUID(channelPrefix + CHANNEL_FADE_TEXT), + new QuantityType<>(fadeTextInSeconds, Units.SECOND)); + } + updateState(new ChannelUID(channelPrefix + CHANNEL_RAINBOW), + this.app.getRainbow() ? OnOffType.ON : OnOffType.OFF); + + updateState(new ChannelUID(channelPrefix + CHANNEL_ICON), new StringType(this.app.getIcon())); + + String param = ""; + if (this.app.getPushIcon().equals(BigDecimal.ZERO)) { + param = PUSH_ICON_OPTION_0; + } else if (this.app.getPushIcon().equals(BigDecimal.ONE)) { + param = PUSH_ICON_OPTION_1; + } else if (this.app.getPushIcon().equals(new BigDecimal(2))) { + param = PUSH_ICON_OPTION_2; + } + updateState(new ChannelUID(channelPrefix + CHANNEL_PUSH_ICON), new StringType(param)); + + updateState(new ChannelUID(channelPrefix + CHANNEL_TEXTCASE), + new QuantityType<>(this.app.getBlinkText(), Units.ONE)); + + BigDecimal[] background = this.app.getBackground().length == 3 ? this.app.getBackground() + : new BigDecimal[] { BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO }; + updateState(new ChannelUID(channelPrefix + CHANNEL_BACKGROUND), + HSBType.fromRGB(background[0].intValue(), background[1].intValue(), background[2].intValue())); + + BigDecimal[] line = this.app.getLine().length > 0 ? this.app.getLine() : new BigDecimal[] { BigDecimal.ZERO }; + String lineString = Arrays.stream(line).map(BigDecimal::toString).collect(Collectors.joining(",")); + updateState(new ChannelUID(channelPrefix + CHANNEL_LINE), new StringType(lineString)); + + updateState(new ChannelUID(channelPrefix + CHANNEL_LIFETIME), + new QuantityType<>(this.app.getLifetime(), Units.SECOND)); + + String lifetimeMode = this.app.getLifetimeMode().equals(BigDecimal.ZERO) ? "DELETE" : "STALE"; + updateState(new ChannelUID(channelPrefix + CHANNEL_LIFETIME_MODE), new StringType(lifetimeMode)); + + BigDecimal[] bar = this.app.getBar().length > 0 ? this.app.getBar() : new BigDecimal[] { BigDecimal.ZERO }; + String barString = Arrays.stream(bar).map(BigDecimal::toString).collect(Collectors.joining(",")); + updateState(new ChannelUID(channelPrefix + CHANNEL_BAR), new StringType(barString)); + + updateState(new ChannelUID(channelPrefix + CHANNEL_AUTOSCALE), + this.app.getAutoscale() ? OnOffType.ON : OnOffType.OFF); + + updateState(new ChannelUID(channelPrefix + CHANNEL_OVERLAY), new StringType(this.app.getOverlay())); + + BigDecimal progress = this.app.getProgress().compareTo(BigDecimal.ZERO) > 0 ? this.app.getProgress() + : BigDecimal.ZERO; + updateState(new ChannelUID(channelPrefix + CHANNEL_PROGRESS), new QuantityType<>(progress, Units.PERCENT)); + + BigDecimal[] progressC = this.app.getProgressC().length == 3 ? this.app.getProgressC() + : new BigDecimal[] { BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO }; + updateState(new ChannelUID(channelPrefix + CHANNEL_PROGRESSC), + HSBType.fromRGB(progressC[0].intValue(), progressC[1].intValue(), progressC[2].intValue())); + + BigDecimal[] progressBC = this.app.getProgressBC().length == 3 ? this.app.getProgressBC() + : new BigDecimal[] { BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO }; + updateState(new ChannelUID(channelPrefix + CHANNEL_PROGRESSBC), + HSBType.fromRGB(progressBC[0].intValue(), progressBC[1].intValue(), progressBC[2].intValue())); + } + + private void finishInit() { + synchronized (syncLock) { + if (this.synchronizationRequired) { + this.synchronizationRequired = false; + initStates(); + updateApp(); + } + } + updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE); + Future localJob = this.finishInitJob; + if (localJob != null && !localJob.isCancelled() && !localJob.isDone()) { + localJob.cancel(true); + this.finishInitJob = null; + } + } + + public void setActive(boolean active) { + if (this.active != active) { + this.active = active; + updateState(new ChannelUID(channelPrefix + CHANNEL_ACTIVE), active ? OnOffType.ON : OnOffType.OFF); + } + } +} diff --git a/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/handler/AwtrixLightBridgeHandler.java b/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/handler/AwtrixLightBridgeHandler.java new file mode 100644 index 0000000000000..2922e608d8bf9 --- /dev/null +++ b/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/handler/AwtrixLightBridgeHandler.java @@ -0,0 +1,626 @@ +/* + * Copyright (c) 2010-2025 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ + +package org.openhab.binding.mqtt.awtrixlight.internal.handler; + +import static org.openhab.binding.mqtt.awtrixlight.internal.AwtrixLightBindingConstants.*; + +import java.math.BigDecimal; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.mqtt.awtrixlight.internal.BridgeConfigOptions; +import org.openhab.binding.mqtt.awtrixlight.internal.Helper; +import org.openhab.binding.mqtt.awtrixlight.internal.action.AwtrixActions; +import org.openhab.binding.mqtt.awtrixlight.internal.app.AwtrixNotification; +import org.openhab.binding.mqtt.awtrixlight.internal.discovery.AwtrixLightBridgeDiscoveryService; +import org.openhab.binding.mqtt.handler.AbstractBrokerHandler; +import org.openhab.core.io.transport.mqtt.MqttBrokerConnection; +import org.openhab.core.io.transport.mqtt.MqttMessageSubscriber; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.library.types.QuantityType; +import org.openhab.core.library.types.RawType; +import org.openhab.core.library.types.StringType; +import org.openhab.core.library.unit.SIUnits; +import org.openhab.core.library.unit.Units; +import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingStatus; +import org.openhab.core.thing.ThingStatusDetail; +import org.openhab.core.thing.ThingStatusInfo; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.binding.BaseBridgeHandler; +import org.openhab.core.thing.binding.ThingHandler; +import org.openhab.core.thing.binding.ThingHandlerCallback; +import org.openhab.core.thing.binding.ThingHandlerService; +import org.openhab.core.types.Command; +import org.openhab.core.types.RefreshType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link AwtrixLightBridgeHandler} is responsible for handling commands for an awtrix clock device. It is also + * responsible for pushing discovery events to the discovery service. It also delegates trigger events to apps when they + * have been locked onto the clock. + * + * @author Thomas Lauterbach - Initial contribution + */ +@NonNullByDefault +public class AwtrixLightBridgeHandler extends BaseBridgeHandler implements MqttMessageSubscriber { + + public static final Set SUPPORTED_THING_TYPES = Set.of(THING_TYPE_BRIDGE); + + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + private @Nullable MqttBrokerConnection connection; + + private boolean appLock = false; + private int appLockTimeout = 10; + private String basetopic = ""; + private String channelPrefix = ""; + private String currentApp = ""; + private boolean discoverDefaultApps = false; + private BigDecimal lowBatteryThreshold = new BigDecimal(25); + + private Map appHandlers = new HashMap(); + + private @Nullable AwtrixLightBridgeDiscoveryService discoveryCallback; + private @Nullable ScheduledFuture scheduledAppLockTimeout; + private @Nullable ScheduledFuture scheduledFadeBeforeTimeout; + + public AwtrixLightBridgeHandler(Bridge bridge) { + super(bridge); + } + + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + logger.debug("Received command {} of type {} via channel {}", command, command.getClass(), + channelUID.getAsString()); + if (command instanceof RefreshType) { + if (CHANNEL_SCREEN.equals(channelUID.getId())) { + sendMQTT(this.basetopic + TOPIC_SEND_SCREEN, "", false); + } + return; + } + switch (channelUID.getId()) { + case CHANNEL_AUTO_BRIGHTNESS: + if (command instanceof OnOffType) { + boolean param = OnOffType.ON.equals(command); + sendMQTT(this.basetopic + TOPIC_SETTINGS, + "{\"" + FIELD_BRIDGE_SET_AUTO_BRIGHTNESS + "\":" + param + "}", false); + } + break; + case CHANNEL_BRIGHTNESS: + if (command instanceof QuantityType) { + double brightnessInt = ((QuantityType) command).doubleValue(); + if (0 <= brightnessInt && brightnessInt <= 100) { + long param = Math.round(255 * (brightnessInt / 100)); + sendMQTT(this.basetopic + TOPIC_SETTINGS, "{\"" + FIELD_BRIDGE_SET_AUTO_BRIGHTNESS + + "\":false,\"" + FIELD_BRIDGE_SET_BRIGHTNESS + "\":" + param + "}", false); + updateState(new ChannelUID(channelPrefix + CHANNEL_AUTO_BRIGHTNESS), OnOffType.OFF); + } + } + break; + case CHANNEL_DISPLAY: + handleDisplay(command); + break; + case CHANNEL_INDICATOR1: + if (command instanceof OnOffType) { + if (OnOffType.ON.equals(command)) { + activateIndicator(1, new int[] { 0, 255, 0 }); + } else { + deactivateIndicator(1); + } + } + break; + case CHANNEL_INDICATOR2: + if (command instanceof OnOffType) { + if (OnOffType.ON.equals(command)) { + activateIndicator(2, new int[] { 0, 255, 0 }); + } else { + deactivateIndicator(2); + } + } + break; + case CHANNEL_INDICATOR3: + if (command instanceof OnOffType) { + if (OnOffType.ON.equals(command)) { + activateIndicator(3, new int[] { 0, 255, 0 }); + } else { + deactivateIndicator(3); + } + } + break; + case CHANNEL_SOUND: + if (command instanceof StringType) { + playSound(command.toString()); + } + break; + case CHANNEL_RTTTL: + if (command instanceof StringType) { + playRtttl(command.toString()); + } + break; + } + } + + public String getBaseTopic() { + return this.basetopic; + } + + @Override + public void initialize() { + BridgeConfigOptions config = getConfigAs(BridgeConfigOptions.class); + this.basetopic = config.basetopic; + this.appLockTimeout = config.appLockTimeout; + this.discoverDefaultApps = config.discoverDefaultApps; + this.lowBatteryThreshold = config.lowBatteryThreshold; + this.channelPrefix = thing.getUID() + ":"; + logger.debug( + "Configured handler with baseTopic {}, channelPrefix {}, appLockTimeout {}, discoverDefaultApps {}", + this.basetopic, this.channelPrefix, this.appLockTimeout, this.discoverDefaultApps); + bridgeStatusChanged(getBridgeStatus()); + } + + @Override + public void processMessage(String topic, byte[] payload) { + String payloadString = new String(payload, StandardCharsets.UTF_8); + if (topic.endsWith(TOPIC_STATS)) { + if (thing.getStatus() != ThingStatus.ONLINE) { + updateStatus(ThingStatus.ONLINE); + } + handleStatsMessage(payloadString); + } else if (topic.endsWith(TOPIC_STATS_CURRENT_APP)) { + handleCurrentAppMessage(payloadString); + } else if (topic.endsWith(TOPIC_SCREEN)) { + handleScreenMessage(payloadString); + } else if (topic.endsWith(TOPIC_BUTLEFT)) { + String event = "1".equals(payloadString) ? "PRESSED" : "RELEASED"; + handleLeftButton(event); + } else if (topic.endsWith(TOPIC_BUTRIGHT)) { + String event = "1".equals(payloadString) ? "PRESSED" : "RELEASED"; + handleRightButton(event); + } else if (topic.endsWith(TOPIC_BUTSELECT)) { + String event = "1".equals(payloadString) ? "PRESSED" : "RELEASED"; + handleSelectButton(event); + } + } + + public ThingStatusInfo getBridgeStatus() { + Bridge b = getBridge(); + if (b != null) { + return b.getStatusInfo(); + } else { + return new ThingStatusInfo(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE, null); + } + } + + @Override + public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) { + if (bridgeStatusInfo.getStatus() == ThingStatus.OFFLINE) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE); + connection = null; + return; + } + if (bridgeStatusInfo.getStatus() != ThingStatus.ONLINE) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR); + return; + } + + Bridge localBridge = this.getBridge(); + if (localBridge == null) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED, + "Bridge is missing or offline, you need to setup a working MQTT broker first."); + return; + } + ThingHandler handler = localBridge.getHandler(); + if (handler instanceof AbstractBrokerHandler) { + AbstractBrokerHandler abh = (AbstractBrokerHandler) handler; + final MqttBrokerConnection connection; + try { + connection = abh.getConnectionAsync().get(500, TimeUnit.MILLISECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException ignored) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED, + "Bridge handler has no valid broker connection!"); + return; + } + this.connection = connection; + updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.CONFIGURATION_PENDING, + "Waiting for first MQTT message to be received."); + connection.subscribe(this.basetopic + TOPIC_STATS + "/#", this); + connection.subscribe(this.basetopic + TOPIC_STATS_CURRENT_APP + "/#", this); + connection.subscribe(this.basetopic + TOPIC_SCREEN + "/#", this); + } + return; + } + + public @Nullable MqttBrokerConnection getBrokerConnection() { + return connection; + } + + @Override + public void dispose() { + leaveAppControlMode(); + MqttBrokerConnection localConnection = connection; + if (localConnection != null) { + localConnection.unsubscribe(this.basetopic + TOPIC_STATS + "/#", this); + localConnection.unsubscribe(this.basetopic + TOPIC_STATS_CURRENT_APP + "/#", this); + localConnection.unsubscribe(this.basetopic + TOPIC_SCREEN + "/#", this); + } + } + + @Override + public void childHandlerInitialized(ThingHandler childHandler, Thing childThing) { + if (childHandler instanceof AwtrixLightAppHandler) { + AwtrixLightAppHandler alah = (AwtrixLightAppHandler) childHandler; + this.appHandlers.put(alah.getAppName(), alah); + MqttBrokerConnection localConnection = connection; + if (localConnection != null) { + localConnection.subscribe(basetopic + "/custom/" + alah.getAppName(), alah); + } + } + } + + @Override + public void childHandlerDisposed(ThingHandler childHandler, Thing childThing) { + if (childHandler instanceof AwtrixLightAppHandler) { + AwtrixLightAppHandler alah = (AwtrixLightAppHandler) childHandler; + this.appHandlers.remove(alah.getAppName()); + MqttBrokerConnection localConnection = connection; + if (localConnection != null) { + localConnection.unsubscribe(basetopic + "/custom/" + alah.getAppName(), alah); + } + } + } + + @Override + public Collection> getServices() { + ArrayList> services = new ArrayList>(); + services.add(AwtrixActions.class); + services.add(AwtrixLightBridgeDiscoveryService.class); + return services; + } + + public void reboot() { + this.sendMQTT(this.basetopic + TOPIC_REBOOT, "", false); + } + + public void sleep(int seconds) { + this.sendMQTT(this.basetopic + TOPIC_SLEEP, "{\"sleep\":" + seconds + "}", false); + } + + public void playSound(String melodyName) { + this.sendMQTT(this.basetopic + TOPIC_SOUND, "{\"sound\":\"" + melodyName + "\"}", false); + } + + public void playRtttl(String rtttl) { + this.sendMQTT(this.basetopic + TOPIC_RTTTL, rtttl, false); + } + + public void upgrade() { + this.sendMQTT(this.basetopic + TOPIC_UPGRADE, "", false); + } + + public void showNotification(boolean hold, boolean wakeUp, boolean stack, @Nullable String rtttl, + @Nullable String sound, boolean loopSound, Map params) { + AwtrixNotification notification = new AwtrixNotification(); + notification.updateFields(params); + notification.setHold(hold); + notification.setWakeUp(wakeUp); + notification.setStack(stack); + if (rtttl != null) { + notification.setRtttl(rtttl); + } + if (sound != null) { + notification.setSound(sound); + } + notification.setLoopSound(loopSound); + String notificationMessage = notification.getAppConfig(); + this.sendMQTT(this.basetopic + TOPIC_NOTIFY, notificationMessage, false); + } + + public void setAppDiscoveryCallback(AwtrixLightBridgeDiscoveryService awtrixLightBridgeDiscoveryService) { + this.discoveryCallback = awtrixLightBridgeDiscoveryService; + } + + public void removeAppDiscoveryCallback() { + this.discoveryCallback = null; + } + + void updateApp(String appName, String payload) { + sendMQTT(this.basetopic + "/custom/" + appName, payload, true); + } + + void deleteApp(String appName) { + logger.debug("Deleting app {}", appName); + sendMQTT(this.basetopic + "/custom/" + appName, "", true); + } + + private String getIndicatorTopic(int indicatorId) { + String indicatorTopic = ""; + if (indicatorId == 1) { + indicatorTopic = TOPIC_INDICATOR1; + } else if (indicatorId == 2) { + indicatorTopic = TOPIC_INDICATOR2; + } else if (indicatorId == 3) { + indicatorTopic = TOPIC_INDICATOR3; + } + return indicatorTopic; + } + + public void blinkIndicator(int id, int[] rgb, int blinkInMs) { + String indicatorTopic = getIndicatorTopic(id); + if (!"".equals(indicatorTopic) && rgb.length == 3) { + sendMQTT(this.basetopic + indicatorTopic, + "{\"color\":[" + rgb[0] + "," + rgb[1] + "," + rgb[2] + "],\"blink\":" + blinkInMs + "}", false); + } + } + + public void fadeIndicator(int id, int[] rgb, int fadeInMs) { + String indicatorTopic = getIndicatorTopic(id); + if (!"".equals(indicatorTopic) && rgb.length == 3) { + sendMQTT(this.basetopic + indicatorTopic, + "{\"color\":[" + rgb[0] + "," + rgb[1] + "," + rgb[2] + "],\"fade\":" + fadeInMs + "}", false); + } + } + + public void activateIndicator(int id, int[] rgb) { + String indicatorTopic = getIndicatorTopic(id); + if (!"".equals(indicatorTopic) && rgb.length == 3) { + sendMQTT(this.basetopic + indicatorTopic, "{\"color\":[" + rgb[0] + "," + rgb[1] + "," + rgb[2] + "]}", + false); + } + } + + public void deactivateIndicator(int id) { + String indicatorTopic = getIndicatorTopic(id); + if (!"".equals(indicatorTopic)) { + sendMQTT(this.basetopic + indicatorTopic, "{\"color\":\"0\"}", false); + } + } + + private void handleDisplay(Command command) { + if (command instanceof OnOffType) { + HashMap params = new HashMap(); + params.put("power", OnOffType.ON.equals(command)); + String json = Helper.encodeJson(params); + sendMQTT(this.basetopic + TOPIC_POWER, json, false); + } + } + + private void sendMQTT(String commandTopic, String payload, boolean retain) { + logger.debug("Will send {} to topic {}", payload, commandTopic); + byte[] payloadBytes = payload.getBytes(); + MqttBrokerConnection localConnection = connection; + if (localConnection != null) { + localConnection.publish(commandTopic, payloadBytes, 1, retain); + } + } + + private void handleLeftButton(String event) { + triggerChannel(new ChannelUID(channelPrefix + CHANNEL_BUTLEFT), event); + if (this.appLock) { + scheduleAppLockTimeout(); + @Nullable + AwtrixLightAppHandler alah = this.appHandlers.get(this.currentApp); + if (alah != null) { + alah.handleLeftButton(event); + } + } + } + + private void handleRightButton(String event) { + triggerChannel(new ChannelUID(channelPrefix + CHANNEL_BUTRIGHT), event); + if (this.appLock) { + scheduleAppLockTimeout(); + @Nullable + AwtrixLightAppHandler alah = this.appHandlers.get(this.currentApp); + if (alah != null) { + alah.handleRightButton(event); + } + } + } + + private void handleSelectButton(String event) { + triggerChannel(new ChannelUID(channelPrefix + CHANNEL_BUTSELECT), event); + @Nullable + AwtrixLightAppHandler alah = this.appHandlers.get(this.currentApp); + if (alah != null) { + if (!this.appLock) { + if (alah.isButtonControlled()) { + if ("RELEASED".equals(event)) { + sendMQTT(this.basetopic + TOPIC_SETTINGS, "{\"" + FIELD_BRIDGE_SET_AUTO_TRANSITION + + "\":false,\"" + FIELD_BRIDGE_SET_BLOCK_KEYS + "\":true}", false); + this.appLock = true; + scheduleAppLockTimeout(); + } + } + } else { + scheduleAppLockTimeout(); + alah.handleSelectButton(event); + } + } + } + + private void scheduleAppLockTimeout() { + activateIndicator(3, new int[] { 255, 0, 0 }); + Future localSignalSchedule = this.scheduledFadeBeforeTimeout; + if (localSignalSchedule != null && !localSignalSchedule.isCancelled() && !localSignalSchedule.isDone()) { + localSignalSchedule.cancel(true); + } + Future localSchedule = this.scheduledAppLockTimeout; + if (localSchedule != null && !localSchedule.isCancelled() && !localSchedule.isDone()) { + localSchedule.cancel(true); + scheduleAppLockTimeout(); + } else { + this.scheduledAppLockTimeout = scheduler.schedule(this::leaveAppControlMode, this.appLockTimeout, + TimeUnit.SECONDS); + if (this.appLockTimeout > 3) { + this.scheduledFadeBeforeTimeout = scheduler.schedule(this::signalLeaveAppControlMode, + this.appLockTimeout - 3, TimeUnit.SECONDS); + } + } + } + + private void leaveAppControlMode() { + this.appLock = false; + Future localSignalSchedule = this.scheduledFadeBeforeTimeout; + if (localSignalSchedule != null && !localSignalSchedule.isCancelled() && !localSignalSchedule.isDone()) { + localSignalSchedule.cancel(true); + } + Future localSchedule = this.scheduledAppLockTimeout; + if (localSchedule != null && !localSchedule.isCancelled() && !localSchedule.isDone()) { + localSchedule.cancel(true); + } + deactivateIndicator(3); + sendMQTT(this.basetopic + TOPIC_SETTINGS, + "{\"" + FIELD_BRIDGE_SET_AUTO_TRANSITION + "\":true,\"" + FIELD_BRIDGE_SET_BLOCK_KEYS + "\":false}", + false); + } + + private void signalLeaveAppControlMode() { + fadeIndicator(3, new int[] { 255, 0, 0 }, 500); + } + + private void handleScreenMessage(String screenMessage) { + logger.trace("Incoming screen message {}", screenMessage); + byte[] bytes = Helper.decodeImage(screenMessage); + updateState(new ChannelUID(channelPrefix + CHANNEL_SCREEN), new RawType(bytes, "image/png")); + } + + private void handleCurrentAppMessage(String currentAppMessage) { + logger.trace("Incoming currentApp message {}", currentAppMessage); + this.currentApp = currentAppMessage; + ThingHandlerCallback callback = getCallback(); + if (callback != null && callback.isChannelLinked(new ChannelUID(this.channelPrefix + CHANNEL_SCREEN))) { + sendMQTT(this.basetopic + TOPIC_SEND_SCREEN, "", false); + } + if (!this.appHandlers.containsKey(currentAppMessage)) { + if (this.discoverDefaultApps || !Arrays.stream(DEFAULT_APPS).anyMatch(currentAppMessage::equals)) { + AwtrixLightBridgeDiscoveryService localDiscoveryCallback = discoveryCallback; + if (localDiscoveryCallback != null) { + localDiscoveryCallback.appDiscovered(this.basetopic, currentAppMessage); + } + } + } else { + @Nullable + AwtrixLightAppHandler alah = this.appHandlers.get(currentAppMessage); + if (alah != null) { + alah.setActive(true); + } + } + } + + private void handleStatsMessage(String statsMessage) { + logger.trace("Incoming stats message {}", statsMessage); + Map params = Helper.decodeStatsJson(statsMessage); + for (Map.Entry entry : params.entrySet()) { + + @Nullable + String key = entry.getKey(); + @Nullable + Object value = entry.getValue(); + switch (key) { + case FIELD_BRIDGE_UID: + thing.setProperty(PROP_UNIQUEID, (String) value); + break; + case FIELD_BRIDGE_BATTERY: + if (value instanceof BigDecimal) { + updateState(new ChannelUID(channelPrefix + CHANNEL_BATTERY), + new QuantityType<>((BigDecimal) value, Units.PERCENT)); + OnOffType lowBattery = ((BigDecimal) value).compareTo(this.lowBatteryThreshold) <= 0 + ? OnOffType.ON + : OnOffType.OFF; + updateState(new ChannelUID(channelPrefix + CHANNEL_LOW_BATTERY), lowBattery); + } + break; + case FIELD_BRIDGE_BATTERY_RAW: + // Not mapped to channel atm + break; + case FIELD_BRIDGE_FIRMWARE: + thing.setProperty(PROP_FIRMWARE, value.toString()); + break; + case FIELD_BRIDGE_TYPE: + if (value instanceof BigDecimal) { + String vendor = ((BigDecimal) value).compareTo(BigDecimal.ZERO) == 0 ? "Ulanzi" : "Generic"; + thing.setProperty(PROP_VENDOR, vendor); + break; + } + case FIELD_BRIDGE_LUX: + updateState(new ChannelUID(channelPrefix + CHANNEL_LUX), + new QuantityType<>((BigDecimal) value, Units.LUX)); + break; + case FIELD_BRIDGE_LDR_RAW: + // Not mapped to channel atm + break; + case FIELD_BRIDGE_RAM: + // Not mapped to channel atm + break; + case FIELD_BRIDGE_MATRIX: + updateState(new ChannelUID(channelPrefix + CHANNEL_DISPLAY), + (boolean) value ? OnOffType.ON : OnOffType.OFF); + break; + case FIELD_BRIDGE_BRIGHTNESS: + long brightnessInPercent = Math.round((((BigDecimal) value).doubleValue() / 255) * 100); + updateState(new ChannelUID(channelPrefix + CHANNEL_BRIGHTNESS), + new QuantityType<>(brightnessInPercent, Units.PERCENT)); + break; + case FIELD_BRIDGE_TEMPERATURE: + updateState(new ChannelUID(channelPrefix + CHANNEL_TEMPERATURE), + new QuantityType<>((BigDecimal) value, SIUnits.CELSIUS)); + break; + case FIELD_BRIDGE_HUMIDITY: + updateState(new ChannelUID(channelPrefix + CHANNEL_HUMIDITY), + new QuantityType<>((BigDecimal) value, Units.PERCENT)); + break; + case FIELD_BRIDGE_UPTIME: + // Not mapped to channel atm + break; + case FIELD_BRIDGE_WIFI_SIGNAL: + updateState(new ChannelUID(channelPrefix + CHANNEL_RSSI), + new QuantityType<>((BigDecimal) value, Units.ONE)); + break; + case FIELD_BRIDGE_MESSAGES: + // Not mapped to channel atm + break; + case FIELD_BRIDGE_INDICATOR1: + OnOffType indicator1 = (Boolean) value ? OnOffType.ON : OnOffType.OFF; + updateState(new ChannelUID(channelPrefix + CHANNEL_INDICATOR1), indicator1); + break; + case FIELD_BRIDGE_INDICATOR2: + OnOffType indicator2 = (Boolean) value ? OnOffType.ON : OnOffType.OFF; + updateState(new ChannelUID(channelPrefix + CHANNEL_INDICATOR2), indicator2); + break; + case FIELD_BRIDGE_INDICATOR3: + OnOffType indicator3 = (Boolean) value ? OnOffType.ON : OnOffType.OFF; + updateState(new ChannelUID(channelPrefix + CHANNEL_INDICATOR3), indicator3); + break; + case FIELD_BRIDGE_APP: + updateState(new ChannelUID(channelPrefix + CHANNEL_APP), new StringType((String) value)); + break; + } + } + } +} diff --git a/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/resources/OH-INF/config/config.xml b/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/resources/OH-INF/config/config.xml new file mode 100644 index 0000000000000..49850a2535d5b --- /dev/null +++ b/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/resources/OH-INF/config/config.xml @@ -0,0 +1,47 @@ + + + + + + + Base topic as configured in the Awtrix Light device. + awtrix + + + + Timeout in seconds until selected apps return control and the app rotation continues. + 10 + + + + Currently changing settings for the built-in default apps is not implemented. It is therefore + recommended to ignore them during app discovery. + false + + + + Threshold for issuing a low battery warning. + 25 + + + + + + + Name of the app + + + + + + false + + + + diff --git a/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/resources/OH-INF/thing/thing-types.xml new file mode 100644 index 0000000000000..54563d8e3c231 --- /dev/null +++ b/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/resources/OH-INF/thing/thing-types.xml @@ -0,0 +1,593 @@ + + + + + + + + + Device with Awtrix Light firmware + screen + + + + + + + + Left button pressed + + + + Right button pressed + + + + Select button pressed + + + + Switches the display ON or OFF + recommend + + + + + + + + + + + + + + + + + + 2 + + uniqueId + + + + + + + + + An app for an Awtrix Light device + screen + + + recommend + + + recommend + + + + + + + + Left button pressed + + + + Right button pressed + + + + Select button pressed + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2 + + appid + + + + + Number:Illuminance + + Brightness in lux + sun + + Measurement + Light + + + + + + Switch + + Let the clock set brightness automatically based on internal brightness sensor readings + screen + + Control + Light + + + + + Number:Dimensionless + + Screen brightness in percent + screen + + Control + Light + + + + + + Number:Dimensionless + + RSSI value of WiFi signal + qualityofservice + + Measurement + + + + + + Switch + + Indicator state + switch + + Control + Power + + + + + String + + App currently shown on screen + screen + + Status + + + + + + Image + + + + screen + + Status + + + + + Color + + Color to display text and charts on the screen + colorpicker + + Control + ColorTemperature + + + + + Switch + + Resets the app and the linked items to the default settings + switch + + Control + + + + + Color + + Color for display background + colorpicker + + Control + ColorTemperature + + + + + Color + + Color text as gradient from Color to Gradient Color + colorpicker + + Control + ColorTemperature + + + + + Number:Time + + How long the app should be displayed + time + + Control + Duration + + + + + String + + Effect shown in the background of the app + screen + + Control + + + + + + + + + + + + + + + + + + + + + + + + + + + + String + + Changes the color scheme of the effect settings + colorpicker + + Control + + + + + + + + + + + + + + + + + + Switch + + Smoother effect animations + switch + + Control + + + + + Number:Dimensionless + + Playback speed of background animations + time + + Control + + + + + String + + Text displayed in the app + text + + Control + Duration + + + + + Number:Dimensionless + + Change case of displayed text. Default uses the global preset. + text + + Control + + + + + + + + + + + + Number:Dimensionless + + Offset on x-axis in pixel for displayed text + text + + Control + + + + + Switch + + Draws the text on the top of the display + text + + Control + + + + + Number:Time + + Blink text in specified time interval + text + + Control + Duration + + + + + + Number:Time + + Fade text in specified interval + text + + Control + Duration + + + + + + Switch + + Fades the text color in rainbow colors + text + + Control + + + + + Switch + + Short texts will be centered instead of scrolling + text + + Control + + + + + String + + Icon ID or filename without extension + screen + + Control + + + + + String + + Make the icon scroll along with the text + screen + + Control + + + + + + + + + + + + String + + Draw a line graph (format: "value1,value2,value3", last 16 entries will be displayed, last 11 with icon) + qualityofservice + + Control + + + + + Number:Time + + Remove the app if there was no update within specified seconds + time + + Control + Duration + + + + + String + + Delete the app or mark as stale after lifetime + screen + + Control + + + + + + + + + + + String + + Draw a bar graph (format: "value1,value2,value3", last 16 entries will be displayed, last 11 with icon) + qualityofservice + + Control + + + + + Switch + + Automatically scales graphs to fit onto display + qualityofservice + + Control + + + + + String + + Overlay effect (overriden by global clock overlay) + screen + + Control + + + + + + + + + + + + + + + + Number:Dimensionless + + Show progress bar with specified percentage + qualityofservice + + Control + + + + + + Color + + Color of progress bar + colorpicker + + Control + ColorTemperature + + + + + Color + + Color of progress bar background + colorpicker + + Control + ColorTemperature + + + + + Number:Dimensionless + + Speed of text scrolling as percentage of default speed + + Control + + + + + + String + + Name of the melody file in the clocks MELODIES folder + text + + Point + + + + + String + + Ring Tone Text Transfer Language (RTTTL) compliant sound string + text + + Point + + + + diff --git a/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/resources/OH-INF/update/app-updates.xml b/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/resources/OH-INF/update/app-updates.xml new file mode 100644 index 0000000000000..5757daac68d86 --- /dev/null +++ b/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/resources/OH-INF/update/app-updates.xml @@ -0,0 +1,43 @@ + + + + + + + mqtt:lifetime + + + + mqtt:lifetimeMode + + + + mqtt:overlay + + + + + + + system:rawbutton + + Left button pressed + + + + system:rawbutton + + Right button pressed + + + + system:rawbutton + + Select button pressed + + + + + diff --git a/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/resources/OH-INF/update/bridge-updates.xml b/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/resources/OH-INF/update/bridge-updates.xml new file mode 100644 index 0000000000000..0269d49b0366e --- /dev/null +++ b/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/resources/OH-INF/update/bridge-updates.xml @@ -0,0 +1,50 @@ + + + + + + + mqtt:sound + + + mqtt:rtttl + + + + + system.power + + Switches the display ON or OFF + + + + system:battery-level + + + + system:low-battery + + + + system:rawbutton + + Left button pressed + + + + system:rawbutton + + Right button pressed + + + + system:rawbutton + + Select button pressed + + + + + diff --git a/bundles/org.openhab.binding.mqtt.awtrixlight/src/test/java/org/openhab/binding/mqtt/awtrixlight/internal/HelperTest.java b/bundles/org.openhab.binding.mqtt.awtrixlight/src/test/java/org/openhab/binding/mqtt/awtrixlight/internal/HelperTest.java new file mode 100644 index 0000000000000..ef5a3d515fbe9 --- /dev/null +++ b/bundles/org.openhab.binding.mqtt.awtrixlight/src/test/java/org/openhab/binding/mqtt/awtrixlight/internal/HelperTest.java @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2010-2025 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.mqtt.awtrixlight.internal; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.openhab.binding.mqtt.awtrixlight.internal.AwtrixLightBindingConstants.*; + +import java.math.BigDecimal; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; +import org.openhab.binding.mqtt.awtrixlight.internal.app.AwtrixApp; + +/** + * Test cases for the {@link Helper} service. + * + * @author Thomas Lauterbach - Initial contribution + */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +@NonNullByDefault +public class HelperTest { + + @Test + public void convertJson() { + String message = "{\"bat\":100,\"bat_raw\":670,\"type\":0,\"lux\":958,\"ldr_raw\":975,\"ram\":148568,\"bri\":10,\"temp\":24,\"hum\":41,\"uptime\":50092,\"wifi_signal\":-53,\"messages\":2,\"version\":\"0.83\",\"indicator1\":false,\"indicator2\":false,\"indicator3\":false,\"app\":\"Temperature\"}"; + + Map convertedJson = Helper.decodeStatsJson(message); + assertEquals(0, 17 - convertedJson.size()); + + String[] statsProperties = { FIELD_BRIDGE_BATTERY, FIELD_BRIDGE_BATTERY_RAW, FIELD_BRIDGE_FIRMWARE, + FIELD_BRIDGE_TYPE, FIELD_BRIDGE_LUX, FIELD_BRIDGE_LDR_RAW, FIELD_BRIDGE_RAM, FIELD_BRIDGE_BRIGHTNESS, + FIELD_BRIDGE_TEMPERATURE, FIELD_BRIDGE_HUMIDITY, FIELD_BRIDGE_UPTIME, FIELD_BRIDGE_WIFI_SIGNAL, + FIELD_BRIDGE_MESSAGES, FIELD_BRIDGE_INDICATOR1, FIELD_BRIDGE_INDICATOR2, FIELD_BRIDGE_INDICATOR3, + FIELD_BRIDGE_APP }; + + for (String s : statsProperties) { + assertTrue(convertedJson.containsKey(s)); + String[] stringProperties = { FIELD_BRIDGE_APP, FIELD_BRIDGE_FIRMWARE }; + String[] booleanProperties = { FIELD_BRIDGE_INDICATOR1, FIELD_BRIDGE_INDICATOR2, FIELD_BRIDGE_INDICATOR3 }; + if (Arrays.stream(stringProperties).anyMatch(s::equals)) { + Object prop = convertedJson.get(s); + assertNotNull(prop); + assertEquals(String.class, prop.getClass()); + } else if (Arrays.stream(booleanProperties).anyMatch(s::equals)) { + Object prop = convertedJson.get(s); + assertNotNull(prop); + assertEquals(Boolean.class, prop.getClass()); + } else { + Object prop = convertedJson.get(s); + assertNotNull(prop); + assertEquals(BigDecimal.class, prop.getClass()); + } + } + } + + @Test + public void encodeJson() { + HashMap inputMap = new HashMap<>(); + inputMap.put("Test1", "Test1"); + inputMap.put("Test2", 100); + inputMap.put("Test3", -100); + inputMap.put("Test4", true); + inputMap.put("Test5", false); + + String json = Helper.encodeJson(inputMap); + + assertTrue(json.contains("{")); + assertTrue(json.contains("\"Test1\":\"Test1\"")); + assertTrue(json.contains("\"Test2\":100")); + assertTrue(json.contains("\"Test3\":-100")); + assertTrue(json.contains("\"Test4\":true")); + assertTrue(json.contains("\"Test5\":false")); + assertTrue(json.contains("}")); + assertEquals(4, json.chars().filter(ch -> ch == ',').count()); + } + + @Test + public void convertImage() { + String imageMessage = "[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,14276351,0,0,0,0,0,0,0,0,0,0,0,16777215,0,16777215,0,0,16777215,0,0,16777215,0,16777215,0,0,0,0,0,0,0,0,16777215,13094911,9017343,0,0,0,0,0,0,0,0,0,0,16777215,0,16777215,0,16777215,16777215,0,0,0,0,16777215,0,0,0,0,0,0,0,16777215,14276351,13094911,9017343,6582015,0,0,0,0,0,0,0,0,0,16777215,16777215,16777215,0,0,16777215,0,0,0,16777215,0,0,0,0,0,0,0,0,14276351,13094911,13094911,6582015,6582015,0,0,0,0,0,0,0,0,0,0,0,16777215,0,0,16777215,0,0,16777215,0,0,0,0,0,0,0,0,0,14276351,9017343,9017343,6582015,3030679,0,0,0,0,0,0,0,0,0,0,0,16777215,0,16777215,16777215,16777215,0,16777215,0,16777215,0,0,0,0,0,0,0,0,6582015,6582015,3030679,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]"; + Helper.decodeImage(imageMessage); + } + + @Test + public void mappingRoundTrip() { + AwtrixApp app = new AwtrixApp(); + String appConfig = app.getAppConfig(); + + Map convertJson = Helper.decodeAppJson(appConfig).getAppParams(); + String appConfig2 = Helper.encodeJson(convertJson); + assertEquals(appConfig, appConfig2); + + app.updateFields(convertJson); + String appConfig3 = app.getAppConfig(); + assertEquals(appConfig, appConfig3); + + app.setText("Test"); + // app.setRepeat(new BigDecimal(10)); + BigDecimal[] color = { new BigDecimal(255), new BigDecimal(255), new BigDecimal(255) }; + app.setColor(color); + app.setEffect("Radar"); + app.setEffectSpeed(new BigDecimal(80)); + String appConfig4 = app.getAppConfig(); + assertNotEquals(appConfig, appConfig4); + + AwtrixApp app2 = new AwtrixApp(); + Map convertJson4 = Helper.decodeAppJson(appConfig4).getAppParams(); + app2.updateFields(convertJson4); + String appConfig5 = app2.getAppConfig(); + assertEquals(appConfig4, appConfig5); + + app.updateFields(convertJson); + String appConfig6 = app.getAppConfig(); + assertEquals(appConfig, appConfig6); + } + + @Test + public void trimArray() { + BigDecimal[] untrimmed = { BigDecimal.ZERO, BigDecimal.ONE, BigDecimal.TEN, new BigDecimal(1000) }; + BigDecimal[] trimmed = Helper.leftTrim(untrimmed, 2); + + assertTrue(trimmed.length == 2); + assertEquals(trimmed[0], untrimmed[untrimmed.length - 2]); + assertEquals(trimmed[1], untrimmed[untrimmed.length - 1]); + } +} diff --git a/bundles/pom.xml b/bundles/pom.xml index 6fed251e59339..e64e00ca7f412 100644 --- a/bundles/pom.xml +++ b/bundles/pom.xml @@ -276,6 +276,7 @@ org.openhab.binding.monopriceaudio org.openhab.binding.mpd org.openhab.binding.mqtt + org.openhab.binding.mqtt.awtrixlight org.openhab.binding.mqtt.espmilighthub org.openhab.binding.mqtt.fpp org.openhab.binding.mqtt.generic From 077f66f6b8fd8300e740a31aac4710c3670b8b60 Mon Sep 17 00:00:00 2001 From: Thomas Lauterbach Date: Sun, 9 Feb 2025 13:57:52 +0100 Subject: [PATCH 02/16] Fixed brigde update Signed-off-by: Thomas Lauterbach --- .../src/main/resources/OH-INF/update/bridge-updates.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/resources/OH-INF/update/bridge-updates.xml b/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/resources/OH-INF/update/bridge-updates.xml index 0269d49b0366e..0a3f8b9c8bf4e 100644 --- a/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/resources/OH-INF/update/bridge-updates.xml +++ b/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/resources/OH-INF/update/bridge-updates.xml @@ -14,7 +14,7 @@ - system.power + system:power Switches the display ON or OFF From 97c00d600146aca51000748ecb4c4af8cf279355 Mon Sep 17 00:00:00 2001 From: Thomas Lauterbach Date: Thu, 13 Feb 2025 00:27:53 +0100 Subject: [PATCH 03/16] fixed first review comments Signed-off-by: Thomas Lauterbach --- CODEOWNERS | 1 + bom/openhab-addons/pom.xml | 5 + .../README.md | 203 ++++++++++-------- .../pom.xml | 2 +- .../internal/AwtrixLightHandlerFactory.java | 6 +- .../internal/action/AwtrixActions.java | 45 ++-- .../AwtrixLightBridgeDiscoveryService.java | 6 +- .../main/resources/OH-INF/config/config.xml | 4 +- .../resources/OH-INF/thing/thing-types.xml | 181 +++++----------- .../resources/OH-INF/update/app-updates.xml | 43 ---- .../OH-INF/update/bridge-updates.xml | 50 ----- .../src/main/resources/footer.xml | 1 + 12 files changed, 199 insertions(+), 348 deletions(-) delete mode 100644 bundles/org.openhab.binding.mqtt.awtrixlight/src/main/resources/OH-INF/update/app-updates.xml delete mode 100644 bundles/org.openhab.binding.mqtt.awtrixlight/src/main/resources/OH-INF/update/bridge-updates.xml diff --git a/CODEOWNERS b/CODEOWNERS index 97bd8089e8d23..bb8a1f509f380 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -243,6 +243,7 @@ /bundles/org.openhab.binding.monopriceaudio/ @mlobstein /bundles/org.openhab.binding.mpd/ @stefanroellin /bundles/org.openhab.binding.mqtt/ @ccutrer +/bundles/org.openhab.binding.mqtt.awtrixlight/ @DrRSatzteil /bundles/org.openhab.binding.mqtt.espmilighthub/ @Skinah /bundles/org.openhab.binding.mqtt.fpp/ @computergeek1507 /bundles/org.openhab.binding.mqtt.generic/ @ccutrer diff --git a/bom/openhab-addons/pom.xml b/bom/openhab-addons/pom.xml index bb0b318825c52..555712addf915 100644 --- a/bom/openhab-addons/pom.xml +++ b/bom/openhab-addons/pom.xml @@ -1206,6 +1206,11 @@ org.openhab.binding.mqtt.awtrixlight ${project.version} + + org.openhab.addons.bundles + org.openhab.binding.mqtt.awtrixlight + ${project.version} + org.openhab.addons.bundles org.openhab.binding.mqtt.espmilighthub diff --git a/bundles/org.openhab.binding.mqtt.awtrixlight/README.md b/bundles/org.openhab.binding.mqtt.awtrixlight/README.md index f97ef6b8d87d9..1a093d0937e44 100644 --- a/bundles/org.openhab.binding.mqtt.awtrixlight/README.md +++ b/bundles/org.openhab.binding.mqtt.awtrixlight/README.md @@ -1,107 +1,124 @@ # MQTT Awtrix 3 Binding -This binding allows you to control Awtrix 3 (formerly Awtrix Light) LED matrix displays via MQTT. The Awtrix 3 is a customizable 32x8 LED matrix display that can show various information like time, weather, notifications and custom text/graphics. +This binding allows you to control Awtrix 3 (formerly Awtrix Light) LED matrix displays via MQTT. +Awtrix 3 is a firmware for a 32x8 LED matrix display that can show various information like time, weather, notifications and custom text/graphics. +The most popular choice for a device that supports the Awtrix 3 firmware is the Ulanzi tc0001 clock. ## Supported Things This binding supports two types of things: -| Thing Type | Description | -|------------------------|-------------------------------------------------------------------------------------------------| -| `awtrixclock` (Bridge) | Represents an Awtrix 3 display device. Acts as a bridge for apps. | -| `awtrixapp` | Represents an app running on the Awtrix display. Apps can show text, icons, notifications, etc. | +| Thing Type | Description | +|-------------------------|-------------------------------------------------------------------------------------------------| +| `awtrix-clock` (Bridge) | Represents an Awtrix 3 display device. Acts as a bridge for apps. | +| `awtrix-app` | Represents an app running on the Awtrix display. Apps can show text, icons, notifications, etc. | -## Prerequisites +The binding was tested with the Ulanzi tc0001 clock. -- An MQTT broker (the MQTT binding must be installed and a broker configured) -- An Awtrix 3 LED matrix display configured to use MQTT +## Discovery + +The binding can automatically discover Awtrix 3 devices that publish their status to the configured MQTT broker. +Make sure to use a mqtt prefix that starts with `awtrix` for discovery to work. +It is however recommended to use a prefix with two topic levels, for example `awtrix/clock1` so that you can discover and control multiple devices. +Once a device is discovered, it will appear in the inbox. +There is no need to trigger a discovery scan manually. +Default Awtrix apps can also be discovered if `discoverDefaultApps` is enabled on the bridge. +This is however not recommended as the default apps cannot be controlled with this binding. + +## Binding Configuration + +The Awtrix 3 binding does not offer any binding configuration parameters. ## Thing Configuration ### Bridge Configuration (`awtrixclock`) -| Parameter | Description | Required | Default | +| Parameter | Description | Default | Required | |-----------------------|--------------------------------------------------------------------------------------------------------------------------------------------|----------|----------| -| `basetopic` | The MQTT base topic for the Awtrix device | Yes | "awtrix" | -| `appLockTimeout` | Timeout in seconds before releasing the lock to a selected app and returning to normal app cycle (see App Configuration for more details). | No | 10 | -| `discoverDefaultApps` | Enable discovery of default apps. Since default apps cannot be controlled by this binding this should usually be disabled. | No | false | -| `lowBatteryThreshold` | Battery level threshold for low battery warning. | No | 25 | +| `basetopic` | The MQTT base topic for the Awtrix device | "awtrix" | Yes | +| `appLockTimeout` | Timeout in seconds before releasing the lock to a selected app and returning to normal app cycle (see App Configuration for more details). | 10 | Yes | +| `discoverDefaultApps` | Enable discovery of default apps. Since default apps cannot be controlled by this binding this should usually be disabled. | false | Yes | +| `lowBatteryThreshold` | Battery level threshold for low battery warning. | 25 | Yes | ### App Configuration (`awtrixapp`) -| Parameter | Description | Required | Default | -|--------------|------------------------------------|----------|---------| -| `appname` | Name of the app | Yes | - | -| `useButtons` | Enable button control for this app | No | false | +| Parameter | Description | Default | Required | +|--------------|------------------------------------|---------|----------| +| `appname` | Name of the app | - | Yes | +| `useButtons` | Enable button control for this app | false | No | -When you enable the button control for an app, you can lock the app to the display by pushing the select button on the clock device. A red indicator will be shown while the app is locked and will start to blink shortly before the lock ends. The lock will last for the appLockTimeout set for the bridge. As long as the app is locked the normal app cycle is disabled and you can control the app by pressing the left and right buttons or the select button on the clock device. Pressing a button while the app is locked will reset the lock timeout to the value set for appLockTimeout. Left and right button presses will emit button events on the clock itself and the selected app. The button events can be used by rules to change the displayed app or perform any other actions (for example change the text color of the app or skip the current song playing on your audio device). +When you enable the button control for an app, you can lock the app to the display by pushing the select button on the clock device. +A red indicator will be shown while the app is locked and will start to blink shortly before the lock ends. +The lock will last for the appLockTimeout set for the bridge. +As long as the app is locked the normal app cycle is disabled and you can control the app by pressing the left and right buttons or the select button on the clock device. +Pressing a button while the app is locked will reset the lock timeout to the value set for appLockTimeout. Left and right button presses will emit button events on the clock itself and the selected app. +The button events can be used by rules to change the displayed app or perform any other actions (for example change the text color of the app or skip the current song playing on your audio device). ## Channels ### Bridge Channels (`awtrixclock`) -| Channel | Type | Description | -|-------------------|----------------------|------------------------------------------------------------------------------------------------------------------------------------------------| -| `app` | String | Currently active app: Will show the name of the app that is currently shown on the display. | -| `autoBrightness` | Switch | Automatic brightness control: The clock will adjust the display brightness automatically based on ambient light. | -| `batteryLevel` | Number | Battery level: The battery level of the internal battery in percent. | -| `brightness` | Dimmer | Display brightness: The brightness of the display in percent. | -| `buttonLeft` | Trigger | Left button press event: Triggered when the left button is pressed/released (Event PRESSED or RELEASED). | -| `buttonRight` | Trigger | Right button press event: Triggered when the right button is pressed/released (Event PRESSED or RELEASED). | -| `buttonSelect` | Trigger | Select button press event: Triggered when the select button is pressed/released (Event PRESSED or RELEASED). | -| `display` | Switch | Display on/off: Switches the display on or off. The clock will still stay on while the display is off. | -| `humidity` | Number:Dimensionless | Relative humidity: Relative humidity in percent. For the Ulanzi clock values are usually very inaccurate. | -| `indicator1` | Switch | Control first indicator LED: Switches the first indicator LED on or off. The color of the LED will be green but can be customised by using thing actions (you can also use blinking/fading effects). | -| `indicator2` | Switch | Control second indicator LED: Switches the second indicator LED on or off.The color of the LED will be green but can be customised by using thing actions (you can also use blinking/fading effects). | -| `indicator3` | Switch | Control third indicator LED: Switches the third indicator LED on or off. The color of the LED will be green but can be customised by using thing actions (you can also use blinking/fading effects). | -| `lowBattery` | Switch | Low battery warning: Will be switched ON as soon as the battery level drops below the lowBatteryThreshold set for the bridge. | -| `lux` | Number:Illuminance | Ambient light level: Ambient light level in lux as measured by the built-in light sensor. | -| `rssi` | Number:Dimensionless | WiFi signal strength (RSSI): WiFi signal strength (RSSI) in dBm. | -| `rtttl` | String | Play RTTTL ringtone: Play a ringtone specified in RTTTL format (see https://de.wikipedia.org/wiki/Ring_Tones_Text_Transfer_Language) | -| `screen` | String | Screen image: Allows you to mirror the screen image from the clock. The screen image will be updated automatically when the app changes but can be updated manually by sending a RefreshType command to the channel. | -| `sound` | String | Play sound file: The sound file must be available on the clock device in the MELODIES folder. Save a file with a valid RTTTL string (e.g. melody.txt) in this folder and play it by sending a String command to the channel with the filename without file extension (e.g. "melody"). | -| `temperature` | Number:Temperature | Device temperature: Temperature in °C as measured by the built-in temperature sensor. For the Ulanzi clock values are usually very inaccurate. | +| Channel | Type | Read/Write | Description | +|------------------|----------------------|------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `app` | String | R | Currently active app: Will show the name of the app that is currently shown on the display. | +| `autoBrightness` | Switch | RW | Automatic brightness control: The clock will adjust the display brightness automatically based on ambient light. | +| `batteryLevel` | Number | R | Battery level: The battery level of the internal battery in percent. | +| `brightness` | Dimmer | RW | Display brightness: The brightness of the display in percent. | +| `buttonLeft` | Trigger | R | Left button press event: Triggered when the left button is pressed/released (Event PRESSED or RELEASED). | +| `buttonRight` | Trigger | R | Right button press event: Triggered when the right button is pressed/released (Event PRESSED or RELEASED). | +| `buttonSelect` | Trigger | R | Select button press event: Triggered when the select button is pressed/released (Event PRESSED or RELEASED). | +| `display` | Switch | RW | Display on/off: Switches the display on or off. The clock will still stay on while the display is off. | +| `humidity` | Number:Dimensionless | R | Relative humidity: Relative humidity in percent. For the Ulanzi clock values are usually very inaccurate. | +| `indicator1` | Switch | RW | Control first indicator LED: Switches the first indicator LED on or off. The color of the LED will be green but can be customised by using thing actions (you can also use blinking/fading effects). | +| `indicator2` | Switch | RW | Control second indicator LED: Switches the second indicator LED on or off.The color of the LED will be green but can be customised by using thing actions (you can also use blinking/fading effects). | +| `indicator3` | Switch | RW | Control third indicator LED: Switches the third indicator LED on or off. The color of the LED will be green but can be customised by using thing actions (you can also use blinking/fading effects). | +| `lowBattery` | Switch | R | Low battery warning: Will be switched ON as soon as the battery level drops below the lowBatteryThreshold set for the bridge. | +| `lux` | Number:Illuminance | R | Ambient light level: Ambient light level in lux as measured by the built-in light sensor. | +| `rssi` | Number:Dimensionless | R | WiFi signal strength (RSSI): WiFi signal strength (RSSI) in dBm. | +| `rtttl` | String | W | Play RTTTL ringtone: Play a ringtone specified in RTTTL format (see https://de.wikipedia.org/wiki/Ring_Tones_Text_Transfer_Language) | +| `screen` | String | R | Screen image: Allows you to mirror the screen image from the clock. The screen image will be updated automatically when the app changes but can be updated manually by sending a RefreshType command to the channel. | +| `sound` | String | W | Play sound file: The sound file must be available on the clock device in the MELODIES folder. Save a file with a valid RTTTL string (e.g. melody.txt) in this folder and play it by sending a String command to the channel with the filename without file extension (e.g. "melody"). | +| `temperature` | Number:Temperature | R | Device temperature: Temperature in °C as measured by the built-in temperature sensor. For the Ulanzi clock values are usually very inaccurate. | ### App Channels (`awtrixapp`) - -| Channel | Type | Description | -|----------------------|----------------------|---------------------------------------------------------------------------------------------------------------------------------------------| -| `active` | Switch | Enable/disable the app: Switches the app on or off. Note that channels of inactive apps will be reset to their default values during a restart of openHAB. | -| `autoscale` | Switch | Enable/disable autoscaling for bar and linechart. | -| `background` | Color | Sets a background color. | -| `bar` | String | Shows a bar chart: Send a string with values separated by commas (e.g. "value1,value2,value3"). Only the last 16 values will be displayed. | -| `blink` | Number:Time | Blink text: Blink the text in the specified interval. Ignored if gradientColor or rainbow are set. | -| `center` | Switch | Center short text horizontally and disable scrolling. | -| `color` | Color | Text, bar or line chart color. | -| `duration` | Number:Time | Display duration in seconds. | -| `effect` | String | Display effect (see https://blueforcer.github.io/awtrix3/#/effects for possible values). | -| `effectBlend` | Switch | Enable smoother effect transitions. Only to be used with effect. | -| `effectPalette` | String | Color palette for effects (see https://blueforcer.github.io/awtrix3/#/effects for possible values and how to create custom palettes). Only to be used with effect. | -| `effectSpeed` | Number:Dimensionless | Effect animation speed: Higher means faster (see https://blueforcer.github.io/awtrix3/#/effects). Only to be used with effect. | -| `fade` | Number:Time | Fade text: Fades the text in and out in the specified interval. Ignored if gradientColor or rainbow are set. | -| `gradientColor` | Color | Secondary color for gradient effects. Use color for setting the primary color. | -| `icon` | String | Icon name to display: Install icons on the clock device first. | -| `lifetime` | Number:Time | App lifetime: Define how long the app will remain active on the clock. | -| `lifetimeMode` | String | Lifetime mode: Define if the app should be deleted (Command DELETE) or marked as stale (Command STALE) after lifetime. | -| `line` | String | Shows a line chart: Send a string with values separated by commas (e.g. "value1,value2,value3"). Only the last 16 values will be displayed. | -| `overlay` | String | Enable overlay mode: Shows a weather overlay effect (can be any of clear, snow, rain, drizzle, storm, thunder, frost). | -| `progress` | Number:Dimensionless | Progress value: Shows a progress bar at the bottom of the app with the specified percentage value. | -| `progressBackground` | Color | Progress bar background color: Background color for the progress bar. | -| `progressColor` | Color | Progress bar color: Color for the progress bar. | -| `pushIcon` | String | Push icon animation (STATIC=Icon doesn't move, PUSHOUT=Icon moves with text and will not appear again, PUSHOUTRETURN=Icon moves with text but appears again when the text starts to scroll again). | -| `rainbow` | Switch | Enable rainbow effect: Uses a rainbow effect for the displayed text. | -| `reset` | Switch | Reset app to default state: All channels will be reset to their default values. | -| `scrollSpeed` | Number:Dimensionless | Text scrolling speed: Provide as percentage value. The original speed is 100%. Values above 100% will increase the scrolling speed, values below 100% will decrease it. Setting this value to 0 will disable scrolling completely. | -| `text` | String | Text to display. | -| `textCase` | Number:Dimensionless | Set text case (0=normal, 1=uppercase, 2=lowercase). | -| `textOffset` | Number:Dimensionless | Text offset position: Horizontal offset of the text in pixels. | -| `topText` | String | Draws the text on the top of the display. | +| Channel | Type | Read/Write | Description | +|----------------------|----------------------|------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `active` | Switch | W | Enable/disable the app: Switches the app on or off. Note that channels of inactive apps will be reset to their default values during a restart of openHAB. | +| `autoscale` | Switch | RW | Enable/disable autoscaling for bar and linechart. | +| `background` | Color | RW | Sets a background color. | +| `bar` | String | RW | Shows a bar chart: Send a string with values separated by commas (e.g. "value1,value2,value3"). Only the last 16 values will be displayed. | +| `blink` | Number:Time | RW | Blink text: Blink the text in the specified interval. Ignored if gradientColor or rainbow are set. | +| `center` | Switch | RW | Center short text horizontally and disable scrolling. | +| `color` | Color | RW | Text, bar or line chart color. | +| `duration` | Number:Time | RW | Display duration in seconds. | +| `effect` | String | RW | Display effect (see https://blueforcer.github.io/awtrix3/#/effects for possible values). | +| `effectBlend` | Switch | RW | Enable smoother effect transitions. Only to be used with effect. | +| `effectPalette` | String | RW | Color palette for effects (see https://blueforcer.github.io/awtrix3/#/effects for possible values and how to create custom palettes). Only to be used with effect. | +| `effectSpeed` | Number:Dimensionless | RW | Effect animation speed: Higher means faster (see https://blueforcer.github.io/awtrix3/#/effects). Only to be used with effect. | +| `fade` | Number:Time | RW | Fade text: Fades the text in and out in the specified interval. Ignored if gradientColor or rainbow are set. | +| `gradientColor` | Color | RW | Secondary color for gradient effects. Use color for setting the primary color. | +| `icon` | String | RW | Icon name to display: Install icons on the clock device first. | +| `lifetime` | Number:Time | RW | App lifetime: Define how long the app will remain active on the clock. | +| `lifetimeMode` | String | RW | Lifetime mode: Define if the app should be deleted (Command DELETE) or marked as stale (Command STALE) after lifetime. | +| `line` | String | RW | Shows a line chart: Send a string with values separated by commas (e.g. "value1,value2,value3"). Only the last 16 values will be displayed. | +| `overlay` | String | RW | Enable overlay mode: Shows a weather overlay effect (can be any of clear, snow, rain, drizzle, storm, thunder, frost). | +| `progress` | Number:Dimensionless | RW | Progress value: Shows a progress bar at the bottom of the app with the specified percentage value. | +| `progressBackground` | Color | RW | Progress bar background color: Background color for the progress bar. | +| `progressColor` | Color | RW | Progress bar color: Color for the progress bar. | +| `pushIcon` | String | RW | Push icon animation (STATIC=Icon doesn't move, PUSHOUT=Icon moves with text and will not appear again, PUSHOUTRETURN=Icon moves with text but appears again when the text starts to scroll again). | +| `rainbow` | Switch | RW | Enable rainbow effect: Uses a rainbow effect for the displayed text. | +| `reset` | Switch | RW | Reset app to default state: All channels will be reset to their default values. | +| `scrollSpeed` | Number:Dimensionless | RW | Text scrolling speed: Provide as percentage value. The original speed is 100%. Values above 100% will increase the scrolling speed, values below 100% will decrease it. Setting this value to 0 will disable scrolling completely. | +| `text` | String | RW | Text to display. | +| `textCase` | Number:Dimensionless | RW | Set text case (0=normal, 1=uppercase, 2=lowercase). | +| `textOffset` | Number:Dimensionless | RW | Text offset position: Horizontal offset of the text in pixels. | +| `topText` | String | RW | Draws the text on the top of the display. | ## Full Example ### Things -``` +```java Bridge mqtt:broker:myBroker [ host="localhost", port=1883 ] Bridge mqtt:awtrixclock:myBroker:myAwtrix "Living Room Display" (mqtt:broker:myBroker) [ basetopic="awtrix", appLockTimeout=10, lowBatteryThreshold=25 ] { Thing awtrixapp clock "Clock App" [ appname="clock", useButtons=true ] @@ -113,7 +130,7 @@ Bridge mqtt:awtrixclock:myBroker:myAwtrix "Living Room Display" (mqtt:broker:myB ### Items -``` +```java // Bridge items (Living Room Display) Group gAwtrix "Living Room Awtrix Display" Dimmer Display_Brightness "Brightness [%d %%]" (gAwtrix) { channel="mqtt:awtrixclock:myBroker:myAwtrix:brightness" } @@ -157,7 +174,7 @@ Color Custom_ProgressColor "Progress Color" (gAwtrix) { channel="mqtt:awtrixapp: ### Sitemap -``` +```perl sitemap awtrix label="Awtrix Display" { Frame label="Display Control" { @@ -203,23 +220,19 @@ sitemap awtrix label="Awtrix Display" { ``` -## Discovery - -The binding can automatically discover Awtrix devices that publish their status to the configured MQTT broker. Once a device is discovered, it will appear in the inbox. Default Awtrix apps can also be discovered if `discoverDefaultApps` is enabled on the bridge. This is however not recommended as the default apps cannot be controlled via this binding. - ## Actions The binding provides various actions that can be used in rules to control the Awtrix display. To use these actions, you need to import them in your rules. Rules DSL: -``` +```java val awtrixActions = getActions("mqtt.awtrixlight", "mqtt:awtrixclock:myBroker:myAwtrix") ``` JS Scripting: -```javascript +```java var awtrixActions = actions.thingActions("mqtt.awtrixlight", "mqtt:awtrixclock:myBroker:myAwtrix"); ``` @@ -227,7 +240,7 @@ var awtrixActions = actions.thingActions("mqtt.awtrixlight", "mqtt:awtrixclock:m Control the three indicator LEDs on the Awtrix display (JS Scripting): -```javascript +```java // Blink indicator 1 in red for 1 second awtrixActions.blinkIndicator(1, [255,0,0], 1000) @@ -245,7 +258,7 @@ awtrixActions.deactivateIndicator(1) Control basic device functions: -```javascript +```java // Reboot the device awtrixActions.reboot() @@ -260,7 +273,7 @@ awtrixActions.upgrade() Play sounds and melodies: -```javascript +```java // Play a predefined sound file (without extension) awtrixActions.playSound("notification") @@ -272,7 +285,7 @@ awtrixActions.playRtttl("Indiana:d=4,o=5,b=250:e,8p,8f,8g,8p,1c6,8p.,d,8p,8e,1f, Display notifications on the screen: -``` +```java // Show simple notification with icon awtrixActions.showNotification("Hello World", "alert") @@ -299,15 +312,15 @@ awtrixActions.showCustomNotification( The action method parameters: -| Parameter | Type | Description | -|-----------|------|-------------| -| `hold` | Boolean | Keep notification until manually cleared | -| `wakeUp` | Boolean | Wake up from screen saver | -| `stack` | Boolean | Add to notification stack | -| `rtttl` | String | RTTTL melody to play | -| `sound` | String | Sound file to play (without extension) | -| `loopSound` | Boolean | Loop the sound | -| `params` | Map | Notification parameters | +| Parameter | Type | Description | +|-------------|---------|------------------------------------------| +| `hold` | Boolean | Keep notification until manually cleared | +| `wakeUp` | Boolean | Wake up from screen saver | +| `stack` | Boolean | Add to notification stack | +| `rtttl` | String | RTTTL melody to play | +| `sound` | String | Sound file to play (without extension) | +| `loopSound` | Boolean | Loop the sound | +| `params` | Map | Notification parameters | The `showCustomNotification` action accepts all app channels as shown above as parameters in the params map. @@ -315,7 +328,7 @@ The `showCustomNotification` action accepts all app channels as shown above as p Here are some example rules demonstrating various features: -``` +```java rule "Battery Status Indicator Demo" when diff --git a/bundles/org.openhab.binding.mqtt.awtrixlight/pom.xml b/bundles/org.openhab.binding.mqtt.awtrixlight/pom.xml index 5fa0c9edbf57c..d70b6a59a8d5b 100644 --- a/bundles/org.openhab.binding.mqtt.awtrixlight/pom.xml +++ b/bundles/org.openhab.binding.mqtt.awtrixlight/pom.xml @@ -11,7 +11,7 @@ org.openhab.binding.mqtt.awtrixlight - openHAB Add-ons :: Bundles :: MQTT Awtrix Light + openHAB Add-ons :: Bundles :: MQTT Awtrix 3 diff --git a/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/AwtrixLightHandlerFactory.java b/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/AwtrixLightHandlerFactory.java index 48522b13a7797..84713a67ea1a6 100644 --- a/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/AwtrixLightHandlerFactory.java +++ b/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/AwtrixLightHandlerFactory.java @@ -53,11 +53,9 @@ public boolean supportsThingType(ThingTypeUID thingTypeUID) { protected @Nullable ThingHandler createHandler(Thing thing) { ThingTypeUID thingTypeUID = thing.getThingTypeUID(); if (AwtrixLightBridgeHandler.SUPPORTED_THING_TYPES.contains(thingTypeUID)) { - AwtrixLightBridgeHandler bridgeHandler = new AwtrixLightBridgeHandler((Bridge) thing); - return bridgeHandler; + return new AwtrixLightBridgeHandler((Bridge) thing); } else if (AwtrixLightAppHandler.SUPPORTED_THING_TYPES.contains(thingTypeUID)) { - AwtrixLightAppHandler appHandler = new AwtrixLightAppHandler((Thing) thing); - return appHandler; + return new AwtrixLightAppHandler((Thing) thing); } return null; diff --git a/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/action/AwtrixActions.java b/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/action/AwtrixActions.java index 0c9d4c91f7f8d..efba9b8717878 100644 --- a/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/action/AwtrixActions.java +++ b/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/action/AwtrixActions.java @@ -54,8 +54,8 @@ public void blinkIndicator(int indicatorId, int[] rgb, int blinkTimeInMs) { } public static void blinkIndicator(@Nullable ThingActions actions, int indicatorId, int[] rgb, int blinkTimeInMs) { - if (actions instanceof AwtrixActions) { - ((AwtrixActions) actions).blinkIndicator(indicatorId, rgb, blinkTimeInMs); + if (actions instanceof AwtrixActions awtrixActions) { + awtrixActions.blinkIndicator(indicatorId, rgb, blinkTimeInMs); } else { throw new IllegalArgumentException("Instance is not an AwtrixActions class."); } @@ -70,8 +70,8 @@ public void fadeIndicator(int indicatorId, int[] rgb, int fadeTimeInMs) { } public static void fadeIndicator(@Nullable ThingActions actions, int indicatorId, int[] rgb, int fadeTimeInMs) { - if (actions instanceof AwtrixActions) { - ((AwtrixActions) actions).fadeIndicator(indicatorId, rgb, fadeTimeInMs); + if (actions instanceof AwtrixActions awtrixActions) { + awtrixActions.fadeIndicator(indicatorId, rgb, fadeTimeInMs); } else { throw new IllegalArgumentException("Instance is not an AwtrixActions class."); } @@ -86,8 +86,8 @@ public void activateIndicator(int indicatorId, int[] rgb) { } public static void activateIndicator(@Nullable ThingActions actions, int indicatorId, int[] rgb) { - if (actions instanceof AwtrixActions) { - ((AwtrixActions) actions).activateIndicator(indicatorId, rgb); + if (actions instanceof AwtrixActions awtrixActions) { + awtrixActions.activateIndicator(indicatorId, rgb); } else { throw new IllegalArgumentException("Instance is not an AwtrixActions class."); } @@ -102,8 +102,8 @@ public void deactivateIndicator(int indicatorId) { } public static void deactivateIndicator(@Nullable ThingActions actions, int indicatorId) { - if (actions instanceof AwtrixActions) { - ((AwtrixActions) actions).deactivateIndicator(indicatorId); + if (actions instanceof AwtrixActions awtrixActions) { + awtrixActions.deactivateIndicator(indicatorId); } else { throw new IllegalArgumentException("Instance is not an AwtrixActions class."); } @@ -118,8 +118,8 @@ public void reboot() { } public static void reboot(@Nullable ThingActions actions) { - if (actions instanceof AwtrixActions) { - ((AwtrixActions) actions).reboot(); + if (actions instanceof AwtrixActions awtrixActions) { + awtrixActions.reboot(); } else { throw new IllegalArgumentException("Instance is not an AwtrixActions class."); } @@ -134,8 +134,8 @@ public void sleep(int seconds) { } public static void sleep(@Nullable ThingActions actions, int seconds) { - if (actions instanceof AwtrixActions) { - ((AwtrixActions) actions).sleep(seconds); + if (actions instanceof AwtrixActions awtrixActions) { + awtrixActions.sleep(seconds); } else { throw new IllegalArgumentException("Instance is not an AwtrixActions class."); } @@ -150,8 +150,8 @@ public void upgrade() { } public static void upgrade(@Nullable ThingActions actions) { - if (actions instanceof AwtrixActions) { - ((AwtrixActions) actions).upgrade(); + if (actions instanceof AwtrixActions awtrixActions) { + awtrixActions.upgrade(); } else { throw new IllegalArgumentException("Instance is not an AwtrixActions class."); } @@ -166,9 +166,9 @@ public void playSound(String melody) { } public static void playSound(@Nullable ThingActions actions, @Nullable String melody) { - if (actions instanceof AwtrixActions) { + if (actions instanceof AwtrixActions awtrixActions) { if (melody != null) { - ((AwtrixActions) actions).playSound(melody); + awtrixActions.playSound(melody); } } else { throw new IllegalArgumentException("Instance is not an AwtrixActions class."); @@ -184,9 +184,9 @@ public void playRtttl(String rtttl) { } public static void playRtttl(@Nullable ThingActions actions, @Nullable String rtttl) { - if (actions instanceof AwtrixActions) { + if (actions instanceof AwtrixActions awtrixActions) { if (rtttl != null) { - ((AwtrixActions) actions).playRtttl(rtttl); + awtrixActions.playRtttl(rtttl); } } else { throw new IllegalArgumentException("Instance is not an AwtrixActions class."); @@ -206,9 +206,9 @@ public void showNotification(String message, String icon) { public static void showNotification(@Nullable ThingActions actions, @Nullable String message, @Nullable String icon) { - if (actions instanceof AwtrixActions) { + if (actions instanceof AwtrixActions awtrixActions) { if (message != null && icon != null) { - ((AwtrixActions) actions).showNotification(message, icon); + awtrixActions.showNotification(message, icon); } } else { throw new IllegalArgumentException("Instance is not an AwtrixActions class."); @@ -227,10 +227,9 @@ public void showCustomNotification(Map appParams, boolean hold, public static void showCustomNotification(@Nullable ThingActions actions, @Nullable Map appParams, boolean hold, boolean wakeUp, boolean stack, @Nullable String rtttl, @Nullable String sound, boolean loopSound) { - if (actions instanceof AwtrixActions) { + if (actions instanceof AwtrixActions awtrixActions) { if (appParams != null) { - ((AwtrixActions) actions).showCustomNotification(appParams, hold, wakeUp, stack, rtttl, sound, - loopSound); + awtrixActions.showCustomNotification(appParams, hold, wakeUp, stack, rtttl, sound, loopSound); } } else { throw new IllegalArgumentException("Instance is not an AwtrixActions class."); diff --git a/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/discovery/AwtrixLightBridgeDiscoveryService.java b/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/discovery/AwtrixLightBridgeDiscoveryService.java index 494bcfbad0d60..12268d2d5c1b1 100644 --- a/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/discovery/AwtrixLightBridgeDiscoveryService.java +++ b/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/discovery/AwtrixLightBridgeDiscoveryService.java @@ -59,9 +59,9 @@ public void appDiscovered(String baseTopic, String appName) { @Override public void setThingHandler(ThingHandler handler) { - if (handler instanceof AwtrixLightBridgeHandler) { - this.bridgeHandler = (AwtrixLightBridgeHandler) handler; - ((AwtrixLightBridgeHandler) handler).setAppDiscoveryCallback(this); + if (handler instanceof AwtrixLightBridgeHandler bridgeHandler) { + this.bridgeHandler = bridgeHandler; + bridgeHandler.setAppDiscoveryCallback(this); } } diff --git a/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/resources/OH-INF/config/config.xml b/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/resources/OH-INF/config/config.xml index 49850a2535d5b..15ab491386c1d 100644 --- a/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/resources/OH-INF/config/config.xml +++ b/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/resources/OH-INF/config/config.xml @@ -4,7 +4,7 @@ xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0" xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0 https://openhab.org/schemas/config-description-1.0.0.xsd"> - + Base topic as configured in the Awtrix Light device. @@ -28,7 +28,7 @@ - + Name of the app diff --git a/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/resources/OH-INF/thing/thing-types.xml index 54563d8e3c231..549666c1e1343 100644 --- a/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/resources/OH-INF/thing/thing-types.xml @@ -4,7 +4,7 @@ xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0" xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd"> - + @@ -13,18 +13,18 @@ screen - - + + - + Left button pressed - + Right button pressed - + Select button pressed @@ -34,10 +34,10 @@ recommend - - - - + + + + @@ -52,12 +52,12 @@ 2 uniqueId - + - + - + An app for an Awtrix Light device @@ -72,50 +72,50 @@ - - + + Left button pressed - + Right button pressed - + Select button pressed - + - - - - - + + + + + - + - - - + + + - + - - - + + + 2 appid - + @@ -130,9 +130,9 @@ - + Switch - + Let the clock set brightness automatically based on internal brightness sensor readings screen @@ -158,9 +158,6 @@ RSSI value of WiFi signal qualityofservice - - Measurement - @@ -180,9 +177,6 @@ App currently shown on screen screen - - Status - @@ -195,19 +189,16 @@ ]]> screen - - Status - - + Color - + Color to display text and charts on the screen colorpicker Control - ColorTemperature + Color @@ -216,9 +207,6 @@ Resets the app and the linked items to the default settings switch - - Control - @@ -228,14 +216,14 @@ colorpicker Control - ColorTemperature + Color - + Color - Color text as gradient from Color to Gradient Color + Color text as gradient from Main Color to Gradient Color colorpicker Control @@ -259,9 +247,6 @@ Effect shown in the background of the app screen - - Control - @@ -287,7 +272,7 @@ - + String Changes the color scheme of the effect settings @@ -310,24 +295,18 @@ - + Switch Smoother effect animations switch - - Control - - + Number:Dimensionless Playback speed of background animations time - - Control - @@ -335,20 +314,13 @@ Text displayed in the app text - - Control - Duration - - + Number:Dimensionless Change case of displayed text. Default uses the global preset. text - - Control - @@ -358,27 +330,21 @@ - + Number:Dimensionless Offset on x-axis in pixel for displayed text text - - Control - - + Switch Draws the text on the top of the display text - - Control - - + Number:Time Blink text in specified time interval @@ -390,7 +356,7 @@ - + Number:Time Fade text in specified interval @@ -407,9 +373,6 @@ Fades the text color in rainbow colors text - - Control - @@ -417,9 +380,6 @@ Short texts will be centered instead of scrolling text - - Control - @@ -427,19 +387,13 @@ Icon ID or filename without extension screen - - Control - - + String Make the icon scroll along with the text screen - - Control - @@ -454,9 +408,6 @@ Draw a line graph (format: "value1,value2,value3", last 16 entries will be displayed, last 11 with icon) qualityofservice - - Control - @@ -470,14 +421,11 @@ - + String Delete the app or mark as stale after lifetime screen - - Control - @@ -491,9 +439,6 @@ Draw a bar graph (format: "value1,value2,value3", last 16 entries will be displayed, last 11 with icon) qualityofservice - - Control - @@ -501,9 +446,6 @@ Automatically scales graphs to fit onto display qualityofservice - - Control - @@ -511,9 +453,6 @@ Overlay effect (overriden by global clock overlay) screen - - Control - @@ -532,13 +471,10 @@ Show progress bar with specified percentage qualityofservice - - Control - - + Color Color of progress bar @@ -549,24 +485,21 @@ - + Color Color of progress bar background colorpicker Control - ColorTemperature + Color - + Number:Dimensionless Speed of text scrolling as percentage of default speed - - Control - @@ -575,9 +508,6 @@ Name of the melody file in the clocks MELODIES folder text - - Point - @@ -585,9 +515,6 @@ Ring Tone Text Transfer Language (RTTTL) compliant sound string text - - Point - diff --git a/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/resources/OH-INF/update/app-updates.xml b/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/resources/OH-INF/update/app-updates.xml deleted file mode 100644 index 5757daac68d86..0000000000000 --- a/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/resources/OH-INF/update/app-updates.xml +++ /dev/null @@ -1,43 +0,0 @@ - - - - - - - mqtt:lifetime - - - - mqtt:lifetimeMode - - - - mqtt:overlay - - - - - - - system:rawbutton - - Left button pressed - - - - system:rawbutton - - Right button pressed - - - - system:rawbutton - - Select button pressed - - - - - diff --git a/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/resources/OH-INF/update/bridge-updates.xml b/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/resources/OH-INF/update/bridge-updates.xml deleted file mode 100644 index 0a3f8b9c8bf4e..0000000000000 --- a/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/resources/OH-INF/update/bridge-updates.xml +++ /dev/null @@ -1,50 +0,0 @@ - - - - - - - mqtt:sound - - - mqtt:rtttl - - - - - system:power - - Switches the display ON or OFF - - - - system:battery-level - - - - system:low-battery - - - - system:rawbutton - - Left button pressed - - - - system:rawbutton - - Right button pressed - - - - system:rawbutton - - Select button pressed - - - - - diff --git a/features/openhab-addons/src/main/resources/footer.xml b/features/openhab-addons/src/main/resources/footer.xml index 42e4a59b78c28..6a1d944130665 100644 --- a/features/openhab-addons/src/main/resources/footer.xml +++ b/features/openhab-addons/src/main/resources/footer.xml @@ -29,6 +29,7 @@ mvn:com.fasterxml.jackson.datatype/jackson-datatype-jdk8/${jackson.version} mvn:org.openhab.osgiify/com.hubspot.immutables.immutables-exceptions/1.9 mvn:org.openhab.addons.bundles/org.openhab.binding.mqtt/${project.version} + mvn:org.openhab.addons.bundles/org.openhab.binding.mqtt.awtrixlight/${project.version} mvn:org.openhab.addons.bundles/org.openhab.binding.mqtt.espmilighthub/${project.version} mvn:org.openhab.addons.bundles/org.openhab.binding.mqtt.fpp/${project.version} mvn:org.openhab.addons.bundles/org.openhab.binding.mqtt.generic/${project.version} From f7b895f23737cc3f09a56e2f8400ccd234950aa8 Mon Sep 17 00:00:00 2001 From: Thomas Lauterbach Date: Thu, 13 Feb 2025 00:40:34 +0100 Subject: [PATCH 04/16] README reflects channel updates Signed-off-by: Thomas Lauterbach --- .../README.md | 107 +++++++++--------- 1 file changed, 53 insertions(+), 54 deletions(-) diff --git a/bundles/org.openhab.binding.mqtt.awtrixlight/README.md b/bundles/org.openhab.binding.mqtt.awtrixlight/README.md index 1a093d0937e44..b76f2de6c4b92 100644 --- a/bundles/org.openhab.binding.mqtt.awtrixlight/README.md +++ b/bundles/org.openhab.binding.mqtt.awtrixlight/README.md @@ -57,62 +57,61 @@ The button events can be used by rules to change the displayed app or perform an ## Channels ### Bridge Channels (`awtrixclock`) - -| Channel | Type | Read/Write | Description | -|------------------|----------------------|------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `app` | String | R | Currently active app: Will show the name of the app that is currently shown on the display. | -| `autoBrightness` | Switch | RW | Automatic brightness control: The clock will adjust the display brightness automatically based on ambient light. | -| `batteryLevel` | Number | R | Battery level: The battery level of the internal battery in percent. | -| `brightness` | Dimmer | RW | Display brightness: The brightness of the display in percent. | -| `buttonLeft` | Trigger | R | Left button press event: Triggered when the left button is pressed/released (Event PRESSED or RELEASED). | -| `buttonRight` | Trigger | R | Right button press event: Triggered when the right button is pressed/released (Event PRESSED or RELEASED). | -| `buttonSelect` | Trigger | R | Select button press event: Triggered when the select button is pressed/released (Event PRESSED or RELEASED). | -| `display` | Switch | RW | Display on/off: Switches the display on or off. The clock will still stay on while the display is off. | -| `humidity` | Number:Dimensionless | R | Relative humidity: Relative humidity in percent. For the Ulanzi clock values are usually very inaccurate. | -| `indicator1` | Switch | RW | Control first indicator LED: Switches the first indicator LED on or off. The color of the LED will be green but can be customised by using thing actions (you can also use blinking/fading effects). | -| `indicator2` | Switch | RW | Control second indicator LED: Switches the second indicator LED on or off.The color of the LED will be green but can be customised by using thing actions (you can also use blinking/fading effects). | -| `indicator3` | Switch | RW | Control third indicator LED: Switches the third indicator LED on or off. The color of the LED will be green but can be customised by using thing actions (you can also use blinking/fading effects). | -| `lowBattery` | Switch | R | Low battery warning: Will be switched ON as soon as the battery level drops below the lowBatteryThreshold set for the bridge. | -| `lux` | Number:Illuminance | R | Ambient light level: Ambient light level in lux as measured by the built-in light sensor. | -| `rssi` | Number:Dimensionless | R | WiFi signal strength (RSSI): WiFi signal strength (RSSI) in dBm. | -| `rtttl` | String | W | Play RTTTL ringtone: Play a ringtone specified in RTTTL format (see https://de.wikipedia.org/wiki/Ring_Tones_Text_Transfer_Language) | -| `screen` | String | R | Screen image: Allows you to mirror the screen image from the clock. The screen image will be updated automatically when the app changes but can be updated manually by sending a RefreshType command to the channel. | -| `sound` | String | W | Play sound file: The sound file must be available on the clock device in the MELODIES folder. Save a file with a valid RTTTL string (e.g. melody.txt) in this folder and play it by sending a String command to the channel with the filename without file extension (e.g. "melody"). | -| `temperature` | Number:Temperature | R | Device temperature: Temperature in °C as measured by the built-in temperature sensor. For the Ulanzi clock values are usually very inaccurate. | +| Channel | Type | Read/Write | Description | +|-------------------|----------------------|------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `app` | String | R | Currently active app: Will show the name of the app that is currently shown on the display. | +| `auto-brightness` | Switch | RW | Automatic brightness control: The clock will adjust the display brightness automatically based on ambient light. | +| `battery-level` | Number | R | Battery level: The battery level of the internal battery in percent. | +| `brightness` | Dimmer | RW | Display brightness: The brightness of the display in percent. | +| `button-left` | Trigger | R | Left button press event: Triggered when the left button is pressed/released (Event PRESSED or RELEASED). | +| `button-right` | Trigger | R | Right button press event: Triggered when the right button is pressed/released (Event PRESSED or RELEASED). | +| `button-select` | Trigger | R | Select button press event: Triggered when the select button is pressed/released (Event PRESSED or RELEASED). | +| `display` | Switch | RW | Display on/off: Switches the display on or off. The clock will still stay on while the display is off. | +| `humidity` | Number:Dimensionless | R | Relative humidity: Relative humidity in percent. For the Ulanzi clock values are usually very inaccurate. | +| `indicator-1` | Switch | RW | Control first indicator LED: Switches the first indicator LED on or off. The color of the LED will be green but can be customised by using thing actions (you can also use blinking/fading effects). | +| `indicator-2` | Switch | RW | Control second indicator LED: Switches the second indicator LED on or off.The color of the LED will be green but can be customised by using thing actions (you can also use blinking/fading effects). | +| `indicator-3` | Switch | RW | Control third indicator LED: Switches the third indicator LED on or off. The color of the LED will be green but can be customised by using thing actions (you can also use blinking/fading effects). | +| `low-battery` | Switch | R | Low battery warning: Will be switched ON as soon as the battery level drops below the lowBatteryThreshold set for the bridge. | +| `lux` | Number:Illuminance | R | Ambient light level: Ambient light level in lux as measured by the built-in light sensor. | +| `rssi` | Number:Dimensionless | R | WiFi signal strength (RSSI): WiFi signal strength (RSSI) in dBm. | +| `rtttl` | String | W | Play RTTTL ringtone: Play a ringtone specified in RTTTL format (see https://de.wikipedia.org/wiki/Ring_Tones_Text_Transfer_Language) | +| `screen` | String | R | Screen image: Allows you to mirror the screen image from the clock. The screen image will be updated automatically when the app changes but can be updated manually by sending a RefreshType command to the channel. | +| `sound` | String | W | Play sound file: The sound file must be available on the clock device in the MELODIES folder. Save a file with a valid RTTTL string (e.g. melody.txt) in this folder and play it by sending a String command to the channel with the filename without file extension (e.g. "melody"). | +| `temperature` | Number:Temperature | R | Device temperature: Temperature in °C as measured by the built-in temperature sensor. For the Ulanzi clock values are usually very inaccurate. | ### App Channels (`awtrixapp`) -| Channel | Type | Read/Write | Description | -|----------------------|----------------------|------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `active` | Switch | W | Enable/disable the app: Switches the app on or off. Note that channels of inactive apps will be reset to their default values during a restart of openHAB. | -| `autoscale` | Switch | RW | Enable/disable autoscaling for bar and linechart. | -| `background` | Color | RW | Sets a background color. | -| `bar` | String | RW | Shows a bar chart: Send a string with values separated by commas (e.g. "value1,value2,value3"). Only the last 16 values will be displayed. | -| `blink` | Number:Time | RW | Blink text: Blink the text in the specified interval. Ignored if gradientColor or rainbow are set. | -| `center` | Switch | RW | Center short text horizontally and disable scrolling. | -| `color` | Color | RW | Text, bar or line chart color. | -| `duration` | Number:Time | RW | Display duration in seconds. | -| `effect` | String | RW | Display effect (see https://blueforcer.github.io/awtrix3/#/effects for possible values). | -| `effectBlend` | Switch | RW | Enable smoother effect transitions. Only to be used with effect. | -| `effectPalette` | String | RW | Color palette for effects (see https://blueforcer.github.io/awtrix3/#/effects for possible values and how to create custom palettes). Only to be used with effect. | -| `effectSpeed` | Number:Dimensionless | RW | Effect animation speed: Higher means faster (see https://blueforcer.github.io/awtrix3/#/effects). Only to be used with effect. | -| `fade` | Number:Time | RW | Fade text: Fades the text in and out in the specified interval. Ignored if gradientColor or rainbow are set. | -| `gradientColor` | Color | RW | Secondary color for gradient effects. Use color for setting the primary color. | -| `icon` | String | RW | Icon name to display: Install icons on the clock device first. | -| `lifetime` | Number:Time | RW | App lifetime: Define how long the app will remain active on the clock. | -| `lifetimeMode` | String | RW | Lifetime mode: Define if the app should be deleted (Command DELETE) or marked as stale (Command STALE) after lifetime. | -| `line` | String | RW | Shows a line chart: Send a string with values separated by commas (e.g. "value1,value2,value3"). Only the last 16 values will be displayed. | -| `overlay` | String | RW | Enable overlay mode: Shows a weather overlay effect (can be any of clear, snow, rain, drizzle, storm, thunder, frost). | -| `progress` | Number:Dimensionless | RW | Progress value: Shows a progress bar at the bottom of the app with the specified percentage value. | -| `progressBackground` | Color | RW | Progress bar background color: Background color for the progress bar. | -| `progressColor` | Color | RW | Progress bar color: Color for the progress bar. | -| `pushIcon` | String | RW | Push icon animation (STATIC=Icon doesn't move, PUSHOUT=Icon moves with text and will not appear again, PUSHOUTRETURN=Icon moves with text but appears again when the text starts to scroll again). | -| `rainbow` | Switch | RW | Enable rainbow effect: Uses a rainbow effect for the displayed text. | -| `reset` | Switch | RW | Reset app to default state: All channels will be reset to their default values. | -| `scrollSpeed` | Number:Dimensionless | RW | Text scrolling speed: Provide as percentage value. The original speed is 100%. Values above 100% will increase the scrolling speed, values below 100% will decrease it. Setting this value to 0 will disable scrolling completely. | -| `text` | String | RW | Text to display. | -| `textCase` | Number:Dimensionless | RW | Set text case (0=normal, 1=uppercase, 2=lowercase). | -| `textOffset` | Number:Dimensionless | RW | Text offset position: Horizontal offset of the text in pixels. | -| `topText` | String | RW | Draws the text on the top of the display. | +| Channel | Type | Read/Write | Description | +|-----------------------|----------------------|------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `active` | Switch | W | Enable/disable the app: Switches the app on or off. Note that channels of inactive apps will be reset to their default values during a restart of openHAB. | +| `autoscale` | Switch | RW | Enable/disable autoscaling for bar and linechart. | +| `background` | Color | RW | Sets a background color. | +| `bar` | String | RW | Shows a bar chart: Send a string with values separated by commas (e.g. "value1,value2,value3"). Only the last 16 values will be displayed. | +| `blink` | Number:Time | RW | Blink text: Blink the text in the specified interval. Ignored if gradientColor or rainbow are set. | +| `center` | Switch | RW | Center short text horizontally and disable scrolling. | +| `color` | Color | RW | Text, bar or line chart color. | +| `duration` | Number:Time | RW | Display duration in seconds. | +| `effect` | String | RW | Display effect (see https://blueforcer.github.io/awtrix3/#/effects for possible values). | +| `effect-blend` | Switch | RW | Enable smoother effect transitions. Only to be used with effect. | +| `effect-palette` | String | RW | Color palette for effects (see https://blueforcer.github.io/awtrix3/#/effects for possible values and how to create custom palettes). Only to be used with effect. | +| `effect-speed` | Number:Dimensionless | RW | Effect animation speed: Higher means faster (see https://blueforcer.github.io/awtrix3/#/effects). Only to be used with effect. | +| `fade` | Number:Time | RW | Fade text: Fades the text in and out in the specified interval. Ignored if gradientColor or rainbow are set. | +| `gradient-color` | Color | RW | Secondary color for gradient effects. Use color for setting the primary color. | +| `icon` | String | RW | Icon name to display: Install icons on the clock device first. | +| `lifetime` | Number:Time | RW | App lifetime: Define how long the app will remain active on the clock. | +| `lifetime-mode` | String | RW | Lifetime mode: Define if the app should be deleted (Command DELETE) or marked as stale (Command STALE) after lifetime. | +| `line` | String | RW | Shows a line chart: Send a string with values separated by commas (e.g. "value1,value2,value3"). Only the last 16 values will be displayed. | +| `overlay` | String | RW | Enable overlay mode: Shows a weather overlay effect (can be any of clear, snow, rain, drizzle, storm, thunder, frost). | +| `progress` | Number:Dimensionless | RW | Progress value: Shows a progress bar at the bottom of the app with the specified percentage value. | +| `progress-background` | Color | RW | Progress bar background color: Background color for the progress bar. | +| `progress-color` | Color | RW | Progress bar color: Color for the progress bar. | +| `push-icon` | String | RW | Push icon animation (STATIC=Icon doesn't move, PUSHOUT=Icon moves with text and will not appear again, PUSHOUTRETURN=Icon moves with text but appears again when the text starts to scroll again). | +| `rainbow` | Switch | RW | Enable rainbow effect: Uses a rainbow effect for the displayed text. | +| `reset` | Switch | RW | Reset app to default state: All channels will be reset to their default values. | +| `scroll-speed` | Number:Dimensionless | RW | Text scrolling speed: Provide as percentage value. The original speed is 100%. Values above 100% will increase the scrolling speed, values below 100% will decrease it. Setting this value to 0 will disable scrolling completely. | +| `text` | String | RW | Text to display. | +| `text-case` | Number:Dimensionless | RW | Set text case (0=normal, 1=uppercase, 2=lowercase). | +| `text-offset` | Number:Dimensionless | RW | Text offset position: Horizontal offset of the text in pixels. | +| `top-text` | String | RW | Draws the text on the top of the display. | ## Full Example From d78e7df121725ad2c3c16d54033d6610cd419a04 Mon Sep 17 00:00:00 2001 From: Thomas Lauterbach Date: Thu, 13 Feb 2025 00:49:51 +0100 Subject: [PATCH 05/16] Constants reflect channel id updates Signed-off-by: Thomas Lauterbach --- .../internal/AwtrixLightBindingConstants.java | 44 +++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/AwtrixLightBindingConstants.java b/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/AwtrixLightBindingConstants.java index de2cb6017c1c4..824d6bd46703f 100644 --- a/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/AwtrixLightBindingConstants.java +++ b/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/AwtrixLightBindingConstants.java @@ -118,20 +118,20 @@ public class AwtrixLightBindingConstants { public static final String[] DEFAULT_APPS = { "Time", "Date", "Temperature", "Humidity", "Battery" }; // Common Channels - public static final String CHANNEL_BUTLEFT = "buttonleft"; - public static final String CHANNEL_BUTRIGHT = "buttonright"; - public static final String CHANNEL_BUTSELECT = "buttonselect"; + public static final String CHANNEL_BUTLEFT = "button-left"; + public static final String CHANNEL_BUTRIGHT = "button-right"; + public static final String CHANNEL_BUTSELECT = "button-select"; // Clock Channels public static final String CHANNEL_APP = "app"; - public static final String CHANNEL_AUTO_BRIGHTNESS = "autoBrightness"; - public static final String CHANNEL_BATTERY = "batterylevel"; + public static final String CHANNEL_AUTO_BRIGHTNESS = "auto-brightness"; + public static final String CHANNEL_BATTERY = "battery-level"; public static final String CHANNEL_BRIGHTNESS = "brightness"; public static final String CHANNEL_DISPLAY = "display"; public static final String CHANNEL_HUMIDITY = "humidity"; - public static final String CHANNEL_INDICATOR1 = "indicator1"; - public static final String CHANNEL_INDICATOR2 = "indicator2"; - public static final String CHANNEL_INDICATOR3 = "indicator3"; + public static final String CHANNEL_INDICATOR1 = "indicator-1"; + public static final String CHANNEL_INDICATOR2 = "indicator-2"; + public static final String CHANNEL_INDICATOR3 = "indicator-3"; public static final String CHANNEL_LOW_BATTERY = "low-battery"; public static final String CHANNEL_LUX = "lux"; public static final String CHANNEL_RSSI = "rssi"; @@ -145,33 +145,33 @@ public class AwtrixLightBindingConstants { public static final String CHANNEL_AUTOSCALE = "autoscale"; public static final String CHANNEL_BACKGROUND = "background"; public static final String CHANNEL_BAR = "bar"; - public static final String CHANNEL_BLINK_TEXT = "blinkText"; + public static final String CHANNEL_BLINK_TEXT = "blink-text"; public static final String CHANNEL_CENTER = "center"; public static final String CHANNEL_COLOR = "color"; public static final String CHANNEL_DURATION = "duration"; public static final String CHANNEL_EFFECT = "effect"; - public static final String CHANNEL_EFFECT_BLEND = "effectBlend"; - public static final String CHANNEL_EFFECT_PALETTE = "effectPalette"; - public static final String CHANNEL_EFFECT_SPEED = "effectSpeed"; - public static final String CHANNEL_FADE_TEXT = "fadeText"; - public static final String CHANNEL_GRADIENT_COLOR = "gradientColor"; + public static final String CHANNEL_EFFECT_BLEND = "effect-blend"; + public static final String CHANNEL_EFFECT_PALETTE = "effect-palette"; + public static final String CHANNEL_EFFECT_SPEED = "effect-speed"; + public static final String CHANNEL_FADE_TEXT = "fade-text"; + public static final String CHANNEL_GRADIENT_COLOR = "gradient-color"; public static final String CHANNEL_ICON = "icon"; public static final String CHANNEL_LIFETIME = "lifetime"; - public static final String CHANNEL_LIFETIME_MODE = "lifetimeMode"; + public static final String CHANNEL_LIFETIME_MODE = "lifetime-mode"; public static final String CHANNEL_LINE = "line"; public static final String CHANNEL_OVERLAY = "overlay"; public static final String CHANNEL_PROGRESS = "progress"; - public static final String CHANNEL_PROGRESSC = "progressColor"; - public static final String CHANNEL_PROGRESSBC = "progressBackground"; - public static final String CHANNEL_PUSH_ICON = "pushIcon"; + public static final String CHANNEL_PROGRESSC = "progress-color"; + public static final String CHANNEL_PROGRESSBC = "progress-background"; + public static final String CHANNEL_PUSH_ICON = "push-icon"; public static final String CHANNEL_RAINBOW = "rainbow"; public static final String CHANNEL_REPEAT = "repeat"; public static final String CHANNEL_RESET = "reset"; - public static final String CHANNEL_SCROLLSPEED = "scrollSpeed"; + public static final String CHANNEL_SCROLLSPEED = "scroll-speed"; public static final String CHANNEL_TEXT = "text"; - public static final String CHANNEL_TEXTCASE = "textCase"; - public static final String CHANNEL_TEXT_OFFSET = "textOffset"; - public static final String CHANNEL_TOP_TEXT = "topText"; + public static final String CHANNEL_TEXTCASE = "text-case"; + public static final String CHANNEL_TEXT_OFFSET = "text-offset"; + public static final String CHANNEL_TOP_TEXT = "top-text"; public static final String PUSH_ICON_OPTION_0 = "STATIC"; public static final String PUSH_ICON_OPTION_1 = "PUSHOUT"; From b822c4aef42fc88048047389d6ef36b7b463c2e4 Mon Sep 17 00:00:00 2001 From: Thomas Lauterbach Date: Fri, 14 Feb 2025 16:34:22 +0100 Subject: [PATCH 06/16] Fixes blinkText init and improves README Signed-off-by: Thomas Lauterbach --- .../README.md | 227 +++++++++++++++--- .../handler/AwtrixLightAppHandler.java | 6 +- 2 files changed, 197 insertions(+), 36 deletions(-) diff --git a/bundles/org.openhab.binding.mqtt.awtrixlight/README.md b/bundles/org.openhab.binding.mqtt.awtrixlight/README.md index b76f2de6c4b92..c6df40dcff5c4 100644 --- a/bundles/org.openhab.binding.mqtt.awtrixlight/README.md +++ b/bundles/org.openhab.binding.mqtt.awtrixlight/README.md @@ -80,38 +80,199 @@ The button events can be used by rules to change the displayed app or perform an | `temperature` | Number:Temperature | R | Device temperature: Temperature in °C as measured by the built-in temperature sensor. For the Ulanzi clock values are usually very inaccurate. | ### App Channels (`awtrixapp`) -| Channel | Type | Read/Write | Description | -|-----------------------|----------------------|------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `active` | Switch | W | Enable/disable the app: Switches the app on or off. Note that channels of inactive apps will be reset to their default values during a restart of openHAB. | -| `autoscale` | Switch | RW | Enable/disable autoscaling for bar and linechart. | -| `background` | Color | RW | Sets a background color. | -| `bar` | String | RW | Shows a bar chart: Send a string with values separated by commas (e.g. "value1,value2,value3"). Only the last 16 values will be displayed. | -| `blink` | Number:Time | RW | Blink text: Blink the text in the specified interval. Ignored if gradientColor or rainbow are set. | -| `center` | Switch | RW | Center short text horizontally and disable scrolling. | -| `color` | Color | RW | Text, bar or line chart color. | -| `duration` | Number:Time | RW | Display duration in seconds. | -| `effect` | String | RW | Display effect (see https://blueforcer.github.io/awtrix3/#/effects for possible values). | -| `effect-blend` | Switch | RW | Enable smoother effect transitions. Only to be used with effect. | -| `effect-palette` | String | RW | Color palette for effects (see https://blueforcer.github.io/awtrix3/#/effects for possible values and how to create custom palettes). Only to be used with effect. | -| `effect-speed` | Number:Dimensionless | RW | Effect animation speed: Higher means faster (see https://blueforcer.github.io/awtrix3/#/effects). Only to be used with effect. | -| `fade` | Number:Time | RW | Fade text: Fades the text in and out in the specified interval. Ignored if gradientColor or rainbow are set. | -| `gradient-color` | Color | RW | Secondary color for gradient effects. Use color for setting the primary color. | -| `icon` | String | RW | Icon name to display: Install icons on the clock device first. | -| `lifetime` | Number:Time | RW | App lifetime: Define how long the app will remain active on the clock. | -| `lifetime-mode` | String | RW | Lifetime mode: Define if the app should be deleted (Command DELETE) or marked as stale (Command STALE) after lifetime. | -| `line` | String | RW | Shows a line chart: Send a string with values separated by commas (e.g. "value1,value2,value3"). Only the last 16 values will be displayed. | -| `overlay` | String | RW | Enable overlay mode: Shows a weather overlay effect (can be any of clear, snow, rain, drizzle, storm, thunder, frost). | -| `progress` | Number:Dimensionless | RW | Progress value: Shows a progress bar at the bottom of the app with the specified percentage value. | -| `progress-background` | Color | RW | Progress bar background color: Background color for the progress bar. | -| `progress-color` | Color | RW | Progress bar color: Color for the progress bar. | -| `push-icon` | String | RW | Push icon animation (STATIC=Icon doesn't move, PUSHOUT=Icon moves with text and will not appear again, PUSHOUTRETURN=Icon moves with text but appears again when the text starts to scroll again). | -| `rainbow` | Switch | RW | Enable rainbow effect: Uses a rainbow effect for the displayed text. | -| `reset` | Switch | RW | Reset app to default state: All channels will be reset to their default values. | -| `scroll-speed` | Number:Dimensionless | RW | Text scrolling speed: Provide as percentage value. The original speed is 100%. Values above 100% will increase the scrolling speed, values below 100% will decrease it. Setting this value to 0 will disable scrolling completely. | -| `text` | String | RW | Text to display. | -| `text-case` | Number:Dimensionless | RW | Set text case (0=normal, 1=uppercase, 2=lowercase). | -| `text-offset` | Number:Dimensionless | RW | Text offset position: Horizontal offset of the text in pixels. | -| `top-text` | String | RW | Draws the text on the top of the display. | + +| Channel | Action parameter (see Actions) | Action parameter type | Type | Read/Write | Description | | +|-----------------------|------------------------------------------|------------------------------------------------|----------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---| +| `active` | - | Switch | W | Enable/disable the app: Switches the app on or off. Note that channels of inactive apps will be reset to their default values during a restart of openHAB. | | | +| `autoscale` | `autoscale` | boolean | Switch | RW | Enable/disable autoscaling for bar and linechart. | | +| `background` | `background` | int[] (rgb-Array) | Color | RW | Sets a background color. | | +| `bar` | `bar` | String | String | RW | Shows a bar chart: Send a string with values separated by commas (e.g. "value1,value2,value3"). Only the last 16 values will be displayed. | | +| `blink` | `blinkText` | BigDecimal (in milliseconds) | Number:Time | RW | Blink text: Blink the text in the specified interval. Ignored if gradientColor or rainbow are set. | | +| `center` | `center` | boolean | Switch | RW | Center short text horizontally and disable scrolling. | | +| `color` | `color` | BigDecimal[] (rgb-Array) | Color | RW | Text, bar or line chart color. | | +| `duration` | `duration` | BigDecimal | Number:Time | RW | Display duration in seconds. | | +| `effect` | `effectSettings` | Map<String, Object> | String | RW | Display effect (see https://blueforcer.github.io/awtrix3/#/effects for possible values). | | +| `effect-blend` | `blend` as key in `effectSettings` | boolean | Switch | RW | Enable smoother effect transitions. Only to be used with effect. | | +| `effect-palette` | `palette` as key in `effectSettings` Map | String ("None" for default) | String | RW | Color palette for effects (see https://blueforcer.github.io/awtrix3/#/effects for possible values and how to create custom palettes). Only to be used with effect. | | +| `effect-speed` | `speed` as key in `effectSettings` Map | BigDecimal | Number:Dimensionless | RW | Effect animation speed: Higher means faster (see https://blueforcer.github.io/awtrix3/#/effects). Only to be used with effect. | | +| `fade` | `fadeText` | BigDecimal (in milliseconds) | Number:Time | RW | Fade text: Fades the text in and out in the specified interval. Ignored if gradientColor or rainbow are set. | | +| `gradient-color` | `gradient` | BigDecimal[] (rgb-Array) | Color | RW | Secondary color for gradient effects. Use color for setting the primary color. | | +| `icon` | `icon` | String | String | RW | Icon name to display: Install icons on the clock device first. | | +| `lifetime` | `lifetime` | BigDecimal | Number:Time | RW | App lifetime: Define how long the app will remain active on the clock. | | +| `lifetime-mode` | `lifetimeMode` | BigDecimal (0=DELETE, 1=STALE) | String | RW | Lifetime mode: Define if the app should be deleted (Command DELETE) or marked as stale (Command STALE) after lifetime. | | +| `line` | `line` | String | String | RW | Shows a line chart: Send a string with values separated by commas (e.g. "value1,value2,value3"). Only the last 16 values will be displayed. | | +| `overlay` | `overlay` | String | String | RW | Enable overlay mode: Shows a weather overlay effect (can be any of clear, snow, rain, drizzle, storm, thunder, frost). | | +| `progress` | `progress` | String | Number:Dimensionless | RW | Progress value: Shows a progress bar at the bottom of the app with the specified percentage value. | | +| `progress-background` | `progressBC` | BigDecimal[] (rgb-Array) | Color | RW | Progress bar background color: Background color for the progress bar. | | +| `progress-color` | `progressC` | BigDecimal[] (rgb-Array) | Color | RW | Progress bar color: Color for the progress bar. | | +| `push-icon` | `pushIcon` | BigDecimal (0=STATIC, 1=PUSHOUT, 2=PUSHRETURN) | String | RW | Push icon animation (STATIC=Icon doesn't move, PUSHOUT=Icon moves with text and will not appear again, PUSHOUTRETURN=Icon moves with text but appears again when the text starts to scroll again). | | +| `rainbow` | `rainbow` | boolean | Switch | RW | Enable rainbow effect: Uses a rainbow effect for the displayed text. | | +| `reset` | - | | Switch | RW | Reset app to default state: All channels will be reset to their default values. | | +| `scroll-speed` | `scrollSpeed` | BigDecimal | Number:Dimensionless | RW | Text scrolling speed: Provide as percentage value. The original speed is 100%. Values above 100% will increase the scrolling speed, values below 100% will decrease it. Setting this value to 0 will disable scrolling completely. | | +| `text` | `text` | String | String | RW | Text to display. | | +| `text-case` | `textCase` | BigDecimal | Number:Dimensionless | RW | Set text case (0=normal, 1=uppercase, 2=lowercase). | | +| `text-offset` | `textOffset` | BigDecimal | Number:Dimensionless | RW | Text offset position: Horizontal offset of the text in pixels. | | +| `top-text` | `topText` | boolean | Switch | RW | Draws the text on the top of the display. | | + +## Actions + +The binding supports various actions that can be used in rules to control the Awtrix display. To use these actions, you need to import them in your rules (see examples below). + +The following actions are supported: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Action NameDescriptionParameter TypeParameter NameParameter Description
blinkIndicatorBlink an indicator LED.intindicatorId1 for top indicator, 2 for center indicator or 3 for bottom indicator.
int[]rgbProvide an array with exactly 3 values for red, green and blue (in the range 0-255).
intblinkTimeInMsProvide the blink time in milliseconds.
fadeIndicatorFade an indicator LED in and out.intindicatorId1 for top indicator, 2 for center indicator or 3 for bottom indicator.
int[]rgbProvide an array with exactly 3 values for red, green and blue (in the range 0-255).
intfadeTimeInMsProvide the fade time in milliseconds.
activateIndicatorActivate an indicator LED.intindicatorId1 for top indicator, 2 for center indicator or 3 for bottom indicator.
int[]rgbProvide an array with exactly 3 values for red, green and blue (in the range 0-255).
deactivateIndicatorDeactivate an indicator LED.intindicatorId1 for top indicator, 2 for center indicator or 3 for bottom indicator.
rebootReboot the device
sleepMake the device sleep for a number of seconds.intsecondsDevice will wake up after the specified number of seconds. Sleep can only be interrupted by a press of the select button.
upgradeUpgrade the device if a firmware update is available.
playSoundPlay a sound file saved on the device.StringmelodyThe sound file name saved in the clocks MELODIES folder (without the file extension).
playRtttlPlay a rtttl sound.StringrtttlThe rtttl string to play.
showNotificationShow a notification.StringmessageThe message to show.
StringiconThe name of the icon saved on the device that is shown with the message.
showCustomNotificationShow a notification with maximal customization options.Map<String, Object>appParamsMap that holds any parameter that is available for an Awtrix App as shown in the App Channels section of the documentation. Please use the values shown in the Action parameter column of the App Channels section as keys.
booleanholdWhether the notification should stay on the screen until the user presses the select button.
booleanwakeUpWhether the notification should wake up the device if the display is currently switched off.
booleanstackWhether the notification should be stacked on top of the previous notification or replace the currently active notification.
StringrtttlPlay the specified rtttl sound when displaying the notification.
StringrtttlPlay the specified rtttl ringtone when displaying the notification. Set to null for no sound or when the sound parameter is set.
StringsoundPlay the specified sound file when displaying the notification. Set to null for no sound or when the rtttl parameter is set.
booleanloopSoundWhether the sound should be played in a loop until the notification is dismissed.
## Full Example @@ -219,7 +380,7 @@ sitemap awtrix label="Awtrix Display" { ``` -## Actions +### Actions The binding provides various actions that can be used in rules to control the Awtrix display. To use these actions, you need to import them in your rules. diff --git a/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/handler/AwtrixLightAppHandler.java b/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/handler/AwtrixLightAppHandler.java index 2eb375527a958..42d9d0e3c4b1b 100644 --- a/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/handler/AwtrixLightAppHandler.java +++ b/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/handler/AwtrixLightAppHandler.java @@ -584,6 +584,9 @@ private void initStates() { updateState(new ChannelUID(channelPrefix + CHANNEL_TEXT_OFFSET), new QuantityType<>(this.app.getTextOffset(), Units.ONE)); + updateState(new ChannelUID(channelPrefix + CHANNEL_TEXTCASE), + new QuantityType<>(this.app.getTextCase(), Units.ONE)); + updateState(new ChannelUID(channelPrefix + CHANNEL_TOP_TEXT), this.app.getTopText() ? OnOffType.ON : OnOffType.OFF); @@ -616,9 +619,6 @@ private void initStates() { } updateState(new ChannelUID(channelPrefix + CHANNEL_PUSH_ICON), new StringType(param)); - updateState(new ChannelUID(channelPrefix + CHANNEL_TEXTCASE), - new QuantityType<>(this.app.getBlinkText(), Units.ONE)); - BigDecimal[] background = this.app.getBackground().length == 3 ? this.app.getBackground() : new BigDecimal[] { BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO }; updateState(new ChannelUID(channelPrefix + CHANNEL_BACKGROUND), From a42454418f964ba976c42b0a6b0bec1954bb7315 Mon Sep 17 00:00:00 2001 From: Thomas Lauterbach Date: Mon, 17 Feb 2025 21:36:44 +0100 Subject: [PATCH 07/16] Use primitive types Signed-off-by: Thomas Lauterbach --- .../README.md | 1 + .../internal/AwtrixLightBindingConstants.java | 14 +- .../internal/BridgeConfigOptions.java | 4 +- .../mqtt/awtrixlight/internal/Helper.java | 8 +- .../awtrixlight/internal/app/AwtrixApp.java | 236 ++++++++++-------- .../AwtrixLightBridgeDiscoveryService.java | 5 +- .../AwtrixLightDiscoveryService.java | 5 +- .../handler/AwtrixLightAppHandler.java | 136 ++++------ .../handler/AwtrixLightBridgeHandler.java | 76 +++--- .../resources/OH-INF/thing/thing-types.xml | 26 -- .../mqtt/awtrixlight/internal/HelperTest.java | 123 ++++++--- 11 files changed, 333 insertions(+), 301 deletions(-) diff --git a/bundles/org.openhab.binding.mqtt.awtrixlight/README.md b/bundles/org.openhab.binding.mqtt.awtrixlight/README.md index c6df40dcff5c4..e3748bca3cf24 100644 --- a/bundles/org.openhab.binding.mqtt.awtrixlight/README.md +++ b/bundles/org.openhab.binding.mqtt.awtrixlight/README.md @@ -57,6 +57,7 @@ The button events can be used by rules to change the displayed app or perform an ## Channels ### Bridge Channels (`awtrixclock`) + | Channel | Type | Read/Write | Description | |-------------------|----------------------|------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `app` | String | R | Currently active app: Will show the name of the app that is currently shown on the display. | diff --git a/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/AwtrixLightBindingConstants.java b/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/AwtrixLightBindingConstants.java index 824d6bd46703f..4e75fb8cecd12 100644 --- a/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/AwtrixLightBindingConstants.java +++ b/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/AwtrixLightBindingConstants.java @@ -15,7 +15,6 @@ import static org.openhab.binding.mqtt.MqttBindingConstants.BINDING_ID; -import java.math.BigDecimal; import java.util.Set; import org.eclipse.jdt.annotation.NonNullByDefault; @@ -31,14 +30,10 @@ public class AwtrixLightBindingConstants { // List of all Thing Type UIDs - public static final ThingTypeUID THING_TYPE_APP = new ThingTypeUID(BINDING_ID, "awtrixapp"); - public static final ThingTypeUID THING_TYPE_BRIDGE = new ThingTypeUID(BINDING_ID, "awtrixclock"); + public static final ThingTypeUID THING_TYPE_APP = new ThingTypeUID(BINDING_ID, "awtrix-app"); + public static final ThingTypeUID THING_TYPE_BRIDGE = new ThingTypeUID(BINDING_ID, "awtrix-clock"); public static final Set SUPPORTED_THING_TYPES = Set.of(THING_TYPE_APP, THING_TYPE_BRIDGE); - // Thing Type IDs - public static final String AWTRIX_APP = "awtrixapp"; - public static final String AWTRIX_CLOCK = "awtrixclock"; - // Matrix Size public static final int SCREEN_HEIGHT = 8; public static final int SCREEN_WIDTH = 32; @@ -176,9 +171,4 @@ public class AwtrixLightBindingConstants { public static final String PUSH_ICON_OPTION_0 = "STATIC"; public static final String PUSH_ICON_OPTION_1 = "PUSHOUT"; public static final String PUSH_ICON_OPTION_2 = "PUSHOUTRETURN"; - - // Just some little helpers... - public static final BigDecimal MINUSONE = new BigDecimal(-1); - public static final BigDecimal ONEHUNDRED = new BigDecimal(100); - public static final BigDecimal THOUSAND = new BigDecimal(1000); } diff --git a/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/BridgeConfigOptions.java b/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/BridgeConfigOptions.java index f3d7b2f53e516..7bb764d446bff 100644 --- a/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/BridgeConfigOptions.java +++ b/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/BridgeConfigOptions.java @@ -12,8 +12,6 @@ */ package org.openhab.binding.mqtt.awtrixlight.internal; -import java.math.BigDecimal; - import org.eclipse.jdt.annotation.NonNullByDefault; /** @@ -26,5 +24,5 @@ public class BridgeConfigOptions { public String basetopic = "awtrix"; public int appLockTimeout = 10; public boolean discoverDefaultApps = false; - public BigDecimal lowBatteryThreshold = new BigDecimal(25); + public int lowBatteryThreshold = 25; } diff --git a/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/Helper.java b/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/Helper.java index 4606eb17ad6c8..958a83e50882a 100644 --- a/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/Helper.java +++ b/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/Helper.java @@ -18,7 +18,6 @@ import java.awt.image.BufferedImage; import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.math.BigDecimal; import java.util.Arrays; import java.util.HashMap; import java.util.Map; @@ -33,7 +32,6 @@ import com.google.gson.Gson; import com.google.gson.GsonBuilder; -import com.google.gson.ToNumberPolicy; import com.google.gson.reflect.TypeToken; /** @@ -46,7 +44,7 @@ public class Helper { private static final Logger LOGGER = LoggerFactory.getLogger(Helper.class); - private static final Gson GSON = new GsonBuilder().setObjectToNumberStrategy(ToNumberPolicy.BIG_DECIMAL).create(); + private static final Gson GSON = new GsonBuilder().create(); public static Map decodeStatsJson(String statsJson) { Map stats = GSON.fromJson(statsJson, new TypeToken>() { @@ -110,9 +108,9 @@ public static byte[] decodeImage(String messageJSON) { return bytes == null ? new byte[0] : bytes; } - public static BigDecimal[] leftTrim(BigDecimal[] data, int length) { + public static int[] leftTrim(int[] data, int length) { if (length < data.length) { - BigDecimal[] trimmed = new BigDecimal[length]; + int[] trimmed = new int[length]; for (int i = data.length - length; i < data.length; i++) { trimmed[i - (data.length - length)] = data[i]; } diff --git a/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/app/AwtrixApp.java b/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/app/AwtrixApp.java index 22018e3879007..ce433236f756d 100644 --- a/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/app/AwtrixApp.java +++ b/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/app/AwtrixApp.java @@ -12,9 +12,6 @@ */ package org.openhab.binding.mqtt.awtrixlight.internal.app; -import static org.openhab.binding.mqtt.awtrixlight.internal.AwtrixLightBindingConstants.*; - -import java.math.BigDecimal; import java.util.Arrays; import java.util.HashMap; import java.util.Map; @@ -33,58 +30,58 @@ public class AwtrixApp { public static final String DEFAULT_TEXT = "New Awtrix App"; - public static final BigDecimal DEFAULT_TEXTCASE = BigDecimal.ZERO; + public static final int DEFAULT_TEXTCASE = 0; public static final boolean DEFAULT_TOPTEXT = false; - public static final BigDecimal DEFAULT_TEXTOFFSET = BigDecimal.ZERO; + public static final int DEFAULT_TEXTOFFSET = 0; public static final boolean DEFAULT_CENTER = true; - public static final BigDecimal[] DEFAULT_COLOR = {}; - public static final BigDecimal[] DEFAULT_GRADIENT = {}; - public static final BigDecimal DEFAULT_BLINKTEXT = BigDecimal.ZERO; - public static final BigDecimal DEFAULT_FADETEXT = BigDecimal.ZERO; - public static final BigDecimal[] DEFAULT_BACKGROUND = {}; + public static final int[] DEFAULT_COLOR = {}; + public static final int[][] DEFAULT_GRADIENT = {}; + public static final int DEFAULT_BLINKTEXT = 0; + public static final int DEFAULT_FADETEXT = 0; + public static final int[] DEFAULT_BACKGROUND = {}; public static final boolean DEFAULT_RAINBOW = false; public static final String DEFAULT_ICON = "None"; - public static final BigDecimal DEFAULT_PUSHICON = BigDecimal.ZERO; - public static final BigDecimal DEFAULT_DURATION = new BigDecimal(7); - public static final BigDecimal[] DEFAULT_LINE = {}; - public static final BigDecimal DEFAULT_LIFETIME = BigDecimal.ZERO; - public static final BigDecimal DEFAULT_LIFETIME_MODE = BigDecimal.ZERO; - public static final BigDecimal[] DEFAULT_BAR = {}; + public static final int DEFAULT_PUSHICON = 0; + public static final int DEFAULT_DURATION = 7; + public static final int[] DEFAULT_LINE = {}; + public static final int DEFAULT_LIFETIME = 0; + public static final int DEFAULT_LIFETIME_MODE = 0; + public static final int[] DEFAULT_BAR = {}; public static final boolean DEFAULT_AUTOSCALE = true; public static final String DEFAULT_OVERLAY = "Clear"; - public static final BigDecimal DEFAULT_PROGRESS = MINUSONE; - public static final BigDecimal[] DEFAULT_PROGRESSC = {}; - public static final BigDecimal[] DEFAULT_PROGRESSBC = {}; - public static final BigDecimal DEFAULT_SCROLLSPEED = ONEHUNDRED; + public static final int DEFAULT_PROGRESS = -1; + public static final int[] DEFAULT_PROGRESSC = {}; + public static final int[] DEFAULT_PROGRESSBC = {}; + public static final int DEFAULT_SCROLLSPEED = 100; public static final String DEFAULT_EFFECT = "None"; - public static final BigDecimal DEFAULT_EFFECTSPEED = BigDecimal.ONE; + public static final int DEFAULT_EFFECTSPEED = 100; public static final String DEFAULT_EFFECTPALETTE = "None"; public static final boolean DEFAULT_EFFECTBLEND = true; private String text = DEFAULT_TEXT; - private BigDecimal textCase = DEFAULT_TEXTCASE; + private int textCase = DEFAULT_TEXTCASE; private boolean topText = DEFAULT_TOPTEXT; - private BigDecimal textOffset = DEFAULT_TEXTOFFSET; + private int textOffset = DEFAULT_TEXTOFFSET; private boolean center = DEFAULT_CENTER; - private BigDecimal[] color = DEFAULT_COLOR; - private BigDecimal[] gradient = DEFAULT_GRADIENT; - private BigDecimal blinkText = DEFAULT_BLINKTEXT; - private BigDecimal fadeText = DEFAULT_FADETEXT; - private BigDecimal[] background = DEFAULT_BACKGROUND; + private int[] color = DEFAULT_COLOR; + private int[][] gradient = DEFAULT_GRADIENT; + private int blinkText = DEFAULT_BLINKTEXT; + private int fadeText = DEFAULT_FADETEXT; + private int[] background = DEFAULT_BACKGROUND; private boolean rainbow = DEFAULT_RAINBOW; private String icon = DEFAULT_ICON; - private BigDecimal pushIcon = DEFAULT_PUSHICON; - private BigDecimal duration = DEFAULT_DURATION; - private BigDecimal[] line = DEFAULT_LINE; - private BigDecimal lifetime = DEFAULT_LIFETIME; - private BigDecimal lifetimeMode = DEFAULT_LIFETIME_MODE; - private BigDecimal[] bar = DEFAULT_BAR; + private int pushIcon = DEFAULT_PUSHICON; + private int duration = DEFAULT_DURATION; + private int[] line = DEFAULT_LINE; + private int lifetime = DEFAULT_LIFETIME; + private int lifetimeMode = DEFAULT_LIFETIME_MODE; + private int[] bar = DEFAULT_BAR; private boolean autoscale = DEFAULT_AUTOSCALE; private String overlay = DEFAULT_OVERLAY; - private BigDecimal progress = DEFAULT_PROGRESS; - private BigDecimal[] progressC = DEFAULT_PROGRESSC; - private BigDecimal[] progressBC = DEFAULT_PROGRESSBC; - private BigDecimal scrollSpeed = DEFAULT_SCROLLSPEED; + private int progress = DEFAULT_PROGRESS; + private int[] progressC = DEFAULT_PROGRESSC; + private int[] progressBC = DEFAULT_PROGRESSBC; + private int scrollSpeed = DEFAULT_SCROLLSPEED; private String effect = DEFAULT_EFFECT; // effectSettings properties @@ -144,27 +141,27 @@ public void setText(String text) { this.text = text; } - public BigDecimal getTextCase() { + public int getTextCase() { return this.textCase; } - public void setTextCase(BigDecimal textCase) { + public void setTextCase(int textCase) { this.textCase = textCase; } - public Boolean getTopText() { + public boolean getTopText() { return this.topText; } - public void setTopText(Boolean topText) { + public void setTopText(boolean topText) { this.topText = topText; } - public BigDecimal getTextOffset() { + public int getTextOffset() { return this.textOffset; } - public void setTextOffset(BigDecimal textOffset) { + public void setTextOffset(int textOffset) { this.textOffset = textOffset; } @@ -176,43 +173,59 @@ public void setCenter(Boolean center) { this.center = center; } - public BigDecimal[] getColor() { + public int[] getColor() { return this.color; } - public void setColor(BigDecimal[] color) { + public void setColor(int[] color) { this.color = color; + if (this.gradient.length == 2) { + this.gradient[0] = color; + } } - public BigDecimal[] getGradient() { + public int[][] getGradient() { return this.gradient; } - public void setGradient(BigDecimal[] gradient) { - this.gradient = gradient; + public void setGradient(int[][] gradient) { + if (gradient.length != 2) { + this.gradient = DEFAULT_GRADIENT; + } else { + this.gradient = gradient; + this.color = gradient[0]; + } + } + + public void setGradient(int[] gradient) { + if (gradient.length != 3) { + this.gradient = DEFAULT_GRADIENT; + } else { + this.gradient = new int[][] { this.color, gradient }; + } } - public BigDecimal getBlinkText() { + public int getBlinkText() { return this.blinkText; } - public void setBlinkText(BigDecimal blinkText) { + public void setBlinkText(int blinkText) { this.blinkText = blinkText; } - public BigDecimal getFadeText() { + public int getFadeText() { return this.fadeText; } - public void setFadeText(BigDecimal fadeText) { + public void setFadeText(int fadeText) { this.fadeText = fadeText; } - public BigDecimal[] getBackground() { + public int[] getBackground() { return this.background; } - public void setBackground(BigDecimal[] background) { + public void setBackground(int[] background) { this.background = background; } @@ -232,51 +245,51 @@ public void setIcon(String icon) { this.icon = icon; } - public BigDecimal getPushIcon() { + public int getPushIcon() { return this.pushIcon; } - public void setPushIcon(BigDecimal pushIcon) { + public void setPushIcon(int pushIcon) { this.pushIcon = pushIcon; } - public BigDecimal getDuration() { + public int getDuration() { return this.duration; } - public void setDuration(BigDecimal duration) { + public void setDuration(int duration) { this.duration = duration; } - public BigDecimal[] getLine() { + public int[] getLine() { return this.line; } - public void setLine(BigDecimal[] line) { + public void setLine(int[] line) { this.line = line; } - public BigDecimal getLifetime() { + public int getLifetime() { return this.lifetime; } - public void setLifetime(BigDecimal lifetime) { + public void setLifetime(int lifetime) { this.lifetime = lifetime; } - public BigDecimal getLifetimeMode() { + public int getLifetimeMode() { return this.lifetimeMode; } - public void setLifetimeMode(BigDecimal lifetimeMode) { + public void setLifetimeMode(int lifetimeMode) { this.lifetimeMode = lifetimeMode; } - public BigDecimal[] getBar() { + public int[] getBar() { return this.bar; } - public void setBar(BigDecimal[] bar) { + public void setBar(int[] bar) { this.bar = bar; } @@ -296,39 +309,35 @@ public void setOverlay(String overlay) { this.overlay = overlay; } - public BigDecimal getProgress() { + public int getProgress() { return this.progress; } - public void setProgress(BigDecimal progress) { + public void setProgress(int progress) { this.progress = progress; } - public BigDecimal[] getProgressC() { - return this.progressC; - } - - public BigDecimal[] setProgressC() { + public int[] getProgressC() { return this.progressC; } - public void setProgressC(BigDecimal[] progressC) { + public void setProgressC(int[] progressC) { this.progressC = progressC; } - public BigDecimal[] getProgressBC() { + public int[] getProgressBC() { return this.progressBC; } - public void setProgressBC(BigDecimal[] progressBC) { + public void setProgressBC(int[] progressBC) { this.progressBC = progressBC; } - public BigDecimal getScrollSpeed() { + public int getScrollSpeed() { return this.scrollSpeed; } - public void setScrollSpeed(BigDecimal scrollSpeed) { + public void setScrollSpeed(int scrollSpeed) { this.scrollSpeed = scrollSpeed; } @@ -382,7 +391,7 @@ public Map getAppParams() { fields.put("duration", this.duration); fields.putAll(getGraphConfig()); fields.putAll(getProgressConfig()); - if (this.scrollSpeed.compareTo(BigDecimal.ZERO) == 0) { + if (this.scrollSpeed == 0) { fields.put("noScroll", true); } else { fields.put("scrollSpeed", this.scrollSpeed); @@ -402,36 +411,42 @@ private boolean getBoolValue(Map params, String key, boolean def return defaultValue; } - private BigDecimal getNumberValue(Map params, String key, BigDecimal defaultValue) { + private int getNumberValue(Map params, String key, int defaultValue) { if (params.containsKey(key)) { @Nullable Object value = params.get(key); - if (value instanceof BigDecimal) { - return (BigDecimal) value; + if (value instanceof Integer intValue) { + return intValue; } } return defaultValue; } - private BigDecimal[] getNumberArrayValue(Map params, String key, BigDecimal[] defaultValue) { + private int[] getNumberArrayValue(Map params, String key, int[] defaultValue) { if (params.containsKey(key)) { @Nullable Object value = params.get(key); - if (value instanceof BigDecimal[]) { - return (BigDecimal[]) value; + if (value instanceof int[] intArray) { + return intArray; } } return defaultValue; } - private BigDecimal[] getGradientValue(Map params, BigDecimal[] defaultValue) { + private int[][] getGradientValue(Map params, int[][] defaultValue) { if (params.containsKey("gradient")) { @Nullable - Object value = params.get("gradient"); - if (value instanceof BigDecimal[][] && ((BigDecimal[][]) value).length == 2) { - BigDecimal[] gradientColor = ((BigDecimal[][]) value)[1]; - if (gradientColor.length == 3) { - return gradientColor; + Object gradientParam = params.get("gradient"); + // Check if we got a complete gradient with two colors + if (gradientParam instanceof int[][] gradient) { + return gradient; + } + // Check if we got a single color for the gradient + if (gradientParam instanceof int[] gradient) { + @Nullable + Object colorParam = params.get("color"); + if (colorParam instanceof int[] color) { + return new int[][] { color, gradient }; } } } @@ -480,16 +495,25 @@ private String getStringValue(Map params, String key, String def private Map getColorConfig() { Map fields = new HashMap(); - if (this.gradient.length != 3) { + // When we don't have a valid gradient array, we just provide a color if available + if (this.gradient.length != 2) { if (this.color.length == 3) { fields.put("color", this.color); } } else { - if (this.color.length == 3) { - BigDecimal[][] gradientColors = { this.color, this.gradient }; - fields.put("gradient", gradientColors); + // Here we have a gradient array. Use it unless it's not a valid gradient + if (this.gradient[0] != null && this.gradient[0].length == 3 && this.gradient[1] != null + && this.gradient[1].length == 3) { + fields.put("gradient", this.gradient); } else { - fields.put("color", this.gradient); + // If we don't have a valid gradient, we try to provide any color we find + if (this.color.length == 3) { + fields.put("color", this.color); + } else if (this.gradient[0] != null && this.gradient[0].length == 3) { + fields.put("color", this.gradient); + } else if (this.gradient[1] != null && this.gradient[1].length == 3) { + fields.put("color", this.gradient); + } } } return fields; @@ -498,9 +522,9 @@ private Map getColorConfig() { private Map getTextEffectConfig() { Map fields = new HashMap(); if (this.color.length == 0 || this.gradient.length == 0) { - if (this.blinkText.compareTo(BigDecimal.ZERO) > 0) { + if (this.blinkText > 0) { fields.put("blinkText", this.blinkText); - } else if (this.fadeText.compareTo(BigDecimal.ZERO) > 0) { + } else if (this.fadeText > 0) { fields.put("fadeText", this.fadeText); } else if (this.rainbow) { fields.put("rainbow", this.rainbow); @@ -529,7 +553,7 @@ private Map getIconConfig() { private Map getGraphConfig() { Map fields = new HashMap(); String graphType = null; - BigDecimal[] data = null; + int[] data = null; if (this.bar.length > 0) { graphType = "bar"; if ("None".equals(this.icon)) { @@ -554,7 +578,7 @@ private Map getGraphConfig() { private Map getProgressConfig() { Map fields = new HashMap(); - if (progress.compareTo(MINUSONE) > 0 && progress.compareTo(ONEHUNDRED) <= 0) { + if (progress > -1 && progress <= 100) { fields.put("progress", this.progress); if (this.progressC.length == 3) { fields.put("progressC", this.progressC); @@ -571,7 +595,7 @@ private Map getEffectConfig() { Map effectSettings = new HashMap(); fields.put("effect", this.effect); if (!"None".equals(this.effect)) { - if (getEffectSpeed().compareTo(MINUSONE) > 0) { + if (getEffectSpeed() > -1) { effectSettings.put("speed", getEffectSpeed()); } effectSettings.put("palette", getEffectPalette()); @@ -581,17 +605,17 @@ private Map getEffectConfig() { return fields; } - public BigDecimal getEffectSpeed() { + public int getEffectSpeed() { @Nullable Object effectSpeed = this.effectSettings.get("speed"); - if (effectSpeed instanceof BigDecimal) { - return (BigDecimal) effectSpeed; + if (effectSpeed instanceof Number numberValue) { + return numberValue.intValue(); } else { return DEFAULT_EFFECTSPEED; } } - public void setEffectSpeed(BigDecimal effectSpeed) { + public void setEffectSpeed(int effectSpeed) { this.effectSettings.put("speed", effectSpeed); } diff --git a/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/discovery/AwtrixLightBridgeDiscoveryService.java b/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/discovery/AwtrixLightBridgeDiscoveryService.java index 12268d2d5c1b1..6137d4d74042c 100644 --- a/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/discovery/AwtrixLightBridgeDiscoveryService.java +++ b/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/discovery/AwtrixLightBridgeDiscoveryService.java @@ -13,7 +13,6 @@ package org.openhab.binding.mqtt.awtrixlight.internal.discovery; -import static org.openhab.binding.mqtt.MqttBindingConstants.BINDING_ID; import static org.openhab.binding.mqtt.awtrixlight.internal.AwtrixLightBindingConstants.*; import java.util.Map; @@ -24,7 +23,6 @@ import org.openhab.binding.mqtt.awtrixlight.internal.handler.AwtrixLightBridgeHandler; import org.openhab.core.config.discovery.AbstractDiscoveryService; import org.openhab.core.config.discovery.DiscoveryResultBuilder; -import org.openhab.core.thing.ThingTypeUID; import org.openhab.core.thing.ThingUID; import org.openhab.core.thing.binding.ThingHandler; import org.openhab.core.thing.binding.ThingHandlerService; @@ -87,8 +85,7 @@ protected void startScan() { void publishApp(ThingUID connectionBridgeUid, String bridgeHardwareId, String basetopic, String appName) { if (!"Notification".equals(appName)) { String appId = bridgeHardwareId + "-" + appName; - thingDiscovered(DiscoveryResultBuilder - .create(new ThingUID(new ThingTypeUID(BINDING_ID, AWTRIX_APP), connectionBridgeUid, appName)) + thingDiscovered(DiscoveryResultBuilder.create(new ThingUID(THING_TYPE_APP, connectionBridgeUid, appName)) .withBridge(connectionBridgeUid).withProperty(PROP_APPID, appId) .withProperty(PROP_APP_CONTROLLABLE, false).withProperty(PROP_APPNAME, appName) .withRepresentationProperty(PROP_APPID).withLabel("Awtrix App " + appName).build()); diff --git a/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/discovery/AwtrixLightDiscoveryService.java b/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/discovery/AwtrixLightDiscoveryService.java index 8914157708149..a1898c96f9ab0 100644 --- a/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/discovery/AwtrixLightDiscoveryService.java +++ b/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/discovery/AwtrixLightDiscoveryService.java @@ -13,7 +13,6 @@ package org.openhab.binding.mqtt.awtrixlight.internal.discovery; -import static org.openhab.binding.mqtt.MqttBindingConstants.BINDING_ID; import static org.openhab.binding.mqtt.awtrixlight.internal.AwtrixLightBindingConstants.*; import java.nio.charset.StandardCharsets; @@ -28,7 +27,6 @@ import org.openhab.core.config.discovery.DiscoveryResultBuilder; import org.openhab.core.config.discovery.DiscoveryService; import org.openhab.core.io.transport.mqtt.MqttBrokerConnection; -import org.openhab.core.thing.ThingTypeUID; import org.openhab.core.thing.ThingUID; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; @@ -104,8 +102,7 @@ protected MQTTTopicDiscoveryService getDiscoveryService() { void publishClock(ThingUID connectionBridgeUid, String baseTopic, String vendor, String firmware, String hardwareUid) { String name = baseTopic.replace(TOPIC_BASE + "/", ""); - thingDiscovered(DiscoveryResultBuilder - .create(new ThingUID(new ThingTypeUID(BINDING_ID, AWTRIX_CLOCK), connectionBridgeUid, hardwareUid)) + thingDiscovered(DiscoveryResultBuilder.create(new ThingUID(THING_TYPE_BRIDGE, connectionBridgeUid, hardwareUid)) .withBridge(connectionBridgeUid).withProperty(PROP_VENDOR, vendor).withProperty(PROP_FIRMWARE, firmware) .withProperty(PROP_UNIQUEID, hardwareUid).withProperty(PROP_BASETOPIC, baseTopic) .withRepresentationProperty(PROP_UNIQUEID).withLabel("Awtrix Clock " + name).build()); diff --git a/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/handler/AwtrixLightAppHandler.java b/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/handler/AwtrixLightAppHandler.java index 42d9d0e3c4b1b..5924094104d83 100644 --- a/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/handler/AwtrixLightAppHandler.java +++ b/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/handler/AwtrixLightAppHandler.java @@ -15,7 +15,6 @@ import static org.openhab.binding.mqtt.awtrixlight.internal.AwtrixLightBindingConstants.*; -import java.math.BigDecimal; import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Map; @@ -23,7 +22,6 @@ import java.util.concurrent.Future; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; @@ -119,24 +117,22 @@ public void handleCommand(ChannelUID channelUID, Command command) { break; case CHANNEL_COLOR: if (command instanceof HSBType) { - int[] hsbToRgb = ColorUtil.hsbToRgb((HSBType) command); - this.app.setColor(convertRgbArray(hsbToRgb)); + this.app.setColor(ColorUtil.hsbToRgb((HSBType) command)); } break; case CHANNEL_GRADIENT_COLOR: if (command instanceof HSBType) { - int[] hsbToRgb = ColorUtil.hsbToRgb((HSBType) command); - this.app.setGradient(convertRgbArray(hsbToRgb)); + this.app.setGradient(ColorUtil.hsbToRgb((HSBType) command)); } break; case CHANNEL_SCROLLSPEED: if (command instanceof QuantityType) { - this.app.setScrollSpeed(((QuantityType) command).toBigDecimal()); + this.app.setScrollSpeed(((QuantityType) command).intValue()); } break; case CHANNEL_DURATION: if (command instanceof QuantityType) { - this.app.setDuration(((QuantityType) command).toBigDecimal()); + this.app.setDuration(((QuantityType) command).intValue()); } break; case CHANNEL_EFFECT: @@ -146,7 +142,7 @@ public void handleCommand(ChannelUID channelUID, Command command) { break; case CHANNEL_EFFECT_SPEED: if (command instanceof QuantityType) { - this.app.setEffectSpeed(((QuantityType) command).toBigDecimal()); + this.app.setEffectSpeed(((QuantityType) command).intValue()); } break; case CHANNEL_EFFECT_PALETTE: @@ -166,7 +162,7 @@ public void handleCommand(ChannelUID channelUID, Command command) { break; case CHANNEL_TEXT_OFFSET: if (command instanceof QuantityType) { - this.app.setTextOffset(((QuantityType) command).toBigDecimal()); + this.app.setTextOffset(((QuantityType) command).intValue()); } break; case CHANNEL_TOP_TEXT: @@ -176,7 +172,7 @@ public void handleCommand(ChannelUID channelUID, Command command) { break; case CHANNEL_TEXTCASE: if (command instanceof QuantityType) { - this.app.setTextCase(((QuantityType) command).toBigDecimal()); + this.app.setTextCase(((QuantityType) command).intValue()); } break; case CHANNEL_CENTER: @@ -188,7 +184,7 @@ public void handleCommand(ChannelUID channelUID, Command command) { if (command instanceof QuantityType) { QuantityType blinkInS = ((QuantityType) command).toUnit(Units.SECOND); if (blinkInS != null) { - BigDecimal blinkInMs = blinkInS.toBigDecimal().multiply(THOUSAND); + int blinkInMs = blinkInS.intValue() * 1000; this.app.setBlinkText(blinkInMs); } } @@ -197,7 +193,7 @@ public void handleCommand(ChannelUID channelUID, Command command) { if (command instanceof QuantityType) { QuantityType fadeInS = ((QuantityType) command).toUnit(Units.SECOND); if (fadeInS != null) { - BigDecimal fadeInMs = fadeInS.toBigDecimal().multiply(THOUSAND); + int fadeInMs = fadeInS.intValue() * 1000; this.app.setFadeText(fadeInMs); } } @@ -216,32 +212,29 @@ public void handleCommand(ChannelUID channelUID, Command command) { if (command instanceof StringType) { switch (((StringType) command).toString()) { case PUSH_ICON_OPTION_0: - this.app.setPushIcon(new BigDecimal(0)); + this.app.setPushIcon(0); break; case PUSH_ICON_OPTION_1: - this.app.setPushIcon(new BigDecimal(1)); + this.app.setPushIcon(1); break; case PUSH_ICON_OPTION_2: - this.app.setPushIcon(new BigDecimal(2)); + this.app.setPushIcon(2); break; } } break; case CHANNEL_BACKGROUND: if (command instanceof HSBType) { - int[] hsbToRgb = ColorUtil.hsbToRgb((HSBType) command); - this.app.setBackground(convertRgbArray(hsbToRgb)); + int[] rgb = ColorUtil.hsbToRgb((HSBType) command); + this.app.setBackground(rgb); } break; case CHANNEL_LINE: if (command instanceof StringType) { try { String[] points = command.toString().split(","); - BigDecimal[] pointsAsNumber = new BigDecimal[points.length]; - for (int i = 0; i < points.length; i++) { - pointsAsNumber[i] = new BigDecimal(points[i]); - } - this.app.setLine(pointsAsNumber); + int[] pointsAsInt = Arrays.stream(points).mapToInt(Integer::parseInt).toArray(); + this.app.setLine(pointsAsInt); } catch (Exception e) { logger.warn("Command {} cannot be parsed as line graph. Format should be: 1,2,3,4,5", command.toString()); @@ -250,17 +243,17 @@ public void handleCommand(ChannelUID channelUID, Command command) { break; case CHANNEL_LIFETIME: if (command instanceof QuantityType) { - this.app.setLifetime(((QuantityType) command).toBigDecimal()); + this.app.setLifetime(((QuantityType) command).intValue()); } break; case CHANNEL_LIFETIME_MODE: if (command instanceof StringType) { switch (command.toString()) { case "DELETE": - this.app.setLifetimeMode(new BigDecimal(0)); + this.app.setLifetimeMode(0); break; case "STALE": - this.app.setLifetimeMode(new BigDecimal(1)); + this.app.setLifetimeMode(1); break; } } @@ -269,11 +262,8 @@ public void handleCommand(ChannelUID channelUID, Command command) { if (command instanceof StringType) { try { String[] points = command.toString().split(","); - BigDecimal[] pointsAsNumber = new BigDecimal[points.length]; - for (int i = 0; i < points.length; i++) { - pointsAsNumber[i] = new BigDecimal(points[i]); - } - this.app.setBar(pointsAsNumber); + int[] pointsAsInt = Arrays.stream(points).mapToInt(Integer::parseInt).toArray(); + this.app.setBar(pointsAsInt); } catch (Exception e) { logger.warn("Command {} cannot be parsed as bar graph. Format should be: 1,2,3,4,5", command.toString()); @@ -292,19 +282,19 @@ public void handleCommand(ChannelUID channelUID, Command command) { break; case CHANNEL_PROGRESS: if (command instanceof QuantityType) { - this.app.setProgress(((QuantityType) command).toBigDecimal()); + this.app.setProgress(((QuantityType) command).intValue()); } break; case CHANNEL_PROGRESSC: if (command instanceof HSBType) { - int[] hsbToRgb = ColorUtil.hsbToRgb((HSBType) command); - this.app.setProgressC(convertRgbArray(hsbToRgb)); + int[] rgb = ColorUtil.hsbToRgb((HSBType) command); + this.app.setProgressC(rgb); } break; case CHANNEL_PROGRESSBC: if (command instanceof HSBType) { - int[] hsbToRgb = ColorUtil.hsbToRgb((HSBType) command); - this.app.setProgressBC(convertRgbArray(hsbToRgb)); + int[] rgb = ColorUtil.hsbToRgb((HSBType) command); + this.app.setProgressBC(rgb); } break; } @@ -518,15 +508,6 @@ void handleSelectButton(String event) { triggerChannel(new ChannelUID(channelPrefix + CHANNEL_BUTSELECT), event); } - private BigDecimal[] convertRgbArray(int[] rgbIn) { - if (rgbIn.length == 3) { - BigDecimal[] rgb = Arrays.stream(rgbIn).mapToObj(BigDecimal::new).toArray(BigDecimal[]::new); - return rgb; - } else { - return new BigDecimal[0]; - } - } - private void deleteApp() { Bridge bridge = getBridge(); if (bridge != null) { @@ -552,15 +533,13 @@ private void updateApp() { private void initStates() { updateState(new ChannelUID(channelPrefix + CHANNEL_ACTIVE), this.active ? OnOffType.ON : OnOffType.OFF); - BigDecimal[] color = this.app.getColor().length == 3 ? this.app.getColor() - : new BigDecimal[] { BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO }; - updateState(new ChannelUID(channelPrefix + CHANNEL_COLOR), - HSBType.fromRGB(color[0].intValue(), color[1].intValue(), color[2].intValue())); + int[] color = this.app.getColor().length == 3 ? this.app.getColor() : new int[] { 0, 0, 0 }; + updateState(new ChannelUID(channelPrefix + CHANNEL_COLOR), HSBType.fromRGB(color[0], color[1], color[2])); - BigDecimal[] gradient = this.app.getGradient().length == 3 ? this.app.getGradient() - : new BigDecimal[] { BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO }; + int[] gradient = this.app.getGradient().length == 2 && this.app.getGradient()[1] != null + && this.app.getGradient()[1].length == 3 ? this.app.getGradient()[1] : new int[] { 0, 0, 0 }; updateState(new ChannelUID(channelPrefix + CHANNEL_GRADIENT_COLOR), - HSBType.fromRGB(gradient[0].intValue(), gradient[1].intValue(), gradient[2].intValue())); + HSBType.fromRGB(gradient[0], gradient[1], gradient[2])); updateState(new ChannelUID(channelPrefix + CHANNEL_SCROLLSPEED), new QuantityType<>(this.app.getScrollSpeed(), Units.PERCENT)); @@ -593,69 +572,60 @@ private void initStates() { updateState(new ChannelUID(channelPrefix + CHANNEL_CENTER), this.app.getCenter() ? OnOffType.ON : OnOffType.OFF); - BigDecimal blinkTextInSeconds = this.app.getBlinkText().divide(THOUSAND); - if (blinkTextInSeconds != null) { - updateState(new ChannelUID(channelPrefix + CHANNEL_BLINK_TEXT), - new QuantityType<>(blinkTextInSeconds, Units.SECOND)); - } + int blinkTextInSeconds = Math.round(this.app.getBlinkText() / 1000); + updateState(new ChannelUID(channelPrefix + CHANNEL_BLINK_TEXT), + new QuantityType<>(blinkTextInSeconds, Units.SECOND)); + + int fadeTextInSeconds = Math.round(this.app.getFadeText() / 1000); + updateState(new ChannelUID(channelPrefix + CHANNEL_FADE_TEXT), + new QuantityType<>(fadeTextInSeconds, Units.SECOND)); - BigDecimal fadeTextInSeconds = this.app.getFadeText().divide(THOUSAND); - if (fadeTextInSeconds != null) { - updateState(new ChannelUID(channelPrefix + CHANNEL_FADE_TEXT), - new QuantityType<>(fadeTextInSeconds, Units.SECOND)); - } updateState(new ChannelUID(channelPrefix + CHANNEL_RAINBOW), this.app.getRainbow() ? OnOffType.ON : OnOffType.OFF); updateState(new ChannelUID(channelPrefix + CHANNEL_ICON), new StringType(this.app.getIcon())); String param = ""; - if (this.app.getPushIcon().equals(BigDecimal.ZERO)) { + if (this.app.getPushIcon() == 0) { param = PUSH_ICON_OPTION_0; - } else if (this.app.getPushIcon().equals(BigDecimal.ONE)) { + } else if (this.app.getPushIcon() == 1) { param = PUSH_ICON_OPTION_1; - } else if (this.app.getPushIcon().equals(new BigDecimal(2))) { + } else if (this.app.getPushIcon() == 2) { param = PUSH_ICON_OPTION_2; } updateState(new ChannelUID(channelPrefix + CHANNEL_PUSH_ICON), new StringType(param)); - BigDecimal[] background = this.app.getBackground().length == 3 ? this.app.getBackground() - : new BigDecimal[] { BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO }; + int[] background = this.app.getBackground().length == 3 ? this.app.getBackground() : new int[] { 0, 0, 0 }; updateState(new ChannelUID(channelPrefix + CHANNEL_BACKGROUND), - HSBType.fromRGB(background[0].intValue(), background[1].intValue(), background[2].intValue())); + HSBType.fromRGB(background[0], background[1], background[2])); - BigDecimal[] line = this.app.getLine().length > 0 ? this.app.getLine() : new BigDecimal[] { BigDecimal.ZERO }; - String lineString = Arrays.stream(line).map(BigDecimal::toString).collect(Collectors.joining(",")); - updateState(new ChannelUID(channelPrefix + CHANNEL_LINE), new StringType(lineString)); + String line = this.app.getLine().length > 0 ? Arrays.toString(this.app.getLine()) : ""; + updateState(new ChannelUID(channelPrefix + CHANNEL_LINE), new StringType(line)); updateState(new ChannelUID(channelPrefix + CHANNEL_LIFETIME), new QuantityType<>(this.app.getLifetime(), Units.SECOND)); - String lifetimeMode = this.app.getLifetimeMode().equals(BigDecimal.ZERO) ? "DELETE" : "STALE"; + String lifetimeMode = this.app.getLifetimeMode() == 0 ? "DELETE" : "STALE"; updateState(new ChannelUID(channelPrefix + CHANNEL_LIFETIME_MODE), new StringType(lifetimeMode)); - BigDecimal[] bar = this.app.getBar().length > 0 ? this.app.getBar() : new BigDecimal[] { BigDecimal.ZERO }; - String barString = Arrays.stream(bar).map(BigDecimal::toString).collect(Collectors.joining(",")); - updateState(new ChannelUID(channelPrefix + CHANNEL_BAR), new StringType(barString)); + String bar = this.app.getBar().length > 0 ? Arrays.toString(this.app.getBar()) : ""; + updateState(new ChannelUID(channelPrefix + CHANNEL_BAR), new StringType(bar)); updateState(new ChannelUID(channelPrefix + CHANNEL_AUTOSCALE), this.app.getAutoscale() ? OnOffType.ON : OnOffType.OFF); updateState(new ChannelUID(channelPrefix + CHANNEL_OVERLAY), new StringType(this.app.getOverlay())); - BigDecimal progress = this.app.getProgress().compareTo(BigDecimal.ZERO) > 0 ? this.app.getProgress() - : BigDecimal.ZERO; + int progress = Math.max(this.app.getProgress(), 0); updateState(new ChannelUID(channelPrefix + CHANNEL_PROGRESS), new QuantityType<>(progress, Units.PERCENT)); - BigDecimal[] progressC = this.app.getProgressC().length == 3 ? this.app.getProgressC() - : new BigDecimal[] { BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO }; + int[] progressC = this.app.getProgressC().length == 3 ? this.app.getProgressC() : new int[] { 0, 0, 0 }; updateState(new ChannelUID(channelPrefix + CHANNEL_PROGRESSC), - HSBType.fromRGB(progressC[0].intValue(), progressC[1].intValue(), progressC[2].intValue())); + HSBType.fromRGB(progressC[0], progressC[1], progressC[2])); - BigDecimal[] progressBC = this.app.getProgressBC().length == 3 ? this.app.getProgressBC() - : new BigDecimal[] { BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO }; + int[] progressBC = this.app.getProgressBC().length == 3 ? this.app.getProgressBC() : new int[] { 0, 0, 0 }; updateState(new ChannelUID(channelPrefix + CHANNEL_PROGRESSBC), - HSBType.fromRGB(progressBC[0].intValue(), progressBC[1].intValue(), progressBC[2].intValue())); + HSBType.fromRGB(progressBC[0], progressBC[1], progressBC[2])); } private void finishInit() { diff --git a/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/handler/AwtrixLightBridgeHandler.java b/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/handler/AwtrixLightBridgeHandler.java index 2922e608d8bf9..d081bd82fcf85 100644 --- a/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/handler/AwtrixLightBridgeHandler.java +++ b/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/handler/AwtrixLightBridgeHandler.java @@ -15,7 +15,6 @@ import static org.openhab.binding.mqtt.awtrixlight.internal.AwtrixLightBindingConstants.*; -import java.math.BigDecimal; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; @@ -82,7 +81,7 @@ public class AwtrixLightBridgeHandler extends BaseBridgeHandler implements MqttM private String channelPrefix = ""; private String currentApp = ""; private boolean discoverDefaultApps = false; - private BigDecimal lowBatteryThreshold = new BigDecimal(25); + private int lowBatteryThreshold = 25; private Map appHandlers = new HashMap(); @@ -547,11 +546,10 @@ private void handleStatsMessage(String statsMessage) { thing.setProperty(PROP_UNIQUEID, (String) value); break; case FIELD_BRIDGE_BATTERY: - if (value instanceof BigDecimal) { + if (value instanceof Number numberVal) { updateState(new ChannelUID(channelPrefix + CHANNEL_BATTERY), - new QuantityType<>((BigDecimal) value, Units.PERCENT)); - OnOffType lowBattery = ((BigDecimal) value).compareTo(this.lowBatteryThreshold) <= 0 - ? OnOffType.ON + new QuantityType<>(numberVal, Units.PERCENT)); + OnOffType lowBattery = numberVal.intValue() < this.lowBatteryThreshold ? OnOffType.ON : OnOffType.OFF; updateState(new ChannelUID(channelPrefix + CHANNEL_LOW_BATTERY), lowBattery); } @@ -563,14 +561,16 @@ private void handleStatsMessage(String statsMessage) { thing.setProperty(PROP_FIRMWARE, value.toString()); break; case FIELD_BRIDGE_TYPE: - if (value instanceof BigDecimal) { - String vendor = ((BigDecimal) value).compareTo(BigDecimal.ZERO) == 0 ? "Ulanzi" : "Generic"; + if (value instanceof Number numberVal) { + String vendor = numberVal.intValue() == 0 ? "Ulanzi" : "Generic"; thing.setProperty(PROP_VENDOR, vendor); - break; } + break; case FIELD_BRIDGE_LUX: - updateState(new ChannelUID(channelPrefix + CHANNEL_LUX), - new QuantityType<>((BigDecimal) value, Units.LUX)); + if (value instanceof Number numberVal) { + updateState(new ChannelUID(channelPrefix + CHANNEL_LUX), + new QuantityType<>(numberVal, Units.LUX)); + } break; case FIELD_BRIDGE_LDR_RAW: // Not mapped to channel atm @@ -579,46 +579,64 @@ private void handleStatsMessage(String statsMessage) { // Not mapped to channel atm break; case FIELD_BRIDGE_MATRIX: - updateState(new ChannelUID(channelPrefix + CHANNEL_DISPLAY), - (boolean) value ? OnOffType.ON : OnOffType.OFF); + if (value instanceof Boolean booleanVal) { + updateState(new ChannelUID(channelPrefix + CHANNEL_DISPLAY), + booleanVal ? OnOffType.ON : OnOffType.OFF); + } break; case FIELD_BRIDGE_BRIGHTNESS: - long brightnessInPercent = Math.round((((BigDecimal) value).doubleValue() / 255) * 100); - updateState(new ChannelUID(channelPrefix + CHANNEL_BRIGHTNESS), - new QuantityType<>(brightnessInPercent, Units.PERCENT)); + if (value instanceof Number numberVal) { + long brightnessInPercent = Math.round((numberVal.doubleValue() / 255) * 100); + updateState(new ChannelUID(channelPrefix + CHANNEL_BRIGHTNESS), + new QuantityType<>(brightnessInPercent, Units.PERCENT)); + } break; case FIELD_BRIDGE_TEMPERATURE: - updateState(new ChannelUID(channelPrefix + CHANNEL_TEMPERATURE), - new QuantityType<>((BigDecimal) value, SIUnits.CELSIUS)); + if (value instanceof Number numberVal) { + updateState(new ChannelUID(channelPrefix + CHANNEL_TEMPERATURE), + new QuantityType<>(numberVal, SIUnits.CELSIUS)); + } break; case FIELD_BRIDGE_HUMIDITY: - updateState(new ChannelUID(channelPrefix + CHANNEL_HUMIDITY), - new QuantityType<>((BigDecimal) value, Units.PERCENT)); + if (value instanceof Number numberVal) { + updateState(new ChannelUID(channelPrefix + CHANNEL_HUMIDITY), + new QuantityType<>(numberVal, Units.PERCENT)); + } break; case FIELD_BRIDGE_UPTIME: // Not mapped to channel atm break; case FIELD_BRIDGE_WIFI_SIGNAL: - updateState(new ChannelUID(channelPrefix + CHANNEL_RSSI), - new QuantityType<>((BigDecimal) value, Units.ONE)); + if (value instanceof Number numberVal) { + updateState(new ChannelUID(channelPrefix + CHANNEL_RSSI), + new QuantityType<>(numberVal, Units.ONE)); + } break; case FIELD_BRIDGE_MESSAGES: // Not mapped to channel atm break; case FIELD_BRIDGE_INDICATOR1: - OnOffType indicator1 = (Boolean) value ? OnOffType.ON : OnOffType.OFF; - updateState(new ChannelUID(channelPrefix + CHANNEL_INDICATOR1), indicator1); + if (value instanceof Boolean booleanVal) { + OnOffType indicator1 = booleanVal ? OnOffType.ON : OnOffType.OFF; + updateState(new ChannelUID(channelPrefix + CHANNEL_INDICATOR1), indicator1); + } break; case FIELD_BRIDGE_INDICATOR2: - OnOffType indicator2 = (Boolean) value ? OnOffType.ON : OnOffType.OFF; - updateState(new ChannelUID(channelPrefix + CHANNEL_INDICATOR2), indicator2); + if (value instanceof Boolean booleanVal) { + OnOffType indicator2 = booleanVal ? OnOffType.ON : OnOffType.OFF; + updateState(new ChannelUID(channelPrefix + CHANNEL_INDICATOR2), indicator2); + } break; case FIELD_BRIDGE_INDICATOR3: - OnOffType indicator3 = (Boolean) value ? OnOffType.ON : OnOffType.OFF; - updateState(new ChannelUID(channelPrefix + CHANNEL_INDICATOR3), indicator3); + if (value instanceof Boolean booleanVal) { + OnOffType indicator3 = booleanVal ? OnOffType.ON : OnOffType.OFF; + updateState(new ChannelUID(channelPrefix + CHANNEL_INDICATOR3), indicator3); + } break; case FIELD_BRIDGE_APP: - updateState(new ChannelUID(channelPrefix + CHANNEL_APP), new StringType((String) value)); + if (value instanceof String stringVal) { + updateState(new ChannelUID(channelPrefix + CHANNEL_APP), new StringType(stringVal)); + } break; } } diff --git a/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/resources/OH-INF/thing/thing-types.xml index 549666c1e1343..c03d128a6203c 100644 --- a/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/resources/OH-INF/thing/thing-types.xml @@ -49,7 +49,6 @@ - 2 uniqueId @@ -112,7 +111,6 @@ - 2 appid @@ -196,10 +194,6 @@ Color to display text and charts on the screen colorpicker - - Control - Color -
@@ -214,10 +208,6 @@ Color for display background colorpicker - - Control - Color - @@ -225,10 +215,6 @@ Color text as gradient from Main Color to Gradient Color colorpicker - - Control - ColorTemperature - @@ -236,10 +222,6 @@ How long the app should be displayed time - - Control - Duration - @@ -479,10 +461,6 @@ Color of progress bar colorpicker - - Control - ColorTemperature - @@ -490,10 +468,6 @@ Color of progress bar background colorpicker - - Control - Color - diff --git a/bundles/org.openhab.binding.mqtt.awtrixlight/src/test/java/org/openhab/binding/mqtt/awtrixlight/internal/HelperTest.java b/bundles/org.openhab.binding.mqtt.awtrixlight/src/test/java/org/openhab/binding/mqtt/awtrixlight/internal/HelperTest.java index ef5a3d515fbe9..32c17448ef327 100644 --- a/bundles/org.openhab.binding.mqtt.awtrixlight/src/test/java/org/openhab/binding/mqtt/awtrixlight/internal/HelperTest.java +++ b/bundles/org.openhab.binding.mqtt.awtrixlight/src/test/java/org/openhab/binding/mqtt/awtrixlight/internal/HelperTest.java @@ -18,7 +18,6 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static org.openhab.binding.mqtt.awtrixlight.internal.AwtrixLightBindingConstants.*; -import java.math.BigDecimal; import java.util.Arrays; import java.util.HashMap; import java.util.Map; @@ -69,7 +68,7 @@ public void convertJson() { } else { Object prop = convertedJson.get(s); assertNotNull(prop); - assertEquals(BigDecimal.class, prop.getClass()); + assertEquals(Double.class, prop.getClass()); } } } @@ -101,43 +100,109 @@ public void convertImage() { Helper.decodeImage(imageMessage); } - @Test - public void mappingRoundTrip() { + private AwtrixApp getTestApp() { AwtrixApp app = new AwtrixApp(); - String appConfig = app.getAppConfig(); + app.setText("Test"); + app.setColor(new int[] { 255, 255, 255 }); + app.setEffect("Radar"); + app.setEffectSpeed(80); + app.setEffectPalette("test"); + app.setScrollSpeed(70); + return app; + } - Map convertJson = Helper.decodeAppJson(appConfig).getAppParams(); - String appConfig2 = Helper.encodeJson(convertJson); - assertEquals(appConfig, appConfig2); + private AwtrixApp getTestAppWithGradient() { + AwtrixApp app = new AwtrixApp(); + app.setText("Test"); + app.setColor(new int[] { 255, 255, 255 }); + app.setGradient(new int[] { 255, 0, 0 }); + return app; + } - app.updateFields(convertJson); - String appConfig3 = app.getAppConfig(); - assertEquals(appConfig, appConfig3); + private AwtrixApp getTestAppWithIncompatibleOptions() { + AwtrixApp app = getTestApp(); + app.setFadeText(100); + // Rainbow is incompatible with fadeText and will be ignored when generating the JSON + app.setRainbow(true); + return app; + } - app.setText("Test"); - // app.setRepeat(new BigDecimal(10)); - BigDecimal[] color = { new BigDecimal(255), new BigDecimal(255), new BigDecimal(255) }; - app.setColor(color); - app.setEffect("Radar"); - app.setEffectSpeed(new BigDecimal(80)); - String appConfig4 = app.getAppConfig(); - assertNotEquals(appConfig, appConfig4); + @Test + public void copyAppViaJson() { + AwtrixApp app = getTestApp(); + String json = app.getAppConfig(); + AwtrixApp app2 = Helper.decodeAppJson(json); + + assertEquals(json, app2.getAppConfig()); + assertEquals(app.toString(), app2.toString()); + } + + @Test + public void copyAppViaParams() { + AwtrixApp app = getTestApp(); + Map appParams = app.getAppParams(); + + AwtrixApp app2 = new AwtrixApp(); + app2.updateFields(appParams); + + assertEquals(app.getAppConfig(), app2.getAppConfig()); + assertEquals(app.toString(), app2.toString()); + } + + @Test + public void copyAppViaJsonWithGradient() { + AwtrixApp app = getTestAppWithGradient(); + String json = app.getAppConfig(); + AwtrixApp app2 = Helper.decodeAppJson(json); + + assertEquals(json, app2.getAppConfig()); + } + + @Test + public void copyAppViaParamsWithGradient() { + AwtrixApp app = getTestAppWithGradient(); + Map appParams = app.getAppParams(); AwtrixApp app2 = new AwtrixApp(); - Map convertJson4 = Helper.decodeAppJson(appConfig4).getAppParams(); - app2.updateFields(convertJson4); - String appConfig5 = app2.getAppConfig(); - assertEquals(appConfig4, appConfig5); - - app.updateFields(convertJson); - String appConfig6 = app.getAppConfig(); - assertEquals(appConfig, appConfig6); + app2.updateFields(appParams); + + assertEquals(app.getAppConfig(), app2.getAppConfig()); + } + + @Test + public void copyAppViaJsonWithIncompatibleOptions() { + AwtrixApp app = getTestAppWithIncompatibleOptions(); + String json = app.getAppConfig(); + AwtrixApp app2 = Helper.decodeAppJson(json); + + // Incompatible options are not copied to the new app + assertNotEquals(app.getRainbow(), app2.getRainbow()); + assertEquals(app.getFadeText(), app2.getFadeText()); + + // But the generated json should still be the same + assertEquals(json, app2.getAppConfig()); + } + + @Test + public void copyAppViaParamsWithIncompatibleOptions() { + AwtrixApp app = getTestAppWithIncompatibleOptions(); + Map appParams = app.getAppParams(); + + AwtrixApp app2 = new AwtrixApp(); + app2.updateFields(appParams); + + // Incompatible options are not copied to the new app + assertNotEquals(app.getRainbow(), app2.getRainbow()); + assertEquals(app.getFadeText(), app2.getFadeText()); + + // But the generated json should still be the same + assertEquals(app.getAppConfig(), app2.getAppConfig()); } @Test public void trimArray() { - BigDecimal[] untrimmed = { BigDecimal.ZERO, BigDecimal.ONE, BigDecimal.TEN, new BigDecimal(1000) }; - BigDecimal[] trimmed = Helper.leftTrim(untrimmed, 2); + int[] untrimmed = { 0, 1, 10, 1000 }; + int[] trimmed = Helper.leftTrim(untrimmed, 2); assertTrue(trimmed.length == 2); assertEquals(trimmed[0], untrimmed[untrimmed.length - 2]); From 421e8197bf3338e83f847ed9c93b21df32f5493e Mon Sep 17 00:00:00 2001 From: Thomas Lauterbach Date: Tue, 18 Feb 2025 21:03:27 +0100 Subject: [PATCH 08/16] Improve channel init Signed-off-by: Thomas Lauterbach --- .../awtrixlight/internal/app/AwtrixApp.java | 42 +++++------------ .../handler/AwtrixLightAppHandler.java | 46 ++++++++++--------- .../mqtt/awtrixlight/internal/HelperTest.java | 6 ++- 3 files changed, 41 insertions(+), 53 deletions(-) diff --git a/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/app/AwtrixApp.java b/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/app/AwtrixApp.java index ce433236f756d..d8a31686a6fb9 100644 --- a/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/app/AwtrixApp.java +++ b/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/app/AwtrixApp.java @@ -15,6 +15,7 @@ import java.util.Arrays; import java.util.HashMap; import java.util.Map; +import java.util.stream.Collectors; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; @@ -34,11 +35,11 @@ public class AwtrixApp { public static final boolean DEFAULT_TOPTEXT = false; public static final int DEFAULT_TEXTOFFSET = 0; public static final boolean DEFAULT_CENTER = true; - public static final int[] DEFAULT_COLOR = {}; + public static final int[] DEFAULT_COLOR = { 255, 255, 255 }; public static final int[][] DEFAULT_GRADIENT = {}; public static final int DEFAULT_BLINKTEXT = 0; public static final int DEFAULT_FADETEXT = 0; - public static final int[] DEFAULT_BACKGROUND = {}; + public static final int[] DEFAULT_BACKGROUND = { 0, 0, 0 }; public static final boolean DEFAULT_RAINBOW = false; public static final String DEFAULT_ICON = "None"; public static final int DEFAULT_PUSHICON = 0; @@ -50,8 +51,8 @@ public class AwtrixApp { public static final boolean DEFAULT_AUTOSCALE = true; public static final String DEFAULT_OVERLAY = "Clear"; public static final int DEFAULT_PROGRESS = -1; - public static final int[] DEFAULT_PROGRESSC = {}; - public static final int[] DEFAULT_PROGRESSBC = {}; + public static final int[] DEFAULT_PROGRESSC = { 0, 255, 0 }; + public static final int[] DEFAULT_PROGRESSBC = { 255, 255, 255 }; public static final int DEFAULT_SCROLLSPEED = 100; public static final String DEFAULT_EFFECT = "None"; public static final int DEFAULT_EFFECTSPEED = 100; @@ -179,9 +180,6 @@ public int[] getColor() { public void setColor(int[] color) { this.color = color; - if (this.gradient.length == 2) { - this.gradient[0] = color; - } } public int[][] getGradient() { @@ -189,20 +187,7 @@ public int[][] getGradient() { } public void setGradient(int[][] gradient) { - if (gradient.length != 2) { - this.gradient = DEFAULT_GRADIENT; - } else { - this.gradient = gradient; - this.color = gradient[0]; - } - } - - public void setGradient(int[] gradient) { - if (gradient.length != 3) { - this.gradient = DEFAULT_GRADIENT; - } else { - this.gradient = new int[][] { this.color, gradient }; - } + this.gradient = gradient; } public int getBlinkText() { @@ -359,8 +344,9 @@ public void setEffectSettings(Map effectSettings) { protected String propertiesAsString() { return "text=" + text + ", textCase=" + textCase + ", topText=" + topText + ", textOffset=" + textOffset - + ", center=" + center + ", color=" + Arrays.toString(color) + ", gradient=" + Arrays.toString(gradient) - + ", blinkText=" + blinkText + ", fadeText=" + fadeText + ", background=" + Arrays.toString(background) + + ", center=" + center + ", color=" + Arrays.toString(color) + ", gradient=[" + + Arrays.stream(gradient).map(color -> Arrays.toString(color)).collect(Collectors.joining(", ")) + + "], blinkText=" + blinkText + ", fadeText=" + fadeText + ", background=" + Arrays.toString(background) + ", rainbow=" + rainbow + ", icon=" + icon + ", pushIcon=" + pushIcon + ", duration=" + duration + ", line=" + Arrays.toString(line) + ", lifetime=" + lifetime + ", lifetimeMode=" + lifetimeMode + ", bar=" + Arrays.toString(bar) + ", autoscale=" + autoscale + ", overlay=" + overlay + ", progress=" @@ -521,7 +507,7 @@ private Map getColorConfig() { private Map getTextEffectConfig() { Map fields = new HashMap(); - if (this.color.length == 0 || this.gradient.length == 0) { + if (Arrays.equals(this.color, DEFAULT_COLOR) && Arrays.equals(this.gradient, DEFAULT_GRADIENT)) { if (this.blinkText > 0) { fields.put("blinkText", this.blinkText); } else if (this.fadeText > 0) { @@ -580,12 +566,8 @@ private Map getProgressConfig() { Map fields = new HashMap(); if (progress > -1 && progress <= 100) { fields.put("progress", this.progress); - if (this.progressC.length == 3) { - fields.put("progressC", this.progressC); - } - if (this.progressBC.length == 3) { - fields.put("progressBC", this.progressBC); - } + fields.put("progressC", this.progressC); + fields.put("progressBC", this.progressBC); } return fields; } diff --git a/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/handler/AwtrixLightAppHandler.java b/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/handler/AwtrixLightAppHandler.java index 5924094104d83..5f67b3feca2d0 100644 --- a/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/handler/AwtrixLightAppHandler.java +++ b/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/handler/AwtrixLightAppHandler.java @@ -117,12 +117,17 @@ public void handleCommand(ChannelUID channelUID, Command command) { break; case CHANNEL_COLOR: if (command instanceof HSBType) { - this.app.setColor(ColorUtil.hsbToRgb((HSBType) command)); + int[] rgb = ColorUtil.hsbToRgb((HSBType) command); + this.app.setColor(rgb); + if (this.app.getGradient().length != 0) { + this.app.setGradient(new int[][] { rgb, this.app.getGradient()[1] }); + } } break; case CHANNEL_GRADIENT_COLOR: if (command instanceof HSBType) { - this.app.setGradient(ColorUtil.hsbToRgb((HSBType) command)); + int[] rgb = ColorUtil.hsbToRgb((HSBType) command); + this.app.setGradient(new int[][] { this.app.getColor(), rgb }); } break; case CHANNEL_SCROLLSPEED: @@ -533,13 +538,10 @@ private void updateApp() { private void initStates() { updateState(new ChannelUID(channelPrefix + CHANNEL_ACTIVE), this.active ? OnOffType.ON : OnOffType.OFF); - int[] color = this.app.getColor().length == 3 ? this.app.getColor() : new int[] { 0, 0, 0 }; + int[] color = this.app.getColor(); updateState(new ChannelUID(channelPrefix + CHANNEL_COLOR), HSBType.fromRGB(color[0], color[1], color[2])); - - int[] gradient = this.app.getGradient().length == 2 && this.app.getGradient()[1] != null - && this.app.getGradient()[1].length == 3 ? this.app.getGradient()[1] : new int[] { 0, 0, 0 }; updateState(new ChannelUID(channelPrefix + CHANNEL_GRADIENT_COLOR), - HSBType.fromRGB(gradient[0], gradient[1], gradient[2])); + HSBType.fromRGB(color[0], color[1], color[2])); updateState(new ChannelUID(channelPrefix + CHANNEL_SCROLLSPEED), new QuantityType<>(this.app.getScrollSpeed(), Units.PERCENT)); @@ -585,22 +587,23 @@ private void initStates() { updateState(new ChannelUID(channelPrefix + CHANNEL_ICON), new StringType(this.app.getIcon())); - String param = ""; - if (this.app.getPushIcon() == 0) { - param = PUSH_ICON_OPTION_0; - } else if (this.app.getPushIcon() == 1) { - param = PUSH_ICON_OPTION_1; - } else if (this.app.getPushIcon() == 2) { - param = PUSH_ICON_OPTION_2; + switch (this.app.getPushIcon()) { + case 0: + updateState(new ChannelUID(channelPrefix + CHANNEL_PUSH_ICON), new StringType(PUSH_ICON_OPTION_0)); + break; + case 1: + updateState(new ChannelUID(channelPrefix + CHANNEL_PUSH_ICON), new StringType(PUSH_ICON_OPTION_1)); + break; + case 2: + updateState(new ChannelUID(channelPrefix + CHANNEL_PUSH_ICON), new StringType(PUSH_ICON_OPTION_2)); + break; } - updateState(new ChannelUID(channelPrefix + CHANNEL_PUSH_ICON), new StringType(param)); - int[] background = this.app.getBackground().length == 3 ? this.app.getBackground() : new int[] { 0, 0, 0 }; + int[] background = this.app.getBackground(); updateState(new ChannelUID(channelPrefix + CHANNEL_BACKGROUND), HSBType.fromRGB(background[0], background[1], background[2])); - String line = this.app.getLine().length > 0 ? Arrays.toString(this.app.getLine()) : ""; - updateState(new ChannelUID(channelPrefix + CHANNEL_LINE), new StringType(line)); + updateState(new ChannelUID(channelPrefix + CHANNEL_LINE), new StringType(Arrays.toString(this.app.getLine()))); updateState(new ChannelUID(channelPrefix + CHANNEL_LIFETIME), new QuantityType<>(this.app.getLifetime(), Units.SECOND)); @@ -608,8 +611,7 @@ private void initStates() { String lifetimeMode = this.app.getLifetimeMode() == 0 ? "DELETE" : "STALE"; updateState(new ChannelUID(channelPrefix + CHANNEL_LIFETIME_MODE), new StringType(lifetimeMode)); - String bar = this.app.getBar().length > 0 ? Arrays.toString(this.app.getBar()) : ""; - updateState(new ChannelUID(channelPrefix + CHANNEL_BAR), new StringType(bar)); + updateState(new ChannelUID(channelPrefix + CHANNEL_BAR), new StringType(Arrays.toString(this.app.getBar()))); updateState(new ChannelUID(channelPrefix + CHANNEL_AUTOSCALE), this.app.getAutoscale() ? OnOffType.ON : OnOffType.OFF); @@ -619,11 +621,11 @@ private void initStates() { int progress = Math.max(this.app.getProgress(), 0); updateState(new ChannelUID(channelPrefix + CHANNEL_PROGRESS), new QuantityType<>(progress, Units.PERCENT)); - int[] progressC = this.app.getProgressC().length == 3 ? this.app.getProgressC() : new int[] { 0, 0, 0 }; + int[] progressC = this.app.getProgressC(); updateState(new ChannelUID(channelPrefix + CHANNEL_PROGRESSC), HSBType.fromRGB(progressC[0], progressC[1], progressC[2])); - int[] progressBC = this.app.getProgressBC().length == 3 ? this.app.getProgressBC() : new int[] { 0, 0, 0 }; + int[] progressBC = this.app.getProgressBC(); updateState(new ChannelUID(channelPrefix + CHANNEL_PROGRESSBC), HSBType.fromRGB(progressBC[0], progressBC[1], progressBC[2])); } diff --git a/bundles/org.openhab.binding.mqtt.awtrixlight/src/test/java/org/openhab/binding/mqtt/awtrixlight/internal/HelperTest.java b/bundles/org.openhab.binding.mqtt.awtrixlight/src/test/java/org/openhab/binding/mqtt/awtrixlight/internal/HelperTest.java index 32c17448ef327..8aa866d204d5d 100644 --- a/bundles/org.openhab.binding.mqtt.awtrixlight/src/test/java/org/openhab/binding/mqtt/awtrixlight/internal/HelperTest.java +++ b/bundles/org.openhab.binding.mqtt.awtrixlight/src/test/java/org/openhab/binding/mqtt/awtrixlight/internal/HelperTest.java @@ -23,6 +23,7 @@ import java.util.Map; import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.junit.jupiter.MockitoExtension; @@ -58,14 +59,17 @@ public void convertJson() { String[] stringProperties = { FIELD_BRIDGE_APP, FIELD_BRIDGE_FIRMWARE }; String[] booleanProperties = { FIELD_BRIDGE_INDICATOR1, FIELD_BRIDGE_INDICATOR2, FIELD_BRIDGE_INDICATOR3 }; if (Arrays.stream(stringProperties).anyMatch(s::equals)) { + @Nullable Object prop = convertedJson.get(s); assertNotNull(prop); assertEquals(String.class, prop.getClass()); } else if (Arrays.stream(booleanProperties).anyMatch(s::equals)) { + @Nullable Object prop = convertedJson.get(s); assertNotNull(prop); assertEquals(Boolean.class, prop.getClass()); } else { + @Nullable Object prop = convertedJson.get(s); assertNotNull(prop); assertEquals(Double.class, prop.getClass()); @@ -115,7 +119,7 @@ private AwtrixApp getTestAppWithGradient() { AwtrixApp app = new AwtrixApp(); app.setText("Test"); app.setColor(new int[] { 255, 255, 255 }); - app.setGradient(new int[] { 255, 0, 0 }); + app.setGradient(new int[][] { { 255, 255, 255 }, { 255, 0, 0 } }); return app; } From 425728bdb306e47384b90bcb59230abbc925ed81 Mon Sep 17 00:00:00 2001 From: Thomas Lauterbach Date: Tue, 18 Feb 2025 23:14:58 +0100 Subject: [PATCH 09/16] Fix second round of review Signed-off-by: Thomas Lauterbach --- .../README.md | 94 +++++++++---------- .../mqtt/awtrixlight/internal/Helper.java | 2 +- .../handler/AwtrixLightAppHandler.java | 91 +++++++++--------- .../handler/AwtrixLightBridgeHandler.java | 17 +--- .../main/resources/OH-INF/config/config.xml | 12 +-- .../resources/OH-INF/thing/thing-types.xml | 3 - 6 files changed, 103 insertions(+), 116 deletions(-) diff --git a/bundles/org.openhab.binding.mqtt.awtrixlight/README.md b/bundles/org.openhab.binding.mqtt.awtrixlight/README.md index e3748bca3cf24..563ef05ea7cdb 100644 --- a/bundles/org.openhab.binding.mqtt.awtrixlight/README.md +++ b/bundles/org.openhab.binding.mqtt.awtrixlight/README.md @@ -31,7 +31,7 @@ The Awtrix 3 binding does not offer any binding configuration parameters. ## Thing Configuration -### Bridge Configuration (`awtrixclock`) +### Bridge Configuration (`awtrix-clock`) | Parameter | Description | Default | Required | |-----------------------|--------------------------------------------------------------------------------------------------------------------------------------------|----------|----------| @@ -40,7 +40,7 @@ The Awtrix 3 binding does not offer any binding configuration parameters. | `discoverDefaultApps` | Enable discovery of default apps. Since default apps cannot be controlled by this binding this should usually be disabled. | false | Yes | | `lowBatteryThreshold` | Battery level threshold for low battery warning. | 25 | Yes | -### App Configuration (`awtrixapp`) +### App Configuration (`awtrix-app`) | Parameter | Description | Default | Required | |--------------|------------------------------------|---------|----------| @@ -56,7 +56,7 @@ The button events can be used by rules to change the displayed app or perform an ## Channels -### Bridge Channels (`awtrixclock`) +### Bridge Channels (`awtrix-clock`) | Channel | Type | Read/Write | Description | |-------------------|----------------------|------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| @@ -80,7 +80,7 @@ The button events can be used by rules to change the displayed app or perform an | `sound` | String | W | Play sound file: The sound file must be available on the clock device in the MELODIES folder. Save a file with a valid RTTTL string (e.g. melody.txt) in this folder and play it by sending a String command to the channel with the filename without file extension (e.g. "melody"). | | `temperature` | Number:Temperature | R | Device temperature: Temperature in °C as measured by the built-in temperature sensor. For the Ulanzi clock values are usually very inaccurate. | -### App Channels (`awtrixapp`) +### App Channels (`awtrix-app`) | Channel | Action parameter (see Actions) | Action parameter type | Type | Read/Write | Description | | |-----------------------|------------------------------------------|------------------------------------------------|----------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---| @@ -281,11 +281,11 @@ The following actions are supported: ```java Bridge mqtt:broker:myBroker [ host="localhost", port=1883 ] -Bridge mqtt:awtrixclock:myBroker:myAwtrix "Living Room Display" (mqtt:broker:myBroker) [ basetopic="awtrix", appLockTimeout=10, lowBatteryThreshold=25 ] { - Thing awtrixapp clock "Clock App" [ appname="clock", useButtons=true ] - Thing awtrixapp weather "Weather App" [ appname="weather" ] - Thing awtrixapp calendar "Calendar App" [ appname="calendar" ] - Thing awtrixapp custom "Custom App" [ appname="custom" ] +Bridge mqtt:awtrix-clock:myBroker:myAwtrix "Living Room Display" (mqtt:broker:myBroker) [ basetopic="awtrix", appLockTimeout=10, lowBatteryThreshold=25 ] { + Thing awtrix-app clock "Clock App" [ appname="clock", useButtons=true ] + Thing awtrix-app weather "Weather App" [ appname="weather" ] + Thing awtrix-app calendar "Calendar App" [ appname="calendar" ] + Thing awtrix-app custom "Custom App" [ appname="custom" ] } ``` @@ -294,43 +294,43 @@ Bridge mqtt:awtrixclock:myBroker:myAwtrix "Living Room Display" (mqtt:broker:myB ```java // Bridge items (Living Room Display) Group gAwtrix "Living Room Awtrix Display" -Dimmer Display_Brightness "Brightness [%d %%]" (gAwtrix) { channel="mqtt:awtrixclock:myBroker:myAwtrix:brightness" } -Switch Display_Power "Power" (gAwtrix) { channel="mqtt:awtrixclock:myBroker:myAwtrix:power" } -Switch Display_Screen "Screen" (gAwtrix) { channel="mqtt:awtrixclock:myBroker:myAwtrix:display" } -Switch Display_Sound "Sound" (gAwtrix) { channel="mqtt:awtrixclock:myBroker:myAwtrix:sound" } -Switch Display_AutoBrightness "Auto Brightness" (gAwtrix) { channel="mqtt:awtrixclock:myBroker:myAwtrix:autoBrightness" } -Number:Temperature Display_Temperature "Temperature [%.1f °C]" (gAwtrix) { channel="mqtt:awtrixclock:myBroker:myAwtrix:temperature" } -Number:Dimensionless Display_Humidity "Humidity [%d %%]" (gAwtrix) { channel="mqtt:awtrixclock:myBroker:myAwtrix:humidity" } -Number Display_Battery "Battery Level [%d %%]" (gAwtrix) { channel="mqtt:awtrixclock:myBroker:myAwtrix:batteryLevel" } -Switch Display_LowBattery "Low Battery" (gAwtrix) { channel="mqtt:awtrixclock:myBroker:myAwtrix:lowBattery" } -Number:Dimensionless Display_WiFi "WiFi Signal [%d %%]" (gAwtrix) { channel="mqtt:awtrixclock:myBroker:myAwtrix:rssi" } -String Display_CurrentApp "Active App [%s]" (gAwtrix) { channel="mqtt:awtrixclock:myBroker:myAwtrix:app" } +Dimmer Display_Brightness "Brightness [%d %%]" (gAwtrix) { channel="mqtt:awtrix-clock:myBroker:myAwtrix:brightness" } +Switch Display_Power "Power" (gAwtrix) { channel="mqtt:awtrix-clock:myBroker:myAwtrix:power" } +Switch Display_Screen "Screen" (gAwtrix) { channel="mqtt:awtrix-clock:myBroker:myAwtrix:display" } +Switch Display_Sound "Sound" (gAwtrix) { channel="mqtt:awtrix-clock:myBroker:myAwtrix:sound" } +Switch Display_AutoBrightness "Auto Brightness" (gAwtrix) { channel="mqtt:awtrix-clock:myBroker:myAwtrix:autoBrightness" } +Number:Temperature Display_Temperature "Temperature [%.1f °C]" (gAwtrix) { channel="mqtt:awtrix-clock:myBroker:myAwtrix:temperature" } +Number:Dimensionless Display_Humidity "Humidity [%d %%]" (gAwtrix) { channel="mqtt:awtrix-clock:myBroker:myAwtrix:humidity" } +Number Display_Battery "Battery Level [%d %%]" (gAwtrix) { channel="mqtt:awtrix-clock:myBroker:myAwtrix:batteryLevel" } +Switch Display_LowBattery "Low Battery" (gAwtrix) { channel="mqtt:awtrix-clock:myBroker:myAwtrix:lowBattery" } +Number:Dimensionless Display_WiFi "WiFi Signal [%d %%]" (gAwtrix) { channel="mqtt:awtrix-clock:myBroker:myAwtrix:rssi" } +String Display_CurrentApp "Active App [%s]" (gAwtrix) { channel="mqtt:awtrix-clock:myBroker:myAwtrix:app" } // Clock App items Group gAwtrixClock "Clock App" -Switch Clock_Active "Clock Active" (gAwtrixClock) { channel="mqtt:awtrixapp:myBroker:myAwtrix:clock:active" } -String Clock_Text "Clock Text" (gAwtrixClock) { channel="mqtt:awtrixapp:myBroker:myAwtrix:clock:text" } -Color Clock_Color "Clock Color" (gAwtrixClock) { channel="mqtt:awtrixapp:myBroker:myAwtrix:clock:color" } -Number Clock_Duration "Clock Duration" (gAwtrixClock) { channel="mqtt:awtrixapp:myBroker:myAwtrix:clock:duration" } +Switch Clock_Active "Clock Active" (gAwtrixClock) { channel="mqtt:awtrix-app:myBroker:myAwtrix:clock:active" } +String Clock_Text "Clock Text" (gAwtrixClock) { channel="mqtt:awtrix-app:myBroker:myAwtrix:clock:text" } +Color Clock_Color "Clock Color" (gAwtrixClock) { channel="mqtt:awtrix-app:myBroker:myAwtrix:clock:color" } +Number Clock_Duration "Clock Duration" (gAwtrixClock) { channel="mqtt:awtrix-app:myBroker:myAwtrix:clock:duration" } // Weather App items Group gAwtrixWeather "Weather App" -Switch Weather_Active "Weather Active" (gAwtrixWeather) { channel="mqtt:awtrixapp:myBroker:myAwtrix:weather:active" } -String Weather_Text "Weather Text" (gAwtrixWeather) { channel="mqtt:awtrixapp:myBroker:myAwtrix:weather:text" } -String Weather_Icon "Weather Icon" (gAwtrixWeather) { channel="mqtt:awtrixapp:myBroker:myAwtrix:weather:icon" } -Color Weather_Color "Weather Color" (gAwtrixWeather) { channel="mqtt:awtrixapp:myBroker:myAwtrix:weather:color" } -Switch Weather_Rainbow "Weather Rainbow Effect" (gAwtrixWeather) { channel="mqtt:awtrixapp:myBroker:myAwtrix:weather:rainbow" } +Switch Weather_Active "Weather Active" (gAwtrixWeather) { channel="mqtt:awtrix-app:myBroker:myAwtrix:weather:active" } +String Weather_Text "Weather Text" (gAwtrixWeather) { channel="mqtt:awtrix-app:myBroker:myAwtrix:weather:text" } +String Weather_Icon "Weather Icon" (gAwtrixWeather) { channel="mqtt:awtrix-app:myBroker:myAwtrix:weather:icon" } +Color Weather_Color "Weather Color" (gAwtrixWeather) { channel="mqtt:awtrix-app:myBroker:myAwtrix:weather:color" } +Switch Weather_Rainbow "Weather Rainbow Effect" (gAwtrixWeather) { channel="mqtt:awtrix-app:myBroker:myAwtrix:weather:rainbow" } // Custom App items with advanced features -Switch Custom_Active "Custom App Active" (gAwtrix) { channel="mqtt:awtrixapp:myBroker:myAwtrix:custom:active" } -String Custom_Text "Custom Text" (gAwtrix) { channel="mqtt:awtrixapp:myBroker:myAwtrix:custom:text" } -String Custom_Icon "Custom Icon" (gAwtrix) { channel="mqtt:awtrixapp:myBroker:myAwtrix:custom:icon" } -Color Custom_Color "Text Color" (gAwtrix) { channel="mqtt:awtrixapp:myBroker:myAwtrix:custom:color" } -Color Custom_Background "Background Color" (gAwtrix) { channel="mqtt:awtrixapp:myBroker:myAwtrix:custom:background" } -Number:Dimensionless Custom_ScrollSpeed "Scroll Speed" (gAwtrix) { channel="mqtt:awtrixapp:myBroker:myAwtrix:custom:scrollSpeed" } -Switch Custom_Center "Center Text" (gAwtrix) { channel="mqtt:awtrixapp:myBroker:myAwtrix:custom:center" } -Number:Dimensionless Custom_Progress "Progress [%d %%]" (gAwtrix) { channel="mqtt:awtrixapp:myBroker:myAwtrix:custom:progress" } -Color Custom_ProgressColor "Progress Color" (gAwtrix) { channel="mqtt:awtrixapp:myBroker:myAwtrix:custom:progressColor" } +Switch Custom_Active "Custom App Active" (gAwtrix) { channel="mqtt:awtrix-app:myBroker:myAwtrix:custom:active" } +String Custom_Text "Custom Text" (gAwtrix) { channel="mqtt:awtrix-app:myBroker:myAwtrix:custom:text" } +String Custom_Icon "Custom Icon" (gAwtrix) { channel="mqtt:awtrix-app:myBroker:myAwtrix:custom:icon" } +Color Custom_Color "Text Color" (gAwtrix) { channel="mqtt:awtrix-app:myBroker:myAwtrix:custom:color" } +Color Custom_Background "Background Color" (gAwtrix) { channel="mqtt:awtrix-app:myBroker:myAwtrix:custom:background" } +Number:Dimensionless Custom_ScrollSpeed "Scroll Speed" (gAwtrix) { channel="mqtt:awtrix-app:myBroker:myAwtrix:custom:scrollSpeed" } +Switch Custom_Center "Center Text" (gAwtrix) { channel="mqtt:awtrix-app:myBroker:myAwtrix:custom:center" } +Number:Dimensionless Custom_Progress "Progress [%d %%]" (gAwtrix) { channel="mqtt:awtrix-app:myBroker:myAwtrix:custom:progress" } +Color Custom_ProgressColor "Progress Color" (gAwtrix) { channel="mqtt:awtrix-app:myBroker:myAwtrix:custom:progressColor" } ``` ### Sitemap @@ -388,13 +388,13 @@ The binding provides various actions that can be used in rules to control the Aw Rules DSL: ```java -val awtrixActions = getActions("mqtt.awtrixlight", "mqtt:awtrixclock:myBroker:myAwtrix") +val awtrixActions = getActions("mqtt.awtrixlight", "mqtt:awtrix-clock:myBroker:myAwtrix") ``` JS Scripting: ```java -var awtrixActions = actions.thingActions("mqtt.awtrixlight", "mqtt:awtrixclock:myBroker:myAwtrix"); +var awtrixActions = actions.thingActions("mqtt.awtrixlight", "mqtt:awtrix-clock:myBroker:myAwtrix"); ``` ### Indicator Control @@ -497,14 +497,14 @@ when then if (Display_Battery.state <= 20) { // Show low battery warning with blinking red indicator - getActions("mqtt.awtrixlight", "mqtt:awtrixclock:myBroker:myAwtrix").blinkIndicator(1, [255,0,0], 500) - getActions("mqtt.awtrixlight", "mqtt:awtrixclock:myBroker:myAwtrix").showNotification("Low Battery!", "battery-alert") + getActions("mqtt.awtrixlight", "mqtt:awtrix-clock:myBroker:myAwtrix").blinkIndicator(1, [255,0,0], 500) + getActions("mqtt.awtrixlight", "mqtt:awtrix-clock:myBroker:myAwtrix").showNotification("Low Battery!", "battery-alert") } else if (Display_Battery.state <= 50) { // Show yellow indicator for medium battery - getActions("mqtt.awtrixlight", "mqtt:awtrixclock:myBroker:myAwtrix").setIndicator(1, [255,255,0]) + getActions("mqtt.awtrixlight", "mqtt:awtrix-clock:myBroker:myAwtrix").setIndicator(1, [255,255,0]) } else { // Show green indicator for good battery - getActions("mqtt.awtrixlight", "mqtt:awtrixclock:myBroker:myAwtrix").setIndicator(1, [0,255,0]) + getActions("mqtt.awtrixlight", "mqtt:awtrix-clock:myBroker:myAwtrix").setIndicator(1, [0,255,0]) } end @@ -513,7 +513,7 @@ when Item Doorbell_Button changed to ON then // Play sound and show notification - getActions("mqtt.awtrixlight", "mqtt:awtrixclock:myBroker:myAwtrix").playRtttl("doorbell:d=4,o=6,b=100:8e,8g,8e,8c") + getActions("mqtt.awtrixlight", "mqtt:awtrix-clock:myBroker:myAwtrix").playRtttl("doorbell:d=4,o=6,b=100:8e,8g,8e,8c") var params = newHashMap( 'text' -> "Doorbell", @@ -522,7 +522,7 @@ then 'pushIcon' -> "PUSHOUT", 'center' -> true ) - getActions("mqtt.awtrixlight", "mqtt:awtrixclock:myBroker:myAwtrix").showCustomNotification( + getActions("mqtt.awtrixlight", "mqtt:awtrix-clock:myBroker:myAwtrix").showCustomNotification( params, false, true, true, "", "", false ) end @@ -541,7 +541,7 @@ then if (progress == 100) { // Play sound when done - getActions("mqtt.awtrixlight", "mqtt:awtrixclock:myBroker:myAwtrix").playSound("complete") + getActions("mqtt.awtrixlight", "mqtt:awtrix-clock:myBroker:myAwtrix").playSound("complete") } end ``` diff --git a/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/Helper.java b/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/Helper.java index 958a83e50882a..31ded6a09f8fe 100644 --- a/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/Helper.java +++ b/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/Helper.java @@ -103,7 +103,7 @@ public static byte[] decodeImage(String messageJSON) { ImageIO.write(image, "png", baos); bytes = baos.toByteArray(); } catch (IOException e) { - LOGGER.error("Failed to decode image", e); + LOGGER.warn("Failed to decode image", e); } return bytes == null ? new byte[0] : bytes; } diff --git a/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/handler/AwtrixLightAppHandler.java b/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/handler/AwtrixLightAppHandler.java index 5f67b3feca2d0..ce1905ef64812 100644 --- a/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/handler/AwtrixLightAppHandler.java +++ b/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/handler/AwtrixLightAppHandler.java @@ -106,7 +106,7 @@ public void handleCommand(ChannelUID channelUID, Command command) { break; case CHANNEL_RESET: if (command instanceof OnOffType) { - if (OnOffType.ON.equals((OnOffType) command)) { + if (OnOffType.ON.equals(command)) { deleteApp(); this.app = new AwtrixApp(); updateApp(); @@ -116,8 +116,8 @@ public void handleCommand(ChannelUID channelUID, Command command) { } break; case CHANNEL_COLOR: - if (command instanceof HSBType) { - int[] rgb = ColorUtil.hsbToRgb((HSBType) command); + if (command instanceof HSBType hsbCommand) { + int[] rgb = ColorUtil.hsbToRgb(hsbCommand); this.app.setColor(rgb); if (this.app.getGradient().length != 0) { this.app.setGradient(new int[][] { rgb, this.app.getGradient()[1] }); @@ -125,69 +125,69 @@ public void handleCommand(ChannelUID channelUID, Command command) { } break; case CHANNEL_GRADIENT_COLOR: - if (command instanceof HSBType) { - int[] rgb = ColorUtil.hsbToRgb((HSBType) command); + if (command instanceof HSBType hsbCommand) { + int[] rgb = ColorUtil.hsbToRgb(hsbCommand); this.app.setGradient(new int[][] { this.app.getColor(), rgb }); } break; case CHANNEL_SCROLLSPEED: - if (command instanceof QuantityType) { - this.app.setScrollSpeed(((QuantityType) command).intValue()); + if (command instanceof QuantityType quantityCommand) { + this.app.setScrollSpeed(quantityCommand.intValue()); } break; case CHANNEL_DURATION: - if (command instanceof QuantityType) { - this.app.setDuration(((QuantityType) command).intValue()); + if (command instanceof QuantityType quantityCommand) { + this.app.setDuration(quantityCommand.intValue()); } break; case CHANNEL_EFFECT: if (command instanceof StringType) { - this.app.setEffect(((StringType) command).toString()); + this.app.setEffect(command.toString()); } break; case CHANNEL_EFFECT_SPEED: - if (command instanceof QuantityType) { - this.app.setEffectSpeed(((QuantityType) command).intValue()); + if (command instanceof QuantityType quantityCommand) { + this.app.setEffectSpeed(quantityCommand.intValue()); } break; case CHANNEL_EFFECT_PALETTE: if (command instanceof StringType) { - this.app.setEffectPalette(((StringType) command).toString()); + this.app.setEffectPalette(command.toString()); } break; case CHANNEL_EFFECT_BLEND: if (command instanceof OnOffType) { - this.app.setEffectBlend(command.equals(OnOffType.ON)); + this.app.setEffectBlend(OnOffType.ON.equals(command)); } break; case CHANNEL_TEXT: if (command instanceof StringType) { - this.app.setText(((StringType) command).toString()); + this.app.setText(command.toString()); } break; case CHANNEL_TEXT_OFFSET: - if (command instanceof QuantityType) { - this.app.setTextOffset(((QuantityType) command).intValue()); + if (command instanceof QuantityType quantityCommand) { + this.app.setTextOffset(quantityCommand.intValue()); } break; case CHANNEL_TOP_TEXT: if (command instanceof OnOffType) { - this.app.setTopText(command.equals(OnOffType.ON)); + this.app.setTopText(OnOffType.ON.equals(command)); } break; case CHANNEL_TEXTCASE: - if (command instanceof QuantityType) { - this.app.setTextCase(((QuantityType) command).intValue()); + if (command instanceof QuantityType quantityCommand) { + this.app.setTextCase(quantityCommand.intValue()); } break; case CHANNEL_CENTER: if (command instanceof OnOffType) { - this.app.setCenter(command.equals(OnOffType.ON)); + this.app.setCenter(OnOffType.ON.equals(command)); } break; case CHANNEL_BLINK_TEXT: - if (command instanceof QuantityType) { - QuantityType blinkInS = ((QuantityType) command).toUnit(Units.SECOND); + if (command instanceof QuantityType quantityCommand) { + QuantityType blinkInS = quantityCommand.toUnit(Units.SECOND); if (blinkInS != null) { int blinkInMs = blinkInS.intValue() * 1000; this.app.setBlinkText(blinkInMs); @@ -195,8 +195,8 @@ public void handleCommand(ChannelUID channelUID, Command command) { } break; case CHANNEL_FADE_TEXT: - if (command instanceof QuantityType) { - QuantityType fadeInS = ((QuantityType) command).toUnit(Units.SECOND); + if (command instanceof QuantityType quantityCommand) { + QuantityType fadeInS = quantityCommand.toUnit(Units.SECOND); if (fadeInS != null) { int fadeInMs = fadeInS.intValue() * 1000; this.app.setFadeText(fadeInMs); @@ -205,17 +205,17 @@ public void handleCommand(ChannelUID channelUID, Command command) { break; case CHANNEL_RAINBOW: if (command instanceof OnOffType) { - this.app.setRainbow(command.equals(OnOffType.ON)); + this.app.setRainbow(OnOffType.ON.equals(command)); } break; case CHANNEL_ICON: if (command instanceof StringType) { - this.app.setIcon(((StringType) command).toString()); + this.app.setIcon(command.toString()); } break; case CHANNEL_PUSH_ICON: if (command instanceof StringType) { - switch (((StringType) command).toString()) { + switch (command.toString()) { case PUSH_ICON_OPTION_0: this.app.setPushIcon(0); break; @@ -229,8 +229,8 @@ public void handleCommand(ChannelUID channelUID, Command command) { } break; case CHANNEL_BACKGROUND: - if (command instanceof HSBType) { - int[] rgb = ColorUtil.hsbToRgb((HSBType) command); + if (command instanceof HSBType hsbCommand) { + int[] rgb = ColorUtil.hsbToRgb(hsbCommand); this.app.setBackground(rgb); } break; @@ -247,8 +247,8 @@ public void handleCommand(ChannelUID channelUID, Command command) { } break; case CHANNEL_LIFETIME: - if (command instanceof QuantityType) { - this.app.setLifetime(((QuantityType) command).intValue()); + if (command instanceof QuantityType quantityCommand) { + this.app.setLifetime(quantityCommand.intValue()); } break; case CHANNEL_LIFETIME_MODE: @@ -277,28 +277,28 @@ public void handleCommand(ChannelUID channelUID, Command command) { break; case CHANNEL_AUTOSCALE: if (command instanceof OnOffType) { - this.app.setAutoscale(command.equals(OnOffType.ON)); + this.app.setAutoscale(OnOffType.ON.equals(command)); } break; case CHANNEL_OVERLAY: if (command instanceof StringType) { - this.app.setOverlay(((StringType) command).toString()); + this.app.setOverlay(command.toString()); } break; case CHANNEL_PROGRESS: - if (command instanceof QuantityType) { - this.app.setProgress(((QuantityType) command).intValue()); + if (command instanceof QuantityType quantityCommand) { + this.app.setProgress(quantityCommand.intValue()); } break; case CHANNEL_PROGRESSC: - if (command instanceof HSBType) { - int[] rgb = ColorUtil.hsbToRgb((HSBType) command); + if (command instanceof HSBType hsbCommand) { + int[] rgb = ColorUtil.hsbToRgb(hsbCommand); this.app.setProgressC(rgb); } break; case CHANNEL_PROGRESSBC: - if (command instanceof HSBType) { - int[] rgb = ColorUtil.hsbToRgb((HSBType) command); + if (command instanceof HSBType hsbCommand) { + int[] rgb = ColorUtil.hsbToRgb(hsbCommand); this.app.setProgressBC(rgb); } break; @@ -463,8 +463,7 @@ public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) { return; } ThingHandler handler = localBridge.getHandler(); - if (handler instanceof AwtrixLightBridgeHandler) { - AwtrixLightBridgeHandler albh = (AwtrixLightBridgeHandler) handler; + if (handler instanceof AwtrixLightBridgeHandler albh) { Map bridgeProperties = albh.getThing().getProperties(); @Nullable String bridgeHardwareId = bridgeProperties.get(PROP_UNIQUEID); @@ -517,8 +516,7 @@ private void deleteApp() { Bridge bridge = getBridge(); if (bridge != null) { BridgeHandler bridgeHandler = bridge.getHandler(); - if (bridgeHandler instanceof AwtrixLightBridgeHandler) { - AwtrixLightBridgeHandler albh = (AwtrixLightBridgeHandler) bridgeHandler; + if (bridgeHandler instanceof AwtrixLightBridgeHandler albh) { albh.deleteApp(this.appName); } } @@ -528,8 +526,7 @@ private void updateApp() { Bridge bridge = getBridge(); if (bridge != null) { BridgeHandler bridgeHandler = bridge.getHandler(); - if (bridgeHandler instanceof AwtrixLightBridgeHandler) { - AwtrixLightBridgeHandler albh = (AwtrixLightBridgeHandler) bridgeHandler; + if (bridgeHandler instanceof AwtrixLightBridgeHandler albh) { albh.updateApp(this.appName, this.app.getAppConfig()); } } @@ -640,7 +637,7 @@ private void finishInit() { } updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE); Future localJob = this.finishInitJob; - if (localJob != null && !localJob.isCancelled() && !localJob.isDone()) { + if (localJob != null) { localJob.cancel(true); this.finishInitJob = null; } diff --git a/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/handler/AwtrixLightBridgeHandler.java b/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/handler/AwtrixLightBridgeHandler.java index d081bd82fcf85..3554b1db4a17e 100644 --- a/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/handler/AwtrixLightBridgeHandler.java +++ b/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/handler/AwtrixLightBridgeHandler.java @@ -112,8 +112,8 @@ public void handleCommand(ChannelUID channelUID, Command command) { } break; case CHANNEL_BRIGHTNESS: - if (command instanceof QuantityType) { - double brightnessInt = ((QuantityType) command).doubleValue(); + if (command instanceof QuantityType quantityCommand) { + double brightnessInt = quantityCommand.doubleValue(); if (0 <= brightnessInt && brightnessInt <= 100) { long param = Math.round(255 * (brightnessInt / 100)); sendMQTT(this.basetopic + TOPIC_SETTINGS, "{\"" + FIELD_BRIDGE_SET_AUTO_BRIGHTNESS @@ -235,8 +235,7 @@ public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) { return; } ThingHandler handler = localBridge.getHandler(); - if (handler instanceof AbstractBrokerHandler) { - AbstractBrokerHandler abh = (AbstractBrokerHandler) handler; + if (handler instanceof AbstractBrokerHandler abh) { final MqttBrokerConnection connection; try { connection = abh.getConnectionAsync().get(500, TimeUnit.MILLISECONDS); @@ -272,8 +271,7 @@ public void dispose() { @Override public void childHandlerInitialized(ThingHandler childHandler, Thing childThing) { - if (childHandler instanceof AwtrixLightAppHandler) { - AwtrixLightAppHandler alah = (AwtrixLightAppHandler) childHandler; + if (childHandler instanceof AwtrixLightAppHandler alah) { this.appHandlers.put(alah.getAppName(), alah); MqttBrokerConnection localConnection = connection; if (localConnection != null) { @@ -284,8 +282,7 @@ public void childHandlerInitialized(ThingHandler childHandler, Thing childThing) @Override public void childHandlerDisposed(ThingHandler childHandler, Thing childThing) { - if (childHandler instanceof AwtrixLightAppHandler) { - AwtrixLightAppHandler alah = (AwtrixLightAppHandler) childHandler; + if (childHandler instanceof AwtrixLightAppHandler alah) { this.appHandlers.remove(alah.getAppName()); MqttBrokerConnection localConnection = connection; if (localConnection != null) { @@ -504,13 +501,11 @@ private void signalLeaveAppControlMode() { } private void handleScreenMessage(String screenMessage) { - logger.trace("Incoming screen message {}", screenMessage); byte[] bytes = Helper.decodeImage(screenMessage); updateState(new ChannelUID(channelPrefix + CHANNEL_SCREEN), new RawType(bytes, "image/png")); } private void handleCurrentAppMessage(String currentAppMessage) { - logger.trace("Incoming currentApp message {}", currentAppMessage); this.currentApp = currentAppMessage; ThingHandlerCallback callback = getCallback(); if (callback != null && callback.isChannelLinked(new ChannelUID(this.channelPrefix + CHANNEL_SCREEN))) { @@ -533,10 +528,8 @@ private void handleCurrentAppMessage(String currentAppMessage) { } private void handleStatsMessage(String statsMessage) { - logger.trace("Incoming stats message {}", statsMessage); Map params = Helper.decodeStatsJson(statsMessage); for (Map.Entry entry : params.entrySet()) { - @Nullable String key = entry.getKey(); @Nullable diff --git a/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/resources/OH-INF/config/config.xml b/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/resources/OH-INF/config/config.xml index 15ab491386c1d..829fcdebb1359 100644 --- a/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/resources/OH-INF/config/config.xml +++ b/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/resources/OH-INF/config/config.xml @@ -6,23 +6,23 @@ - + Base topic as configured in the Awtrix Light device. awtrix - + Timeout in seconds until selected apps return control and the app rotation continues. 10 - + Currently changing settings for the built-in default apps is not implemented. It is therefore recommended to ignore them during app discovery. false - + Threshold for issuing a low battery warning. 25 @@ -30,11 +30,11 @@ - + Name of the app - + Effect Color Palette Changes the color scheme of the effect settings colorpicker - - Control - From 6fbbd971b4dc45b2aa6829f5ec55e376fd46909a Mon Sep 17 00:00:00 2001 From: Thomas Lauterbach Date: Tue, 18 Feb 2025 23:19:25 +0100 Subject: [PATCH 10/16] Removes null param from ThingStatusInfo constructor call Signed-off-by: Thomas Lauterbach --- .../awtrixlight/internal/handler/AwtrixLightBridgeHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/handler/AwtrixLightBridgeHandler.java b/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/handler/AwtrixLightBridgeHandler.java index 3554b1db4a17e..af646cea17010 100644 --- a/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/handler/AwtrixLightBridgeHandler.java +++ b/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/handler/AwtrixLightBridgeHandler.java @@ -212,7 +212,7 @@ public ThingStatusInfo getBridgeStatus() { if (b != null) { return b.getStatusInfo(); } else { - return new ThingStatusInfo(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE, null); + return new ThingStatusInfo(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE); } } From 9491687ebc74f2f640d3b1aff93f66a19429639d Mon Sep 17 00:00:00 2001 From: Thomas Lauterbach Date: Wed, 19 Feb 2025 21:04:12 +0100 Subject: [PATCH 11/16] Revert "Removes null param from ThingStatusInfo constructor call" This reverts commit 6fbbd971b4dc45b2aa6829f5ec55e376fd46909a. Signed-off-by: Thomas Lauterbach --- .../awtrixlight/internal/handler/AwtrixLightBridgeHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/handler/AwtrixLightBridgeHandler.java b/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/handler/AwtrixLightBridgeHandler.java index af646cea17010..3554b1db4a17e 100644 --- a/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/handler/AwtrixLightBridgeHandler.java +++ b/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/handler/AwtrixLightBridgeHandler.java @@ -212,7 +212,7 @@ public ThingStatusInfo getBridgeStatus() { if (b != null) { return b.getStatusInfo(); } else { - return new ThingStatusInfo(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE); + return new ThingStatusInfo(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE, null); } } From 2ac6f2c737677518ede17e530459a246ef7ac111 Mon Sep 17 00:00:00 2001 From: Thomas Lauterbach Date: Wed, 19 Feb 2025 23:43:16 +0100 Subject: [PATCH 12/16] Don't try to delete apps during first init Signed-off-by: Thomas Lauterbach --- .../awtrixlight/internal/handler/AwtrixLightAppHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/handler/AwtrixLightAppHandler.java b/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/handler/AwtrixLightAppHandler.java index ce1905ef64812..02ea602142e6c 100644 --- a/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/handler/AwtrixLightAppHandler.java +++ b/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/handler/AwtrixLightAppHandler.java @@ -425,7 +425,7 @@ public void handleRemoval() { public void initialize() { this.synchronizationRequired = true; AppConfigOptions config = getConfigAs(AppConfigOptions.class); - if (!this.appName.equals(config.appname)) { + if (!this.appName.isBlank() && !this.appName.equals(config.appname)) { // The app name has changed. Get rid of the old App first and init a new one deleteApp(); } From 6c34c4c88e1d36230e37c4a914383037e1942854 Mon Sep 17 00:00:00 2001 From: Thomas Lauterbach Date: Wed, 19 Feb 2025 23:50:12 +0100 Subject: [PATCH 13/16] Cleanup channel labels Signed-off-by: Thomas Lauterbach --- .../resources/OH-INF/thing/thing-types.xml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/resources/OH-INF/thing/thing-types.xml index aec0950c14d93..3bd34326edecd 100644 --- a/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/resources/OH-INF/thing/thing-types.xml @@ -226,7 +226,7 @@ String - + Effect shown in the background of the app screen @@ -283,7 +283,7 @@ Number:Dimensionless - + Playback speed of background animations time @@ -318,8 +318,8 @@ Switch - - Draws the text on the top of the display + + Aligns the text with the top of the display text @@ -356,7 +356,7 @@ Switch - + Short texts will be centered instead of scrolling text @@ -422,7 +422,7 @@ Switch - + Automatically scales graphs to fit onto display qualityofservice @@ -462,7 +462,7 @@ Color - + Color of progress bar background colorpicker @@ -476,8 +476,8 @@ String - - Name of the melody file in the clocks MELODIES folder + + Name of the melody file in the MELODIES folder text From e9e35c63c6dde44d34d2bc4aac361963817d9549 Mon Sep 17 00:00:00 2001 From: Thomas Lauterbach Date: Thu, 20 Feb 2025 21:07:00 +0100 Subject: [PATCH 14/16] Fixes 3rd review round results Signed-off-by: Thomas Lauterbach --- bom/openhab-addons/pom.xml | 5 ----- .../awtrixlight/internal/app/AwtrixApp.java | 19 +++++++++---------- 2 files changed, 9 insertions(+), 15 deletions(-) diff --git a/bom/openhab-addons/pom.xml b/bom/openhab-addons/pom.xml index 555712addf915..bb0b318825c52 100644 --- a/bom/openhab-addons/pom.xml +++ b/bom/openhab-addons/pom.xml @@ -1206,11 +1206,6 @@ org.openhab.binding.mqtt.awtrixlight ${project.version}
- - org.openhab.addons.bundles - org.openhab.binding.mqtt.awtrixlight - ${project.version} - org.openhab.addons.bundles org.openhab.binding.mqtt.espmilighthub diff --git a/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/app/AwtrixApp.java b/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/app/AwtrixApp.java index d8a31686a6fb9..770ebbbefd193 100644 --- a/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/app/AwtrixApp.java +++ b/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/app/AwtrixApp.java @@ -390,8 +390,8 @@ private boolean getBoolValue(Map params, String key, boolean def if (params.containsKey(key)) { @Nullable Object value = params.get(key); - if (value instanceof Boolean) { - return (Boolean) value; + if (value instanceof Boolean boolValue) { + return boolValue; } } return defaultValue; @@ -443,8 +443,7 @@ private Map getEffectSettingsValues(Map params, if (params.containsKey("effectSettings")) { @Nullable Object value = params.get("effectSettings"); - if (value instanceof Map) { - Map map = (Map) value; + if (value instanceof Map map) { if (map.containsKey("speed")) { Object speed = map.get("speed"); if (speed != null) { @@ -472,8 +471,8 @@ private String getStringValue(Map params, String key, String def if (params.containsKey(key)) { @Nullable Object value = params.get(key); - if (value instanceof String) { - return (String) value; + if (value instanceof String stringValue) { + return stringValue; } } return defaultValue; @@ -604,8 +603,8 @@ public void setEffectSpeed(int effectSpeed) { public String getEffectPalette() { @Nullable Object effectPalette = this.effectSettings.get("palette"); - if (effectPalette instanceof String) { - return (String) effectPalette; + if (effectPalette instanceof String stringValue) { + return stringValue; } else { return DEFAULT_EFFECTPALETTE; } @@ -618,8 +617,8 @@ public void setEffectPalette(String effectPalette) { public Boolean getEffectBlend() { @Nullable Object effectBlend = this.effectSettings.get("blend"); - if (effectBlend instanceof Boolean) { - return (Boolean) effectBlend; + if (effectBlend instanceof Boolean boolValue) { + return boolValue; } else { return DEFAULT_EFFECTBLEND; } From 158312a0625b5e14470d2c169560826f9da92a4d Mon Sep 17 00:00:00 2001 From: Thomas Lauterbach Date: Thu, 20 Feb 2025 21:14:49 +0100 Subject: [PATCH 15/16] Fix warning Signed-off-by: Thomas Lauterbach --- .../awtrixlight/internal/handler/AwtrixLightAppHandler.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/handler/AwtrixLightAppHandler.java b/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/handler/AwtrixLightAppHandler.java index 02ea602142e6c..5f274ba053b24 100644 --- a/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/handler/AwtrixLightAppHandler.java +++ b/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/handler/AwtrixLightAppHandler.java @@ -186,7 +186,7 @@ public void handleCommand(ChannelUID channelUID, Command command) { } break; case CHANNEL_BLINK_TEXT: - if (command instanceof QuantityType quantityCommand) { + if (command instanceof QuantityType quantityCommand) { QuantityType blinkInS = quantityCommand.toUnit(Units.SECOND); if (blinkInS != null) { int blinkInMs = blinkInS.intValue() * 1000; @@ -195,7 +195,7 @@ public void handleCommand(ChannelUID channelUID, Command command) { } break; case CHANNEL_FADE_TEXT: - if (command instanceof QuantityType quantityCommand) { + if (command instanceof QuantityType quantityCommand) { QuantityType fadeInS = quantityCommand.toUnit(Units.SECOND); if (fadeInS != null) { int fadeInMs = fadeInS.intValue() * 1000; From 0f97111e60d2c666be79c93767d88f48b89516d8 Mon Sep 17 00:00:00 2001 From: Thomas Lauterbach Date: Thu, 20 Feb 2025 23:10:56 +0100 Subject: [PATCH 16/16] Accept channel-names as app params input Signed-off-by: Thomas Lauterbach --- .../README.md | 60 ++++++---- .../internal/AwtrixLightBindingConstants.java | 1 - .../awtrixlight/internal/app/AwtrixApp.java | 103 +++++++----------- .../handler/AwtrixLightBridgeHandler.java | 1 + .../mqtt/awtrixlight/internal/HelperTest.java | 39 ------- 5 files changed, 74 insertions(+), 130 deletions(-) diff --git a/bundles/org.openhab.binding.mqtt.awtrixlight/README.md b/bundles/org.openhab.binding.mqtt.awtrixlight/README.md index 563ef05ea7cdb..bab113bfa9ae7 100644 --- a/bundles/org.openhab.binding.mqtt.awtrixlight/README.md +++ b/bundles/org.openhab.binding.mqtt.awtrixlight/README.md @@ -58,32 +58,44 @@ The button events can be used by rules to change the displayed app or perform an ### Bridge Channels (`awtrix-clock`) -| Channel | Type | Read/Write | Description | -|-------------------|----------------------|------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `app` | String | R | Currently active app: Will show the name of the app that is currently shown on the display. | -| `auto-brightness` | Switch | RW | Automatic brightness control: The clock will adjust the display brightness automatically based on ambient light. | -| `battery-level` | Number | R | Battery level: The battery level of the internal battery in percent. | -| `brightness` | Dimmer | RW | Display brightness: The brightness of the display in percent. | -| `button-left` | Trigger | R | Left button press event: Triggered when the left button is pressed/released (Event PRESSED or RELEASED). | -| `button-right` | Trigger | R | Right button press event: Triggered when the right button is pressed/released (Event PRESSED or RELEASED). | -| `button-select` | Trigger | R | Select button press event: Triggered when the select button is pressed/released (Event PRESSED or RELEASED). | -| `display` | Switch | RW | Display on/off: Switches the display on or off. The clock will still stay on while the display is off. | -| `humidity` | Number:Dimensionless | R | Relative humidity: Relative humidity in percent. For the Ulanzi clock values are usually very inaccurate. | -| `indicator-1` | Switch | RW | Control first indicator LED: Switches the first indicator LED on or off. The color of the LED will be green but can be customised by using thing actions (you can also use blinking/fading effects). | -| `indicator-2` | Switch | RW | Control second indicator LED: Switches the second indicator LED on or off.The color of the LED will be green but can be customised by using thing actions (you can also use blinking/fading effects). | -| `indicator-3` | Switch | RW | Control third indicator LED: Switches the third indicator LED on or off. The color of the LED will be green but can be customised by using thing actions (you can also use blinking/fading effects). | -| `low-battery` | Switch | R | Low battery warning: Will be switched ON as soon as the battery level drops below the lowBatteryThreshold set for the bridge. | -| `lux` | Number:Illuminance | R | Ambient light level: Ambient light level in lux as measured by the built-in light sensor. | -| `rssi` | Number:Dimensionless | R | WiFi signal strength (RSSI): WiFi signal strength (RSSI) in dBm. | -| `rtttl` | String | W | Play RTTTL ringtone: Play a ringtone specified in RTTTL format (see https://de.wikipedia.org/wiki/Ring_Tones_Text_Transfer_Language) | -| `screen` | String | R | Screen image: Allows you to mirror the screen image from the clock. The screen image will be updated automatically when the app changes but can be updated manually by sending a RefreshType command to the channel. | -| `sound` | String | W | Play sound file: The sound file must be available on the clock device in the MELODIES folder. Save a file with a valid RTTTL string (e.g. melody.txt) in this folder and play it by sending a String command to the channel with the filename without file extension (e.g. "melody"). | -| `temperature` | Number:Temperature | R | Device temperature: Temperature in °C as measured by the built-in temperature sensor. For the Ulanzi clock values are usually very inaccurate. | +| Channel | Action parameter (see Actions) | Read/Write | Description | +|-----------------------|------------------------------------------|------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `active` | - | RW | Enable/disable the app: Switches the app on or off. Note that channels of inactive apps will be reset to their default values during a restart of openHAB. | +| `autoscale` | `autoscale` | RW | Enable/disable autoscaling for bar and linechart. | +| `background` | `background` | RW | Sets a background color. | +| `bar` | `bar` | RW | Shows a bar chart: Send a string with values separated by commas (e.g. "value1,value2,value3"). Only the last 16 values will be displayed. | +| `blink` | `blinkText` | RW | Blink text: Blink the text in the specified interval. Ignored if gradientColor or rainbow are set. | +| `center` | `center` | RW | Center short text horizontally and disable scrolling. | +| `color` | `color` | RW | Text, bar or line chart color. | +| `duration` | `duration` | RW | Display duration in seconds. | +| `effect` | `effectSettings` | RW | Display effect (see https://blueforcer.github.io/awtrix3/#/effects for possible values). | +| `effect-blend` | `blend` as key in `effectSettings` | RW | Enable smoother effect transitions. Only to be used with effect. | +| `effect-palette` | `palette` as key in `effectSettings` Map | RW | Color palette for effects (see https://blueforcer.github.io/awtrix3/#/effects for possible values and how to create custom palettes). Only to be used with effect. | +| `effect-speed` | `speed` as key in `effectSettings` Map | RW | Effect animation speed: Higher means faster (see https://blueforcer.github.io/awtrix3/#/effects). Only to be used with effect. | +| `fade` | `fadeText` | RW | Fade text: Fades the text in and out in the specified interval. Ignored if gradientColor or rainbow are set. | +| `gradient-color` | `gradient` | RW | Secondary color for gradient effects. Use color for setting the primary color. | +| `icon` | `icon` | RW | Icon name to display: Install icons on the clock device first. | +| `lifetime` | `lifetime` | RW | App lifetime: Define how long the app will remain active on the clock. | +| `lifetime-mode` | `lifetimeMode` | RW | Lifetime mode: Define if the app should be deleted (Command DELETE) or marked as stale (Command STALE) after lifetime. | +| `line` | `line` | RW | Shows a line chart: Send a string with values separated by commas (e.g. "value1,value2,value3"). Only the last 16 values will be displayed. | +| `overlay` | `overlay` | RW | Enable overlay mode: Shows a weather overlay effect (can be any of clear, snow, rain, drizzle, storm, thunder, frost). | +| `progress` | `progress` | RW | Progress value: Shows a progress bar at the bottom of the app with the specified percentage value. | +| `progress-background` | `progressBC` | RW | Progress bar background color: Background color for the progress bar. | +| `progress-color` | `progressC` | RW | Progress bar color: Color for the progress bar. | +| `push-icon` | `pushIcon` | RW | Push icon animation (STATIC=Icon doesn't move, PUSHOUT=Icon moves with text and will not appear again, PUSHOUTRETURN=Icon moves with text but appears again when the text starts to scroll again). | +| `rainbow` | `rainbow` | RW | Enable rainbow effect: Uses a rainbow effect for the displayed text. | +| `reset` | - | RW | Reset app to default state: All channels will be reset to their default values. | +| `scroll` | `scrollText` | RW | Scroll text: Scroll the text in the specified interval. Ignored if gradientColor or rainbow are set. | +| `scroll-speed` | `scrollSpeed` | RW | Text scrolling speed: Provide as percentage value. The original speed is 100%. Values above 100% will increase the scrolling speed, values below 100% will decrease it. Setting this value to 0 will disable scrolling completely. | +| `text` | `text` | RW | Text to display. | +| `text-case` | `textCase` | RW | Set text case (0=normal, 1=uppercase, 2=lowercase). | +| `text-offset` | `textOffset` | RW | Text offset position: Horizontal offset of the text in pixels. | +| `top-text` | `topText` | RW | Draws the text on the top of the display. | ### App Channels (`awtrix-app`) -| Channel | Action parameter (see Actions) | Action parameter type | Type | Read/Write | Description | | -|-----------------------|------------------------------------------|------------------------------------------------|----------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---| +| Channel | Type | Read/Write | Description | | +|-----------------------|-----------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---| | `active` | - | Switch | W | Enable/disable the app: Switches the app on or off. Note that channels of inactive apps will be reset to their default values during a restart of openHAB. | | | | `autoscale` | `autoscale` | boolean | Switch | RW | Enable/disable autoscaling for bar and linechart. | | | `background` | `background` | int[] (rgb-Array) | Color | RW | Sets a background color. | | @@ -236,7 +248,7 @@ The following actions are supported: Show a notification with maximal customization options. Map<String, Object> appParams - Map that holds any parameter that is available for an Awtrix App as shown in the App Channels section of the documentation. Please use the values shown in the Action parameter column of the App Channels section as keys. + Map that holds any parameter that is available for an Awtrix App as shown in the App Channels section of the documentation. Use the channel ids as keys. boolean diff --git a/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/AwtrixLightBindingConstants.java b/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/AwtrixLightBindingConstants.java index 4e75fb8cecd12..1e945ae492f1a 100644 --- a/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/AwtrixLightBindingConstants.java +++ b/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/AwtrixLightBindingConstants.java @@ -160,7 +160,6 @@ public class AwtrixLightBindingConstants { public static final String CHANNEL_PROGRESSBC = "progress-background"; public static final String CHANNEL_PUSH_ICON = "push-icon"; public static final String CHANNEL_RAINBOW = "rainbow"; - public static final String CHANNEL_REPEAT = "repeat"; public static final String CHANNEL_RESET = "reset"; public static final String CHANNEL_SCROLLSPEED = "scroll-speed"; public static final String CHANNEL_TEXT = "text"; diff --git a/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/app/AwtrixApp.java b/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/app/AwtrixApp.java index 770ebbbefd193..59daff54fd660 100644 --- a/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/app/AwtrixApp.java +++ b/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/app/AwtrixApp.java @@ -12,6 +12,8 @@ */ package org.openhab.binding.mqtt.awtrixlight.internal.app; +import static org.openhab.binding.mqtt.awtrixlight.internal.AwtrixLightBindingConstants.*; + import java.util.Arrays; import java.util.HashMap; import java.util.Map; @@ -96,37 +98,37 @@ public AwtrixApp() { } public void updateFields(Map params) { - this.text = getStringValue(params, "text", DEFAULT_TEXT); - this.textCase = getNumberValue(params, "textCase", DEFAULT_TEXTCASE); - this.topText = getBoolValue(params, "topText", DEFAULT_TOPTEXT); - this.textOffset = getNumberValue(params, "textOffset", DEFAULT_TEXTOFFSET); - this.center = getBoolValue(params, "center", DEFAULT_CENTER); - this.color = getNumberArrayValue(params, "color", DEFAULT_COLOR); + this.text = getStringValue(params, CHANNEL_TEXT, DEFAULT_TEXT); + this.textCase = getNumberValue(params, CHANNEL_TEXTCASE, DEFAULT_TEXTCASE); + this.topText = getBoolValue(params, CHANNEL_TOP_TEXT, DEFAULT_TOPTEXT); + this.textOffset = getNumberValue(params, CHANNEL_TEXT_OFFSET, DEFAULT_TEXTOFFSET); + this.center = getBoolValue(params, CHANNEL_CENTER, DEFAULT_CENTER); + this.color = getNumberArrayValue(params, CHANNEL_COLOR, DEFAULT_COLOR); this.gradient = getGradientValue(params, DEFAULT_GRADIENT); - this.blinkText = getNumberValue(params, "blinkText", DEFAULT_BLINKTEXT); - this.fadeText = getNumberValue(params, "fadeText", DEFAULT_FADETEXT); - this.background = getNumberArrayValue(params, "background", DEFAULT_BACKGROUND); - this.rainbow = getBoolValue(params, "rainbow", DEFAULT_RAINBOW); - this.icon = getStringValue(params, "icon", DEFAULT_ICON); - this.pushIcon = getNumberValue(params, "pushIcon", DEFAULT_PUSHICON); - this.duration = getNumberValue(params, "duration", DEFAULT_DURATION); - this.line = getNumberArrayValue(params, "line", DEFAULT_LINE); - this.lifetime = getNumberValue(params, "lifetime", DEFAULT_LIFETIME); - this.lifetimeMode = getNumberValue(params, "lifetimeMode", DEFAULT_LIFETIME_MODE); - this.bar = getNumberArrayValue(params, "bar", DEFAULT_BAR); - this.autoscale = getBoolValue(params, "autoscale", DEFAULT_AUTOSCALE); - this.overlay = getStringValue(params, "overlay", DEFAULT_OVERLAY); - this.progress = getNumberValue(params, "progress", DEFAULT_PROGRESS); - this.progressC = getNumberArrayValue(params, "progressC", DEFAULT_PROGRESSC); - this.progressBC = getNumberArrayValue(params, "progressBC", DEFAULT_PROGRESSBC); - this.scrollSpeed = getNumberValue(params, "scrollSpeed", DEFAULT_SCROLLSPEED); - this.effect = getStringValue(params, "effect", DEFAULT_EFFECT); - - Map defaultEffectSettings = new HashMap(); - defaultEffectSettings.put("speed", DEFAULT_EFFECTSPEED); - defaultEffectSettings.put("palette", DEFAULT_EFFECTPALETTE); - defaultEffectSettings.put("blend", DEFAULT_EFFECTBLEND); - this.effectSettings = getEffectSettingsValues(params, defaultEffectSettings); + this.blinkText = getNumberValue(params, CHANNEL_BLINK_TEXT, DEFAULT_BLINKTEXT); + this.fadeText = getNumberValue(params, CHANNEL_FADE_TEXT, DEFAULT_FADETEXT); + this.background = getNumberArrayValue(params, CHANNEL_BACKGROUND, DEFAULT_BACKGROUND); + this.rainbow = getBoolValue(params, CHANNEL_RAINBOW, DEFAULT_RAINBOW); + this.icon = getStringValue(params, CHANNEL_ICON, DEFAULT_ICON); + this.pushIcon = getNumberValue(params, CHANNEL_PUSH_ICON, DEFAULT_PUSHICON); + this.duration = getNumberValue(params, CHANNEL_DURATION, DEFAULT_DURATION); + this.line = getNumberArrayValue(params, CHANNEL_LINE, DEFAULT_LINE); + this.lifetime = getNumberValue(params, CHANNEL_LIFETIME, DEFAULT_LIFETIME); + this.lifetimeMode = getNumberValue(params, CHANNEL_LIFETIME_MODE, DEFAULT_LIFETIME_MODE); + this.bar = getNumberArrayValue(params, CHANNEL_BAR, DEFAULT_BAR); + this.autoscale = getBoolValue(params, CHANNEL_AUTOSCALE, DEFAULT_AUTOSCALE); + this.overlay = getStringValue(params, CHANNEL_OVERLAY, DEFAULT_OVERLAY); + this.progress = getNumberValue(params, CHANNEL_PROGRESS, DEFAULT_PROGRESS); + this.progressC = getNumberArrayValue(params, CHANNEL_PROGRESSC, DEFAULT_PROGRESSC); + this.progressBC = getNumberArrayValue(params, CHANNEL_PROGRESSBC, DEFAULT_PROGRESSBC); + this.scrollSpeed = getNumberValue(params, CHANNEL_SCROLLSPEED, DEFAULT_SCROLLSPEED); + this.effect = getStringValue(params, CHANNEL_EFFECT, DEFAULT_EFFECT); + + Map effectSettings = new HashMap(); + effectSettings.put("speed", getNumberValue(params, CHANNEL_EFFECT_SPEED, DEFAULT_EFFECTSPEED)); + effectSettings.put("palette", getStringValue(params, CHANNEL_EFFECT_PALETTE, DEFAULT_EFFECTPALETTE)); + effectSettings.put("blend", getBoolValue(params, CHANNEL_EFFECT_BLEND, DEFAULT_EFFECTBLEND)); + this.effectSettings = effectSettings; } public String getAppConfig() { @@ -420,53 +422,22 @@ private int[] getNumberArrayValue(Map params, String key, int[] } private int[][] getGradientValue(Map params, int[][] defaultValue) { - if (params.containsKey("gradient")) { + if (params.containsKey(CHANNEL_GRADIENT_COLOR)) { @Nullable - Object gradientParam = params.get("gradient"); - // Check if we got a complete gradient with two colors - if (gradientParam instanceof int[][] gradient) { - return gradient; - } - // Check if we got a single color for the gradient + Object gradientParam = params.get(CHANNEL_GRADIENT_COLOR); if (gradientParam instanceof int[] gradient) { @Nullable - Object colorParam = params.get("color"); + Object colorParam = params.get(CHANNEL_COLOR); if (colorParam instanceof int[] color) { return new int[][] { color, gradient }; + } else { + return new int[][] { DEFAULT_COLOR, gradient }; } } } return defaultValue; } - private Map getEffectSettingsValues(Map params, Map defaultValues) { - if (params.containsKey("effectSettings")) { - @Nullable - Object value = params.get("effectSettings"); - if (value instanceof Map map) { - if (map.containsKey("speed")) { - Object speed = map.get("speed"); - if (speed != null) { - defaultValues.put("speed", speed); - } - } - if (map.containsKey("palette")) { - Object palette = map.get("palette"); - if (palette != null) { - defaultValues.put("palette", palette); - } - } - if (map.containsKey("blend")) { - Object blend = map.get("blend"); - if (blend != null) { - defaultValues.put("blend", blend); - } - } - } - } - return defaultValues; - } - private String getStringValue(Map params, String key, String defaultValue) { if (params.containsKey(key)) { @Nullable diff --git a/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/handler/AwtrixLightBridgeHandler.java b/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/handler/AwtrixLightBridgeHandler.java index 3554b1db4a17e..c7202c0f24dbd 100644 --- a/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/handler/AwtrixLightBridgeHandler.java +++ b/bundles/org.openhab.binding.mqtt.awtrixlight/src/main/java/org/openhab/binding/mqtt/awtrixlight/internal/handler/AwtrixLightBridgeHandler.java @@ -236,6 +236,7 @@ public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) { } ThingHandler handler = localBridge.getHandler(); if (handler instanceof AbstractBrokerHandler abh) { + @Nullable final MqttBrokerConnection connection; try { connection = abh.getConnectionAsync().get(500, TimeUnit.MILLISECONDS); diff --git a/bundles/org.openhab.binding.mqtt.awtrixlight/src/test/java/org/openhab/binding/mqtt/awtrixlight/internal/HelperTest.java b/bundles/org.openhab.binding.mqtt.awtrixlight/src/test/java/org/openhab/binding/mqtt/awtrixlight/internal/HelperTest.java index 8aa866d204d5d..535d164f0b46d 100644 --- a/bundles/org.openhab.binding.mqtt.awtrixlight/src/test/java/org/openhab/binding/mqtt/awtrixlight/internal/HelperTest.java +++ b/bundles/org.openhab.binding.mqtt.awtrixlight/src/test/java/org/openhab/binding/mqtt/awtrixlight/internal/HelperTest.java @@ -141,18 +141,6 @@ public void copyAppViaJson() { assertEquals(app.toString(), app2.toString()); } - @Test - public void copyAppViaParams() { - AwtrixApp app = getTestApp(); - Map appParams = app.getAppParams(); - - AwtrixApp app2 = new AwtrixApp(); - app2.updateFields(appParams); - - assertEquals(app.getAppConfig(), app2.getAppConfig()); - assertEquals(app.toString(), app2.toString()); - } - @Test public void copyAppViaJsonWithGradient() { AwtrixApp app = getTestAppWithGradient(); @@ -162,17 +150,6 @@ public void copyAppViaJsonWithGradient() { assertEquals(json, app2.getAppConfig()); } - @Test - public void copyAppViaParamsWithGradient() { - AwtrixApp app = getTestAppWithGradient(); - Map appParams = app.getAppParams(); - - AwtrixApp app2 = new AwtrixApp(); - app2.updateFields(appParams); - - assertEquals(app.getAppConfig(), app2.getAppConfig()); - } - @Test public void copyAppViaJsonWithIncompatibleOptions() { AwtrixApp app = getTestAppWithIncompatibleOptions(); @@ -187,22 +164,6 @@ public void copyAppViaJsonWithIncompatibleOptions() { assertEquals(json, app2.getAppConfig()); } - @Test - public void copyAppViaParamsWithIncompatibleOptions() { - AwtrixApp app = getTestAppWithIncompatibleOptions(); - Map appParams = app.getAppParams(); - - AwtrixApp app2 = new AwtrixApp(); - app2.updateFields(appParams); - - // Incompatible options are not copied to the new app - assertNotEquals(app.getRainbow(), app2.getRainbow()); - assertEquals(app.getFadeText(), app2.getFadeText()); - - // But the generated json should still be the same - assertEquals(app.getAppConfig(), app2.getAppConfig()); - } - @Test public void trimArray() { int[] untrimmed = { 0, 1, 10, 1000 };