Referência: JQ Expressions for Digital Twin Adapters

Padrões de expressão JQ práticos para entradas de dispositivo, envelopes de adaptador, condições de rota e mapeamentos de carga útil para normalizar a telemetria no que é definido nos modelos de gêmeos digitais.

Principais Conceitos

Você pode gravar expressões JQ em um adaptador de gêmeo digital como placeholders ${ ... } para calcular valores de destino em payload-mapping e avaliar condições de rota.

Maneiras comuns de usar expressões JQ:

  • Entrada do dispositivo: payloads brutos postados por dispositivos.
  • Envelope: Declara pontos finais de referência e formas de payload de exemplo; por exemplo, você pode definir o mapeamento timeObserved.
  • Rotas: Avalie condições, incluindo pontos finais, cabeçalhos, corpo e selecione um mapeamento de payload.
  • Mapeamentos de carga útil: Transforme, converta unidades, renomeie chaves e normalize para o esquema de modelo DTDL.
  • Saída: A saída JSON normalizada que deve atender à validação de modelo duplo digital, incluindo tipos, intervalos e unidades.
  • Suporte à função de mapeamento: As funções aritmética e floor são aceitas. Não há suporte para funções como toInteger e number.
  • Correspondência de ponto final: Usa condições baseadas em segmento com endpoint(n), por exemplo: ${endpoint(1) == 'home' and endpoint(2) == 'sonnen' and endpoint(3) == 'status'} em vez de padrões curinga não suportados.
  • Esquema inteiro x duplo:
    • Para schema: "integer", certifique-se de que o mapeamento emite números integrais (por exemplo, "${(.velocity_kph / 1.609) | floor}").
    • Para schema: "double", saídas fracionárias são aceitas; use floor somente se quiser o armazenamento de números inteiros como duplo, por exemplo, 68.0.
  • Tipo 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.
    • 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 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.
  • Tratamento de envelope e tempo: Se o timeObserved não for fornecido, a plataforma poderá usar receivedTime. Use fromdateformat, todateformat e funções relacionadas para conversões de tempo em mapeamentos de envelope ou payload.
Observação

A validação de dados acontece no seu modelo DTDL. Se uma propriedade for declarada como integer, a saída normalizada deverá ser um valor integer, não um string ou float. Consulte o padrão de correção de número inteiro abaixo.

Para obter mais informações, consulte Referência de Extensão de Validação DTMI

Exemplos de entrada de dispositivo

Payloads de entrada típicos com esquemas específicos da unidade:

Unidades padrão dos EUA milhas por hora:

{
  "speed": 60
}

Unidades métricas europeias quilómetros por hora:

{
  "velocity_kph": 110
}

Payload aninhado de um sensor:

{
  "telemetry": { "temp_c": 22.4, "humidity": 48 }
}

Matriz de amostras:

{
  "samples": [ { "kph": 30 }, { "kph": 50 }, { "kph": 0 } ]
}

Estes exemplos mostram um modelo DTDL onde a temperatura é um inteiro dentro do intervalo de 0-100 e umidade é opcional.

Quando ambos os valores estiverem presentes e a temperatura estiver dentro do intervalo permitido e do tipo correto, ele será válido e o valor de entrada completo será ingerido:
{ "temperature": 60, "humidity": 45 }
Quando um valor é ausente, apenas os dados válidos são ingeridos. Neste exemplo, a temperatura está ausente para que a umidade seja ingerida e o valor da temperatura não seja atualizado.
{
"humidity": 45
}
Quando um valor é nulo, ele é ignorado e somente o valor válido é ingerido:
{
		“temperature”: null
 		“humidity”: 45
	}

Mapeamento de envelope

O envelope.json declara um referenceEndpoint e um referencePayload. Opcionalmente, um mapeamento de envelope pode definir time-observed:

{
  "referenceEndpoint": "telemetry/automotive/standard",
  "referencePayload": {
    "dataFormat": "JSON",
    "data": { "speed": 65 }
  }
}
Observação

