Have U managed to get protocol for writing to controller (aseko display can control filtration pump forex.) as well?
Thanks.
Have U managed to get protocol for writing to controller (aseko display can control filtration pump forex.) as well?
Thanks.
Hi @jmnemonicj No I haven’t, sorry. I don’t know how those controls might be available or if that will need an extra control unit. I wonder if this will be possible when they develop the API. However I don’t get any indication on what that will be yet. They are not jumping to reply on the requests of information and data. I had to nag to get the protocol.
How does that control of the pump work? Is it the one that shuts the pump off if there is no flow to the sensors?
It is just pump control for any purpose needed from control unit. You can check my previous post: Changes to ASEKO Pool Live and API that will affect the current integration - #22 by jmnemonicj
@marvin78 Hi, good work. How do you setup your passthrough? Do you get all the data to appear in your Aseko app as it would without your dee-tour?
Thanks
There is no magic in there. It’s a simple TCP out node directly connected to the TCP in node. The data is not changed in the process so that everything is sent to the Aseko servers as it would be the case directly from the device.
Hallo zusammen, ich sende euch mal meine NodeRed-Konfig.
[{"id":"e70a6137fb418674","type":"tcp in","z":"8ad928231e422642","name":"Daten von der ASIN AQUA Home empfangen","server":"server","host":"","port":"47524","datamode":"stream","datatype":"buffer","newline":"","topic":"","trim":false,"base64":false,"tls":"","x":410,"y":500,"wires":[["8b44b324630c01cc","14ffd394a48681cf","f3f3017c66641d9c","1771ae89f74ceced","698e73d5851abfd8"]]},{"id":"8b44b324630c01cc","type":"tcp out","z":"8ad928231e422642","name":"Daten an pool.aseko.com:47524 weiterleiten.","host":"pool.aseko.com","port":"47524","beserver":"client","base64":false,"end":false,"tls":"","x":1850,"y":500,"wires":[]},{"id":"1771ae89f74ceced","type":"function","z":"8ad928231e422642","name":"Parse Pool Data (V2025-01-13)","func":"// Funktion zur Überprüfung auf Plausibilität der Temperatur\nfunction istTemperaturPlausibel(temperatur, minTemp, maxTemp) {\n return temperatur >= minTemp && temperatur <= maxTemp;\n}\n\n// Beispiel: Daten als Array von Bytes\nlet data = msg.payload;\n\n// Berechne die entsprechenden Werte\nlet chlorByte1 = data[16];\nlet chlorByte2 = data[17];\nlet phByte1 = data[14];\nlet phByte2 = data[15];\nlet chlor = (chlorByte1 * 256 + chlorByte2) / 100.0;\nlet pH = (phByte1 * 256 + phByte2) / 100.0;\n\n// Berechne die Luft- und Wassertemperatur\nlet lufttempByte1 = data[23];\nlet lufttempByte2 = data[24];\nlet wassertempByte1 = data[25];\nlet wassertempByte2 = data[26];\n\n// Lufttemperaturberechnung mit Two's Complement\nlet luftTempRaw = (lufttempByte1 << 8) | lufttempByte2;\nif (lufttempByte1 & 0x80) { // Vorzeichen-Bit prüfen\n luftTempRaw = luftTempRaw - 0x10000; // Two's Complement\n}\nlet luft_temperature = luftTempRaw / 10.0;\n\n// Wassertemperaturberechnung (positiv angenommen)\nlet wasser_temperature = ((wassertempByte1 * 256) + wassertempByte2) / 10.0;\n\nlet aktuellerWasserstand = data[27];\nlet aktuellerWasserstand_offset = data[27] + 33;\n\nlet year = data[6] + 2000;\nlet month = String(data[7]).padStart(2, '0');\nlet day = String(data[8]).padStart(2, '0');\nlet hour = String(data[9]).padStart(2, '0');\nlet minute = String(data[10]).padStart(2, '0');\nlet second = String(data[11]).padStart(2, '0');\n\nlet date = `${day}.${month}.${year}`;\nlet time = `${hour}:${minute}:${second}`;\n\nlet waterLevelLow = data[102];\nlet refillOn = data[103];\nlet refillOff = data[104];\nlet waterLevelHigh = data[105];\n\n// Formatiere die Filterzeiten\nlet filterzeit1Start = `${String(data[56]).padStart(2, '0')}:${String(data[57]).padStart(2, '0')}`;\nlet filterzeit1Ende = `${String(data[58]).padStart(2, '0')}:${String(data[59]).padStart(2, '0')}`;\nlet filterzeit2Start = `${String(data[60]).padStart(2, '0')}:${String(data[61]).padStart(2, '0')}`;\nlet filterzeit2Ende = `${String(data[62]).padStart(2, '0')}:${String(data[63]).padStart(2, '0')}`;\n\n// Formatiere die Rückspülung Startzeit\nlet rueckspuelungStart = `${String(data[69]).padStart(2, '0')}:${String(data[70]).padStart(2, '0')}`;\n\n// Berechne das Poolvolumen\nlet poolVolumen = (data[92] * 256) + data[93]; // In der Einheit, die durch die Daten definiert ist\n\n// Berechne die Max_Fuellzeit in Minuten\nlet maxFuellzeit = ((data[76] * 256 + data[77]) / 60).toFixed(2); // auf zwei Dezimalstellen gerundet\n\n// Berechne die Dosierverzögerung in Minuten\nlet dosierverzoegerung = ((data[106] * 256 + data[107]) / 60).toFixed(2); // auf zwei Dezimalstellen gerundet\n\n// Berechne die Startverzögerung in Minuten\nlet startverzoegerung = ((data[109] * 256 + data[110]) / 60).toFixed(2); // auf zwei Dezimalstellen gerundet\n\n// Definiere plausible Temperaturbereiche\nlet minLuftTemp = -20; // Beispiel: Minimale akzeptierte Außentemperatur in °C\nlet maxLuftTemp = 50; // Beispiel: Maximale akzeptierte Außentemperatur in °C\nlet minWasserTemp = 0; // Beispiel: Minimale akzeptierte Wassertemperatur in °C\nlet maxWasserTemp = 50; // Beispiel: Maximale akzeptierte Wassertemperatur in °C\n\n// Vorherige Werte aus dem globalen Kontext laden\nlet vorherigeDaten = global.get('poolDaten') || {\n Luft_Temp_ist: null,\n Wasser_Temp_ist: null\n};\n\n// Filtere die Temperaturen, um Ausreißer zu vermeiden\nif (!istTemperaturPlausibel(luft_temperature, minLuftTemp, maxLuftTemp)) {\n node.warn(\"Unplausible Außentemperatur, verwende alten Wert.\");\n luft_temperature = vorherigeDaten.Luft_Temp_ist !== null ? vorherigeDaten.Luft_Temp_ist : luft_temperature;\n}\n\nif (!istTemperaturPlausibel(wasser_temperature, minWasserTemp, maxWasserTemp)) {\n node.warn(\"Unplausible Wassertemperatur, verwende alten Wert.\");\n wasser_temperature = vorherigeDaten.Wasser_Temp_ist !== null ? vorherigeDaten.Wasser_Temp_ist : wasser_temperature;\n}\n\n// Erstelle das JSON-Objekt mit den extrahierten Werten\nlet parsedData = {\n Datum: date,\n Zeit: time,\n Chlor_Wert_ist: chlor,\n pH_Wert_ist: pH,\n Luft_Temp_ist: luft_temperature,\n Wasser_Temp_ist: wasser_temperature,\n Wasserstand_ist: aktuellerWasserstand_offset,\n Chlor_Wert_soll: data[53] / 10.0,\n pH_Wert_soll: data[52] / 10.0,\n Algezid_Dosierung: data[72],\n Floc_Dosierung: data[54],\n Wasser_Temp_soll: data[55],\n Wasserstand_hoch: waterLevelHigh,\n Nachfuellen_aus: refillOff,\n Nachfuellen_an: refillOn,\n Wasserstand_niedrig: waterLevelLow,\n Wasserstand_Sonde: aktuellerWasserstand,\n Filterzeit_1_Start: filterzeit1Start,\n Filterzeit_1_Ende: filterzeit1Ende,\n Filterzeit_2_Start: filterzeit2Start,\n Filterzeit_2_Ende: filterzeit2Ende,\n Rueckspuelung_alle_x_Tage: data[68],\n Rueckspuehlung_Start: rueckspuelungStart,\n max_Fuellzeit_in_Minuten: maxFuellzeit,\n Dosierverzoegerung_in_Minuten: dosierverzoegerung,\n Start_Verzoegerung_in_Minuten: startverzoegerung,\n Pool_Volumen: poolVolumen,\n Konzentration: data[111],\n Konzentration_pH_minus: data[112],\n max_Anzahl_Chlor_Dosen: data[114],\n max_Anzahl_pH_Dosen: data[115]\n};\n\n// Speichere die aktuellen plausibilisierten Werte im globalen Kontext\nglobal.set('poolDaten', {\n Luft_Temp_ist: luft_temperature,\n Wasser_Temp_ist: wasser_temperature\n});\n\n// Gib das JSON-Objekt als msg.payload zurück\nmsg.payload = parsedData;\nreturn msg;\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":790,"y":560,"wires":[["8b6491ccd4b07eb1"]]},{"id":"14ffd394a48681cf","type":"function","z":"8ad928231e422642","name":"System Status","func":"// Beispiel: Daten als Array von Bytes\nlet data = msg.payload;\n\n// Berechne den Wert von Index 79\nlet currentStatus = data[78];\n\n// Wenn der Wert 0 ist, keine Änderung senden\nif (currentStatus === 0) {\n return null;\n}\n\n// Hole den vorherigen Status und die aktuellen Werte aus dem Kontext\nlet previousStatus = context.get('previousStatus') || null;\nlet previousFiltration = context.get('previousFiltration') || false;\nlet previousStandby = context.get('previousStandby') || false;\nlet previousHeating = context.get('previousHeating') || false;\nlet previousOpenMenu = context.get('previousOpenMenu') || false;\n\n// Initialisiere die Variablen für filtration, standby und heating\nlet filtration = previousFiltration;\nlet standby = previousStandby;\nlet heating = previousHeating;\nlet openMenu = previousOpenMenu;\n\n// Wenn der aktuelle Status 40 ist, setze open-menu auf true, ändere die anderen Variablen nicht\nif (currentStatus === 40) {\n openMenu = true;\n} else {\n // Setze die Parameter basierend auf dem extrahierten Wert\n if (currentStatus === 34) {\n filtration = true;\n standby = false;\n heating = false;\n } else if (currentStatus === 162) {\n filtration = true;\n standby = false;\n heating = true;\n } else if (currentStatus === 226) {\n filtration = false;\n standby = false;\n heating = false;\n } else if (currentStatus === 64) {\n filtration = false;\n standby = true;\n heating = false;\n } else if (currentStatus === 1) {\n filtration = true;\n standby = true;\n heating = false;\n }\n openMenu = false; // openMenu auf false setzen, wenn currentStatus nicht 40 ist\n}\n\n// Speichere den aktuellen Status und die Variablen für zukünftige Vergleiche\ncontext.set('previousStatus', currentStatus);\ncontext.set('previousFiltration', filtration);\ncontext.set('previousStandby', standby);\ncontext.set('previousHeating', heating);\ncontext.set('previousOpenMenu', openMenu);\n\n// Erstelle die Nachricht mit 1 und 0 Werten\nmsg.payload = {\n currentStatus: currentStatus,\n filtration: filtration ? 1 : 0,\n standby: standby ? 1 : 0,\n heating: heating ? 1 : 0,\n openMenu: openMenu ? 1 : 0\n};\n\n// Rückgabe der Nachricht\nreturn msg;\n","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":740,"y":600,"wires":[["8b6491ccd4b07eb1"]]},{"id":"8b6491ccd4b07eb1","type":"influxdb out","z":"8ad928231e422642","influxdb":"0691a6f12001ebbf","name":"ASIN Aqua Home to Pool DB","measurement":"asin_aqua_home","precision":"","retentionPolicy":"","database":"your_database","precisionV18FluxV20":"ms","retentionPolicyV18Flux":"","org":"your_org","bucket":"your_bucket","x":1800,"y":580,"wires":[]},{"id":"698e73d5851abfd8","type":"function","z":"8ad928231e422642","name":"Ausgabe der ASIN Aqua Home Daten (V2025-01-13)","func":"// Funktion zur Überprüfung auf Plausibilität\nfunction pruefePlausibilitaet(aktuellerWert, vorherigerWert, toleranzProzent = 50) {\n if (vorherigerWert === null) return true; // Kein Vergleich möglich, akzeptiere Wert\n let abweichung = Math.abs(aktuellerWert - vorherigerWert);\n let maximaleSchwankung = (vorherigerWert * toleranzProzent) / 100;\n return abweichung <= maximaleSchwankung;\n}\n\n// Funktion zur Umrechnung von Two's Complement für Temperaturen\nfunction berechneTemperatur(byte1, byte2) {\n let rohwert = (byte1 << 8) | byte2; // Bytes zusammenfügen\n if (byte1 & 0x80) { // Vorzeichen-Bit prüfen\n rohwert -= 0x10000; // Two's Complement für negative Werte\n }\n return rohwert / 10.0; // Temperatur in °C umrechnen\n}\n\n// Beispiel: Daten als Array von Bytes\nlet data = msg.payload;\n\n// Berechne die entsprechenden Werte\nlet chlorByte1 = data[16];\nlet chlorByte2 = data[17];\nlet phByte1 = data[14];\nlet phByte2 = data[15];\nlet chlor = (chlorByte1 * 256 + chlorByte2) / 100.0;\nlet pH = (phByte1 * 256 + phByte2) / 100.0;\n\n// Berechne Luft- und Wassertemperatur mit Two's Complement\nlet luft_temperature = berechneTemperatur(data[23], data[24]);\nlet wasser_temperature = berechneTemperatur(data[25], data[26]);\n\nlet aktuellerWasserstand = data[27];\nlet aktuellerWasserstand_offset = data[27] + 33;\n\nlet year = data[6] + 2000;\nlet month = String(data[7]).padStart(2, '0');\nlet day = String(data[8]).padStart(2, '0');\nlet hour = String(data[9]).padStart(2, '0');\nlet minute = String(data[10]).padStart(2, '0');\nlet second = String(data[11]).padStart(2, '0');\n\nlet date = `${day}.${month}.${year}`;\nlet time = `${hour}:${minute}:${second}`;\n\nlet waterLevelLow = data[102];\nlet refillOn = data[103];\nlet refillOff = data[104];\nlet waterLevelHigh = data[105];\n\n// Formatiere die Filterzeiten\nlet filterzeit1Start = `${String(data[56]).padStart(2, '0')}:${String(data[57]).padStart(2, '0')}`;\nlet filterzeit1Ende = `${String(data[58]).padStart(2, '0')}:${String(data[59]).padStart(2, '0')}`;\nlet filterzeit2Start = `${String(data[60]).padStart(2, '0')}:${String(data[61]).padStart(2, '0')}`;\nlet filterzeit2Ende = `${String(data[62]).padStart(2, '0')}:${String(data[63]).padStart(2, '0')}`;\n\n// Formatiere die Rückspülung Startzeit\nlet rueckspuelungStart = `${String(data[69]).padStart(2, '0')}:${String(data[70]).padStart(2, '0')}`;\n\n// Berechne das Poolvolumen\nlet poolVolumen = (data[92] * 256) + data[93];\n\n// Berechne die Max_Fuellzeit in Minuten\nlet maxFuellzeit = ((data[76] * 256 + data[77]) / 60).toFixed(2);\n\n// Berechne die Dosierverzögerung in Minuten\nlet dosierverzoegerung = ((data[106] * 256 + data[107]) / 60).toFixed(2);\n\n// Berechne die Startverzögerung in Minuten\nlet startverzoegerung = ((data[109] * 256 + data[110]) / 60).toFixed(2);\n\n// Vorherige Werte aus dem globalen Kontext laden\nlet vorherigeDaten = global.get('poolDaten') || {\n Luft_Temp_ist: null,\n Wasser_Temp_ist: null\n};\n\n// Plausibilitätsprüfung und Anpassung\nif (!pruefePlausibilitaet(luft_temperature, vorherigeDaten.Luft_Temp_ist)) {\n node.warn(\"Unplausible Außentemperatur, verwende alten Wert.\");\n luft_temperature = vorherigeDaten.Luft_Temp_ist;\n}\n\nif (!pruefePlausibilitaet(wasser_temperature, vorherigeDaten.Wasser_Temp_ist)) {\n node.warn(\"Unplausible Wassertemperatur, verwende alten Wert.\");\n wasser_temperature = vorherigeDaten.Wasser_Temp_ist;\n}\n\n// Berechne die Abweichung der Systemzeit zur aktuellen Zeit\nlet aktuelleZeit = new Date();\nlet systemZeitString = `${year}-${month}-${day}T${hour}:${minute}:${second}`;\nlet systemZeit = new Date(systemZeitString);\n\n// Überprüfe, ob die Systemzeit valide ist\nif (isNaN(systemZeit.getTime())) {\n throw new Error(\"Ungültiges Systemzeitformat.\");\n}\n\n// @ts-ignore\nlet time_diff_seconds = Math.abs((aktuelleZeit - systemZeit) / 1000); // Differenz in Sekunden\nlet hours = Math.floor(time_diff_seconds / 3600);\nlet minutes = Math.floor((time_diff_seconds % 3600) / 60);\nlet seconds = Math.floor(time_diff_seconds % 60);\nlet time_diff_formatted = `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`;\n\n// Setze Zeit_Stellen basierend auf der Abweichung\nlet zeit_stellen = time_diff_seconds > 300 ? 'Ja' : 'Nein'; // \"Ja\", wenn Abweichung größer als 5 Minuten, ansonsten \"Nein\"\n\n// Erstelle das JSON-Objekt mit den extrahierten Werten und der Zeitabweichung\nlet parsedData = {\n System_Datum: date,\n System_Zeit: time,\n Zeit_Abweichung: time_diff_formatted,\n Zeit_Stellen: zeit_stellen,\n Chlor_Wert_ist: chlor,\n pH_Wert_ist: pH,\n Luft_Temp_ist: luft_temperature,\n Wasser_Temp_ist: wasser_temperature,\n Wasserstand_ist: aktuellerWasserstand_offset,\n Chlor_Wert_soll: data[53] / 10.0,\n pH_Wert_soll: data[52] / 10.0,\n Algezid_Dosierung: data[72],\n Floc_Dosierung: data[54],\n Wasser_Temp_soll: data[55],\n Wasserstand_hoch: waterLevelHigh,\n Nachfuellen_aus: refillOff,\n Nachfuellen_an: refillOn,\n Wasserstand_niedrig: waterLevelLow,\n Wasserstand_Sonde: aktuellerWasserstand,\n Filterzeit_1_Start: filterzeit1Start,\n Filterzeit_1_Ende: filterzeit1Ende,\n Filterzeit_2_Start: filterzeit2Start,\n Filterzeit_2_Ende: filterzeit2Ende,\n Rueckspuelung_alle_x_Tage: data[68],\n Rueckspuehlung_Start: rueckspuelungStart,\n max_Fuellzeit_in_Minuten: maxFuellzeit,\n Dosierverzoegerung_in_Minuten: dosierverzoegerung,\n Start_Verzoegerung_in_Minuten: startverzoegerung,\n Pool_Volumen: poolVolumen,\n Konzentration: data[111],\n Konzentration_pH_minus: data[112],\n max_Anzahl_Chlor_Dosen: data[114],\n max_Anzahl_pH_Dosen: data[115]\n};\n\n// Speichere die aktuellen plausibilisierten Werte im globalen Kontext\nglobal.set('poolDaten', {\n Luft_Temp_ist: luft_temperature,\n Wasser_Temp_ist: wasser_temperature\n});\n\n// Gib das JSON-Objekt als msg.payload zurück\nmsg.payload = parsedData;\nreturn msg;\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":860,"y":680,"wires":[["9492dc67037c64ec"]]},{"id":"f3f3017c66641d9c","type":"function","z":"8ad928231e422642","name":"Status (Datenpunkt 79)","func":"let data = msg.payload;\n\n// Berechne den Wert von Index 79\nlet currentStatus = data[78];\n\n// Wenn der Wert 0 ist, keine Änderung senden\nif (currentStatus === 0) {\n return null;\n}\n\n// Hole den vorherigen Status und die aktuellen Werte aus dem Kontext\nlet previousStatus = context.get('previousStatus') || null;\nlet previousFiltration = context.get('previousFiltration') || false;\nlet previousStandby = context.get('previousStandby') || false;\nlet previousHeating = context.get('previousHeating') || false;\nlet previousOpenMenu = context.get('previousOpenMenu') || false;\n\n// Initialisiere die Variablen für filtration, standby und heating\nlet filtration = previousFiltration;\nlet standby = previousStandby;\nlet heating = previousHeating;\nlet openMenu = previousOpenMenu;\n\n// Definiere gültige Statuswerte = true\nconst filtrationStatus = [1, 34, 162, 98, 130, 131];\nconst heatingStatus = [162, 130, 131];\nconst standbyStatus = [64, 1];\nconst openMenuStatus = [40];\n\n// Wenn der aktuelle Status 40 ist, setze open-menu auf true, ändere die anderen Variablen nicht\nif (currentStatus === 40) {\n openMenu = true;\n} else {\n // Setze die Parameter basierend auf dem extrahierten Wert\n if (filtrationStatus.includes(currentStatus)) {\n filtration = true;\n } else if (currentStatus === 226 || currentStatus === 64) { // Beachte: 64 explizit hinzufügen\n filtration = false;\n }\n\n if (heatingStatus.includes(currentStatus)) {\n heating = true;\n } else if (currentStatus !== 162) {\n heating = false;\n }\n\n if (standbyStatus.includes(currentStatus)) {\n standby = true;\n } else if (currentStatus !== 64 && currentStatus !== 1) {\n standby = false;\n }\n\n openMenu = false; // openMenu auf false setzen, wenn currentStatus nicht 40 ist\n}\n\n// Speichere den aktuellen Status und die Variablen für zukünftige Vergleiche\ncontext.set('previousStatus', currentStatus);\ncontext.set('previousFiltration', filtration);\ncontext.set('previousStandby', standby);\ncontext.set('previousHeating', heating);\ncontext.set('previousOpenMenu', openMenu);\n\n// Erstelle die Nachricht mit true und false Werten\nmsg.payload = {\n currentStatus: currentStatus,\n filtration: filtration,\n standby: standby,\n heating: heating,\n openMenu: openMenu\n};\n\n// Rückgabe der Nachricht\nreturn msg;\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":770,"y":720,"wires":[["92d12b5c96e9fd56"]]},{"id":"9492dc67037c64ec","type":"link out","z":"8ad928231e422642","name":"Asin Aqua Home Daten","mode":"link","links":["32dc1f195ac6d700","92e3f3208be1ee55","cc53226320fb2b0a","53c1bdbf25d0e170","8e769d5d7e8ec539","0695490e5b669336"],"x":1695,"y":680,"wires":[]},{"id":"92d12b5c96e9fd56","type":"link out","z":"8ad928231e422642","name":"Pumpen Status","mode":"link","links":["0dc34e5181f801a1","2bb0dd6f5fbb1262"],"x":1695,"y":720,"wires":[]},{"id":"0695490e5b669336","type":"link in","z":"8ad928231e422642","name":"link in 17","links":["9492dc67037c64ec"],"x":255,"y":1040,"wires":[["43ec6c1acce765db"]]},{"id":"2bb0dd6f5fbb1262","type":"link in","z":"8ad928231e422642","name":"link in 18","links":["92d12b5c96e9fd56"],"x":255,"y":1080,"wires":[["327163032804b66b"]]},{"id":"7d677a9fe948d8af","type":"comment","z":"8ad928231e422642","name":"Datenstrom in MQTT nach HomeAssistant","info":"","x":360,"y":960,"wires":[]},{"id":"7c2041d19dbf969a","type":"comment","z":"8ad928231e422642","name":"Im USR-K5 Modul den Serial Port von pool.aseko.com auf 10.100.0.90 geändert.","info":"Den Serial Port von pool.aseko.com auf 10.100.0.90 geändert.","x":480,"y":120,"wires":[]},{"id":"7ad6202f686e52c5","type":"comment","z":"8ad928231e422642","name":"ASIN AQUA Home => aseko Cloud","info":"","x":340,"y":80,"wires":[]},{"id":"43ec6c1acce765db","type":"change","z":"8ad928231e422642","name":"msg.payload.sensors","rules":[{"t":"move","p":"payload","pt":"msg","to":"payload.sensors","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":440,"y":1040,"wires":[["436e31921d4b08eb"]]},{"id":"327163032804b66b","type":"change","z":"8ad928231e422642","name":"msg.payload.status","rules":[{"t":"move","p":"payload","pt":"msg","to":"payload.status","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":430,"y":1080,"wires":[["436e31921d4b08eb"]]},{"id":"436e31921d4b08eb","type":"function","z":"8ad928231e422642","name":"Übertragen als MQTT Gerät in HomeAssistant (V2024-12-30)","func":"let sensorData = msg.payload.sensors || {}; // Sicherstellen, dass sensorData ein leeres Objekt ist, wenn es nicht existiert\nlet statusData = msg.payload.status || {}; // Sicherstellen, dass statusData ein leeres Objekt ist, wenn es nicht existiert\nlet baseTopic = 'homeassistant/sensor';\n\n// Funktion zur Übertragung von Sensordaten\nfor (let key in sensorData) {\n if (sensorData.hasOwnProperty(key)) {\n let discoveryTopic = `${baseTopic}/${key}/config`;\n let stateTopic = `${baseTopic}/${key}`;\n let payload = {\n name: key.replace('_in_Minuten', ''),\n state_topic: stateTopic,\n value_template: '{{ value }}',\n unique_id: `${key}_sensor`,\n device: {\n identifiers: ['pool_sensor_system'],\n name: 'Poolsteuerung',\n model: 'ASIN Aqua Home VS',\n manufacturer: 'ASEKO'\n }\n };\n\n // Anpassungen für Zeitwerte\n switch (key) {\n case 'System_Zeit':\n let timePartsSystem = sensorData[key].split(':');\n if (timePartsSystem.length === 3) {\n let currentDate = new Date();\n\n // Erzeuge ein Date-Objekt mit der Zeit aus den empfangenen Daten\n let isoTime = new Date(currentDate.getFullYear(), currentDate.getMonth(), currentDate.getDate(), timePartsSystem[0], timePartsSystem[1], timePartsSystem[2]);\n\n // Erhalte die lokale Zeit unter Berücksichtigung der Sommerzeit\n let localHours = isoTime.getHours();\n let localMinutes = isoTime.getMinutes();\n let localSeconds = isoTime.getSeconds();\n\n // Anpassen, falls die Systemzeit eine Stunde vor der tatsächlichen Zeit liegt\n let timeString = `${localHours.toString().padStart(2, '0')}:${localMinutes.toString().padStart(2, '0')}:${localSeconds.toString().padStart(2, '0')}`;\n sensorData[key] = timeString;\n } else {\n // Standardzeit verwenden, wenn keine Eingabe vorhanden ist\n let currentDate = new Date();\n let timeString = `${currentDate.getHours().toString().padStart(2, '0')}:${currentDate.getMinutes().toString().padStart(2, '0')}:${currentDate.getSeconds().toString().padStart(2, '0')}`;\n sensorData[key] = timeString;\n }\n payload.value_template = \"{{ value }}\"; // Übergabe des Zeitwertes\n break;\n \n case 'Zeit_Stellen':\n // Hier sicherstellen, dass der Wert von \"Ja\"/\"Nein\" korrekt übermittelt wird\n if (sensorData[key] === 'Ja') {\n sensorData[key] = 'Ja';\n } else {\n sensorData[key] = 'Nein';\n }\n break;\n\n case 'Zeit_Abweichung':\n // Sicherstellen, dass der Wert im Format HH:MM:SS vorliegt\n let timePartsAbweichung = sensorData[key].split(':'); // Beispiel: \"00:04:14\"\n if (timePartsAbweichung.length === 3) {\n let timeString = timePartsAbweichung.join(':'); // Beispiel: \"00:04:14\"\n sensorData[key] = timeString; // Nur die Uhrzeit (00:04:14)\n } else {\n sensorData[key] = \"00:00:00\"; // Standardwert setzen\n }\n break;\n\n // Weitere Anpassungen hier\n\n case 'max_Fuellzeit_in_Minuten':\n case 'Dosierverzoegerung_in_Minuten':\n case 'Start_Verzoegerung_in_Minuten':\n payload.unit_of_measurement = 'Min.';\n break;\n\n case 'Chlor_Wert_ist':\n case 'Chlor_Wert_soll':\n payload.unit_of_measurement = 'ppm';\n break;\n\n case 'Luft_Temp_ist':\n case 'Wasser_Temp_ist':\n case 'Wasser_Temp_soll':\n payload.unit_of_measurement = '°C';\n break;\n\n case 'Wasserstand_ist':\n case 'Wasserstand_hoch':\n case 'Wasserstand_niedrig':\n case 'Nachfuellen_an':\n case 'Nachfuellen_aus':\n case 'Wasserstand_Sonde':\n payload.unit_of_measurement = 'cm';\n break;\n\n case 'Rueckspuelung_alle_x_Tage':\n payload.unit_of_measurement = 'Tage';\n break;\n\n case 'Pool_Volumen':\n payload.unit_of_measurement = 'qm';\n break;\n }\n\n // Sende Discovery- und State-Nachrichten\n node.send({ topic: discoveryTopic, payload: JSON.stringify(payload) });\n node.send({ topic: stateTopic, payload: sensorData[key] });\n }\n}\n\n// Funktion zur Übertragung von Statusdaten\nfor (let key in statusData) {\n if (statusData.hasOwnProperty(key)) {\n let discoveryTopic = `${baseTopic}/status_${key}/config`;\n let stateTopic = `${baseTopic}/status_${key}`;\n let payload = {\n name: `Status ${key}`,\n state_topic: stateTopic,\n value_template: '{{ value }}',\n unique_id: `status_${key}_sensor`,\n device: {\n identifiers: ['pool_sensor_system'],\n name: 'Poolsteuerung',\n model: 'ASIN Aqua Home VS',\n manufacturer: 'ASEKO'\n }\n };\n\n // Umwandlung von true/false in \"ein\"/\"aus\" für Statuswerte\n if (['filtration', 'standby', 'heating', 'openMenu'].includes(key)) {\n payload.value_template = '{{ value | bool }}';\n }\n\n // Sende Discovery- und State-Nachrichten\n node.send({ topic: discoveryTopic, payload: JSON.stringify(payload) });\n node.send({ topic: stateTopic, payload: statusData[key] });\n }\n}\n\nreturn null;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":860,"y":1040,"wires":[["6bbc5fe20c8059b1"]]},{"id":"6bbc5fe20c8059b1","type":"mqtt out","z":"8ad928231e422642","name":"MQTT Send","topic":"","qos":"0","retain":"true","respTopic":"","contentType":"","userProps":"","correl":"","expiry":"","broker":"22f544be.780a3c","x":1250,"y":1040,"wires":[]},{"id":"0691a6f12001ebbf","type":"influxdb","hostname":"127.0.0.1","port":"8086","protocol":"http","database":"pool","name":"Pool DB","usetls":false,"tls":"","influxdbVersion":"1.x","url":"http://localhost:8086","timeout":"10","rejectUnauthorized":true},{"id":"22f544be.780a3c","type":"mqtt-broker","name":"HomeAssistant-MQTT","broker":"10.100.1.90","port":"1883","clientid":"NodeRed","autoConnect":true,"usetls":false,"compatmode":false,"protocolVersion":"4","keepalive":"60","cleansession":true,"autoUnsubscribe":true,"birthTopic":"","birthQos":"0","birthPayload":"","birthMsg":{},"closeTopic":"","closeQos":"0","closePayload":"","closeMsg":{},"willTopic":"","willQos":"0","willPayload":"","willMsg":{},"userProps":"","sessionExpiry":""}]
I put together a custom integration, which listens for the data from the unit - it can be found here if anyone is interested in testing: GitHub - hopkins-tk/home-assistant-aseko-local: Aseko Local integration for Home Assistant
Currently awaiting adding to HACS list of integrations:
I run it along with the optional goduplicator container to keep the data flow to Aseko servers.
Tested with Aseko ASIN Aqua Salt unit.
Great, will test it in the evening ![]()
Hey hopkins, working like a charm, thanks ![]()
It would be great to sniff incoming connections from cloud as well to manage controlling, like filtration pump ![]()
There is an issue regarding refresh of the items for mirrored connection.
It worked for a while, but stop after then:
I can still see the fresh values in aseko cloud app, but not in homeassistant itself.
So it seems there is just issue for mirrored data only.
It will be refreshed, when I restart aseko module, but the behavior is the same as previous. It will stop working after a while.
Shoudl I rise a tiket in github for this one?
Thanks,
Hi jmnemonicj,
thanks for the testing.
Please raise an issue and try to provide logs from HA after enabling logging in UI for Aseko Local.
It would be also interesting to know if testart/reload of the Aseko Local component helps and what version of HA you are running.
Thanks
Thanks, check it plz: Refresh data only once · Issue #14 · hopkins-tk/home-assistant-aseko-local · GitHub
Thanks so much for the HACS integration. I get a few entities for the Asin Aqua Home. I know there is way more data coming out of the Asin Aqua Home. Any chance your integration could read that info too? Let me know what you would need keeping in mind I am not too familiar with fetching logs so detailed guidance would accelerate things.
Speaking of detailed guidance, could someone break down the steps on getting the mirrored connection to work. Not sure how to setup go duplicator. Screenshots would be amazing to have
Sorry for asking a lot from you…
Hi,
let’s start with goduplicator setup at aseko side:
The 192.168.1.213 is may Asin Aqua local IP address, you will find the configuration form there.
I am expecting admin/admin are your credentials, go to the “Serial port” settings.
Put the note regarding your “Remote server address”, you will need it later on.
Change the “Remote Server Addr” to the IP address of your docker instance where goduplicator will by installed. (Mine is 192.168.1.163)
Save!
Check your docker side (I am using Portainer for the purpose) then:
I am using bgrynblat image as goduplicator source.
Change the command parameters as follows:
-f “your previously saved Remote server address”
-m “your Home Assistant local ip address for mirrored data stream”
Don’t use HA IP address as primary one (-f), I got errors for the scenario :-|.
Save, run the container and enjoy.
That’s it.
Thank @jmnemonicj . With some guess work I managed to understand your instructions. Not that they’re bad, I’m just a complete newbee.
So I installed Docker; added the Portainer extension; and got the bgrynblat image running.
I tried creating the container but it exits after a few seconds with exit code 2. Can you spot the mistake I’m making?
What are your goduplicator logs?
Should be something like the following:
![]()
I think your HA endpoint is not ok:
![]()
That seems to be the HA web UI address, not Aseko local integration endpoint.
Check the integration settings:
and modify the port (8123) to the one you have found.
Where is your docker image running? You forwarded the data stream from Aseko unit to the same address (192.168.68.76) as the mirrored one, just different port (8123). Is it ok?
I corrected the port to 47524 as it shows in my Aseko integration.
Since the container doesn’t run, it isn’t giving me an IP address. So the Aseko unit isn’t sending to the correct IP address.
Here are the logs which don’t show the accepted connection:
flag provided but not defined: -m:192.168.68.76:47524
Usage of /goduplicator:
-d duration
delay connecting to mirror after unsuccessful attempt (default 20s)
-f string
forward to address (e.g. ‘localhost:8081’)
-l string
listen address (e.g. ‘localhost:8080’)
-m value
comma separated list of mirror addresses (e.g. ‘localhost:8082,localhost:8083’)
-r forward response (default true)
-t duration
mirror connect timeout (default 500ms)
-wt duration
mirror write timeout (default 20ms)
-z use zero copy
flag provided but not defined: -m:192.168.68.76:47524
Usage of /goduplicator:
-d duration
delay connecting to mirror after unsuccessful attempt (default 20s)
-f string
forward to address (e.g. ‘localhost:8081’)
-l string
listen address (e.g. ‘localhost:8080’)
-m value
comma separated list of mirror addresses (e.g. ‘localhost:8082,localhost:8083’)
-r forward response (default true)
-t duration
mirror connect timeout (default 500ms)
-wt duration
mirror write timeout (default 20ms)
-z use zero copy
Not sure how to identify where my docker image is running. Here are some screenshots:
Your params are not correct:
Thanks for that. Got the container to work. However, when I plug in it’s IP address into the aseko unit, I don’t seem to be getting data.
The 172… IP address does seem weird.
Can you spot what I’ve gotten wrong?