The MQTT bridge lets any device that can publish to a broker ask DecisionNode questions without an SDK of its own: an ESP32, an industrial gateway, a Node-RED flow or a camera that can only publish JPEG bytes. A device publishes a request on a topic; the bridge sends it to the API and publishes the answer on a response topic.
device ── decisionnode/request/<id> ──▶ broker ──▶ decision-node-mqtt ──▶ DecisionNode API
device ◀── decisionnode/response/<id> ── broker ◀──────────┘Install#
The bridge is one static binary or a container image. Every release has an archive for Linux (x86-64, arm64, armv7, armv6), macOS (x86-64, arm64) and Windows (x86-64, arm64), with checksums.txt and a keyless signature of it. The image is for linux/amd64, linux/arm64 and linux/arm/v7, tagged latest and with each version.
docker pull ghcr.io/bynn-intelligence/decision-node-mqtt:latestVerify the checksums' signature with cosign before you trust an archive. Building from source needs Go 1.26 or later.
cosign verify-blob --certificate checksums.txt.pem --signature checksums.txt.sig \
--certificate-identity-regexp 'https://github.com/Bynn-Intelligence/decision-node-mqtt/' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com checksums.txtQuickstart with mosquitto#
You need an API key and Docker. The repository's compose file starts a mosquitto broker on port 1883 and the bridge beside it; then listen for answers in one terminal and ask a question:
cd examples
DECISIONNODE_API_KEY=dn_live_... docker compose up --buildmosquitto_sub -h localhost -t 'decisionnode/response/#' -v &
mosquitto_pub -h localhost -t decisionnode/request/sensor-7 -m '{
"id": "r-1",
"state": {"temperature_c": 81, "trend": "rising", "limit_c": 85},
"questions": {
"overheating": {"type": "truth", "instructions": "Will this machine overheat within the hour?"}
}
}'The answer arrives on decisionnode/: id echoes the request's, request_id is the API's, and decision is the API's response body exactly as it was sent. A request that could not be answered gets error instead, with the API's status, error_type and message, or the bridge's own error_type (payload_too_large, rate_limited, overloaded, invalid_payload, no_profile, timeout_error, connection_error) when the API was not reached.
Configuration#
- Topics. Requests arrive on
mqtt.subscribe,decisionnode/request/+by default; answers go to<response_prefix>/<id>,decisionnode/response/<id>by default. An MQTT 5 client may set its own response topic and correlation data, used when the topic matchesmqtt.response_topics. An MQTT 3.1.1 client puts anidin the JSON request. - Requests are a JSON request (
id,state,questions, up to 16images,model,boosters,fallback,profile; an unknown field is refused) or the bytes of a JPEG, PNG or WebP image. - Profiles are named requests a topic's payloads fill in, so a camera that only publishes JPEG bytes is answered with its topic's questions.
- Settings come from a YAML file (
-config, orDN_MQTT_CONFIG) and the environment, the environment winning:DN_MQTT_URL(mqtt://,mqtts://,ws://,wss://and the like),DN_MQTT_CLIENT_ID,DN_MQTT_USERNAME,DN_MQTT_PASSWORD,DECISIONNODE_MODEL,DN_MQTT_HTTP_LISTEN.config.example.yamllists every setting with its default. - TLS URLs connect over TLS 1.2 or later and verify the broker's certificate; certificate checks cannot be turned off.
- Health and metrics on
:8080:/healthzand Prometheus/metrics. Logs are JSON lines on stderr; payloads are never logged.
profiles:
gate:
state: Camera 2 at the north gate of the loading yard.
image_id: gate
questions:
vehicle:
type: truth
instructions: Is a vehicle waiting at the gate?
topics:
- filter: decisionnode/request/gate-north
profile: gateThe API key#
The key comes only from DECISIONNODE_API_KEY or a file (api.key_file), and the broker password only from DN_MQTT_PASSWORD or mqtt.password_file. The YAML has no key for either, and an unknown key is an error, so a key pasted into the file is refused rather than used. Neither is ever read from a topic or written to a log. Devices never hold the key: they publish to the broker, and the bridge alone calls the API.
The source, the full README and the issues are in the repository. MIT licence.