O identificador especial receivedTime pode ser fornecido pela plataforma quando o dispositivo omite time. Se o mapeamento de envelope for especificado e contiver um timeObserved, receivedTime será usado como valor timeObserved.

Padrões de condição de rota

Condições de roteamento são regras ou expressões usadas para determinar qual regra de mapeamento ou processamento deve ser aplicada a uma mensagem ou solicitação de entrada. Se uma condição de rota for avaliada como verdadeira, a regra de mapeamento ou transformação associada será acionada e aplicada.

Neste exemplo, a condição de rota adaptador duplo digital define duas rotas para processar mensagens de dispositivo de entrada, com base no formato dos dados, por exemplo, se unidades de métrica forem recebidas no 3º segmento do caminho do ponto final, /vehicle/speed/metric-units/

[
  {
    "description": "European metric to mph; convert then floor (no explicit cast).",
    "condition": "${endpoint(3) == \"metric-units\"}",
    "payloadMapping": {
      "$.speed": "${(.velocity_kph / 1.609) | floor}"
    },
    "referencePayload": {
      "dataFormat": "JSON",
      "data": { "velocity_kph": 104 }
    }
  },
  {
    "description": "USA standard units passthrough.",
    "condition": "*",
    "payloadMapping": { "$.speed": "$.speed" }
  }
]
  • endpoint(n) seleciona o segmento de caminho n-th (com base em 0 ou 1, dependendo do adaptador). No cenário de normalização de unidades de medida, endpoint(3) é usado para que /vehicle/speed/metric-units/ corresponda ao terceiro segmento metric-units.
  • Coloque condições mais específicas antes do catch-all "*".
  • payloadMapping:
    • Obtém a velocidade do campo velocity_kph em quilômetros por hora do payload e a converte em milhas por hora: .velocity_kph / 1.609
    • Aplica floor para arredondar para baixo para o inteiro mais próximo floor não é convertido, portanto, o resultado pode ser um flutuante.
    • O resultado é atribuído ao campo de saída speed.
  • referencePayload:
    • Demonstra a entrada esperada para esta rota: {"velocity_kph": 104}
  • Ponto final de entrada: /vehicle/speed/metric-units/device123
  • Payload de entrada: { "velocity_kph": 104 } Mapeamento de saída: { "speed": 64 } (since 104 / 1.609 = 64.64, floor is 64)

Exemplos de mapeamento de carga útil

Expressões JQ comuns para mapeamentos de payload:

  • Pass-through: "$.speed": "$.speed"
  • Renomear chave: "$.speed": "${.velocity_kph}"
  • Conversão de unidade: "$.mph": "${.kph / 1.609}"
  • Piso/teto/rodada: "${.x | floor}", "${.x | ceil}", "${.x | round}"
  • Coalesce/default (dependente de versão): "${ if .value? then .value else 0 end }"
  • Conversão de tipo:
    • Para o número: "${.value | tonumber}" (suporta tonumber na maioria dos builds)
    • Para string: "${.value | tostring}"
    Observação

    Em um adaptador gêmeo digital IoT, funções de conversão como toInteger ou number não são aceitas no inbound-routes. Você pode usar aritmética com floor definido para um esquema inteiro ou usar schema: "double" e formatos de arredondamento para ingestão de dados downstream. inbound-routes deve ser um JSON válido; as expressões pertencem a strings entre aspas "${ ... }".
  • Extração aninhada: "${.telemetry.temp_c}"
  • Mapa de matriz: "${[ .samples[] | .kph / 1.609 | floor ]}"
  • Condicional: "${ if .kph > 0 then .kph / 1.609 else 0 end }"
Observação

Dependendo da integração do seu adaptador, as expressões entre aspas "${...}" podem ser serializadas como strings. Quando o mecanismo suporta expressões sem aspas, por exemplo, ${...} como valor bruto, é recomendável que o form emita um número JSON em vez de uma string.

Nuances: Tipos inteiros, strings e números calculados

