Victron MPPT Regler mit VEdirect

Super danke. Hätte auch noch Tests mit dem framehandler gemacht und das haut speziell beim MPPT über serial1 nicht hin.
Ich brauche nur die Werte aus meinem Beispiel.
Das heißt vom shunt über serial 2:
Soc, I, Ce, V
Vom MPPT über Serial 1:
VPV, PPV, Load, und MPPT Status
Dann auf dem anderen Screen die Ertrag Werte von heute und von gestern.

Das bekomme ich zu morgen gebacken.
Nachdem ich nu auch die Doku gelesen habe:
Dein Shunt schmeisst tatsächlich 33 unterschiedliche Werte raus.
Übertragen wird im Sekundentakt und es müssen nicht immer die selben Felder in einem Paket enthalten sein....

Aber das soll nicht dein Problem sein. :slight_smile:

1 Like

Test....
Auf Serial1 soll der MPPT dran hängen.
Wenn ich mich mit der CRC nicht vertan habe, soll nach ein paar Sekunden was im Seriellen Monitor erscheinen.

// Forensketch  
// https://forum.arduino.cc/

#include <Streaming.h>     // https://github.com/janelia-arduino/Streaming

//#define DEBUG              // Wenn aktiviert, werden Zwischenwerte ausgegeben

#ifdef DEBUG
  #define DBG_PRINTLN(...) Serial << __VA_ARGS__ << endl
#else
  #define DBG_PRINTLN(...)
#endif
constexpr uint8_t maxLabelLen {15};
constexpr uint8_t maxValueLen {34};

struct FIELD
{
  char label[maxLabelLen];
  char value[maxValueLen];
};


enum class VE_STATE  {LABEL, VALUE, CHECK};

struct VEMSG
{
  FIELD field;
  uint8_t crc = 0;
  VE_STATE state;
  uint8_t idx = 0;
} vemsg;



struct VE_MPPT
{
  uint32_t V;
  uint32_t VPV;
  uint32_t PPV;
  int32_t I;
  int32_t IL;
  bool LOAD;
  bool RELAIS;
  uint32_t OR;
  uint32_t H19;
  uint32_t H20;
  uint32_t H21;
  uint32_t H22;
  uint32_t H23;
  uint8_t ERR;
  uint8_t CS;
  char FW[10];
  uint16_t PID;
  char SER[12];
  uint16_t HSDS;
  uint8_t MPPT;
};

VE_MPPT t_MPPT;   // temporäres Struct für Werteaufnahme

VE_MPPT ve_mppt;


constexpr uint32_t intervall {3000};
uint32_t lastMillis;


void setup()
{
  Serial.begin(115200);
  Serial1.begin(19200);
  delay(300);
  Serial << (F("\r\nStart...\r\n")) << endl;
  initve_mppt();
}

void  loop()
{
  debugPrint(intervall, lastMillis);
}

void debugPrint(const uint32_t intervall, uint32_t &lastMillis)
{
  if (millis() - lastMillis > intervall)
  {
    Serial << F("ProduktID: ")     << _HEX(getVePid()) << endl;
    Serial << F("Firmware: ")     <<  getVeFw() << endl;
    Serial << F("Seriennummer: ") << getVeSer() << endl;
    Serial << F("Spannung: ")      << getVeV() / 1000.0 << "V" << endl;
    Serial << F("Strom: ")         << getVeI() / 1000.0 << "A" << endl;
    Serial << F("PanelSpannung: ") << getVeVpv() / 1000.0 << "V" << endl;
    Serial << endl;
    lastMillis = millis();
  }
}
//

void serialEvent1()                    //
{
  while (Serial1.available())
  { veReadIn(Serial1.read()); }
}

// GETTER
/*INDENT-OFF*/
uint32_t getVePid()   { return (ve_mppt.PID); }
char *getVeFw()       { return (ve_mppt.FW); }
char *getVeSer()      { return (ve_mppt.SER); }
uint32_t getVeV()     { return (ve_mppt.V); }
uint32_t getVeVpv()   { return (ve_mppt.VPV); }
uint32_t getVePpv()   { return (ve_mppt.PPV); }
int32_t getVeI()      { return (ve_mppt.I); }
int32_t getVeIl()     { return (ve_mppt.IL); }
bool getVeLoad()      { return (ve_mppt.LOAD); }
bool getVeRelais()    { return (ve_mppt.RELAIS); }
uint32_t getVeOr()    { return (ve_mppt.OR); }
uint32_t getVeH19()   { return (ve_mppt.H19); }
uint32_t getVeH20()   { return (ve_mppt.H20); }
uint32_t getVeH21()   { return (ve_mppt.H21); }
uint32_t getVeH22()   { return (ve_mppt.H22); }
uint32_t getVeH23()   { return (ve_mppt.H23); }
uint8_t getVeErr()    { return (ve_mppt.ERR); }
uint32_t getVeCs()    { return (ve_mppt.CS); }
uint32_t getVeHsds()  { return (ve_mppt.HSDS); }
uint8_t getVeMppt()   { return (ve_mppt.MPPT); }
/*INDENT-ON*/


