Skip to content

Stock assets

Every BUSY Bar ships with pictures, animations, sounds, fonts and themes already on it. Referencing one costs nothing and needs no upload - which makes it the first thing to reach for, before converting and uploading your own.

Everything here is shown as well as named: the pictures and animations are enlarged onto the black an unlit panel shows, and the sounds have players. One thing does not survive being read on GitHub - it drops audio players - so the published guide is the complete version.

They live under /ext/apps_assets/, in two trees:

Tree What it is
shared/ For anyone: icons, status animations, fonts, notification sounds
busy/ The BUSY timer's own decoration - session animations, indicators, its themes

How to reference one

Anything that draws or plays takes stock_path for a built-in and path for a file you uploaded. A stock path is the tree, the folder and the file name with its extension - relative to /ext/apps_assets/:

from busylib import BusyBar, types

with BusyBar("192.168.1.50", token=PIN) as bar:
    bar.display_draw(
        types.DisplayElements(
            application_name="my-app",
            elements=[
                types.ImageElement(
                    id="10",
                    x=0,
                    y=4,
                    stock_path="shared/images/clock_5x5.image",
                ),
            ],
        )
    )

    bar.audio_play(
        stock_path="shared/sounds/calendar_event_starts.snd",
        application_name="my-app",
    )

The folder and the extension are both required: the flat shared/clock form the OpenAPI spec suggests is refused by the device.

The size is in the file name, and it is the size you have to lay out around - clock_5x5.image is five pixels square. _front_ and _back_ say which display an asset was drawn for: the front is a 72x16 strip, the back a 16x16 square. Nothing stops you drawing either one anywhere; it will just look wrong.

What is there

The counts below come from the firmware sources, not from a bar: a bar carries whatever its owner has uploaded or deleted, and a bar on an older build has whatever that build shipped - as this one does, with 19 of the 20 status animations, because the twentieth landed a week after it was built. So this is what a bar ships with, and asking a bar is how you find out what one actually has.

make stock-assets FIRMWARE=<checkout> regenerates the table, and CHECK=1 reports drift without writing:

Counted from the firmware sources at 736af4c78, 2026-09-11:

On the device How many Since Last changed
Icons and pictures shared/images/ 84, 66 of them the dt_* sticker set 0.8.1 2026-08-07
Status animations shared/animations/ 20 0.8.1 2026-09-11
Fonts shared/fonts/ 10 0.8.1 2026-05-15
Notification sounds shared/sounds/ 3 0.8.1 2026-05-12
Timer animations busy/animations/ 22 0.1.0 2026-07-16
Timer pictures busy/images/ 13 0.1.0 2026-07-16
Timer sounds busy/sounds/ 3 0.1.0 2026-03-20
Themes busy/themes/ 12 0.7.2 2026-05-13

Icons

Eight of them have names in busylib, because notifications lay themselves out around one and need its width. The pictures below link into the firmware at the commit the table above was counted from, so they cannot drift from it:

The eight with short names:

Name File
checkmark_front_8x8 check checkmark_front_8x8.image
error_front_8x8 error error_front_8x8.image
info_front_8x8 info info_front_8x8.image
low_battery_front_8x8 low_battery low_battery_front_8x8.image
clock_5x5 clock clock_5x5.image
hourglass_5x5 hourglass hourglass_5x5.image
start_11x11 start start_11x11.image
setup_11x11 setup setup_11x11.image

The Draw Tool's set, under the names the Draw Tool shows - these are 16x16, twice the width of most built-in icons, and the layout moves the text along accordingly:

