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:
- 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.
- O estado reportado ecoa a versão desejada aplicada. O dispositivo inclui
desired_version: 42em seu documento reportado, para que o gêmeo saiba qual solicitação foi atendida. - Último escritor vence, de forma explícita. Para a maioria dos setpoints, a versão mais recente vence. Registre
sourcepara que a interface possa mostrar quem fez a mudança. - Merges por campo quando necessário. Se uma aplicação é dona de
speede outra demode, faça o merge no nível do campo em vez de substituir o documento inteiro, ou separe-os em topics retidos diferentes. - 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:
- Conecte-se com um Last Will no topic de status.
- Publique
onlineno topic de status, retido. - Publique o estado reportado atual completo, retido, a partir dos sensores locais, não da memória do que foi enviado por último.
- Assine o topic desejado e receba o documento desejado retido.
- 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
- Estado reportado escrito apenas pelo dispositivo, estado desejado apenas pelas aplicações.
- Ambos os documentos retidos, com versão e timestamp.
- O reportado ecoa a última versão desejada aplicada.
- Ações enviadas como comandos não retidos, com confirmações.
- Last Will em um topic de status retido.
- O dispositivo publica o reportado antes de aplicar o desejado na reconexão.
- O dispositivo valida limites e reporta rejeições.
- 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.



