Gêmeo digital

Sincronização de Estado de Gêmeo Digital: Padrões de Estado Reportado, Desejado e Conflitos

Como manter um gêmeo digital e seu ativo físico em sincronia: estado reportado e desejado, números de versão, tratamento de conflitos, recuperação após reconexão e topics MQTT retidos, com um dispositivo Python funcional.

Diagrama de sincronização de estado de gêmeo digital com documentos de estado desejado e reportado fluindo entre uma aplicação, um broker MQTT e um dispositivo
Rode isso no seu próprio broker MQTTTenha um broker MQTT privado e gerenciado no plano Free e comece com exemplos de código prontos.
Teste grátis Exemplos de código Sem cartão de crédito

A sincronização de estado de gêmeo digital é o problema de manter de acordo aquilo em que o gêmeo acredita e aquilo que o ativo realmente é, mesmo diante de desconexões, atrasos e comandos conflitantes. Este artigo explica o modelo de estado reportado vs desejado, versionamento e tratamento de conflitos, e como implementar tudo isso com topics MQTT retidos, terminando com um dispositivo Python funcional que converge para o estado desejado.

Para o contexto mais amplo, comece pelo guia de tecnologia de gêmeo digital.

Por Que a Sincronização É Difícil

Um gêmeo só é útil se estiver certo. Na prática, vários fatores conspiram contra isso:

  • Latência: o gêmeo vê um valor alguns milissegundos ou segundos depois de ele ter mudado.
  • Desconexões: um dispositivo fica offline e volta com um estado diferente.
  • Escritores concorrentes: um operador e uma automação alteram o mesmo setpoint.
  • Ordenação: mensagens de caminhos diferentes chegam fora de ordem.
  • Falha parcial: um comando é entregue, mas a confirmação se perde.

Um bom design de sincronização não elimina esses problemas; ele os torna visíveis e recuperáveis.

O Modelo de Estado Reportado e Desejado

O padrão mais usado, popularizado por serviços de device shadow e frameworks de gêmeo como o Eclipse Ditto, divide o estado em dois documentos:

Documento Escrito por Significado
Reportado Somente o dispositivo O que o ativo realmente é neste momento
Desejado Aplicações, operadores, automações O que o ativo deve se tornar
Delta (derivado) Calculado Campos em que o desejado difere do reportado

A regra de ouro: somente o dispositivo escreve o estado reportado. Uma aplicação que quer uma bomba de uma estação de saneamento a 1.450 rpm escreve desired.speed = 1450. O dispositivo vê o delta, age e então reporta reported.speed = 1450. A interface do gêmeo mostra os dois, para que os operadores possam distinguir "solicitado" de "alcançado".

Essa separação elimina toda uma classe de bugs em que a interface mostra um valor que a máquina nunca chegou a atingir. Compare como shadows leves e gêmeos completos aplicam isso em device shadow vs gêmeo digital.

Implementando a Sincronização de Estado de Gêmeo Digital com MQTT

O MQTT se mapeia de forma limpa para esse modelo usando topics retidos:

connectmq/demo/<id>/plant1/pump7/state/reported   retained, written by device
connectmq/demo/<id>/plant1/pump7/state/desired    retained, written by apps
connectmq/demo/<id>/plant1/pump7/status           retained, last will "offline"

Como os dois topics de estado são retidos, o broker entrega a versão mais recente a qualquer um que assine:

  • Um visualizador que abre o gêmeo recebe imediatamente o estado reportado e o desejado.
  • Um dispositivo que se reconecta após uma queda recebe imediatamente o estado desejado mais recente e pode convergir, mesmo que a mudança desejada tenha sido feita enquanto ele estava offline.

Essa segunda propriedade é a chave para uma sincronização robusta. Você não precisa de um armazenamento separado de "comandos pendentes" para mudanças do tipo setpoint; o documento desejado retido é o comando pendente. Veja gêmeo digital com retained MQTT para um tutorial completo sobre estado retido do gêmeo.

Estado Desejado vs Comandos

Use o estado desejado para alvos idempotentes: setpoints, modos, configuração, liga/desliga. Aplicá-los duas vezes tem o mesmo efeito que aplicá-los uma vez. Use comandos não retidos para ações: zerar um contador, abrir uma porta por cinco segundos, iniciar uma calibração. Reter um comando de ação faria com que ele fosse repetido a cada reconexão, o que é perigoso.

