SDK reference¶
The repository ships three bindings for one device contract. The table below is the binding-parity reference; runtime availability is a separate concern.
Note
This is the binding-parity reference for the device SDKs. The device-side SDK family and its place in the onboarding flow are described on the trustmint device-developer portal at https://docs.flexgalaxy.ai/dev/trustmint/protocol/device-sdks/, and the wire contract every binding conforms to is the frozen DDI-1 contract at https://docs.flexgalaxy.ai/dev/trustmint/protocol/ddi-1-contract/.
Capability |
Python |
Android/Kotlin |
C |
|---|---|---|---|
identify, provision, time |
yes |
yes |
yes |
certificate rotation |
yes |
yes |
yes |
MQTT connect and reconnect |
yes |
yes; automatic reconnect and re-subscribe |
yes |
telemetry |
yes |
yes |
yes |
telemetry app namespace (PLT-0069) |
yes |
yes |
yes |
CLIENT attributes |
full protobuf |
full protobuf |
flat numeric values |
SHARED push and ack |
integrated manager |
separate |
integrated callback |
command and response |
yes |
yes |
yes |
heartbeat and lifecycle |
yes |
yes |
yes |
desired/delta compatibility callbacks |
yes |
yes |
delta callback |
OTA |
yes |
yes |
yes |
licensing/status compatibility |
no |
yes; backend not published |
no |
Python 0.1.0¶
Install from an authorized checkout with python -m pip install ./sdks/python.
The package requires Python 3.9 or newer and is imported as fgai_mqtt.
Identity and provisioning¶
ProvisioningClient(base_url, attestation_bearer, *, gateway_ca=None, verify_tls=True, mtls_base_url=None, client_cert=None, client_key=None, base_backoff=1, max_backoff=60, max_retries=5)identify(model, sn, *, registrar_hint=None, attestation_method=None, firmware_version=None, hardware_revision=None) -> IdentityResultprovision(thing_id, enrollment_token, csr_pem, *, device_attributes=None) -> ProvisionResultrotate(csr_pem) -> RotateResultget_time() -> tuple[str, int]build_csr(serial, model, thing_id, key=None, *, rsa_bits=None) -> tuple[str, str]ProvisionedIdentity.persist(result, private_key_pem, directory, default_mqtt_port=8883) -> ProvisionedIdentity— writes the leaf, key and CA and the broker endpoint the provisioning response carried asshared_config.mqtt_endpoint, persisted asendpoint.json. A response with no usable endpoint raisesEndpointNotConfiguredErrorand writes nothing.ProvisionedIdentity.load(directory) -> ProvisionedIdentity— reads that record back, so a rebooted device reaches the same broker without being re-provisioned.ProvisionedIdentity.device_config(**overrides) -> DeviceConfig
fgai_mqtt.regions names the two provisioning origins as a vocabulary, never a
default: TOKYO_ORIGIN (things.flexgalaxy.ai) and SEOUL_ORIGIN
(things.ap-northeast-2.flexgalaxy.ai). Pass the origin of the account’s region
as persist(..., origin=...), which records it beside the certificates so OTA
and certificate rotation read the same region back instead of re-deciding it.
Each origin is accepted in both forms and they are the same origin: its
host (TOKYO_ORIGIN.host) and its short name (TOKYO_ORIGIN.label,
"tokyo" — the form the platform itself writes into a device’s record).
origin_for_host() resolves either form to the same entry, and every origin
check below compares origins, not spellings — so a device recorded "tokyo"
works with origin=TOKYO_ORIGIN.host and vice versa. A name outside the
vocabulary is never mapped onto the nearest origin.
An ordinary save naming a different origin raises OriginMismatchError and
writes nothing, and a not-authorized MQTT CONNACK raises RegionRefusedError
and stops the reconnect loop. The same OriginMismatchError is raised by
connect() when the configured host is on a different origin than the one the
device recorded, or names no known origin at all — before the connection is
attempted, so the credential never reaches a broker that did not issue it. A
device whose record names no origin (never given one, or a non-platform gateway)
dials as before.
persist(..., reprovision=True) is the one explicit re-provision path, and the
only way to change a recorded origin. See
Device connectivity.
IdentityResult contains thing_id, enrollment_token,
provision_endpoint, server_time, and metadata. ProvisionResult contains
the leaf and CA PEM, identity, shared configuration, and optional issuance
metadata. RotateResult contains only thing_id and certificate.
MQTT¶
DeviceConfig fields are host, port=8883, device_id, ca_cert,
client_cert, client_key, initial_state=None, keepalive=60,
session_expiry=3600, https_base_url=None, https_timeout=30, and
app_namespace=None. A malformed app_namespace raises
InvalidAppNamespaceError from the constructor.
host and https_base_url are the endpoint this device was enrolled against —
the SDK ships no default broker hostname and no default DDI origin, so both are
supplied by the caller (normally from the persisted record above). A
DeviceConfig with no https_base_url is legal; building an OtaClient from
it raises EndpointNotConfiguredError. A blank host refuses in the
constructor with the same error: a host that names nothing is the absence of an
endpoint, not a hostname to dial.
FGAIDeviceClient(config) provides:
client_idandconnectedpropertiesconnect(timeout=30)anddisconnect()publish_telemetry(metrics, tags=None, sequence=None, app_namespace=None)publish_heartbeat(uptime_seconds, firmware_version, memory_pct=0, cpu_pct=0)publish_reported_state(state, version=None)report_client_attribute(attributes, version=None)set_shared_attribute_callback(callback)andshared_attribute_shadowset_command_callback(callback)set_twin_callback(callback)lazy
ota: OtaClient
Unexpected disconnect triggers exponential reconnect from 1 to 60 seconds with 25% positive jitter. The client re-subscribes command, shared-attribute, and twin handlers after a reconnect even when the broker reports a resumed session.
OTA¶
OtaClient provides poll(), get_document(href),
send_feedback(href, action_id, execution, finished="none", details=None), and
download(href, destination, expected_sha256) -> OtaArtifact.
OtaDocument.link(relation) returns one top-level HAL link;
find_links(relation) recursively finds all matching links.
Android/Kotlin 1.1.0¶
The Android library is published to the configured private CodeArtifact repository
as ai.flexgalaxy.ddi:ddi-android-sdk:1.2.0-<source-commit>. It requires Android
API 29 or newer and JVM 17. New Kotlin applications use the neutral
ai.flexgalaxy.ddi.sdk API; external package coordinates, HTTP APIs, and public
hostnames expose only DDI names.
High-level facade¶
Create DdiDeviceSdk(DdiSdkConfig(apiBaseUrl = ..., attestationBearer = ...)).
apiBaseUrl is the DDI HTTPS origin this device was enrolled against. The SDK
ships no default origin, so a config without one fails at construction instead
of dialling an origin the device was never enrolled against; buildMqttConfig
applies the same rule to the broker, refusing a provisioning response that
carried no shared_config.mqtt_endpoint rather than falling back to a built-in
hostname; MqttConfig refuses a blank host at construction, the same rule the
Python and C bindings apply where a host enters the client. Production callers
must also supply a real attestation bearer.
The live high-level journey is:
provisionDevice(model, serial, firmwareVersion)returnsDdiProvisioning.Persist its
DdiDeviceCredentialsin protected storage.Call
buildMqttConfig(provisioning)andconfigureMqtt(config).Use
sdk.mqttandsdk.ota; both are bound to the samethingIdand PEM identity.After reboot, restore credentials and call
bindCredentials(credentials)before accessingotaor configuring MQTT.
The SDK intentionally does not claim to persist an extractable provisioning key inside Android Keystore. A product may replace the current PEM-backed bootstrap with a hardware-backed key implementation, but it must preserve the certificate identity and never upload the private key.
Provisioning¶
ProvisioningClient supplies identify, provision, rotate, and getTime.
ProvisioningCsr.generate(serial, model, thingId) returns csrPem and
privateKeyPem. Results are IdentityResult, ProvisionResult, SharedConfig,
RotateResult, and TimeResult. Provisioning is blocking OkHttp work even
though the high-level facade wraps its composed flow in Dispatchers.IO.
DdiOrigins names the two provisioning origins: TOKYO_AP_NORTHEAST_1
(https://things.flexgalaxy.ai) and SEOUL_AP_NORTHEAST_2
(https://things.ap-northeast-2.flexgalaxy.ai). Pass the one matching the
account’s region as DdiSdkConfig.apiBaseUrl; the SDK ships no default, so a
config without one fails at construction.
The origin a device was provisioned against is persisted with its stored
credentials and exposed as TrustMintSDK.provisioningOrigin.
TrustMintSDK.initialize refuses a SdkConfig.apiBaseUrl that names a different
one, throwing SdkException.OriginMismatchException with both origins and
leaving the stored record untouched. TrustMintSDK.reset(), followed by a fresh
enrolment, is the explicit re-provision path. See
Device connectivity.
MQTT¶
MqttConfig fields are host, port=8883, deviceId, CA/client/key PEM,
cleanStart=false, sessionExpiryInterval=3600, keepAliveInterval=60,
willDelayInterval=5, automaticReconnect=true, reconnectMinDelaySeconds=1,
reconnectMaxDelaySeconds=120, optional initialState, and optional
appNamespace.
Its computed client ID is fgai-<deviceId>; canonicalAppNamespace is the
lowercased, trimmed slug that goes on the wire, and a malformed one throws
IllegalArgumentException from the constructor. AppNamespace.normalize and
AppNamespace.isValid expose the same grammar. Both facades’ buildMqttConfig
take an optional appNamespace.
FgaiMqttClient provides isConnected, connect(timeout=30.seconds),
disconnect, publishTelemetry(metrics, tags, appNamespace),
publishHeartbeat, publishReportedState, setCommandCallback, and
setTwinCallback.
SharedAttributeManager(deviceId, publisher, callback) is a separate adapter.
It exposes shadow, pushTopic, ackTopic, setCallback,
subscribeSharedPush, onSharedPush, and publishAck. The application must
provide a SharedAttributePublisher backed by the actual MQTT transport.
FgaiMqttClient arms MQTT automatic reconnect itself, on by default and capped
by reconnectMaxDelaySeconds, so the host application no longer has to drive
the reconnect. On every reconnect the SDK replays its own subscriptions
(commands, twin desired, and twin delta), so a device that comes back is not
left deaf. The separate SharedAttributeManager is unchanged: its
subscribeSharedPush subscription is not part of that replay, and an
application that uses it must re-issue the subscription after a reconnect.
OTA¶
DdiDeviceSdk.ota returns DdiOtaClient. It provides poll, document,
feedback, and download; DdiOtaDocument provides link and findLinks.
Downloads verify SHA-256 before returning DdiOtaArtifact. The client accepts
only same-origin links under /ddi/v1/ota.
Legacy gateway facade¶
The direct GatewayClient/thingsboardBaseUrl path has been removed because it
bypassed managed DDI. The old TrustMintSDK stored-enrollment licensing methods
remain an artifact-compatibility surface, are not part of the managed DDI route,
and are intentionally absent from DdiDeviceSdk.
C 1.0.0¶
The C11 project builds libfgai-mqtt, plus dependency-reduced
libfgai-provisioning and libfgai-ota static libraries. Install headers under
fgai/; include fgai/fgai_mqtt.h for MQTT and
fgai/fgai_mqtt_provisioning.h or fgai/fgai_ota.h for standalone use.
Configuration and lifecycle¶
fgai_mqtt_config_new,fgai_mqtt_config_set_device_id,fgai_mqtt_config_set_tls,fgai_mqtt_config_set_keepalive,fgai_mqtt_config_set_session_expiry,fgai_mqtt_config_set_app_namespace,fgai_mqtt_config_free.set_app_namespaceis the only setter that returnsfgai_mqtt_error_t; on rejection the previous namespace is left in place.fgai_mqtt_config_newtakes the broker host, which is this device’s own configured endpoint (shared_config.mqtt_endpoint) — the SDK compiles no default broker hostname in, and a NULL, empty, or whitespace-only host returns no config rather than a client that dials nothing.fgai_mqtt_client_new,fgai_mqtt_client_connect,fgai_mqtt_client_disconnect,fgai_mqtt_client_freecommand, twin-delta, disconnect, and shared-attribute callback setters
MQTT calls return fgai_mqtt_error_t: FGAI_MQTT_OK, or connect, TLS,
publish, subscribe, serialization, deserialization, timeout, argument, and
allocation errors.
Publish and respond¶
fgai_mqtt_client_publish_telemetry(returnsFGAI_MQTT_ERR_INVALID_ARGand publishes nothing iftel.app_namespaceis a malformed slug)fgai_mqtt_client_publish_reportedfgai_mqtt_client_publish_heartbeatfgai_mqtt_client_publish_lifecyclefgai_mqtt_client_publish_shared_ackfgai_mqtt_client_respond_command
fgai_mqtt_client_handle_shared_push is public primarily for transport adapters
and unit tests. It decodes, applies, and acknowledges a raw shared push.
fgai_mqtt_app_namespace_normalize is public so a device reading its namespace
from a config file can fail at start-up rather than on the wire.
Provisioning¶
Create and tune a client with fgai_provisioning_client_new, the CA/mTLS/retry
setters, and fgai_provisioning_client_free. The flow functions are
fgai_provisioning_identify, fgai_provisioning_build_csr,
fgai_provisioning_provision, fgai_provisioning_rotate, and
fgai_provisioning_get_time. A provisioning response whose shared_config
carries no usable mqtt_endpoint is refused with FGAI_PROV_ERR_NO_ENDPOINT
rather than reported as success with an empty endpoint.
FGAI_ORIGIN_TOKYO and FGAI_ORIGIN_SEOUL name the two provisioning origins
(things.flexgalaxy.ai and things.ap-northeast-2.flexgalaxy.ai). Pass the one
matching the account’s region and record it with
fgai_provisioning_origin_save, which writes it once: re-saving the same origin
is idempotent, and a save naming a different one is refused with
FGAI_PROV_ERR_ORIGIN_MISMATCH, leaving the record exactly as it was.
fgai_provisioning_origin_replace is the one explicit re-provision path. A
not-authorized MQTT CONNACK is reported as FGAI_MQTT_ERR_REGION_REFUSED and is
terminal. See
Device connectivity.
Inspect failures with fgai_provisioning_last_error_code,
fgai_provisioning_last_error_detail,
fgai_provisioning_last_error_status,
fgai_provisioning_error_is_retryable, and
fgai_provisioning_error_is_terminal. The last-error view is owned by the
client and is not thread-safe.
OTA¶
Create with fgai_ota_client_new(https_origin, ca_path, client_cert_path, client_key_path). Use fgai_ota_poll, fgai_ota_get,
fgai_ota_feedback, fgai_ota_download, fgai_ota_find_link, and
fgai_ota_resolve_link. Free response bodies with
fgai_ota_response_free and inspect diagnostic text with
fgai_ota_last_error.
Wire rules common to every binding¶
MQTT is version 5, mTLS is mandatory, and content type is
application/x-protobuf.The stable MQTT client ID is
fgai-<thing_id>and the session expiry default is one hour.The certificate SAN and topic
thing_idmust match.Telemetry and heartbeat use QoS 0. Attributes, lifecycle, shared acks, and command responses use QoS 1.
The optional telemetry app namespace is
^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$, 3 to 63 characters, lowercased and trimmed by the binding and rejected at construction otherwise. Omitting it leaves the field off the wire entirely, so an unnamespaced sample is byte-identical to a pre-PLT-0069 build. The value is device-asserted: it narrows a read the device owner already granted, never widens one, and no authorization decision turns on it alone.OTA accepts HTTPS links only, pins them to the configured origin, and confines them to
/ddi/v1/ota.