dt_apple_green
dt_apple_green
dt_apple_red
dt_apple_red
dt_apple_yellow
dt_apple_yellow
dt_available
dt_available
dt_basketball
dt_basketball
dt_book
dt_book
dt_burger
dt_burger
dt_chicken
dt_chicken
dt_coctail
dt_coctail
dt_coffee
dt_coffee
dt_crescent_moon_1
dt_crescent_moon_1
dt_crescent_moon_2
dt_crescent_moon_2
dt_dialog
dt_dialog
dt_dialog_no
dt_dialog_no
dt_dialog_yes
dt_dialog_yes
dt_drink_1
dt_drink_1
dt_drink_2
dt_drink_2
dt_emoji_angry
dt_emoji_angry
dt_emoji_awkward
dt_emoji_awkward
dt_emoji_cry
dt_emoji_cry
dt_emoji_dead
dt_emoji_dead
dt_emoji_evil
dt_emoji_evil
dt_emoji_expressionless
dt_emoji_expressionless
dt_emoji_eyes
dt_emoji_eyes
dt_emoji_fatigue
dt_emoji_fatigue
dt_emoji_glasses
dt_emoji_glasses
dt_emoji_grinning
dt_emoji_grinning
dt_emoji_happy
dt_emoji_happy
dt_emoji_heart_eyes
dt_emoji_heart_eyes
dt_emoji_laught
dt_emoji_laught
dt_emoji_melted
dt_emoji_melted
dt_emoji_panic
dt_emoji_panic
dt_emoji_relief
dt_emoji_relief
dt_emoji_sad
dt_emoji_sad
dt_emoji_sleep
dt_emoji_sleep
dt_emoji_surprised
dt_emoji_surprised
dt_emoji_sweat_smile
dt_emoji_sweat_smile
dt_emoji_tounge
dt_emoji_tounge
dt_football
dt_football
dt_heart_blue
dt_heart_blue
dt_heart_green
dt_heart_green
dt_heart_light_blue
dt_heart_light_blue
dt_heart_orange
dt_heart_orange
dt_heart_pink
dt_heart_pink
dt_heart_red
dt_heart_red
dt_heart_violet
dt_heart_violet
dt_heart_yellow
dt_heart_yellow
dt_home
dt_home
dt_leaf
dt_leaf
dt_moon_1
dt_moon_1
dt_moon_2
dt_moon_2
dt_no
dt_no
dt_pie
dt_pie
dt_pizza
dt_pizza
dt_pizza_margarita
dt_pizza_margarita
dt_pizza_peperoni
dt_pizza_peperoni
dt_sparkls_1
dt_sparkls_1
dt_sparkls_2
dt_sparkls_2
dt_study
dt_study
dt_tea
dt_tea
dt_tennis
dt_tennis
dt_toast
dt_toast
dt_tomato
dt_tomato
dt_unavailable
dt_unavailable
dt_work
dt_work
dt_yes
dt_yes

The rest of what the firmware ships, mostly its own furniture:

active_indicator_left_28x7
active_indicator_left_28x7
active_indicator_right_28x7
active_indicator_right_28x7
apps_menu_back_12x12
apps_menu_back_12x12
charging_battery_front_8x8
charging_battery_front_8x8
checkmark_back_11x11
checkmark_back_11x11
error_back_11x11
error_back_11x11
info_back_11x11
info_back_11x11
missing_battery_front_8x8
missing_battery_front_8x8
unknown_app_back_11x11
unknown_app_back_11x11
unknown_app_front_8x8
unknown_app_front_8x8

And the timer's own:

header_busy_41x16
header_busy_41x16
header_custom_41x16
header_custom_41x16
hourglass_11x11
hourglass_11x11
hourglass_8x8
hourglass_8x8
indicator_busy_41x16
indicator_busy_41x16
indicator_mask_41x16
indicator_mask_41x16
indicator_rest_41x16
indicator_rest_41x16
palette_11x11
palette_11x11
palette_8x8
palette_8x8
pause_5x5
pause_5x5
smart_home_11x11
smart_home_11x11
smart_home_8x8
smart_home_8x8
tick_red_6x5
tick_red_6x5

The names in code:

Name File Width
check shared/images/checkmark_front_8x8.image 8
error shared/images/error_front_8x8.image 8
info shared/images/info_front_8x8.image 8
low_battery shared/images/low_battery_front_8x8.image 8
clock shared/images/clock_5x5.image 5
hourglass shared/images/hourglass_5x5.image 5
start shared/images/start_11x11.image 11
setup shared/images/setup_11x11.image 11
from busylib.features import notification