// Splittet Eingangsdaten
void veReadIn(const char inChar)
{
  if (inChar == 0x0D)                                                // once field End ?
  { vemsg.state = VE_STATE::CHECK; }                                 // field Split
  else
  { vemsg.crc = (vemsg.crc + static_cast<uint8_t>(inChar)) & 0xFF; } // add
  if (inChar == 0x0A)
  {
    vemsg.state = VE_STATE::LABEL;
    return;
  }
  switch (vemsg.state)                                               //
  {
    case VE_STATE::LABEL:               // read Label
      //Serial.print(inChar);
      if (inChar == '\t')
      {
        vemsg.idx = 0;
        vemsg.state = VE_STATE::VALUE;
      }
      else if (vemsg.idx < maxLabelLen)
      { vemsg.field.label[vemsg.idx++] = toupper(inChar); }
      break;
    case VE_STATE::VALUE:              // read Value;
      //Serial.print(inChar);
      if (vemsg.idx < maxValueLen)
      {
        //Serial.print("AA");
        vemsg.field.value[vemsg.idx++] = inChar;
        // Serial.print(vemsg.field.value);
      }
      break;
    case VE_STATE::CHECK:
      // Serial.println();
      if (strcmp(vemsg.field.label, "CHECKSUM") == 0)  // Last Field?
      {
        if (vemsg.crc == 0)                      // crc OK
        { temp2mppt(); }                         // struct free for all
        initRead();                              // clear all Data
      }
      else
      {
        set_tempMPPT();
        initField();
      }
      vemsg.crc = (vemsg.crc + static_cast<uint8_t>(inChar)) & 0xFF;
      vemsg.idx = 0;
      break;
    default:
      initRead();
      break;
  }
}
//
// Temporären Puffer in Arbeitspuffer kopieren
void temp2mppt()
{
  ve_mppt.V = t_MPPT.V;
  ve_mppt.VPV = t_MPPT.VPV;
  ve_mppt.PPV = t_MPPT.PPV;
  ve_mppt.I = t_MPPT.I;
  ve_mppt.IL = t_MPPT.IL;
  ve_mppt.LOAD = t_MPPT.LOAD;
  ve_mppt.RELAIS = t_MPPT.RELAIS;
  ve_mppt.OR = t_MPPT.OR;
  ve_mppt.H19 = t_MPPT.H19;
  ve_mppt.H20 = t_MPPT.H20;
  ve_mppt.H21 = t_MPPT.H21;
  ve_mppt.H22 = t_MPPT.H22;
  ve_mppt.H23 = t_MPPT.H23;
  ve_mppt.ERR = t_MPPT.ERR;
  ve_mppt.CS = t_MPPT.CS;
  strcpy(ve_mppt.FW, t_MPPT.FW);
  ve_mppt.PID = t_MPPT.PID;
  strcpy(ve_mppt.SER, t_MPPT.SER);
  ve_mppt.HSDS = t_MPPT.HSDS;
  ve_mppt.MPPT = t_MPPT.MPPT;
}
//
// ausgelesene Daten in temporären Puffer
void set_tempMPPT()
{
  if (strcmp(vemsg.field.label, "V") == 0)
  { t_MPPT.V      = atol(vemsg.field.value); }
  else if (strcmp(vemsg.field.label, "VPV") == 0)
  { t_MPPT.VPV    = atol(vemsg.field.value); }
  else if (strcmp(vemsg.field.label, "PPV") == 0)
  { t_MPPT.PPV    = atol(vemsg.field.value); }
  else if (strcmp(vemsg.field.label, "I") == 0)
  { t_MPPT.I      = atol(vemsg.field.value); }
  else if (strcmp(vemsg.field.label, "IL") == 0)
  { t_MPPT.IL     = atol(vemsg.field.value); }
  else if (strcmp(vemsg.field.label, "LOAD") == 0)
  { t_MPPT.LOAD   = strcmp(vemsg.field.value, "ON") == 0 ? true : false; }
  else if (strcmp(vemsg.field.label, "RELAIS") == 0)
  { t_MPPT.RELAIS = strcmp(vemsg.field.value, "ON") == 0 ? true : false; }
  else if (strcmp(vemsg.field.label, "OR") == 0)
  { t_MPPT.OR     = atol(vemsg.field.value); }
  else if (strcmp(vemsg.field.label, "H19") == 0)
  { t_MPPT.H19    = atol(vemsg.field.value); }
  else if (strcmp(vemsg.field.label, "H20") == 0)
  { t_MPPT.H20    = atol(vemsg.field.value); }
  else if (strcmp(vemsg.field.label, "H21") == 0)
  { t_MPPT.H21    = atol(vemsg.field.value); }
  else if (strcmp(vemsg.field.label, "H22") == 0)
  { t_MPPT.H22    = atol(vemsg.field.value); }
  else if (strcmp(vemsg.field.label, "H23") == 0)
  { t_MPPT.H23    = atol(vemsg.field.value); }
  else if (strcmp(vemsg.field.label, "ERR") == 0)
  { t_MPPT.ERR    = atoi(vemsg.field.value); }
  else if (strcmp(vemsg.field.label, "CS") == 0)
  { t_MPPT.CS     = atoi(vemsg.field.value); }
  else if (strcmp(vemsg.field.label, "FW") == 0)
  { strcpy(t_MPPT.FW, vemsg.field.value); }
  else if (strcmp(vemsg.field.label, "PID") == 0)
  { t_MPPT.PID    = strtol(vemsg.field.value, NULL, 16); }
  else if (strcmp(vemsg.field.label, "SER#") == 0)
  { strcpy(t_MPPT.SER, vemsg.field.value); }
  else if (strcmp(vemsg.field.label, "HSDS") == 0)
  { t_MPPT.HSDS   = atoi(vemsg.field.value); }
  else if (strcmp(vemsg.field.label, "MPPT") == 0)
  { t_MPPT.MPPT   = atoi(vemsg.field.value); }
}
//
// Feldsatz leeren
void initField()
{
  memset(vemsg.field.label, '\0', maxLabelLen);
  memset(vemsg.field.value, '\0', maxValueLen);
}
//
//
void initRead()
{
  initField();
  vemsg.crc = 0;
  vemsg.state = VE_STATE::LABEL;
  vemsg.idx = 0;
}
//
void initve_mppt()
{
  ve_mppt.V = 0;
  ve_mppt.VPV = 0;
  ve_mppt.PPV = 0;
  ve_mppt.I = 0;
  ve_mppt.IL = 0;
  ve_mppt.LOAD = false;
  ve_mppt.RELAIS = false;
  ve_mppt.OR = 0;
  ve_mppt.H19 = 0;
  ve_mppt.H20 = 0;
  ve_mppt.H21 = 0;
  ve_mppt.H22 = 0;
  ve_mppt.H23 = 0;
  ve_mppt.ERR = 0;
  ve_mppt.CS = 0;
  memset (ve_mppt.FW, '\0', sizeof(ve_mppt.FW));
  ve_mppt.PID = 0;
  memset (ve_mppt.SER, '\0', sizeof(ve_mppt.SER));
  ve_mppt.HSDS = 0;
  ve_mppt.MPPT = 0;
}

Moin,
Ich hab einen Test durchlaufen lassen können.
Meine CRC-Berechnerei ist richtig.
Was falsch ist, ist die Annahme das sich die Labels alle in Großbuchstaben halten.
Darum habe ich diese Zeile:

im obigen Code geändert in:
{ vemsg.field.label[vemsg.idx++] = toupper(inChar); }
und schon sollte es funktionieren :slight_smile:

Ok. Ich werde das dann mal Testen. Muß nur noch etwas den Camper anheizen.

Start...

ProduktID: A060
Firmware: 164
Seriennummer: HQ2423Q2KQP
Spannung: 12.83V
Strom: -4.02A
PanelSpannung: 0.01V

ProduktID: A060
Firmware: 164
Seriennummer: HQ2423Q2KQP
Spannung: 12.83V
Strom: -4.01A
PanelSpannung: 0.01V

ProduktID: A060
Firmware: 164
Seriennummer: HQ2423Q2KQP
Spannung: 12.83V
Strom: -4.00A
PanelSpannung: 0.01V

ProduktID: A060
Firmware: 164
Seriennummer: HQ2423Q2KQP
Spannung: 12.81V
Strom: -4.13A
PanelSpannung: 0.01V

schaut fürs erste schon mal sehr gut aus.

//Shunt Werte******************
// Batterieladung (SOC)
 // Batteriespannung (V)
 // Ladestrom (I)
 // Verbrauchte Ah (CE)
   
//Solar Werte Panel***************
 // Solarleistung (PPV)
 // Solarspannung (VPV)
 // Lademodus (CS)
  // Lastausgang (LOAD)
  
//Solar1 Ertrag*********************
// Solar Ertrag heute (H20)
// Max. Solar Leistung heute (H21)
// Solar Ertrag gestern (H22)
// Max. Solar Leistung gestern (H23)
 
// Solar2 Regler Daten****************
// Produkt ID (PID)
// Seriennummer (SER#)
 // Firmware Version (FW)

