Sign in Join

Live chat for Free Pascal, Lazarus, Delphi and every Pascal programmer.

Catalog · How the catalog works

Random project

AWTrix4D

AWTrix Ulanzi TC001 desktop clock Client for Delphi

View on GitHub

Anyone may press Interesting, signed in or not. It helps others find this project.

Member rating

No ratings yet.

Sign in to rate this project.

README

AWTrix4D

pascal
![AWTrix4D ??? Ulanzi TC001 Client Library for Delphi](/catalog-images/1f/1f3437d9a3ff4f17ff042f85.png)

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 Free needed.
  • 🎨 Native color support — pass TAlphaColor or TColor values 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

🧱 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.

bat
:: 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.awtrix4d

Blocks 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:

  1. Add the Source folder to your project's unit search path, or
  2. Add the four units to your project with explicit paths.
pascal
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

pascal
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:

pascal
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:

pascal
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 off

For notifications kept on screen (Hold(True)), dismiss them explicitly:

pascal
LClient.DismissNotification;

A plain text notification, without a builder, is also supported:

pascal
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:

pascal
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 reboots

Multiple pages under the same base name (SetCustomApps) let you build a small multi-page app:

pascal
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:

pascal
LClient.DeleteCustomApp('delphi');

Jump directly to an app (built-in or custom) instead of waiting for the loop:

pascal
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)
pascal
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:

pascal
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:

pascal
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:

pascal
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):

pascal
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:

pascal
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:

pascal
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:

pascal
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 the TColor and TAlphaColor conversions 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/TJSONArray values returned by the library (GetStats, GetSettings, BuildJSON, ...) are owned by the caller: free them in a try..finally block.
  • TJSONObject values 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(...) and EffectSettings(...) are the only builder methods that take ownership of the TJSONValue you 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.

⚠️ EraseFlash formats the flash memory and EEPROM (WiFi settings survive) and ResetSettings clears 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.