Quando uma propriedade DTDL é schema: "integer", a saída normalizada deve ser do tipo inteiro. Dois modos de falha comuns ao calcular valores:

  1. Stringificação: Uma expressão entre aspas pode gerar "68" (string), com falha na validação de inteiros.
  2. Números flutuantes: A aritmética produz 68.0; alguns validadores tratam isso como não inteiro, mesmo que matematicamente integral.

Corrigir padrões:

  • Usar somente andar para esquema de inteiro: "${(.velocity_kph / 1.609) | floor}" para produzir um número integral que satisfaça a digitação de inteiro.
  • Alternativa: alterne a propriedade de modelo para schema: "double" para preservar a precisão fracionária ou aplique floor no mapeamento ao armazenar um número inteiro como um dobro. Arredonde/formate no APEX/SQL conforme necessário.

Exemplos de Mapeamento de Adaptador Gêmeo Digital

Este exemplo normaliza quilômetros por hora (KPH) para milhas por hora (MPH) usando floor (sem conversão).

{
  "description": "European auto uses metric units; convert to mph and floor to whole number.",
  "condition": "${endpoint(3) == \"metric-units\"}",
  "payloadMapping": {
    "$.speed": "${(.velocity_kph / 1.609) | floor}"
  },
  "referencePayload": {
    "dataFormat": "JSON",
    "data": { "velocity_kph": 104 }
  }
}

Exemplo: Pass-through catch-all padrão

{
  "description": "English units passthrough.",
  "condition": "*",
  "payloadMapping": {
    "$.speed": "$.speed"
  }
}

Exemplo: telemetria aninhada e padrão de coalescência

{
  "description": "Extract nested temp; default to 0 when missing.",
  "condition": "${endpoint(2) == \"env\"}",
  "payload-mapping": {
    "$.room_temp_c": "${ if .telemetry.temp_c? then .telemetry.temp_c else 0 end }"
  }
}

Exemplo: Normalização de matriz

{
  "description": "Normalize kph samples to mph (whole-number mph via floor).",
  "condition": "${.samples?}",
  "payload-mapping": {
    "$.speeds": "${ [ .samples[] | .kph / 1.609 | floor ] }"
  }
}

Expectativas de saída vs. validação do modelo

As saídas devem atender aos esquemas e restrições do modelo de gêmeo digital. Para o modelo de unidade automotiva em model.json, consulte Cenário: Normalizando Unidades de Medida usando um Adaptador Digital Twin:

  • name: speed
  • schema: integer
  • unit: milePerHour
  • minimum: 0, maximum: 100

speed normalizado deve ser um inteiro dentro de 0,100. Uma string calculada "68" ou um número flutuante 68.0 falhará na validação.

Limitações e dicas

  • A disponibilidade do filtro varia: A maioria dos filtros jq principais (floor, ceil, round, tonumber, tostring, map, select, add)
  • Digitar emissão: As expressões incorporadas em strings entre aspas podem ser serializadas como strings. Preferir expressões sem aspas brutas se houver suporte para emitir tipos numéricos.
  • Tratamento nulo: As operações em null podem produzir null. Use if .x? then ... else ... end para padrões defensivos.
  • Precisão: A aritmética de ponto flutuante pode introduzir artefatos de arredondamento; aplique round e floor conforme necessário antes da conversão.
  • Conversão de inteiro: Em testes observados, toInteger e number não foram aceitos no inbound-routes no momento da criação do adaptador. Preferir aritmética + floor para esquema inteiro ou usar schema: "double" e arredondar o formato para baixo.

Aplicando a correção de inteiros ao cenário automotivo

O cenário cria um modelo com um número inteiro speed e roteia payloads de unidade de métrica para o mesmo modelo de gêmeo digital. Sem uma conversão explícita, o valor calculado pode ser rejeitado pela validação devido ao desvio de tipo (string ou número semelhante a flutuante).

Correção usada no cenário:

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