Das hier sind die Werte, die ich in mein Skript integriert brauche. Sie sind auch gleich auf die einzelnen Screen aufgeteilt. :slight_smile:

Das darfste selbst ausprobieren :slight_smile:
Es gibt einen Block der mit dem Kommentar // Getter beginnt.
Wenn Du im debugPrint() nach dem selben Muster, wie dort vorgestellt die jeweilige Funktion aufrufst, bekommst Du die entsprechenden Daten übergeben.

WICHTIG!
Du brauchst später bei der Integration KEINE zusätzliche Variable anzulegen, um Dir den Inhalt zu merken!
Ich halte mich streng an die Implementations guidlines von victron!
Das heißt, Du kannst mit dem Funktionsaufruf immer und immer wieder die jeweiligen Daten holen.

Und ich werd mal zusehen, wann ich es schaffe den Shunt zu bauen.

So, ich habe das mal in meinen Code eingearbeitet. Es funktioniert schon mal sehr gut. Jetzt brauche noch die die Funktionen für den Shunt, dann kann ich das mal im ganzen Testen. Aber bis jetzt ist es die eine der am best funktionierenden Versionen. Die Checksumme prüfen wir auch mit oder? Nach eine Frage: Wie kann ich die Serial << F("ProduktID: ") << _HEX(getVePid()) << endl; in mein Tft.print einfügen? Das bekomme ich einen Fehler.

:star_struck:

Ich ja :grin:
Ich halte mich an die Guidelines ohne Wenn und Aber.
Nur wenn die CRC stimmt, werden die temporären Werte in den Arbeitsbereich kopiert.
Später kommt noch ein Zähler dazu, damit nicht jede fehlerhafte Übertragung zu einem Fehler führt und dann werden die Werte aus dem Arbeitsbereich gelöscht, wenn 10 aufeinanderfolgende fehler auftreten.

Genau so, wie du das sonst mit serial.print machen würdest.
Serial.print(getVePid(), HEX));

ok. Super. So klappt das. Ich hoffe das bleibt auch so, wenn wir dann den Shunt noch mit an Serial2 gleichzeitig auslesen. Bin mir nicht ganz sicher, ob da nicht immer das abfragen von alle 3 Serial probleme macht. Was meinst du dazu?

Ich seh da keine Probleme.
Am späten Nachmittag bleibt mir etwas Zeit um das insgesamt noch zu verfeinern und dann schaun wa mal.
Wird vermutlich wieder spät.

1 Like

vielleicht doch etwas zu früh gefreut. Problem:
wenn ich in einen Screen des MPPT wechsle, dann braucht er teilweise ewig, bis er mir die richtigen Werte anzeigt und wenn sich dann ein Wert wie z. B. die Leistung ändert, dann muß ich den Screen erst verlassen und wieder aufrufen, damit ich die richtigen Werte bekomme.

Dann machst Du was falsch:-)

Also:
Die Routine für die Darstellung auf dem SerMon ist nur aller paar Sekunden - die Darstellung auf dem TFT muss natürlich da raus.
Auf dem TFT musst du nattürlich selbst dafür sorgen, dass die Werte aktuallisiert werden.

Zeig doch mal Code.

Wenn Du das lesen kannst ....
Dann darfst Du mir erzählen, was für ein Gerät Du da hast und ich bastel zu dem Code in #43 Dir eine passende Routine erstmal für einen NANO.
Wenn Du dann auf dem seriellen Monitor was siehst, kann man damit später auch den ESP füttern...

Hier möchte ich mich mal kurz einklinken. Die print-Klasse wird auch von der Bibliothek für die Displayanzeige unterstützt. Was mit Serial geht, funktioniert auch mit tft. Beim Display mußt Du ggf. zusätzlich den Cursor plazieren, die Textfarbe und -größe und dergleichen angeben. Kleines Beispiel:

  tft.setCursor(0, 0, 2);
  tft << (F("\r\nStart...\r\n")) << endl;
  
  Serial << F("ProduktID: ") << _HEX(getVePid()) << endl;
  tft    << F("ProduktID: ") << _HEX(getVePid()) << endl;

  Serial.printf( "ProduktID: %X\n", getVePid() );
  tft.printf( "ProduktID: %X\n", getVePid() );

  char buf[20];
  snprintf(buf, sizeof(buf), "ProduktID: %X\n", getVePid() );
  Serial.print(buf);
  tft.print(buf);

Du kommst mir grade richtig... :grin: :grin: :grin:
Sieht Du, was der Codevon mir mal werden soll?

Mein nächster Ansatz ist, die Leseroutine von der Schnittstelle so universell zu machen dass ich das jeweilige Zielstruct mit übergebe.

Jetzt die Frage der Fragen:
wenn ich daraus eine Klasse baue (Ja, das wird mein erster Versuch dazu), wird dann die Funktion wiederverwendet oder kann ich mir das sparen, weil mit jeder Instanz ein eigener Funktionsaufruf generiert wird?

//**********************************************
// Display Typ zuweisen  1 = True Components   *
#define Display 1     /* 0 = AzDelivery        *
***********************************************/

// Tft Display
#include <Adafruit_GFX.h>
#include <TouchScreen.h>
#include <MCUFRIEND_kbv.h>
MCUFRIEND_kbv tft;

// Fastwrite Pin Steuerung
#include <digitalWriteFast.h>

// Uhr
#include <Wire.h>
#include "RTClib.h"
RTC_DS3231 rtc;

// DHT22
#include "DHT.h"
const int DHTPIN = 53;
const int DHTTYPE = DHT22;
DHT dht(DHTPIN, DHTTYPE);
unsigned long lastDHTRead = 0;
const long dhtInterval = 2000;


// DS18B20
#include <OneWire.h>
#include <DallasTemperature.h>
const int ONE_WIRE_BUS = 51;
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature DS18B20(&oneWire);

#include <Streaming.h> 

//#define DEBUG              // Wenn aktiviert, werden Zwischenwerte ausgegeben

#ifdef DEBUG
  #define DBG_PRINTLN(...) Serial << __VA_ARGS__ << endl
#else
  #define DBG_PRINTLN(...)
#endif

// Touch Panel
#define MINPRESSURE 100
#define MAXPRESSURE 1000

#if Display
//True Components Display
constexpr int XP = 8, XM = A2, YP = A3, YM = 9; //320x480 
constexpr int TS_LEFT = 921, TS_RT = 107, TS_TOP = 73, TS_BOT = 923;
#else
//AZ Delivery Display
constexpr int XP = 6, XM = A2, YP = A1, YM = 7; //320x480 
constexpr int TS_LEFT = 187, TS_RT = 916, TS_TOP = 939, TS_BOT = 211;
#endif

TouchScreen ts = TouchScreen(XP, YP, XM, YM, 300);
TSPoint p;

// tft Button
Adafruit_GFX_Button on_btn, off_btn, page1_btn, page2_btn, page3_btn, page4_btn, page5_btn, page6_btn, page7_btn;;
Adafruit_GFX_Button ok_btn, cncl_btn, plus_btn, minus_btn;
Adafruit_GFX_Button menu_btn, info_btn, back_btn, next_btn;
int pixel_x, pixel_y;

