Cenário: Normalizando Unidades de Medida usando um Adaptador Digital Twin

Este cenário explica como usar um modelo de gêmeo digital, um adaptador de gêmeo digital com mapeamentos de envelope e rota para normalizar a telemetria específica em um esquema comum e como validar o fluxo publicando payloads de dispositivos de amostra.

Esse cenário demonstra como postar dados de telemetria automotiva com diferentes unidades de medida de milhas por hora e quilômetros por hora usando diferentes pontos finais, duas instâncias de gêmeos digitais e duas chaves externas diferentes. Para obter mais informações sobre os padrões JQ referenciados nesse cenário, consulte Referência: Expressões JQ para Adaptadores Gêmeos Digitais

Um adaptador de gêmeo digital roteia solicitações por ponto final e payloads de mapas para um único modelo. Por exemplo, o modelo de métrica envia velocidade em quilômetros por hora (velocity_kph), enquanto outra instância digital twin padrão envia milhas por hora (speed).

Tarefas

Entenda os arquivos neste cenário:

Trechos de código que você pode usar em seu modelo de gêmeo digital referenciado nas etapas abaixo:

  • model.json — Modelo de gêmeo digital baseado em especificações DTDL v3 com uma propriedade de telemetria speed em milhas por hora que usa uma extensão de validação que aplica limites na faixa de valores de 0 a 100.
  • envelope.json — Configuração de envelope que declara um ponto final de referência e uma forma de payload de exemplo.
  • routes.json — Condições de rota e mapeamentos de payload que convertem quilômetros por hora em milhas por hora.
  • script.sh — Neste exemplo, você pode salvar todos os comandos da CLI do OCI listados abaixo para criar um modelo de gêmeo digital, adaptador e instâncias, além dos comandos curl para POSTar a telemetria de amostra e, em seguida, executar como um script shell script.sh.
  • Para concluir as etapas neste cenário, você pode criar e salvar a CLI do OCI e os comandos curl listados nas etapas abaixo e executar esse cenário como um script de shell: script.sh

Este exemplo de trecho de código model.json do modelo de gêmeo digital usa uma extensão personalizada dtmi:com:oracle:dtdl:extension:validation;1 que aplica regras de validação ao esquema JSON para os elementos "Telemetry", "Historized", "Validated", "Velocity". Se os dados não corresponderem aos valores esperados definidos nesta validação, os dados serão rejeitados.

Para obter uma lista completa das regras de propriedade de validação suportadas, consulte Referência de Extensão de Validação DTMI.

model.json

{
  "@context":[
    "dtmi:dtdl:context;3",
    "dtmi:dtdl:extension:historization;1",
    "dtmi:com:oracle:dtdl:extension:validation;1",
    "dtmi:dtdl:extension:quantitativeTypes;1"
  ],
  "@id":"dtmi:com:oracle:iot:poc:testmodel;1",
  "@type":"Interface",
  "contents":[
    {
      "@type":[ "Telemetry", "Historized", "Validated", "Velocity" ],
      "displayName":"Speed",
      "name":"speed",
      "schema":"integer",
      "unit":"milePerHour",
      "minimum":0,
      "maximum":100
    }
  ]
}

envelope.json

{
  "referenceEndpoint": "telemetry/automotive/usa-standard-units",
  "referencePayload": {
    "dataFormat": "JSON",
    "data": {
      "speed": 65
    }
  }
}