Versionamento e Tratamento de Conflitos

Todo documento de estado deve conter uma versão e um timestamp:

{
  "version": 42,
  "ts": 1792152000000,
  "source": "operator:maria",
  "speed_rpm": 1450,
  "mode": "AUTO"
}

Use-os para tratar conflitos:

  1. Versões monotônicas no estado desejado. Os escritores leem o documento desejado atual, incrementam a versão e publicam. Os consumidores ignoram qualquer mensagem desejada com versão menor que a já aplicada.
  2. O estado reportado ecoa a versão desejada aplicada. O dispositivo inclui desired_version: 42 em seu documento reportado, para que o gêmeo saiba qual solicitação foi atendida.
  3. Último escritor vence, de forma explícita. Para a maioria dos setpoints, a versão mais recente vence. Registre source para que a interface possa mostrar quem fez a mudança.
  4. Merges por campo quando necessário. Se uma aplicação é dona de speed e outra de mode, faça o merge no nível do campo em vez de substituir o documento inteiro, ou separe-os em topics retidos diferentes.
  5. Proteções no dispositivo. O dispositivo rejeita valores fora dos limites seguros e reporta a rejeição com um motivo, em vez de ignorá-los silenciosamente.

Os brokers não aplicam versões por você; essa é uma lógica dos seus publicadores e do dispositivo. As user properties do MQTT 5 são um lugar conveniente para transportar metadados de versão sem mexer no payload.

Tratando Reconexões e Estado Desatualizado

Quando um dispositivo se reconecta, siga uma sequência fixa:

  1. Conecte-se com um Last Will no topic de status.
  2. Publique online no topic de status, retido.
  3. Publique o estado reportado atual completo, retido, a partir dos sensores locais, não da memória do que foi enviado por último.
  4. Assine o topic desejado e receba o documento desejado retido.
  5. Calcule o delta, aplique-o e publique o estado reportado atualizado.

Fazer o passo 3 antes do passo 4 importa: o gêmeo primeiro conhece a verdade e depois o dispositivo converge para o alvo. Se você inverter a ordem, um visualizador pode ver por um instante um estado reportado desatualizado rotulado como atual.

No lado do gêmeo, trate o estado reportado como desatualizado quando o topic de status indicar offline. Deixe-o em cinza em vez de mostrar valores antigos como se estivessem ao vivo.

Um Dispositivo Funcional que Converge para o Estado Desejado

Este dispositivo Python usa o servidor MQTT público, feito para testes, e paho-mqtt 2.x. Substitua 9c4e pelo seu próprio prefixo aleatório.

import json
import time
import paho.mqtt.client as mqtt

BASE = "connectmq/demo/9c4e/plant1/pump7"
LIMITS = {"speed_rpm": (0, 3000)}

reported = {"version": 0, "desired_version": 0, "speed_rpm": 0, "mode": "MANUAL"}

def publish_reported(client):
    reported["version"] += 1
    reported["ts"] = int(time.time() * 1000)
    client.publish(f"{BASE}/state/reported", json.dumps(reported), qos=1, retain=True)

def on_connect(client, userdata, flags, reason_code, properties):
    client.publish(f"{BASE}/status", "online", qos=1, retain=True)
    publish_reported(client)
    client.subscribe(f"{BASE}/state/desired", qos=1)

def on_message(client, userdata, msg):
    if not msg.payload:
        return
    desired = json.loads(msg.payload)
    if desired.get("version", 0) <= reported["desired_version"]:
        return
    errors = []
    for key, value in desired.items():
        if key in ("version", "ts", "source"):
            continue
        low, high = LIMITS.get(key, (None, None))
        if low is not None and not (low <= value <= high):
            errors.append(f"{key} out of range")
            continue
        reported[key] = value
    reported["desired_version"] = desired["version"]
    reported["errors"] = errors
    publish_reported(client)

client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id="pump7-9c4e")
client.will_set(f"{BASE}/status", "offline", qos=1, retain=True)
client.on_connect = on_connect
client.on_message = on_message
client.connect("broker.connectmq.com", 1883)
client.loop_forever()