//Button entprellen
unsigned long lastPressTime = 0;
const unsigned long debounceDelay = 950; // Entprellzeit

// Relais Heizung
constexpr int RELAISHEIZUNG = 52; 

// Aktor Alarm
constexpr int RELAISINNENLICHT = 29;

// Relais Arbeitsscheinwerfer
constexpr int RELAISSCHEINWERFER = 22;

// Alarm Kontakte
constexpr int AKONTAKT = 27;
constexpr int SKONTAKT = 23;
int Alarm_ausgeloest = 0;

// Timer 
unsigned long Timer1500 = 1500;
unsigned long Timer2500 = 2500;
unsigned long Timer5000 = 5000;

unsigned long oldMillis_1500 = 0;
unsigned long oldMillis_2500 = 0;
unsigned long oldMillis_5000 = 0;

// Farben definieren
constexpr uint16_t BLACK = 0x0000, BLUE = 0x001F, RED = 0xF800, DARKGREEN = 0x02C2, CYAN = 0x07FF, WHITE = 0xFFFF, YELLOW = 0xFFE0;

// Victron MPPT + Shunt
//char buf[33];
char bufShunt[33];
//************************************************************ MPPT Code aus Forum************************************************************
constexpr uint8_t maxLabelLen {15};
constexpr uint8_t maxValueLen {34};

struct FIELD
{
  char label[maxLabelLen];
  char value[maxValueLen];
};


enum class VE_STATE  {LABEL, VALUE, CHECK};

struct VEMSG
{
  FIELD field;
  uint8_t crc = 0;
  VE_STATE state;
  uint8_t idx = 0;
} vemsg;



struct VE_MPPT
{
  uint32_t V;
  uint32_t VPV;
  uint32_t PPV;
  int32_t I;
  int32_t IL;
  bool LOAD;
  bool RELAIS;
  uint32_t OR;
  uint32_t H19;
  uint32_t H20;
  uint32_t H21;
  uint32_t H22;
  uint32_t H23;
  uint8_t ERR;
  uint8_t CS;
  char FW[10];
  uint16_t PID;
  char SER[12];
  uint16_t HSDS;
  uint8_t MPPT;
};

VE_MPPT t_MPPT;   // temporäres Struct für Werteaufnahme

VE_MPPT ve_mppt;


constexpr uint32_t intervall {3000};
uint32_t lastMillis;
//**********************************************************************************
// Screen
enum pageId {MENU, BATTERIE, SOLAR, SOLAR1, SOLAR2, WETTER, HEIZUNG, ALARM, SCHEINWERFER};

// Standard Seite
unsigned int currentPage = MENU, oldPage = -1;

//*****************************************************************************Setup***********************************************************************************************************************************************
void setup(void) { 
  Serial.begin(115200);
  Serial1.begin(19200);
  Serial2.begin(19200);
  Serial3.begin(9600);
  tft.begin();
  tft.setRotation(0);
  tft.fillScreen(BLACK);
  rtc.begin();
  dht.begin();
  DS18B20.begin();
  pinModeFast(RELAISHEIZUNG, OUTPUT);
  pinModeFast(RELAISINNENLICHT, OUTPUT);
  pinModeFast(RELAISSCHEINWERFER, OUTPUT);
  pinModeFast(SKONTAKT, INPUT_PULLUP);
  initve_mppt();
  //rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
  //rtc.adjust(DateTime(2025, 1, 16, 13, 59, 0)); //zum manuellen einstellen der Uhrzeit
   
  currentPage = WETTER;
}
//*********************************************************************************************************************************************************************************************************************************

bool down ;
void loop(void) {
  down = Touch_getXY();
  switch (currentPage) {
    case MENU:
      if (currentPage != oldPage)   drawMenuScreen();
      redrawButtons();
      
      if (page1_btn.justPressed()) changePage(HEIZUNG, page1_btn);
      if (page2_btn.justPressed()) changePage(BATTERIE, page2_btn);
      if (page3_btn.justPressed()) changePage(SOLAR1, page3_btn);
      if (page4_btn.justPressed()) changePage(WETTER, page4_btn);
      if (page5_btn.justPressed()) changePage(SOLAR, page5_btn);
      if (page6_btn.justPressed()) changePage(ALARM, page6_btn);
      if (page7_btn.justPressed()) changePage(SCHEINWERFER, page7_btn);
      break;

    case HEIZUNG:
      if (currentPage != oldPage) {
        Heizung();
        currentPage = MENU;
      }
      break;
    
    case WETTER:
      if (currentPage != oldPage) drawWetterScreen();
        getTemperatur();

        if (millis() - oldMillis_1500 >= Timer1500) {
        getUhr();
        oldMillis_1500 = millis();
        }
                              
      menu_btn.press(down && menu_btn.contains(pixel_x, pixel_y));

      if (menu_btn.justReleased())
        menu_btn.drawButton();

      if (millis() - lastPressTime > debounceDelay) {
        if (menu_btn.justPressed()) {
          menu_btn.drawButton(true);
          currentPage = MENU;
          lastPressTime = millis();
        }
      }
      break;

    case BATTERIE:
      if (currentPage != oldPage)   drawBatterieScreen();
      getShunt();
             
      menu_btn.press(down && menu_btn.contains(pixel_x, pixel_y));
      
      if (menu_btn.justReleased())
        menu_btn.drawButton();

      if (millis() - lastPressTime > debounceDelay) {
        if (menu_btn.justPressed()) {
          menu_btn.drawButton(true);
          currentPage = MENU;
          lastPressTime = millis();
        }
      }
      break;      

    case SOLAR:
      if (currentPage != oldPage)   drawSolarScreen();
      getSolar();
       
      menu_btn.press(down && menu_btn.contains(pixel_x, pixel_y));
      next_btn.press(down && next_btn.contains(pixel_x, pixel_y));
      if (menu_btn.justReleased())
        menu_btn.drawButton();
      
      if (millis() - lastPressTime > debounceDelay) {
        if (menu_btn.justPressed()) {
          menu_btn.drawButton(true);
          currentPage = MENU;
          lastPressTime = millis();
        }
      }
      break;

    case SOLAR1:
      if (currentPage != oldPage)   drawSolar1Screen();
      getSolar1();
      
      menu_btn.press(down && menu_btn.contains(pixel_x, pixel_y));
      next_btn.press(down && next_btn.contains(pixel_x, pixel_y));
      if (menu_btn.justReleased())
        menu_btn.drawButton();
      if (next_btn.justReleased())
        next_btn.drawButton();

      if (millis() - lastPressTime > debounceDelay) {
        if (menu_btn.justPressed()) {
          menu_btn.drawButton(true);
          currentPage = MENU;
          lastPressTime = millis();
        }
      }
      if (millis() - lastPressTime > debounceDelay) {
        if (next_btn.justPressed()) {
          next_btn.drawButton(true);
          currentPage = SOLAR2;
          lastPressTime = millis();
        }
      }
      break;

    case SOLAR2:
      if (currentPage != oldPage)   drawSolar2Screen();
      getSolar2();
      
      menu_btn.press(down && menu_btn.contains(pixel_x, pixel_y));
      
      if (menu_btn.justReleased())
        menu_btn.drawButton();
      
      if (millis() - lastPressTime > debounceDelay) {
        if (menu_btn.justPressed()) {
          menu_btn.drawButton(true);
          currentPage = MENU;
          lastPressTime = millis();
        }
      }
      break;
    
    case ALARM:
      if (currentPage != oldPage) drawAlarmScreen();
      Ausgeloest();
      getTemperatur();

      if (millis() - oldMillis_1500 >= Timer1500) {
        getUhr();
        oldMillis_1500 = millis();
      }

      menu_btn.press(down && menu_btn.contains(pixel_x, pixel_y));
      
      if (menu_btn.justReleased())
        menu_btn.drawButton();

      if (millis() - lastPressTime > debounceDelay) {
        if (menu_btn.justPressed()) {
          menu_btn.drawButton(true);
          currentPage = MENU;
          lastPressTime = millis();
        }
      }
      break;
  
    case SCHEINWERFER:
      if (currentPage != oldPage) {        
        Scheinwerfer();
        updateScheinwerferButton();
        currentPage = MENU;
      }
      break;
  }

/*  if (oldPage == currentPage) {
    down = Touch_getXY();
  } 
  else {
    down = false;
  }*/
  Sabotage();
  Bluetooth();
}

