// KWL mit easyControls über Modbus TCP steuern v5.3 // by jan.wachsmuth@web.de, Feedback gerne erwünscht. // //INPUTS //AI1 .... Betriebsart (noch nicht implementiert) //AI2 .... Lüfterstufe Normalbetrieb (schreiben, 0 .. 4) //AI3 .... Funktion Stoßlüften (schreiben, 0 oder 1): Lüfterstufe auf Maximum (=4) einstellen //AI4 .... Bypass - Raumtemperatur (Bypass wird bei geringeren Innentemperaturen nicht aktiviert) //AI5 .... Bypass - min. Aussentemperatur (Bypass wird bei geringeren Aussentempaturen abgeschaltet) //AI6 .... Abfragefrequenz der Statuswerte aus Helios in Sek - z.B. 10 => ca. alle 10 Sek werden Werte abgefragt //AI7 .... Funktion Intensivlüften (schreiben, 0 oder 1): Lüfterstufe um 1 erhöhen, wenn z.B. CO2 Sensor eine Verschlechterung // der Luftqualität feststellt oder über eine Schaltuhr wenn meist mehrere Personen anwesend sind // Fehlermeldungen im Log reduzieren, um Lebensdauer der SD-Karte nicht zu verkürzen // sinnvolle Werte sind z.B. 10 Fehler pro 60 Minuten. //AI8 .... Anzahl der Fehlermeldungen im Log pro Zeitspanne (Default: 10 Fehler) //AI9 .... Zeitintervall für Zähler der Fehlermeldungen in Minuten (Default: 60 min) // Es werden im Intervall nur die o.a. Anzahl der Fehler im Detail protokolliert Wenn mehr Fehler produziert // werden, dann gibt es am Ende des Intervalls für die zusätzlichen Fehler nur eine Sammelmeldung mit der Anzahl. // //OUTPUTS //AQ1 .... Temperatur Aussenluft //AQ2 .... Temperatur Zuluft //AQ3 .... Temperatur Fortluft //AQ4 .... Temperatur Abluft //AQ5 .... Ventilator Zuluft (rpm) //AQ6 .... Ventilator Abluft (rpm) //AQ7 .... Bypass aktiv //AQ8 .... Anzahl Fehler //AQ9 .... Anzahl Warnungen //A10 .... Anzahl Infos //A11 .... Lüfterstufe Normalbetrieb (Radiotasten) //A12 .... aktive Lüfterstufe (aus KWL auslesen) //TQ1 .... Artikelbezeichnung //CONSTANTS // Disable/enable debug mode (writes log messages) #define DEBUG_MODE 0 //VARIABLES char* answer; float tempAussenluft = 0.0; float tempZuluft = 0.0; float tempFortluft = 0.0; float tempAbluft = 0.0; float rpmZuluft = 0.0; float rpmAbluft = 0.0; float bypass = 0.0; int bypassTemp = 0.0; int bypassTempLast = 0.0; float minAussenBypass = 0.0; int minAussenBypassTemp = 0.0; int minAussenBypassTempLast = 0.0; int anzahlFehler = 0; int anzahlWarnungen = 0; int anzahlInfo = 0; char artikelBez[32]; int betriebsart; int luefterStufe = 3; int luefterStufeLast; int stosslueften; int intensivlueften; int luefterStufeNormal; int pullFreq = 10; STREAM* stream; unsigned int lastLoopTime; unsigned int currentTime; unsigned int errorInterval; unsigned int errorCount; unsigned int errorMax; unsigned int errorTime; char errorMessage[255]; // Modbus TCP header char modbusTCP[30]; char tIDl,tIDh; char szBuffer[100]; int nLen; int err; int intValue; float floatValue; char stringValue[255]; printf("INFO: Helios KWL Modbus TCP start"); // modbusTCP Header vorbereiten und konstante Werte eintragen tIDl=0; tIDh=0; // Protocol Identifier - 2 Byte Zahl, immer 0 modbusTCP[2]=0; modbusTCP[3]=0; // Length - 2 Byte Zahl - Länge des nachfolgenden Datenpaketes insgesamt modbusTCP[4]=0; modbusTCP[5]=0; // Unit Identifier - immer 180 für Helios KWL modbusTCP[6]=180; // reference number - Zweck unklar modbusTCP[8]=0; modbusTCP[9]=1; // Word count - Anzahl der zu schreibenden Worte (16 Bit) modbusTCP[10]=0; void printError(char* errorMessage) { if (errorCount == 0) { errorTime=getcurrenttime(); } if (errorCount < errorMax) printf(errorMessage); errorCount++; sleep(1000); } int modbusTCPwriteVar(char* heliosVar) { int wordLen; int nLen; // Anfrage mit modbusTCP Header erstellen (variabler Anteil) // Transaction Identifier - 2 Byte Zahl, die mit jeder Anfrage hochzählt modbusTCP[0] = tIDh; modbusTCP[1] = tIDl; tIDl++; if (tIDl == 256) { tIDl = 0; tIDh++; if (tIDh == 256) tIDh = 0; } // Function Code modbusTCP[7] = 16; // Write Multiple Registers (16) // Word count - Anzahl der zu schreibenden Worte (16 Bit) // inkl. 0-Byte am Ende (+1), aufrunden (+1) nLen = strlen(heliosVar); wordLen = (nLen + 2) >> 1; nLen = wordLen << 1; // Length - 2 Byte Zahl - Länge des nachfolgenden Datenpaketes insgesamt modbusTCP[5] = 7 + nLen; // Anzahl der zu schreibenden Register (a 16 Bit) modbusTCP[11] = wordLen; // Byte count - Anzahl der nachfolgenden Bytes modbusTCP[12] = nLen; strncpy(&modbusTCP[13], heliosVar, nLen); // Header und Daten an Helios senden stream_write(stream, modbusTCP, 13 + nLen); stream_flush(stream); // TCP Datenpaket empfangen, 500 msec auf Antwort warten nLen = stream_read(stream, szBuffer, sizeof(szBuffer) - 1, 500); if (nLen == 0) { sprintf(errorMessage, "ERROR: Helios KWL modbusTCP select or write variable %s failed (0 bytes read) !", heliosVar); printError(errorMessage); return 1; } else { // weitere Überprüfung der Antwort fehlt - Werte ähnlich wie in der Anfrage // es wird angenommen, dass die Antwort passt return 0; } } char* modbusTCPreadVar(char* heliosVar, int len) { // "countWords" Worte (a 16-Bit) aus Holding Register auslesen // TCP Anfrage mit modbusTCP Header erstellen // Transaction Identifier - 2 Byte Zahl, die mit jeder Anfrage hochzählt modbusTCP[0] = tIDh; modbusTCP[1] = tIDl; tIDl++; if (tIDl == 256) { tIDl = 0; tIDh++; if (tIDh == 256) tIDh = 0; } // Length - 2 Byte Zahl - Länge des nachfolgenden Datenpaketes insgesamt modbusTCP[5] = 6; // Function Code modbusTCP[7] = 3; // Read Holding Registers (3) // Word count - Anzahl der zu lesenden Worte (16 Bit), muss < 256 sein! // Variable=6 Byte, "="=1 Byte, Wert=len Byte, 0-Byte am Ende (+1), aufrunden (+1) modbusTCP[11] = (9 + len) >> 1; // TCP Datenpaket mit modbus Header senden stream_write(stream, modbusTCP, 12); stream_flush(stream); // TCP Datenpaket empfangen, 500 msec auf Antwort warten nLen = stream_read(stream, szBuffer, sizeof(szBuffer) - 1, 500); // Länge der Antwort prüfen (mind v0XXXX=y\0) if (nLen>16) { // Variable auslesen und überprüfen, immer 6 Bytes lang "v0XXXX" if (strncmp(heliosVar, &szBuffer[9], 6) == 0) { return &szBuffer[16]; } else { szBuffer[15]=0; sprintf(errorMessage, "ERROR: Helios KWL modbusTCP read variable %s is not %s !", heliosVar, &szBuffer[9]); printError(errorMessage); return NULL; } } else { sprintf(errorMessage, "ERROR: Helios KWL modbusTCP read variable %s - answer too short, only %d Bytes, <%s>", heliosVar, nLen, &szBuffer[9]); printError(errorMessage); return NULL; } } // liest eine Helios Variable vom Typ Float aus und schreibt diese in floatValue // Rückgabewert: True, wenn eine Zahl gelesen wurde int heliosReadFloat(float* floatValue, char* heliosVar, int len) { int err; char* answer; // 1. Teil: String für Variable schreiben err = modbusTCPwriteVar(heliosVar); if (err) return false; // 2. Teil: Antwort aus Holding Register auslesen answer = modbusTCPreadVar(heliosVar, len); if (answer == NULL) return false; // Überprüfung der Antwort auf korrekte Gleitkommazahl fehlt noch *floatValue = atof(answer); if (DEBUG_MODE) { sprintf(errorMessage, "DEBUG: Helios KWL modbusTCP READ %s = %f", heliosVar, *floatValue); printError(errorMessage); } return true; } // liest eine Helios Variable vom Typ Int aus und schreibt diese in intValue // Rückgabewert: True, wenn eine Zahl gelesen wurde int heliosReadInt(int * intValue, char* heliosVar, int len) { int err; char* answer; // 1. Teil: String für Variable schreiben err = modbusTCPwriteVar(heliosVar); if (err) return false; // 2. Teil: Antwort aus Holding Register auslesen answer = modbusTCPreadVar(heliosVar, len); if (answer == NULL) return false; *intValue = atoi(answer); if (DEBUG_MODE) { sprintf(errorMessage, "DEBUG: Helios KWL modbusTCP READ %s = %i", heliosVar, *intValue); printError(errorMessage); } return true; } // liest eine Helios Variable vom Typ String aus und schreibt diese in stringValue // Rückgabewert: True, wenn eine Zahl gelesen wurde int heliosReadString(char* stringValue, char* heliosVar, int len) { int err; char* answer; // 1. Teil: String für Variable schreiben err = modbusTCPwriteVar(heliosVar); if (err) return false; // 2. Teil: Antwort aus Holding Register auslesen answer = modbusTCPreadVar(heliosVar, len); if (answer == NULL) return false; strcpy(stringValue, answer); if (DEBUG_MODE) { sprintf(errorMessage, "DEBUG: Helios KWL modbusTCP READ %s = %s", heliosVar, stringValue); printError(errorMessage); } return true; } // schreibt einen Wert vom Typ Int in eine Helios Variable void heliosWriteInt(char* heliosVar, int value) { char heliosString[20]; sprintf(heliosString,"%s=%d", heliosVar, value); if (DEBUG_MODE) { sprintf(errorMessage, "DEBUG: Helios KWL modbusTCP WRITE %s = %d", heliosVar, value); printError(errorMessage); } modbusTCPwriteVar(heliosString); } // schreibt einen Wert vom Typ Float in eine Helios Variable void heliosWriteFloat(char* heliosVar, float value) { char heliosString[20]; sprintf(heliosString,"%s=%f", heliosVar, value); if (DEBUG_MODE) { sprintf(errorMessage, "DEBUG: Helios KWL modbusTCP WRITE %s = %f", heliosVar, value); printError(errorMessage); } modbusTCPwriteVar(heliosString); } // BEGIN MAIN SCRIPT // Betriebsart wählen 0 = Automat. 1 = Handbetrieb betriebsart=(int)getinput(0); // Parameter auf Gültigkeit prüfen if(betriebsart<0) betriebsart=0; if(betriebsart>1) betriebsart=1; // Fehlerzähler initialisieren errorCount = 0; // max. Anzahl der Fehler, die im Intervall geloggt werden sollen - wird nur am Anfang ausgelesen errorMax = (unsigned int)getinput(7); if (errorMax == 0) errorMax = 10; // Default: 10 Fehlermeldungen (pro Stunden, s.u.) errorTime=getcurrenttime(); // Zeitintervall in Minuten, in dem der Zähler für die Fehlermeldungen zurückgesetzt wird errorInterval = (unsigned int)(getinput(8) * 60); // in Sekunden umrechnen if (errorInterval == 0) errorInterval = 3600; // Default: 1 Stunde, wenn kein Wert vorgegeben wird // vorherige Werte auf ungültige Werte setzen, damit die aktuellen Werte von Loxone zu Helios geschrieben werden luefterStufeLast = -1; minAussenBypassTempLast = -1; bypassTempLast = -1; // Ende der Initialisierung // main loop while(TRUE) { // Lüfterstufe für Normalbetrieb lesen (Wertebereich 0...4) luefterStufeNormal = (int)getinput(1); // Funktion Stoßlüften: maximale Stufe einstellen (=4) stosslueften = (int)getinput(2); // Funktion Intensivlüften: Lüfterstufe um 1 erhöhen, wenn z.B. CO2 Sensor eine Verschlechterung der Luftqualität feststellt intensivlueften = (int)getinput(6); // aktuelle Lüfterstufe einstellen if (stosslueften == 1) { luefterStufe = 4; } else { if (intensivlueften == 1) { luefterStufe = luefterStufeNormal+1; } else { luefterStufe = luefterStufeNormal; } } // Bypasstemperatur aus Loxone lesen (10...40) bypassTemp = (int)getinput(3); // Wertebereich ggf. anpassen if (bypassTemp > 40) bypassTemp = 40; if (bypassTemp < 10) bypassTemp = 10; // Min. Aussentemperatur für Bypass aus Loxone lesen (0...40) minAussenBypassTemp = (int)getinput(4); // Wertebereich ggf. anpassen if (minAussenBypassTemp > 40) minAussenBypassTemp = 40; if (minAussenBypassTemp < 0) minAussenBypassTemp = 0; // Abfragefrequenz in Sekunden für das Auslesen der Parameter aus Helios KWL pullFreq = (int)getinput(5); if (pullFreq == 0) pullFreq = 10; // Default sind 10 Sekunden // aktuelle Zeit ermitteln currentTime = getcurrenttime(); // Meldung mit Summe der Fehler im Zeitintervall, wenn zu viele Fehler auftraten if (currentTime > errorTime + errorInterval) { if (errorCount > errorMax) printf("ERROR: Helios KWL modbusTCP - %u errors within %u minutes! Only first %u errors logged.", errorCount, (unsigned int)(errorInterval/60), errorMax); errorCount = 0; } // Werte schreiben und/oder lesen, wenn // entweder das Abfrageintervall erreicht ist // oder sich mind. ein Wert geändert hat if (currentTime>=lastLoopTime+pullFreq || luefterStufe!=luefterStufeLast || bypassTemp!=bypassTempLast || minAussenBypassTemp!=minAussenBypassTempLast) { // Stream zur IP-Adresse der Helios KWL (Modbus TCP Gegenstelle) stream = stream_create("/dev/tcp/192.168.128.11/502", 1, 0); if (stream != NULL) { lastLoopTime = currentTime; // neue Lüfterstufe nur schreiben, wenn sich diese geändert hat if (luefterStufe != luefterStufeLast) { heliosWriteInt("v00102", luefterStufe); luefterStufeLast = luefterStufe; } setoutput(10, luefterStufeNormal); // Lüfterstufe für Normalbetrieb setoutput(11, luefterStufe); // akutelle Lüfterstufe sleep(50); // wurde bypassTemp wurde in Loxone geändert? if (bypassTemp != bypassTempLast) { heliosWriteInt("v01035", bypassTemp); bypassTempLast = bypassTemp; } sleep(50); // wurde minAussenBypassTemp wurde in Loxone geändert? if (minAussenBypassTemp != minAussenBypassTempLast) { heliosWriteInt("v01036", minAussenBypassTemp); minAussenBypassTempLast = minAussenBypassTemp; } sleep(50); // Temperatur Aussenluft auslesen, char[7] if (heliosReadFloat(&floatValue, "v00104", 7)) { tempAussenluft = floatValue; setoutput(0, tempAussenluft); } sleep(50); // Temperatur Zuluft auslesen, char[7] if (heliosReadFloat(&floatValue, "v00105", 7)) { tempZuluft = floatValue; setoutput(1, tempZuluft); } sleep(50); // Temperatur Fortluft auslesen, char[7] if (heliosReadFloat(&floatValue, "v00106", 7)) { tempFortluft = floatValue; setoutput(2, tempFortluft); } sleep(50); // Temperatur Abluft auslesen, char[7] if (heliosReadFloat(&floatValue, "v00107", 7)) { tempAbluft = floatValue; setoutput(3, tempAbluft); } sleep(50); // Drehzahl Zuluft (rpm) auslesen, char[4] if (heliosReadFloat(&floatValue, "v00348", 4)) { rpmZuluft = floatValue; setoutput(4, rpmZuluft); } sleep(50); // Drehzahl Abluft (rpm) auslesen, char[4] if (heliosReadFloat(&floatValue, "v00349", 4)) { rpmAbluft = floatValue; setoutput(5, rpmAbluft); } sleep(50); // Status Bypass auslesen, char[1], undokumentiert! if (heliosReadFloat(&floatValue, "v02119", 1)) { bypass = floatValue; setoutput(6, bypass); } sleep(50); // AnzahlFehler auslesen, char[2] if (heliosReadInt(&intValue, "v01300", 2)) { anzahlFehler = intValue; setoutput(7, (float)anzahlFehler); } sleep(50); // AnzahlWarnungen auslesen, char[1] if (heliosReadInt(&intValue, "v01301", 1)) { anzahlWarnungen = intValue; setoutput(8, (float)anzahlWarnungen); } sleep(50); // AnzahlInfo auslesen, char[1] if (heliosReadInt(&intValue, "v01302", 1)) { anzahlInfo = intValue; setoutput(9, (float)anzahlInfo); } sleep(50); // Artikelbezeichnung auslesen, char[31] if (heliosReadString(&artikelBez, "v00000", 31)) { setoutputtext(0, artikelBez); } sleep(50); stream_close(stream); } else { sprintf(errorMessage, "ERROR: Helios KWL modbusTCP - unable to create TCP stream"); printError(errorMessage); sleep(500); } } sleep(500); }