In meinem letzten Artikel habe ich euch die Grundlagen des Smart Campers gezeigt. Wir können jetzt die Lichter schalten und die Temperatur im Fahrzeug messen. Da unser Camper aber erst richtig “smart” werden kann, wenn alle Komponenten Teil des Systems sind, habe ich mir Gedanken gemacht, wie ich an das Kernstück des Fahrzeuges, das Westfalia Zentralsteuergerät herankomme, da hier die Heizung, das Warmwasser und der Kühlschrank gesteuert und die Füllstände der Tanks gelesen werden können. Eine Anfrage an Westfalia, ob es hier eine Möglichkeit gibt, blieb leider unbeantwortet. Das macht es zwar schwieriger aber weckt auch meinen Tatendrang, es trotzdem zu schaffen. Um es kurz zu machen: Auch ohne die Hilfe von Westfalia habe ich eine Möglichkeit gefunden mich in das Zentralsteuergerät einzuklinken. Dieser Artikel beschreibt, wie man das Westfalia Zentralsteuergerät in das Homematic-System einbinden kann.

Grundlagen Westfalia Bedienteil und Zentralsteuergerät

Ich habe festgestellt, dass das Bedienteil über der Schiebetür mit 4 Kabeln versorgt wird. 12V und Ground sowie 2 verdrillte Adern (das ist auch schon der erste Hinweis, um was es sich hier handeln könnte). Da neben dem Stromanschluss nur 2 Kabel vorhanden sind, kann das Bedienteil nicht die ganze Magic im Camper machen, es muss noch irgendwo etwas mit sehr viel mehr Kabeln sein.

Ich erinnerte mich, beim Einbau der Netzvorrangschaltung in der Serviceklappe ein Steuergerät gesehen zu haben, aus der viele Kabel führten. Dazu muss die Bodenplatte in der Serviceklappe entfernt werden. Das Steuergerät ist von unten mit Klettverschluss an der Bodenplatte befestigt (Ich habe gelesen, dass bei den größeren Fahrzeugen das Steuergerät im Küchenblock ist). Hier habe ich 2 verdrillte Kabel in denselben Farben gefunden wie am Bedienteil. Die Geräte scheinen also über diese Kabel zu kommunizieren. Als nächstes habe ich mir die beiden Geräte mal von innen angeschaut. Und siehe da, an den Anschlüssen ist im Inneren jeweils ein TLE6250G CAN Transreceiver verlötet. Es handelt sich also um einen CAN-Bus.

Mit einem CAN-Analyzer (USBtin EB USB-CAN Adapter) kann man feststellen, dass die beiden Geräte über 500kBps kommunizieren.  4 IDs werden im Abstand von ca 15ms immer wieder gesendet.

ID DLC Startkonfiguration Frequenz Verwendung
0x350 2 00 60 15ms Tasten vom Bedienteil
0x200 8 6b 03 00 40 80 10 01 e0 15ms Antwort vom Zentralsteuergerät
0x330 8 B0 00 60 78 00 00 00 00 15ms Vielleicht die Zeit
0x240 8 03 26 0b e9 89 59 8d 6d 1s Keine Ahnung, brauche ich aber auch nicht

 

Es zeigt sich, dass das Bedienteil einfach nur stupide die Änderungen an den Tasten weitergibt, also die Position des Rades und das Drücken der Tasten. All das ist in Byte0 der ID 0x350 enthalten:

Ich kann mich durch Senden der richtigen Kombinationen durch die Menüs des Bedienteils bewegen und Werte lesen bzw. schreiben. Mit jeder Änderung der Drehposition, zeigt das Bedienteil ein anderes Menü als markiert an. Dies äußert sich dadurch, dass in der Antwort des Steuergerätes das 5te Byte die aktuell markierte Menüposition zeigt. Die Kombination aus dem Lesen der ID 0x200 und dem Senden von Tastenkombinationen ermöglich also eine Navigation durch die verschiedenen Menüs

