Hallo
Also über Mqtt läuft die Truma an über befehle
Wie bekomme ich das in Node red rein ?
Also läuft soweit sogut bei mir , wie kriege ich das jetzt aufs Touch display ? Kann ich das irgendwie ändern ?
Hier ist der Flow für die Trumanita:
[{"id":"833a195641375a45","type":"group","z":"e52f6733608c09d4","name":"Use Trumanita from Touchdiplay and App","style":{"label":true},"nodes":["d1b321787e1cdd60","fc52d518da9db319","34880553029838cc","206e636671413b08","23f10568c1554cd7","1dccde7f8a92d84f","2564e0f05785bc24","d7f1063474eff4aa","d6cd05cfa4680bbe","60b24be35c2b5c8b","ba5f43b3ac6ab0b6","22ec4f1fdf90fc36","959ec9f5640ddfec","a93cac03bea1fd3e","b81cb24b6a4ac3e6"],"x":34,"y":299,"w":1012,"h":322},{"id":"d1b321787e1cdd60","type":"mqtt in","z":"e52f6733608c09d4","g":"833a195641375a45","name":"","topic":"CI/devices/TrumaCombiHeater/control/full","qos":"2","datatype":"auto-detect","broker":"0ddb07c65cbb68bc","nl":false,"rap":true,"rh":0,"inputs":0,"x":220,"y":460,"wires":[["23f10568c1554cd7","1dccde7f8a92d84f"]]},{"id":"fc52d518da9db319","type":"debug","z":"e52f6733608c09d4","g":"833a195641375a45","name":"debug 7","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":760,"y":580,"wires":[]},{"id":"34880553029838cc","type":"inject","z":"e52f6733608c09d4","g":"833a195641375a45","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":140,"y":420,"wires":[["206e636671413b08"]]},{"id":"206e636671413b08","type":"function","z":"e52f6733608c09d4","g":"833a195641375a45","name":"set truma for touchdisplay","func":"global.set(\"truma_CI\", true)\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":370,"y":420,"wires":[[]]},{"id":"23f10568c1554cd7","type":"function","z":"e52f6733608c09d4","g":"833a195641375a45","name":"ci to trumanita mapping","func":"//global.get(\"temp1\") is hardcoded in line 116 as ambient temperature (defaults to 20)\n\n// Helper: normalize value to string in a tolerant way\nfunction s(v) {\n if (v === null || v === undefined) return \"\";\n return String(v).trim().toLowerCase();\n}\n\n// Clamp utility\nfunction clamp(n, min, max) {\n if (typeof n !== \"number\" || isNaN(n)) return min;\n return Math.max(min, Math.min(max, n));\n}\n\n// ---- 1) WATER -> boiler\nfunction mapWaterToBoiler(water) {\n const w = s(water);\n if (w === \"off\" || w === \"0\" || w === \"false\") return \"off\";\n if (w === \"eco\" || w === \"40\" || w === \"on\" || w === \"true\") return \"eco\";\n if (w === \"hot\" || w === \"60\") return \"hot\";\n // Unknown -> safe default\n return \"off\";\n}\n\n// ---- 2) ENERGY + POWERLIMIT -> energymix, mode, mode2\nfunction normEnergy(energy) {\n const e = s(energy);\n if (e === \"fuel\" || e === \"gas\" || e === \"1\") return \"gas\";\n if (e === \"electric\" || e === \"electricity\" || e === \"2\") return \"electric\";\n if (e === \"mix\" || e === \"both\" || e === \"3\") return \"mix\";\n return \"gas\"; // safe default\n}\n\nfunction normPowerLimit(pl) {\n const p = s(pl);\n if (p === \"900\" || p === \"1\") return 900;\n if (p === \"1800\" || p === \"2\") return 1800;\n const n = Number(pl);\n if (n === 900 || n === 1800) return n;\n return undefined; // unknown\n}\n\nfunction computeEnergyFields(energy, powerlimit) {\n const e = normEnergy(energy);\n const p = normPowerLimit(powerlimit);\n\n let energymix, mode, mode2;\n\n if (e === \"gas\") {\n energymix = \"gas\";\n mode = \"gas\";\n mode2 = \"gas\";\n } else if (e === \"electric\") {\n energymix = \"electric\";\n // select electric cartridge stage via mode\n mode = (p === 1800) ? \"mix2\" : \"mix1\"; // default to 900 if unknown\n mode2 = \"electric\";\n } else { // mix\n // stage depends on powerlimit\n const stage = (p === 1800) ? \"mix2\" : \"mix1\";\n energymix = stage;\n mode = stage;\n // follow your examples: mode2 'gas' during mix\n mode2 = \"gas\";\n }\n\n return { energymix, mode, mode2 };\n}\n\n// ---- 3) AIR -> heater, temp, fan (with anti-cooling + deadband)\nfunction mapAir(payload, lastTemp) {\n // -------- Tunables --------\n const DEAD_BAND = 0.3; // °C; prevents chatter around setpoint\n const STOP_WHEN_ABOVE = true; // fan=0 if ambient >= desired-DEAD_BAND\n const FORCE_HEATER_OFF = false; // also force heater=\"off\" when above\n // --------------------------\n\n const mode = s(payload.air_mode); // \"normal\"/\"heat\" or \"auto\"/\"automatic\"\n const at = payload.air_target;\n\n // Determine heater on/off and desired temp\n let heater = \"off\";\n let temp; // may remain undefined → we'll fill later\n\n const offish = (v) => [\"off\", \"0\", \"false\"].includes(s(v));\n const onish = (v) => [\"on\", \"true\"].includes(s(v));\n const toNum = (v) => (typeof v === \"number\") ? v : (v != null && /^\\d+$/.test(String(v).trim()) ? Number(v) : NaN);\n\n if (offish(at)) {\n heater = \"off\";\n } else if (onish(at)) {\n heater = \"on\";\n temp = 20;\n } else {\n const maybeNum = toNum(at);\n if (!isNaN(maybeNum)) {\n heater = \"on\";\n if (mode === \"auto\" || mode === \"automatic\") {\n temp = clamp(maybeNum, 18, 25); // auto constraints\n } else {\n temp = clamp(maybeNum, 5, 29); // normal constraints (target max 29)\n }\n } else {\n // Unknown → be safe but still provide a temp\n heater = \"off\";\n temp = (typeof lastTemp === \"number\") ? lastTemp : 20;\n }\n }\n\n // Desired setpoint must always be present\n const desired = (typeof temp === \"number\")\n ? temp\n : (typeof lastTemp === \"number\" ? lastTemp : 20);\n\n // Robust ambient extraction with NaN fallback\n const ambientRaw = global.get(\"temp1\");\n const ambientNum = Number(ambientRaw);\n const ambient = (typeof ambientRaw === \"number\" && !isNaN(ambientRaw))\n ? ambientRaw\n : (!isNaN(ambientNum) ? ambientNum : 20);\n\n // Are we below the setpoint (with deadband)?\n const belowSetpoint = (desired - ambient) > DEAD_BAND;\n\n // FAN CONTROL STRATEGY (dynamic numeric fan + safety rules)\n // - If heater OFF → fan = 0\n // - If at/above setpoint → fan = 0 (and optionally heater OFF)\n // - Else: fan = round((desired - ambient)/1.5), clamped 0–10\n // - AUTO/automatic → min fan 5 (only when below setpoint)\n let fan = 0;\n\n if (heater === \"off\") {\n fan = 0;\n } else if (STOP_WHEN_ABOVE && !belowSetpoint) {\n fan = 0;\n if (FORCE_HEATER_OFF) heater = \"off\";\n } else {\n const delta = Math.max(desired - ambient, 0); // only positive drive (no cooling)\n fan = Math.round(clamp(delta / 1.5, 0, 10));\n if ((mode === \"auto\" || mode === \"automatic\") && belowSetpoint)\n fan = Math.max(fan, 5);\n }\n\n return { heater, temp: desired, fan };\n}\n\n// ---------------------- MAIN ----------------------\nconst src = msg.payload || {};\nconst last = flow.get(\"last_truma_config\") || {};\n\n// Build out step by step\nconst out = {};\n\n// boiler\nout.boiler = mapWaterToBoiler(src.water);\n\n// energy-derived fields\nconst ef = computeEnergyFields(src.energy, src.powerlimit);\nout.energymix = ef.energymix;\nout.mode = ef.mode;\nout.mode2 = ef.mode2;\n\n// air-derived fields (pass last temp so we always have one)\nconst air = mapAir(src, (typeof last.temp === \"number\" ? last.temp : undefined));\nout.heater = air.heater;\nout.temp = air.temp; // ALWAYS set\nout.fan = air.fan;\n\n// ---- Finalize: guarantee full schema & sane defaults\n// If any field slipped through undefined, fill from last or default.\nfunction pick(cur, prev, def) {\n return (cur !== undefined && cur !== null) ? cur\n : (prev !== undefined && prev !== null) ? prev\n : def;\n}\n\nout.boiler = pick(out.boiler, last.boiler, \"off\");\nout.energymix = pick(out.energymix, last.energymix, \"gas\");\nout.mode = pick(out.mode, last.mode, \"gas\");\nout.mode2 = pick(out.mode2, last.mode2, \"gas\");\nout.heater = pick(out.heater, last.heater, \"off\");\nout.fan = pick(out.fan, last.fan, 0);\n\n// temp: keep within 5–29 to satisfy target spec (even if heater is off)\nout.temp = pick(out.temp, last.temp, 20);\nout.temp = clamp(out.temp, 5, 29);\n\n// Persist last full config for next time\nflow.set(\"last_truma_config\", out);\n\n// Emit\nmsg.payload = out;\nmsg.topic = \"truma/control/truma_config\";\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":550,"y":460,"wires":[["fc52d518da9db319","2564e0f05785bc24"]]},{"id":"1dccde7f8a92d84f","type":"debug","z":"e52f6733608c09d4","g":"833a195641375a45","name":"debug 8","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":500,"y":580,"wires":[]},{"id":"2564e0f05785bc24","type":"mqtt out","z":"e52f6733608c09d4","g":"833a195641375a45","name":"","topic":"","qos":"","retain":"","respTopic":"","contentType":"","userProps":"","correl":"","expiry":"","broker":"0ddb07c65cbb68bc","x":770,"y":460,"wires":[]},{"id":"d7f1063474eff4aa","type":"inject","z":"e52f6733608c09d4","g":"833a195641375a45","name":"heater on","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"truma/control/heater","payload":"on","payloadType":"str","x":580,"y":340,"wires":[["2564e0f05785bc24","60b24be35c2b5c8b"]]},{"id":"d6cd05cfa4680bbe","type":"inject","z":"e52f6733608c09d4","g":"833a195641375a45","name":"heater off","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"truma/control/heater","payload":"off","payloadType":"str","x":580,"y":380,"wires":[["2564e0f05785bc24"]]},{"id":"60b24be35c2b5c8b","type":"delay","z":"e52f6733608c09d4","g":"833a195641375a45","name":"20ms delay","pauseType":"delay","timeout":"20","timeoutUnits":"milliseconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"allowrate":false,"outputs":1,"x":750,"y":340,"wires":[["ba5f43b3ac6ab0b6"]]},{"id":"ba5f43b3ac6ab0b6","type":"function","z":"e52f6733608c09d4","g":"833a195641375a45","name":"set temp 25","func":"msg.payload = 25\nmsg.topic = \"truma/control/temp\"\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":910,"y":340,"wires":[["2564e0f05785bc24"]]},{"id":"22ec4f1fdf90fc36","type":"comment","z":"e52f6733608c09d4","g":"833a195641375a45","name":"↑ \"on\" command and \"temp\" needs to be set \\n seperately when not using the \"full\" topic","info":"","x":850,"y":400,"wires":[]},{"id":"959ec9f5640ddfec","type":"comment","z":"e52f6733608c09d4","g":"833a195641375a45","name":"↑ Truma Ci full command \\n mapped to trumanita commands","info":"","x":572,"y":512,"wires":[]},{"id":"a93cac03bea1fd3e","type":"comment","z":"e52f6733608c09d4","g":"833a195641375a45","name":"needed to activate Truma widget \\n for touchdisplay and app ↓ ","info":"","x":350,"y":360,"wires":[]},{"id":"b81cb24b6a4ac3e6","type":"comment","z":"e52f6733608c09d4","g":"833a195641375a45","name":"↑ Receives commands from Truma CI controller","info":"","x":240,"y":500,"wires":[]},{"id":"0ddb07c65cbb68bc","type":"mqtt-broker","name":"","broker":"http://localhost","port":"1883","clientid":"","autoConnect":true,"usetls":false,"protocolVersion":"4","keepalive":"60","cleansession":true,"birthTopic":"","birthQos":"0","birthPayload":"","birthMsg":{},"closeTopic":"","closeQos":"0","closePayload":"","closeMsg":{},"willTopic":"","willQos":"0","willPayload":"","willMsg":{},"sessionExpiry":""}]
Damit wird die Truma CI Variable auf true gesetzt, damit die Truma in der App und auf dem Touchdisplay angezeigt wird. Dann greifen wir die Befehle, die wir eigentlich an das CI Script senden per MQTT ab und wandeln sie um für das Trumanita Script. Bitte beachtet dabei, dass nicht alle Daten zu 100% stimmen, aber grundsätzlich lässt sich die Truma damit steuern.
Die Dokumentation dazu findet ihr auf Github
Es ist nicht intensiv getestet worden, ob alle Befehle korrekt übersetzt werden und entsprechend ankommen
Das ist nur für die App und das Touchdisplay, aus dem Dashboard heraus werden die Befehle anders abgesetzt!
Das hätte ich mir vor 2 Wochen gewünscht, vermutlich wäre eine reine MQTT verbindung dann doch möglich gewesen.
Gruß Arno
Man muss dem Problem auch immer mal etwas Zeit geben sich von selbst zu lösen ![]()
Aber ich bin gespannt auf deine Ergebnisse nächsten September!
Ahh werde es nach dem Wochenende testen, bei mir ging das Display nicht mehr wenn ich die portes anschalte im Pi menu
Hallo vincent , werde morgen den Teumanita flow testen , ich hatte aber immer , also davor schons als trumanita ging ohne display , das problem das das Display immer auf einen Art schleife gin , also Ladebildschirm und ich es nur durch neu formatieren wieder ging !
Irgendwie begann das problem Immer wieder wenn ich den port aktiviere wie in dee trumanita beschreibung
Ist das problem bekannt ?
Ich habe einen core Pcb
