I support almost everything
speech is in latest beta, raw command support it already, but direct flow for this was not available until now…
It is written by me and SOL 6.1 ![]()
I support almost everything
speech is in latest beta, raw command support it already, but direct flow for this was not available until now…
It is written by me and SOL 6.1 ![]()
With that discount, I ordered two… will report soon back ![]()
Thanks @Bckp for the amazing work! I noticed that I cannot play an MP3 file on the TC002 which was previously stored locally on the device. The MP3 play functionality only works with external URLs. Can this function be added?
Yep, fix will be ready in few hours (fix is ready, just need to wait for athom to clear my train for release) when you have drafr for release, you cant do beta release at same time
Great, that was quick!
2.3.7 is in test, with play MP3 from device, and new card RAW command
so you can send any command you want to the device… i think it is getting kind of crazy, how much the device support, so you can always send direct command if im not here to help with flow card…
EDIT: 2.3.8 - i have changed mp3 to autocomplete
it will be easier
Nice Awtrix for Homey app and Ulanzi device.
I’m currently exploring the many possibilities. The first screen definition is already working!
I asked Ulanzi, if they send me one device for testing
the silence was breath taking
they did not even respond… and more then 500 devices is registered and active in homey ![]()
Starting to have interesting collection, TCO01 with Awtrix3, TCO01 with AwtrixNG, TCO02 with AwtrixNG, but all devices is at office where i can play with them, and at home i have none
![]()
I don’t like the standard Awtrix NG clock (bold font). Although there are many options for configuring the clock, I couldn’t achieve the desired result.
With a lot of help from Claude AI, I created an Awtrix script (so this is slightly off-topic) that now includes all the desired options, along with a custom font.
Options:
Script (just paste this in the Awtrix script tab):
# =====================================================================
# CLOCK for AWTRIX NG
# =====================================================================
# Shows the time at the top with custom, hand-drawn "chunky" digits and
# the date in capitals below it. The digits are not drawn with a font but
# with filled rectangles (seven-segment layout), so width, height,
# thickness, corners and the shape of the 1 are adjustable. Optionally,
# seconds follow the minutes, separated by a second colon.
#
# Structure of the script:
# init() - reads the settings and prepares fixed data
# loop() - runs about once per second and rebuilds the time as soon
# as the minute changes (heavy work belongs here, not in draw)
# draw() - runs ~40x per second and only draws what is already ready;
# the seconds are read from the clock here (this costs no
# memory) so they keep exact time
#
# The lines below start with "# @config" and are turned by AWTRIX into
# input fields under the gear icon of the app. The text after help=
# appears as an explanation next to the field. With group= fields end up
# in a collapsible submenu (Colors, Dimensions, Display).
# This works from AWTRIX NG 1.2.2.
# =====================================================================
# @name Clock
# @desc Centered clock with custom chunky digits, optional seconds, and date
# @version 3.0
# --- Submenu: Colors ---
# @config time_color color "Time" group="Colors" default=#00FFFF help="Color of the digits and the colon."
# @config date_color color "Date" group="Colors" default=#FFBC05 help="Color of the date line at the bottom."
# --- Submenu: Dimensions ---
# @config digit_width number "Digit width" group="Dimensions" default=6 min=3 max=10 unit=px help="Width of a digit in pixels. The 1 and the colon have their own width. With seconds, at most 7 px fits on a 52 px wide panel."
# @config digit_height number "Digit height" group="Dimensions" default=8 min=5 max=12 unit=px help="Height of the digits in pixels. The date sits below them."
# @config thickness number "Stroke thickness" group="Dimensions" default=2 min=1 max=4 unit=px help="Thickness of the strokes the digits are made of. Rounded corners work best from 2."
# @config spacing number "Spacing between characters" group="Dimensions" default=1 min=0 max=3 unit=px help="Empty pixels between the digits and the colon."
# --- Submenu: Display ---
# @config show_seconds bool "Show seconds" group="Display" default=false help="On: a second colon and the seconds follow the minutes."
# @config rounded bool "Rounded corners" group="Display" default=false help="On: the corner pixel of the outer corners is left out. Off: sharp corners."
# @config one_style select "Style of the 1" group="Display" default=flag options="narrow,flag,flag and foot" help="narrow: just a stem. flag: stem with a 2 px flag. flag and foot: also a 2 px foot on both sides."
# @config colon_behavior select "Colon" group="Display" default=blink options=blink,breathe,static help="blink: on and off. breathe: brightness fades softly up and down. static: always on."
# @config colon_size select "Colon size" group="Display" default=medium options=small,medium,large help="Height of each block, 2 px wide: small 1 px, medium 2 px, large 4 px (limited at a low digit height)."
# @config colon_cycle number "Cycle duration" group="Display" default=1000 min=200 max=5000 step=50 unit=ms help="Speed of blinking and breathing: time for one on-and-off, or one up-and-down."
class Clock
# -------------------------------------------------------------------
# Members (the "memory slots" of the app). Each member is declared here
# and gets its starting value in init().
# -------------------------------------------------------------------
var time_color, date_color # colors from the settings (numbers 0xRRGGBB)
var digit_width, digit_height # size of a digit in pixels
var thickness, spacing # stroke thickness and space between characters
var show_seconds # true = show seconds after the minutes
var rounded # true = leave out the corner pixels of the digits
var one_mode # 0 = narrow, 1 = with flag, 2 = with flag and foot
var colon_mode # 0 = static, 1 = blink, 2 = breathe
var colon_height # height of each colon block (width is always 2)
var colon_cycle # duration of one blink or breathe cycle in ms
var colon_half_cycle # half of that, for the on/off switch when blinking
var segments # per digit a bitmask of the 7 segments
var characters, cell_widths # characters (0-9, -1 = colon, -2 = seconds digit) and their widths
var total_width # width of the whole group
var date_text # ready-made date string
var last_minute # to rebuild only when a new minute starts
var day_names, month_names # lookup lists in capitals
var y_top, y_date # top of the time and baseline of the date
# -------------------------------------------------------------------
# init(): runs once when the app loads, and again after you save the
# settings. Everything that can be derived from the settings goes
# here, so draw() does not have to think about it.
# -------------------------------------------------------------------
def init()
# Fetch settings. store.get() returns the configured value, or the
# default from the @config line if nothing has been set yet.
self.time_color = store.get("time_color")
self.date_color = store.get("date_color")
self.digit_width = store.get("digit_width")
self.digit_height = store.get("digit_height")
self.thickness = store.get("thickness")
self.spacing = store.get("spacing")
self.show_seconds = store.get("show_seconds") == true
self.rounded = store.get("rounded") == true
# Convert the style of the 1 to a number. Comparing text costs time,
# and draw() runs 40x per second, so we only do it once here.
var style_choice = store.get("one_style")
self.one_mode = 1 # default: with flag
if style_choice == "narrow"
self.one_mode = 0
elif style_choice == "flag and foot"
self.one_mode = 2
end
# Behavior of the colon: static, blink or breathe (also as a number)
var behavior_choice = store.get("colon_behavior")
self.colon_mode = 1 # default: blink
if behavior_choice == "static"
self.colon_mode = 0
elif behavior_choice == "breathe"
self.colon_mode = 2
end
# Speed: one cycle in milliseconds. num() converts the value to a
# number; the lower limit prevents division by zero on odd input.
self.colon_cycle = num(store.get("colon_cycle"), 1000)
if self.colon_cycle < 100 self.colon_cycle = 100 end
self.colon_half_cycle = self.colon_cycle / 2 # blinking: half on, half off
# Size of the colon: blocks 2 wide and 1, 2 or 4 high
var size_choice = store.get("colon_size")
self.colon_height = 2 # default: medium
if size_choice == "small"
self.colon_height = 1
elif size_choice == "large"
self.colon_height = 4
end
# Limit so the two blocks never touch at a low digit height
var max_colon_height = self.digit_height / 2 - 1
if max_colon_height < 1 max_colon_height = 1 end
if self.colon_height > max_colon_height
self.colon_height = max_colon_height
end
# Seven-segment table. Each digit is a number whose bits say which
# segments are lit:
#
# aaaa a = 1 (top)
# f b b = 2 (top right)
# f b c = 4 (bottom right)
# gggg d = 8 (bottom)
# e c e = 16 (bottom left)
# e c f = 32 (top left)
# dddd g = 64 (middle)
#
# Example: the 7 uses a + b + c = 1 + 2 + 4 = 7.
# 0 1 2 3 4 5 6 7 8 9
self.segments = [63, 6, 91, 79, 102, 109, 125, 7, 127, 111]
# Starting values for what loop() fills later. nil means: time not
# known yet, draw() then shows "..." instead of an empty panel.
self.characters = nil
self.cell_widths = nil
self.total_width = 0
self.date_text = nil
self.last_minute = -1 # -1 = never built yet
# Names for the date. weekday() returns 0 for Sunday, so the list starts at SUN.
self.day_names = ["SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"]
self.month_names = ["JAN", "FEB", "MAR", "APR", "MAY", "JUN",
"JUL", "AUG", "SEP", "OCT", "NOV", "DEC"]
# Vertical placement. Adjust these two if the time or the date does
# not sit right on the panel.
self.y_top = 1 # top pixel row of the time
self.y_date = height() # baseline of the date (bottom of the letters)
end
# -------------------------------------------------------------------
# loop(): about once per second, even when the app is not on screen.
# Here we build everything that only changes when the minute changes.
# -------------------------------------------------------------------
def loop()
var hour_value = hour()
# Right after boot the clock does not know the time yet and returns -1.
if hour_value < 0 return end
var minute_value = minute()
# Nothing to do as long as the minute has stayed the same
if minute_value == self.last_minute return end
self.last_minute = minute_value
# List of characters: hours without a leading zero (1 or 2 digits), then
# the colon (-1), then always 2 digits for the minutes.
var chars = []
if hour_value >= 10
chars.push(hour_value / 10) # tens of the hour (only from 10 o'clock)
end
chars.push(hour_value % 10) # units of the hour
chars.push(-1) # colon
chars.push(minute_value / 10) # tens of the minutes
chars.push(minute_value % 10) # units of the minutes
# Seconds: a second colon and two slots (-2) whose digit draw() fills
# in itself, because the seconds change every second.
if self.show_seconds
chars.push(-1)
chars.push(-2)
chars.push(-2)
end
# Determine the width per character and add them up. This only needs to
# happen once per minute and not in every frame. The seconds slots (-2)
# always keep the full digit width, so the group does not shift back
# and forth when a 1 passes by.
var widths = []
var total = 0
for i : 0 .. size(chars) - 1
var symbol = chars[i]
var char_width = self.digit_width # normal digit (and seconds slot): configured width
if symbol == -1
char_width = 2 # colon: blocks 2 wide
elif symbol == 1
# The 1 is narrower than other digits:
# narrow: just the stem (thickness)
# flag: stem + 2 px flag on the left
# flag and foot: stem + 2 px on both sides
char_width = self.thickness + self.one_mode * 2
end
widths.push(char_width)
total += char_width + self.spacing
end
self.characters = chars
self.cell_widths = widths
# The spacing after the last character does not count, otherwise the
# group sits half a spacing too far to the left.
self.total_width = total - self.spacing
# Date string, for example "THU 22 OCT". Only built here, because every
# + on a string creates a new string and we do not want that 40x per second.
self.date_text = self.day_names[weekday()] + " " + str(day()) + " " +
self.month_names[month() - 1]
end
# -------------------------------------------------------------------
# draw(): about 40x per second, only while the app is on screen.
# Nothing is built or calculated here that is already ready. We read
# the members and draw. Anything that works with new text or lists in
# here would put needless load on the memory.
# -------------------------------------------------------------------
def draw()
clear()
# Time not known yet: show a gray "..." instead of an empty screen.
if self.characters == nil
font("small")
text(1, self.y_date, "...", 0x666666)
return
end
# Short names for frequently used values, to keep the drawing lines readable
var stroke = self.thickness # stroke thickness
var digit_h = self.digit_height # digit height
var y = self.y_top # top of the digits
var color = self.time_color # color of the digits
var half = (digit_h - stroke) / 2 # y distance to the top of the middle stroke
# Starting point of the group: the whole group is centered as one unit,
# regardless of whether the hour has 1 or 2 digits and whether seconds are on.
var x = (width() - self.total_width) / 2
# Current second, only used for the seconds slots (-2).
# second() returns -1 if the time is not known yet; then we show 0.
var secs = second()
if secs < 0 secs = 0 end
var sec_slot = 0 # 0 = tens, 1 = units of the seconds
# --- Colon: determine visibility and color for this frame ---
# Both colons (hours/minutes and minutes/seconds) use this.
var colon_visible = true
var colon_color = color
if self.colon_mode == 1
# Blink: first half of the cycle on, second half off.
# now_ms() % cycle is the position within the current cycle.
colon_visible = now_ms() % self.colon_cycle < self.colon_half_cycle
elif self.colon_mode == 2
# Breathe: the position in the cycle is converted to 0..2000 and from
# that we make a triangle wave 0..1000 (up, then down). The square
# makes the movement softer, and the lower limit of 10% makes sure
# the colon never goes completely out.
var phase = (now_ms() % self.colon_cycle) * 2000 / self.colon_cycle
var brightness = phase < 1000 ? phase : 2000 - phase
brightness = brightness * brightness / 1000
brightness = 100 + brightness * 9 / 10 # between 10% and 100% (scale 0..1000)
# The color is split into red, green and blue (with bit shifts)
# and each part is multiplied by the brightness.
colon_color = rgb(((color >> 16) & 255) * brightness / 1000,
((color >> 8) & 255) * brightness / 1000,
(color & 255) * brightness / 1000)
end
# Fixed position of the two colon blocks: around a quarter and three
# quarters of the digit height, and 1 px lower than the calculated spot.
var colon_top = y + digit_h / 4 - self.colon_height / 2 + 1
var colon_bottom = y + (3 * digit_h) / 4 - self.colon_height / 2 + 1
# --- Draw all characters from left to right ---
for i : 0 .. size(self.characters) - 1
var symbol = self.characters[i] # 0-9, -1 = colon, -2 = seconds digit
var cell_w = self.cell_widths[i] # width of the cell of this character
var draw_x = x # x where this digit is drawn
var draw_w = cell_w # width this digit is drawn with
if symbol == -2
# Seconds slot: pick the digit from the current second.
symbol = sec_slot == 0 ? secs / 10 : secs % 10
sec_slot += 1
if symbol == 1
# A 1 is narrower than the fixed cell: draw it at its natural
# width and center it in the cell, so the group does not shift.
draw_w = stroke + self.one_mode * 2
draw_x = x + (cell_w - draw_w) / 2
end
end
if symbol < 0
# Colon: two blocks above each other. The spot stays reserved even
# when it is invisible at the moment, so nothing shifts.
if colon_visible
rect_fill(x, colon_top, cell_w, self.colon_height, colon_color)
rect_fill(x, colon_bottom, cell_w, self.colon_height, colon_color)
end
elif symbol == 1 && self.one_mode > 0
# The 1 with flag (and optionally foot) has its own drawing and does
# not use the segments. The stem starts 2 px from the left, leaving
# room for the flag on the left.
rect_fill(draw_x + 2, y, stroke, digit_h, color) # stem
rect_fill(draw_x, y + stroke, 2, stroke, color) # flag, 2 px long
if self.one_mode == 2
rect_fill(draw_x, y + digit_h - stroke, draw_w, stroke, color) # foot: 2 px on both sides
end
else
# Normal digit: check which segments are lit and draw each as a rectangle.
# (mask & 1) != 0 means: bit 1 is set, so segment a is lit.
var mask = self.segments[symbol]
if (mask & 1) != 0 rect_fill(draw_x, y, draw_w, stroke, color) end # a: top bar
if (mask & 2) != 0 rect_fill(draw_x + draw_w - stroke, y, stroke, half + stroke, color) end # b: top right, down to the middle bar
if (mask & 4) != 0 rect_fill(draw_x + draw_w - stroke, y + half, stroke, digit_h - half, color) end # c: bottom right, from the middle bar
if (mask & 8) != 0 rect_fill(draw_x, y + digit_h - stroke, draw_w, stroke, color) end # d: bottom bar
if (mask & 16) != 0 rect_fill(draw_x, y + half, stroke, digit_h - half, color) end # e: bottom left, from the middle bar
if (mask & 32) != 0 rect_fill(draw_x, y, stroke, half + stroke, color) end # f: top left, down to the middle bar
if (mask & 64) != 0 rect_fill(draw_x, y + half, draw_w, stroke, color) end # g: middle bar
# Rounded corners: erase the corner pixel, but only where two strokes
# really meet (free ends stay square). The numbers are combined
# bits: 33 = a+f, 3 = a+b, 24 = d+e, 12 = d+c.
# Drawing black (0x000000) over the corner makes it disappear.
if self.rounded
if (mask & 33) == 33 pixel(draw_x, y, 0x000000) end # top left: a and f
if (mask & 3) == 3 pixel(draw_x + draw_w - 1, y, 0x000000) end # top right: a and b
if (mask & 24) == 24 pixel(draw_x, y + digit_h - 1, 0x000000) end # bottom left: d and e
if (mask & 12) == 12 pixel(draw_x + draw_w - 1, y + digit_h - 1, 0x000000) end # bottom right: d and c
end
end
# On to the spot of the next character: width of this cell + spacing
x += cell_w + self.spacing
end
# --- Date line ---
# The built-in small font. text_ink_width() measures the width that is
# actually lit, so the date ends up exactly in the middle.
font("small")
var date_width = text_ink_width(self.date_text)
text((width() - date_width) / 2, self.y_date, self.date_text, self.date_color)
end
end
# Required last line: hands an instance of the app back to AWTRIX.
return Clock()
Result (Dutch version):
There is even place, where you can publish it, if you like - https://awtrix.de/
That also provides ability to upload script directly to the AWtrix, I must say that some scripts are really awesome, mostly for AWtrix TC002, like :
And with Homey integration, you can control them, modify parameters, turning on-off (just submitted PR for that)… really nice. So eg. for Beautiful Weather, I see incomming rain only when it’s really incomming, or sunset, sunrise is enabled just shortly before, Solar data are shown only when there is any Solar activity etc.
Also check Blueforcer’s Sound Showcase script
.
@Bckp: Is there a way to control the built-in voice of the TC002. E.g. change the speed or pitch? When using this flowcard:
Actually it’s already serving as my new Doorbell chim and it serves also as Radio player for some situations
I wasn’t expecting so much from it but, happy to have it now…
i will check the API, if it is in API, i will add it ![]()