Por que é necessário:

  • floor garante que o valor seja numerado como um todo, satisfazendo a digitação de inteiros.
  • Alternativa: use schema: "double" para preservar a precisão fracionária e arredondar e formatar a jusante, se necessário.
Observação

Ao usar o esquema inteiro, prefira aritmética mais floor. Para esquema duplo, você pode omitir floor para manter MPH fracionário ou incluí-lo para armazenar um número inteiro MPH como um duplo.

Snippets de referência rápida

Exemplos suportados:

  • Condição: correspondência de ponto final: "condition": "${endpoint(3) == \"metric-units\"}"
  • Mapeamento: repasse: "$.speed": "$.speed"
  • Mapeamento: kph → mph (esquema inteiro): "$.speed": "${(.kph / 1.609) | floor}"
  • Mapeamento: aninhado: "$.room_temp_c": "${.telemetry.temp_c}"
  • Mapeamento: padrão: "$.value": "${ if .value? then .value else 0 end }"
  • Transformação de matriz: "$.list": "${ [ .arr[] | .x | tonumber ] }"

Comportamento do Modelo Inteiro versus Duplo

Esta seção resume como o tipo de esquema de modelo afeta o mapeamento e a validação do adaptador.

  • esquema: "integer"
    • Tipo: Deve ser um número JSON inteiro que seja integral. Strings como 68 ou resultados semelhantes a float 68.0 podem falhar na validação de inteiros.
    • Mapeamento: Aritmética é permitida; floor é suportado. Não há suporte para funções como toInteger e number.
    • Padrão: Use "${(.velocity_kph / 1.609) | floor}" para coagir a MPH integral. Essa validação e telemetria aprovada foi aceita como HTTP 202.
    • Intervalo: Os valores mínimo e máximo são impostos, por exemplo, 0–100
  • schema: "double"
    • Tipo: Qualquer integral ou fracionária de número JSON é aceita se estiver dentro do intervalo; nenhum arredondamento automático executado pela plataforma.
    • Mapeamento: Você pode preservar a precisão fracionária, por exemplo, "${.velocity_kph / 1.609}", ou também aplicar floor para produzir um número inteiro de MPH armazenado como duplo, por exemplo: 68.0
    • Intervalo: Se definido, mínimo e máximo imposto, por exemplo: 0–100

Resultados de telemetria observados

As seguintes combinações foram validadas de ponta a ponta (HTTP/1.1 202 Accepted):

  • Modelo inteiro + mapeamento de piso: "${(.velocity_kph / 1.609) | floor}" → aceito
  • Modelo duplo + mapeamento bruto: "${.velocity_kph / 1.609}" → mph fracionário aceito
  • Modelo duplo + mapeamento de piso: "${(.velocity_kph / 1.609) | floor}" → número inteiro mph aceito e armazenado como double

Exemplo de curl (placeholders)

Modelo duplo, valor bruto que é mph fracionário conforme esperado:

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

Modelo duplo, andar um número inteiro MPH, que é armazenado como duplo:

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

Resposta HTTP esperada para cada caso:

HTTP/1.1 202 Accepted
content-type: text/plain

Accepted

Downstream em considerações de APEX ou SQL

  • Com double, a velocidade fracional é preservada. Use o SQL FLOOR/ROUND para exibição de número inteiro:
    SELECT FLOOR(speed) AS speed_mph FROM ...
  • Com inteiro, verifique se o mapeamento gera valores integrais (por exemplo, via floor) para atender à validação.

Nome de usuário e cotações de autorização

No OCI IoT, o uso do nome de usuário de autenticação Básica é igual à instância external-key. Se a instância for criada com aspas incorporadas na chave externa, essas aspas se tornarão parte do nome de usuário necessário e deverão ser enviadas literalmente. Isso muitas vezes causa problemas de cotação de shell.

Melhores práticas: Crie instâncias de gêmeos digitais com chaves externas que não incluam aspas, por exemplo, american-auto Quando você precisar autenticar-se com nomes de usuário entre aspas, construa um cabeçalho Authorization: Basic ... em vez de usar -u para evitar erros de cotação.