Awtrix 3 Binding (formerly Awtrix Light)

This is a binding to control an Ulanzi clock (Ulanzi TC001 Smart Pixel Clock 2882) flashed with the custom Awtrix 3 firmware. This binding may also be used to control other devices (e.g. self-built) that use the same firmware.


Version 1.2.2

  • Added channel update information for automated thing updates

Version 1.2.1

  • Fixed a bug when trimming data points for graphs

Version 1.2

  • Added deep sleep action (invoke with sleep(SECONDS))
  • Added app lifetime and lifetimeMode channels (remove or mark an app as stale when there was no update for a certain amount of time)
  • Added overlay channel for apps (weather condition overlay effect)

Version 1.1

  • Compiled against OH 4.2.0; Works with OH 4.1 but please note that the name of the file has changed. Remove the old addon from the addon folder first and then insert the new addon file.
  • Actions can now also be used within rules DSL

Version 1.0

  • No functional changes. Going to stable now with the release of OH 4.1.0. Documentation is still missing though. Please ask any questions in this thread. I will compile all the answers into a proper documentation at some point in time.

Version 0.9.1

  • No functional changes. Just recompiled against 4.1.0 RC1 for compatibility reasons

Version 0.9

  • Sync the display channel back from the clock to OH (requires Awtrix Light firmware v0.89 or higher)

Version 0.8.1

  • Fixed trigger channels

Version 0.8 - ONLY FOR openHAB 4.1.0.M2 and UP!!! You can find the previous version below in the comments

  • Added support for notifications (via thing actions)

Version 0.7.1

  • Fixed pushIcon channel

Version 0.7

  • Brightness channel is writeable now, Autobrightness channel was added

Version 0.6

  • Don’t accept commands while synching
  • Channel improvements

Version 0.5.1

  • Fixed display command

Version 0.5

  • Smoother app initialisation for existing apps. Init will now take a bit longer now for newly created apps due to a longer timeout to receive the retained app message.

Version 0.4

  • Now initialising all app items when they are linked for convenience. Note that the initialised values do not always represent the actual values of the clock (some are actually undefined).

Version 0.3

  • Fixed thing actions: reboot, sound, upgrade
  • Fixed thing configuration reset by discovery. Note that the basetopic value cannot be changed as long as the clock still sends on the configured value
  • Sorted app channels in a more logical order
  • Removed buttonControlled channel for apps

Version 0.2

  • Fixed DiscoveryService “stealing” retained app messages. This change makes the recommended topic a requirement: MQTT prefix now always has to look like this: “awtrix/CLOCKNAME” where CLOCKNAME is a name of your choice.

Version 0.1

  • initial release (beta state). Known issues: Bridge config options (baseTopic, appLockTimeout, discoverDefaultApps) cannot be updated after bridge creation.



Source code:


Hey everyone,

I created a binding for the Ulanzi Smart Clock (Ulanzi TC001 Awtrix Smart Pixel Clock 2882 – ULANZI) flashed with the custom Awtrix Light firmware (GitHub - Blueforcer/awtrix-light: Custom firmware for the Ulanzi Smart Pixel clock or self made awtrix. Getting started is easy as 1-2-3). I would be very happy if somebody would be willing to give it a go and support me in testing this.

This binding is based on the mqtt binding so please make sure that you install the mqtt binding before you drop the jar file in your addon folder.