await notification.notify(bar, "Laundry done", icon="check", application_name="my-app")

notification.icons(bar) returns every image the bar actually holds, each with the width read from its file header - the dt_* sticker set included (food, faces, activities: dt_coffee, dt_emoji_happy, dt_work ...). Pass a StockIcon from that list anywhere a name is taken.

Animations

shared/animations/ holds the twelve status animations, one per theme, as 72x16 strips - dnd_72x16.anim, meeting_72x16.anim, lunch_72x16.anim, flow_72x16.anim, coding_72x16.anim, on_air_72x16.anim, on_call_72x16.anim, booked_72x16.anim, back_soon_72x16.anim, chill_time_72x16.anim, keep_out_72x16.anim, low_social_battery_72x16.anim - plus the calendar, spinner and transition pieces the firmware uses.

busy/animations/ is the timer's own: progress, particles, the start logos and the transitions between phases.

These are the only pictures kept here rather than linked: the source is a zip of frames, which no browser unpacks. They are thinned to about two dozen frames with the delay stretched to match, so the movement is the bar's at a fraction of the weight.

What the firmware ships:

azuki_16x16
azuki_16x16
8 of 8 frames
back_soon_72x16
back_soon_72x16
17 of 241 frames
booked_72x16
booked_72x16
16 of 600 frames
calendar_event_16x16
calendar_event_16x16
17 of 180 frames
calendar_reminder_16x16
calendar_reminder_16x16
17 of 180 frames
chill_time_72x16
chill_time_72x16
16 of 600 frames
coding_72x16
coding_72x16
17 of 296 frames
dnd_72x16
dnd_72x16
16 of 77 frames
flow_72x16
flow_72x16
16 of 301 frames
keep_out_72x16
keep_out_72x16
17 of 181 frames
low_social_battery_72x16
low_social_battery_72x16
16 of 300 frames
lunch_72x16
lunch_72x16
16 of 540 frames
meeting_72x16
meeting_72x16
16 of 525 frames
on_air_72x16
on_air_72x16
17 of 181 frames
on_call_72x16
on_call_72x16
16 of 121 frames
spinner_back_16x16
spinner_back_16x16
8 of 8 frames
spinner_front_8x8
spinner_front_8x8
16 of 16 frames
start_menu_31x16
start_menu_31x16
17 of 260 frames
transition_select_72x16
transition_select_72x16
17 of 66 frames
wave_invitation_72x16
wave_invitation_72x16
19 of 55 frames

The timer's own:

arrow_green_5x5
arrow_green_5x5
15 of 60 frames
arrow_red_5x5
arrow_red_5x5
15 of 60 frames
ending_particles_72x16
ending_particles_72x16
16 of 121 frames
ending_progress_72x16
ending_progress_72x16
16 of 106 frames
finished_confetti_72x16
finished_confetti_72x16
17 of 181 frames
indicator_busy_72x16
indicator_busy_72x16
16 of 1200 frames
indicator_busy_transition_72x16
indicator_busy_transition_72x16
14 of 41 frames
overview_72x16
overview_72x16
17 of 135 frames
particles_busy_41x16
particles_busy_41x16
15 of 60 frames
particles_rest_41x16
particles_rest_41x16
15 of 60 frames
progress_busy_41x16
progress_busy_41x16
1 of 1 frames
progress_rest_41x22
progress_rest_41x22
17 of 150 frames
start_logo_busy_41x16
start_logo_busy_41x16
15 of 120 frames
start_logo_custom_41x16
start_logo_custom_41x16
15 of 120 frames
transition_done_busy_72x16
transition_done_busy_72x16
16 of 31 frames
transition_done_rest_72x16
transition_done_rest_72x16
16 of 31 frames
transition_flash_72x16
transition_flash_72x16
21 of 21 frames
transition_oval_72x16
transition_oval_72x16
14 of 41 frames
transition_pause_72x16
transition_pause_72x16
14 of 41 frames
transition_select_green_72x16
transition_select_green_72x16
17 of 66 frames
transition_select_red_72x16
transition_select_red_72x16
17 of 66 frames
transition_skip_72x16
transition_skip_72x16
15 of 30 frames
types.AnimationElement(
    id="10",
    x=0,
    y=0,
    loop=True,
    stock_path="shared/animations/dnd_72x16.anim",
)  # one element of a DisplayElements payload, as above