Este arquivo routes.json listado abaixo contém 3 expressões que transformam os quilômetros e normalizam a carga útil de dados em uma unidade de medida, milhas por hora:
  • Uma expressão de condição que avalia os dados do ponto final:

    "condition" : "${endpoint(3) == \"metric-units\"}"

    A sintaxe ${ ... } indica uma expressão que avalia o valor do terceiro parâmetro de ponto final ou elemento de caminho em uma chamada de API endpoint(3). A condição compara o valor retornado com metric-units. Se for verdade, então aplica essas regras.

  • Expressão de mapeamento de carga útil:

    "payloadMapping" : { "$.speed": "${(.velocity_kph / 1.609) | floor}"}

    A sintaxe ${ ... } indica uma expressão, essa expressão avalia e executa um cálculo aritmético para converter a velocidade ou a velocidade de quilômetros por hora em milhas por hora (.velocity_kph / 1.609); essa expressão divide o campo velocity_kph por 1.609 e, em seguida, aplica a função floor, arredondando para baixo para o inteiro mais próximo. Este valor vem da conversão de quilômetros em milhas, que é quilômetros = milhas × 1.60934,

  • "payloadMapping": {"$.speed": "$.speed"} Esta é uma expressão de mapeamento de valor direto, que passa pelo valor da velocidade como está.
[
  {
    "description" : "Automotive data using metric units (kilometers) that's converted to miles, with a different external key in the digital twin instance",
    "condition" : "${endpoint(3) == \"metric-units\"}",
    "payloadMapping" : { 
      "$.speed": "${(.velocity_kph / 1.609) | floor}"

    },
    "referencePayload" : {
      "dataFormat" : "JSON",
      "data" : { "velocity_kph": 104 }
    }
  },
  {
    "description" : "Auto 1 and Auto 2 use USA standard units, shows speed as is.", 
    "condition" : "*",
    "payloadMapping" : { "$.speed": "$.speed" }
  }
]

Etapa 1: Criar um Modelo Digital Twin

Use este comando da CLI oci iot digital-twin-model create para criar um modelo gêmeo digital usando o arquivo model.json. Este modelo de gêmeo digital padroniza speed em milhas por hora.

Este comando registra o modelo de gêmeo digital com este URI DTMI dtmi:com:oracle:iot:poc:testmodel;1 conforme definido na menção model.json acima.

oci iot digital-twin-model create \
  --iot-domain-id <iot-domain-ocid> \
  --display-name "Test Digital Twin Model" \
  --description "Model for testing automotive telemetry routing and unit normalization" \
  --spec file://~/model.json

Etapa 2: Criar um Adaptador Digital Twin com um Envelope e Rotas

Crie um adaptador que faça referência à especificação de modelo de gêmeo digital DTMI e que use o envelope de entrada e as rotas para normalizar a telemetria de entrada.

oci iot digital-twin-adapter create \
  --iot-domain-id <iot-domain-ocid> \
  --display-name "automotive-speed-adapter" \
  --description "Routes by units" \
  --digital-twin-model-spec-uri "dtmi:com:oracle:iot:poc:testmodel;1" \
  --inbound-envelope file://~/envelope.json \
  --inbound-routes file://~/routes.json

O referenceEndpoint no envelope.json é telemetry/automotive/usa-standard-units. O arquivo routes.json:

  • Encaminha solicitações para o terceiro segmento de caminho que é igual a metric-units, por exemplo, /telemetry/automotive/metric-units, e depois converte velocity_kph em speed em mph, e dispara o resultado.
  • Usa uma condição catch-all padrão (*) para passar por speed inalterado para automóveis que usam milhas por hora.

Etapa 3: Criar Duas Instâncias Gêmeas Digitais

Crie duas instâncias de gêmeos digitais que são autenticadas usando um vault secreto e compartilhe as duas instâncias de gêmeos digitais que compartilham o mesmo adaptador de gêmeos digitais. Os pontos finais são definidos para que cada instância de gêmeo digital possa postar dados em um ponto final exclusivo:
  • Ponto final para mph: https://device-host/telemetry/automotive/usa-standard-units
  • Ponto final para kph: https://device-host/telemetry/automotive/metric-units
Substitua o OCID do adaptador de gêmeo digital pelo OCID do adaptador de gêmeo digital criado na etapa anterior. Substitua os nomes de exibição e as chaves externas pelos valores do seu ambiente.
Observação

