Skip to content

Clients

BusyBar is the synchronous client and AsyncBusyBar is its async/await counterpart. Both compose the same set of endpoint mixins, so the method surface is identical apart from the coroutines.

from busylib import AsyncBusyBar, BusyBar

bb = BusyBar("10.0.4.20")

async_bb = AsyncBusyBar(addr="10.0.4.20", token="my-access-key")

Creating either client has no terminal output and does not contact the device. The first endpoint call performs the connection; see the quick start for its expected output.

busylib.client.BusyBar

BusyBar(addr: str | None = None, *, token: str | None = None, timeout: float | Timeout | None = None, max_retries: int = 2, backoff: float = DEFAULT_BACKOFF, transport: BaseTransport | None = None, api_version: str | None = None, compatibility_mode: CompatibilityMode = 'warn', is_cloud: bool | None = None, device_api_version: str | None = None)

Bases: AccessMixin, AccountMixin, BusyMixin, TimeMixin, UpdaterMixin, FirmwareMixin, StorageMixin, AssetsMixin, DisplayMixin, AudioMixin, WifiMixin, InputMixin, SmartHomeMixin, StateStreamMixin, BleMixin, SyncClientBase

HTTPX-based client for the BUSY Bar API.

Build a client for one bar.

addr is a device address; leaving it out with a token reaches the bar through the cloud, at the host BUSYLIB_CLOUD_URL names. Pass is_cloud=True only to name a cloud host per client, which is what stops an address like api.dev.busy.app being taken for a device.

usb property

usb: UsbController

Lazy-loaded USB controller.

is_usb_connected property

is_usb_connected: bool

Returns True if a USB device was found and connected.

connection_type class-attribute instance-attribute

connection_type: Literal['local', 'cloud', 'network'] = 'network'

max_retries instance-attribute

max_retries = max(0, int(max_retries))

backoff instance-attribute

backoff = backoff

api_version instance-attribute

api_version = api_version or versioning.API_VERSION

compatibility_mode instance-attribute

compatibility_mode: CompatibilityMode = compatibility_mode

client instance-attribute

client = httpx2.Client(base_url=self.base_url, headers=headers or None, timeout=self._timeout, transport=transport)

device_api_version property

device_api_version: str | None

The device's API version, if it is known yet.

None until version() has been called or a value was supplied to the constructor.

is_cloud property

is_cloud: bool

Check whether connection uses cloud mode.

Returns True for cloud connection_type.

is_local property

is_local: bool

Check whether connection uses local mode.

Returns True for local connection_type.

usb_reboot

usb_reboot(*, raise_on_error: bool = False) -> bool

Attempt to reboot the device via USB.

Returns True on success and False on failure by default. If raise_on_error is True, re-raises BusyBarUsbError.

usb_reset

usb_reset(*, raise_on_error: bool = False) -> bool

Alias for usb_reboot().

Provided for callers that prefer "reset" naming.

device_at_least

device_at_least(minimum: str) -> bool | None

Whether the bar is known to run at least minimum.

None means the version is not known, which is not the same as "older": a caller deciding between two request contracts should send the current one rather than assume the device is behind.

method_compatibility

method_compatibility(method_name: str) -> versioning.MethodCompatibility | None

Return declarative OpenAPI compatibility metadata for a client method.

is_local_available

is_local_available() -> bool

Check local API reachability on base_url.

Returns True when /api/version responds without network errors.

close

close() -> None

api_request

api_request(method: str, path: str, *, params: dict[str, Any] | None = None, headers: dict[str, str] | None = None, session_id: str | None = None, application_name: str | None = None, json_payload: JsonType | None = None, data: bytes | Iterable[bytes] | None = None, expect_bytes: bool = False, allow_text: bool = False, timeout: float | Timeout | None = None) -> JsonType | bytes | str

Execute a raw API request through the current client session.

Advanced callers can control path, params, headers, body, and request context without creating a separate HTTP client.

prepare_request

prepare_request(method: str, path: str, *, params: dict[str, Any] | None = None, headers: dict[str, str] | None = None, session_id: str | None = None, application_name: str | None = None, json_payload: JsonType | None = None, data: bytes | Iterable[bytes] | None = None, expect_bytes: bool = False, allow_text: bool = False, timeout: float | Timeout | None = None) -> PreparedRequest

Build a prepared request without executing network I/O.

External integrations can inspect the prepared payload, route it to custom transports, or execute later via execute_prepared_request.

execute_prepared_request

execute_prepared_request(prepared: PreparedRequest, *, client: Client | None = None) -> JsonType | bytes | str

Execute a previously prepared request.

By default the current httpx2.Client is used. Callers may inject a custom client while preserving error mapping. Prepared streaming content is single-use and should be regenerated for repeated executions.

ble_enable

ble_enable() -> types.SuccessResponse