Einstellung des Zentralsteuergeräts über CAN lesen bzw. Schreiben

Mit einem ESP32 Microcontroller und einem MCP2515 CAN Modul war die Kommunikation zum Zentralsteuergerät schnell hergestellt. Der im Abschnitt Installation bereitgestellte Code sendet die Menübefehle so lange bis das entsprechende Register ausgewählt ist. Dann betätigt der Code die Enter-Taste und sucht mit dem Drehrad den Wert, der gelesen oder geschrieben werden soll. Die Werte finden sich meistens im Bit0 der ID 0x200. Beim Schreiben wird der Wert über die Pfeiltasten so lange geändert, bis der Zielwert erreicht ist. Anschließend wird das Menü mit Drehrad und Entertaste wieder verlassen.

Kommunikation zwischen ESP32 und der Homematic

Jetzt müssen die Informationen noch mit der Homematic ausgetauscht werden und das ganze wie immer möglichst einfach und energiesparend. Ich habe eine Bibliothek gefunden, die asksin++ heißt und das Erstellen eigener Homematic-Komponenten ermöglicht, die über Funk eingebunden werden können. Leider bin ich aus dem Code nicht schlau geworden und habe mich dann für eine andere sehr viel einfachere Variante entschieden. Da mein Raspberry Pi sowieso im WLAN eingebunden ist, kann ich auch einfach über WLAN die Kommunikation zwischen ESP32 und der Homematic bewerkstelligen. Dafür benutze ich das Addon XMLAPI, das es ermöglicht über API-Calls Variablen der Homematic zu lesen und zu ändern. Da WLAN aber den Stromverbrauch meines ESP32 stark in die Höhe treibt, habe ich diesen über einen GPIO mit dem Raspberry Pi verbunden. Der ESP32 verbringt den Großteil seines Daseins in DeepSleep bei quasi 0mA Stromaufnahme. Sobald er eine Aktion (Wert Lesen oder Schreiben) ausführen soll, schreibt die Homematic den Befehl, der ausgeführt werden soll in die Variable „ESP32_Befehl“, der ESP32 wird über den GPIO geweckt, verbindet sich mit dem WLAN, liest über die XML-API den Befehl und führt ihn aus. Beim Lesen speichert der ESP32 den über den CAN-Bus ermittelten Wert in der dafür vorgesehenen Variablen und bestätigt in der Variablen „ESP32_Befehl“ durch setzten des Wertes „OK“ den Erfolg. Beim Schreiben holt sich der ESP32 den gewünschten Wert aus der entsprechenden Variablen und überträgt ihn über den CAN-Bus (durch aufeinanderfolgende Tastenbefehle) an das Steuergerät.

Das war’s zu den Grundlagen, als nächstes beschreibe ich, wie ihr es nachbauen könnt.

Komponenten:

Grundvoraussetzung ist, dass ihr die Raspberrymatic auf dem Raspberry Pi und das WLAN wie im ersten Teil der Smart Camper Artikel beschrieben aufgebaut habt.

Zusätzlich zu diesen Komponenten braucht ihr noch:

1x ESP32 Development Board (ca. 10€)
1x MCP2515 CAN Bus Shield (ca. 5€)
1x Satz Jumper Drähte (ca. 5€)

Installation:

Vorbereitungen in der Homematic WEB UI

Variable Typ Werte
HomeMatic_Befehl String XXX_Lesen / XXX_Schreiben
Status_Heizung Werteliste 0;1
Lueftug_Heizung Werteliste 0;1;2;3
SollTemp_Bad Zahl 10-40
SollTemp_IN Zahl 10-40
Status_Kuehlschrank Werteliste 0,1,2,3,4,5,6
Boiler Werteliste 0;1
Wasserstand Zahl

 

Code auf den ESP32 installieren

MCP2515::ERROR MCP2515::setNormalMode()