/**********************************************************************************************************************************************************************************************************************************
                                                                                                   SCREENS 
***********************************************************************************************************************************************************************************************************************************/
// Hauptbildschirm
void drawMenuScreen() {
  Alarm_ausgeloest = 0;
  tft.fillScreen(BLACK);
  tft.setTextColor(BLUE);
  digitalWriteFast(RELAISINNENLICHT, LOW);
  tft.setCursor(50, 8);
  tft.println(F("WoMo-Monitor"));
  tft.setTextSize(2);

  page1_btn.initButton(&tft, 120, 60, 210, 35 , WHITE, CYAN, BLACK, "Heizung", 2);
  page2_btn.initButton(&tft, 120, 172, 210, 35 , WHITE, CYAN, BLACK, "Batterie", 2);
  page3_btn.initButton(&tft, 175, 227, 100, 35 , WHITE, CYAN, BLACK, "Sol his", 2);
  page4_btn.initButton(&tft, 120, 115, 210, 35 , WHITE, CYAN, BLACK, "Temp", 2);
  page5_btn.initButton(&tft, 65, 227, 100, 35 , WHITE, CYAN, BLACK, "Sol akt", 2);
  page6_btn.initButton(&tft, 65, 282, 100, 35 , RED, CYAN, RED, "Alarm", 2);
  updateScheinwerferButton();

  page1_btn.drawButton(false);
  page2_btn.drawButton(false);
  page3_btn.drawButton(false);
  page4_btn.drawButton(false);
  page5_btn.drawButton(false);
  page6_btn.drawButton(false);
  
  tft.drawRoundRect(5, 32, 230, 280, 10, BLUE);
  oldPage = currentPage;
}

// Farbe Scheinwerfer Button nach Zustand ändern
void updateScheinwerferButton() {
  if (digitalReadFast(RELAISSCHEINWERFER) == HIGH) {
    page7_btn.initButton(&tft, 175, 282, 100, 35 , WHITE, YELLOW, BLACK, "Scheinw", 2);
  } 
  else {
    page7_btn.initButton(&tft, 175, 282, 100, 35 , WHITE, CYAN, BLACK, "Scheinw", 2);
  }
  page7_btn.drawButton(false);
}

// Shunt Werte Batterie 
void drawBatterieScreen() {
  tft.setTextSize(2);
  tft.fillScreen(BLACK);
  tft.setTextColor(WHITE,BLACK); 
  tft.setCursor(10, 10);
  tft.println(F("Batteriespannung V:"));
  tft.setCursor(10, 70);
  tft.println(F("Ladestrom A:"));
  tft.setCursor(10, 130);
  tft.println(F("verbrauchte AH:"));
  tft.setCursor(10, 190);
  tft.println(F("Ladestand %:"));
    
  menu_btn.initButton(&tft, 120, 295, 120, 35, WHITE, CYAN, BLACK, "MENU", 2);
  menu_btn.drawButton(false);
  oldPage = currentPage;
}

// Solar Leistung,Spannung, Modus Laden, Zustand Lastausgang
void drawSolarScreen() {
  tft.setTextSize(2); 
  tft.fillScreen(BLACK);
  tft.setTextColor(WHITE,BLACK); 
  tft.setCursor(0, 10);
  tft.println(F("Leistung W:"));
  tft.setCursor(0,70);
  tft.println(F("Spannung Panel V:"));
  tft.setCursor(0,130);
  tft.println(F("Laden Modus"));
  tft.setCursor(0, 190);
  tft.println(F("Zustand Lastausgang:"));  

  menu_btn.initButton(&tft, 120, 295, 120, 35, WHITE, CYAN, BLACK, "MENU", 2);
  menu_btn.drawButton(false);
  oldPage = currentPage;
}

// Solar Ertrag heute und gestern
void drawSolar1Screen() {
  tft.setTextSize(2); 
  tft.fillScreen(BLACK);
  tft.setTextColor(WHITE,BLACK); 
  tft.setCursor(0, 10);
  tft.println(F("Ertrag heute Wh:"));
  tft.setCursor(0,70);
  tft.println(F("P max heute W:"));
  tft.setCursor(0,130);
  tft.println(F("Ertrag gestern Wh:"));
  tft.setCursor(0,190);
  tft.println(F("P max gestern W:"));

  menu_btn.initButton(&tft, 60, 295, 80, 35, WHITE, CYAN, BLACK, "MENU", 2);
  next_btn.initButton(&tft, 180, 295, 80, 35, WHITE, CYAN, BLACK, "INFO", 2);
  menu_btn.drawButton(false);
  next_btn.drawButton(false);
  oldPage = currentPage;
}

// MPPT Regler Product ID, Seriennummer, Sofwarestand
void drawSolar2Screen() {
  tft.setTextSize(2); 
  tft.fillScreen(BLACK);
  tft.setTextColor(WHITE,BLACK); 
  tft.setCursor(0, 10);
  tft.println(F("Product ID:"));
  tft.setCursor(0,70);
  tft.println(F("Seriennummer:"));
  tft.setCursor(0,130);
  tft.println(F("Software Version:"));
  tft.setCursor(0,190);

  menu_btn.initButton(&tft,120, 295, 120, 35, WHITE, CYAN, BLACK, "MENU", 2);
  menu_btn.drawButton(false);
  oldPage = currentPage;
}