ble_disable

ble_disable() -> types.SuccessResponse

ble_status

ble_status() -> types.BleStatus

ble_pairing_forget

ble_pairing_forget() -> types.SuccessResponse

stream_status_ws

stream_status_ws() -> None

Stream device status updates via WebSocket /api/status/ws.

Sync iteration is not supported because websocket lifecycle and frame handling are asynchronous in this client implementation.

smart_home_pairing

smart_home_pairing() -> types.SmartHomePairingInfo

Fetch smart home pairing status via GET /api/smart_home/pairing.

smart_home_pairing_start

smart_home_pairing_start() -> types.SmartHomePairingPayload

Start smart home pairing via POST /api/smart_home/pairing.

smart_home_pairing_stop

smart_home_pairing_stop() -> types.SuccessResponse

Stop smart home pairing via DELETE /api/smart_home/pairing.

smart_home_switch

smart_home_switch() -> types.SmartHomeSwitchState

Fetch smart home switch state via GET /api/smart_home/switch.

smart_home_switch_set

smart_home_switch_set(state: bool, *, startup: Literal['off', 'on', 'toggle', 'last'] | None = None) -> types.SuccessResponse

Set smart home switch state via POST /api/smart_home/switch.

input

input(key: InputKey) -> types.SuccessResponse

wifi_enable

wifi_enable() -> types.SuccessResponse

Removed from the device API.

No supported firmware serves POST /api/wifi/enable, so calling this raises BusyBarRemovedEndpointError. Use wifi_connect() / wifi_disconnect() instead.

wifi_disable

wifi_disable() -> types.SuccessResponse

Removed from the device API.

No supported firmware serves POST /api/wifi/disable, so calling this raises BusyBarRemovedEndpointError. Use wifi_connect() / wifi_disconnect() instead.

wifi_status

wifi_status() -> types.StatusResponse

wifi_connect

wifi_connect(config: ConnectRequestConfig | dict[str, Any]) -> types.SuccessResponse

wifi_disconnect

wifi_disconnect() -> types.SuccessResponse

wifi_networks

wifi_networks() -> types.NetworkResponse

Scan for nearby networks via GET /api/wifi/networks.

The device cannot scan while it is associated: doing so returns 400 "Scan not possible when connected" as a BusyBarAPIError. Disconnect first with wifi_disconnect(), or skip the scan and pass the SSID to wifi_connect() directly.

audio_play

audio_play(*, path: str | None = None, stock_path: str | None = None, payload: AudioPlayRequest | dict[str, Any] | None = None, **request_kwargs: Unpack[RequestKwargs]) -> types.SuccessResponse

Play audio through POST /api/audio/play.

The endpoint payload may reference either an uploaded asset path or a stock path. Explicit path and stock_path keyword arguments override the same keys provided in payload. Request context such as application_name and session_id is accepted through request kwargs; use api_request for fully custom bodies.

audio_stop

audio_stop() -> types.SuccessResponse

Stop audio through DELETE /api/audio/play.

Uses API-like naming for callers that mirror firmware endpoints.

audio_volume

audio_volume() -> types.AudioVolumeInfo

audio_volume_set

audio_volume_set(volume: float) -> types.SuccessResponse

display_draw

display_draw(display_data: DisplayElements | dict[str, Any], *, clear_before_draw: bool = False, sanitize_text: bool = False, **request_kwargs: Unpack[RequestKwargs]) -> types.SuccessResponse

display

display(display_data: DisplayElements | dict[str, Any], *, clear_before_draw: bool = False, sanitize_text: bool = False, audio_payload: AudioPlayRequest | dict[str, Any] | None = None, **request_kwargs: Unpack[RequestKwargs]) -> types.SuccessResponse

Render display content and optionally play audio after draw.

Operations are sequential, not atomic: clear may succeed before draw fails, and audio failure may occur after display content is visible. Exceptions include the failed endpoint path for diagnostics.

display_clear

display_clear(*, element_ids: Sequence[str] | None = None, **request_kwargs: Unpack[RequestKwargs]) -> types.SuccessResponse

Clear display content through DELETE /api/display/draw.

Without element_ids this removes everything the application drew. With them, only those elements go - which is how a drawing built from several pieces loses one of them without being redrawn whole.

Uses API-like naming for callers that mirror firmware endpoints.

display_brightness

display_brightness() -> types.DisplayBrightnessInfo

display_brightness_set

display_brightness_set(value: BrightnessValue) -> types.SuccessResponse

screen

screen(display_id: DisplaySpecLike) -> bytes

Fetch a single display frame via GET /api/screen.

Returns RGB bytes, three per pixel: 3456 for the front display (72x16) and 38400 for the back (160x80, sent L4-packed and expanded to grey triples here). The device sends colour as BGR and it is swapped on the way out.