{

modifyRegister(MCP_CANCTRL, CANCTRL_OSM, CANCTRL_OSM);

return setMode(CANCTRL_REQOP_NORMAL);

}

Sie führt dazu, dass eine CAN Nachricht nur ein einziges Mal gesendet wird und kein acknowledge erwartet wird. Im Datenblatt des MCP2525 als Single-Shot-Modus beschrieben.

Anpassung der spezifischen Variablen:

Der Quellcode für den ESP32 befindet sich am Ende des Artikels zum Download. Um ihn einzusetzen, müssen noch folgende Variablen angepasst werden:

Typ Bezeichnung Bsp:
const char* ssid “MeinWLAN”
const char* password “MeinPW”
const String IP_Adr_HomeMatic “192.168.1.2”
String ISE_HomeMatic_Befehl “1234”
String ISE_Status_Heizung “1235”
String ISE_Lueftug_Heizung “1236”
String ISE_SollTemp_Bad “1237”
String ISE_SollTemp_IN “1238“
String ISE_Status_Kuehlschrank “1239“
String ISE_Boiler “1240“
String ISE_Wasserstand “1241”

Verbindung der Kompoenten

ESP32 mit dem MCP2515 und dem Raspberry Pi verbinden wie im letzten Bild gezeigt. CAN-H und CAN-L können mit Jumper-Kabeln an das an das Westfalia Zentralsteuergerät (Diagnose Eingang) angesteckt werden.

Programme in der Homematic

Um bei Änderung einer Variablen zu veranlassen, dass diese an das Zentralsteuergerät übertragen wird, wird in der Homematic ein Programm geschrieben, das bei Änderung der Variablen ausgelöst wird. Gleichzeitig muss aber verhindert werden, dass das Programm ausgelöst wird, wenn über die XMLApi ein Wert vom ESP32 gelesen wird. Hierfür gibt es die zusätzliche Bedingung, dass der Befehl „OK” sein muss. Dies ist erst der Fall, wenn der Vorgang davor erfolgreich abgeschlossen wurde. Folgendes Beispiel zeigt das Programm, das natürlich für jede der Variablen angelegt werden muss.

Cloudmatic

Um das Ganze über das Handy steuern zu können, habe ich die neuen Funktionen in meiner CloudMatic hinzugefügt. Folgendes Bild zeigt die neue UI

Fazit

Nicht trivial nachzubauen, aber mit meiner Vorarbeit ist es in überschaubarer Zeit zu realisieren. Man sollte aber schonmal mit Microcontrollern gearbeitet haben. Jetzt kann ich von der Skipiste meine Heizung einschalten oder den Kühlschrank aktivieren, damit der kühl ist, wenn die Reise losgeht. Ich habe mein Ziel des Smart Campers zwar noch nicht erreicht, aber auf jeden Fall einen großen Schritt in die Richtung gemacht. Als nächstes mache ich mir Gedanken über die Türen und die Werte der Solaranlage.

#include <Arduino.h>
#include <mcp2515.h>
#include <HTTPClient.h>
#include <SPI.h>
#include "WiFi.h"

bool DEBUG_MODUS = true;//Auch Serielle eingaben werden als CAN-Nachrichten berücksichtigt + Serielle Infos

const char* ssid = "XXXX";
const char* password =  "XXXX";
const String IP_Adr_HomeMatic =  "192.168.178.2";

String ISE_HomeMatic_Befehl = "XXXX";
String ISE_ESP32_Boot_Count = "XXXX";

String ISE_Status_Heizung = "XXXX";
String ISE_Lueftug_Heizung = "XXXX";
String ISE_SollTemp_Bad = "XXXX";
String ISE_SollTemp_IN = "XXXX";
String ISE_Status_Kuehlschrank = "XXXX";
String ISE_Boiler = "XXXX";
String ISE_Wasserstand = "XXXX";