Ao criar uma instância de gêmeo digital com autenticação, você pode usar um segredo de vault ou um certificado mTLS para autenticação. Para segurança, é uma prática recomendada criar um segredo de vault ou certificado mTLS exclusivo para cada instância de gêmeo digital. Todos os recursos devem estar na mesma região e tenancy que qualquer outro recurso relacionado do IoT.

Se você usar um certificado mTLS para autenticação, use o nome comum do certificado como a chave externa: --external-key <common-name-from-certificate-details>. Consulte Cenário: Criar uma Instância Digital Twin que use um Certificado mTLS.

Um administrador deve adicionar a política para criar segredos ou certificados. Consulte a Etapa 3 em Pré-requisitos.

Instância de gêmeo digital para unidades padrão dos EUA, milhas por hora (mph), observe a chave externa:

american-auto-standard-units
oci iot digital-twin-instance create \
  --iot-domain-id <IoT-domain-ocid> \
  --display-name "auto using miles per hour" \
  --external-key american-auto-standard-units \
  --digital-twin-adapter-id <same-digital-twin-adapter-ocid> \
  --auth-id <secret-ocid-or-certificate-ocid>
Instância de gêmeo digital para unidades métricas europeias, quilômetros por hora (kph), observe a chave externa:

european-auto-metric-units

oci iot digital-twin-instance create \
  --iot-domain-id <IoT-domain-ocid> \
  --display-name "auto using kilometers per hour" \
  --external-key european-auto-metric-units \
  --digital-twin-adapter-id <same-digital-twin-adapter-ocid> \
  --auth-id <secret-ocid-or-certificate-ocid>

Etapa 4: Enviar telemetria de amostra para validar o roteamento e o mapeamento

Para enviar a telemetria, você precisa da chave externa da resposta da instância do gêmeo digital da Etapa 3, da senha do dispositivo e do ponto final do host do dispositivo.

Se a instância de gêmeo digital usar o segredo do vault para autenticação, você deverá usar como o valor de segredo codificado de base 64 como a senha do dispositivo.

  • Chave externa: Substitua a external-key pela external-key da instância de gêmeo digital com a qual você deseja trabalhar. Para evitar problemas de cotação, é uma prática recomendada não usar cotações no valor da chave externa.
  • Senha do dispositivo: Substitua a senha do dispositivo pelo conteúdo de segredo de texto simples ou pelo certificado mTLS.
  • Host do dispositivo: Substitua device-host pelo host do dispositivo do domínio IoT. Para obter o URL do ponto final do host do dispositivo para o domínio IoT, consulte Obtendo Detalhes de um Domínio IoT.

-u "external-key:device-password-secret-contents-or-certificate"

Observação

Dependendo do seu sistema operacional ou do seu aplicativo, alguns aplicativos ou editores de código podem adicionar aspas indesejadas aos seus valores, isso pode causar um erro. Consulte Diagnosticando e Solucionando Problemas para obter exemplos de cotação.
curl -i -X POST \
  -u "european-auto-metric-units:device-password-secret-or-certificate" \
  -H "Content-Type: application/json" \
  "https://device-host/telemetry/automotive/metric-units" \
  -d '{ "velocity_kph": 0 }'
curl -i -X POST \
  -u "european-auto-metric-units:device-password-secret-or-certificate" \
  -H "Content-Type: application/json" \
  "https://device-host/telemetry/automotive/metric-units" \
  -d '{ "velocity_kph": 110 }'

curl -i -X POST \
  -u "american-auto-standard-units:device-password-secret-or-certificate" \
  -H "Content-Type: application/json" \
  "https://device-host/telemetry/automotive/usa-standard-units" \
  -d '{ "speed": 0 }'

curl -i -X POST \
  -u "american-auto-standard-units:device-password-vault-secret-base-64-or-certificate-OCID" \
  -H "Content-Type: application/json" \
  "https://device-host/telemetry/automotive/usa-standard-units" \
  -d '{ "speed": 60 }'

