Device operations

This guide covers the complete unified DDI device capability set, including Bazaar-authored OTA delivery. All operations use the provisioned thing_id and certificate; none requires a device application to know an internal service hostname or token.

Check Runtime surface before relying on an operation. A binding can implement a protocol message before the managed platform consumes that message end to end.

Provision and rotate identity

The common flow is identify → generate a key and CSR locally → provision → persist → connect. Only the CSR crosses the network. The private key must remain in a hardware-backed or otherwise protected device store.

Python:

from fgai_mqtt import ProvisionedIdentity, ProvisioningClient
from fgai_mqtt._csr import build_csr

provisioner = ProvisioningClient(
    "https://things.flexgalaxy.ai",
    attestation_bearer=bootstrap_bearer,
)
identity = provisioner.identify(model="RB-100", sn="SN-0001")
csr_pem, key_pem = build_csr("SN-0001", "RB-100", identity.thing_id)
issued = provisioner.provision(
    identity.thing_id,
    identity.enrollment_token,
    csr_pem,
)
device_identity = ProvisionedIdentity.persist(issued, key_pem, "certs")

Android/Kotlin uses DdiDeviceSdk.provisionDevice, which composes identify, on-device CSR generation, and provision on Dispatchers.IO. DdiDeviceSdk.buildMqttConfig performs the MQTT hand-off. Persist the returned DdiDeviceCredentials in a protected application/device store and call bindCredentials after reboot.

C uses fgai_provisioning_client_new, fgai_provisioning_identify, fgai_provisioning_build_csr, and fgai_provisioning_provision. Free every result with its matching fgai_provisioning_*_free function.

For rotation, generate a fresh key and CSR, configure the current certificate for mTLS, then call the binding’s rotate operation. Persist the new certificate and key as one atomic update. The rotate response contains only thing_id and certificate; retain the existing CA chain and MQTT configuration.

Publish telemetry

Telemetry is a map of numeric metrics plus optional string tags. Bindings encode DeviceTelemetry, add the device identity and current time, and publish QoS 0 to device/<thing_id>/telemetry. The managed route is live.

device.publish_telemetry(
    {"temperature": 22.5, "battery_percent": 87.0},
    tags={"site": "factory-a"},
)
client.publishTelemetry(
    metrics = mapOf("temperature" to 22.5, "battery_percent" to 87.0),
    tags = mapOf("site" to "factory-a"),
)
fgai_mqtt_metric_t metrics[] = {
    {"temperature", 22.5},
    {"battery_percent", 87.0},
};
fgai_mqtt_telemetry_t sample = FGAI_MQTT_TELEMETRY_INIT;
sample.metrics = metrics;
sample.n_metrics = 2;
fgai_mqtt_client_publish_telemetry(client, &sample);

QoS 0 favors freshness over replay. Add an application sequence when duplicate or missing sample detection matters; the SDK auto-increments when omitted.

For a tablet, controller, and MCU that form one customer-visible product, follow How to integrate a composite robot. It shows which identity publishes each producer’s telemetry and why the MCU does not need a third broker connection.

Report device-owned attributes

Use the CLIENT attribute path for firmware, hardware, and current device state. It is live and bridged to the platform through Kafka. The Python name is report_client_attribute; its wire shape is the same reported-state protobuf used by publish_reported_state.

device.report_client_attribute({
    "firmware_version": "1.0.0",
    "hardware_revision": "rev-b",
})
client.publishReportedState(
    mapOf("firmware_version" to "1.0.0", "hardware_revision" to "rev-b")
)
fgai_mqtt_metric_t state[] = {{"battery_percent", 87.0}};
fgai_mqtt_client_publish_reported(client, state, 1, reported_version++);

The C convenience API currently accepts flat numeric values only. Use Python or Android when the state requires strings, booleans, or nested protobuf Struct values.

Republish a complete CLIENT snapshot after reconnect so the platform can recover from retained-session loss.

Apply shared attributes

The platform pushes a DeviceAttributeSharedPush to device/<thing_id>/attribute/shared/push. Register the callback before connect, apply the value durably, and return. Python and C then publish the matching QoS 1 acknowledgement automatically. The Android SharedAttributeManager performs the same effect-then-ack flow when connected to a SharedAttributePublisher.

def apply_shared(key, value):
    settings_store.apply(key, value)

device.set_shared_attribute_callback(apply_shared)

The message-body device_id is advisory. The SDK always routes the ack through the identity-bound topic. The managed push down-channel is live; persistence of the acknowledgement leg is still reserved. Do not use acknowledgement persistence as a rollout completion signal yet.

Receive and answer commands

Register the callback before connect. A command handler must be idempotent by command_id, perform bounded work, and preserve the supplied response function or correlation data.

from fgai_mqtt._proto import command_pb2

def on_command(command, respond):
    result = execute_once(command.command_id, command.name, command.payload)
    respond(
        status=command_pb2.COMMAND_STATUS_SUCCEEDED,
        result=result,
        error=None,
    )

device.set_command_callback(on_command)

Android uses FgaiMqttClient.setCommandCallback { command, respond -> ... }. C registers fgai_mqtt_client_set_command_callback and answers with fgai_mqtt_client_respond_command, passing the request correlation bytes.

Command delivery to devices is live. Managed persistence of device/<thing_id>/commands/ack is reserved, so a backend must not yet treat the MQTT response as a durable audit record.

Heartbeat and lifecycle

All three MQTT bindings can publish heartbeat and lifecycle protobufs. Connect and graceful disconnect automatically emit lifecycle events, and an MQTT Will represents unexpected disconnect. Python and Android expose heartbeat helpers; C exposes fgai_mqtt_client_publish_heartbeat and fgai_mqtt_client_publish_lifecycle.

The managed platform currently observes broker presence, but does not persist the heartbeat or lifecycle topics as backend events. Use these messages for forward compatibility, not as the only health or decommissioning signal.

Poll and install OTA

OTA stays within the same certificate boundary:

  1. Poll https://things.flexgalaxy.ai/ddi/v1/ota with device mTLS.

  2. Follow the returned HawkBit HAL relations through the SDK only.

  3. Send download/execution feedback to the provided feedback relation.

  4. Download to a temporary destination with an expected SHA-256 value.

  5. Publish the artifact to its final location only after integrity verification.

  6. Install with the product’s A/B, recovery, or transactional update mechanism.

  7. Report final execution and result feedback.

Python:

root = device.ota.poll()
action = root.link("deploymentBase")
if action:
    deployment = device.ota.get_document(action)
    artifact_href = deployment.find_links("download-http")[0]
    artifact = device.ota.download(
        artifact_href,
        "/var/lib/device/update.bin",
        expected_sha256=expected_sha256,
    )

Android obtains DdiDeviceSdk.ota after provisioning or binding restored credentials, then calls poll, document, feedback, and download. C creates fgai_ota_client_t, then uses fgai_ota_poll, fgai_ota_get, fgai_ota_feedback, and fgai_ota_download.

Every binding rejects a HAL link that changes origin or escapes /ddi/v1/ota. Devices never receive or send a HawkBit GatewayToken, controller ID, or tenant. Bazaar authors assignments and rollout policy; the private shared-service HawkBit deployment executes them behind DDI.

Desired-state compatibility

Python exposes set_twin_callback; Android exposes setTwinCallback; C exposes fgai_mqtt_client_set_twin_delta_callback. These names predate the frozen attribute wire contract. On the managed platform, cloud-owned configuration is the SHARED push flow described above. Prefer shared-attribute APIs for new code.

See SDK reference for exact method signatures and Errors and recovery for failure handling.