//RTC_DATA_ATTR = 16kB RTC-Memory bleibt im sleep modus erhalten
RTC_DATA_ATTR int bootCount = 0; 
RTC_DATA_ATTR byte DrehPosByte32 = 0x00;//Letzte Drehposition wird hier gehalten
RTC_DATA_ATTR byte SendenByte = 0x00;
const byte EnterKnopf = 0x40;
const byte RunterKnopf = 0x20;
const byte RaufKnopf = 0x80;

MCP2515 mcp2515(5); //CS an Pin 5
struct can_frame canMsg_TX; //Senden Nachricht
struct can_frame canMsg_RX; //Empfangen Nachricht


unsigned long Zeitstempel = 0;   // Zeitstempel für Timeout
const int Timeout = 10000; //Bricht die Bewegung im Menue nach dieser Zeit ab, weil davon auszugehen ist, das etwas nicht stimmt

int Zaehler_Nachricht = 0; //hält die aktuelle Anzahl der Wiederholungen
int Anzahl_Nachricht_Wiederholen = 10; //wie oft eine Nachricht (nur aktuelle Drehposition) gesendet wird, bis die Nächste erzeugt wird
                                      //bei 50 => 1 Sekunde bis nächsten Befehl
                                      //bei 6[empfohlen] => ca. 120ms/Befehl oder 8Befehle/Sekunde 
                                      //ACHTUNG: Wert muss Mindestens 2 sein, sonst werden Knöpfe gedrückt aber nicht mehr losgelassen

//Dann und Sonst-Befehle für die Menuepfade
const int ZielErreicht = 0;
const int RadPlus = 1;
const int RadMinus = 2;
const int Enter = 3;
const int Runter = 4;
const int Rauf = 5;

// Hier sind die Menuepfade der einzelnen Funtionen gespeichert:
// CANByte, Erwartungswert, BefehlDann,BefehlSonst 
byte MenuePos_KuehlschrankStufe [2][4] {  {5,0x15,Enter,RadPlus},
                                          {5,0xa5,ZielErreicht,RadPlus}};
byte MenuePos_HeizungSchalten [2][4] {    {5,0x16,Enter,RadPlus},
                                          {5,0xd6,ZielErreicht,RadPlus}};
byte MenuePos_HeizungLueftung [2][4] {    {5,0x16,Enter,RadPlus},
                                          {5,0xe6,ZielErreicht,RadPlus}};
byte MenuePos_HeizungTemp [3][4] {        {5,0x16,Enter,RadPlus},
                                          {5,0x16,ZielErreicht,RadPlus}};
byte MenuePos_HeizungTempBad [3][4] {     {5,0x16,Enter,RadPlus},
                                          {5,0x16,ZielErreicht,RadMinus}};
byte MenuePos_HeizungBoiler [2][4] {      {5,0x16,Enter,RadPlus},
                                          {5,0xe6,ZielErreicht,RadPlus}};
byte MenuePos_Wasserstand [2][4] {        {5,0x12,Enter,RadPlus},
                                          {5,0x12,ZielErreicht,ZielErreicht}};

//Setup
void setup1() {
  Serial.begin(115200);

}

