[{"data":1,"prerenderedAt":420},["ShallowReactive",2],{"doc-\u002Fhardware\u002Fdiy-esp32-mqtt":3},{"id":4,"title":5,"body":6,"description":410,"edit":411,"extension":412,"meta":413,"navigation":414,"path":415,"seo":416,"stem":417,"vertical":411,"weight":418,"__hash__":419},"content\u002Fhardware\u002Fdiy-esp32-mqtt.md","DIY ESP32 + MQTT",{"type":7,"value":8,"toc":401},"minimark",[9,19,29,37,42,53,56,60,66,84,88,95,190,194,227,231,238,345,351,355,386,390,397],[10,11,12,13,18],"p",{},"Same hardware as ",[14,15,17],"a",{"href":16},"\u002Fhardware\u002Fdiy-esp32","DIY ESP32 + ESPHome",", different\ntransport. Use this when:",[20,21,22,26],"ul",{},[23,24,25],"li",{},"The site does not allow outbound HTTPS but does allow a single TCP\nconnection to a local MQTT broker.",[23,27,28],{},"You already operate an MQTT broker (Home Assistant, EMQX cluster, plant\nbus) and want OpenSense to subscribe to it as a tenant.",[10,30,31,32,36],{},"OpenSense exposes a ",[33,34,35],"strong",{},"multi-tenant MQTT bridge",". Your devices publish to\nyour broker (yours, your premises); OpenSense subscribes to a topic prefix\nvia an outbound bridge from our broker side.",[38,39,41],"h2",{"id":40},"topology","Topology",[43,44,49],"pre",{"className":45,"code":47,"language":48},[46],"language-text","[ESP32] ──mqtt──▶ [your EMQX, local] ──bridge──▶ [OpenSense MQTT bridge] ──ingest──▶ [storage]\n","text",[50,51,47],"code",{"__ignoreMap":52},"",[10,54,55],{},"The bridge from your broker to ours uses an MQTT 5 outbound bridge with\nmTLS. Both EMQX and Mosquitto (1.6+) support this out of the box.",[38,57,59],{"id":58},"provision-in-opensense","Provision in OpenSense",[10,61,62,65],{},[50,63,64],{},"+ ADD DEVICE → MQTT → Bridge",". The dialog shows:",[20,67,68,75,78],{},[23,69,70,71,74],{},"A bridge URL: ",[50,72,73],{},"mqtts:\u002F\u002Fmqtt.opensense.murzin.digital:8883",".",[23,76,77],{},"A client certificate + private key for your broker. Download the bundle.",[23,79,80,81,74],{},"A topic prefix you must publish under, e.g. ",[50,82,83],{},"opensense\u002Facc_4f3c\u002F",[38,85,87],{"id":86},"emqx-bridge-config","EMQX bridge config",[10,89,90,91,94],{},"In EMQX dashboard, ",[50,92,93],{},"Integration → Bridges → MQTT Bridge → Outbound",":",[96,97,98,111],"table",{},[99,100,101],"thead",{},[102,103,104,108],"tr",{},[105,106,107],"th",{},"Field",[105,109,110],{},"Value",[112,113,114,125,135,143,151,159,167,175],"tbody",{},[102,115,116,120],{},[117,118,119],"td",{},"Server",[117,121,122],{},[50,123,124],{},"mqtt.opensense.murzin.digital:8883",[102,126,127,130],{},[117,128,129],{},"Client ID prefix",[117,131,132],{},[50,133,134],{},"os-{acc_id}-",[102,136,137,140],{},[117,138,139],{},"Username",[117,141,142],{},"(leave empty)",[102,144,145,148],{},[117,146,147],{},"TLS",[117,149,150],{},"enabled",[102,152,153,156],{},[117,154,155],{},"Client cert",[117,157,158],{},"the cert from the bundle",[102,160,161,164],{},[117,162,163],{},"Client key",[117,165,166],{},"the key from the bundle",[102,168,169,172],{},[117,170,171],{},"CA",[117,173,174],{},"the CA from the bundle",[102,176,177,180],{},[117,178,179],{},"Topic mapping",[117,181,182,183,186,187],{},"local: ",[50,184,185],{},"local\u002Fsensors\u002F#"," → remote: ",[50,188,189],{},"opensense\u002Facc_4f3c\u002F#",[38,191,193],{"id":192},"mosquitto-bridge-config","Mosquitto bridge config",[195,196,198,201,204,207,224],"cmd",{"label":197},"$ \u002Fetc\u002Fmosquitto\u002Fconf.d\u002Fopensense-bridge.conf",[10,199,200],{},"connection opensense\naddress mqtt.opensense.murzin.digital:8883\nbridge_protocol_version mqttv50",[10,202,203],{},"bridge_cafile  \u002Fetc\u002Fmosquitto\u002Fopensense\u002Fca.pem\nbridge_certfile \u002Fetc\u002Fmosquitto\u002Fopensense\u002Fclient.pem\nbridge_keyfile  \u002Fetc\u002Fmosquitto\u002Fopensense\u002Fclient.key\nbridge_insecure false",[10,205,206],{},"cleansession true\nnotifications false\nstart_type automatic\nrestart_timeout 10",[208,209,211,212],"h1",{"id":210},"localsensors-opensenseacc_4f3c","local\u002Fsensors\u002F",[213,214,215,216],"device",{},"\u002F",[217,218,219,220],"channel",{}," → opensense\u002Facc_4f3c\u002F",[213,221,215,222],{},[217,223],{},[10,225,226],{},"topic # out 1 local\u002Fsensors\u002F opensense\u002Facc_4f3c\u002F",[38,228,230],{"id":229},"device-side-payload","Device-side payload",[10,232,233,234,237],{},"Each device publishes a JSON message per measurement cycle to\n",[50,235,236],{},"local\u002Fsensors\u002F\u003Cdevice_id>"," (with retain off, QoS 1):",[43,239,243],{"className":240,"code":241,"language":242,"meta":52,"style":52},"language-json shiki shiki-themes github-dark github-dark","{\n  \"ts\": \"2026-05-17T08:22:00Z\",\n  \"measurements\": [\n    { \"type\": \"temperature\", \"value\": 4.2 },\n    { \"type\": \"humidity\",    \"value\": 64.1 }\n  ]\n}\n","json",[50,244,245,254,271,280,308,333,339],{"__ignoreMap":52},[246,247,250],"span",{"class":248,"line":249},"line",1,[246,251,253],{"class":252},"suv1-","{\n",[246,255,257,261,264,268],{"class":248,"line":256},2,[246,258,260],{"class":259},"s8ozJ","  \"ts\"",[246,262,263],{"class":252},": ",[246,265,267],{"class":266},"s4wv1","\"2026-05-17T08:22:00Z\"",[246,269,270],{"class":252},",\n",[246,272,274,277],{"class":248,"line":273},3,[246,275,276],{"class":259},"  \"measurements\"",[246,278,279],{"class":252},": [\n",[246,281,283,286,289,291,294,297,300,302,305],{"class":248,"line":282},4,[246,284,285],{"class":252},"    { ",[246,287,288],{"class":259},"\"type\"",[246,290,263],{"class":252},[246,292,293],{"class":266},"\"temperature\"",[246,295,296],{"class":252},", ",[246,298,299],{"class":259},"\"value\"",[246,301,263],{"class":252},[246,303,304],{"class":259},"4.2",[246,306,307],{"class":252}," },\n",[246,309,311,313,315,317,320,323,325,327,330],{"class":248,"line":310},5,[246,312,285],{"class":252},[246,314,288],{"class":259},[246,316,263],{"class":252},[246,318,319],{"class":266},"\"humidity\"",[246,321,322],{"class":252},",    ",[246,324,299],{"class":259},[246,326,263],{"class":252},[246,328,329],{"class":259},"64.1",[246,331,332],{"class":252}," }\n",[246,334,336],{"class":248,"line":335},6,[246,337,338],{"class":252},"  ]\n",[246,340,342],{"class":248,"line":341},7,[246,343,344],{"class":252},"}\n",[10,346,347,350],{},[50,348,349],{},"ts"," is optional; if omitted, OpenSense stamps with the broker-receive\ntime, which is fine for cold-chain but adds ~200 ms jitter.",[38,352,354],{"id":353},"qos-retain-lwt","QoS, retain, LWT",[20,356,357,363,369],{},[23,358,359,362],{},[33,360,361],{},"QoS 1."," QoS 0 loses messages on bridge reconnect; QoS 2 is overhead\nthe ingest pipeline does not need.",[23,364,365,368],{},[33,366,367],{},"Retain: off."," OpenSense ingests measurements as a stream, not state.\nRetained messages would re-fire on each broker restart and create\nduplicate rows (which the idempotency layer would then deduplicate, but\ninefficiently).",[23,370,371,374,375,378,379,382,383,74],{},[33,372,373],{},"Last-will testament."," Set on your device: topic\n",[50,376,377],{},"local\u002Fsensors\u002F\u003Cdevice_id>\u002Flwt",", payload ",[50,380,381],{},"{\"status\":\"offline\"}",",\nQoS 1, retain on. The bridge forwards this; OpenSense flips the\ndevice's connection state to ",[50,384,385],{},"offline",[38,387,389],{"id":388},"why-not-direct-device-to-opensense-mqtt","Why not direct device-to-OpenSense MQTT?",[10,391,392,393,396],{},"We support direct device connections at the ",[33,394,395],{},"Team"," tier, not Solo. The\nreason is operational: per-device certs, per-device ACLs, and bandwidth\naccounting against a multi-tenant broker are not free, and Solo is priced\nto assume an HTTP-first integration. If your devices number in the\nhundreds, contact us before scaling.",[398,399,400],"style",{},"html pre.shiki code .suv1-, html code.shiki .suv1-{--shiki-default:#E1E4E8;--shiki-dark:#E1E4E8}html pre.shiki code .s8ozJ, html code.shiki .s8ozJ{--shiki-default:#79B8FF;--shiki-dark:#79B8FF}html pre.shiki code .s4wv1, html code.shiki .s4wv1{--shiki-default:#9ECBFF;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":52,"searchDepth":273,"depth":273,"links":402},[403,404,405,406,407,408,409],{"id":40,"depth":256,"text":41},{"id":58,"depth":256,"text":59},{"id":86,"depth":256,"text":87},{"id":192,"depth":256,"text":193},{"id":229,"depth":256,"text":230},{"id":353,"depth":256,"text":354},{"id":388,"depth":256,"text":389},"Bring-your-own broker (EMQX \u002F Mosquitto)",null,"md",{},true,"\u002Fhardware\u002Fdiy-esp32-mqtt",{"title":5,"description":410},"hardware\u002Fdiy-esp32-mqtt",160,"_u8atbRFVHNWM6BCC9ofMv_FblrySc1H0S_KbdPEwBQ",1779022953859]