Etapa 5: Verificar o comportamento da normalização

A condição de roteamento ${endpoint(3) == "metric-units"} avalia os dados e aplica o seguinte mapeamento de payload ao ponto final de dados das unidades de métrica:

"$.speed": "${(.velocity_kph / 1.609) | floor}"

Resultado esperado:

  • O mapeamento do adaptador converte quilômetros por hora (kph) em milhas por hora (mph) e, em seguida, aplica o andar para atender a um esquema inteiro:

    speed_mph = floor(velocity_kph / 1.609)

  • Neste exemplo, velocity_kph = 0: speed_mph = floor(0 / 1.609) = floor(0) = 0 mph

    Depois do andar indica o passo de arredondamento que força o resultado a um número inteiro, arredondando para baixo em direção ao infinito negativo. Isso é necessário quando seu modelo DTDL declara a telemetria de velocidade como schema: "integer" para que o valor seja um inteiro, não um flutuante ou uma string.

  • velocity_kph = 110speed = floor(110 / 1.609) = 68 mph
  • Postagens de dados padrão com speed passam inalteradas, por exemplo, os valores deste exemplo: 0, 60

Se a validação do modelo estiver ativada minimum: 0, maximum: 100, os valores fora do intervalo serão rejeitados de acordo com as regras de validação.

Usa conversão de tipo flexível:

  • Pass-through (mph, schema "integer"): {"speed": "60"} e {"speed": 60.0} são armazenados como 60. {"speed": "60.2"} é rejeitado, a menos que o mapeamento gere para um inteiro (por exemplo, com floor).
  • Rota de métrica (kph → mph): {"velocity_kph": 110}68; {"velocity_kph": "110"}68 porque o mapeamento floor emite um inteiro. Mantenha entradas aritméticas numéricas para evitar erros de expressão; sempre que possível, prefira 110 em vez de "110".
  • Arredondamento permanece explícito: A conversão suave não arredonda automaticamente 68.35 para 68. Use floor para esquema de número inteiro ou alterne o modelo para schema: "double" para preservar frações.

Práticas Recomendadas

  • Arquivos JSON de referência para adaptadores e especificações de modelo de gêmeo digital: Quando você faz upload de um adaptador usando a CLI, pode usar arquivos JSON para especificar o mapeamento de dados. Nos comandos da CLI, você pode fazer referência a arquivos como file://~/name.json ou fornecer um caminho absoluto ou relativo, dependendo do seu ambiente de shell. Dependendo do seu sistema operacional, você pode ter uma sintaxe ligeiramente diferente com aspas, barras ou localização do arquivo por padrão. Consulte Gerenciando Entrada e Saída da CLI e Usando um Arquivo JSON para Entrada Complexa.
  • Os arquivos de configuração JSON (envelope, rotas) usam nomes de campo de API em camelCase (por exemplo, referenceEndpoint). A CLI do OCI passa esses arquivos por meio de argumentos file:// inalterados; portanto, o uso de JSON camelCase com CLI é esperado e correto.
  • O referenceEndpoint em envelope.json deve refletir um ponto final típico do seu adaptador.
  • A condição de rota curinga (*) é avaliada após condições específicas; ordene suas definições de rota de acordo.
  • Escopo de conversão suave: Strings numéricas e duplas de números inteiros são aceitas quando correspondem ao tipo de modelo (por exemplo, inteiro). Os auxiliares de conversão como number() e toInteger permanecem sem suporte em expressões de rota; confie em aritmética e floor ou adote schema: "double" para preservar frações.

Variação: Usando schema="double" em vez de floor

Esta variação mostra como a definição do esquema de propriedade do modelo como double afeta o mapeamento do adaptador e os valores registrados. Com double, o validador aceita qualquer valor numérico integral ou fracionário que atenda às restrições de intervalo, sem arredondamento automático. Você pode optar por preservar a precisão fracionária (bruta) ou coagir a números inteiros usando floor. Ambos passam na validação desde que os valores permaneçam dentro do intervalo definido.