void setup() {

  int WiFi_Connect_Versuche = 0;
  Serial.begin(115200);
  mcp2515.reset();
  mcp2515.setBitrate(CAN_500KBPS,MCP_8MHZ);
  mcp2515.setNormalMode();
  Serial_Senden("Setup OK");
  
  WiFi.begin(ssid, password);
  
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial_Senden("Connecting to WiFi..");
    ++WiFi_Connect_Versuche;
    if(WiFi_Connect_Versuche>20){
      ESP.restart();// Reset, wenn was WiFi Modul spinnt und nach 10sekunden noch keine Verbindung besteht
    }
  }
  ++bootCount;
  Serial_Senden("Verbunden");
  
  //Read_ISE_ID_Value(ISE_HomeMatic_Befehl);
  //Write_ISE_ID_Value(ISE_ESP32_Boot_Count, String(bootCount));

  //der Raspberry Pi Zero möchte etwas sagen
  pinMode(GPIO_NUM_34, INPUT_PULLUP);
  Serial_Senden(String(digitalRead(GPIO_NUM_34)));
  if (digitalRead(GPIO_NUM_34)==1){
    String Befehl = Read_ISE_ID_Value(ISE_HomeMatic_Befehl);
    String Funktion = split(Befehl, '_',0);
    String LesenSchreiben = split(Befehl, '_',1);
    Serial_Senden(Befehl);
    if (Funktion=="Alles"){
      CAN_BUS_LesenSchreiben("HeizungBoiler",LesenSchreiben );
      CAN_BUS_LesenSchreiben("HeizungTempBad",LesenSchreiben );
      CAN_BUS_LesenSchreiben("HeizungTemp",LesenSchreiben );
      CAN_BUS_LesenSchreiben("HeizungLueftung",LesenSchreiben );
      CAN_BUS_LesenSchreiben("HeizungSchalten",LesenSchreiben );
      CAN_BUS_LesenSchreiben("Wasserstand",LesenSchreiben );
      CAN_BUS_LesenSchreiben("Kuehlschrank",LesenSchreiben );
    } 
    else{
      CAN_BUS_LesenSchreiben(Funktion,LesenSchreiben );
    }  
  }

  //Schlafenszeit bis GPIO34 High
  esp_sleep_enable_ext0_wakeup(GPIO_NUM_34, HIGH);
  Serial_Senden("mcp2515 und ich gehen schlafen!!");
  mcp2515.setSleepMode();
  esp_deep_sleep_start();
}

//Endlosschleife
void loop() {
  //Das hier wird nie passieren, da vorher der SleepMode aktiviert wird
}

//true wenn Nachricht mit ID 0x200 erhalten (nur alle 2 mal)
bool CAN_Nachricht_0x200_erhalten(){
  if (mcp2515.readMessage(&canMsg_RX) == MCP2515::ERROR_OK) {
    if (canMsg_RX.can_id == 0x200){
      if(Zaehler_Nachricht <(Anzahl_Nachricht_Wiederholen/2)){
        Zaehler_Nachricht++;
        CAN_Senden(SendenByte);
      }
      else if (Zaehler_Nachricht <Anzahl_Nachricht_Wiederholen-1){
        Zaehler_Nachricht++;
        CAN_Senden(DrehPosByte32);
      }
      else {
        Zaehler_Nachricht = 0;
        return true;
      }
    }
  }    
  else if (DEBUG_MODUS == true){
    if (Serial.available() > 0){
      // read the incoming stringg
      String incomingString = Serial.readStringUntil('\n');
      for (int i = 0; i < 8; i++) {
        canMsg_RX.data[i] = byte(incomingString.toInt());
      }
      if(Zaehler_Nachricht <Anzahl_Nachricht_Wiederholen-1){
        Zaehler_Nachricht++;
        CAN_Senden(DrehPosByte32);
      }
      else {
        Zaehler_Nachricht = 0;
        return true;
      }
    }
  }
  return false;
}

void CAN_Senden(byte Byte0){
  SendenByte = Byte0;
  canMsg_TX.can_id = 0x351;
  canMsg_TX.can_dlc = 2;
  canMsg_TX.data[0] = Byte0;
  canMsg_TX.data[1] = 0x60;  
  mcp2515.sendMessage(&canMsg_TX);
}

void MenueRad_drehen(bool UZS){
  if (UZS == true){
    if(DrehPosByte32<32){++DrehPosByte32;}
    else{DrehPosByte32 = 0;}
  }
  else{
    if(DrehPosByte32!=0){--DrehPosByte32;}
    else{DrehPosByte32 = 31;}
  }
}

