Pylontech US2000B Daten über Konsole (RS232) auslesen

  • Hallo Leute,


    ich lese hier schon seit einiger Zeit als Gast mit, habe mich jetzt aber endlich registriert. Ich betreibe seit einigen Monaten eine Hybridanlage mit einem Effekta AX M1 und einem Akku Pylontech 9,6 kW. Im Moment sind auf dem Dach 2,4 kWp ich rüste aber gerade noch auf 4,2 kWp auf. Im Sommer wird damit so gut wie 100% Stromverbrauch abgedeckt. Im Hebst und Winter muss ich mal abwarten.


    Jetzt meine Frage:


    der Pylontech hat ja eine RS232 Leitung als Konsole. Hier würde ich gerne die Daten abfragen, in Zukunft auch mit einem Arduino der die Daten auf einem WEB-Server ablegt, so dass man von außen etwas abfragen kann. Ähnliches habe ich in anderem Zusammenhang schon gemacht, ich habe also die Erfahrung dazu.


    Jetzt habe ich als ersten Test einen PC mit USB/RS232 Adapter angeschlossen, bekomme aber keine brauchbare Rückmeldung. Egal was man ihm schickt, er antwortet immer mit einer Hand voll verkrumpelter Zeichen, so als ob die Baudrate o.ä. nicht stimmt. Ich habe alle Baudraten von 300 bis bis 256.00 probiert, mit und ohne Parity, usw. Es kommt aber nichts Brauchbares zurück. Es sieht immer so aus wir auf dem beigefügten Bild.


    Der USB/RS232 Adapter arbeitet fehlerfrei. Wenn ich TX und RX überbrücke kommt ein sauberes Echo zurück.


    Was mache ich falsch? Wer kann dazu etwas beitragen? Das haben hier doch diverse Leute schon gemacht.


  • Was mache ich falsch? Wer kann dazu etwas beitragen? Das haben hier doch diverse Leute schon gemacht.


    Du musst genau den String schicken der im Thread angegeben wurde. Bei dir sieht man im Bild 3 Bytes die im Asc Mode geschickt wurde. Das sind zu wenige Bytes. Den String musst du im Terminal im HEX Mode schicken und die Baudraten umschalten (zuerst 1200 dann 115200). So habe ich das in C++ programmiert.


    const uint8 initstring1[] = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";

    const uint8 initstring2[] = "\r\ninfo\nlogin debug\nconfig\nctrl\nprot\n";

    const uint8 initstring3[] = { 0x7E, 0x32, 0x30, 0x30, 0x31, 0x34, 0x36, 0x38, 0x32, 0x43, 0x30, 0x30, 0x34, 0x38, 0x35, 0x32, 0x30, 0x46, 0x43, 0x43, 0x33, 0x0D, 0x0 };

    const uint8 initstring4[] = "\r\npwr\n";




    void CSolarDatenDlg::QueryBattery()

    {


    CMMTimer timer;

    bool bBattery = m_QueryBattery.GetCheck() != 0;

    bool bGridOff = m_bAlarmGridOff.GetCheck() != 0;

    bool bCurrentDataOk = 1;

    bool bWRDataOk = 1;

    bool bRead;

    bool bReadSuccess = 0;

    bool bReadTimeout = 0;

    UCHAR* pRxBuffer;


    CString s;

    UCHAR rx[10];

    CHAR txbuffer[100];

    UCHAR rxbuffer[255];

    int nBuffer;

    char szbuffer[100];

    uint16 crc;

    char szport[10];



    if (bBattery)

    {


    m_nProgress.SetWindowText(TEXT("Battery"));

    m_nProgress.SetBarBkColor(RGB(0, 255, 0));

    m_nProgress.SetState(PBST_NORMAL);

    m_nProgress.SetPos(10);



    }

    else

    {

    m_nProgress.SetBarBkColor(RGB(255, 0, 0));

    m_nProgress.SetPos(0);

    m_nProgress.SetState(PBST_ERROR);

    m_nProgress.SetWindowText(TEXT("Battery off!"));

    m_nValidBatteryData++;

    return;


    }



    int nPos = m_SelectCom3.GetCurSel();


    sprintf(szport, "\\\\.\\COM%i", nPos + 1);


    if (!m_comport3.open(CString(szport).GetBuffer(), 1200, 15))

    {

    sprintf(szbuffer, "Open COM%i failed!", nPos + 1);

    AddListString(CString(szbuffer));


    }

    else

    {


    }



    if (m_comport3.IsOpen())

    {


    m_comport3.flushRX();


    Sleep(10);


    m_comport3.write((UCHAR*)initstring3, (sizeof(initstring3) / sizeof(initstring3[0])) - 1);


    m_comport3.ImmediateSend();


    Sleep(((sizeof(initstring3) / sizeof(initstring3[0])) - 1)*20);


    m_comport3.flushRX();


    m_comport3.flush();


    Sleep(10);


    m_comport3.close();


    Sleep(10);


    if (!m_comport3.open(CString(szport).GetBuffer(), 115200, 2))

    {

    sprintf(szbuffer, "Open %i failed!", nPos + 1);

    AddListString(CString(szbuffer));


    }

    else

    {


    }



    if (m_comport3.write((UCHAR*)initstring4, (sizeof(initstring4)/sizeof(initstring4[0]))-1))

    {


    timer.Start(1, 1);


    bRead = TRUE;


    pRxBuffer = (UCHAR*)malloc(5000);


    if (pRxBuffer == NULL)

    {

    bRead = FALSE;

    }

    else

    {

    memset(pRxBuffer, 0, 5000);

    }


    m_comport3.ClearStatistics();


    pRxBuffer[0] = '!';

    nBuffer = 1;


    while (bRead)

    {

    if (m_comport3.readX(rx, 1))

    {

    pRxBuffer[nBuffer] = rx[0];

    nBuffer++;

    timer.Reset();

    timer.Stop();

    timer.Start(1, 1);

    m_nProgress.SetPos(nBuffer / (1355/10));


    if ((pRxBuffer[nBuffer-1] == 0x24) && (pRxBuffer[nBuffer-2] == 0x24))

    {

    bRead = FALSE;

    bReadSuccess = TRUE;

    }


    if ((pRxBuffer[nBuffer-1] == '@'))

    {

    pRxBuffer[0] = rx[0];

    nBuffer = 1;

    }



    }

    else

    {

    Sleep(10);

    }



    if (timer.GetTotalMilliseconds() > 500)

    {

    bRead = FALSE;

    bReadTimeout = TRUE;

    AddListString(CString(_T("Battery read timeout!")));

    }


    if (nBuffer == (5000 - 1))

    {

    bRead = FALSE;

    AddListString(CString(_T("Battery read buffer overflow!")));

    }

    }


    if (bReadSuccess)

    {

    // ok

    pRxBuffer[nBuffer] = 0;

    if (pRxBuffer[0] = '@')

    {

    AddListString(CString(_T("Battery read ok!")));

    m_SolarDatenData.UpdateBattery((CHAR*)pRxBuffer);

    m_SolarDatenData.AddTimeStamp();


    float nmax = 0;

    float nmin = 100;

    float navg = 0;

    int nBatteries = 0;


    for (int i = 0; i<8; i++)

    {

    if (m_SolarDatenData.bBatPresent[i])

    {

    if (m_SolarDatenData.BatProzent[i] > nmax)

    {

    nmax = m_SolarDatenData.BatProzent[i];

    }


    if (m_SolarDatenData.BatProzent[i] < nmin)

    {

    nmin = m_SolarDatenData.BatProzent[i];

    }


    navg += m_SolarDatenData.BatProzent[i];


    nBatteries++;

    }


    }


    m_SolarDatenData.BatAvgProz = navg / ((float)nBatteries);

    m_SolarDatenData.BatMinProz = nmin;

    m_SolarDatenData.BatMaxProz = nmax;

    m_SolarDatenData.nBatteries = nBatteries;


    if (nmin <= m_nBatteryLow)

    {

    m_BatteryLow.SetCheck(TRUE);

    }

    else if (nmin >= m_nBatteryBackToBattery)

    {

    if ((m_nBatteryBackToBattery == 100) && (m_SolarDatenData.BatteryVoltage >= 52.0f))

    {

    if (1 || m_BackToBatteryMin <= m_BackToBatteryMinCounter)

    {

    m_BatteryLow.SetCheck(FALSE);

    }

    else

    {

    m_BackToBatteryMinCounter++;

    }


    }

    else if ((m_nBatteryBackToBattery == 100) )

    {


    }

    else

    {

    m_BackToBatteryMinCounter = 0;

    m_BatteryLow.SetCheck(FALSE);

    }


    }


    if (nmin <= m_nBatteryVeryLow)

    {

    m_BatteryVeryLow.SetCheck(TRUE);

    }

    else if (nmin > m_nBatteryStopChargingLevel)

    {

    m_BatteryVeryLow.SetCheck(FALSE);

    }


    if (nmin >= m_nBatteryExcessPower)

    {

    // heater ?


    }


    if (nmin <= m_nBatteryLow)

    {

    SetRelay1(TRUE);

    }

    else

    {

    SetRelay1(FALSE);

    }


    if (nmin <= m_nBatteryVeryLow)

    {

    SetRelay2(TRUE);

    }

    else

    {

    SetRelay2(FALSE);

    }


    if (nmin >= m_nBatteryExcessPower)

    {

    // heater ?


    SetRelay3(TRUE);

    }

    else

    {

    SetRelay3(FALSE);

    }


    if (nmin < m_nBatteryBelowLevelGoStandby)

    {

    // go standby


    SetRelay4(TRUE);

    }

    else

    {

    SetRelay4(FALSE);

    }



    }

    else

    {

    AddListString(CString(_T("Battery read sync failed!")));

    }


    m_nValidBatteryData = 0;


    }

    else

    {

    m_nProgress.SetBarBkColor(RGB(255, 0, 0));

    m_nProgress.SetPos(0);

    m_nProgress.SetState(PBST_ERROR);

    m_nProgress.SetWindowText(TEXT("Battery failed!"));

    AddListString(CString(_T("Battery read failed!")));

    m_nValidBatteryData++;

    }


    free(pRxBuffer);


    }

    m_comport3.flushRX();

    m_comport3.close();

    m_nProgress.SetPos(0);


    }



    }

  • Hallo an FS, RE und den gesamten Rest,


    erst mal danke für eure Rückmeldungen. Ich habe einige Tage rumprobiert, auch schon vor meinem Eintrag und auch mit dem HEX-String versucht, trotzdem habe ich nie eine brauchbare Antwort vom Pylon bekommen. Irgendwann habe ich dann alles ausgeschaltet und wieder neu gestartet. Dann lief es. Das sollte jeder mal machen, der nichts Brauchbares zustande bringt. Vermutlich habe ich mit meiner Fummelei den Konsolenport in einen Zustand versetzt wo nichts mehr läuft. Mit HTerm kann man sich sehr gut einarbeiten. Mal sehen wie es dann mit meinen Planungen bzgl. Arduino (oder Raspberry) weiter geht. Habe da diverse Ideen.


    Auch das Programm BatteryView läuft. Es ist am Konsolenport über RS232 mit 115.200 Baud angeschlossen. Zuvor habe ich die Pylons wie im ersten Beitrag beschrieben mit dem langen HEX-String vorbereitet.



    Hier kann man endlich mal den genauen Ladezustand erkennen. Läuft alles sehr gut.

  • Ja, es läuft und ich bekomme die Werte die ich haben möchte. Aber die "Frickelei" mit erst mal 1200 Baud und dann umschalten auf 115.200 gefällt mir nicht so gut. Gibt es nicht einen Startstring, wo man einfach bei 1200 Baud bleibt. Irgendwie muss es gehen, weil das Programm BatteryView bei Connect u.a. auch 1200 Baud anbietet. Das würde die Sache mit dem Arduino erleichtern und sauberer machen.


    Hat jemand eine Idee oder kann dazu etwas sagen?

  • dear members. This site though it is in German is so good i thank you for your contributions. I am working on a project to understand the rs485 protocol on the pylontech battery. With 1 connected and with a stack . This has proved difficult and I am appealing to any one who has the single battery and multiple batteries to post some samples of their messages from rs485 in these two types of application.

    This is to confirm which bytes are responsible for the current charge limit broadcast to the inverter and also which bytes will control peak current discharge as well as installed capacity. The document for the protocol is a real mess and is hard to understand. Having samples of the code with a single battery and with then sample code of two batteries then three and four will help identify this . I hope some one can assist . It will be greatly beneficial. Thankyou.

  • Ja, es läuft und ich bekomme die Werte die ich haben möchte. Aber die "Frickelei" mit erst mal 1200 Baud und dann umschalten auf 115.200 gefällt mir nicht so gut. Gibt es nicht einen Startstring, wo man einfach bei 1200 Baud bleibt. Irgendwie muss es gehen, weil das Programm BatteryView bei Connect u.a. auch 1200 Baud anbietet. Das würde die Sache mit dem Arduino erleichtern und sauberer machen.


    Hat jemand eine Idee oder kann dazu etwas sagen?

    Ja, das geht schon, läuft bei mir mittels Arduino auch nur mit 9600Baud ohne Umschaltung. Man muss sich halt auf den Abfragestring aus der Anleitung beschränken und nicht mittels Service-Schnittstelle alles mögliche abfragen. Dann muss man auch nicht auf die höhere Baudrate umschalten, es reicht einfach mittels 9600Baud den String hinzuschicken und die Antwort auszuwerten.

    Genaueres siehe am Anfang dieses Beitrags:

    Pylontech US2000B: Daten, Protokolle, Programme

  • Ja, das geht schon, läuft bei mir mittels Arduino auch nur mit 9600Baud ohne Umschaltung. Man muss sich halt auf den Abfragestring aus der Anleitung beschränken und nicht mittels Service-Schnittstelle alles mögliche abfragen. Dann muss man auch nicht auf die höhere Baudrate umschalten, es reicht einfach mittels 9600Baud den String hinzuschicken und die Antwort auszuwerten.

    Genaueres siehe am Anfang dieses Beitrags:

    Pylontech US2000B: Daten, Protokolle, Programme

    Hallo,


    ich habe mal den Hex-String aus deinem Arduinoprogramm genommen und mit HTERM an den Pylon geschickt:

    byte pylonask[] = {0x7E,0x32,0x30,0x30,0x31,0x34,0x36,0x34,0x32,0x45,0x30,0x30,0x32,0x30,0x31,0x46,0x44,0x33,0x35,0x0D};


    Das sieht dann so aus:



    Der Pylon antwortet zwar, aber damit kann ich nichts anfangen. Ich behaupte mal dass die Baudrate von 1200 richtig ist, da der Rückgabestring mit 0D (also CR) endet. Diese Antwort kommt immer, auch dann wenn ich nicht den HEX-String sende sondern einfach ein paar wirre Zeichen.


    Wenn ich einfach 'xyz' oder 'help' an den Pylon schicke kommt genau die selbe Antwort:



    Du schickst ja mit dem Arduino mit 1200 Baud genau den oben gezeigten String: Serial1.write(pylonask, sizeof(pylonask));


    Was mache ich da falsch ??? Wenn man das zum Test mit HTERM macht muss doch auch etwas Brauchbares zurück kommen.

  • Das kenn ich, ein paar mal probieren und/oder das Pylontech mal ein- und ausschalten, vor allem wenn man es vorher schon mit anderen Baudraten oder Befehlen versucht hat, dann geht es plötzlich :/

  • Mit HTERM klappt das bei mir nicht. Habe daher einfach mal einen Arduino geschnappt und mit SoftwareSerial und 1200 Baud diesen String an die RS232-Schnittstelle des ersten Pylon geschickt:


    // 1. Batterie - funktioniert

    byte pylonask[] = {0x7E, 0x32, 0x30, 0x30, 0x31, 0x34, 0x36, 0x34, 0x32, 0x45, 0x30, 0x30, 0x32, 0x30, 0x31, 0x46, 0x44, 0x33, 0x35, 0x0D};


    Das ist der String der in der Dokumentation auf Seite 23 bzw. Kapitel 5 beschrieben wird. Der Pylon antwortet damit ganz sauber. Wie gesagt, alles mit 1200 Baud, also ohne die Fummelei mit anderen Baudraten. Die Baudrate umschalten ist auch völlig unnötig, mit 1200 funktioniert es einwandfrei und schnell genug. Ganz wichtig ist aber in diesem Include die Puffergröße zu erhöhen:


    C:\Programme(x86)\Arduino\hardware\arduino\avr\libraries\SoftwareSerial.h


    /******************************************************************************

    * Definitions

    ******************************************************************************/


    #ifndef _SS_MAX_RX_BUFF

    #define _SS_MAX_RX_BUFF 135 // RX buffer size <--- diese Zeile anpassen

    #endif


    Der Standard steht auf 64 Bytes. Damit liest der Arduino dann nicht den kompletten String ein und es funktioniert nicht.


    Mit dem Rückgabestring habe ich mir eine Anzeige zusammengebastelt. Das sieht dann so wie auf den Bildern aus. Da sieht man die wichtigsten Daten, auch ohne PC und ohne BatteryView. Damit erfüllt es erst mal seinen Zweck.


    Jetzt mein Problem:


    Ich lese nur die erste Batterie aus. Diese Werte nehme ich einfach mal vier, in der Annahme dass alle vier Batterien synchron laufen. Dann komme ich auf die angezeigten Werte. Ich schaffe es aber nicht die zweite, dritte und vierte Batterie anzusprechen. Ich würde dann gerne aus allen vier Batterien den Mittelwert nehmen. Für die weiteren Batterien habe ich diese Strings:


    // 2. Batterie - funktioniert nicht

    //byte pylonask[] = {0x7E, 0x32, 0x30, 0x30, 0x32, 0x34, 0x36, 0x34, 0x32, 0x45, 0x30, 0x30, 0x32, 0x30, 0x32, 0x46, 0x44, 0x33, 0x33, 0x0D};

    // 3. Batterie - funktioniert nicht

    //byte pylonask[] = {0x7E, 0x32, 0x30, 0x30, 0x33, 0x34, 0x36, 0x34, 0x32, 0x45, 0x30, 0x30, 0x32, 0x30, 0x33, 0x46, 0x44, 0x33, 0x31, 0x0D};

    // 4. Batterie - funktioniert nicht

    //byte pylonask[] = {0x7E, 0x32, 0x30, 0x30, 0x34, 0x34, 0x36, 0x34, 0x32, 0x45, 0x30, 0x30, 0x32, 0x30, 0x34, 0x46, 0x44, 0x32, 0x46, 0x0D};


    Damit kommt keine brauchbare Antwort. Die Checksumme (die letzen 4 Bytes) ist nicht das Problem, da weiß ich inzwischen wie ich die manuell bestimmen kann, das findet man in der Doku. Ich habe es auch mit diversen anderen Strings versucht, die ich hier im Forum an unterschiedlichen Stellen gefunden habe.


    Ich mache es wohlgemerkt nicht über RS485 sondern über RS232. Die Antwort kommt auch nur von der ersten Batterie. Wenn man die Leitung auf eine andere Batterie umsteckt kommt da nichts.


    Hat schon mal jemand ganz konkret über RS232 die zweite, dritte oder vierte Batterie abgefragt? Wie muss der Abfragestring aussehen? Es muss aber über die RS232 der ersten Batterie möglich sein, da BatterieView an genau der selben Leitung die Daten von allen vier Batterien ermittelt.