The response body is base64-encoded, uncompressed framebuffer bytes (the Content-Type: image/bmp header is misleading, there is no real BMP header).

frame

frame(display_id: DisplaySpecLike) -> Frame

Fetch a display frame as a Frame via GET /api/screen.

The same bytes as screen(), with the geometry and the display attached, so callers can read pixels, rows or a PNG without tracking the layout themselves.

assets_upload

assets_upload(application_name: str, filename: str, data: bytes, *, timeout: float | Timeout | None = ASSET_UPLOAD_TIMEOUT) -> types.SuccessResponse

Upload an asset file for the given application.

Uses a longer default timeout to tolerate large payload uploads.

assets_delete

assets_delete(application_name: str) -> types.SuccessResponse

storage_write

storage_write(path: str, data: bytes, *, append: bool = False, timeout: float | None = 60.0, progress_callback: Callable[[int, int], None] | None = None, chunk_size: int = 64 * 1024) -> types.SuccessResponse

Write a file, converting it for the device first.

append adds to the file instead of replacing it, creating it if it is not there - which is what makes a log or a growing asset possible without reading the whole file back first.

storage_read

storage_read(path: str) -> bytes

storage_list

storage_list(path: str) -> types.StorageList

storage_remove

storage_remove(path: str) -> types.SuccessResponse

storage_mkdir

storage_mkdir(path: str) -> types.SuccessResponse

Create a storage directory via POST /api/storage/mkdir.

storage_rename

storage_rename(old_path: str, new_path: str) -> types.SuccessResponse

Rename a storage entry via POST /api/storage/rename.

storage_status

storage_status() -> types.StorageStatus

version

version() -> types.VersionInfo

Fetch API version info and validate compatibility.

transport

transport() -> types.NetworkInterfaceInfo

Fetch active network transport via GET /api/transport.

status

status() -> types.Status

Fetch full device status via GET /api/status.

status_device

status_device() -> types.StatusDevice

Fetch device manufacturing status via GET /api/status/device.

status_firmware

status_firmware() -> types.StatusFirmware

Fetch firmware status via GET /api/status/firmware.

status_system

status_system() -> types.StatusSystem

Fetch runtime status via GET /api/status/system.

status_power

status_power() -> types.StatusPower

Fetch power status via GET /api/status/power.

log_dump

log_dump(filename: str | None = None, *, path: str | None = None) -> types.LogDumpResponse

Dump the in-memory device log buffer to a storage file.

The contract changed at OpenAPI 25.0.0 and the two are not translatable, so both are kept rather than one being dropped:

  • filename is a bare name matching ^[a-zA-Z0-9_-]+$; the device adds the extension and the storage path. 25.0.0 and later.
  • path is a full destination path such as /ext/dump.log. Before 25.0.0.

With neither, the device writes to its own default and the request is identical on both, which is why plain log_dump() needs no version at all.

When the device version is known - after version(), or from device_api_version= - asking for the wrong one raises here instead of being refused by the bar with a bare 400.

name

name() -> types.DeviceNameResponse

Fetch device name via GET /api/name.

name_set

name_set(name: str) -> types.SuccessResponse

Set device name via POST /api/name.

time

time() -> types.DeviceTimeResponse

Fetch device time via GET /api/time.

update

update(firmware_data: bytes) -> types.SuccessResponse

Upload firmware update TAR and initiate update.

update_check

update_check() -> types.SuccessResponse

Start asynchronous firmware update check.

update_status

update_status() -> types.UpdateStatus

Get firmware update status with progress information.

update_changelog

update_changelog(version: str) -> types.UpdateChangelogResponse

Fetch update changelog for a specific version.

update_install

update_install(version: str) -> types.SuccessResponse

Start firmware update installation by version.

update_abort_download

update_abort_download() -> types.SuccessResponse

Abort an ongoing firmware download.

update_autoupdate

update_autoupdate() -> types.AutoupdateSettings

Fetch autoupdate settings via GET /api/update/autoupdate.

update_autoupdate_set

update_autoupdate_set(settings: AutoupdateSettings | dict[str, object]) -> types.SuccessResponse

Set autoupdate settings via POST /api/update/autoupdate.

time_timezone_info

time_timezone_info() -> types.TimezoneInfo

Fetch current device timezone via GET /api/time/timezone.

time_timezone_list

time_timezone_list() -> types.TimezoneListResponse

Fetch supported device timezones via GET /api/time/tzlist.

time_timestamp

time_timestamp(timestamp: str) -> types.SuccessResponse

Set device time via POST /api/time/timestamp.

time_timezone

time_timezone(timezone: str) -> types.SuccessResponse

Set device timezone via POST /api/time/timezone.

busy_snapshot

busy_snapshot() -> types.BusySnapshot

Fetch busy snapshot via GET /api/busy/snapshot.