There is no documentation yet but here are some first steps to get you started:

  1. Connect your clock to MQTT and set a mqtt prefix starting with “awtrix” followed by your clock name on the second level like this: awtrix/CLOCKNAME with CLOCKNAME being a name of your choice for your clock.
  2. The clock should be autodiscovered in openHAB (as a bridge). Otherwise you can of course also add it manually with the prefix you chose in step 1 as baseTopic configuration.
  3. If you have already added Apps to your clock before these Apps should be discovered automatically. Otherwise create a new app as a thing manually with a name of your choice.


  • The bridge thing mainly offers information about the integrated sensors and allows you to switch the display on or off. It also publishes button trigger events when the pyhsical buttons are pressed on the device
  • The app things can be used to display information on the screen. The main options are to display icons, text, bar/line graphs and a progress bar. Around these channels you will find lots of channels to style the app the way you want. You can use only the channels required to create the desired look of your app. Otherwise the default values of the firmware will be used.
  • You can also use apps to control other parts of openHAB: When you enable the “Button Controlled” config option the clock will allow to select the currently shown app by pushing the select key (middle button). The app rotation will stop when you selected an app (indicated by the red icon in the bottom right corner). All subsequent button pushes will also be emitted by the trigger channels of the selected app (note that the clock thing will also emit the same events). You can use this to create nice little controls for other things. See my example for a simple Spotify control (Uses: Icon, Text, Progress Bar, Button Controlled and Color customization for the progress bar) below:

Further details:

  • The clock itself does not report back any information for custom apps (except the name) so this binding relies on the use of retained mqtt messages as persistent storage (apps are deleted from the clock during a reboot). Therefore, when you added custom apps either through the http api or without retained messages the items will not be in sync with the clock display initially. If you create the app via OH the items will remain synchronized. Note that after an OH reboot some item states may be reset when there were invalid combinations of properties. For example: When you have set the blinkText and the rainbow property at the same time the rainbow property will not be sent to the clock because they are incompatible. Since the item states are restored from the retained mqtt message after reboot they will be reset to the default value after a reboot thus resynching the clock and the items.
  • There are some more options to change settings for the clock that are not (yet) exposed as channels. If needed I’m happy to add some more or all of them as channels. However the clock reports the current settings only once after startup. So synchronization from the clock to OH is possible but only by restarting the clock (there exists an Action to reboot). So this might be an option if you like to get states synchronized from the clock to your OH instance (this is not implemented yet) however.
  • The binding can also detect the built-in apps however it is not possible to control the apps beyond what can be configured globally for the clock. Therefore by default these apps will not be shown in the discovery results (config option “Discover buil-in Apps”).
  • The screen mirror channel is only sending the image once an app has changed. You can achieve a higher frame rate by sending RefreshType commands to the screen channel. However note that it is not ideal to stream the screen with a high framerate via mqtt. If you’re really interested in a more or less reliable screen streaming you should consider using the http api instead (should be possible through the http binding)
  • Be careful when deactivating apps: you can use this feature to temporarily deactivate an app and reuse it later. However note that if you reboot OH in between the binding will initialise the linked channels with the default settings.

If you want to delete an app permanently please proceed as follows:

  1. Deactivate the app first via the “active”-channel: Link to a switch item and switch to off
  2. Delete the thing

If you already deleted the thing before deactivating it: recreate the app with the same app name again and follow steps 1. and 2.


You can get the clock with an additional 10€ off right now with the code “U10”

Probably also possible from other parts of the world.

1 Like

Please note that the binding from version 0.8 and up will only be compiled against OH 4.1.0.M2 (or later). You can find the latest version (v0.7.1) for OH 4.0.X here:

1 Like


Hello DrRSatzeil. I don’t find how to use notifications with your binding. Could you give a example ?


thanks for reaching out, sorry there is no documentation yet. Let me give you an example:

You can use thing actions to create notifications. Use either the showNotification (uses a lot of defaults) or showCustomNotication action. The first one takes a message string and an icon string as input. The latter allows for all sorts of customisations.

Parameters are:
Map<String, Object> appParams (any customisation that is available for apps)
boolean hold (keeps notification active until dismissed manually)
boolean wakeUp (activate display if off)
boolean stack (stack notification so it gets displayed after the previous notifications have been dismissed. This could also be the other way round, did not really test this yet)
String rtttl (melody as rtttl string)
String sound (melody as name of melody file)
boolean loopSound (repeat sound as long as notification is shown)

Concrete example in JavaScript:

var map1 = new Map();

map1.set('text', 'Test2');
map1.set('icon', 'tonne_gelb');
map1.set('rainbow', true);

