How to integrate a composite robot¶
This guide connects an Android tablet and robot controller as two DDI devices inside one customer-visible robot, forwards MCU health through the controller, and assigns OTA installation ownership for all three computers.
Read Composite robot terminal design first for the identity counts and trade-offs.
Prerequisites¶
one registrar-created
robot_asset_id;registered model and serial pairs for the tablet and robot controller;
separate bootstrap credentials for both devices;
the Android/Kotlin SDK on the tablet and the C SDK on the controller;
a protected key store on each directly connected device;
an AOA framing protocol with producer, component, sequence, and payload fields;
product-specific Android, controller, and MCU installers; and
Dev2M releases and OTAForge assignment policy for the two OTA targets.
The examples use placeholders. Never place bootstrap credentials, enrollment tokens, private keys, or certificate bodies in logs or source control.
1. Register the asset and component graph¶
Create one logical asset, then attach two registered DDI devices and one MCU component:
robot_asset_id: robot-00042
├── tablet serial: TAB-00042 role: tablet
└── controller serial: ROB-00042 role: controller
└── MCU serial: IMU-00042 role: imu_mcu
Perform this through the platform registrar or manufacturing workflow, not an
untrusted device assertion. Store robot_asset_id and component_role as
inventory metadata that downstream telemetry consumers can join to each
certificate-derived thing_id.
Verification: the platform shows one robot asset with two DDI identities. The MCU appears in component inventory but has no certificate or broker client.
2. Provision both DDI identities¶
On Android, use ProvisioningClient.identify, ProvisioningCsr.generate, and
ProvisioningClient.provision, then build an MqttConfig from the returned
tablet certificate and private key. Run blocking provisioning calls on
Dispatchers.IO.
On the controller, use:
fgai_provisioning_client_t *provisioner =
fgai_provisioning_client_new("https://things.flexgalaxy.ai", bootstrap_bearer);
fgai_provisioning_identity_t identity = {0};
fgai_provisioning_keypair_t keypair = {0};
fgai_provisioning_result_t issued = {0};
fgai_provisioning_identify(
provisioner, "ROBOT-CONTROLLER", "ROB-00042",
NULL, NULL, firmware_version, hardware_revision, &identity);
fgai_provisioning_build_csr(
"ROB-00042", "ROBOT-CONTROLLER", identity.thing_id, 0, &keypair);
fgai_provisioning_provision(
provisioner, identity.thing_id, identity.enrollment_token,
keypair.csr_pem, NULL, &issued);
Check every C return value in production and free each result with its matching
fgai_provisioning_*_free function. Persist the key, leaf, and CA atomically.
The repository’s
provision_robust.c
is the complete checked implementation.
Verification: the tablet and controller have different thing_id values and
certificate SANs. Neither device can read the other’s private key.
4. Publish ClearJanitor cleaning-job telemetry¶
Publish numeric job outcomes from the tablet and identify the producer with tags:
tabletClient.publishTelemetry(
metrics = mapOf(
"job_duration_seconds" to job.durationSeconds.toDouble(),
"cleaned_area_m2" to job.cleanedAreaM2,
"water_used_ml" to job.waterUsedMl.toDouble(),
),
tags = mapOf(
"producer" to "clearjanitor",
"applet_id" to "clearjanitor",
"robot_asset_id" to robotAssetId,
"job_id" to job.id,
),
)
Keep job descriptions, maps, and other structured records in an application
API or object store. The current DDI telemetry helper accepts only numeric
metrics and string tags. Treat robot_asset_id as query context; the platform
must derive authorization and ownership from the certificate identity and
registrar relationship.
Verification: the message arrives on
device/<tablet_thing_id>/telemetry with the tablet certificate identity and
the producer=clearjanitor tag.
5. Publish runnerchecker and MCU telemetry¶
runnerchecker publishes through the controller client:
fgai_mqtt_metric_t motor_metrics[] = {
{"motor_current_a", motor_current_a},
{"motor_temperature_c", motor_temperature_c},
{"motor_health_score", motor_health_score},
};
fgai_mqtt_tag_t motor_tags[] = {
{"producer", "runnerchecker"},
{"component", "drive_motor_left"},
{"robot_asset_id", robot_asset_id},
};
fgai_mqtt_telemetry_t motor_sample = FGAI_MQTT_TELEMETRY_INIT;
motor_sample.metrics = motor_metrics;
motor_sample.n_metrics = 3;
motor_sample.tags = motor_tags;
motor_sample.n_tags = 3;
fgai_mqtt_client_publish_telemetry(controller, &motor_sample);
Read raw IMU measurements locally, then publish bounded summaries through the same controller client:
fgai_mqtt_metric_t imu_metrics[] = {
{"accel_rms_mps2", accel_rms_mps2},
{"gyro_peak_dps", gyro_peak_dps},
{"imu_temperature_c", imu_temperature_c},
};
fgai_mqtt_tag_t imu_tags[] = {
{"producer", "mcu"},
{"component_id", "imu-01"},
{"robot_asset_id", robot_asset_id},
};
fgai_mqtt_telemetry_t imu_sample = FGAI_MQTT_TELEMETRY_INIT;
imu_sample.metrics = imu_metrics;
imu_sample.n_metrics = 3;
imu_sample.tags = imu_tags;
imu_sample.n_tags = 3;
fgai_mqtt_client_publish_telemetry(controller, &imu_sample);
The controller owns the DDI sequence and publish timestamp. Preserve the MCU
sequence and measurement age in a future batch schema if exact source timing is
required; the current convenience API cannot override observed_at.
Verification: both messages arrive on
device/<controller_thing_id>/telemetry, separated by their producer and
component tags.
6. Keep AOA local and identity-neutral¶
Define an AOA envelope that is independent of cloud credentials. At minimum it should carry a local producer ID, component ID, monotonically increasing sequence, payload type, payload length, and checksum. The receiver maps that local producer to its own DDI telemetry call.
Do not send DDI private keys, bootstrap credentials, or enrollment tokens over AOA. Do not treat “USB cable connected” as proof that two registered devices belong to the same customer asset; the platform registry is authoritative.
Verification: disconnecting AOA stops local cross-component data without changing either device’s certificate or cloud ownership relation.
7. Implement three installers behind two OTA targets¶
The tablet polls its certificate-bound OTA path and hands a verified artifact to
a privileged Android installer. The Android SDK version 1.1.0 has a known gap:
provisionDevice() does not populate the internal enrollment store required by
DdiDeviceSdk.ota. Do not claim tablet OTA is complete until the binding has a
public, tested provisioning-to-store hand-off.
The controller creates its OTA client with the same certificate files used for DDI:
fgai_ota_client_t *ota = fgai_ota_client_new(
"https://things.flexgalaxy.ai",
"ca.crt",
"device.crt",
"device.key");
fgai_ota_response_t root = {0};
fgai_ota_error_t result = fgai_ota_poll(ota, &root);
For a robot-runtime artifact, stage it in the inactive slot, verify compatibility, switch atomically, reboot, and report the running version. For an MCU artifact, the controller must:
download it through
fgai_ota_downloadwith the expected SHA-256;verify model, hardware revision, image signature, and rollback policy;
enter the MCU bootloader and flash a recoverable bank;
read back or boot-test the new MCU version; and
call
fgai_ota_feedbackwith success or failure details.
The SDK preserves HawkBit’s HAL document. Application code must inspect the assignment metadata and dispatch each artifact to the correct installer; the SDK intentionally does not infer an installer from a filename.
Verification: run separate isolated assignments for tablet, robot runtime, and MCU firmware. Each must update only its intended component and produce feedback under the tablet or controller certificate that polled the assignment.
8. Exercise replacement and failure paths¶
Before production rollout, prove these cases:
replace the tablet while retaining the logical robot and controller history;
reject a certificate whose
thing_iddoes not match its MQTT topic;reconnect and re-subscribe both MQTT clients after broker interruption;
lose AOA while both DDI sessions remain correctly identified;
reject a foreign or non-OTA HAL link;
reject a bad SHA-256 without publishing the temporary artifact;
fail an MCU flash and recover its prior bank; and
prevent a controller artifact from reaching the Android or MCU installer.
Troubleshooting¶
Symptom |
Cause |
Fix |
|---|---|---|
broker disconnects on publish |
certificate identity and topic |
use the device’s own registration and frozen topic tree |
platform shows two customer robots |
tablet and controller were not attached to one logical asset |
correct the registrar relationship; do not merge certificates |
MCU appears as a third online DDI device |
it was registered as a direct client despite being controller-proxied |
retain a component record but remove the unnecessary DDI credential |
IMU traffic overwhelms the controller or broker |
raw high-rate samples were published individually |
filter, aggregate, and rate-limit locally; design a batch schema if raw data is required |
tablet OTA accessor rejects the device as unenrolled |
Android 1.1.0 provisioning does not populate its internal store |
implement and test the provisioning-to-store hand-off before enabling tablet OTA |
robot is online only when the tablet process is running |
the controller’s DDI session was incorrectly terminated in the app |
provide IP transport and let the controller establish its own mTLS connection |
Related references: Device operations, SDK reference, Errors and recovery, and Runtime surface.