busy_snapshot_set

busy_snapshot_set(snapshot: BusySnapshot) -> types.SuccessResponse

Set busy snapshot via PUT /api/busy/snapshot.

busy_profile

busy_profile(slot: BusyProfileSlot) -> types.BusyProfile

Fetch a busy profile slot via GET /api/busy/profiles/{slot}.

busy_profile_set

busy_profile_set(slot: BusyProfileSlot, profile: BusyProfile | dict[str, object]) -> types.SuccessResponse

Set a busy profile slot via PUT /api/busy/profiles/{slot}.

account_unlink() -> types.SuccessResponse

Unlink the device from the account via DELETE /api/account.

account_link() -> types.AccountLink

Request an account link code via POST /api/account/link.

account_info

account_info() -> types.AccountInfo

Fetch linked account info via GET /api/account/info.

account_status

account_status() -> types.AccountState

Fetch MQTT status info via GET /api/account/status.

account_backend

account_backend() -> types.AccountBackend

Fetch MQTT backend settings via GET /api/account/backend.

account_backend_set

account_backend_set(backend: AccountBackend | dict[str, object]) -> types.SuccessResponse

Set MQTT backend settings via PUT /api/account/backend.

account_profile

account_profile() -> types.AccountProfile

Removed from the device API.

No supported firmware serves GET /api/account/profile, so calling this raises BusyBarRemovedEndpointError. Use account_backend() instead.

account_profile_set

account_profile_set(profile: AccountProfileName, custom_url: str | None = None) -> types.SuccessResponse

Removed from the device API.

No supported firmware serves POST /api/account/profile, so calling this raises BusyBarRemovedEndpointError. Use account_backend_set() instead.

access

access() -> types.HttpAccessInfo

Fetch HTTP access mode via GET /api/access.

access_set

access_set(mode: HttpAccessMode, key: str) -> types.SuccessResponse

Set HTTP access mode via POST /api/access.

access_tokens_list

access_tokens_list() -> types.AccessTokensInfo

List issued access tokens via GET /api/access/tokens.

The secret itself is returned only when a token is minted, so every token here has token set to None.

Added by busy-app/busybar-firmware#886 and served from OpenAPI 27.5.0, first released in firmware 1.2.3; earlier firmware 404s.

access_token_mint

access_token_mint(name: str) -> types.AccessToken

Issue a new access token via POST /api/access/tokens.

This is the only time the device discloses the secret; it cannot be read back afterwards.

Added by busy-app/busybar-firmware#886 and served from OpenAPI 27.5.0, first released in firmware 1.2.3; earlier firmware 404s.

access_tokens_delete_all

access_tokens_delete_all() -> types.SuccessResponse

Revoke every access token via DELETE /api/access/tokens.

Added by busy-app/busybar-firmware#886 and served from OpenAPI 27.5.0, first released in firmware 1.2.3; earlier firmware 404s.

access_tokens_revoke

access_tokens_revoke(short_id: str) -> types.SuccessResponse

Revoke one access token via DELETE /api/access/tokens/{short_id}.

Added by busy-app/busybar-firmware#886 and served from OpenAPI 27.5.0, first released in firmware 1.2.3; earlier firmware 404s.

busylib.client.AsyncBusyBar

AsyncBusyBar(addr: str | None = None, *, token: str | None = None, timeout: float | Timeout | None = None, max_retries: int = 2, backoff: float = DEFAULT_BACKOFF, transport: AsyncBaseTransport | None = None, api_version: str | None = None, compatibility_mode: CompatibilityMode = 'warn', is_cloud: bool | None = None, device_api_version: str | None = None)

Bases: AsyncAccessMixin, AsyncAccountMixin, AsyncBusyMixin, AsyncTimeMixin, AsyncUpdaterMixin, AsyncFirmwareMixin, AsyncStorageMixin, AsyncAssetsMixin, AsyncDisplayMixin, AsyncAudioMixin, AsyncWifiMixin, AsyncInputMixin, AsyncSmartHomeMixin, AsyncStateStreamMixin, AsyncBleMixin, AsyncClientBase

Async HTTPX-based client for the BUSY Bar API.

Build a client for one bar.

addr is a device address; leaving it out with a token reaches the bar through the cloud, at the host BUSYLIB_CLOUD_URL names. Pass is_cloud=True only to name a cloud host per client, which is what stops an address like api.dev.busy.app being taken for a device.

usb property

usb: AsyncUsbController

Lazy-loaded USB controller.

is_usb_connected property

is_usb_connected: bool

Returns True if a USB device was found and connected.

connection_type class-attribute instance-attribute

connection_type: Literal['local', 'cloud', 'network'] = 'network'

max_retries instance-attribute

max_retries = max(0, int(max_retries))

backoff instance-attribute

backoff = backoff

