---
格式版本: 2
标题: "Working with Digital Twin Adapters in OCI Internet of Things Platform | cloud-infrastructure"
原文链接: "https://blogs.oracle.com/cloud-infrastructure/working-with-iot-digital-twin-adapters"
发布日期: "2026-08-13"
发布时间校准状态: "found"
发布时间需复核: "否"
发布时间来源: "rule:local:strict_original_body"
发布时间证据: "August 13, 2026 9 minute read"
发布时间校准原因: "规则确认唯一严格发布时间，来源 local:strict_original_body"
发布时间校准置信度: "high"
发布时间候选数量: 5
发布时间严格候选数量: 1
发布时间原页读取状态: "source template page reused from URL open"
发布时间未找到原因: ""
发布时间校准时间: "2026-08-14T19:23:43+08:00"
发布时间仲裁状态: "skipped"
发布时间仲裁尝试次数: 0
发布时间仲裁耗时毫秒: 0
发现时间: "2026-08-14T19:19:31+08:00"
入库时间: "2026-08-14T11:23:43.542Z"
来源平台: "固定入口"
搜索渠道: "fixed_url"
搜索词: "https://blogs.oracle.com/?page=news"
匹配关键词:
  []
相关厂家:
  - "Oracle"
相关专家:
  []
内容类型: "网页"
抓取工具: "CDP Render"
清洗工具: "CDP Text + Defuddle/Readability 正文提取"
原始附件:
  []
AI优质: "否"
AI打分: 22
AI分档: "非优质"
AI质检状态: "不通过"
AI打分理由: "内容为Oracle IoT数字孪生适配器技术文档，与超节点/AI Rack/机柜级AI基础设施完全无关。"
AI质检模型: "tx-deepseek-v4-flash"
AI质检时间: "2026-08-14T19:24:03+08:00"
AI主题相关性: 0
AI来源权威性: 12
AI新颖性: 0
AI技术细节: 0
AI商业部署信号: 0
AI完整性: 10
AI摘要: "OCI IoT Platform的数字孪生适配器可将设备异构JSON遥测映射到统一数字孪生模型，确保应用读取一致的资产数据。"
AI摘要模型: "ali-deepseek-v4-flash"
AI摘要时间: "2026-09-07T03:31:24.216Z"
采集批次: "2026年8月14日18点30分41秒"
采集批次ID: "20260814-183041-680"
去重键: "https://blogs.oracle.com/cloud-infrastructure/working-with-iot-digital-twin-adapters"
---

Digital twin applications need a consistent view of each asset, but device telemetry often arrives in different shapes. One device might send an attribute named *motorTemperature*, another might send *mtrTemp*, and another device might report a pressure value in psi while the model stores the pressure in bar. OCI Internet of Things Platform (OCI IoT Platform) digital twin adapters translate those device-specific payloads into the canonical model structure that applications use.

Using a *WaterPump* model, we demonstrate how adapters handle timestamps, nested components, attribute names, unit conversion, endpoint-based routing, and selected JQ expressions to map heterogeneous telemetry streams to a canonical model.

## Introduction

Digital twin adapters apply to both directly connected devices and indirectly connected devices. In both cases, the adapter maps an inbound payload into the digital twin model so applications can read a consistent asset representation.

These examples focus on adapter behavior for device telemetry. Gateway configuration, gateway routing, and gateway telemetry setup are outside the scope of these examples.

## An example: WaterPump model