// Temperatur, Uhrzeit
void drawWetterScreen() {
  tft.fillScreen(BLACK);
  tft.setTextColor(WHITE, BLACK);
  tft.setTextSize(3);
  // Innenbereich
  tft.setCursor(10, 70);
  tft.print(F("innen:"));
  tft.drawLine(0, 60, tft.width() * 2, 60, WHITE);
  tft.drawLine(0, 185, tft.width() * 2, 185, WHITE);
  // Außenbereich
  tft.setCursor(10, 195);
  tft.print(F("aussen:"));
  menu_btn.initButton(&tft, 121, 295, 100, 35 , WHITE, CYAN, BLACK, "MENU", 2);
  menu_btn.drawButton(false);
  oldPage = currentPage;
}

// Alarm, Temperatur, Uhrzeit
void drawAlarmScreen() {
  tft.fillScreen(BLACK);
  tft.setTextColor(WHITE, BLACK);
  tft.setTextSize(3);
  // Innenbereich
  tft.setCursor(10, 70);
  tft.print(F("innen:"));
  tft.drawLine(0, 60, tft.width() * 2, 60, WHITE);
  tft.drawLine(0, 185, tft.width() * 2, 185, WHITE);
  // Außenbereich
  tft.setCursor(10, 195);
  tft.print(F("aussen:"));
  menu_btn.initButton(&tft,121, 295, 100, 35, WHITE, RED, BLACK, "AUS", 2);
  menu_btn.drawButton(false);
  oldPage = currentPage;
}

/***********************************************************************************************************************************************************************************************************************************
                                                                                                            Funktionen
*************************************************************************************************************************************************************************************************************************************/
//Touch display
bool Touch_getXY() {
  p = ts.getPoint();
  pinModeFast(YP, OUTPUT);
  pinModeFast(XM, OUTPUT);
  digitalWriteFast(YP, HIGH);
  digitalWriteFast(XM, HIGH);

  if (p.z > MINPRESSURE && p.z < MAXPRESSURE) {
    pixel_x = map(p.x, TS_LEFT, TS_RT, 0, tft.width());
    pixel_y = map(p.y, TS_TOP, TS_BOT, 0, tft.height());
    return true;
  }
  return false;
}

void redrawButtons() {
  page1_btn.press(down && page1_btn.contains(pixel_x, pixel_y));
  page2_btn.press(down && page2_btn.contains(pixel_x, pixel_y));
  page3_btn.press(down && page3_btn.contains(pixel_x, pixel_y));
  page4_btn.press(down && page4_btn.contains(pixel_x, pixel_y));
  page5_btn.press(down && page5_btn.contains(pixel_x, pixel_y));
  page6_btn.press(down && page6_btn.contains(pixel_x, pixel_y));
  page7_btn.press(down && page7_btn.contains(pixel_x, pixel_y));

  if (page1_btn.justReleased()) page1_btn.drawButton();
  if (page2_btn.justReleased()) page2_btn.drawButton();
  if (page3_btn.justReleased()) page3_btn.drawButton();
  if (page4_btn.justReleased()) page4_btn.drawButton();
  if (page5_btn.justReleased()) page5_btn.drawButton();
  if (page6_btn.justReleased()) page6_btn.drawButton();
  if (page7_btn.justReleased()) page7_btn.drawButton();
}

void changePage(unsigned int newPage, Adafruit_GFX_Button &btn) {
  if (millis() - lastPressTime > debounceDelay) {
    btn.drawButton(true);
    currentPage = newPage;
    lastPressTime = millis();
  }
}

//Heizung Relais schalten
void Heizung() {
  digitalWriteFast(RELAISHEIZUNG, HIGH);
  delay(1000); 
  digitalWriteFast(RELAISHEIZUNG, LOW);
  page1_btn.drawButton(false);
}

//Uhrzeit auslesen
void getUhr() {
  char daysOfTheWeek[7][12] = { "So", "Mo", "Di", "Mi", "Do", "Fr", "Sa"};

  tft.setTextSize(3);
  tft.setTextColor(WHITE, BLACK);
  tft.setCursor(180, 15);
  DateTime now = rtc.now();
  
  // Wochentag
  tft.print(daysOfTheWeek[now.dayOfTheWeek()]);

  // Uhrzeit
  tft.setCursor(10, 15);
  if (now.hour() < 10) {
    tft.print(F("0"));
    tft.print(now.hour());
  }
  else{
    tft.print(now.hour(), DEC);
  }
  tft.print(':');
  if (now.minute() < 10) {
    tft.print(F("0"));
    tft.print(now.minute());
  } 
  else{
    tft.print(now.minute(), DEC);
  }
  tft.print(':');
  if (now.second() < 10) {
    tft.print(F("0"));
    tft.print(now.second());
  } 
  else{
    tft.print(now.second(), DEC);
  }
}

void getTemperatur() {
  tft.setTextSize(3); 
  if (millis() - lastDHTRead >= dhtInterval) {
    lastDHTRead = millis();
    float h = dht.readHumidity();
    float te = dht.readTemperature();

    /*if (isnan(h) || isnan(te)) {
      Serial.println("Fehler beim Lesen des DHT-Sensors!");
      return;  // Springt aus der Funktion raus, falls Sensorfehler
    }*/

    //float h, te, h_alt, t_alt;
    float h_alt, t_alt;
    //h = dht.readHumidity();
    //te = dht.readTemperature();
    DS18B20.requestTemperatures();
    float tpa = DS18B20.getTempCByIndex(0);
  
    static float lastTe = -1000.0, lastTpa = -1000.0, lastH = -1.0;
    if (te != lastTe || h != lastH || tpa != lastTpa) {
          
      int tempColor = (te < 10) ? BLUE : (te >= 10 && te < 25) ? DARKGREEN : RED;
      tft.setTextColor(tempColor, BLACK);
      tft.setCursor(10, 105); 
      tft.print(te, 1);
      tft.setTextColor(WHITE, BLACK);
      tft.print(F(" "));
      tft.write(0xF7);
      tft.print(F("C"));

      int humidityColor = (h < 40) ? BLUE : (h >= 40 && h < 60) ? DARKGREEN : RED;
      tft.setTextColor(humidityColor, BLACK);
      tft.setCursor(10, 145);
      tft.print(h, 1);
      tft.setTextColor(WHITE, BLACK);
      tft.print(F(" "));
      tft.print(F("%"));

      int outsideTempColor = (tpa < 0) ? BLUE : (tpa >= 0 && tpa < 10) ? BLUE : (tpa >= 10 && tpa < 25) ? DARKGREEN : RED;
      tft.setTextColor(outsideTempColor, BLACK);
      tft.setCursor(10, 235);
      tft.print(tpa, 1);
      tft.setTextColor(WHITE, BLACK);
      tft.print(F(" "));
      tft.write(0xF7);
      tft.print(F("C"));

      lastTe = te;
      lastTpa = tpa;
      lastH = h;
    }
  }
}

void debugPrint(const uint32_t intervall, uint32_t &lastMillis)
{
  if (millis() - lastMillis > intervall)
  {
    Serial << F("ProduktID: ")     << _HEX(getVePid()) << endl;
    Serial << F("Firmware: ")     <<  getVeFw() << endl;
    Serial << F("Seriennummer: ") << getVeSer() << endl;
    Serial << F("Spannung: ")      << getVeV() / 1000.0 << "V" << endl;
    Serial << F("Strom: ")         << getVeI() / 1000.0 << "A" << endl;
    Serial << F("PanelSpannung: ") << getVeVpv() / 1000.0 << "V" << endl;
    Serial << endl;
    lastMillis = millis();
  }
}
//