var awtrixactions = actions.thingActions("mqtt.awtrixlight", "mqtt:awtrixclock:a0e7f22d86:awtrix_1210f4");
awtrixactions.showCustomNotification(map1, false, true, true, "siren:d=16,o=5,b=200:e", "", false);

or just

var awtrixactions = actions.thingActions("mqtt.awtrixlight", "mqtt:awtrixclock:a0e7f22d86:awtrix_1210f4");
awtrixactions.showNotification(“Test”, “0815”)

Edit: note that you can execute these actions on the clock itself not individual apps. However I think it might make sense to also enable this for apps so that notifications could inherit all the settings of an app and just override these where needed.

Edit 2: note that the actions are not yet included in the 0.7.1 version but only in the higher versions that are only working OH 4.1.0 M2 and up.

Hi @DrRSatzteil, thank you for this binding. I read about the TC001 in the German computer magazin c’t, found your binding and thought “give it a try”.

The TC001 was delivered yesterday and I’ve already set-up applications for showing the total amount of electrical energy my house used in the last hour and a display of the title and artist if (and only if) the Sonos speaker in the room the the TC001 is located plays some music. And I hadn’t used MQTT in openhab until yesterday… Alltogether very easy to set up with your binding!

Since I am still on openHAB 4.0.1, I’m using version 0.7.1 of your binding.

I was thinking of using the visual indicators to show if a door or window is open. Top right indicator for an open window on the first floor, bottom right for an open window on the ground floor and middle right for an open door.

After creating the corresponding item for Indicator 1, I saw in openhab.log, that the item gets updated with it’s current status every 10 seconds:

2023-11-11 17:45:32.182 [INFO ] [openhab.event.ItemStateUpdatedEvent ] - Item 'lnkFirstFloor_Room2_Awtrix_Indicator1' updated to OFF
2023-11-11 17:45:42.098 [INFO ] [openhab.event.ItemStateUpdatedEvent ] - Item 'lnkFirstFloor_Room2_Awtrix_Indicator1' updated to OFF
2023-11-11 17:45:52.149 [INFO ] [openhab.event.ItemStateUpdatedEvent ] - Item 'lnkFirstFloor_Room2_Awtrix_Indicator1' updated to OFF
2023-11-11 17:46:02.984 [INFO ] [openhab.event.ItemStateUpdatedEvent ] - Item 'lnkFirstFloor_Room2_Awtrix_Indicator1' updated to OFF
2023-11-11 17:46:06.398 [INFO ] [openhab.event.ItemCommandEvent ] - Item 'lnkFirstFloor_Room2_Awtrix_Indicator1' received command ON
2023-11-11 17:46:06.439 [INFO ] [penhab.event.ItemStatePredictedEvent] - Item 'lnkFirstFloor_Room2_Awtrix_Indicator1' predicted to become ON
2023-11-11 17:46:06.445 [INFO ] [openhab.event.ItemStateUpdatedEvent ] - Item 'lnkFirstFloor_Room2_Awtrix_Indicator1' updated to ON
2023-11-11 17:46:06.446 [INFO ] [openhab.event.ItemStateChangedEvent ] - Item 'lnkFirstFloor_Room2_Awtrix_Indicator1' changed from OFF to ON
2023-11-11 17:46:12.113 [INFO ] [openhab.event.ItemStateUpdatedEvent ] - Item 'lnkFirstFloor_Room2_Awtrix_Indicator1' updated to ON
2023-11-11 17:46:22.296 [INFO ] [openhab.event.ItemStateUpdatedEvent ] - Item 'lnkFirstFloor_Room2_Awtrix_Indicator1' updated to ON
2023-11-11 17:46:32.146 [INFO ] [openhab.event.ItemStateUpdatedEvent ] - Item 'lnkFirstFloor_Room2_Awtrix_Indicator1' updated to ON
2023-11-11 17:46:42.165 [INFO ] [openhab.event.ItemStateUpdatedEvent ] - Item 'lnkFirstFloor_Room2_Awtrix_Indicator1' updated to ON

Are you aware of this? Has this already been addressed by a newer version of the plugin? I’m not really sure if I want to fill up the logs with this unneeded information…

