API
- class Device(*args, startup: str | None = None, attempts: int = 0, auto_sync_time: bool = True, **kwargs)
Belay interface into a micropython device.
Can be used as a context manager; calls
self.closeon exit.Uses the
autoregistry.RegistryMetametaclass for easy-to-access subclasses.- implementation
Implementation details of device.
- Type:
Notes
When subclassing Device, it is probably easier to override the following method hooks rather than overriding
__init__():__pre_autoinit__()- Called before@Device.setup(autoinit=True)methods. Ideal for syncing files or dependencies viasync()orsync_dependencies().__post_init__()- Called after@Device.setup(autoinit=True)methods. Ideal for setting host attributes based on device state.
- MAX_CMD_HISTORY_LEN = 1000
- __post_init__()
Runs at the very end of
__init__.Good for subclasses to initialize attributes or to perform additional device setup.
- __pre_autoinit__()
Runs near the end of
__init__, but before methods marked withsetup(autoinit=True)are invoked.- This would be a good location to call items like:
self.sync(...)- Basic file syncself.sync_dependencies(...)- More advanced sync. Recommended way of getting dependencies on-device.
- close() None
Close the connection to device.
Automatically called on context manager exit.
- proxy(cmd: str, delete: bool | None = None) ProxyObject | tuple[ProxyObject, ...]
Create a
ProxyObjectfor interacting with remote objects.This is a convenience method that combines object creation and proxy wrapping. It automatically detects whether
cmdis a simple variable name, an expression, or an import statement, and handles each appropriately.- Parameters:
cmd (str) --
Remote object reference, expression, or import statement.
Identifier (e.g.,
"my_var"): Creates proxy to existing variableExpression (e.g.,
"[1, 2, 3]"): Evaluates and creates proxy to resultImport (e.g.,
"import os"): Imports module and creates proxy to it
delete (Optional[bool]) --
Control remote object lifecycle when proxy is deleted.
True: Delete remote object when proxy is garbage collectedFalse: Keep remote object after proxy deletionNone(default): Auto-detect based oncmd:Identifiers →
False(preserve existing variables)Import statements →
False(preserve imported modules)Expressions →
True(clean up temporary objects)
- Returns:
Single
ProxyObjectfor most casesTuple of
ProxyObjectinstances for multi-import statements (e.g.,"from math import sin, cos")
- Return type:
Union[ProxyObject, Tuple[ProxyObject, ...]]
Examples
Reference existing variable:
device("sensor_data = [1, 2, 3, 4, 5]") data_proxy = device.proxy("sensor_data") print(data_proxy[0]) # 1 # sensor_data persists on device after data_proxy is deleted
Create proxy from expression:
# Create temporary list on device temp_proxy = device.proxy("[10, 20, 30]") print(temp_proxy[1]) # 20 # List is auto-deleted when temp_proxy goes out of scope
Import and use modules:
# Import a module machine = device.proxy("import machine") pin = machine.Pin(25, machine.Pin.OUT) pin.on()
Multiple imports:
# Import multiple items sin, cos = device.proxy("from math import sin, cos") result = sin(0) # 0
See also
ProxyObjectThe proxy object class for remote object interaction
Device.__call__Lower-level method for executing code with proxy support
- reconnect(attempts: int | None = None) None
Reconnect to the device and replay the command history.
- Parameters:
attempts (int) -- Number of times to attempt to connect to board with a 1 second delay in-between. If
None, defaults to whatever value was supplied to init. If init value is 0, then defaults to 1.
- static setup() Callable[[P], R]
- static setup(*, implementation: str = '', **kwargs) Callable[[Callable[[P], R]], Callable[[P], R]]
Execute decorated function's body in a global-context on-device when called.
Function arguments are also set in the global context.
Can either be used as a staticmethod
@Device.setupfor marking methods in a subclass ofDevice, or as a standard method@device.setupfor marking functions to a specificDeviceinstance.- Parameters:
f (Callable) -- Function to decorate. Can only accept and return python literals.
minify (bool) -- Minify
cmdcode prior to sending. Defaults toTrue.register (bool) -- Assign an attribute to
self.setupwith same name asf. Defaults toTrue.record (bool) -- Each invocation of the executer is recorded for playback upon reconnect. Defaults to
True.autoinit (bool) -- Automatically invokes decorated functions at the end of object
__init__. Methods will be executed in order-registered. Defaults toFalse.ignore_errors (bool) -- Discard any device-side uncaught exception. Defaults to
False.implementation (str) -- If supplied, the provided method will only be used if the board's implementation name matches. Several methods of the same name can be overloaded that support different implementations. Common values include "micropython", and "circuitpython". Defaults to an empty string, which all implementations will match to.
- soft_reset()
Reset device, executing
main.pyif available.
- sync(folder: str | Path, dst: str = '/', keep: None | list | str | bool = None, ignore: None | list | str = None, minify: bool = True, mpy_cross_binary: str | Path | None = None, progress_update=None) None
Sync a local directory to the remote filesystem.
For each local file, check the remote file's hash, and transfer if they differ. If a file/folder exists on the remote filesystem that doesn't exist in the local folder, then delete it (unless it's in
keep).- Parameters:
folder (str, Path) -- Single file or directory of files to sync to the root of the board's filesystem.
dst (str) -- Destination directory on device. Defaults to unpacking
folderto root.keep (None | str | list | bool) -- Do NOT delete these file(s) on-device if not present in
folder. Iftrue, don't delete any files on device. Iffalse, delete all unsynced files (same as passing[]). Ifdst is None, defaults to["boot.py", "webrepl_cfg.py", "lib"].ignore (None | str | list) -- Git's wildmatch patterns to NOT sync to the device. Defaults to
["*.pyc", "__pycache__", ".DS_Store", ".pytest_cache"].minify (bool) -- Minify python files prior to syncing. Defaults to
True.mpy_cross_binary (Union[str, Path, None]) -- Path to mpy-cross binary. If provided,
.pywill automatically be compiled. Takes precedence over minifying.progress_update -- Partial for
rich.progress.Progress.update(task_id,...)to update with sync status.
- sync_dependencies(package: ModuleType | str, *subfolders: str | Path, dst='/lib', **kwargs)
Convenience method for syncing dependencies bundled with package.
If using Belay's package manager feature, set
dependencies_pathto a folder inside your python package (e.g.dependencies_path="mypackage/dependencies").The following example will sync all the files/folders in
mypackage/dependencies/mainto device's/lib.import mypackage device.sync_package(mypackage, "dependencies/main")
For intended use,
sync_dependenciesshould be only be called once. Multiple invocations overwrite/delete previous calls' contents.# Good device.sync_package(mypackage, "dependencies/main", "dependencies/dev") # Bad (deletes on-device files from "dependencies/main") device.sync_package(mypackage, "dependencies/main") device.sync_package(mypackage, "dependencies/dev")
- Parameters:
package (Union[ModuleType, str]) -- Either the imported package or the name of a package that contains the data we would like to sync.
*subfolders -- Subfolder(s) to combine and then sync to
dst. Typically something like "dependencies/main"dst (Union[str, Path]) -- On-device destination directory. Defaults to
/lib.**kwargs -- Passed along to
Device.sync.
- sync_time(samples: int = 10) float
Synchronize time between host and device.
Measures the offset between the device's monotonic clock and the host's epoch time using multiple round-trip measurements. Uses the sample with minimum RTT to minimize latency uncertainty.
This is automatically called during Device initialization if
auto_sync_time=True(the default).- Parameters:
samples (int) -- Number of round-trip measurements to take. More samples improve accuracy but take longer. Default: 10.
- Returns:
The calculated time offset in seconds (device_time - host_time).
- Return type:
float
Notes
- Expected accuracy:
USB Serial: ~10-50ms typical
Network (Telnet/WebREPL): ~20-100ms typical
Factors: connection type, system load, number of samples
The offset assumes symmetric network delay (time to send ≈ time to receive).
Examples
>>> offset = device.sync_time() # Perform synchronization >>> device.sync_time(samples=20) # More samples for better accuracy
- static task() Callable[[P], R]
- static task(**kwargs) Callable[[Callable[[P], R]], Callable[[P], R]]
Execute decorated function on-device.
Sends source code to device at decoration time. Execution sends involves much smaller overhead.
Can either be used as a staticmethod
@Device.taskfor marking methods in a subclass ofDevice, or as a standard method@device.taskfor marking functions to a specificDeviceinstance.- Parameters:
f (Callable) -- Function to decorate. Can only accept and return python literals.
minify (bool) -- Minify
cmdcode prior to sending. Defaults toTrue.register (bool) -- Assign an attribute to
self.taskwith same name asf. Defaults toTrue.record (bool) -- Each invocation of the executer is recorded for playback upon reconnect. Only recommended to be set to
Truefor a setup-like function. Defaults toFalse.implementation (str) -- If supplied, the provided method will only be used if the board's implementation name matches. Several methods of the same name can be overloaded that support different implementations. Common values include "micropython", and "circuitpython". Defaults to an empty string, which all implementations will match to.
trusted (bool) -- Fully trust remote device. When set to
False, only[None, bool, bytes, int, float, str, List, Dict, Set]return values can be parsed. When set toTrue, any value who'sreprcan be evaluated to create a python object can be returned. However, this also allows the remote device to execute arbitrary code on host. Defaults toFalse.return_time (bool) -- When
True, calling the task returns a tuple of(result, host_datetime)wherehost_datetimeis adatetime.datetimeobject representing the estimated midpoint time when the task executed on the device, converted to host time. The timestamp is captured by measuring device time before and after evaluation, then averaging. For generator tasks, each yielded value becomes(value, host_datetime). Requires time synchronization (auto_sync_time=Trueor explicitsync_time()call). RaisesValueErrorif timing data is unavailable. Defaults toFalse.
- static teardown() Callable[[P], R]
- static teardown(**kwargs) Callable[[Callable[[P], R]], Callable[[P], R]]
Executes decorated function's body in a global-context on-device when
device.close()is called.Function arguments are also set in the global context.
Can either be used as a staticmethod
@Device.teardownfor marking methods in a subclass ofDevice, or as a standard method@device.teardownfor marking functions to a specificDeviceinstance.- Parameters:
f (Callable) -- Function to decorate. Can only accept and return python literals.
minify (bool) -- Minify
cmdcode prior to sending. Defaults toTrue.register (bool) -- Assign an attribute to
self.teardownwith same name asf. Defaults toTrue.record (bool) -- Each invocation of the executer is recorded for playback upon reconnect. Defaults to
True.ignore_errors (bool) -- Discard any device-side uncaught exception. Defaults to
False.implementation (str) -- If supplied, the provided method will only be used if the board's implementation name matches. Several methods of the same name can be overloaded that support different implementations. Common values include "micropython", and "circuitpython". Defaults to an empty string, which all implementations will match to.
- terminal(*, exit_char='\x1d')
Start a blocking interactive terminal over the serial port.
- static thread() Callable[[P], R]
- static thread(**kwargs) Callable[[Callable[[P], R]], Callable[[P], R]]
Spawn on-device thread that executes decorated function.
Can either be used as a staticmethod
@Device.threadfor marking methods in a subclass ofDevice, or as a standard method@device.threadfor marking functions to a specificDeviceinstance.- Parameters:
f (Callable) -- Function to decorate. Can only accept python literals as arguments.
minify (bool) -- Minify
cmdcode prior to sending. Defaults toTrue.register (bool) -- Assign an attribute to
self.threadwith same name asf. Defaults toTrue.record (bool) -- Each invocation of the executer is recorded for playback upon reconnect. Defaults to
True.implementation (str) -- If supplied, the provided method will only be used if the board's implementation name matches. Several methods of the same name can be overloaded that support different implementations. Common values include "micropython", and "circuitpython". Defaults to an empty string, which all implementations will match to.
- property time_offset: float
Get the current time offset between device and host.
The offset is defined as
device_time - host_timein seconds. Whenauto_sync_time=True(default), this value is automatically maintained and refined with each device call that includes timing.Time offset can be used to convert between device and host timestamps:
host_time = device_time_sec - device.time_offset device_time_sec = host_time + device.time_offset
- Returns:
The time offset in seconds (device_time - host_time). Positive values indicate device is ahead of host.
- Return type:
float
- Raises:
ValueError -- If time synchronization has not been performed yet.
Notes
The offset handles device tick wrap-around (every ~12.4 days) and automatically detects device resets. For best accuracy, call
sync_time()explicitly to establish initial offset using weighted averaging of multiple samples.Examples
>>> device.sync_time() # Establish accurate initial offset >>> offset = device.time_offset >>> print(f"Device is {offset:.3f}s ahead of host") Device is 0.042s ahead of host
- class ProxyObject(device: Device, name: str, delete: bool = True)
Proxy object for interacting with remote MicroPython/CircuitPython objects.
ProxyObjectprovides a transparent wrapper around remote objects, allowing you to interact with them as if they were local Python objects. Operations on the proxy are forwarded to the device, and results are automatically retrieved and wrapped as needed.Note
ProxyObjects are typically created using
Device.proxy()rather than instantiating this class directly. TheDevice.proxy()method provides convenient automatic detection of imports and expressions.Return Value Behavior
- When accessing proxy attributes or calling methods:
Immutable types (int, float, str, bool, None, bytes) are returned directly
Mutable types (list, dict, custom objects) are returned as
ProxyObject
Examples
Basic attribute and method access:
# Create a remote sensor object device("sensor = TemperatureSensor()") # Create proxy to interact with it sensor = device.proxy("sensor") # Access attributes - immutable values returned directly temp = sensor.temperature # Returns actual float print(f"Temperature: {temp}°C") # Call methods sensor.calibrate() # Calls "sensor.calibrate()" on the micropython device. sensor.set_threshold(25.0)
Working with collections:
# Create remote list device("data = [1, 2, 3, 4, 5]") data_proxy = device.proxy("data") # Access elements - immutable ints returned directly print(data_proxy[0]) # 1 print(data_proxy[-1]) # 5 # Slice - returns ProxyObject wrapping the slice subset = data_proxy[1:3] # ProxyObject for [2, 3] # Modify elements data_proxy[0] = 100 print(device("data")) # [100, 2, 3, 4, 5] # Iterate for item in data_proxy: print(item)
Importing and using modules:
# Import module machine = device.proxy("import machine") # Use imported module pin = machine.Pin(25, machine.Pin.OUT) pin.on()
Nested object access:
# Create nested structure device(\"\"\" class Config: def __init__(self): self.settings = {'brightness': 10, 'mode': 'auto'} config = Config() \"\"\") # Access nested objects config = device.proxy("config") settings = config.settings # Returns ProxyObject for dict brightness = settings['brightness'] # Returns 10 (int) # Modify nested values settings['brightness'] = 20
See also
Device.proxyRecommended method for creating ProxyObjects
Device.__call__Lower-level method for executing code with proxy support
- __init__(device: Device, name: str, delete: bool = True)
Create a
ProxyObject.- Parameters:
device (belay.Device) -- Belay
Deviceobject for interacting with the micropython board.name (str) -- Name of the remote object for the proxy-object to interact with.
delete (bool) -- Delete micropython reference on cpython delete.
- __getitem__(key)
Get item from remote object by key or index.
Supports indexing (
proxy[0]) and slicing (proxy[1:3]). Returns immutable values directly, mutable values asProxyObject.
- __setitem__(key, value)
Set item in remote object by key or index.
Supports setting list elements (
proxy[0] = value) and dict keys (proxy['key'] = value).
- __len__() int
Return the length of the remote object.
- __del__()
Delete reference to micropython object.
- __str__()
String representation of remote object.
- __repr__()
Return repr showing this is a ProxyObject wrapping a remote object.
Format:
<ProxyObject {remote_repr}>whereremote_repris the representation of the remote object.
- __contains__(item)
Test membership in remote object.
Supports the
inoperator:item in proxy. Works with lists, dicts, tuples, sets, and other containers.
- __eq__(other)
Equality comparison with remote object.
- __ne__(other)
Inequality comparison with remote object.
- __lt__(other)
Less than comparison with remote object.
- __le__(other)
Less than or equal comparison with remote object.
- __gt__(other)
Greater than comparison with remote object.
- __ge__(other)
Greater than or equal comparison with remote object.
- __iter__()
Return an iterator over the remote object.
- __hash__()
Return hash of the remote object.
- __call__(*args, **kwargs)
Call the remote object as a function or method.
Supports calling remote functions/methods with arguments. ProxyObject arguments are automatically resolved to their remote equivalents. Returns immutable values directly, mutable values as
ProxyObject.Note: Generator functions are not fully supported yet.
- class Implementation(name: str, version: tuple[int, int, int] = (0, 0, 0), platform: str = '', arch: int | None = None, emitters: tuple[str, ...] = ())
Implementation dataclass detailing the device.
- Parameters:
name (str) -- Type of python running on device. One of
{"micropython", "circuitpython"}.version (Tuple[int, int, int]) --
(major, minor, patch)Semantic versioning of device's firmware.platform (str) -- Board identifier. May not be consistent from MicroPython to CircuitPython. e.g. The Pi Pico is "rp2" in MicroPython, but "RP2040" in CircuitPython.
emitters (tuple[str]) -- Tuple of available emitters on-device
{"native", "viper"}.
- arch: str | None
- emitters: tuple[str, ...]
- name: str
- platform: str
- version: tuple[int, int, int]
- list_devices() list[UsbSpecifier]
Lists available device ports.
- Returns:
Available devices identifiers.
- Return type:
List[UsbSpecifier]
- exception AuthenticationError
Bases:
BelayExceptionInvalid password or similar.
- exception BelayException
Bases:
ExceptionRoot Belay exception class.
- exception ConnectionFailedError
Bases:
BelayExceptionUnable to connect to specified device.
- exception ConnectionLost
Bases:
ConnectionFailedErrorLost connection to device.
- exception DeviceNotFoundError
Bases:
BelayExceptionUnable to find specified device.
Bases:
BelayExceptionFeature unavailable for your board's implementation.
- exception InsufficientSpecifierError
Bases:
BelayExceptionSpecifier wasn't unique enough to determine a single device.
- exception IntegrityError
Bases:
BelayExceptionFile integrity verification failed (hash mismatch).
- exception InternalError
Bases:
BelayExceptionInternal to Belay logic error.
- exception MaxHistoryLengthError
Bases:
BelayExceptionToo many commands were given.
- exception NoMatchingExecuterError
Bases:
BelayExceptionNo valid executer found for the given board Implementation.
- exception NotBelayResponseError
Bases:
BelayExceptionParsed response wasn't for Belay.
- exception PackageNotFoundError
Bases:
BelayExceptionPackage could not be found in index or URL.
- exception SpecialFunctionNameError
Bases:
BelayExceptionAttempted to use a reserved Belay function name.
The following name rules are reserved:
Names that start and end with double underscore,
__.Names that start with
_belayor__belay