api_version instance-attribute

api_version = api_version or versioning.API_VERSION

compatibility_mode instance-attribute

compatibility_mode: CompatibilityMode = compatibility_mode

client instance-attribute

client = httpx2.AsyncClient(base_url=self.base_url, headers=headers or None, timeout=self._timeout, transport=transport)

device_api_version property

device_api_version: str | None

The device's API version, if it is known yet.

None until version() has been called or a value was supplied to the constructor.

is_cloud property

is_cloud: bool

Check whether connection uses cloud mode.

Returns True for cloud connection_type.

is_local property

is_local: bool

Check whether connection uses local mode.

Returns True for local connection_type.

usb_reboot async

usb_reboot(*, raise_on_error: bool = False) -> bool

Attempt to reboot the device via USB.

usb_reset async

usb_reset(*, raise_on_error: bool = False) -> bool

Alias for usb_reboot().

Provided for callers that prefer "reset" naming.

device_at_least

device_at_least(minimum: str) -> bool | None

Whether the bar is known to run at least minimum.

None means the version is not known, which is not the same as "older": a caller deciding between two request contracts should send the current one rather than assume the device is behind.

method_compatibility

method_compatibility(method_name: str) -> versioning.MethodCompatibility | None

Return declarative OpenAPI compatibility metadata for a client method.

is_local_available async

is_local_available() -> bool

Check local API reachability on base_url.

Returns True when /api/version responds without network errors.

aclose async

aclose() -> None

api_request async

api_request(method: str, path: str, *, params: dict[str, Any] | None = None, headers: dict[str, str] | None = None, session_id: str | None = None, application_name: str | None = None, json_payload: JsonType | None = None, data: bytes | AsyncIterable[bytes] | None = None, expect_bytes: bool = False, allow_text: bool = False, timeout: float | Timeout | None = None) -> JsonType | bytes | str

Execute a raw async API request through the current client session.

Advanced callers can control path, params, headers, body, and request context without creating a separate HTTP client.

prepare_request

prepare_request(method: str, path: str, *, params: dict[str, Any] | None = None, headers: dict[str, str] | None = None, session_id: str | None = None, application_name: str | None = None, json_payload: JsonType | None = None, data: bytes | AsyncIterable[bytes] | None = None, expect_bytes: bool = False, allow_text: bool = False, timeout: float | Timeout | None = None) -> PreparedRequest

Build a prepared async request without executing network I/O.

External integrations can inspect the prepared payload, route it to custom transports, or execute later via execute_prepared_request. Prepared request should be executed with an async executor.

execute_prepared_request async

execute_prepared_request(prepared: PreparedRequest, *, client: AsyncClient | None = None) -> JsonType | bytes | str

Execute a previously prepared async request.

By default the current httpx2.AsyncClient is used. Callers may inject a custom client while preserving error mapping. Prepared streaming content is single-use and should be regenerated for repeated executions.

ble_enable async

ble_enable() -> types.SuccessResponse

ble_disable async

ble_disable() -> types.SuccessResponse

ble_status async

ble_status() -> types.BleStatus

ble_pairing_forget async

ble_pairing_forget() -> types.SuccessResponse

stream_status_ws async

stream_status_ws(*, enable: bool = True, decode_protobuf: bool = True) -> AsyncIterator[dict[str, Any] | bytes | str]

Open /api/status/ws and yield status updates.

When decode_protobuf=True, binary frames are decoded using BSB_State.State protobuf schema from bsb-protobuf and converted into dictionaries with original proto field names.

smart_home_pairing async

smart_home_pairing() -> types.SmartHomePairingInfo

Fetch smart home pairing status via GET /api/smart_home/pairing.

smart_home_pairing_start async

smart_home_pairing_start() -> types.SmartHomePairingPayload

Start smart home pairing via POST /api/smart_home/pairing.

smart_home_pairing_stop async

smart_home_pairing_stop() -> types.SuccessResponse

Stop smart home pairing via DELETE /api/smart_home/pairing.

smart_home_switch async

smart_home_switch() -> types.SmartHomeSwitchState

Fetch smart home switch state via GET /api/smart_home/switch.

smart_home_switch_set async

smart_home_switch_set(state: bool, *, startup: Literal['off', 'on', 'toggle', 'last'] | None = None) -> types.SuccessResponse

Set smart home switch state via POST /api/smart_home/switch.

input async

input(key: InputKey) -> types.SuccessResponse

wifi_enable async

wifi_enable() -> types.SuccessResponse

Removed from the device API.

No supported firmware serves POST /api/wifi/enable, so calling this raises BusyBarRemovedEndpointError. Use wifi_connect() / wifi_disconnect() instead.

wifi_disable async

wifi_disable() -> types.SuccessResponse

Removed from the device API.

