[
  {
    "id": "tt_mqtt_tab",
    "type": "tab",
    "label": "ThermalTrace MQTT bridge",
    "disabled": false,
    "info": "Subscribe to a local Mosquitto topic and POST readings to ThermalTrace /api/ingest/mqtt. Set env THERMALTRACE_INGEST_KEY (Dashboard → Devices push key)."
  },
  {
    "id": "tt_mqtt_in",
    "type": "mqtt in",
    "z": "tt_mqtt_tab",
    "name": "Garage temp topic",
    "topic": "home/garage/temp",
    "qos": "0",
    "datatype": "auto-detect",
    "broker": "",
    "nl": false,
    "rap": true,
    "rh": 0,
    "inputs": 0,
    "x": 150,
    "y": 120,
    "wires": [["tt_mqtt_ratelimit"]]
  },
  {
    "id": "tt_mqtt_ratelimit",
    "type": "delay",
    "z": "tt_mqtt_tab",
    "name": "Max 1 POST / 60s",
    "pauseType": "rate",
    "timeout": "5",
    "timeoutUnits": "seconds",
    "rate": "1",
    "nbRateUnits": "60",
    "rateUnits": "second",
    "randomFirst": "1",
    "randomLast": "5",
    "randomUnits": "seconds",
    "drop": true,
    "allowrate": false,
    "outputs": 1,
    "x": 370,
    "y": 120,
    "wires": [["tt_mqtt_fn"]]
  },
  {
    "id": "tt_mqtt_fn",
    "type": "function",
    "z": "tt_mqtt_tab",
    "name": "Wrap MQTT envelope",
    "func": "let payload = msg.payload;\nif (typeof payload === \"string\") {\n  try { payload = JSON.parse(payload); } catch (e) { return null; }\n}\nif (typeof payload === \"number\") {\n  payload = { temp1: payload };\n}\nif (!payload || typeof payload !== \"object\") {\n  node.warn(\"ThermalTrace bridge: payload must be JSON object\");\n  return null;\n}\nmsg.headers = {\n  \"Content-Type\": \"application/json\",\n  \"X-Ingest-Key\": env.get(\"THERMALTRACE_INGEST_KEY\") || \"YOUR_DEVICE_KEY\"\n};\nmsg.payload = {\n  topic: msg.topic || \"home/garage/temp\",\n  message: payload\n};\nreturn msg;",
    "outputs": 1,
    "timeout": 0,
    "noerr": 0,
    "initialize": "",
    "finalize": "",
    "libs": [],
    "x": 590,
    "y": 120,
    "wires": [["tt_mqtt_http"]]
  },
  {
    "id": "tt_mqtt_http",
    "type": "http request",
    "z": "tt_mqtt_tab",
    "name": "POST ThermalTrace",
    "method": "POST",
    "ret": "txt",
    "paytoqs": "ignore",
    "url": "https://thermaltrace.dev/api/ingest/mqtt",
    "tls": "",
    "persist": false,
    "proxy": "",
    "insecureHTTPParser": false,
    "authType": "",
    "senderr": false,
    "headers": [],
    "x": 820,
    "y": 120,
    "wires": [["tt_mqtt_debug"]]
  },
  {
    "id": "tt_mqtt_debug",
    "type": "debug",
    "z": "tt_mqtt_tab",
    "name": "Ingest response",
    "active": true,
    "tosidebar": true,
    "console": false,
    "tostatus": true,
    "complete": "true",
    "targetType": "full",
    "statusVal": "statusCode",
    "statusType": "auto",
    "x": 1040,
    "y": 120,
    "wires": []
  },
  {
    "id": "tt_door_tab",
    "type": "tab",
    "label": "ThermalTrace door bridge",
    "disabled": false,
    "info": "Optional: mirror garage door MQTT state to ThermalTrace as kind=door"
  },
  {
    "id": "tt_door_in",
    "type": "mqtt in",
    "z": "tt_door_tab",
    "name": "Garage door topic",
    "topic": "home/garage/door",
    "qos": "0",
    "datatype": "auto-detect",
    "broker": "",
    "nl": false,
    "rap": true,
    "rh": 0,
    "inputs": 0,
    "x": 150,
    "y": 120,
    "wires": [["tt_door_fn"]]
  },
  {
    "id": "tt_door_fn",
    "type": "function",
    "z": "tt_door_tab",
    "name": "Map door bool",
    "func": "let open = msg.payload;\nif (typeof open === \"string\") {\n  const s = open.toLowerCase();\n  if (s === \"open\" || s === \"on\" || s === \"true\" || s === \"1\") open = true;\n  else if (s === \"closed\" || s === \"off\" || s === \"false\" || s === \"0\") open = false;\n  else try { open = JSON.parse(open); } catch (e) { return null; }\n}\nif (typeof open === \"object\" && open != null && \"state\" in open) {\n  open = open.state;\n}\nif (typeof open !== \"boolean\") {\n  node.warn(\"Door bridge: expected boolean/open/closed\");\n  return null;\n}\nmsg.headers = {\n  \"Content-Type\": \"application/json\",\n  \"X-Ingest-Key\": env.get(\"THERMALTRACE_INGEST_KEY\") || \"YOUR_DEVICE_KEY\"\n};\nmsg.payload = {\n  topic: msg.topic || \"home/garage/door\",\n  message: {\n    sensors: [{ key: \"garage_door\", kind: \"door\", bool: open }]\n  }\n};\nreturn msg;",
    "outputs": 1,
    "timeout": 0,
    "noerr": 0,
    "initialize": "",
    "finalize": "",
    "libs": [],
    "x": 370,
    "y": 120,
    "wires": [["tt_door_http"]]
  },
  {
    "id": "tt_door_http",
    "type": "http request",
    "z": "tt_door_tab",
    "name": "POST ThermalTrace",
    "method": "POST",
    "ret": "txt",
    "paytoqs": "ignore",
    "url": "https://thermaltrace.dev/api/ingest/mqtt",
    "tls": "",
    "persist": false,
    "proxy": "",
    "insecureHTTPParser": false,
    "authType": "",
    "senderr": false,
    "headers": [],
    "x": 600,
    "y": 120,
    "wires": [["tt_door_debug"]]
  },
  {
    "id": "tt_door_debug",
    "type": "debug",
    "z": "tt_door_tab",
    "name": "Door ingest response",
    "active": true,
    "tosidebar": true,
    "console": false,
    "tostatus": true,
    "complete": "true",
    "targetType": "full",
    "statusVal": "statusCode",
    "statusType": "auto",
    "x": 840,
    "y": 120,
    "wires": []
  }
]