Defina um estado desejado em outro terminal:

mosquitto_pub -h broker.connectmq.com -p 1883 -i pump7-ops-9c4e -q 1 -r \
  -t "connectmq/demo/9c4e/plant1/pump7/state/desired" \
  -m '{"version": 1, "source": "operator", "speed_rpm": 1450, "mode": "AUTO"}'

Pare o dispositivo, publique a versão 2 com uma nova velocidade e inicie o dispositivo novamente. Ele recebe o documento desejado retido ao assinar e converge imediatamente. Acompanhe tudo com mosquitto_sub -t "connectmq/demo/9c4e/plant1/pump7/#" -v.

Mostrando o Estado de Sincronização na Interface do Gêmeo

A lógica de sincronização é só metade do trabalho; os operadores precisam enxergá-la. Uma boa interface de gêmeo torna o estado de sincronização explícito em vez de escondê-lo:

  • Pendente: o desejado difere do reportado e o dispositivo está online. Mostre o valor-alvo ao lado do real, por exemplo "1.450 rpm solicitado, 1.200 rpm real".
  • Aplicado: o reportado ecoa a versão desejada mais recente. Mostre apenas o valor real.
  • Rejeitado: o dispositivo reportou um erro para aquela versão. Mostre o motivo e mantenha o valor anterior.
  • Desatualizado: o topic de status indica offline. Deixe os valores em cinza e mostre quando foram atualizados pela última vez.

Acrescente também um timeout. Se uma mudança desejada ficar pendente por mais tempo do que o dispositivo normalmente precisa, sinalize-a para que alguém investigue, em vez de presumir que a máquina obedeceu. Esses estados decorrem diretamente dos documentos reportado e desejado versionados descritos acima, então não exigem infraestrutura extra.

Checklist de Sincronização de Estado

  1. Estado reportado escrito apenas pelo dispositivo, estado desejado apenas pelas aplicações.
  2. Ambos os documentos retidos, com versão e timestamp.
  3. O reportado ecoa a última versão desejada aplicada.
  4. Ações enviadas como comandos não retidos, com confirmações.
  5. Last Will em um topic de status retido.
  6. O dispositivo publica o reportado antes de aplicar o desejado na reconexão.
  7. O dispositivo valida limites e reporta rejeições.
  8. A interface mostra desejado vs reportado e marca gêmeos offline como desatualizados.

Sincronize Seus Gêmeos Digitais no ConnectMQ

O ConnectMQ é um broker MQTT gerenciado com suporte a retained messages, Last Will, QoS de 0 a 2 conforme o plano e recursos do MQTT 5 como user properties, expiração de mensagens e expiração de sessão. O explorador de topics ao vivo do portal facilita inspecionar os documentos reportado e desejado lado a lado. Sua lógica de sincronização fica nos dispositivos e aplicações, conectados como clientes MQTT comuns, enquanto o broker MQTT na nuvem cuida da entrega. Comece grátis no plano Serverless em app.connectmq.com, sem cartão de crédito.

Perguntas frequentes

Qual é a diferença entre estado reportado e desejado?

O estado reportado é o que o dispositivo realmente é, escrito apenas pelo dispositivo. O estado desejado é o que as aplicações querem que ele se torne. A diferença entre os dois é o delta sobre o qual o dispositivo precisa agir.

Por que usar retained messages para o estado desejado?

Um documento desejado retido é entregue ao dispositivo assim que ele assina, então mudanças feitas enquanto ele estava offline são aplicadas automaticamente na reconexão.

Como resolver atualizações conflitantes em um gêmeo digital?

Acrescente versões e origens aos documentos desejados, ignore versões mais antigas, faça merge por campo quando a responsabilidade for diferente e deixe o dispositivo validar e reportar o que realmente aplicou.

Comandos devem ser retidos?

Não. Retenha apenas o estado desejado idempotente. Comandos de ação devem ser não retidos, de preferência com expiração de mensagem e uma confirmação.

Artigos relacionados

Todos os artigos →

Seu broker MQTT privado está a poucos cliques de distância

Crie uma conta, receba as credenciais do seu broker e conecte seu primeiro dispositivo — sem servidores para instalar, atualizar ou escalar.