No supported firmware serves POST /api/wifi/disable, so calling this raises BusyBarRemovedEndpointError. Use wifi_connect() / wifi_disconnect() instead.

wifi_status async

wifi_status() -> types.StatusResponse

wifi_connect async

wifi_connect(config: ConnectRequestConfig | dict[str, Any]) -> types.SuccessResponse

wifi_disconnect async

wifi_disconnect() -> types.SuccessResponse

wifi_networks async

wifi_networks() -> types.NetworkResponse

Scan for nearby networks via GET /api/wifi/networks.

The device cannot scan while it is associated: doing so returns 400 "Scan not possible when connected" as a BusyBarAPIError. Disconnect first with wifi_disconnect(), or skip the scan and pass the SSID to wifi_connect() directly.

audio_play async

audio_play(*, path: str | None = None, stock_path: str | None = None, payload: AudioPlayRequest | dict[str, Any] | None = None, **request_kwargs: Unpack[RequestKwargs]) -> types.SuccessResponse

Play audio through async POST /api/audio/play.

The endpoint payload may reference either an uploaded asset path or a stock path. Explicit path and stock_path keyword arguments override the same keys provided in payload. Request context such as application_name and session_id is accepted through request kwargs; use api_request for fully custom bodies.

audio_stop async

audio_stop() -> types.SuccessResponse

Stop audio through async DELETE /api/audio/play.

Uses API-like naming for callers that mirror firmware endpoints.

audio_volume async

audio_volume() -> types.AudioVolumeInfo

audio_volume_set async

audio_volume_set(volume: float) -> types.SuccessResponse

display_draw async

display_draw(display_data: DisplayElements | dict[str, Any], *, clear_before_draw: bool = False, sanitize_text: bool = False, **request_kwargs: Unpack[RequestKwargs]) -> types.SuccessResponse

display async

display(display_data: DisplayElements | dict[str, Any], *, clear_before_draw: bool = False, sanitize_text: bool = False, audio_payload: AudioPlayRequest | dict[str, Any] | None = None, **request_kwargs: Unpack[RequestKwargs]) -> types.SuccessResponse

Render display content and optionally play audio after draw.

Operations are sequential, not atomic: clear may succeed before draw fails, and audio failure may occur after display content is visible. Exceptions include the failed endpoint path for diagnostics.

display_clear async

display_clear(*, element_ids: Sequence[str] | None = None, **request_kwargs: Unpack[RequestKwargs]) -> types.SuccessResponse

Clear display content through async DELETE /api/display/draw.

Without element_ids this removes everything the application drew. With them, only those elements go - which is how a drawing built from several pieces loses one of them without being redrawn whole.

Uses API-like naming for callers that mirror firmware endpoints.

display_brightness async

display_brightness() -> types.DisplayBrightnessInfo

display_brightness_set async

display_brightness_set(value: BrightnessValue) -> types.SuccessResponse

screen async

screen(display_id: DisplaySpecLike) -> bytes

Fetch a single display frame via GET /api/screen.

Returns RGB bytes, three per pixel: 3456 for the front display (72x16) and 38400 for the back (160x80, sent L4-packed and expanded to grey triples here). The device sends colour as BGR and it is swapped on the way out.

The response body is base64-encoded, uncompressed framebuffer bytes (the Content-Type: image/bmp header is misleading, there is no real BMP header).

frame async

frame(display_id: DisplaySpecLike) -> Frame

Fetch a display frame as a Frame via GET /api/screen.

The same bytes as screen(), with the geometry and the display attached, so callers can read pixels, rows or a PNG without tracking the layout themselves.

assets_upload async

assets_upload(application_name: str, filename: str, data: bytes, *, timeout: float | Timeout | None = ASSET_UPLOAD_TIMEOUT) -> types.SuccessResponse

Upload an asset file for the given application.

Uses a longer default timeout to tolerate large payload uploads.

assets_delete async

assets_delete(application_name: str) -> types.SuccessResponse

storage_write async

storage_write(path: str, data: bytes, *, append: bool = False, timeout: float | None = 60.0, progress_callback: Callable[[int, int], None] | None = None, chunk_size: int = 64 * 1024) -> types.SuccessResponse

storage_read async

storage_read(path: str) -> bytes

storage_list async

storage_list(path: str) -> types.StorageList

storage_remove async

storage_remove(path: str) -> types.SuccessResponse

storage_mkdir async

storage_mkdir(path: str) -> types.SuccessResponse

Create a storage directory via POST /api/storage/mkdir.

storage_rename async

storage_rename(old_path: str, new_path: str) -> types.SuccessResponse

Rename a storage entry via POST /api/storage/rename.

storage_status async

storage_status() -> types.StorageStatus

version async

version() -> types.VersionInfo

Fetch API version info and validate compatibility.

transport async

transport() -> types.NetworkInterfaceInfo