O que a validação schema=double faz

  • Aceitação de tipo: Aceita números JSON com ou sem partes fracionais, por exemplo: 60, 68.35. Strings como "68" permanecem inválidas.
  • Intervalo: Mínimo e máximo, por exemplo, neste exemplo, 0–100 são impostos.
  • Sem arredondamento automático: A plataforma IoT não arredonda valores; você controla o arredondamento no mapeamento do adaptador de gêmeo digital ou downstream usando APEX ou SQL, dependendo dos sistemas configurados para exibir seus dados.

Arquivos usados nesta variação:

  • model_double.json — Modelo DTDL com schema: "double".
    {
      "@context": [
        "dtmi:dtdl:context;3",
        "dtmi:dtdl:extension:historization;1",
        "dtmi:com:oracle:dtdl:extension:validation;1",
        "dtmi:dtdl:extension:quantitativeTypes;1"
      ],
      "@id": "dtmi:com:oracle:iot:poc:testmodeldouble;1",
      "@type": "Interface",
      "contents": [
        {
          "@type": [
            "Telemetry",
            "Historized",
            "Validated",
            "Velocity"
          ],
          "displayName": "Speed",
          "name": "speed",
          "schema": "double",
          "unit": "milePerHour",
          "minimum": 0,
          "maximum": 100
        }
      ]
    }
  • routes_double_raw.json — O mapeamento preserva a precisão fracionária: "$.speed": "${.velocity_kph / 1.609}".
    [
      {
        "description": "Double model: European metric units to miles per hour (mph); preserving fractional precision (no floor).",
        "condition": "${endpoint(3) == \"metric-units\"}",
        "payloadMapping": {
          "$.speed": "${.velocity_kph / 1.609}"
        },
        "referencePayload": {
          "dataFormat": "JSON",
          "data": { "velocity_kph": 110 }
        }
      },
      {
        "description": "Double model: USA standard units passthrough.",
        "condition": "*",
        "payloadMapping": { "$.speed": "$.speed" }
      }
    ]
  • routes_double_floor.json — O mapeamento se coerce para o número inteiro mph: "$.speed": "${(.velocity_kph / 1.609) | floor}" armazenado como um duplo.
    [
      {
        "description": "Double model: European metric units to miles per hour (mph); floor to whole number (stored as double).",
        "condition": "${endpoint(3) == \"metric-units\"}",
        "payloadMapping": {
          "$.speed": "${(.velocity_kph / 1.609) | floor}"
        },
        "referencePayload": {
          "dataFormat": "JSON",
          "data": { "velocity_kph": 110 }
        }
      },
      {
        "description": "Double model: USA standard units passthrough.",
        "condition": "*",
        "payloadMapping": { "$.speed": "$.speed" }
      }
    ]
    

Etapa A: Criar o modelo de gêmeo digital usando o double

oci iot digital-twin-model create \
  --iot-domain-id iot-domain-ocid \
  --display-name "TestModelSpeedDouble" \
  --spec file://model_double.json

Etapa B: Criar dois adaptadores associados ao modelo duplo

Use valores brutos para preservar a precisão fracionária:

oci iot digital-twin-adapter create \
  --iot-domain-id iot-domain-ocid \
  --display-name "auto-adapter-double-raw" \
  --digital-twin-model-id double-model-ocid \
  --inbound-envelope file://envelope.json \
  --inbound-routes file://routes_double_raw.json

Floor usa um número inteiro como o mph, que é um dobro:

oci iot digital-twin-adapter create \
  --iot-domain-id iot-domain-ocid \
  --display-name "auto-adapter-double-floor" \
  --digital-twin-model-id double-model-ocid \
  --inbound-envelope file://envelope.json \
  --inbound-routes file://routes_double_floor.json

Etapa C: Criar instâncias gêmeas digitais para cada adaptador

