Skip to content
Go back

Eclipse Ditto + HiveMQ (MQTT) - Build a Tiny LED Digital Twin

Python code snippets and a docker environment are linked at the end of the post!!

We’ll wire up a digital twin for a tiny device called dt-led and a logical controller called dt-controller. The controller tells the LED device: turn ON/OFF. The device acknowledges and updates its twin state (the “source of truth” in Ditto).

You’ll meet four core Ditto ideas along the way:

  1. Policies — Who can do what on which resources (things, messages, policies).
  2. Things — Your twins (dt-led and dt-controller) with attributes and features.
  3. Connections — How Ditto bridges to external systems (here: HiveMQ MQTT broker).
  4. Ditto Protocol — The JSON envelope Ditto uses on MQTT for commands/events.

The high‑level architecture

Key takeaways:


Ditto Essentials in 90 seconds


Identity & Policy — unlocking the right doors

Our policy grants just-enough permissions to the two subjects we care about:

Here is a minimal, friendly version of the policy document you’d PUT to Ditto:

{
  "entries": {
    "owner": {
      "subjects": { "nginx:ditto": { "type": "nginx basic auth user" } },
      "resources": {
        "thing:/": { "grant": ["READ", "WRITE"], "revoke": [] },
        "policy:/": { "grant": ["READ", "WRITE"], "revoke": [] },
        "message:/": { "grant": ["READ", "WRITE"], "revoke": [] }
      }
    },
    "connection": {
      "subjects": {
        "connection:hivemq-mqtt": { "type": "Connection to HiveMQ MQTT broker" }
      },
      "resources": {
        "thing:/": { "grant": ["READ", "WRITE"], "revoke": [] },
        "message:/": { "grant": ["READ", "WRITE"], "revoke": [] }
      }
    }
  }
}

Why give the connection thing:/ + message:/? - So Ditto is allowed to publish live messages to devices, and consume device updates to modify the twin.


Modeling the twins (Things)

We’ll create two things:

  1. org.eclipse.ditto:dt-led
    • attributes.name = "LED Device"
    • features.status_led.properties.status = "OFF" (initial state)
  2. org.eclipse.ditto:dt-controller
    • attributes.name = "LED Controller"

Conceptually, the LED thing holds state. The Controller is stateless and just sends commands.

You’d upsert each Thing with an HTTP PUT to /api/2/things/<thingId>, including the policyId that points at the policy we created.


The MQTT connection (Ditto ↔ HiveMQ)

Connection settings worth remembering:

Sources (consuming from device → Ditto)

{
  "sources": [
    {
      "addresses": ["devices/#"],
      "authorizationContext": ["connection:hivemq-mqtt"],
      "qos": 1,
      "payloadMappings": [{ "mappingId": "ditto" }]
    }
  ]
}

Any publish to devices/... (e.g., devices/org.eclipse.ditto:dt-led/twin) will be ingested by Ditto and interpreted as a Ditto Protocol message.

Targets (publishing from Ditto → device)

{
  "targets": [
    {
      "address": "devices/{{ thing:id }}",
      "topics": ["_/_/things/live/messages", "_/_/things/twin/events"],
      "authorizationContext": ["connection:hivemq-mqtt"],
      "qos": 1,
      "payloadMappings": [{ "mappingId": "ditto" }]
    }
  ]
}

The message flow - end‑to‑end

Controller → LED (HTTP inbox message)

The controller tells the LED to toggle via Ditto’s HTTP inbox:

curl -u ditto:ditto -H "Content-Type: application/json" -X POST   'http://localhost:8080/api/2/things/org.eclipse.ditto:dt-led/inbox/messages/toggle_led?timeout=0'   -d '{"status":"ON","source_thing":"org.eclipse.ditto:dt-controller"}'

Ditto routes this as a live message to MQTT target devices/org.eclipse.ditto:dt-led. The payload (what the device sees) is a Ditto Protocol envelope like:

{
  "topic": "org.eclipse.ditto/dt-led/things/live/messages/toggle_led",
  "headers": {
    "content-type": "application/json",
    "correlation-id": "..."
  },
  "path": "/",
  "value": { "status": "ON", "source_thing": "org.eclipse.ditto:dt-controller" }
}

Note the internal topic is a Ditto Protocol topic (with / after the namespace), while the MQTT broker topic you subscribe to is devices/org.eclipse.ditto:dt-led.

LED device → Ditto (twin update)

Once the device sets its real LED, it reports state back to the twin. Two equivalent options:

A) HTTP to the twin’s feature path

curl -u ditto:ditto -H "Content-Type: application/json" -X PUT   'http://localhost:8080/api/2/things/org.eclipse.ditto:dt-led/features/status_led/properties'   -d '{"status":"ON"}'

B) MQTT publish (Ditto Protocol)

{
  "topic": "org.eclipse.ditto/dt-led/things/twin/commands/modify",
  "headers": {
    "response-required": false,
    "content-type": "application/vnd.eclipse.ditto+json",
    "correlation-id": "led-{timestamp}"
  },
  "path": "/features/status_led/properties",
  "value": { "status": "ON" }
}

Ditto applies the update; anyone watching the twin gets events. With our connection’s targets.topics set to include twin/events, Ditto can also publish those events out to MQTT for downstream consumers or dashboards.


Observability & sanity checks


Troubleshooting

SymptomLikely CauseWhat to check
401 Unauthorized on HTTPWrong basic‑authUse ditto:ditto (demo) or your creds; confirm you hit the correct /api/2 base path
403 from DittoPolicy denies the actionEnsure policy grants READ/WRITE on thing:/ and message:/ for both nginx:ditto and connection:hivemq-mqtt
Ditto connection stuck CLOSEDBroker not reachable or wrong uriIs the broker at tcp://hivemq:1883 (inside Docker) or tcp://localhost:1883 (host)? Firewall?
Device doesn’t get commandsWrong MQTT topicDevice must subscribe to devices/{{ thing:id }} (e.g., devices/org.eclipse.ditto:dt-led)
Device publishes but Ditto ignores itMissing ditto payload mappingEnsure both sources and targets include payloadMappings: [{{"mappingId":"ditto"}}]
Twin doesn’t changeWrong Ditto Protocol pathFor our LED: /features/status_led/properties is the path to modify
No twin events on MQTTTargets topics missing twin/eventsAdd "_/_/things/twin/events" to the connection targets.topics

Production hardening - beyond the demo


Where to go next


Code Snippets

Add the following HiveMQ service to the Docker Compose file in the Ditto repository on GitHub:

# HiveMQ MQTT broker (Community Edition)
hivemq:
  image: hivemq/hivemq-ce:latest
  deploy:
    resources:
      limits:
        memory: 512m
  networks:
    default:
      aliases: [hivemq]
  ports:
    # - 8181:8080
    - 1883:1883 # MQTT
  logging:
    options: { max-size: 50m }
# If you prefer HiveMQ Enterprise Trial with Control Center UI, use:
# image: hivemq/hivemq4:latest
# ports:
#   - "1883:1883"   # MQTT
#   - "8082:8080"   # Control Center UI available at http://localhost:8082docker-compose.yaml

Python examples are available in this GitHub Gist https://gist.github.com/anshdavid/5092b13b74fa8fb9827b9328f5013814


If this post saved you a few hours, share it or drop a ⭐ on your internal knowledge base!


Share this post on:

Previous Post
Indispensable Role of Context Engineering - Part 1
Next Post
Eclipse Ditto: Enabling an Interoperable Digital Twin Layer for IoT