Fetch active network transport via GET /api/transport.

status async

status() -> types.Status

Fetch full device status via GET /api/status.

status_device async

status_device() -> types.StatusDevice

Fetch device manufacturing status via GET /api/status/device.

status_firmware async

status_firmware() -> types.StatusFirmware

Fetch firmware status via GET /api/status/firmware.

status_system async

status_system() -> types.StatusSystem

Fetch runtime status via GET /api/status/system.

status_power async

status_power() -> types.StatusPower

Fetch power status via GET /api/status/power.

log_dump async

log_dump(filename: str | None = None, *, path: str | None = None) -> types.LogDumpResponse

Dump the in-memory device log buffer to a storage file.

The contract changed at OpenAPI 25.0.0 and the two are not translatable, so both are kept rather than one being dropped:

  • filename is a bare name matching ^[a-zA-Z0-9_-]+$; the device adds the extension and the storage path. 25.0.0 and later.
  • path is a full destination path such as /ext/dump.log. Before 25.0.0.

With neither, the device writes to its own default and the request is identical on both, which is why plain log_dump() needs no version at all.

When the device version is known - after version(), or from device_api_version= - asking for the wrong one raises here instead of being refused by the bar with a bare 400.

name async

name() -> types.DeviceNameResponse

Fetch device name via GET /api/name.

name_set async

name_set(name: str) -> types.SuccessResponse

Set device name via POST /api/name.

time async

time() -> types.DeviceTimeResponse

Fetch device time via GET /api/time.

update async

update(firmware_data: bytes) -> types.SuccessResponse

Upload firmware update TAR and initiate update.

update_check async

update_check() -> types.SuccessResponse

Start asynchronous firmware update check.

update_status async

update_status() -> types.UpdateStatus

Get firmware update status with progress information.

update_changelog async

update_changelog(version: str) -> types.UpdateChangelogResponse

Fetch update changelog for a specific version.

update_install async

update_install(version: str) -> types.SuccessResponse

Start firmware update installation by version.

update_abort_download async

update_abort_download() -> types.SuccessResponse

Abort an ongoing firmware download.

update_autoupdate async

update_autoupdate() -> types.AutoupdateSettings

Fetch autoupdate settings via GET /api/update/autoupdate.

update_autoupdate_set async

update_autoupdate_set(settings: AutoupdateSettings | dict[str, object]) -> types.SuccessResponse

Set autoupdate settings via POST /api/update/autoupdate.

time_timezone_info async

time_timezone_info() -> types.TimezoneInfo

Fetch current device timezone via GET /api/time/timezone.

time_timezone_list async

time_timezone_list() -> types.TimezoneListResponse

Fetch supported device timezones via GET /api/time/tzlist.

time_timestamp async

time_timestamp(timestamp: str) -> types.SuccessResponse

Set device time via POST /api/time/timestamp.

time_timezone async

time_timezone(timezone: str) -> types.SuccessResponse

Set device timezone via POST /api/time/timezone.

busy_snapshot async

busy_snapshot() -> types.BusySnapshot

Fetch busy snapshot via GET /api/busy/snapshot.

busy_snapshot_set async

busy_snapshot_set(snapshot: BusySnapshot) -> types.SuccessResponse

Set busy snapshot via PUT /api/busy/snapshot.

busy_profile async

busy_profile(slot: BusyProfileSlot) -> types.BusyProfile

Fetch a busy profile slot via GET /api/busy/profiles/{slot}.

busy_profile_set async

busy_profile_set(slot: BusyProfileSlot, profile: BusyProfile | dict[str, object]) -> types.SuccessResponse

Set a busy profile slot via PUT /api/busy/profiles/{slot}.

account_unlink() -> types.SuccessResponse

Unlink the device from the account via DELETE /api/account.

account_link() -> types.AccountLink

Request an account link code via POST /api/account/link.

account_info async

account_info() -> types.AccountInfo

Fetch linked account info via GET /api/account/info.

account_status async

account_status() -> types.AccountState

Fetch MQTT status info via GET /api/account/status.

account_backend async

account_backend() -> types.AccountBackend

Fetch MQTT backend settings via GET /api/account/backend.

account_backend_set async

account_backend_set(backend: AccountBackend | dict[str, object]) -> types.SuccessResponse

Set MQTT backend settings via PUT /api/account/backend.

account_profile async

account_profile() -> types.AccountProfile

Removed from the device API.

No supported firmware serves GET /api/account/profile, so calling this raises BusyBarRemovedEndpointError. Use account_backend() instead.

account_profile_set async

account_profile_set(profile: AccountProfileName, custom_url: str | None = None) -> types.SuccessResponse

Removed from the device API.

No supported firmware serves POST /api/account/profile, so calling this raises BusyBarRemovedEndpointError. Use account_backend_set() instead.