//Berechnet den Wert falls dieser nicht klar im Byte steht
float Wert_aus_Byte(byte ByteWert, int WertTyp){
  switch ( WertTyp )
  {
    case 1: //Temperatur
      if (ByteWert % 5 == 0){
        Serial_Senden(String(float(ByteWert)/10));
        return float(ByteWert)/10;
      }
      else{
        Serial_Senden(String(float(256+ByteWert)/10));
        return float(256+ByteWert)/10;
      }
      break; 
  case 2: //Boiler
      if (ByteWert == 0x96){
        return 1;
      }
      else{
        return 0;
      }
    default:
      return float(ByteWert);
  }
}
//Berechnet den Wert der in das Can Byte geschreiben werden muss
byte Byte_aus_Wert(float FloatWert, int WertTyp){
  switch ( WertTyp )
  {
    case 1: //Temperatur
      if (FloatWert >=26){
        return (FloatWert*10)-256;
      }
      else{
        return FloatWert*10;
      }
      break; 
    case 2: //Boiler
      if (FloatWert == 1){
        return 0x96;
      }
      else{
        return 0xa6;
      }
    default:
      return FloatWert;
  }
}

//Führt einen Tastenbefehl aus
bool BefehlAusfuehren (byte Befehl){
  switch ( Befehl )
  {
      case ZielErreicht: //0 = Erledigt
          CAN_Senden(DrehPosByte32);
          return false;
          //Letzter Befehl => Fertig
      case RadPlus://1 = Rad+
          MenueRad_drehen(true);
          CAN_Senden(DrehPosByte32);
          return true;
      case RadMinus://2 = Rad-
          MenueRad_drehen(false);
          CAN_Senden(DrehPosByte32);
          return true;
      case Enter://3 = OK
          CAN_Senden(DrehPosByte32|EnterKnopf);
          return true;
      case Runter://4 = Runter
          CAN_Senden(DrehPosByte32|RunterKnopf);
          return true;
      case Rauf://5 = Rauf
          CAN_Senden(DrehPosByte32|RaufKnopf);
          return true;
  }
}
//Liest ByteLesen und berechnet den Wert abhängig vom Typ (1=Temperatur)
float Wert_Lesen(byte ByteLesen, int WertTyp){
  return Wert_aus_Byte(canMsg_RX.data[ByteLesen],WertTyp);
}
//Schreibt den Sollwert (vom Ty Float) in ByteSetzen abhängig vom Typ (1=Temperatur)
float Wert_Setzen(float SollWert, int WertTyp, byte ByteSetzen){
  int Taste;
  Zeitstempel = millis();
  if (Wert_aus_Byte(canMsg_RX.data[ByteSetzen],WertTyp)>SollWert) {
    Taste = Runter;
  }
  else{Taste = Rauf; }
  do{
    if (CAN_Nachricht_0x200_erhalten()==true){
      if (Taste == Runter){
        if(Wert_aus_Byte(canMsg_RX.data[ByteSetzen],WertTyp)>SollWert){
          BefehlAusfuehren(Taste);
        }
        else{return 1;}
      }
      else{
        if(Wert_aus_Byte(canMsg_RX.data[ByteSetzen],WertTyp)<SollWert){
          BefehlAusfuehren(Taste);
        }
        else{return 1;}
      }
    }
  }while (millis() - Zeitstempel <= Timeout);//Timeout
  Serial_Senden("Timeout");
  return 0;
}
//Öffnet das ausgewählte Menue
bool MenueEintrag_auswaehlen(byte MenuePos [] [4]){
  int i = 0;
  Zeitstempel = millis();
  do{
    if (CAN_Nachricht_0x200_erhalten()==true){
      if(canMsg_RX.data[MenuePos[i][0]] != MenuePos[i][1]){//Wenn RegisterWert != Erwartungswert
         Serial_Senden("Falscher Wert");
         BefehlAusfuehren(MenuePos[i][3]);
      }
      else{ //Wenn RegisterWert == Erwartungswert
         Serial_Senden("Erwarteter Wert");
         if(BefehlAusfuehren(MenuePos[i][2])==false){
            //Zielposition erreicht
            return true;
         }
         ++i;
      }
    }
  }while (millis() - Zeitstempel <= Timeout);//Timeout
  Serial_Senden("Timeout");
  return false;
}
//Beendet das Menu und geht wieder ins Hauptmenue
bool Menue_Exit(){
  Zeitstempel = millis();
  do{
    if (CAN_Nachricht_0x200_erhalten()==true){
      if(canMsg_RX.data[5]==0x14){
        BefehlAusfuehren(Enter);
        return true;
      }
      else if(canMsg_RX.data[5]==0x35){
        BefehlAusfuehren(Enter);
        return true;
      }
      else if(canMsg_RX.data[5]==0x32){
        BefehlAusfuehren(Enter);
        return true;
      }
      else if(canMsg_RX.data[5]==0x36){
        BefehlAusfuehren(Enter);
        return true;
      }
      else{
        BefehlAusfuehren(RadPlus);
      }
    }
  }while (millis() - Zeitstempel <= Timeout);//Timeout
  Serial_Senden("Timeout");
  return false;
}

