roboto.domain.devices
Device management for the Roboto platform.
This module provides functionality for managing devices - non-human entities that can interact with Roboto on behalf of organizations. Devices are typically robots or other systems that upload data to the platform.
The main components are: - Device: Core device entity for registration, token management, and operations - DeviceRecord: Wire-transmissible representation of device data - CreateDeviceRequest: Request payload for device registration - UpdateDeviceRequest: Request payload for updating a device
Devices are identified by unique device IDs within their organization and can be assigned API tokens for authentication. They serve as the primary mechanism for automated data ingestion and platform interaction.
Submodules
Package Contents
CreateDeviceRequest
Bases: pydantic.BaseModel
Request payload to create a new device.
This request is used to register a new device with the Roboto platform. The device will be associated with the specified organization and can subsequently be used for authentication and data operations.
Parameters
data AnyAttributes
CreateDeviceRequest.custom_fields
Initial values for Ready custom fields on this device.
Each key must be the name of a CustomField that is Ready for the caller’s org and the Device entity type; each value must satisfy the field’s declared type. Names that are undefined or not Ready, and values that don’t match the field’s type, are rejected with a structured error.
CreateDeviceRequest.device_id
A user-provided identifier for a device, which is unique within that device’s org.
CreateDeviceRequest.metadata
Key-value metadata pairs to associate with the device for discovery and search.
CreateDeviceRequest.org_id
The org to which this device belongs. If None, the device will be registered under the caller’s organization (if they belong to only one org) or an error will be raised if the caller belongs to multiple organizations.
Device
A device is a non-human entity that can interact with Roboto on behalf of an organization.
Devices represent robots, systems, or other non-human entities that need to authenticate and interact with the Roboto platform. Each device is uniquely identified by a device_id within its organization and can be assigned API tokens for secure authentication.
Common device types include:
- Robots that upload log data directly from their onboard software
- Automated upload stations that collect and transmit data from multiple sources
- Edge computing devices that process and forward data to Roboto
Devices are associated with Org entities and can create Token objects for authentication. The underlying data is stored in DeviceRecord objects for wire transmission.
Device IDs are typically meaningful identifiers like serial numbers, asset tags, or other organization-specific naming schemes that help identify the physical or logical entity in the real world.
Parameters
roboto_client Optional[roboto.Device.clear_custom_field()
Clear a single custom-field value on this device to None.
Parameters
name strReturn type
Device.clear_custom_fields()
Clear multiple custom-field values on this device to None.
Parameters
names collections.Return type
Device.create()
Register a new device with the Roboto platform.
Creates a new device entity that can authenticate and interact with Roboto on behalf of the specified organization. The device_id must be unique within the organization.
Parameters
device_id strA user-provided identifier for the device, unique within the organization. This is typically a meaningful identifier like a serial number, asset tag, or other organization-specific naming scheme.
metadata Optional[dict[str, Any]]Optional key-value pairs to associate with the device for discovery and search. For example: {“model”: “mk2”, “serial_number”: “SN001234”}.
tags Optional[list[str]]Optional list of tags to associate with the device for discovery and organization. For example: [“production”, “warehouse-a”].
custom_fields Optional[dict[str, Any]]Optional initial values for Ready custom fields defined on Devices in the caller’s org. Keys must match Ready field names; values must satisfy each field’s declared type.
caller_org_id Optional[str]The organization ID to register the device under. If not specified and the caller belongs to only one organization, that organization will be used. Required if the caller belongs to multiple organizations.
roboto_client Optional[roboto.Optional RobotoClient instance for API communication. If not provided, the default client configuration will be used.
Returns
A Device instance representing the newly registered device.
Raises
If a device with the same device_id already exists in the specified organization.
If the caller lacks permission to create devices in the specified organization.
If the device_id is invalid or the organization ID is malformed.
Usage
Register a robot device:
device = Device.create(device_id="robot_001", caller_org_id="og_abc123")
print(f"Registered device: {device.device_id}")
# Registered device: robot_001Register an upload station:
device = Device.create(device_id="upload_station_alpha")
print(f"Device org: {device.org_id}")
# Device org: og_xyz789Device.create_session()
Create one Session on this Device, optionally with its files, topics, and schemas.
The one-session form of create_sessions(), taking a single declaration’s fields as arguments and sharing its semantics: the Session, its file attachments, its topics, and its time ranges are created together or not at all, and every file the declaration names must already be uploaded. name identifies the Session within this Device, so resending the same call is safe; the platform reuses the Session already registered under that name instead of creating a second one. Arguments left at their defaults are left out of the request, so a call that reuses an existing Session never overwrites attributes it does not name. To create a Session with no name, or one spanning several Devices, use create().
A declaration the platform refuses raises here. Only create_sessions() reports a refusal instead of raising it, because only a batch has positions to trace refusals back to.
Declared times are stored exactly as given, in each file’s own timestamps, and read as nanoseconds since the Unix epoch; the platform never invents a wall-clock time. To place the Session at the wall-clock time it happened, supply anchor, or call set_unix_offset() later.
Parameters
name strName of the Session, unique within this Device (max 120 characters).
description Optional[str]Optional description of the Session.
metadata Optional[dict[str, Any]]Optional initial metadata. Sessions are not filterable or sortable by metadata keys; for queryable structured attributes, define a custom field on the Session entity type.
tags Optional[collections.Optional initial tags. Sessions can be filtered by tag membership but are not sortable by tag.
custom_fields Optional[dict[str, Any]]Optional initial values for Ready custom fields defined on Sessions in this Device’s org. Keys must match Ready field names; values must satisfy each field’s declared type.
anchor Optional[roboto.Optional wall-clock anchor, the real-world instant at which the declared data’s time 0 occurred. An int is nanoseconds since the Unix epoch; any other Time is read as to_epoch_nanoseconds() reads it (a datetime or ISO 8601 string is that instant; a float, Decimal, or numeric string is seconds since the epoch). It applies to every file entry that does not carry its own anchor_ns.
files Optional[collections.Files composing this Session, with the topics whose data each one carries. Every file must already be uploaded, and may appear at most once. Files can also be included after creation with add_file() or add_files().
Returns
The created Session.
Raises
TypeErrorIf anchor is not one of the Time types.
ValueErrorIf anchor is a boolean, a negative number (an int, float, Decimal, or numeric string), or a string that is neither a number of seconds nor an ISO 8601 timestamp. Raised before anything is sent to the platform.
OverflowErrorIf anchor is an infinite float, Decimal, or string, such as "inf". Raised before anything is sent to the platform.
pydantic.ValidationErrorIf name is empty or longer than 120 characters, anchor does not fall after the Unix epoch or is too large for a signed 64-bit integer of nanoseconds, a file appears in more than one entry, files declares more than MAX_FILES_AND_TOPICS_PER_REQUEST files and topics combined, or representations name one file in two storage formats. Raised while the request is being built, before anything is sent to the platform.
If the platform refuses the declaration, either because it contradicts data the platform already holds or because it carries a value the platform rejects, such as a custom_fields value that does not satisfy its field’s declared type. No Session, file attachment, topic, or time range is created; the topic identifiers and schema definitions the declaration resolved stay stored, and a resend reuses them.
If something the declaration was prepared against changed while it was being written. Nothing is created; resending is the fix.
If this Device is no longer registered, the file_id of a file entry, or of a representation one of its topics lists, does not name a file in this Device’s organization whose status is Available, or the declaration names something else that does not exist, such as a custom_fields key naming a custom field the organization does not define on Sessions. Nothing is created.
If the caller lacks permission to create Sessions on this Device, or to edit a file the declaration declares topics on, a file it anchors, or a file a listed representation names, or lacks topic edit access in this Device’s organization while the declaration states is_default_for_reads on a timeline source.
If the platform refuses the declaration under an error code this SDK release does not define. Carries the code and message the platform sent.
Usage
Create a Session and add a file to it:
device = Device.from_id("robot_001", org_id="og_abc123")
session = device.create_session(name="2024-05-01_morning_run")
session.add_file("fl_0123456789abcdef")Create a Session placed at the wall-clock time it was recorded:
import datetime
session = device.create_session(
name="2024-05-01_morning_run",
anchor=datetime.datetime(2024, 5, 1, 9, 30, tzinfo=datetime.timezone.utc),
)Device.create_sessions()
Create many Sessions on this Device, each with its files, topics, and schemas, in one call.
Each call accepts up to MAX_SESSIONS_PER_REQUEST declarations, one per Session (e.g. the episodes of a LeRobot dataset), and up to MAX_FILES_AND_TOPICS_PER_REQUEST files and topics combined, counted across every declaration; split anything larger across several calls. Every file a declaration names must already be uploaded; upload_files() returns the file IDs it creates, and files from any number of datasets may appear in one batch.
The platform decides which declarations to refuse before writing anything, then writes the rest together. A declaration’s Session, file attachments, topics, and time ranges are created together or not at all, and a declaration the platform refuses leaves the others written as if it were absent. None of the declarations is written when a failure the platform did not anticipate, such as a timeout, interrupts the call, or when a Session a declaration reuses is deleted before the call completes, which raises RobotoNotFoundException. Each declaration is written as it would be had the ones before it been sent as calls of their own: a later declaration anchoring data an earlier one holds moves the earlier Session’s time range with it. A refused declaration, or a call that fails, still leaves behind the topic identifiers and schema definitions it resolved, which a resend reuses.
Check failed before treating the batch as done. Each entry there is the RobotoDomainException the platform refused a declaration with, so isinstance tells the reasons apart; a refusal under an error code this SDK release does not define arrives as RobotoUnrecognizedErrorException.
Resending the same call is safe. This Device plus each declaration’s name identifies the Session the declaration creates or reuses, so a resend fills in only what is missing rather than duplicating what an earlier attempt created.
Declared times are stored exactly as given, in each file’s own timestamps, and read as nanoseconds since the Unix epoch; the platform never invents a wall-clock time. A recording whose timestamps start at 0 therefore sits at the epoch until it is anchored. To place a Session at the wall-clock time it happened, supply anchor_ns, or call set_unix_offset() later.
Parameters
sessions collections.One declaration per Session to create. An empty sequence returns an empty response without contacting the platform.
Returns
A BatchResponse with one element per declaration, in request order, holding either the Session the declaration created or why the platform refused it. A declaration naming a Session this Device already holds yields that Session rather than a second one.
Raises
pydantic.ValidationErrorIf more than MAX_SESSIONS_PER_REQUEST declarations are given, the batch declares more than MAX_FILES_AND_TOPICS_PER_REQUEST files and topics combined, the same session name is declared more than once, or representations name one file in two storage formats. All are enforced when the request body is constructed, before anything is sent to the platform.
If the batch is malformed. Nothing is created.
If this Device is no longer registered, the file_id of a file entry, or of a representation one of its topics lists, does not name a file in this Device’s organization whose status is Available, or a Session a declaration reuses is deleted before the call completes. Nothing is created.
If the caller lacks permission to create Sessions on this Device, or to edit a file a declaration declares topics on, a file it anchors, or a file a listed representation names, or lacks topic edit access in this Device’s organization while a declaration states is_default_for_reads on a timeline source.
Usage
Register two chunks of one recording as a single Session, each chunk’s topic data read from the chunk itself:
import pathlib
from roboto.domain.datasets import Dataset
from roboto.domain.devices import Device
from roboto.domain.topics import CanonicalDataType, RepresentationStorageFormat
from roboto.experimental.ingest import (
Field,
McapLogTimeSource,
RepresentationDeclaration,
Schema,
TopicDeclaration,
)
from roboto.experimental.sessions import SessionDeclaration, SessionFile
imu_schema = Schema(
name="sensor_msgs/msg/Imu",
fields=[
Field(
name="angular_velocity_x",
data_type="float64",
canonical_data_type=CanonicalDataType.Number,
),
],
)
dataset = Dataset.from_id("ds_0123456789ab")
device = Device.from_id("robot_001")
chunks = [pathlib.Path("recording/chunk_0000.mcap"), pathlib.Path("recording/chunk_0001.mcap")]
file_ids = dataset.upload_files(chunks)
batch = device.create_sessions(
[
SessionDeclaration(
name="morning_drive",
files=[
SessionFile(
file_id=file_ids[chunks[0]],
topics=[
TopicDeclaration(
topic_name="/imu",
topic_schema=imu_schema,
timeline_sources=[
McapLogTimeSource(
min_file_timestamp_ns=1_785_974_400_000_000_000,
max_file_timestamp_ns=1_785_974_404_000_000_000,
),
],
representations=[
RepresentationDeclaration(
file_id=file_ids[chunks[0]],
storage_format=RepresentationStorageFormat.MCAP,
),
],
),
],
),
SessionFile(
file_id=file_ids[chunks[1]],
topics=[
TopicDeclaration(
topic_name="/imu",
topic_schema=imu_schema,
timeline_sources=[
McapLogTimeSource(
min_file_timestamp_ns=1_785_974_404_000_000_000,
max_file_timestamp_ns=1_785_974_408_000_000_000,
),
],
representations=[
RepresentationDeclaration(
file_id=file_ids[chunks[1]],
storage_format=RepresentationStorageFormat.MCAP,
),
],
),
],
),
],
),
],
)
batch.failed
# []Device.create_token()
Create an authentication token for this device.
Generates a new API token that can be used to authenticate requests made on behalf of this device. The token secret is returned only once and cannot be retrieved again, so it must be stored securely by the caller.
Parameters
expiry_days intNumber of days until the token expires. Defaults to 366 days (1 year). Must be a positive integer.
name Optional[str]Human-readable name for the token. If not provided, defaults to “{org_id}_{device_id}” format.
description Optional[str]Optional description explaining the token’s purpose or usage context.
api_scopes Optional[collections.Optional set of API scopes to limit the token’s permissions. If not provided, the token will have full access to all APIs.
Returns
A tuple containing:
- Token: The Token object representing the created token
- str: The secret token value (only available at creation time)
Raises
If token creation fails or the secret is not returned by the server (this should never happen under normal circumstances).
If the caller lacks permission to create tokens for this device.
Usage
Create a token with default settings:
device = Device.from_id("robot_001", org_id="og_abc123")
token, secret = device.create_token()
print(f"Token created: {token.token_id}")
print(f"Secret (save this!): {secret}")
# Token created: to_abc123def456
# Secret (save this!): robo_pat_abc123def456...Create a token with custom expiry and description:
token, secret = device.create_token(
expiry_days=30, name="Monthly Upload Token", description="Token for automated monthly data uploads"
)
print(f"Token expires in 30 days: {token.token_id}")
# Token expires in 30 days: to_def789ghi012Properties
Device.created
The timestamp when this device was registered with Roboto.
Device.created_by
The user ID of the person who registered this device.
Device.custom_fields
Custom-field values defined on Devices in this org.
Every Ready CustomField defined for (org_id, Device) appears as a key. Values that have not been set on this device surface as None rather than being absent. Empty when no custom fields are defined for the org.
A Timestamp value is returned as an ISO 8601 string.
Device.delete()
Delete this device from the Roboto platform.
Permanently removes this device and all associated tokens. This action cannot be undone. Any tokens created for this device will be immediately invalidated.
The device’s own files (files) are deleted with it, every version of each, shortly after this call returns. With keep_files=True they move to the org root instead, under devices/<universal_device_id>/, keeping their file IDs and every version, so they stay reachable through org.files. Links among the device’s files are deleted in both cases, never moved; their targets are left alone.
Parameters
keep_files boolMove the device’s files to the org root instead of deleting them. Requires permission to upload files to the org.
Raises
If the caller lacks permission to delete this device.
If the device has already been deleted or does not exist.
Return type
Usage
Delete a device after confirming its identity:
device = Device.from_id("old_robot_001")
print(f"Deleting device: {device.device_id}")
device.delete()
print("Device deleted successfully")
# Deleting device: old_robot_001
# Device deleted successfullyDelete a device but keep its calibrations and manifests in the org root:
device = Device.from_id("old_robot_001")
device.delete(keep_files=True)Properties
Device.device_id
This device’s ID. Device ID is a user-provided identifier for a device, which is unique within the device’s org.
Device.encoded_device_id
The device ID, URL-encoded. This is useful for constructing URLs to Roboto APIs which contain the device ID.
Device.files
The files associated with this device: its calibrations, part manifests, and the like.
These are the device’s own files, distinct from the dataset files whose device_id names this device as the one that recorded them.
Raises
The device has no universal_device_id, which only a Roboto deployment that predates device files returns.
Return type
Device.for_org()
List all devices registered for a given organization.
Retrieves all devices that belong to the specified organization. For organizations with large numbers of devices, this method uses pagination and yields results as they become available from the API.
Parameters
org_id strThe organization ID to list devices for.
roboto_client Optional[roboto.Optional RobotoClient instance for API communication. If not provided, the default client configuration will be used.
Returns
A generator of Device objects. For organizations with many devices, this may involve multiple service calls, and the generator will yield results as they become available.
Raises
If the caller lacks permission to list devices in the specified organization.
If the specified organization does not exist.
Usage
List all devices in an organization:
for device in Device.for_org("og_abc123"):
print(f"Device: {device.device_id} (created: {device.created})")
# Device: robot_001 (created: 2024-01-15 10:30:00)
# Device: upload_station_beta (created: 2024-01-17 09:15:00)Count devices in an organization:
device_count = sum(1 for _ in Device.for_org("og_abc123"))
print(f"Total devices: {device_count}")
# Total devices: 2Device.from_id()
Retrieve a device by its device ID.
Looks up and returns a Device instance for the specified device_id. The device_id must be unique within the organization scope.
Parameters
device_id strThe device ID to look up. This is the user-provided identifier that was specified when the device was created.
roboto_client Optional[roboto.Optional RobotoClient instance for API communication. If not provided, the default client configuration will be used.
org_id Optional[str]The organization ID that owns the device. If not specified and the caller belongs to only one organization, that organization will be used. Required if the caller belongs to multiple organizations.
Returns
A Device object representing the specified device.
Raises
If the specified device is not registered with Roboto or does not exist in the specified organization.
If the caller lacks permission to access the device or the specified organization.
If the device_id or org_id parameters are malformed.
Usage
Retrieve a device by ID with explicit organization:
device = Device.from_id("robot_001", org_id="og_abc123")
print(f"Device: {device.device_id} in org {device.org_id}")
# Device: robot_001 in org og_abc123Retrieve a device:
device = Device.from_id("upload_station_alpha")
print(f"Found device created by: {device.created_by}")
# Found device created by: user@example.comDevice.get_or_create()
Register a device, or return the existing one if device_id is already taken.
metadata, tags, and custom_fields are applied only by the call that registers the device; a device that is already registered comes back unchanged.
Parameters
device_id strA user-provided identifier for the device, unique within the organization.
metadata Optional[dict[str, Any]]Optional key-value pairs to associate with the device on first registration.
tags Optional[list[str]]Optional tags to associate with the device on first registration.
custom_fields Optional[dict[str, Any]]Optional initial values for Ready custom fields, applied on first registration.
caller_org_id Optional[str]The organization the device belongs to. Required if the caller belongs to multiple organizations.
roboto_client Optional[roboto.Optional RobotoClient instance for API communication.
Returns
The newly registered or pre-existing Device.
Raises
If the caller lacks permission to create devices in, or read devices from, the specified organization.
If the device_id is invalid or the organization ID is malformed.
If the device is deleted between the registration attempt and the lookup that follows it. Those are two calls rather than one atomic operation, so the race is possible, though unlikely.
Usage
device = Device.get_or_create(device_id="aloha_001")
device.device_id
# 'aloha_001'Device.list_sessions()
Iterate all Sessions attached to this Device.
Yields results as they are returned from the server, paginating transparently.
Usage
Print the name of every Session for a Device:
device = Device.from_id("robot_001", org_id="og_abc123")
for session in device.list_sessions():
print(session.name)Return type
Properties
Device.metadata
Key-value metadata pairs associated with this device.
Device.modified
The timestamp when this device record was last modified.
Device.modified_by
The user ID of the person who last modified this device record.
Device.put_metadata()
Add or update metadata fields for this device.
Parameters
metadata dict[str, Any]Key-value pairs to add or update in the device’s metadata. Existing keys will be overwritten, new keys will be added.
Returns
Updated Device instance with the new metadata.
Usage
device = Device.from_id("robot_001")
updated_device = device.put_metadata({"firmware_version": "2.1.0", "location": "warehouse-b"})
print(updated_device.metadata["firmware_version"])
# 2.1.0Device.put_tags()
Add tags to this device.
Parameters
tags list[str]List of tags to add to the device. Duplicate tags will be ignored.
Returns
Updated Device instance with the new tags added.
Usage
device = Device.from_id("robot_001")
updated_device = device.put_tags(["production", "warehouse-c"])
print("production" in updated_device.tags)
# TrueProperties
Device.record
Underlying DeviceRecord for this device.
This is the wire representation used in API requests and may evolve over time; prefer the public Device API unless you need direct access to the record.
Device.remove_metadata()
Remove metadata fields from this device.
Parameters
keys list[str]List of metadata keys to remove from the device.
Returns
Updated Device instance with the specified metadata keys removed.
Usage
device = Device.from_id("robot_001")
updated_device = device.remove_metadata(["old_field", "deprecated_key"])Device.remove_tags()
Remove tags from this device.
Parameters
tags list[str]List of tags to remove from the device.
Returns
Updated Device instance with the specified tags removed.
Usage
device = Device.from_id("robot_001")
updated_device = device.remove_tags(["old_tag", "deprecated"])Device.set_custom_field()
Device.set_custom_fields()
Properties
Device.tokens()
Retrieve all authentication tokens associated with this device.
Returns a list of all tokens that have been created for this device, including both active and expired tokens. The token secrets are not included in the response as they are only available at creation time.
Returns
A sequence of Token objects representing all tokens created for this device. The sequence may be empty if no tokens have been created.
Raises
If the caller lacks permission to list tokens for this device.
Usage
List all tokens for a device:
device = Device.from_id("robot_001")
tokens = device.tokens()
for token in tokens:
print(f"Token: {token.token_id}")
# Token: to_abc123def456
# Token: to_ghi789jkl012Check if device has any tokens:
device = Device.from_id("new_robot")
if device.tokens():
print("Device has tokens")
else:
print("No tokens found for device")
# No tokens found for deviceDevice.update()
Update device properties using a structured request.
Parameters
UpdateDeviceRequest containing the changes to apply.
Returns
Updated Device instance with the changes applied.
Usage
from roboto.updates import MetadataChangeset
device = Device.from_id("robot_001")
updated_device = device.update(
UpdateDeviceRequest(
metadata_changeset=MetadataChangeset(
put_fields={"version": "2.0"}, put_tags=["updated"], remove_tags=["old"]
)
)
)DeviceRecord
Bases: pydantic.BaseModel
A wire-transmissible representation of a device.
This record contains all the essential information about a device that can be transmitted over the network. It includes metadata about when the device was created and modified, along with its organizational association.
Parameters
data AnyAttributes
DeviceRecord.custom_fields
Values for the custom fields defined on Devices in this org.
Every Ready custom field defined for (org_id, Device) appears as a key; values that have not been set surface as None rather than being absent. Empty when no custom fields are defined for the org.
DeviceRecord.device_id
A user-provided identifier for a device, which is unique within that device’s org.
DeviceRecord.metadata
Key-value metadata pairs associated with this device.
DeviceRecord.modified
Date/time when this device record was last modified.
UpdateDeviceRequest
Bases: pydantic.BaseModel
Request payload for updating device properties.
Used to modify device metadata and tags. Supports granular updates through metadata changesets that can add, update, or remove specific fields and tags without affecting other properties.
Parameters
data AnyAttributes
UpdateDeviceRequest.custom_fields_changeset
Changes to apply to Ready custom-field values on this device.
Each referenced field name must be a Ready custom field for this device’s org and the Device entity type; each set_fields value must satisfy the field’s declared type. Names that are undefined or not Ready are rejected with a structured error. Field names not mentioned by the changeset are left unchanged.
UpdateDeviceRequest.metadata_changeset
Metadata changes to apply (add, update, or remove fields/tags).