Keep up the good work!

1 Like

Hi Thorsten,

thank you very much for your feedback, it’s really appreciated!

Actually I wasn’t really aware of the log messages but now that you mentioned it it’s obvious to me why they are here. I agree that it would be nicer if the binding would only send updates when something really changed so I will consider this change.

I just need to double check first if comparing the current status message with the last one is enough or if there are some special cases there. I guess it should be fairly simple though.

Edit: oh and since you mentioned that you just created your mqtt setup make sure that you have your broker configured to use persistence. Probably you got that right already, I just mention this because I just realised during the binding implementation that I never defined a persistence strategy for the broker. I just never needed it before but it will lead to problems with this binding because it uses the persistence of broker to store the state of the apps during reboots (both the OH host and the clock)

Edit2: In one of the latest versions I also added actions for the indicators so that you can also choose the Color and blink/fade intervals. The method signatures are:

blinkIndicator(int indicatorId, int[] rgb, int blinkTimeInMs)
fadeIndicator(int indicatorId, int[] rgb, int fadeTimeInMs)
activateIndicator(int indicatorId, int[] rgb)
deactivateIndicator(int indicatorId)

Hi Thomas,

Ok, I will keep that in mind, although I don’t think I need this right now. My apps for the clock a fairly simple, very few channels are linked (active, text, scrollSpeed, icon and duration) and the apps are inactive most of the time. For example, the “Show title and artist of my Sonos speaker” app only gets activated for up to two minutes if title/artist information changes by setting the information for all channels to the preferred values. The app will be deactivated after 2 mins using the “Expiration Timer” metadata of the “active” channel. When nothing changes, the clock will only display the built-in clock app - no flickering and scrolling :wink:

Ooookay, this makes me think if I should really upgrade OH to 4.1.0.M2. Sounds like one doesn’t need the indicator channels anymore, or do I misinterpret something? :thinking:

If you need this or not really depends on your use case and whether you restart your mqtt broker. Just remember that the binding will reintialize your items with the default values when you reboot OH and the apps were inactive before that. It’s not a big deal to fix that though for a couple of items (you may want to have a script rule run at startup).

Regarding the indicator channels: I still see these as a quick shortcut if you don’t need different colors or want to customise the blinking intervals. It’s just not really convenient to get all this functionality with channels/items so I thought it might be a good compromise to just add some actions if you want more control over the indicators.

Hi =)
does the rssi channel work for you?
There is allways just a UNDEF

It works for me, yes. Which version do you use?

I’ve built kind of a poor man’s approach to integrating 2 Awtrix Light displays in my infrastructure.
It’s based on some JavaScript:

Firstly the classes that take care of the actual work (please replace the MQTT bridge UID accordingly):

class AWL_App {

