Catalog · How the catalog works
AWTrix4D
AWTrix Ulanzi TC001 desktop clock Client for Delphi
- ★ 2 on GitHub
Anyone may press Interesting, signed in or not. It helps others find this project.
README
AWTrix4D
AWTrix4D is a native Delphi client library for the HTTP REST API of the AWTrix 3 firmware (formerly AWTrix Light), as found on smart pixel clocks such as the Ulanzi Desktop Clock TC001.
- 📦 Zero external dependencies — only RTL units (
System.Net.HttpClient,System.JSON,System.UITypes, ...): no Indy, no third-party packages, works with desktop and mobile compilers. - 🌐 Full HTTP API coverage — notifications, custom apps, settings, power, moodlight, indicators, sound, stats and more (see the table below).
- ⛓️ Fluent builders — payloads and settings are composed with chainable, reference-counted
interface builders: no manual
Freeneeded. - 🎨 Native color support — pass
TAlphaColororTColorvalues directly; the library converts them to the"#RRGGBB"format expected by the device. - 💬 Speaking errors — dedicated exception hierarchy with host, endpoint and HTTP status.
🛠️ Reference Delphi version: Delphi 13.1 Florence.
📑 Table of contents
- 📦 Installation
- 🚀 Quick start
- 📖 Usage guide
- 🎨 Colors
- 🧠 Memory ownership rules
- 🛑 Error handling
- 📋 API coverage
- 📡 Status
- 📄 License
📦 Installation
🧱 Using Blocks (package manager)
AWTrix4D is published in the official Blocks community repository as marcobreveglieri.awtrix4d.
Blocks downloads, compiles, and wires versioned Delphi packages into your IDE for you.
:: Install the Blocks CLI once (requires Windows + winget)
winget install DelphiBlocks.Blocks
:: From your project folder: initialize the workspace and pick your Delphi version
blocks init
:: Add AWTrix4D to the project
blocks install marcobreveglieri.awtrix4dBlocks fetches the sources, compiles the AWTrix4D runtime package with MSBuild, and registers the library and search paths in your IDE — then just uses the units you need.
[!TIP]
To pin a specific release, append a SemVer constraint, e.g.
blocks install marcobreveglieri.awtrix4d@^1.0.0.
🛠️ Manual installation
Either:
- Add the
Sourcefolder to your project's unit search path, or - Add the four units to your project with explicit paths.
Source/
AWTrix4D.Types.pas colors, enums, constants, exceptions
AWTrix4D.Payload.pas fluent builder for app/notification payloads
AWTrix4D.Settings.pas fluent builder for device settings
AWTrix4D.Client.pas the HTTP client🚀 Quick start
uses
System.UITypes,
AWTrix4D.Client,
AWTrix4D.Payload,
AWTrix4D.Settings,
AWTrix4D.Types;
var LClient := TAWTrixClient.Create('192.168.1.50'); // port 80 by default
try
// One-time notification: red text with an icon.
LClient.Notify(TAWTrixPayload.New
.Text('Hello from Delphi!')
.Icon(412)
.Color(TAlphaColorRec.Red)
.ScrollSpeed(50)
.Duration(8));
// Persistent custom app, refreshed by posting to the same name.
LClient.SetCustomApp('delphi', TAWTrixPayload.New
.TextFragment('Delphi ', TAlphaColorRec.Red)
.TextFragment('rocks!', TAlphaColorRec.White)
.Lifetime(120)
.LifetimeMode(lmStale));
// Batch several device settings in one request.
LClient.UpdateSettings(TAWTrixSettings.New
.Brightness(120)
.AutoBrightness(False));
finally
LClient.Free;
end;A complete console example lives in Samples/ConsoleDemo (AWTrixDemo.dproj): pass the device address on the command line, e.g. AWTrixDemo 192.168.1.50 (if omitted, it is asked interactively).
📖 Usage guide
A tour of the library organized by task, meant as a lightweight substitute for a wiki. Every snippet below uses real method and enum names from the library — see API coverage for the full endpoint-to-method map.
Creating and configuring the client
TAWTrixClient owns one HTTP connection to one device and is not thread-safe. Create one
instance per device, always inside a try..finally:
var LClient := TAWTrixClient.Create('192.168.1.50', 80); // AHost, APort (default 80)
try
LClient.ConnectTimeout := 3000; // ms, TCP connect phase (default 3000)
LClient.ResponseTimeout := 5000; // ms, waiting for the response (default 5000)
// ... use LClient ...
finally
LClient.Free;
end;Host and Port are exposed as read-only properties for diagnostics/logging.
Notifications
Notifications are transient overlays shown on top of the current app. Build one with
TAWTrixPayload.New and send it with Notify:
LClient.Notify(TAWTrixPayload.New
.Text('Build succeeded!')
.Color(TAlphaColorRec.Limegreen)
.Icon(1234)
.Sound('success') // RTTTL file in MELODIES, or a 4-digit MP3 number
.Duration(6)
.Hold(False) // True keeps it on screen until dismissed
.Wakeup); // wake the matrix if it was offFor notifications kept on screen (Hold(True)), dismiss them explicitly:
LClient.DismissNotification;A plain text notification, without a builder, is also supported:
LClient.Notify('Quick heads-up');Custom apps
Custom apps are persistent pages that join the device's app loop until deleted. Push (and later refresh) one by posting to the same name:
LClient.SetCustomApp('delphi', TAWTrixPayload.New
.TextFragment('Delphi ', TAlphaColorRec.Red)
.TextFragment('rocks!', TAlphaColorRec.White)
.Icon(412)
.RepeatCount(2)
.Lifetime(120) // seconds without an update before expiry
.LifetimeMode(lmStale) // lmStale keeps it, lmDelete removes it
.Save(False)); // True persists it in flash across rebootsMultiple pages under the same base name (SetCustomApps) let you build a small multi-page app:
LClient.SetCustomApps('weather', [
TAWTrixPayload.New.Text('Rome: 24 C'),
TAWTrixPayload.New.Text('Milan: 21 C')
]);Remove it — DeleteCustomApp deletes every app whose name begins with the given prefix:
LClient.DeleteCustomApp('delphi');Jump directly to an app (built-in or custom) instead of waiting for the loop:
LClient.SwitchApp(AWTrixAppTemperature); // built-in app constant
LClient.SwitchApp('delphi'); // or a custom app name
LClient.NextApp;
LClient.PreviousApp;Built-in app name constants: AWTrixAppTime, AWTrixAppDate, AWTrixAppTemperature,
AWTrixAppHumidity, AWTrixAppBattery.
Indicators
The three colored indicator LEDs are addressed through TAWTrixIndicator:
| Value | Position |
|---|---|
aiUpperRight |
upper-right indicator (/api/indicator1) |
aiRight |
right indicator (/api/indicator2) |
aiLowerRight |
lower-right indicator (/api/indicator3) |
LClient.SetIndicator(aiUpperRight, TAlphaColorRec.Dodgerblue); // steady
LClient.SetIndicatorBlink(aiRight, TAlphaColorRec.Red, 500); // blink every 500 ms
LClient.SetIndicatorFade(aiLowerRight, TAlphaColorRec.Gold, 1000); // fade every 1000 ms
LClient.ClearIndicator(aiRight);Moodlight and sound
Flood the whole matrix with a color, or play a melody:
LClient.SetMoodlight(180, TAlphaColorRec.Orange); // brightness 0..255, color
LClient.SetMoodlightKelvin(180, 2700); // color temperature in kelvin
LClient.DisableMoodlight;
LClient.PlaySound('doorbell'); // RTTTL filename (no extension) or a 4-digit MP3 number
LClient.PlayRtttl('two_short:d=4,o=5,b=100:16e6,16e6');Drawing on the matrix
The payload builder also accepts low-level drawing instructions, useful for custom apps and notifications alike:
LClient.SetCustomApp('canvas', TAWTrixPayload.New
.DrawFilledRect(0, 0, 8, 8, TAlphaColorRec.Navy)
.DrawCircle(16, 4, 3, TAlphaColorRec.Cyan)
.DrawLine(0, 7, 31, 7, TAlphaColorRec.White)
.DrawText(2, 1, 'Hi', TAlphaColorRec.Yellow));Charts and progress bars are dedicated helpers on top of the same payload:
LClient.Notify(TAWTrixPayload.New
.Text('CPU load')
.Bar([10, 40, 70, 55, 90])
.Progress(72)
.ProgressColor(TAlphaColorRec.Orangered));Reading device state
Get* methods return a JSON value that you must free (see
Memory ownership rules):
var LStats := LClient.GetStats;
try
WriteLn(LStats.Format(2));
finally
LStats.Free;
end;
var LEffects := LClient.GetEffects;
try
for var LEffect in LEffects do
WriteLn(LEffect.Value);
finally
LEffects.Free;
end;The same pattern applies to GetSettings, GetTransitions, GetLoop and GetScreen.
Device settings
Settings are written to the device's flash, which has a limited number of write cycles — batch
everything you need into a single UpdateSettings call instead of many small ones:
LClient.UpdateSettings(TAWTrixSettings.New
.Brightness(120)
.AutoBrightness(False)
.TextColor(TAlphaColorRec.White)
.TimeFormat('%H:%M:%S')
.DateFormat('%d.%m.%y')
.StartWeekOnMonday(True)
.TransitionEffect(trFade)
.TransitionSpeed(400)
.Volume(15));Anything not covered by a dedicated method — on either builder — is still reachable through
Raw(key, value).
Handling errors
Every call can fail either at the network level or with a non-2xx HTTP response; the library raises a dedicated exception for each case so you can react accordingly:
try
LClient.Notify('Hello!');
except
on E: EAWTrixConnectionError do
WriteLn(Format('Cannot reach %s (endpoint %s): %s', [E.Host, E.Endpoint, E.Message]));
on E: EAWTrixApiError do
WriteLn(Format('Device replied %d at %s: %s', [E.StatusCode, E.Endpoint, E.ResponseBody]));
on E: Exception do
WriteLn('Unexpected error: ' + E.Message);
end;See Error handling for the full exception hierarchy.
🎨 Colors
TAWTrixColor converts implicitly from TAlphaColor, TColor and hex strings:
Builder.Color(TAlphaColorRec.Red); // FMX-style alpha color
Builder.Color(TColorRec.Red); // VCL-style color (BGR order handled)
Builder.Color('#00FF80'); // hex string
Builder.Color(TAWTrixColor.FromRGB(0, 255, 128));Note: a bare integer literal (e.g.
Builder.Color($FF0000)) is ambiguous between theTColorandTAlphaColorconversions and does not compile. Use a typed constant (TAlphaColorRec.Red,TColorRec.Red,clRed) or one of the named constructors (FromRGB,FromHex,FromColor,FromAlphaColor).
🧠 Memory ownership rules
TJSONObject/TJSONArrayvalues returned by the library (GetStats,GetSettings,BuildJSON, ...) are owned by the caller: free them in atry..finallyblock.TJSONObjectvalues passed to the client (Notify,SetCustomApp,UpdateSettings) are never owned by the client: you keep the responsibility of freeing them.- The fluent builders are interfaces: reference counting frees them automatically.
Raw(...)andEffectSettings(...)are the only builder methods that take ownership of theTJSONValueyou pass in.
🛑 Error handling
All failures raise exceptions derived from EAWTrixError:
| Exception | Meaning | Extra info |
|---|---|---|
EAWTrixConnectionError |
device unreachable (offline, wrong address, timeout) | Host, Endpoint |
EAWTrixApiError |
device replied with an HTTP status outside 200..299 | StatusCode, Endpoint, ResponseBody |
📋 API coverage
| AWTrix endpoint | Method(s) |
|---|---|
GET /api/stats |
GetStats |
GET /api/effects |
GetEffects |
GET /api/transitions |
GetTransitions |
GET /api/loop |
GetLoop |
GET /api/screen |
GetScreen |
GET /api/settings |
GetSettings |
POST /api/power |
SetPower |
POST /api/sleep |
DeepSleep |
POST /api/sound |
PlaySound |
POST /api/rtttl |
PlayRtttl |
POST /api/moodlight |
SetMoodlight, SetMoodlightKelvin, DisableMoodlight |
POST /api/indicator1..3 |
SetIndicator, SetIndicatorBlink, SetIndicatorFade, ClearIndicator |
POST /api/notify |
Notify (builder, raw JSON or plain text) |
POST /api/notify/dismiss |
DismissNotification |
POST /api/custom?name=... |
SetCustomApp, SetCustomApps, DeleteCustomApp |
POST /api/nextapp / previousapp |
NextApp, PreviousApp |
POST /api/switch |
SwitchApp (see the AWTrixApp* constants) |
POST /api/settings |
UpdateSettings (builder or raw JSON) |
POST /api/reboot |
Reboot |
POST /api/doupdate |
UpdateFirmware |
POST /api/erase |
EraseFlash ⚠️ factory reset |
POST /api/resetSettings |
ResetSettings ⚠️ resets all settings |
Everything not covered by a dedicated builder method can still be sent through the
Raw(key, value) escape hatch of both builders.
⚠️
EraseFlashformats the flash memory and EEPROM (WiFi settings survive) andResetSettingsclears every setting applied through the settings API. They map to real firmware endpoints and are included for completeness — call them deliberately.
📡 Status
This library has been written against the official AWTrix 3 HTTP API documentation.
📄 License
Released under the MIT License.