//Teilt einen String in 2 Teile
String split(String s, char delimiter, int index) {
  int Position = s.indexOf(delimiter);
  if (index == 0){
    return s.substring(0,Position);
  }
  else{
    return s.substring(Position+1,s.length());
  }
}

void Serial_Senden(String Text){
  if (DEBUG_MODUS == true){
    Serial.println(Text);
  }
}

float CAN_BUS_LesenSchreiben(String Funktion, String LesenSchreiben){
  bool Status_IO = false;
  byte WertByte = 0;
  byte WertTyp = 0;
  float RueckgabeWert = 0;
  String ISE_ID = "";
  if (Funktion=="Kuehlschrank"){
    Serial_Senden("Start Kühlschrankmenue:");
    Status_IO = Menue_Exit();
    if (Status_IO==true){
      Status_IO = MenueEintrag_auswaehlen(MenuePos_KuehlschrankStufe);
    }
    Serial_Senden(String(Status_IO));
    ISE_ID = ISE_Status_Kuehlschrank;
  }
  else if (Funktion=="Wasserstand"){
    Serial_Senden("Start Wasserstand:");
    Status_IO = Menue_Exit();
    if (Status_IO==true){
      Status_IO = MenueEintrag_auswaehlen(MenuePos_KuehlschrankStufe);
    }
    ISE_ID = ISE_Wasserstand;
  }
  else if (Funktion=="HeizungSchalten"){
    Serial_Senden("Start HeizungSchalten:");
    Status_IO = Menue_Exit();
    if (Status_IO==true){
      Status_IO = MenueEintrag_auswaehlen(MenuePos_HeizungSchalten);
    }
    WertByte = 4;
    WertTyp = 2;
    ISE_ID = ISE_Status_Heizung;
  }
  else if (Funktion=="HeizungLueftung"){
    Serial_Senden("Start HeizungLueftung:");
    Status_IO = Menue_Exit();
    if (Status_IO==true){
      Status_IO = MenueEintrag_auswaehlen(MenuePos_HeizungLueftung);
    }
    ISE_ID = ISE_Lueftug_Heizung;
  }
  else if (Funktion=="HeizungTemp"){
    Serial_Senden("Start HeizungTemp:");
    Status_IO = Menue_Exit();
    if (Status_IO==true){
      Status_IO = MenueEintrag_auswaehlen(MenuePos_HeizungTemp);
    }
    WertTyp = 1;
    ISE_ID = ISE_SollTemp_IN;
  }
  else if (Funktion=="HeizungTempBad"){
    Serial_Senden("Start HeizungTempBad:");
    Status_IO = Menue_Exit();
    if (Status_IO==true){
      Status_IO = MenueEintrag_auswaehlen(MenuePos_HeizungTempBad);
    }
    WertTyp = 1;
    ISE_ID = ISE_SollTemp_Bad;
  }
  else if (Funktion=="HeizungBoiler"){
    Serial_Senden("Start HeizungBoiler:");
    Status_IO = Menue_Exit();
    if (Status_IO==true){
      Status_IO = MenueEintrag_auswaehlen(MenuePos_HeizungBoiler);
    }
    WertByte = 4;
    WertTyp = 2;
    ISE_ID = ISE_Boiler;
  }  
  if(Status_IO==true){
    if (LesenSchreiben == "Schreiben"){
      Serial_Senden("Start Schreiben:");
      String SollWert_String = Read_ISE_ID_Value(ISE_ID);
      float SollWert = SollWert_String.toFloat();
      RueckgabeWert = Wert_Setzen(SollWert,WertTyp,WertByte);
    }
    else{
      Serial_Senden("Start Lesen:");
      RueckgabeWert = Wert_Lesen(WertByte, WertTyp);
      Serial_Senden(String(RueckgabeWert));
      Write_ISE_ID_Value(ISE_ID, String(RueckgabeWert));
    }
    Serial_Senden("Befehl OK = Bestaetigen");
    Write_ISE_ID_Value(ISE_HomeMatic_Befehl, "OK");
    Menue_Exit();
    Zeitstempel = millis();
    do{
      if (CAN_Nachricht_0x200_erhalten()==true){
        CAN_Senden(DrehPosByte32);
        return RueckgabeWert;
      }
     }while (millis() - Zeitstempel <= Timeout);//Timeout
  }
Write_ISE_ID_Value(ISE_HomeMatic_Befehl, "Fehler");
return 0;
}