oci iot digital-twin-instance create \
  --iot-domain-id iot-domain-ocid \
  --display-name "american-auto-raw" \
  --external-key american-auto-raw \
  --digital-twin-adapter-id adapter-double-raw-ocid \
  --auth-id vault-secret-ocid

oci iot digital-twin-instance create \
  --iot-domain-id iot-domain-ocid \
  --display-name "european-auto-raw" \
  --external-key european-auto-raw \
  --digital-twin-adapter-id adapter-double-raw-ocid \
  --auth-id vault-secret-ocid
oci iot digital-twin-instance create \
  --iot-domain-id iot-domain-ocid \
  --display-name "american-auto-dfloor" \
  --external-key american-auto-dfloor \
  --digital-twin-adapter-id adapter-double-floor-ocid \
  --auth-id vault-secret-ocid

oci iot digital-twin-instance create \
  --iot-domain-id iot-domain-ocid \
  --display-name "european-auto-dfloor" \
  --external-key european-auto-dfloor \
  --digital-twin-adapter-id adapter-double-floor-ocid \
  --auth-id vault-secret-ocid

Etapa D: Poste a telemetria da amostra e compare os resultados

Use a chave externa e a senha do dispositivo da instância dupla digital para enviar dados:
 -u "external-key:device-password" \
  • Se o gêmeo digital usar o segredo do vault para autenticação, use o base64-secret como a senha do dispositivo.
  • Se a instância de gêmeo digital usar uma certificação mLTS, use certificate-ocid como a senha do dispositivo.

Valores brutos (double, no floor):

curl -i -X POST \
  -u "american-auto-raw:device-password" \
  -H "Content-Type: application/json" \
  "https://device-host/telemetry/automotive/usa-standard-units" \
  -d '{ "speed": 60 }'

curl -i -X POST \
  -u "european-auto-raw:device-password" \
  -H "Content-Type: application/json" \
  "https://device-host/telemetry/automotive/metric-units" \
  -d '{ "velocity_kph": 110 }'

Resultado Esperado: A segunda publicação produz aproximadamente 68.35… mph (fracionário) e é aceita porque schema=double aceita números fracionários dentro do intervalo.

Piso (duplo, com piso):

curl -i -X POST \
  -u "american-auto-dfloor:device-password" \
  -H "Content-Type: application/json" \
  "https://device-host/telemetry/automotive/usa-standard-units" \
  -d '{ "speed": 60 }'

curl -i -X POST \
  -u "european-auto-dfloor:device-password" \
  -H "Content-Type: application/json" \
  "https://device-host/telemetry/automotive/metric-units" \
  -d '{ "velocity_kph": 110 }'

Resultado Esperado: A segunda publicação produz um número inteiro para 68 e é aceita. O valor é armazenado como um dobro, por exemplo, 68.0, mesmo que seja um número inteiro.

Observações sobre cotações de nome de usuário e impacto downstream

  • Chave externa é igual ao nome de usuário de autenticação: Se uma instância de gêmeo digital for criada usando aspas no valor da chave externa, por exemplo, "\"american-auto-standard-units\"", o nome de usuário de autenticação básica na sua solicitação curl deverá incluir aspas ou ocorrer uma incompatibilidade e resultar em um erro 401 Unauthorized. Para evitar problemas de cotação, é uma prática recomendada não usar aspas no seu valor de chave externa, como nos exemplos neste cenário.
  • Downstream no APEX ou usando SQL: Com schema=double, os valores de mph fracionais são preservados. Se você precisar de números inteiros em relatórios, aplique FLOOR e ROUND no SQL, por exemplo, SELECT FLOOR(speed) FROM …. Com schema=integer, certifique-se de que o mapeamento emite valores integrais, por exemplo, usando floor para satisfazer a digitação de inteiros.
  • Suporte a expressões: O inbound-route aceita aritmética e o floor. Funções como toInteger ou number foram rejeitadas e não são suportadas; use floor ou adote schema: "double" para aceitação fracionária.