void serialEvent1()                    //
{
  while (Serial1.available())
  { veReadIn(Serial1.read()); }
}

// GETTER
/*INDENT-OFF*/
uint32_t getVePid()   { return (ve_mppt.PID); }
char *getVeFw()       { return (ve_mppt.FW); }
char *getVeSer()      { return (ve_mppt.SER); }
uint32_t getVeV()     { return (ve_mppt.V); }
uint32_t getVeVpv()   { return (ve_mppt.VPV); }
uint32_t getVePpv()   { return (ve_mppt.PPV); }
int32_t getVeI()      { return (ve_mppt.I); }
int32_t getVeIl()     { return (ve_mppt.IL); }
bool getVeLoad()      { return (ve_mppt.LOAD); }
bool getVeRelais()    { return (ve_mppt.RELAIS); }
uint32_t getVeOr()    { return (ve_mppt.OR); }
uint32_t getVeH19()   { return (ve_mppt.H19); }
uint32_t getVeH20()   { return (ve_mppt.H20); }
uint32_t getVeH21()   { return (ve_mppt.H21); }
uint32_t getVeH22()   { return (ve_mppt.H22); }
uint32_t getVeH23()   { return (ve_mppt.H23); }
uint8_t getVeErr()    { return (ve_mppt.ERR); }
uint32_t getVeCs()    { return (ve_mppt.CS); }
uint32_t getVeHsds()  { return (ve_mppt.HSDS); }
uint8_t getVeMppt()   { return (ve_mppt.MPPT); }
/*INDENT-ON*/


// Splittet Eingangsdaten
void veReadIn(const char inChar)
{
  if (inChar == 0x0D)                                                // once field End ?
  { vemsg.state = VE_STATE::CHECK; }                                 // field Split
  else
  { vemsg.crc = (vemsg.crc + static_cast<uint8_t>(inChar)) & 0xFF; } // add
  if (inChar == 0x0A)
  {
    vemsg.state = VE_STATE::LABEL;
    return;
  }
  switch (vemsg.state)                                               //
  {
    case VE_STATE::LABEL:               // read Label
      //Serial.print(inChar);
      if (inChar == '\t')
      {
        vemsg.idx = 0;
        vemsg.state = VE_STATE::VALUE;
      }
      else if (vemsg.idx < maxLabelLen)
      { vemsg.field.label[vemsg.idx++] = toupper(inChar); }
      break;
    case VE_STATE::VALUE:              // read Value;
      //Serial.print(inChar);
      if (vemsg.idx < maxValueLen)
      {
        //Serial.print("AA");
        vemsg.field.value[vemsg.idx++] = inChar;
        // Serial.print(vemsg.field.value);
      }
      break;
    case VE_STATE::CHECK:
      // Serial.println();
      if (strcmp(vemsg.field.label, "CHECKSUM") == 0)  // Last Field?
      {
        if (vemsg.crc == 0)                      // crc OK
        { temp2mppt(); }                         // struct free for all
        initRead();                              // clear all Data
      }
      else
      {
        set_tempMPPT();
        initField();
      }
      vemsg.crc = (vemsg.crc + static_cast<uint8_t>(inChar)) & 0xFF;
      vemsg.idx = 0;
      break;
      default:
      initRead();
      break;
  }
}
//
// Temporären Puffer in Arbeitspuffer kopieren
void temp2mppt()
{
  ve_mppt.V = t_MPPT.V;
  ve_mppt.VPV = t_MPPT.VPV;
  ve_mppt.PPV = t_MPPT.PPV;
  ve_mppt.I = t_MPPT.I;
  ve_mppt.IL = t_MPPT.IL;
  ve_mppt.LOAD = t_MPPT.LOAD;
  ve_mppt.RELAIS = t_MPPT.RELAIS;
  ve_mppt.OR = t_MPPT.OR;
  ve_mppt.H19 = t_MPPT.H19;
  ve_mppt.H20 = t_MPPT.H20;
  ve_mppt.H21 = t_MPPT.H21;
  ve_mppt.H22 = t_MPPT.H22;
  ve_mppt.H23 = t_MPPT.H23;
  ve_mppt.ERR = t_MPPT.ERR;
  ve_mppt.CS = t_MPPT.CS;
  strcpy(ve_mppt.FW, t_MPPT.FW);
  ve_mppt.PID = t_MPPT.PID;
  strcpy(ve_mppt.SER, t_MPPT.SER);
  ve_mppt.HSDS = t_MPPT.HSDS;
  ve_mppt.MPPT = t_MPPT.MPPT;
}
//
// ausgelesene Daten in temporären Puffer
void set_tempMPPT()
{
  if (strcmp(vemsg.field.label, "V") == 0)
  { t_MPPT.V      = atol(vemsg.field.value); }
  else if (strcmp(vemsg.field.label, "VPV") == 0)
  { t_MPPT.VPV    = atol(vemsg.field.value); }
  else if (strcmp(vemsg.field.label, "PPV") == 0)
  { t_MPPT.PPV    = atol(vemsg.field.value); }
  else if (strcmp(vemsg.field.label, "I") == 0)
  { t_MPPT.I      = atol(vemsg.field.value); }
  else if (strcmp(vemsg.field.label, "IL") == 0)
  { t_MPPT.IL     = atol(vemsg.field.value); }
  else if (strcmp(vemsg.field.label, "LOAD") == 0)
  { t_MPPT.LOAD   = strcmp(vemsg.field.value, "ON") == 0 ? true : false; }
  else if (strcmp(vemsg.field.label, "RELAIS") == 0)
  { t_MPPT.RELAIS = strcmp(vemsg.field.value, "ON") == 0 ? true : false; }
  else if (strcmp(vemsg.field.label, "OR") == 0)
  { t_MPPT.OR     = atol(vemsg.field.value); }
  else if (strcmp(vemsg.field.label, "H19") == 0)
  { t_MPPT.H19    = atol(vemsg.field.value); }
  else if (strcmp(vemsg.field.label, "H20") == 0)
  { t_MPPT.H20    = atol(vemsg.field.value); }
  else if (strcmp(vemsg.field.label, "H21") == 0)
  { t_MPPT.H21    = atol(vemsg.field.value); }
  else if (strcmp(vemsg.field.label, "H22") == 0)
  { t_MPPT.H22    = atol(vemsg.field.value); }
  else if (strcmp(vemsg.field.label, "H23") == 0)
  { t_MPPT.H23    = atol(vemsg.field.value); }
  else if (strcmp(vemsg.field.label, "ERR") == 0)
  { t_MPPT.ERR    = atoi(vemsg.field.value); }
  else if (strcmp(vemsg.field.label, "CS") == 0)
  { t_MPPT.CS     = atoi(vemsg.field.value); }
  else if (strcmp(vemsg.field.label, "FW") == 0)
  { strcpy(t_MPPT.FW, vemsg.field.value); }
  else if (strcmp(vemsg.field.label, "PID") == 0)
  { t_MPPT.PID    = strtol(vemsg.field.value, NULL, 16); }
  else if (strcmp(vemsg.field.label, "SER#") == 0)
  { strcpy(t_MPPT.SER, vemsg.field.value); }
  else if (strcmp(vemsg.field.label, "HSDS") == 0)
  { t_MPPT.HSDS   = atoi(vemsg.field.value); }
  else if (strcmp(vemsg.field.label, "MPPT") == 0)
  { t_MPPT.MPPT   = atoi(vemsg.field.value); }
}
//
// Feldsatz leeren
void initField()
{
  memset(vemsg.field.label, '\0', maxLabelLen);
  memset(vemsg.field.value, '\0', maxValueLen);
}
//
//
void initRead()
{
  initField();
  vemsg.crc = 0;
  vemsg.state = VE_STATE::LABEL;
  vemsg.idx = 0;
}
//
void initve_mppt()
{
  ve_mppt.V = 0;
  ve_mppt.VPV = 0;
  ve_mppt.PPV = 0;
  ve_mppt.I = 0;
  ve_mppt.IL = 0;
  ve_mppt.LOAD = false;
  ve_mppt.RELAIS = false;
  ve_mppt.OR = 0;
  ve_mppt.H19 = 0;
  ve_mppt.H20 = 0;
  ve_mppt.H21 = 0;
  ve_mppt.H22 = 0;
  ve_mppt.H23 = 0;
  ve_mppt.ERR = 0;
  ve_mppt.CS = 0;
  memset (ve_mppt.FW, '\0', sizeof(ve_mppt.FW));
  ve_mppt.PID = 0;
  memset (ve_mppt.SER, '\0', sizeof(ve_mppt.SER));
  ve_mppt.HSDS = 0;
  ve_mppt.MPPT = 0;
}