//Liest den Value der ISE_ID über xmlapi aus
String Read_ISE_ID_Value(String ISE_ID){
   String Return_value = "";
   if ((WiFi.status() == WL_CONNECTED)) { 
 
    HTTPClient http;
    http.begin("http://" + IP_Adr_HomeMatic + "/addons/xmlapi/sysvar.cgi?ise_id=" + ISE_ID);
    int httpCode = http.GET();                                  
 
    if (httpCode > 0) {
 
        String payload = http.getString();
        //Serial_Senden(payload);
        int StartVal = payload.indexOf("value='")+7;
        int EndVal = payload.indexOf("'",StartVal);
        Return_value = payload.substring(StartVal,EndVal);
      }
 
    else {
      Serial_Senden("Error on HTTP request Homematic xmlapi");
    }
    http.end();
  }
  return Return_value;
}

//Setzt den Value der ISE_ID über xmlapi
void Write_ISE_ID_Value(String ISE_ID, String ISE_Value){
   String Return_value = "";
   if ((WiFi.status() == WL_CONNECTED)) { 
    HTTPClient http;
    http.begin("http://" + IP_Adr_HomeMatic + "/addons/xmlapi/statechange.cgi?ise_id=" + ISE_ID + "&new_value=" + ISE_Value);
    int httpCode = http.GET();                                        
 
    if (httpCode > 0) {
 
        String payload = http.getString();
        int StartVal = payload.indexOf("new_value")+11;
        int EndVal = payload.indexOf(" ",StartVal)-1;
        Return_value = payload.substring(StartVal,EndVal);
        if(Return_value.equals(ISE_Value)){
          Serial_Senden("ISE_Value: " + ISE_ID + " gesetzt:" + ISE_Value);
        }
        else{
          Serial_Senden("Write_ISE_ID_Value error");
        }
      }
    else {
      Serial_Senden("Error on HTTP request Homematic xmlapi");
    }
    http.end();
  }
}

One Comment

  • Mirko sagt:

    Hallo Ihr 2,

    Vielen Dank für den Beitrag, gut beschrieben und Reverse Engineerd. Verfolge Eure Beiträge regelmäßig um die Wartezeit auf unseren Columbus 540AD zu verkürzen. Er soll im Juli kommen. Die eine oder andere Idee werden wir sicher auch aufgreifen.

    Lg Mirko

Leave a Reply