The examples use a *WaterPump* model with pump-level telemetry and a reusable *ElectricMotor* component. The *WaterPump* model follows the format described in the companion post, [Understanding Digital Twin Models in OCI IoT Platform](https://blogs.oracle.com/cloud-infrastructure/understanding-oci-iot-digital-twin-models). The target model shape looks like this:

```
{
  "motor": {
    "motorTemperature": 68.4,
    "vibrationLevel": 1.7,
    "powerConsumption": 12.6
  },
  "flowRate": 247.5,
  "dischargePressure": 4.3
}
```

The model treats *motor* telemetry as part of the pump. Applications can read *motor.motorTemperature*, *motor.vibrationLevel*, *motor.powerConsumption*, *flowRate*, and *dischargePressure* from one canonical structure.

## What adapters do

An adapter maps inbound device payloads into a digital twin model. The model defines the target structure, and the adapter describes how values from the source payload move into that structure.

A digital twin workflow uses three related objects. The model describes the asset. The adapter maps payload values to the model. The digital twin instance associates a model with the adapter. This separation lets device payloads vary while applications read a stable model.

![An example of how the adapter definition and digital twin model combine to normalize telemetry](https://blogs.oracle.com/cloud-infrastructure/wp-content/uploads/sites/83/2026/08/adapter_example.png)

An example of how the adapter definition and digital twin model combine to normalize telemetry

*Figure 1: The digital twin instance associates the model and adapter so applications can read normalized twin state.*

## Structure of an adapter description

An adapter description uses two related JSON elements: an envelope description and an inbound routes description. The envelope describes the shape of the incoming message. The inbound routes define how matching messages are mapped into the digital twin model.

Envelope description:

```
{
    "referenceEndpoint": "/waterpump",
    "referencePayload": {
      "dataFormat": "JSON",
      "data": {
        "motor": {
          "motorTemperature": 68.4,
          "vibrationLevel": 1.7,
          "powerConsumption": 12.6
        },
        "flowRate": 247.5,
        "dischargePressure": 4.3
    }
  }
}
```

The envelope description identifies the model and provides a representative inbound message. The *displayName* and *description* fields make the file readable for developers. The *digitalTwinModelSpecUri* connects the description to the *WaterPump* model. The *inboundEnvelope* section contains the *referenceEndpoint* and *referencePayload*. The *referencePayload* defines the data format and sample payload structure used to test and document expected telemetry.

The envelope description shows what a device or integration sends before adapter mapping is applied. In this default-style example, the payload already follows the *WaterPump* model shape: motor telemetry is nested under *motor*, and *flowRate* and *dischargePressure* appear at the top level. Inbound routes description:

```
[
    {
      "condition": "*",
      "description": "Map the component-aware water pump payload directly to the WaterPump model.",
      "payloadMapping": {
      "$.motor.motorTemperature": "$.motor.motorTemperature",
      "$.motor.vibrationLevel": "$.motor.vibrationLevel",
      "$.motor.powerConsumption": "$.motor.powerConsumption",
      "$.flowRate": "$.flowRate",
      "$.dischargePressure": "$.dischargePressure"
    },
      "referencePayload": {
        "dataFormat": "JSON",
        "data": {
          "motor": {
            "motorTemperature": 68.4,
            "vibrationLevel": 1.7,
            "powerConsumption": 12.6
          },
          "flowRate": 247.5,
          "dischargePressure": 4.3
        }
      }
    }
]
```

The inbound routes define how the platform handles messages that match an adapter route. The *inboundRoutes* array can contain one or more route definitions. Each route includes a *condition*, *description*, *payloadMapping*, and route-level referencePayload. The *condition* determines when the route applies. The wildcard value \* applies the route to any matching inbound message.

The *payloadMapping* section maps source values into digital twin model attributes. In this default-style example, *flowRate* and *dischargePressure* map directly because the incoming attribute names match the model. The *motor* mapping creates the nested component object and maps *motorTemperature*, *vibrationLevel*, and *powerConsumption* into the *ElectricMotor* component used by the *WaterPump* model.

## Where JQ fits

JQ is a query and transformation language for JSON. In an adapter, a simple path such as $.flowRate copies a value from the inbound payload. A JQ expression such as ${(.dischPressPsi \* 0.0689475729)} transforms a value before the platform writes it to the digital twin.

- Use *envelopeMapping* to extract metadata such as timeObserved.
- Use *payloadMapping* to map or transform telemetry values.
- Use JQ expressions to build objects, rename fields, convert units, and normalize timestamp values.

JQ expressions compute target values during route evaluation and payload mapping. Expressions use placeholder syntax, such as ${ … }, in route conditions and mappings. They can select routes based on endpoint segments, headers, or payload values; transform inbound telemetry; convert units; rename fields; normalize timestamps; and produce JSON that matches the digital twin model schema. The normalized output must satisfy model validation, including attribute types, ranges, and units.

Adapter mappings should also account for the model schema. Arithmetic operations and the *floor* function are supported, but casting helpers such as *number()* and *toInteger()* are not supported in route expressions. For integer model attributes, the mapping must emit an integer numeric value, such as ${(.velocity\_kph / 1.609) | *floor* }. For *double* attributes, fractional values are accepted. Use *floor* only when whole-number storage is intended.

Endpoint matching should use segment-based conditions, such as ${ *endpoint* (1) == ‘home’ and *endpoint* (2) == ‘data’ and *endpoint* (3) == ‘status’}, instead of wildcard patterns.

For time handling, map *timeObserved* when the device provides an observation timestamp; otherwise, the platform uses the received time. Functions such as *fromdateformat* and *todateformat* can normalize timestamps in envelope or payload mappings.

## Creating custom adapters

When a device sends telemetry in the same format as the model, the service automatically creates a default adapter to process the incoming messages. No developer action is required when a default adapter is used. Use a custom adapter when telemetry does not match the model or when metadata needs adjustment before the platform stores the sample.

Common adjustments include date and time formatting, payload shape, attribute names, and measurement units. The following examples use the same *WaterPump* model while changing the incoming telemetry to show how adapters handle each case.

## Example 1: Timestamp adjustment

A common adapter task is adjusting the timestamp of a telemetry sample based on a value within the JSON payload. In this example, the telemetry values already match the *WaterPump* model. The adapter extracts the device observation time from the payload and maps it to timeObserved. Incoming telemetry:

```
{
  "timestamp": "2026-07-08T12:00:00.000000Z",
  "motor": {
    "motorTemperature": 68.4,
    "vibrationLevel": 1.7,
    "powerConsumption": 12.6
  },
  "flowRate": 247.5,
  "dischargePressure": 4.3
}
```

Envelope mapping:

```
{
  "referenceEndpoint": "/waterpump",
  "envelopeMapping": {
    "timeObserved": "$.timestamp"
  }
}
```

Telemetry mapping:

```
{
  "$.motor": "${ {motorTemperature: .motor.motorTemperature, vibrationLevel: .motor.vibrationLevel, powerConsumption: .motor.powerConsumption} }",
  "$.flowRate": "$.flowRate",
  "$.dischargePressure": "$.dischargePressure"
}
```

The timestamp describes when the device observed the measurement. Mapping it to *timeObserved* keeps observation time separate from receive time.

The timestamp uses this adapter-ready format:

```
2026-07-08T12:00:00.000000Z
```

Common timestamp shapes include ISO 8601 UTC with fractional seconds, ISO 8601 UTC without fractional seconds, ISO 8601 with a timezone offset, epoch seconds, and epoch milliseconds. Source timestamps should be normalized to the format the adapter process expects.

JQ provides date and time functions for normalization:

- *strptime(format)* parses a timestamp string using a format pattern.
- *strftime(format)* formats a parsed timestamp.
- *mktime* converts a parsed time array to epoch seconds.
- *gmtime* converts epoch seconds to a UTC time array.
- *localtime* converts epoch seconds to a local time array.
- *fromdateiso8601* parses an ISO 8601 timestamp to epoch seconds.
- *todateiso8601* formats epoch seconds as an ISO 8601 timestamp.

## Example 2: Flat telemetry

In the next example the payload reports all values at the top level, whereas the model expects *motor* telemetry inside the *motor* component.

```
{
  "motorTemperature": 68.4,
  "vibrationLevel": 1.7,
  "powerConsumption": 12.6,
  "flowRate": 247.5,
  "dischargePressure": 4.3
}
```

Adapter mapping:

```
{
  "$.motor": "${ {motorTemperature: .motorTemperature, vibrationLevel: .vibrationLevel, powerConsumption: .powerConsumption} }",
  "$.flowRate": "$.flowRate",
  "$.dischargePressure": "$.dischargePressure"
}
```

The adapter builds the nested *motor* object from flat fields in the telemetry message. *flowRate* and *dischargePressure* already match the model, so those fields map directly.

## Example 3: Attribute renaming

Some devices use compact names that are meaningful near the device but less useful to application developers. In this example, *mtr*, *mtrTemp*, *vibLvl*, *pwrUse*, *flowRt*, and *dischPress* map to the canonical model names.

```
{
  "mtr": {
    "mtrTemp": 68.4,
    "vibLvl": 1.7,
    "pwrUse": 12.6
  },
  "flowRt": 247.5,
  "dischPress": 4.3
}
```

Adapter mapping:

```
{
  "$.motor": "${ {motorTemperature: .mtr.mtrTemp, vibrationLevel: .mtr.vibLvl, powerConsumption: .mtr.pwrUse} }",
  "$.flowRate": "$.flowRt",
  "$.dischargePressure": "$.dischPress"
}
```

The adapter preserves the incoming telemetry values while giving applications descriptive model fields defined in the model.

## Example 4: Unit conversion from psi to bar

The *WaterPump* model defines *dischargePressure* in bar. The sample device sends the source value in pounds per square inch and names the field dischPressPsi.

```
{
  "motor": {
    "motorTemperature": 68.4,
    "vibrationLevel": 1.7,
    "powerConsumption": 12.6
  },
  "flowRate": 247.5,
  "dischPressPsi": 62.37
}
```

Adapter mapping:

```
{
  "$.motor": "${ {motorTemperature: .motor.motorTemperature, vibrationLevel: .motor.vibrationLevel, powerConsumption: .motor.powerConsumption} }",
  "$.flowRate": "$.flowRate",
  "$.dischargePressure": "${(.dischPressPsi * 0.0689475729)}"
}
```

The dischargeP *ressure* expression multiplies the incoming psi value by 0.0689475729. With the sample value, 62.37 psi maps to about 4.30 bar. Applications can read one model unit even when devices publish another unit.

## Example 5: Conditional routing by endpoint

Unit conversion can also depend on how a payload reaches the adapter. Route conditions handle cases where one endpoint sends values in a different unit system than the default telemetry path.

In this example, telemetry sent to an endpoint segment named *english-units* reports *motor* temperature in Fahrenheit. The model expects Celsius, so that route converts the temperature. The default route leaves the temperature unchanged.

Inbound routes file:

```
[
    {
      "condition": "${endpoint(2) == \"english-units\"}",
      "description": "Convert motor temperature from Fahrenheit to Celsius for English-unit telemetry.",
      "payloadMapping": {
        "$.motor": "${ {motorTemperature: ((.motor.motorTemperature - 32) * 5 / 9), vibrationLevel: .motor.vibrationLevel, powerConsumption: .motor.powerConsumption} }",
        "$.flowRate": "$.flowRate",
        "$.dischargePressure": "$.dischargePressure"
      },
      "referencePayload": {
        "dataFormat": "JSON",
        "data": {
          "motor": {
            "motorTemperature": 68.4,
            "vibrationLevel": 1.7,
            "powerConsumption": 12.6
          },
          "flowRate": 247.5,
          "dischargePressure": 4.3
        }
      }
    },
    {
      "condition": "*",
      "description": "Map water pump telemetry without changing motor temperature.",
      "payloadMapping": {
        "$.motor": "${ {motorTemperature: .motor.motorTemperature, vibrationLevel: .motor.vibrationLevel, powerConsumption: .motor.powerConsumption} }",
        "$.flowRate": "$.flowRate",
        "$.dischargePressure": "$.dischargePressure"
      },
      "referencePayload": {
        "dataFormat": "JSON",
        "data": {
          "motor": {
            "motorTemperature": 20.2,
            "vibrationLevel": 1.7,
            "powerConsumption": 12.6
          },
          "flowRate": 247.5,
          "dischargePressure": 4.3
        }
      }
    }
]
```

The first route uses the *condition* ${ *endpoint* (2) == “ *english-units* “} to identify telemetry that arrives through the *english-units* segment. That route maps the same payload structure as the default route, but it transforms *motor.motorTemperature* from Fahrenheit to Celsius with (.motor.motorTemperature – 32) \* 5 / 9.

The second route uses the wildcard *condition* \*. It acts as the default route for messages that do not match the *english-units* condition. In that route, *motorTemperature* maps directly from the incoming payload to the model without conversion. The more specific route appears before the wildcard route so the conversion is applied before the default route can match.

## Example 6: Additional JQ features

The previous examples use JQ for direct mapping, unit conversion, and conditional routing. JQ also supports conditional logic, default values, object construction, and value translation, which helps adapters handle payload variations without changing device firmware.

Additional JQ capabilities are described in the OCI Internet of Things Platform documentation on using [JQ expressions](https://docs.oracle.com/en-us/iaas/Content/internet-of-things/jq-adapter-mapping-reference.htm).

The following examples are not part of the *WaterPump* model. However, they show additional JQ patterns that apply when a model needs fallback values or semantic mappings.

Missing-value default:

```
{
  "$.pressure": "${ if has(\"pressure\") then .pressure else 0 end }"
}
```

This mapping checks whether the inbound *pressure* value exists. If the device sends a *pressure* value, the adapter maps that value to the model. If the device does not send the value, the adapter in this example writes 0. This pattern is useful when the model requires a value but some devices omit the field. Use this pattern only when a default value is appropriate.

Semantic value mapping:

```
{
  "$.operatingState": "${ if .state == 1 then \"running\" elif .state == 0 then \"stopped\" else \"unknown\" end }"
}
```

This mapping converts a numeric device value into a string that is easier for applications to read. A device sends 0 when it is stopped and 1 when it is running. The adapter writes *stopped* or *running* into the model. The else branch writes *unknown* when the device sends a value outside the expected range.

## Summary

Digital twin adapters translate device telemetry into the stable shape defined by a digital twin model. Direct mapping works when the payload already matches the model. Custom adapters handle differences such as explicit timestamps, flat payloads, abbreviated field names, nested components, unit conversions, and endpoint-specific routing.

The pattern is consistent: start with the model, inspect the incoming payload, add a reference payload, map source fields into model paths, and use JQ when a value needs structure or transformation.

You can configure OCI Internet of Things to create a normalized view of telemetry from heterogeneous devices. Start with a simple digital twin model and one representative telemetry payload from a real device. Create an adapter that maps that payload into the model, then create a digital twin instance that associates the model with the adapter. Once your digital twin instances are configured, send test telemetry and inspect the stored twin values to confirm that timestamps, units, attribute names, and nested components resolve as expected.

After the first path works, add more payload variations. Use custom adapters for flat telemetry, abbreviated field names, unit conversions, route-specific behavior, and other transformations that let applications read one consistent digital twin structure across different devices.

## Resources

- [OCI Internet of Things Platform](https://www.oracle.com/cloud/cloud-native/iot-platform/)
- [OCI Internet of Things Platform documentation](http://docs.oracle.com/en-us/iaas/Content/internet-of-things/)
- [Creating a Digital Twin Model](https://docs.oracle.com/en-us/iaas/Content/internet-of-things/digital-twin-models.htm)
- [Creating a Digital Twin Adapter](https://docs.oracle.com/en-us/iaas/Content/internet-of-things/digital-twin-adapters.htm)
- [Digital Twins Definition Language v3](https://azure.github.io/opendigitaltwins-dtdl/DTDL/v3/DTDL.v3.html)
- [JQ Expressions for Digital Twin Adapters](https://docs.oracle.com/en-us/iaas/Content/internet-of-things/jq-adapter-mapping-reference.htm)