Sounds

Stock path When the firmware uses it
shared/sounds/calendar_event_starts.snd An event is starting
shared/sounds/calendar_reminder_ends.snd A reminder is over
shared/sounds/volume_change.snd The volume moved
busy/sounds/countdown_tick.snd A session's last seconds
busy/sounds/countdown_finish.snd A phase ended
busy/sounds/session_completed.snd The whole session ended

The first three have short names in notification.STOCK_SOUNDS (event, reminder, volume) and are what notify(sound=...) takes. The players below point at the firmware's own WAV sources, so this is what a bar actually plays - on GitHub they are not shown, since it drops audio players from a rendered file; the published guide has them.

Sound Used for
calendar_event_starts an event is starting
calendar_reminder_ends a reminder is over
volume_change the volume moved
countdown_finish a phase ended
countdown_tick a session's last seconds
session_completed the whole session ended

Fonts

Text names a font rather than pointing at one: tiny, small, normal, condensed, bold, large, extra_large, and superscript - which the firmware added in OpenAPI 27.6 for the raised digits a countdown draws. The largest do not fit two lines on the front display, which is why notification.TWO_LINE_FONTS is the shorter list.

Themes

busy/themes/<name>/theme.json - twelve of them, and they are what a session looks like on the bar. They are chosen by name, not by path:

from busylib.features import timer

await timer.themes(bar)  # what this bar has
await timer.start(bar, theme="meeting")  # for this session only
await timer.set_card_theme(bar, "busy", "dnd")  # from now on

A theme a bar does not have raises UnknownThemeError rather than being written and silently ignored. See timers.

Your own assets

An application can upload its own, and they are drawn and played the same way - with one difference worth knowing. Uploads live in /ext/user_assets/<application>/, and the device resolves a path inside the folder of the application making the call. So an upload is named by file name alone, the same name in two applications is two different files, and one application cannot draw another's:

from busylib import converter
from busylib.features import notification

name, payload = converter.convert_for_storage("logo.png", open("logo.png", "rb").read())
await bar.assets_upload(application_name="my-app", filename=name, data=payload)

await notification.notify(bar, "Deploy done", icon="logo", application_name="my-app")

The size comes from the file - a PNG's IHDR, or the firmware format's own header - because an icon's width is what the text is placed after, and an icon wider than the panel pushes the text off the display. One that does not fit is refused rather than drawn.

Which also means an upload somebody else made - through the BUSY app, the Draw Tool, another script - is not yours to draw. copy_to_application makes it yours, byte for byte:

theirs = next(a for a in await assets.discover_assets(bar) if a.name == "logo")
mine = await assets.copy_to_application(bar, theirs, "my-app")
await notification.notify(bar, "Deployed", icon=mine.name, application_name="my-app")

Reading the map from a bar

Firmware adds and removes assets, and an owner can upload their own, so the bar is the only authority:

from busylib.features import assets

for asset in await assets.discover_assets(bar):
    print(asset.kind, asset.name, asset.reference, asset.application)

discover_assets reads both roots and every kind - images, animations, sounds, fonts and themes - and gives back what to pass in a call: stock_path for a shipped asset, path for an upload. assets.of_kind(bar, "image") is the same thing as the name-to-path mapping a picker wants, and notification.icons(bar) and timer.themes(bar) are the two shorthands for the lists most often offered to a person. None of them has a copy written down in the library to go stale.