    constructor(name, icon, itemName, duration = DEFAULT_DURATION) {
        this.#name = name;
        this.#icon = icon;
        this.#itemName = itemName;
        this.#duration = duration;
        this.#devices = [];

            name: `Update AWL App for item ${itemName}`,
            description: `Post the current value of item ${itemName} to the correponding Awtrix Light custom app`,
            triggers: [
            execute: data => { this.update(this.useCmd ? data.receivedCommand : data.newState); }

    get topicPostfix() { return `/custom/${this.#name}`; }
    get devices() { return this.#devices; }
    toJson(state) { return JSON.stringify({icon: this.#icon, text: state, duration: this.#duration }); }
    _publish(dev, payload) { MQTT.publishMQTT(TOPIC_PREFIX + + this.topicPostfix, payload); }

    registerDevice(dev) {
        this._publish(dev, this.toJson(items.getItem(this.#itemName).state));
        return this;

    update(state) { this.devices.forEach(device => { this._publish(device, this.toJson(state)); }); }
    remove() { this.devices.forEach(device => { this._publish(device, ''); }); }

 * To be used for items of type Number:Temperature
class AWL_TemperatureApp extends AWL_App {
    toJson(state) {
        let parts = state.split(' ');
        let formattedState = parseFloat(parts[0]).toFixed(1) + ' ' + parts[1];
        return super.toJson(formattedState);

 * To be used for items of type Datetime
class AWL_TimeApp extends AWL_App {
    toJson(state) {
        let s = state;
        let start =\d\d:\d\d)/);
        let time = s.substring(start, start + 5);
        return super.toJson(time);

 * Can be bound to String item, sending its contents as a notification to the AWL device upon receiving a command
class AWL_Notification extends AWL_App {
    get topicPostfix() { return '/notify'; }
    registerDevice(dev) {
        this.devices.push(dev); // avoid immediate notification
        return this;

 * Notification to be used in combination with a switch item; a dedicated message can be specified.
class AWL_SwitchNotification extends AWL_Notification {

    constructor(name, icon, itemName, msg, duration = DEFAULT_DURATION) {
        super(name, icon, itemName, duration);
        this.#msg = msg;

    update(state) { super.update(this.#msg); }

 * represents an AWL device
class AWL_Device {


    constructor(name, diplayItem) {
        this.#name = name;
        this.#displayItem = this.#displayItem;

            name: `Display on/off for AWL ${name}`,
            description: `Toggles display of AWL device ${name} on/off`,
            triggers: [ triggers.ItemCommandTrigger(diplayItem) ],
            execute: data => { this.switchDisplay(data.receivedCommand == 'ON'); }

    get name() { return this.#name; }

    switchDisplay(on) { MQTT.publishMQTT(TOPIC_PREFIX + + '/power', JSON.stringify({power: on})); }

module.exports.AWL_App = AWL_App;
module.exports.AWL_TemperatureApp = AWL_TemperatureApp;
module.exports.AWL_TimeApp = AWL_TimeApp;
module.exports.AWL_Notification = AWL_Notification;
module.exports.AWL_SwitchNotification = AWL_SwitchNotification
module.exports.AWL_Device = AWL_Device

Then you can use this simply like this in another script:

const {AWL_TemperatureApp, AWL_Notification, AWL_SwitchNotification, AWL_Device} = require('../efh-bauer/devices/awl');

// Devices
let awl1 = new AWL_Device('display1', 'Display1_onoff');
let awl2 = new AWL_Device('display2', 'Display2_onoff');

// Room Temp
new AWL_TemperatureApp('roomTemp', '4286', 'Temp_R1').registerDevice(awl1);
new AWL_TemperatureApp('roomTemp', '4286', 'Temp_R2').registerDevice(awl2);

// Outdoor Temp
new AWL_TemperatureApp('outTemp', '4285', 'Temp_out')

// Notifications
new AWL_Notification('notification', '555', 'AWL_Notification', 10)

// Door Bell
new AWL_SwitchNotification('doorbell', '56373', 'Doorbell', 'Someone is at the door!', 30)

Maybe this is helpful for someone… at least until the binding has GA status :slightly_smiling_face:

1 Like

Hi Thorsten,

I just double checked my logs and I only see ItemStateChangedEvent messages so I do have two questions:

  1. The log you provided is from the events.log and not from the openhab.log right?
  2. Is there a setting somewhere to also get ItemStateUpdatedEvent events? As said before I don’t see them in my logs and I cannot remember that I ever fiddled around with any of the logging settings


Hi Thomas,

Yes, that is correct. As I am currently not using an indicator, I just linked one indicator channel in MainUI to a new item without changing any settings. After a few seconds, the item changed from NULL to OFF and then the “updated to OFF” logs started.

Do you mean you don’t see any “…updated to…” logs in your events.log file? I see them for all of my items, e.g.

2023-11-16 22:02:58.321 [INFO ] [openhab.event.ItemStateUpdatedEvent ] - Item 'lnkFirstFloor_Room2_UPS1_BatteryCharge' updated to 1
2023-11-16 22:02:58.324 [INFO ] [openhab.event.ItemStateUpdatedEvent ] - Item 'lnkFirstFloor_Room2_UPS1_BatteryRuntime' updated to 3216 s
2023-11-16 22:02:58.326 [INFO ] [openhab.event.ItemStateChangedEvent ] - Item 'lnkFirstFloor_Room2_UPS1_BatteryRuntime' changed from 3360 s to 3216 s
2023-11-16 22:02:58.329 [INFO ] [openhab.event.ItemStateUpdatedEvent ] - Item 'lnkFirstFloor_Room2_UPS1_UPSLoad' updated to 0.07
2023-11-16 22:02:58.332 [INFO ] [openhab.event.ItemStateChangedEvent ] - Item 'lnkFirstFloor_Room2_UPS1_UPSLoad' changed from 0.08 to 0.07
2023-11-16 22:02:58.336 [INFO ] [openhab.event.ItemStateUpdatedEvent ] - Item 'lnkFirstFloor_Room2_UPS1_Status' updated to OL

Two items updated and changed, two items only updated (with the same value). I thought this is normal behaviour. :thinking:

According to Logging | openHAB, the logger which logs when an Item is updated with the old or a new value is openhab.event.ItemStateEvent. This is set to ERROR in my installation:

openhab> log:list | grep ItemState
openhab.event.ItemStateEvent                       x ERROR

Event setting it to OFF doesn’t change anything.

openhab> log:set OFF openhab.event.ItemStateEvent
openhab> log:list | grep ItemState
openhab.event.ItemStateEvent                       x OFF

Could this be a problem in my openHAB instance? May be I should open a new topic for this issue… :thinking:

Exactly :+1:

grep ItemStateUpdatedEvent openhab/userdata/logs/events.log

However I think it was me after all. Apparently I set the log level to error at some point in time. I guess this was due to the same issue that you reported here. Also other bindings seem to behave like this and tend to spam the logs with update events.

openhab.event.ItemStateUpdatedEvent                │ ERROR

I just uploaded a new version since the last one was not compatible with the latest RC1 build. The version contains no functional changes, I just recompiled against the changed codebase.

1 Like

Hi Thomas, when trying to install the binding under OH 4.1.0, I can not find it under the official bindings. When searching, I find it still in the community marketplace. When installing, the message “Installation of add-on marketplace:149739 failed” is displayed.


It’s not yet an official binding but only available in the marketplace. So it’s correct that you can only find it there. I must admit that I haven’t installed it through the marketplace myself so it could be that I made a mistake here?!

You could always just drop the jar in the addon folder of your installation until somebody can tell me what I need to change in my initial post so the installation will succeed :face_with_spiral_eyes:

That is what I did with this file: org.openhab.binding.mqtt.awtrixlight-4.1.0-SNAPSHOT.jar
But still the same problem (even after a clean reboot of OH) In the AddOn-store only one file is displayed which leads to the above issue.
Below the log entry:

2024-01-01 19:15:56.870 [WARN ] [org.apache.felix.fileinstall ] - Error while starting bundle: file:/usr/share/openhab/addons/org.openhab.binding.mqtt.awtrixlight-4.1.0-SNAPSHOT.jar
org.osgi.framework.BundleException: Could not resolve module: org.openhab.binding.mqtt.awtrixlight [243]
Unresolved requirement: Import-Package: org.openhab.binding.mqtt.discovery

at org.eclipse.osgi.container.Module.start( ~[org.eclipse.osgi-3.18.0.jar:?]
at org.eclipse.osgi.internal.framework.EquinoxBundle.start( ~[org.eclipse.osgi-3.18.0.jar:?]
at org.apache.felix.fileinstall.internal.DirectoryWatcher.startBundle( [bundleFile:3.7.4]
at org.apache.felix.fileinstall.internal.DirectoryWatcher.startBundles( [bundleFile:3.7.4]
at org.apache.felix.fileinstall.internal.DirectoryWatcher.doProcess( [bundleFile:3.7.4]
at org.apache.felix.fileinstall.internal.DirectoryWatcher.process( [bundleFile:3.7.4]
at [bundleFile:3.7.4]

I just checked my MQTT-Binding and found these 2 new entries - are these the ones ro be expected?