//*******************************************************************Shunt Werte***********************************************************
void getShunt() {
  if (Serial2.available() > 0) {
    char firstChar = Serial2.peek();  

    // Falls HEX-Daten erkannt werden, ignorieren
    if (firstChar < 32 || firstChar > 126) {
      Serial2.read();  
      return;
    }

    String labelS = Serial2.readStringUntil('\t');  
    String valS = Serial2.readStringUntil('\n');  
    valS.trim();  
    
    Serial.println(labelS);
    Serial.println(valS);

    valS.toCharArray(bufShunt, sizeof(bufShunt));  
    float messwert = atof(bufShunt); 

     tft.setTextSize(2);

    // Batterieladung (SOC)
    if (labelS == "SOC") {
      float result = messwert / 10;  
      tft.setTextColor(WHITE, BLACK);
      tft.setCursor(90, 215);
      tft.println(static_cast<int>(result));  
    }

    // Batteriespannung (V)
    else if (labelS == "V") {
      float result = messwert / 1000.0;  
      tft.setTextColor(WHITE, BLACK);
      tft.setCursor(90, 35);
      tft.println(result);
    }

    // Ladestrom (I)
    else if (labelS == "I") {
      float result = messwert / 1000.0;  
      tft.setCursor(90, 95);
           
      if (messwert < 0) {
        tft.setTextColor(RED, BLACK);  
      } 
      else {
        tft.print(" ");
        tft.setTextColor(BLUE, BLACK);  
      }
      tft.println(result);
    }

    // Verbrauchte Ah (CE)
    else if (labelS == "CE") {
      float result = messwert / 1000.0;  
      tft.setTextColor(WHITE, BLACK);
      tft.setCursor(90, 155);
      tft.println(static_cast<int>(result));  
    }
  }
}

//*******************************************************************Solar Werte Panel*****************************************************
void getSolar() {
  tft.setTextSize(2);
  tft.setTextColor(WHITE, BLACK); 

  // Solarleistung (PPV)
  tft.setCursor(90, 35);
  tft.println(getVePpv()); 

  // Solarspannung (VPV)
  tft.setCursor(90, 95);
  tft.println(getVeVpv() / 1000); 

  // Lademodus (CS)
  tft.setCursor(90, 155);
  int mode = getVeCs();
    if (mode == 2) {
      tft.setTextColor(WHITE, BLACK);
      tft.println("Error");
    }
    else if (mode == 3) {
      tft.setTextColor(BLUE, BLACK);
      tft.println("BULK  ");
    }
    else if (mode == 4) {
      tft.setTextColor(BLUE, BLACK);
      tft.println("ABS   ");
    }
    else if (mode == 5) {
      tft.setTextColor(BLUE, BLACK);
      tft.println("FLOAT ");
    }
    else {
      tft.setTextColor(RED, BLACK); 
      tft.setCursor(90, 155);
      tft.println("OFF ");
    }

  // Lastausgang (LOAD)
  tft.setTextColor(WHITE, BLACK);
  tft.setCursor(90, 215);
  tft.setTextColor(WHITE, BLACK);
  tft.println(getVeLoad());
}

//*******************************************************************Solar Ertrag**********************************************************
void getSolar1() {
  tft.setTextSize(2);
  tft.setTextColor(WHITE, BLACK); 

  // Solar Ertrag heute (H20)
  tft.setCursor(90, 35);
  tft.println(getVeH20() * 10);  
  
  // Max. Solar Leistung heute (H21)
  tft.setCursor(90, 95);
  tft.println(getVeH21());

  // Solar Ertrag gestern (H22)
  tft.setTextColor(BLUE, BLACK); 
  tft.setCursor(90, 155);
  tft.println(getVeH22() * 10);

  // Max. Solar Leistung gestern (H23)
  tft.setTextColor(BLUE, BLACK); 
  tft.setCursor(90, 215);
  tft.println(getVeH23());
}

//*******************************************************************MPPT Regler Daten*****************************************************
void getSolar2() {
  tft.setTextSize(2);
  tft.setTextColor(WHITE, BLACK);  
  
  //PID 
  tft.setCursor(50, 35);
  tft.println(getVePid(), HEX);
  
  // Seriennummer (SER#)
  tft.setCursor(30, 95);
  tft.println(getVeSer());
  

  // Firmware Version (FW)
  tft.setCursor(70, 155);
  float firmwareVersion = atof(getVeFw()) / 100.0;
  tft.println(firmwareVersion, 2);
}

// Abfrage Kontakt für MX Alarm
void Sabotage() {
 
}

// Alarm ausgelöst
void Ausgeloest() {
 
}

// Arbeitsscheinwerfer schalten
void Scheinwerfer() {
  static boolean status=false;
  status = digitalReadFast(RELAISSCHEINWERFER);
  if (status == LOW) {
    digitalWriteFast(RELAISSCHEINWERFER, HIGH);
    page7_btn.drawButton(false);
  }  
  if (status == HIGH) {
    digitalWriteFast(RELAISSCHEINWERFER, LOW);
    page7_btn.drawButton(false);
  }  
}

// Bluetooth schalten
void Bluetooth() {

}

_ging doch nur um das _HEX