access async

access() -> types.HttpAccessInfo

Fetch HTTP access mode via GET /api/access.

access_set async

access_set(mode: HttpAccessMode, key: str) -> types.SuccessResponse

Set HTTP access mode via POST /api/access.

access_tokens_list async

access_tokens_list() -> types.AccessTokensInfo

List issued access tokens via GET /api/access/tokens.

The secret itself is returned only when a token is minted, so every token here has token set to None.

Added by busy-app/busybar-firmware#886 and served from OpenAPI 27.5.0, first released in firmware 1.2.3; earlier firmware 404s.

access_token_mint async

access_token_mint(name: str) -> types.AccessToken

Issue a new access token via POST /api/access/tokens.

This is the only time the device discloses the secret; it cannot be read back afterwards.

Added by busy-app/busybar-firmware#886 and served from OpenAPI 27.5.0, first released in firmware 1.2.3; earlier firmware 404s.

access_tokens_delete_all async

access_tokens_delete_all() -> types.SuccessResponse

Revoke every access token via DELETE /api/access/tokens.

Added by busy-app/busybar-firmware#886 and served from OpenAPI 27.5.0, first released in firmware 1.2.3; earlier firmware 404s.

access_tokens_revoke async

access_tokens_revoke(short_id: str) -> types.SuccessResponse

Revoke one access token via DELETE /api/access/tokens/{short_id}.

Added by busy-app/busybar-firmware#886 and served from OpenAPI 27.5.0, first released in firmware 1.2.3; earlier firmware 404s.

Prepared requests

busylib.client.PreparedRequest

Bases: BaseModel

Prepared low-level request ready for execution by HTTP clients.

This object stores normalized request attributes after payload encoding. Callers can execute it with built-in client transport or pass fields to an external request executor. Serialization is not guaranteed because timeout and streaming content may contain runtime-only objects.

Streaming content based on iterables/generators is single-use and should not be reused across multiple executions.

method instance-attribute

method: str

path instance-attribute

path: str

params instance-attribute

params: dict[str, Any] | None

headers class-attribute instance-attribute

headers: dict[str, str] | None = Field(default=None, repr=False)

content class-attribute instance-attribute

content: bytes | Iterable[bytes] | AsyncIterable[bytes] | None = Field(default=None, repr=False)

expect_bytes instance-attribute

expect_bytes: bool

allow_text instance-attribute

allow_text: bool

timeout instance-attribute

timeout: Timeout

request_id instance-attribute

request_id: str

json_payload class-attribute instance-attribute

json_payload: JsonType | None = None

model_config class-attribute instance-attribute

model_config = ConfigDict(arbitrary_types_allowed=True)

Using your own HTTP session

A host application often already owns a connection pool and wants everything to go through it. Home Assistant, for example, hands integrations a shared aiohttp session; opening a second pool beside it wastes connections and sidesteps the host's timeouts and tracing.

httpx2 transports are the seam for that, and busylib[aiohttp] ships one:

from busylib import AsyncBusyBar
from busylib.transports import AiohttpTransport

bb = AsyncBusyBar("10.0.4.20", transport=AiohttpTransport(session))

Every client method then rides the session you passed. The session is borrowed, never closed — aclose() on the client leaves it usable, because whoever handed it over still owns it.

Status streaming is unaffected either way: it speaks WebSocket through websockets rather than the HTTP transport, so it keeps its own connection.

busylib.transports

Run this client's requests over someone else's HTTP session.

A host application often already owns a connection pool and wants everything it does to go through it - Home Assistant hands integrations a shared aiohttp session, and opening a second pool beside it wastes connections and sidesteps the host's own timeouts and tracing.

httpx2 already has the seam for this: a transport is one method. So nothing in the client changes, and the default stays httpx2's own transport.

from busylib import AsyncBusyBar
from busylib.transports import AiohttpTransport

bb = AsyncBusyBar("10.0.4.20", transport=AiohttpTransport(session))

Status streaming is not affected: it speaks WebSocket through websockets, not through the HTTP transport, so it keeps its own connection either way.

AIOHTTP_MISSING module-attribute

AIOHTTP_MISSING = 'AiohttpTransport needs aiohttp, which is optional: install busylib[aiohttp].'

AiohttpTransport

AiohttpTransport(session: ClientSession)

Bases: AsyncBaseTransport

An httpx2 transport backed by an aiohttp session you already have.

The session is borrowed, never closed: the caller owns its lifetime, and closing it here would break every other user of it.

handle_async_request async

handle_async_request(request: Request) -> httpx2.Response

Perform one request on the borrowed session.

Errors are translated into httpx2 transport errors, because that is the family the client catches to retry and to report; letting an aiohttp exception through would escape that handling entirely.

aclose async

aclose() -> None

Do nothing: the session belongs to whoever passed it in.