[mqtt.awtrixlight] Add LWT support and improve ThingStatus handling (#18658)

* Add LWT support and improve ThingStatus handling

Signed-off-by: Thomas Lauterbach <lauterbachthomas@gmail.com>
This commit is contained in:
Thomas Lauterbach
2025-05-16 12:11:13 +02:00
committed by GitHub
parent f7c5617735
commit 468e16ea22
8 changed files with 252 additions and 10 deletions
@@ -57,6 +57,7 @@ public class AwtrixLightBindingConstants {
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_LWT = "/stats/device";
public static final String TOPIC_NOTIFY = "/notify";
public static final String TOPIC_POWER = "/power";
public static final String TOPIC_REBOOT = "/reboot";
@@ -67,9 +67,11 @@ public class Helper {
String[] pixelStrings = cutBrackets.split(",");
int[] values = Arrays.stream(pixelStrings).mapToInt(Integer::parseInt).toArray();
int numPixels = SCREEN_HEIGHT * SCREEN_WIDTH;
int[][] pixels = new int[SCREEN_HEIGHT][SCREEN_WIDTH];
for (int i = 0; i < 256; i++) {
pixels[i / 32][i % 32] = values[i];
for (int i = 0; i < numPixels; i++) {
pixels[i / SCREEN_WIDTH][i % SCREEN_WIDTH] = values[i];
}
// Resize and add gaps between pixels
@@ -97,7 +99,7 @@ public class Helper {
}
}
byte[] bytes = new byte[256];
byte[] bytes = null;
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ImageIO.write(image, "png", baos);
@@ -631,7 +631,14 @@ public class AwtrixLightAppHandler extends BaseThingHandler implements MqttMessa
updateApp();
}
}
updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE);
ThingStatus bridgeStatus = getBridgeStatus().getStatus();
if (ThingStatus.ONLINE == bridgeStatus) {
updateStatus(ThingStatus.ONLINE);
} else if (ThingStatus.OFFLINE == bridgeStatus) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
} else {
updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.BRIDGE_UNINITIALIZED);
}
Future<?> localJob = this.finishInitJob;
if (localJob != null) {
localJob.cancel(true);
@@ -81,6 +81,7 @@ public class AwtrixLightBridgeHandler extends BaseBridgeHandler implements MqttM
private String channelPrefix = "";
private String currentApp = "";
private boolean discoverDefaultApps = false;
private boolean deviceOnline = false;
private int lowBatteryThreshold = 25;
private Map<String, AwtrixLightAppHandler> appHandlers = new HashMap<String, AwtrixLightAppHandler>();
@@ -187,10 +188,16 @@ public class AwtrixLightBridgeHandler extends BaseBridgeHandler implements MqttM
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) {
ThingStatusInfo statusInfo = getThing().getStatusInfo();
if (ThingStatus.UNKNOWN == statusInfo.getStatus()
&& ThingStatusDetail.CONFIGURATION_PENDING == statusInfo.getStatusDetail()) {
// Obviously the device is online. We just haven't received a LWT message yet.
this.deviceOnline = true;
updateStatus(ThingStatus.ONLINE);
}
handleStatsMessage(payloadString);
} else if (topic.endsWith(TOPIC_LWT)) {
handleLwtMessage(payloadString);
} else if (topic.endsWith(TOPIC_STATS_CURRENT_APP)) {
handleCurrentAppMessage(payloadString);
} else if (topic.endsWith(TOPIC_SCREEN)) {
@@ -236,6 +243,7 @@ public class AwtrixLightBridgeHandler extends BaseBridgeHandler implements MqttM
}
ThingHandler handler = localBridge.getHandler();
if (handler instanceof AbstractBrokerHandler abh) {
@Nullable
final MqttBrokerConnection connection;
try {
connection = abh.getConnectionAsync().get(500, TimeUnit.MILLISECONDS);
@@ -245,11 +253,18 @@ public class AwtrixLightBridgeHandler extends BaseBridgeHandler implements MqttM
return;
}
this.connection = connection;
updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.CONFIGURATION_PENDING,
"Waiting for first MQTT message to be received.");
if (this.deviceOnline) {
updateStatus(ThingStatus.ONLINE);
} else {
updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.CONFIGURATION_PENDING, "Waiting for LWT message.");
}
connection.subscribe(this.basetopic + TOPIC_LWT, this);
connection.subscribe(this.basetopic + TOPIC_STATS + "/#", this);
connection.subscribe(this.basetopic + TOPIC_STATS_CURRENT_APP + "/#", this);
connection.subscribe(this.basetopic + TOPIC_SCREEN + "/#", this);
for (Map.Entry<String, AwtrixLightAppHandler> entry : this.appHandlers.entrySet()) {
connection.subscribe(this.basetopic + "/custom/" + entry.getKey(), entry.getValue());
}
}
return;
}
@@ -263,6 +278,7 @@ public class AwtrixLightBridgeHandler extends BaseBridgeHandler implements MqttM
leaveAppControlMode();
MqttBrokerConnection localConnection = connection;
if (localConnection != null) {
localConnection.unsubscribe(this.basetopic + TOPIC_LWT, this);
localConnection.unsubscribe(this.basetopic + TOPIC_STATS + "/#", this);
localConnection.unsubscribe(this.basetopic + TOPIC_STATS_CURRENT_APP + "/#", this);
localConnection.unsubscribe(this.basetopic + TOPIC_SCREEN + "/#", this);
@@ -634,4 +650,9 @@ public class AwtrixLightBridgeHandler extends BaseBridgeHandler implements MqttM
}
}
}
private void handleLwtMessage(String lwtMessage) {
this.deviceOnline = "online".equals(lwtMessage);
updateStatus(this.deviceOnline ? ThingStatus.ONLINE : ThingStatus.OFFLINE);
}
}
@@ -0,0 +1,162 @@
# thing types
thing-type.mqtt.awtrix-app.label = Awtrix App
thing-type.mqtt.awtrix-app.description = An app for an Awtrix Light device
thing-type.mqtt.awtrix-app.channel.active.label = App Active
thing-type.mqtt.awtrix-app.channel.active.description = App is removed from rotation while inactive
thing-type.mqtt.awtrix-app.channel.button-left.label = Button Left
thing-type.mqtt.awtrix-app.channel.button-left.description = Left button pressed
thing-type.mqtt.awtrix-app.channel.button-right.label = Button Right
thing-type.mqtt.awtrix-app.channel.button-right.description = Right button pressed
thing-type.mqtt.awtrix-app.channel.button-select.label = Button Select
thing-type.mqtt.awtrix-app.channel.button-select.description = Select button pressed
thing-type.mqtt.awtrix-clock.label = Awtrix Light Device
thing-type.mqtt.awtrix-clock.description = Device with Awtrix Light firmware
thing-type.mqtt.awtrix-clock.channel.button-left.label = Button Left
thing-type.mqtt.awtrix-clock.channel.button-left.description = Left button pressed
thing-type.mqtt.awtrix-clock.channel.button-right.label = Button Right
thing-type.mqtt.awtrix-clock.channel.button-right.description = Right button pressed
thing-type.mqtt.awtrix-clock.channel.button-select.label = Button Select
thing-type.mqtt.awtrix-clock.channel.button-select.description = Select button pressed
thing-type.mqtt.awtrix-clock.channel.display.label = Display Power
thing-type.mqtt.awtrix-clock.channel.display.description = Switches the display ON or OFF
thing-type.mqtt.awtrix-clock.channel.indicator-1.label = Indicator 1
thing-type.mqtt.awtrix-clock.channel.indicator-2.label = Indicator 2
thing-type.mqtt.awtrix-clock.channel.indicator-3.label = Indicator 3
# thing types config
thing-type.config.mqtt.awtrix-app.appname.label = App Name
thing-type.config.mqtt.awtrix-app.appname.description = Name of the app
thing-type.config.mqtt.awtrix-app.useButtons.label = Button Controlled
thing-type.config.mqtt.awtrix-app.useButtons.description = Activate if the app can be controlled by the user. The select button locks on current app and the app will emit subsequent button events until the appLockTimeout of the bridge is reached. Every button press before the timeout will reset timeout.
thing-type.config.mqtt.awtrix-clock.appLockTimeout.label = App Selection Timeout
thing-type.config.mqtt.awtrix-clock.appLockTimeout.description = Timeout in seconds until selected apps return control and the app rotation continues.
thing-type.config.mqtt.awtrix-clock.basetopic.label = Base Topic
thing-type.config.mqtt.awtrix-clock.basetopic.description = Base topic as configured in the Awtrix Light device.
thing-type.config.mqtt.awtrix-clock.discoverDefaultApps.label = Discover Built-in Apps
thing-type.config.mqtt.awtrix-clock.discoverDefaultApps.description = Currently changing settings for the built-in default apps is not implemented. It is therefore recommended to ignore them during app discovery.
thing-type.config.mqtt.awtrix-clock.lowBatteryThreshold.label = Low Battery Threshold
thing-type.config.mqtt.awtrix-clock.lowBatteryThreshold.description = Threshold for issuing a low battery warning.
# channel types
channel-type.mqtt.app.label = Active App
channel-type.mqtt.app.description = App currently shown on screen
channel-type.mqtt.auto-brightness.label = Automatic Brightness
channel-type.mqtt.auto-brightness.description = Let the clock set brightness automatically based on internal brightness sensor readings
channel-type.mqtt.autoscale.label = Autoscale Graphs
channel-type.mqtt.autoscale.description = Automatically scales graphs to fit onto display
channel-type.mqtt.background.label = Background Color
channel-type.mqtt.background.description = Color for display background
channel-type.mqtt.bar.label = Bar Graph
channel-type.mqtt.bar.description = Draw a bar graph (format: "value1,value2,value3", last 16 entries will be displayed, last 11 with icon)
channel-type.mqtt.blink-text.label = Blink Text
channel-type.mqtt.blink-text.description = Blink text in specified time interval
channel-type.mqtt.brightness.label = Display Brightness
channel-type.mqtt.brightness.description = Screen brightness in percent
channel-type.mqtt.center.label = Center Text
channel-type.mqtt.center.description = Short texts will be centered instead of scrolling
channel-type.mqtt.duration.label = Duration
channel-type.mqtt.duration.description = How long the app should be displayed
channel-type.mqtt.effect-blend.label = Smooth Effects
channel-type.mqtt.effect-blend.description = Smoother effect animations
channel-type.mqtt.effect-palette.label = Effect Color Palette
channel-type.mqtt.effect-palette.description = Changes the color scheme of the effect settings
channel-type.mqtt.effect-palette.state.option.None = None
channel-type.mqtt.effect-palette.state.option.Cloud = Cloud
channel-type.mqtt.effect-palette.state.option.Lava = Lava
channel-type.mqtt.effect-palette.state.option.Ocean = Ocean
channel-type.mqtt.effect-palette.state.option.Forest = Forest
channel-type.mqtt.effect-palette.state.option.Stripe = Stripe
channel-type.mqtt.effect-palette.state.option.Party = Party
channel-type.mqtt.effect-palette.state.option.Heat = Heat
channel-type.mqtt.effect-palette.state.option.Rainbow = Rainbow
channel-type.mqtt.effect-speed.label = Background Effect Speed
channel-type.mqtt.effect-speed.description = Playback speed of background animations
channel-type.mqtt.effect.label = Background Effect
channel-type.mqtt.effect.description = Effect shown in the background of the app
channel-type.mqtt.effect.state.option.None = None
channel-type.mqtt.effect.state.option.BrickBreaker = BrickBreaker
channel-type.mqtt.effect.state.option.Fireworks = Fireworks
channel-type.mqtt.effect.state.option.Radar = Radar
channel-type.mqtt.effect.state.option.Snake = Snake
channel-type.mqtt.effect.state.option.TheaterChase = Theater Chase
channel-type.mqtt.effect.state.option.SwirlOut = Swirl Out
channel-type.mqtt.effect.state.option.LookingEyes = Looking Eyes
channel-type.mqtt.effect.state.option.Pacifica = Pacifica
channel-type.mqtt.effect.state.option.PlasmaCloud = Plasma Cloud
channel-type.mqtt.effect.state.option.Checkerboard = Checkerboard
channel-type.mqtt.effect.state.option.PingPong = Ping Pong
channel-type.mqtt.effect.state.option.Ripple = Ripple
channel-type.mqtt.effect.state.option.TwinklingStars = Twinkling Stars
channel-type.mqtt.effect.state.option.ColorWaves = Color Waves
channel-type.mqtt.effect.state.option.SwirlIn = Swirl In
channel-type.mqtt.effect.state.option.Matrix = Matrix
channel-type.mqtt.effect.state.option.Plasma = Plasma
channel-type.mqtt.effect.state.option.MovingLine = Moving Line
channel-type.mqtt.fade-text.label = Fade Text
channel-type.mqtt.fade-text.description = Fade text in specified interval
channel-type.mqtt.gradient-color.label = Gradient Color
channel-type.mqtt.gradient-color.description = Color text as gradient from Main Color to Gradient Color
channel-type.mqtt.icon.label = Icon
channel-type.mqtt.icon.description = Icon ID or filename without extension
channel-type.mqtt.indicator.label = Indicator
channel-type.mqtt.indicator.description = Indicator state
channel-type.mqtt.lifetime-mode.label = App Lifetime Mode
channel-type.mqtt.lifetime-mode.description = Delete the app or mark as stale after lifetime
channel-type.mqtt.lifetime-mode.state.option.DELETE = Delete
channel-type.mqtt.lifetime-mode.state.option.STALE = Mark stale
channel-type.mqtt.lifetime.label = App Lifetime
channel-type.mqtt.lifetime.description = Remove the app if there was no update within specified seconds
channel-type.mqtt.line.label = Line Graph
channel-type.mqtt.line.description = Draw a line graph (format: "value1,value2,value3", last 16 entries will be displayed, last 11 with icon)
channel-type.mqtt.lux.label = Lux
channel-type.mqtt.lux.description = Brightness in lux
channel-type.mqtt.main-color.label = Main Color
channel-type.mqtt.main-color.description = Color to display text and charts on the screen
channel-type.mqtt.overlay.label = App Overlay
channel-type.mqtt.overlay.description = Overlay effect (overriden by global clock overlay)
channel-type.mqtt.overlay.state.option.clear = Clear
channel-type.mqtt.overlay.state.option.snow = Snow
channel-type.mqtt.overlay.state.option.rain = Rain
channel-type.mqtt.overlay.state.option.drizzle = Drizzle
channel-type.mqtt.overlay.state.option.storm = Storm
channel-type.mqtt.overlay.state.option.thunder = Thunder
channel-type.mqtt.overlay.state.option.frost = Frost
channel-type.mqtt.progress-background.label = Progress Bar Background
channel-type.mqtt.progress-background.description = Color of progress bar background
channel-type.mqtt.progress-color.label = Progress Bar Color
channel-type.mqtt.progress-color.description = Color of progress bar
channel-type.mqtt.progress.label = Progress Bar
channel-type.mqtt.progress.description = Show progress bar with specified percentage
channel-type.mqtt.push-icon.label = Scroll Icon
channel-type.mqtt.push-icon.description = Make the icon scroll along with the text
channel-type.mqtt.push-icon.state.option.STATIC = Icon does not move
channel-type.mqtt.push-icon.state.option.PUSHOUT = Icon moves and disappears
channel-type.mqtt.push-icon.state.option.PUSHOUTRETURN = Icon moves and reappears
channel-type.mqtt.rainbow.label = Rainbow Color
channel-type.mqtt.rainbow.description = Fades the text color in rainbow colors
channel-type.mqtt.reset.label = Reset
channel-type.mqtt.reset.description = Resets the app and the linked items to the default settings
channel-type.mqtt.rssi.label = WiFi Signal
channel-type.mqtt.rssi.description = RSSI value of WiFi signal
channel-type.mqtt.rtttl.label = Play RTTTL
channel-type.mqtt.rtttl.description = Ring Tone Text Transfer Language (RTTTL) compliant sound string
channel-type.mqtt.screen.label = Screen Mirror
channel-type.mqtt.screen.description = Mirror screen image from the clock. By default only refreshed on app change. Refresh can be also triggered with a RefreshType command.
channel-type.mqtt.scroll-speed.label = Scroll Speed
channel-type.mqtt.scroll-speed.description = Speed of text scrolling as percentage of default speed
channel-type.mqtt.sound.label = Play Melody
channel-type.mqtt.sound.description = Name of the melody file in the MELODIES folder
channel-type.mqtt.text-case.label = Text Case
channel-type.mqtt.text-case.description = Change case of displayed text. Default uses the global preset.
channel-type.mqtt.text-case.state.option.0 = Default
channel-type.mqtt.text-case.state.option.1 = Uppercase
channel-type.mqtt.text-case.state.option.2 = Unchanged
channel-type.mqtt.text-offset.label = Text Offset
channel-type.mqtt.text-offset.description = Offset on x-axis in pixel for displayed text
channel-type.mqtt.text.label = Display Text
channel-type.mqtt.text.description = Text displayed in the app
channel-type.mqtt.top-text.label = Top Text
channel-type.mqtt.top-text.description = Aligns the text with the top of the display
@@ -34,9 +34,15 @@
<autoUpdatePolicy>recommend</autoUpdatePolicy>
</channel>
<channel id="humidity" typeId="system.atmospheric-humidity"/>
<channel id="indicator-1" typeId="indicator"/>
<channel id="indicator-2" typeId="indicator"/>
<channel id="indicator-3" typeId="indicator"/>
<channel id="indicator-1" typeId="indicator">
<label>Indicator 1</label>
</channel>
<channel id="indicator-2" typeId="indicator">
<label>Indicator 2</label>
</channel>
<channel id="indicator-3" typeId="indicator">
<label>Indicator 3</label>
</channel>
<channel id="low-battery" typeId="system.low-battery"/>
<channel id="lux" typeId="lux"/>
<channel id="rssi" typeId="rssi"/>
@@ -49,6 +55,7 @@
<property name="firmware"></property>
<property name="uniqueId"></property>
<property name="vendor"></property>
<property name="thingTypeVersion">1</property>
</properties>
<representation-property>uniqueId</representation-property>
<config-description-ref uri="thing-type:mqtt:awtrix-clock"/>
@@ -63,6 +70,8 @@
<category>screen</category>
<channels>
<channel id="active" typeId="system.power">
<label>App Active</label>
<description>App is removed from rotation while inactive</description>
<autoUpdatePolicy>recommend</autoUpdatePolicy>
</channel>
<channel id="reset" typeId="reset">
@@ -111,6 +120,7 @@
</channels>
<properties>
<property name="appid"></property>
<property name="thingTypeVersion">1</property>
</properties>
<representation-property>appid</representation-property>
<config-description-ref uri="thing-type:mqtt:awtrix-app"/>
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<update:update-descriptions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:update="https://openhab.org/schemas/update-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/update-description/v1.0.0 https://openhab.org/schemas/update-description-1.0.0.xsd">
<thing-type uid="mqtt:awtrix-app">
<instruction-set targetVersion="1">
<update-channel id="active">
<type>system:power</type>
<label>App Active</label>
<description>App is removed from rotation while inactive</description>
</update-channel>
</instruction-set>
</thing-type>
</update:update-descriptions>
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<update:update-descriptions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:update="https://openhab.org/schemas/update-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/update-description/v1.0.0 https://openhab.org/schemas/update-description-1.0.0.xsd">
<thing-type uid="mqtt:awtrix-clock">
<instruction-set targetVersion="1">
<update-channel id="indicator-1">
<type>mqtt:indicator</type>
<label>Indicator 1</label>
</update-channel>
<update-channel id="indicator-2">
<type>mqtt:indicator</type>
<label>Indicator 2</label>
</update-channel>
<update-channel id="indicator-3">
<type>mqtt:indicator</type>
<label>Indicator 3</label>
</update-channel>
</instruction-set>
</thing-type>
</update:update-descriptions>