Scenario: Route Indirect Device Data Using Target and contentRoot
Create a gateway and two indirectly connected devices to route payloads with different nested structures by using target and contentRoot in the gateway adapter's inbound routes.
Use this scenario to create one gateway, an indirectly connected HVAC device whose telemetry is nested under $.data, and an indirectly connected compressor whose telemetry is nested under $.telemetry. The gateway adapter resolves the target device and selects the correct content root in each inbound route.
For more concepts, see Indirectly Connected Devices FAQ. For a general gateway workflow that includes directly and indirectly connected devices, unstructured telemetry, commands, and roaming, see Create Digital Twins for Indirectly Connected Devices Using a Gateway.
Tasks
- Create digital twin models.
- Create digital twin adapters.
- Create the gateway and device instances.
- Send accepted and rejected telemetry data.
- Get the digital twin instance contents.
Save the examples in this scenario as gateway-model.json, hvac-model.json, compressor-model.json, gateway-envelope.json, gateway-routes.json, hvac-envelope.json, hvac-routes.json, compressor-envelope.json, and compressor-routes.json.
Step 1: Create Digital Twin Models
Create a digital twin model for the gateway and one digital twin model for each device type in the scenario. These devices emit structured data.
Use these files: gateway-model.json, hvac-model.json, and compressor-model.json.
{
"@context": [
"dtmi:dtdl:context;3",
"dtmi:dtdl:extension:historization;1"
],
"@id": "dtmi:com:oracle:example:icd:contentroot:gateway;1",
"@type": "Interface",
"displayName": "Gateway Content Root",
"description": "Gateway model for route-level contentRoot coverage",
"contents": [
{
"@type": ["Telemetry", "Historized"],
"name": "cpuUtilization",
"schema": "integer"
},
{
"@type": ["Telemetry", "Historized"],
"name": "memoryUtilization",
"schema": "integer"
},
{
"@type": ["Telemetry", "Historized"],
"name": "storageUtilization",
"schema": "integer"
},
{
"@type": ["Property", "Historized"],
"name": "firmwareVersion",
"schema": "string"
}
]
}{
"@context": [
"dtmi:dtdl:context;3",
"dtmi:dtdl:extension:historization;1"
],
"@id": "dtmi:com:oracle:example:icd:contentroot:hvac;1",
"@type": "Interface",
"displayName": "HVAC Content Root Case 1",
"description": "Indirect device model for payloads nested under $.data",
"contents": [
{
"@type": ["Telemetry", "Historized"],
"name": "temperature",
"schema": "integer"
},
{
"@type": ["Telemetry", "Historized"],
"name": "humidity",
"schema": "integer"
}
]
}{
"@context": [
"dtmi:dtdl:context;3",
"dtmi:dtdl:extension:historization;1"
],
"@id": "dtmi:com:oracle:example:icd:contentroot:compressor;1",
"@type": "Interface",
"displayName": "Compressor Content Root Case 2",
"description": "Indirect device model for payloads nested under $.telemetry",
"contents": [
{
"@type": ["Telemetry", "Historized"],
"name": "pressure",
"schema": "integer"
},
{
"@type": ["Telemetry", "Historized"],
"name": "vibration",
"schema": "double"
}
]
}Use the Console when you want to upload a saved model file or paste the specification directly.
- On the IoT domains list page, open the domain you want to work with. If you need help finding the IoT domains list page, see Listing IoT Domains or Create a new IoT Domain.
- Select the Digital twin models tab, and then select Create.
- Enter a name and an optional description. Avoid entering confidential information.
- Choose Upload the specification or Paste specification, and then use one of the example files from this scenario.
- Optional: Add tags.
- Select Create.
Use one of the following files when you upload or paste the specification.
Use the oci iot digital-twin-model create command to create each digital twin model in the IoT domain.
Create the gateway model
oci iot digital-twin-model create --iot-domain-id <iot-domain-OCID> --spec file://gateway-model.jsonCreate the HVAC model
oci iot digital-twin-model create --iot-domain-id <iot-domain-OCID> --spec file://hvac-model.jsonCreate the compressor model
oci iot digital-twin-model create --iot-domain-id <iot-domain-OCID> --spec file://compressor-model.jsonUse the example model files
gateway-model.json,hvac-model.json, andcompressor-model.json.Run the CreateDigitalTwinModel operation to create a digital twin model in the IoT domain.
Use the same DTDL payload shown in the downloadable digital twin model files when you call the API.
Step 2: Create Digital Twin Adapters
Create one digital twin adapter for the gateway and one adapter for each indirectly connected device type. Use these files: gateway-envelope.json, gateway-routes.json, hvac-envelope.json, hvac-routes.json, compressor-envelope.json, and compressor-routes.json.
Choose a Gateway Adapter Pattern
Resolve target in the inbound envelope when forwarded device messages use one consistent device identification and payload convention. When the envelope-level target resolves to a device, the service delegates the message without evaluating the gateway's inbound routes. When it is absent or resolves to null, the service evaluates the inbound routes.
Resolve target in the inbound routes when one gateway handles different device families, endpoints, or payload structures. The envelope can map common metadata, while each route defines the target and contentRoot appropriate for that message.
This scenario uses route-level targeting because HVAC data is nested under $.data, compressor data is nested under $.telemetry, and the fallback route maps telemetry that belongs to the gateway.
Route by target and contentRoot
The gateway envelope maps the common timeObserved metadata. It does not define target, so the service evaluates the inbound routes in order and processes only the first matching route.
- The HVAC route resolves
targetfromendpoint(2)and delegates the object under$.datato that device's adapter. - The compressor route resolves
targetfromendpoint(2)and delegates the object under$.telemetryto that device's adapter. - The final default route has no
target. It appliespayloadMappingto telemetry that belongs to the gateway.
If an envelope-level target resolves to a non-null value, the service delegates the message using the envelope-level contentRoot and does not evaluate inbound routes. If a route-level target resolves to null, its contentRoot is ignored; when that route also has payloadMapping, the mapping is applied to the current digital twin instead.
Do not define target in both the envelope and the inbound routes. Define contentRoot only with target, and avoid combining target and payloadMapping in the same route.
When the gateway envelope resolves timeObserved, the target adapter inherits that value. A non-null timeObserved resolved by the target adapter overrides the inherited value.
Use a digital twin adapter when the incoming payload must be mapped to the target digital twin model. A digital twin adapter is not required when the device sends unstructured data; you can pass the data through unchanged.
- On the IoT domains list page, open the domain you want to work with.
- Select the Digital twin adapters tab, and then select Create.
- Enter a name and an optional description. Avoid entering confidential information.
- Select the digital twin model that matches the payload you are mapping.
- Turn on Specify inbound envelope and routes, and then upload or paste the envelope JSON and routes JSON for the adapter.
- Optional: Add tags.
- Select Create.
Create the gateway adapter with the gateway files.
gateway-envelope.json{ "referenceEndpoint": "/gateway/gateway-reference", "referencePayload": { "dataFormat": "JSON", "data": { "time": 1773768299143534, "cpuUtil": 30, "memUtil": 25, "diskUtil": 20, "firmware": "Oracle Linux 9.1" } }, "envelopeMapping": { "timeObserved": "$.time" } }gateway-routes.json[ { "condition": "${endpoint(1) == \"hvac\"}", "contentRoot": "$.data", "target": "${endpoint(2)}" }, { "condition": "${endpoint(1) == \"compressor\"}", "contentRoot": "$.telemetry", "target": "${endpoint(2)}" }, { "condition": "*", "payloadMapping": { "$.cpuUtilization": "$.cpuUtil", "$.memoryUtilization": "$.memUtil", "$.storageUtilization": "$.diskUtil", "$.firmwareVersion": "$.firmware" } } ]Create the HVAC and compressor adapters with the matching device files.
hvac-envelope.json{ "referenceEndpoint": "/hvac/icd-case1-reference", "referencePayload": { "dataFormat": "JSON", "data": { "time": 1773768299143535, "temperature": 75, "humidity": 65 } }, "envelopeMapping": { "timeObserved": "$.time" } }hvac-routes.json[ { "condition": "*", "payloadMapping": { "$.temperature": "$.temperature", "$.humidity": "$.humidity" } } ]compressor-envelope.json{ "referenceEndpoint": "/compressor/icd-case2-reference", "referencePayload": { "dataFormat": "JSON", "data": { "time": 1773768299143536, "pressure": 101, "vibration": 0.42 } }, "envelopeMapping": { "timeObserved": "$.time" } }compressor-routes.json[ { "condition": "*", "payloadMapping": { "$.pressure": "$.pressure", "$.vibration": "$.vibration" } } ]Use the oci iot digital-twin-adapter create command to create each adapter in the IoT domain.
Create the gateway adapter
Use this command to associate the gateway digital twin model and provide the envelope and route files.
oci iot digital-twin-adapter create \ --iot-domain-id <iot-domain-OCID> \ --digital-twin-model-spec-uri "dtmi:com:oracle:example:icd:contentroot:gateway;1" \ --inbound-envelope file://gateway-envelope.json \ --inbound-routes file://gateway-routes.jsongateway-envelope.json{ "referenceEndpoint": "/gateway/gateway-reference", "referencePayload": { "dataFormat": "JSON", "data": { "time": 1773768299143534, "cpuUtil": 30, "memUtil": 25, "diskUtil": 20, "firmware": "Oracle Linux 9.1" } }, "envelopeMapping": { "timeObserved": "$.time" } }gateway-routes.json[ { "condition": "${endpoint(1) == \"hvac\"}", "contentRoot": "$.data", "target": "${endpoint(2)}" }, { "condition": "${endpoint(1) == \"compressor\"}", "contentRoot": "$.telemetry", "target": "${endpoint(2)}" }, { "condition": "*", "payloadMapping": { "$.cpuUtilization": "$.cpuUtil", "$.memoryUtilization": "$.memUtil", "$.storageUtilization": "$.diskUtil", "$.firmwareVersion": "$.firmware" } } ]Create the HVAC adapter
oci iot digital-twin-adapter create \ --iot-domain-id <iot-domain-OCID> \ --digital-twin-model-spec-uri "dtmi:com:oracle:example:icd:contentroot:hvac;1" \ --inbound-envelope file://hvac-envelope.json \ --inbound-routes file://hvac-routes.jsonhvac-envelope.json{ "referenceEndpoint": "/hvac/icd-case1-reference", "referencePayload": { "dataFormat": "JSON", "data": { "time": 1773768299143535, "temperature": 75, "humidity": 65 } }, "envelopeMapping": { "timeObserved": "$.time" } }hvac-routes.json[ { "condition": "*", "payloadMapping": { "$.temperature": "$.temperature", "$.humidity": "$.humidity" } } ]Create the compressor adapter
oci iot digital-twin-adapter create \ --iot-domain-id <iot-domain-OCID> \ --digital-twin-model-spec-uri "dtmi:com:oracle:example:icd:contentroot:compressor;1" \ --inbound-envelope file://compressor-envelope.json \ --inbound-routes file://compressor-routes.jsoncompressor-envelope.json{ "referenceEndpoint": "/compressor/icd-case2-reference", "referencePayload": { "dataFormat": "JSON", "data": { "time": 1773768299143536, "pressure": 101, "vibration": 0.42 } }, "envelopeMapping": { "timeObserved": "$.time" } }compressor-routes.json[ { "condition": "*", "payloadMapping": { "$.pressure": "$.pressure", "$.vibration": "$.vibration" } } ]For more information about referencing files from the CLI, see Using a JSON File for Complex Input.
Run the CreateDigitalTwinAdapter operation to create a digital twin adapter in the IoT domain.
Use the same inbound envelope and inbound routes payloads shown in the downloadable files.
Step 3: Create the Gateway and Device Instances
Create the gateway first so you can reuse it when you create the indirectly connected devices.
Use the Console to create a gateway and associate multiple devices to it.
In the Console, there are two ways to create a gateway for the IoT domain. You can create a gateway from the Digital twin instance tab, by selecting the gateway type when you create a digital twin instance. Or you can select the Gateway tab to create the gateway.
- On the IoT domains list page, open the IoT domain you want to work with.
- Select the Gateways tab and then select Create to create a gateway so that it's available for indirect devices.
- On the IoT domain page, select the Digital twin instances tab, select Create, and create an indirectly connected HVAC instance and an indirectly connected compressor instance.
- For the gateway instance, select an authentication ID.
- For an indirectly connected instance, select one or more existing gateways instead of adding device authentication credentials.
- Optional: Add tags.
- Review the digital twin instance summary, and then select Create.
After you create the digital twin instances use the Gateway details page to view which devices depend on and are associated to the gateway.- Find the Gateway details on the IoT domain page, by selecting the Gateway tab to view the gateway list, select a gateway to go to the Gateway details page.
- On the Digital twin instance tab, you can search by type to view the different digital twin instance types for an IoT domain.
- View the directly and indirectly connected devices from the Digital twin instance details page.
For a complete list of settings, see Creating a Digital Twin Instance, Listing Digital Twin Instances, and Getting a Digital Twin's Instance Details.
Use the oci iot digital-twin-instance create command to create the gateway first, and then create the two indirectly connected device instances.
Create the gateway instance
oci iot digital-twin-instance create \ --iot-domain-id <iot-domain-OCID> \ --connectivity-type GATEWAY \ --display-name "Gateway Content Root" \ --auth-id <certificate-or-secret-OCID> \ --digital-twin-adapter-id <gateway-adapter-OCID> \ --external-key gateway1Create the indirectly connected HVAC instance
oci iot digital-twin-instance create \ --iot-domain-id <iot-domain-OCID> \ --connectivity-type INDIRECT \ --display-name "HVAC Content Root Case 1" \ --gateways '["<gateway-instance-OCID>"]' \ --digital-twin-adapter-id <hvac-adapter-OCID> \ --external-key hvac1Create the indirectly connected compressor instance
oci iot digital-twin-instance create \ --iot-domain-id <iot-domain-OCID> \ --connectivity-type INDIRECT \ --display-name "Compressor Content Root Case 2" \ --gateways '["<gateway-instance-OCID>"]' \ --digital-twin-adapter-id <compressor-adapter-OCID> \ --external-key compressor1Run the CreateDigitalTwinInstance operation to create the gateway instance and the two indirectly connected instances.
When you create an indirectly connected instance, make sure that the request includes the gateway association.
Step 4: Send Telemetry Data
Authentication ID options:
- For testing digital twin instances, if you use secret OCID as the authentication ID, then use the device username as the external key and the plain-text secret contents as the device password. For instructions, when you Create a Secret see Step 7 and for secrets that are already created, see Getting a Secret's Contents.
For production digital twin instances use mTLS certificate OCID instead of a vault secret as the authentication ID. When you use a mTLS certificate, then you need to use the common name from the certificate details as the external key:
--external-key <common-name-from-certificate-details>
- Connect to
<domain-short-id>.device.iot.<region>.oci.oraclecloud.comon port8883by using MQTT over TLS. - Authenticate as the gateway external key,
gateway1, because the gateway publishes every message in this example.
Send gateway telemetry through the default route:
mqttx pub \ -h '<domain-short-id>.device.iot.<region>.oci.oraclecloud.com' \ -p 8883 \ -l mqtts \ -t 'gateway/gateway1' \ -m '{ "time": 1773768299143534, "cpuUtil": 30, "memUtil": 25, "diskUtil": 20, "firmware": "Oracle Linux 9.1" }' \ -u gateway1 \ -P '<secret-contents>'The default route maps the gateway payload to
cpuUtilization,memoryUtilization,storageUtilization, andfirmwareVersion.Send HVAC telemetry nested under
$.data:mqttx pub \ -h '<domain-short-id>.device.iot.<region>.oci.oraclecloud.com' \ -p 8883 \ -l mqtts \ -t 'hvac/hvac1' \ -m '{ "time": 1773768299143535, "data": { "temperature": 75, "humidity": 65 } }' \ -u gateway1 \ -P '<secret-contents>'Send compressor telemetry nested under
$.telemetry:mqttx pub \ -h '<domain-short-id>.device.iot.<region>.oci.oraclecloud.com' \ -p 8883 \ -l mqtts \ -t 'compressor/compressor1' \ -m '{ "time": 1773768299143536, "telemetry": { "pressure": 101, "vibration": 0.42 } }' \ -u gateway1 \ -P '<secret-contents>'Use these messages to confirm that each route enforces its configured
contentRoot. These test messages are expected to be rejected because the selected nested object is missing.Send an HVAC message with
$.telemetryinstead of$.data:mqttx pub \ -h '<domain-short-id>.device.iot.<region>.oci.oraclecloud.com' \ -p 8883 \ -l mqtts \ -t 'hvac/hvac1' \ -m '{ "time": 1773768299143537, "telemetry": { "temperature": 76, "humidity": 66 } }' \ -u gateway1 \ -P '<secret-contents>'Send a compressor message with
$.datainstead of$.telemetry:mqttx pub \ -h '<domain-short-id>.device.iot.<region>.oci.oraclecloud.com' \ -p 8883 \ -l mqtts \ -t 'compressor/compressor1' \ -m '{ "time": 1773768299143538, "data": { "pressure": 102, "vibration": 0.52 } }' \ -u gateway1 \ -P '<secret-contents>'
Optional Step 5: Get the Digital Twin Instance Contents
Get the latest snapshot content to confirm that each accepted payload was mapped to the expected digital twin instance.
- Open the IoT domain and select the Digital twin instances tab.
- Select the gateway, HVAC, or compressor digital twin instance.
- Select the Data tab to view its latest snapshot data.
Use the oci iot digital-twin-instance get-content command to verify the mapped fields.
Verify the gateway fields:
cpuUtilization,memoryUtilization,storageUtilization, andfirmwareVersion.oci iot digital-twin-instance get-content --digital-twin-instance-id <gateway-instance-OCID>Verify the HVAC fields:
temperatureandhumidity.oci iot digital-twin-instance get-content --digital-twin-instance-id <hvac-instance-OCID>Verify the compressor fields:
pressureandvibration.oci iot digital-twin-instance get-content --digital-twin-instance-id <compressor-instance-OCID>Run the GetDigitalTwinInstanceContent operation to get content from a digital twin instance.
Choose the Right Connectivity Type for your Digital Twin Instance
How to decide which connectivity type to use for a digital twin instance:
Directly Connected
- Use when
- The device connects to the IoT domain on its own.
- Required parameter
- Authentication ID, for testing use a secret and for production use mTLS certificate.
- Optional input
- If the device payload contains structured data, and you can not program the device to send the data in the format you want to receive then use a digital twin adapter to convert the data into the format you want to use. In this case, the digital twin instance contains an associated digital twin model to a structure the data and an digital twin adapter to convert the data into the format you want to use.
- Do not use
- Gateway association.
Gateway
- Use when
- One device forwards data and commands for itself and for other devices.
- Required parameters
- Authentication ID, a gateway adapter, and an external key.
- Recommended follow-up
- Review the gateway details page to see how many devices depend on the gateway before you change or delete it.
- Do not
- Associate a gateway instance to another gateway instance.
Indirectly Connected
- Use when
- Another device, typically a gateway, sends telemetry data and receives commands on the device's behalf.
- Required
- One or more gateway associations.
- Optional
- A digital twin adapter, when the device uses structured telemetry that still needs payload mapping after the gateway forwards it.
- Do not use
- An authentication ID on the indirectly connected digital twin instance.
Not Connected
- Use when
- Testing your IoT digital twin instance without authentication or data.
- Required
- Must not use authentication or send or receive data on this digital twin instance.
- Optional
- When testing you can set up digital twin relationships for your digital twin instances.
- Do not use
- An authentication ID on digital twin instances that are not connected.
FAQ
- When are the gateway's inbound routes evaluated?
- The service evaluates the inbound routes only when the gateway envelope's
targetexpression is absent or resolves to null. If an envelope-level target resolves to a non-null value, the message is delegated immediately and the inbound routes are not evaluated. - When should I use envelope-level targeting instead of route-level targeting?
- Use envelope-level targeting when forwarded device messages follow one consistent device identification and payload convention. Use route-level targeting when device families require different target expressions, content roots, endpoint conditions, or payload mappings.
- What does a route-level
targetidentify? - It is a JQ expression that must resolve to the external key of a digital twin instance associated with the gateway. In this scenario,
${endpoint(2)}resolves tohvac1orcompressor1. - Does
targetchange the incoming endpoint? - No. The
targetexpression resolves the indirectly connected digital twin and its adapter, but it does not rewrite the endpoint. The target adapter receives the original endpoint and then evaluates its own inbound routes. - Can the target adapter have multiple inbound routes?
- Yes. The target adapter can use condition-based routes, including a wildcard route. Because the original endpoint is preserved, design the route conditions carefully when an adapter is shared by directly and indirectly connected devices.
- When is a route-level
contentRootused? - It is used only when that route's
targetresolves to a non-null value. The service delegates the selected object to the target device's adapter. The HVAC route selects$.data, while the compressor route selects$.telemetry. - What happens when a route's
targetresolves to null? - The route's
contentRootis ignored. If the route also containspayloadMapping, the mapping is applied to the current digital twin instance. The default route uses this behavior for gateway telemetry. - How can the gateway process its own telemetry?
- Define a gateway route whose
targetis absent or resolves to null, and usepayloadMappingto map the data to the gateway digital twin. Gateway telemetry and indirectly connected device telemetry can use separate endpoints so that each route has a clear condition. - Can the target external key come from the message instead of the endpoint?
- Yes. The target expression can resolve an external key from a shared envelope field, such as a device key at the top level of the incoming JSON message. This can be useful when clients use a stable topic instead of placing each device key in the endpoint.
- Can one digital twin model have multiple adapters?
- Yes. You can create multiple adapters for the same model when devices use different data structures. Each digital twin instance is associated with the adapter that matches its incoming data.
- Can directly and indirectly connected devices share an adapter?
- Yes, when their payload structures and endpoint-routing requirements are compatible. Adapters typically represent the structure of incoming data rather than the connectivity topology.
- How many envelope mapping sections can an adapter define?
- An adapter has one
envelopeMappingsection. Use route-leveltargetandcontentRootvalues when the adapter must route multiple indirectly connected device types or payload structures. - How is
timeObservedhandled after delegation? - The target adapter inherits the non-null
timeObservedvalue resolved by the gateway envelope. If the target adapter resolves its own non-null value, that value overrides the inherited timestamp. - Why are the wrong-content-root test messages rejected?
- The HVAC route delegates
$.data, so a payload that contains only$.telemetrydoes not provide the expected HVAC data. The compressor route delegates$.telemetry, so a payload that contains only$.datadoes not provide the expected compressor data.
Next Steps
For additional gateway workflows, including commands, unstructured telemetry, and roaming, see Create Digital Twins for Indirectly Connected Devices Using a Gateway.
Delete the resources used to create this scenario: