irekzielinski / Pylontech-Battery-Monitoring

Adding WiFi monitoring to US2000B and US2000C batteries.
133 stars 57 forks source link

Response is only ???? #1

Open TMHEINE opened 5 years ago

TMHEINE commented 5 years ago

Hi, I did all the same as you did, with the same elements (Wemos D1 mini, Sparkfun Max3232). I can access the web interface. But all responses I get is always "????". What is wrong? Webinterface response

djsyl commented 4 years ago

hello same thing for me !

TMHEINE commented 4 years ago

I use now the can Port to read the SOC. RPI3 with PiCan2

djsyl commented 4 years ago

Now it work for me when use a other max3232 not Sparkfun, not work with the same of description Sparkfun it's good now

TMHEINE commented 3 years ago

Hello,

I tried it again all new. Parts: Wemos D1 Mini v3.0.0 Sparkfun Max3232 (Bought a new one

I get the same Questionsmarks as before

Mainpage: image Power response: image The log is: image

What could be the Problem? Wireing? Arduino?

Compiling Result Arduino: image

Battery is an Pylontech US2000 Plus

Layout of console-Port: image

Anyone who can help me?

irekzielinski commented 3 years ago

@TMHEINE did you get it working? Your log indicates that ESP is not establising any connection with the battery. I suspect this is electrical issue/wiring.

TMHEINE commented 3 years ago

Hello. Didi not get it working. Electrical/wiring should not be the problem. Maybe port is not working or something else. I did not try it again after last test. For the last test I used a new ESP and new Sparkfun. I tried lot of different ways likt exchange RX and TX, Other capasitors, different power supplies. but nothing worked.

So actually i am reading the batteries by can-hat and python.

Wibet10 commented 2 years ago

Hi, I built one of these monitors a couple of years ago and it worked fine with my UC3000 batteries as described in the original design using the RJ11 port. End of last year I fited a new UC3000C battery as master and had to convert to the 8 pin 'Console' port. I rewired the cable and it started working but was erratic. It then refused to read at all and I abandonned it until this month. On investigation, the RS232 signals were barely 8-10volts pk/pk and the MQTT was trying to reconnect every cycle of the program. I also discovered from the web that the UC2000/3000C batteries do not need the 1200/115200 codes and rate change to wake them, they are ready to communicate at 115200 on startup. I have commented out 'void wakeUpConsole and void switchBaud' sections of the code and added this into the void loop: - void loop() { // millis delay check below runs 'mqttLoop' every 30 seconds Client.loop(); if (millis() - lastMillis >= 30*1000UL) // >= 30 sec period {
lastMillis = millis(); // reset ready for next loop mqttLoop(); } ArduinoOTA.handle(); server.handleClient(); timer.run();_ code from this point is original -- The erratic connection problem was fixed by including a 3.3v to 5v level shifter (cost 99p) between the ESP and the MAX3232 as in the attached diagram. This had the effect of increasing the RS232 signal voltages sent to the battery to around 12/15volts pk/pk, much more' in spec' and a very solid connection. There have been some changes to the original project that accomodate the new battery types since I last 'checked in' (November 2021) and reading the above comments, the discoveries I made in adapting my original build may help resolve some of the problems experienced by others. image Hope this helps others connecting to UC2000/3000C batteries. Usual repeat of warnings - proceed at your own risk!

irekzielinski commented 2 years ago

Thanks - that some good findings! Do you really need to wrap mqttLoop(); into this 1in30 seconds condition? There should be no problem calling this every time loop is executed.

Wibet10 commented 2 years ago

Hi, Thanks for the response. I hope the info. on getting a better RS232 connection helps others. In my original build I tried several MAX3232's before I found a working one. On testing the voltages on my worker during the upgrade, @3.3v the o/p is minimum spec, @5v it is comfortably in spec. My coding is 'cut and paste' at best! My original problem was the mqtt disconnecting/reconnecting on every loop. In tracing this back to my mqtt user/password set-up, I broke your mqttloop code (probably the syntax somewhere). My crude fix was to put the timing loop into the main loop and only run the mqttloop every 30 secs. My code now compiles and runs ok, think I'll leave it running for a while and look at fixing it later. Thanks for a great project that has kept up with the battery developments, cheaper than setting up Batteryview and almost as good, nice one!

TMHEINE commented 2 years ago

Even with the level shifter, i get only ???? I order a other Max3232 (not sparkfun) and then try again. @Wibet10 : Is it possible to get the complete changed code for testing? Thanks for you efford

Wibet10 commented 2 years ago

Sorry you are still having proiblems. I have mine running using the enclosed code, it compiles using Arduino IDE 1.8.13 & 2.0.0rc3 on a Win10 PC. I have a genuine Wemos Mini Pro and a 'cheap generic' Max3232. I started out in February with no reads from the Pylon-stack (Master is now UC3000C - new type) and only ??? when I accessed the local 192.168.1.71 site. I 'fiddled about' with the code and put in a 'lot of debug comments' to get my head around how it worked, I did get it going but I think I broke some of the original code? My changes are around lines 745-798 and 848-870 in the attached version. There are a lot of serial.prints/notes I have put in to help me remember what I did, hope it helps you sort yours. DON'T leave any 'extra' Serial.prints in the code or IT WILL totally disrupt the reading of the Pylontech's. (this is the code as it is working for me now - copy + paste it into Arduino IDE) All credit goes to Irek for his original code.

// WiFi code for 'D1 mini Pro' to run Pylontech Console Port into Node Red server on 192.168.1.197 // Has pylontech battery web server access @ 192.168.1.71 (local wifi port) // Updated 07-02-2022 Taking out start at 1200 and handshake to then switch to 115200 (older batteries) - now starts at 115200 and just reads.

include

include

include

include

include

include

include //https://github.com/PaulStoffregen/Time

include // this library needs loading manually

include // this library needs loading manually

//#define DEBUG // Comment out to remove DEBUG Serial prints - useful to see the loops running // if left enabled while connected to pylon - it WILL corrupt the real pylon data reads!!

//IMPORTANT: Specify your WIFI settings:

define WIFI_SSID "xxxxxxxxxxxxxxxxx"

define WIFI_PASS "xxxxxxxxxxxxxx"

//IMPORTANT: Uncomment this line if you want to enable MQTT (and fill correct MQTT_ values below):

define ENABLE_MQTT

ifdef ENABLE_MQTT

//NOTE 1: if you want to change what is pushed via MQTT - edit function: pushBatteryDataToMqtt. (line 822) //NOTE 2: MQTT_TOPIC_ROOT is where battery will push MQTT topics. For example "soc" will be pushed to: "pylon_battery/soc" //Note 3: Using WiFi into Raspberry Pi

define MQTT_SERVER "192.168.1.197" //is Node Red Home Server IP {Home mqtt server 127.0.0.1:1883}

define MQTT_PORT 1883

define MQTT_USER "emonpi"

define MQTT_PASSWORD "xxxxxxxxxxxxxx"

define MQTT_TOPIC_ROOT "emon/pylon_battery/" //this is where mqtt data will be pushed

//#define MQTT_PUSH_FREQ_SEC 30 // MQTT push now set by millis delay in 'void loop'

endif //for #def ENABLE_MQTT

define ABS_DIFF(a, b) (a > b ? a-b : b-a)

WiFiClient batClient; PubSubClient Client(batClient); // set-up MQTT client

char g_szRecvBuff[7000]; static unsigned long g_lastDataSent = 0; //sets initial MQTT last-sent time for first loop unsigned long lastMillis;

ESP8266WebServer server(80); SimpleTimer timer; circular_log<7000> g_log; bool ntpTimeReceived = false; int g_baudRate = 0;

void Log(const char* msg) { g_log.Log(msg);

ifdef DEBUG

Serial.println("logging messages");

endif

} // 'callback' Not needed for Pylon?? void callback(char topic, byte payload, unsigned int length) { Serial.print("Message arrived ["); Serial.print(topic); Serial.print("] "); for (int i = 0; i < length; i++) { Serial.print((char)payload[i]); } Serial.println(); }

void reconnect() { // loop until we are reconnected while (!Client.connected()) { Serial.println("Attempting MQTT connection..."); // Attempt to connect .. if (Client.connect("batClient", MQTT_USER, MQTT_PASSWORD )) { Serial.println("connected to MQTT...."); } else{ Serial.print("failed. rc= "); Serial.println(Client.state()); Serial.println("Try again in 5 seconds ..."); delay(5000); } } }

void setup_wifi(){ // We start by connecting to a WiFi network WiFi.begin(WIFI_SSID, WIFI_PASS); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println(""); Serial.println("WiFi connected"); }

void setup() { // put your setup code here, to run once: Serial.begin(115200); // new line to start serial at 115200

ifdef DEBUG

Serial.println ("."); Serial.println ("Set Serial to 115200");

endif

delay(200);

setup_wifi(); delay(200);

ifdef DEBUG

Serial.println ("Setting up MQTT server in SETUP ~~~~~");

endif

Client.setServer(MQTT_SERVER, MQTT_PORT);
Client.setCallback(callback);    // callback not needed for pylon??

while (!Client.connected()){

Serial.println(); Serial.print("Connecting to "); Serial.println(MQTT_SERVER); if (Client.connect("batClient", MQTT_USER, MQTT_PASSWORD )) { Serial.println(" Connected to MQTT ..."); } else { Serial.print("failed to connect to MQTT with state "); Serial.print(Client.state()); delay(2000); } }

ifdef DEBUG

Serial.println("Connected to MQTT .. in setup");

endif

pinMode(LED_BUILTIN, OUTPUT); digitalWrite(LED_BUILTIN, HIGH);//high is off

ArduinoOTA.setHostname("Home_Battery"); ArduinoOTA.begin(); server.on("/", handleRoot); server.on("/log", handleLog); server.on("/req", handleReq); server.on("/jsonOut", handleJsonOut); server.on("/reboot", [](){ ESP.restart(); });

server.begin();

syncTime();

Log("Boot event"); }

void handleLog() { server.send(200, "text/html", g_log.c_str()); } /* This section commented out as old protocol to switch 1200 to 115200 baud void switchBaud(int newRate) {
if(g_baudRate == newRate) { return; }

if(g_baudRate != 0) { Serial.flush(); delay(20); Serial.end(); delay(20); }

char szMsg[50]; snprintf(szMsg, sizeof(szMsg)-1, "New baud: %d", newRate); Log(szMsg);

Serial.begin(newRate); g_baudRate = newRate;

ifdef DEBUG

Serial.println("End of switchBaud loop");

endif

delay(20); } */

void waitForSerial() { for(int ix=0; ix<150;ix++) { if(Serial.available()) break; delay(10); }

ifdef DEBUG

Serial.println("Waiting for serial");

endif

}

int readFromSerial() {

ifdef DEBUG

Serial.println("Starting int-readFromSerial()");

endif

memset(g_szRecvBuff, 0, sizeof(g_szRecvBuff)); int recvBuffLen = 0; bool foundTerminator = true;

ifdef DEBUG

Serial.println("Terminator TRUE"); //#############found true return####################

endif

waitForSerial();

while(Serial.available()) {

ifdef DEBUG

    Serial.println ("Serial is available");
  #endif

char szResponse[256] = "";
const int readNow = Serial.readBytesUntil('>', szResponse, sizeof(szResponse)-1); //all commands terminate with "$$\r\n\rpylon>" (no new line at the end)

if(readNow > 0 && 
   szResponse[0] != '\0')
{
  if(readNow + recvBuffLen + 1 >= (int)(sizeof(g_szRecvBuff)))
  {
    Log("WARNING: Read too much data on the console!");
    break;
  }

  strcat(g_szRecvBuff, szResponse);
  recvBuffLen += readNow;

  if(strstr(g_szRecvBuff, "$$\r\n\rpylon"))
  {
    strcat(g_szRecvBuff, ">"); //readBytesUntil will skip this, so re-add
    foundTerminator = true;
    break; //found end of the string
  }

  if(strstr(g_szRecvBuff, "Press [Enter] to be continued,other key to exit"))
  {
    //we need to send new line character so battery continues the output
    Serial.write("\r");
  }

  waitForSerial();
}

}

if(recvBuffLen > 0 ) { if(foundTerminator == false) { Log("Failed to find pylon> terminator"); } }

return recvBuffLen; }

bool readFromSerialAndSendResponse() { const int recvBuffLen = readFromSerial(); if(recvBuffLen > 0) { server.sendContent(g_szRecvBuff); return true; }

return false; }

bool sendCommandAndReadSerialResponse(const char* pszCommand) { //switchBaud(115200); Comented out as do not needed to switch serial for 2000/3000C batteries

if(pszCommand[0] != '\0') { Serial.write(pszCommand); } Serial.write("\n");

const int recvBuffLen = readFromSerial(); if(recvBuffLen > 0) { return true;

#ifdef DEBUG
Serial.println("Send Command and Receive Response = True");
#endif

}

//wake up console and try again: // wakeUpConsole(); Commented out not needed

if(pszCommand[0] != '\0') { Serial.write(pszCommand); } Serial.write("\n");

return readFromSerial() > 0; }

void handleReq() {

ifdef DEBUG

Serial.println("starting void-handleReq()");

endif

bool respOK; if(server.hasArg("code") == false) { respOK = sendCommandAndReadSerialResponse(""); } else { respOK = sendCommandAndReadSerialResponse(server.arg("code").c_str()); }

if(respOK) { server.send(200, "text/plain", g_szRecvBuff); } else { server.send(500, "text/plain", "????"); } }

void handleJsonOut() {

ifdef DEBUG

Serial.println("Starting void-handleJsonOut");

endif

if(sendCommandAndReadSerialResponse("pwr") == false) { server.send(500, "text/plain", "Failed to get response to 'pwr' command"); return;

} parsePwrResponse(g_szRecvBuff); prepareJsonOutput(g_szRecvBuff, sizeof(g_szRecvBuff)); server.send(200, "application/json", g_szRecvBuff); }

void handleRoot() {

ifdef DEBUG

Serial.println("Starting void-handleRoot");

endif

unsigned long days = 0, hours = 0, minutes = 0; unsigned long val = os_getCurrentTimeSec();

days = val / (360024); val -= days (3600*24);

hours = val / 3600; val -= hours * 3600;

minutes = val / 60; val -= minutes*60;

static char szTmp[2500] = "";
snprintf(szTmp, sizeof(szTmp)-1, "Home_Battery
Time GMT: %d/%02d/%02d %02d:%02d:%02d (%s)
Uptime: %02d:%02d:%02d.%02d

free heap: %u
Wifi RSSI: %d
Wifi SSID: %s", year(), month(), day(), hour(), minute(), second(), "GMT", (int)days, (int)hours, (int)minutes, (int)val, ESP.getFreeHeap(), WiFi.RSSI(), WiFi.SSID().c_str());

strncat(szTmp, "
Runtime log


", sizeof(szTmp)-1); strncat(szTmp, "
Command:
Power | Help | Event Log | Time", sizeof(szTmp)-1); strncat(szTmp, "", sizeof(szTmp)-1);

server.send(200, "text/html", szTmp); }

unsigned long os_getCurrentTimeSec() { static unsigned int wrapCnt = 0; static unsigned long lastVal = 0; unsigned long currentVal = millis();

if(currentVal < lastVal) { wrapCnt++; }

lastVal = currentVal; unsigned long seconds = currentVal/1000;

//millis will wrap each 50 days, as we are interested only in seconds, let's keep the wrap counter return (wrapCnt*4294967) + seconds; }

void syncTime() { //get time from NTP time_t currentTimeGMT = getNtpTime(); if(currentTimeGMT) { ntpTimeReceived = true; setTime(currentTimeGMT);

ifdef DEBUG

Serial.println("NTP Time Synced OK");
#endif

}
else { timer.setTimeout(5000, syncTime); //try again in 5 seconds } } /* This section commented out not needed for 2000/3000C batteries void wakeUpConsole() { switchBaud(1200);

//byte wakeUpBuff[] = {0x7E, 0x32, 0x30, 0x30, 0x31, 0x34, 0x36, 0x38, 0x32, 0x43, 0x30, 0x30, 0x34, 0x38, 0x35, 0x32, 0x30, 0x46, 0x43, 0x43, 0x33, 0x0D}; //Serial.write(wakeUpBuff, sizeof(wakeUpBuff)); Serial.write("~20014682C0048520FCC3\r"); delay(1000);

byte newLineBuff[] = {0x0E, 0x0A}; switchBaud(115200);

for(int ix=0; ix<10; ix++) { Serial.write(newLineBuff, sizeof(newLineBuff)); delay(1000);

if(Serial.available())
{
  while(Serial.available())
  {
    Serial.read();
  }

  break;
}

} } */

define MAX_PYLON_BATTERIES 4 // was 8

struct pylonBattery { bool isPresent; long soc; //Coulomb in % long voltage; //in mW long current; //in mA, negative value is discharge long tempr; //temp of case or BMS? long cellTempLow; long cellTempHigh; long cellVoltLow; long cellVoltHigh; char baseState[9]; //Charge | Dischg | Idle char voltageState[9]; //Normal char currentState[9]; //Normal char tempState[9]; //Normal char time[20]; //2019-06-08 04:00:29 char b_v_st[9]; //Normal (battery voltage?) char b_t_st[9]; //Normal (battery temperature?)

bool isCharging() const { return strcmp(baseState, "Charge") == 0; } bool isDischarging() const { return strcmp(baseState, "Dischg") == 0; } bool isIdle() const { return strcmp(baseState, "Idle") == 0; } bool isBalancing() const { return strcmp(baseState, "Balance") == 0; }

bool isNormal() const { if(isCharging() == false && isDischarging() == false && isIdle() == false && isBalancing() == false) { return false; //base state looks wrong! }

return  strcmp(voltageState, "Normal") == 0 &&
        strcmp(currentState, "Normal") == 0 &&
        strcmp(tempState,    "Normal") == 0 &&
        strcmp(b_v_st,       "Normal") == 0 &&
        strcmp(b_t_st,       "Normal") == 0 ;

} };

struct batteryStack { int batteryCount; int soc; //in %, if charging: average SOC, otherwise: lowest SOC int temp; //in mC, if highest temp is > 15C, this will show the highest temp, otherwise the lowest long currentDC; //mAh current going in or out of the battery long avgVoltage; //in mV char baseState[10]; //Charge | Dischg | Idle | Balance | Alarm!

pylonBattery batts[MAX_PYLON_BATTERIES];

bool isNormal() const { for(int ix=0; ix<MAX_PYLON_BATTERIES; ix++) { if(batts[ix].isPresent && batts[ix].isNormal() == false) { return false; } }

return true;

}

//in wH long getPowerDC() const { return (long)(((double)currentDC/1000.0)*((double)avgVoltage/1000.0)); }

//wH estimated current on AC side (taking into account Sofar ME3000SP losses) long getEstPowerAc() const { double powerDC = (double)getPowerDC(); if(powerDC == 0) { return 0; } else if(powerDC < 0) { //we are discharging, on AC side we will see less power due to losses if(powerDC < -1000) { return (long)(powerDC0.94); } else if(powerDC < -600) { return (long)(powerDC0.90); } else { return (long)(powerDC0.87); } } else { //we are charging, on AC side we will have more power due to losses if(powerDC > 1000) { return (long)(powerDC1.06); } else if(powerDC > 600) { return (long)(powerDC1.1); } else { return (long)(powerDC1.13); } } } };

batteryStack g_stack;

long extractInt(const char* pStr, int pos) { return atol(pStr+pos); }

void extractStr(const char pStr, int pos, char strOut, int strOutSize) { strOut[strOutSize-1] = '\0'; strncpy(strOut, pStr+pos, strOutSize-1); strOutSize--;

//trim right while(strOutSize > 0) { if(isspace(strOut[strOutSize-1])) { strOut[strOutSize-1] = '\0'; } else { break; }

strOutSize--;

} }

/* Output has mixed \r and \r\n pwr

@

Power Volt Curr Tempr Tlow Thigh Vlow Vhigh Base.St Volt.St Curr.St Temp.St Coulomb Time B.V.St B.T.St

1 49735 -1440 22000 19000 19000 3315 3317 Dischg Normal Normal Normal 93% 2019-06-08 04:00:30 Normal Normal

....

8 - - - - - - - Absent - - - - - - -

Command completed successfully

$$

pylon / bool parsePwrResponse(const char pStr) { if(strstr(pStr, "Command completed successfully") == NULL) { return false; }

int chargeCnt = 0; int dischargeCnt = 0; int idleCnt = 0; int alarmCnt = 0; int socAvg = 0; int socLow = 0; int tempHigh = 0; int tempLow = 0;

memset(&g_stack, 0, sizeof(g_stack));

for(int ix=0; ix<MAX_PYLON_BATTERIES; ix++) { char szToFind[32] = ""; snprintf(szToFind, sizeof(szToFind)-1, "\r\r\n%d ", ix+1);

const char* pLineStart = strstr(pStr, szToFind);
if(pLineStart == NULL)
{
  return false;
}

pLineStart += 3; //move past \r\r\n

extractStr(pLineStart, 55, g_stack.batts[ix].baseState, sizeof(g_stack.batts[ix].baseState));
if(strcmp(g_stack.batts[ix].baseState, "Absent") == 0)
{
  g_stack.batts[ix].isPresent = false;
}
else
{
  g_stack.batts[ix].isPresent = true;
  extractStr(pLineStart, 64, g_stack.batts[ix].voltageState, sizeof(g_stack.batts[ix].voltageState));
  extractStr(pLineStart, 73, g_stack.batts[ix].currentState, sizeof(g_stack.batts[ix].currentState));
  extractStr(pLineStart, 82, g_stack.batts[ix].tempState, sizeof(g_stack.batts[ix].tempState));
  extractStr(pLineStart, 100, g_stack.batts[ix].time, sizeof(g_stack.batts[ix].time));
  extractStr(pLineStart, 121, g_stack.batts[ix].b_v_st, sizeof(g_stack.batts[ix].b_v_st));
  extractStr(pLineStart, 130, g_stack.batts[ix].b_t_st, sizeof(g_stack.batts[ix].b_t_st));
  g_stack.batts[ix].voltage = extractInt(pLineStart, 6);
  g_stack.batts[ix].current = extractInt(pLineStart, 13);
  g_stack.batts[ix].tempr   = extractInt(pLineStart, 20);
  g_stack.batts[ix].cellTempLow    = extractInt(pLineStart, 27);
  g_stack.batts[ix].cellTempHigh   = extractInt(pLineStart, 34);
  g_stack.batts[ix].cellVoltLow    = extractInt(pLineStart, 41);
  g_stack.batts[ix].cellVoltHigh   = extractInt(pLineStart, 48);
  g_stack.batts[ix].soc            = extractInt(pLineStart, 91);

  //////////////////////////////// Post-process ////////////////////////
  g_stack.batteryCount++;
  g_stack.currentDC += g_stack.batts[ix].current;
  g_stack.avgVoltage += g_stack.batts[ix].voltage;
  socAvg += g_stack.batts[ix].soc;

  if(g_stack.batts[ix].isNormal() == false){ alarmCnt++; }
  else if(g_stack.batts[ix].isCharging()){chargeCnt++;}
  else if(g_stack.batts[ix].isDischarging()){dischargeCnt++;}
  else if(g_stack.batts[ix].isIdle()){idleCnt++;}
  else{ alarmCnt++; } //should not really happen!

  if(g_stack.batteryCount == 1)
  {
    socLow = g_stack.batts[ix].soc;
    tempLow  = g_stack.batts[ix].cellTempLow;
    tempHigh = g_stack.batts[ix].cellTempHigh;
  }
  else
  {
    if(socLow > g_stack.batts[ix].soc){socLow = g_stack.batts[ix].soc;}
    if(tempHigh < g_stack.batts[ix].cellTempHigh){tempHigh = g_stack.batts[ix].cellTempHigh;}
    if(tempLow > g_stack.batts[ix].cellTempLow){tempLow = g_stack.batts[ix].cellTempLow;}
  }

}

}

//now update stack state:

g_stack.avgVoltage /= g_stack.batteryCount; g_stack.soc = socLow;

if(tempHigh > 15000) //15C { g_stack.temp = tempHigh; //in the summer we highlight the warmest cell } else { g_stack.temp = tempLow; //in the winter we focus on coldest cell }

if(alarmCnt > 0) { strcpy(g_stack.baseState, "Alarm"); } else if(chargeCnt == g_stack.batteryCount) { strcpy(g_stack.baseState, "Charge"); g_stack.soc = (int)(socAvg / g_stack.batteryCount); } else if(dischargeCnt == g_stack.batteryCount) { strcpy(g_stack.baseState, "Discharge"); } else if(idleCnt == g_stack.batteryCount) { strcpy(g_stack.baseState, "Idle"); } else { strcpy(g_stack.baseState, "Balance"); }

return true; }

void prepareJsonOutput(char* pBuff, int buffSize) {

ifdef DEBUG

Serial.println("starting void-prepareJsonOutput");

endif

memset(pBuff, 0, buffSize); snprintf(pBuff, buffSize-1, "{\"soc\": %d, \"temp\": %d, \"currentDC\": %ld, \"avgVoltage\": %ld, \"baseState\": \"%s\", \"batteryCount\": %d, \"powerDC\": %ld, \"estPowerAC\": %ld, \"isNormal\": %s}", g_stack.soc, g_stack.temp, g_stack.currentDC, g_stack.avgVoltage, g_stack.baseState, g_stack.batteryCount, g_stack.getPowerDC(), g_stack.getEstPowerAc(), g_stack.isNormal() ? "true" : "false"); }

void loop() { // millis delay check below runns mqttLoop every 30 seconds

ifdef DEBUG

Serial.println("starting void-loop()");
#endif

Client.loop();

ifdef DEBUG

Serial.println("completed client.loop ~~~~~~~~~~~~~~~~~");
#endif
 if (millis() - lastMillis >= 30*1000UL) // >= 30 sec period
{   
   // Serial.println("completed MQTT delay check ~~~~~~~~~~~~~~~~~");

lastMillis = millis();  // reset ready for next loop
mqttLoop(); 
}

ArduinoOTA.handle(); server.handleClient(); timer.run();

//if there are bytes availbe on serial here - it's unexpected //when we send a command to battery, we read whole response //if we get anything here anyways - we will log it

int bytesAv = Serial.available(); if(bytesAv > 0) { if(bytesAv > 63) { bytesAv = 63; }

char buff[64+4] = "RCV:";
if(Serial.readBytes(buff+4, bytesAv) > 0)
{
       Log(buff);
   #ifdef DEBUG
       Serial.println("Logging Buffer -  void-loop");
   #endif 
}

}

ifdef DEBUG

   Serial.println("void-loop flash led");
  #endif
  digitalWrite(LED_BUILTIN, LOW);
  delay(50);
  digitalWrite(LED_BUILTIN, HIGH);//high is off

ifdef DEBUG

Serial.println("END of void-loop");

endif

}

// #define ABS_DIFF(a, b) (a > b ? a-b : b-a)

void mqtt_publish_f(const char* topic, float newValue, float oldValue, float minDiff, bool force) { char szTmp[16] = ""; snprintf(szTmp, 15, "%.2f", newValue); if(force || ABS_DIFF(newValue, oldValue) > minDiff) { Client.publish(topic, szTmp, false); } }

void mqtt_publish_i(const char* topic, int newValue, int oldValue, int minDiff, bool force) { char szTmp[16] = ""; snprintf(szTmp, 15, "%d", newValue); if(force || ABS_DIFF(newValue, oldValue) > minDiff) { Client.publish(topic, szTmp, false);

ifdef DEBUG

Serial.println("MQTT publish FALSE");

endif

} }

void mqtt_publish_s(const char topic, const char newValue, const char* oldValue, bool force) { if(force || strcmp(newValue, oldValue) != 0) { Client.publish(topic, newValue, false); } }

void pushBatteryDataToMqtt(const batteryStack& lastSentData, bool forceUpdate / if true - we will send all data regardless if it's the same /) {

ifdef DEBUG

Serial.println("Starting MQTT Publish");

endif

mqtt_publish_f(MQTT_TOPIC_ROOT "s_of_c", g_stack.soc, lastSentData.soc, 0, forceUpdate); mqtt_publish_f(MQTT_TOPIC_ROOT "temp", (float)g_stack.temp/1000.0, (float)lastSentData.temp/1000.0, 0, forceUpdate); mqtt_publish_i(MQTT_TOPIC_ROOT "est_pwr_ac", g_stack.getEstPowerAc(), lastSentData.getEstPowerAc(), 10, forceUpdate); // mqtt_publish_i(MQTT_TOPIC_ROOT "battery_count",g_stack.batteryCount, lastSentData.batteryCount, 0, forceUpdate); mqtt_publish_s(MQTT_TOPIC_ROOT "base_state", g_stack.baseState, lastSentData.baseState , forceUpdate); mqtt_publish_i(MQTT_TOPIC_ROOT "is_normal", g_stack.isNormal() ? 1:0, lastSentData.isNormal() ? 1:0, 0, forceUpdate); }

void mqttLoop() { //if we have problems with connecting to mqtt server, we will attempt to re-estabish connection each 30 sec (not more than that)

ifdef DEBUG

    Serial.println("Start of MQTT loop");
     #endif 

//first: let's make sure we are connected to mqtt const char* topicLastWill = MQTT_TOPIC_ROOT "availability"; if(!Client.connected()){

ifdef DEBUG

    Serial.println("MQTT found not connected .. MQTT loop...going to reconnect loop");
   #endif 
    reconnect();
 Log("Connected to MQTT server: " MQTT_SERVER);  
   #ifdef DEBUG
    Serial.println("Re-Connected to MQTT server: " MQTT_SERVER);
   #endif 
  Client.publish(topicLastWill, "Connected", true);}

//next: read data from battery and send via MQTT (but only as often as set by 'void loop' delay) // Serial.println(" Completed MQTT check OK..."); if(Client.connected()) { (sendCommandAndReadSerialResponse("pwr") == true); }

 #ifdef DEBUG
  Serial.println("sendCommandAndReadSerialResponse = PWR == TRUE....");
 #endif 
{
static batteryStack lastSentData; //this is the last state we sent to MQTT, used to prevent sending the same data over and over again
static unsigned int callCnt = 0;
parsePwrResponse(g_szRecvBuff);

bool forceUpdate = (callCnt % 6 == 0); //push all the data every 20th call (change to 6 for testing)
pushBatteryDataToMqtt(lastSentData, forceUpdate);
     // Serial.println("Forcing Data Update ...");
callCnt++;
     // Serial.println(callCnt++);
memcpy(&lastSentData, &g_stack, sizeof(batteryStack));
}

ifdef DEBUG

Serial.println("END of mqtt.loop");

endif

}

//#endif ENABLE_MQTT

########## END OF CODE ############

fotosettore commented 2 years ago

Hi folks ! For my needs i made a sketch to communicate between US2000C and ESP32. I saw that new pylontech batteries does not needs the change of 1200/115200 and them answer immediately after a little wakeup. The sketch is very poor and minimal. This is only a valid start for best development. The answers of consolle are shown on serial monitor of IDE and hardware serial port number 2 is used, that is totally free from any use. Sorry for sketch sintax but i'm a visually impaired, so I often need to use capital letters so as not to get confused. The schematic I put is a modification of that of Wibet10. Enjoy !!

p.s. if sketch does not work, you must modify the pins of serial2. You can made it in HardwareSerial.cpp

p.s.2 you can use string command or hex command with no difference (println or write)

pylontech_ESP32

//*** code ** // pylonpep

define RXD2 16

define TXD2 17

byte HandShakeBuff[] = {0x0A, 0x0D}; byte GetPowBuff[] = {0x67, 0x65, 0x74, 0x70, 0x77, 0x72, 0x0a, 0x0e, 0x0D} ;

unsigned long currentTime = millis(); unsigned long Call_Timer = 0; const long timeoutTime = 2000; byte Alternate = 0;

//** void setup() {

Serial.begin (115200);
Serial.println ();
Serial.println ("Start ...");

Serial2.begin(115200,SERIAL_8N1, RXD2, TXD2);

HANDSHAKE();
Call_Timer = millis();

}

//** void loop() {

if ( millis() - Call_Timer > timeoutTime )
{
   if (Alternate == 0)
   {
      INFO();
      Alternate = 1;
   }   
   else 
   {
      GETPWR();
      Alternate = 0;
   }   

   Call_Timer = millis();
}

while (Serial2.available()) 
{
   Serial.print(char(Serial2.read()));
}

}

//***** void HANDSHAKE() { Serial.println ("send HandShakeBuff"); Serial2.write(HandShakeBuff, sizeof(HandShakeBuff)); }

//***** void GETPWR() { Serial.println ("send GetPowBuff"); //Serial2.write(GetPowBuff, sizeof(GetPowBuff)); Serial2.println("getpwr"); }

//***** void INFO() { Serial.println ("send info"); //Serial2.write(GetPowBuff, sizeof(GetPowBuff)); Serial2.println ("info"); }

//***** end of code

creativtransfer commented 2 years ago

@fotosettore I seem to get some consistent data after sending GetPowBuff, but how do I parse the data?

fotosettore commented 2 years ago

You can simply follow the sketch of Wibet10, that contains all decode calls. My test was only an hardware test to communicate using a ESP32 without web server.

creativtransfer commented 2 years ago

Thank you for your reply. Actually, I am doing this on a raspberry pi with an USB to RS232 Adapter. So I have to write my own parser. How does a valid reply look like? Can you please post, what kind of response I should get after sending the GetPowBuff Code. As stated, I am getting some data, however I am not sure if this is something valid or if I will also need a Logic-Level Converter. Thank you very much!

fotosettore commented 2 years ago

Here are some calls i made using SERIAL PORT MONITOR software. https://www.eltima.com/products/serial-port-monitor/?gclid=Cj0KCQjw6pOTBhCTARIsAHF23fIkegr2oNlbpRLsIYYpPPFzVjnh94Te6wTvnVYyU2LGrxt4zNspqgEaAu-5EALw_wcB

69 6e 66 6f 0a                                    info.             

69 6e 66 6f 0a 0d 40 0d 0a 0d 44 65 76 69 63 65   info..@...Device  
20 61 64 64 72 65 73 73 20 20 20 20 20 20 3a 20    address      :   
31 0d 0a 0d 4d 61 6e 75 66 61 63 74 75 72 65 72   1...Manufacturer  
20 20 20 20 20 20 20 20 3a 20 50 79 6c 6f 6e 0d           : Pylon.  
0a 0d 44 65 76 69 63 65 20 6e 61 6d 65 20 20 20   ..Device name     
20 20 20 20 20 20 3a 20 55 53 32 4b 42 50 4c 0d         : US2KBPL.  
0a 0d 42 6f 61 72 64 20 76 65 72 73 69 6f 6e 20   ..Board version   
20 20 20 20 20 20 3a 20 50 48 41 4e 54 4f 4d 53         : PHANTOMS  
41 56 31 30 52 30 33 0d 0a 0d 4d 61 69 6e 20 53   AV10R03...Main S  
6f 66 74 20 76 65 72 73 69 6f 6e 20 20 20 3a 20   oft version   :   
42 36 35 2e 31 32 0d 0a 0d 53 6f 66 74 20 20 76   B65.12...Soft  v  
65 72 73 69 6f 6e 20 20 20 20 20 20 20 3a 20 56   ersion       : V  
32 2e 33 0d 0a 0d 42 6f 6f 74 20 20 76 65 72 73   2.3...Boot  vers  
69 6f 6e 20 20 20 20 20 20 20 3a 20 56 32 2e 30   ion       : V2.0  
0d 0a 0d 43 6f 6d 6d 20 76 65 72 73 69 6f 6e 20   ...Comm version   
20 20 20 20 20 20 20 3a 20 56 32 2e 30 0d 0a 0d          : V2.0...  
52 65 6c 65 61 73 65 20 44 61 74 65 20 20 20 20   Release Date      
20 20 20 20 3a 20 31 39 2d 30 36 2d 32 31 0d 0a       : 19-06-21..  
63 69 66 69 63 61 74 69 6f 6e 20 20 20 20 20 20   cification        
20 3a 20 34 38 56 2f 35 30 41 48 0d 0a 0d 43 65    : 48V/50AH...Ce  
6c 6c 20 4e 75 6d 62 65 72 20 20 20 20 20 20 20   ll Number         
20 20 3a 20 31 35 0d 0a 0d 4d 61 78 20 44 69 73     : 15...Max Dis  
63 68 67 20 43 75 72 72 20 20 20 20 20 3a 20 2d   chg Curr     : -  
31 30 30 30 30 30 6d 41 0d 0a 0d 4d 61 78 20 43   100000mA...Max C  
68 61 72 67 65 20 43 75 72 72 20 20 20 20 20 3a   harge Curr     :  
20 31 30 32 30 30 30 6d 41 0d 0a 0d 45 50 4f 4e    102000mA...EPON  
50 6f 72 74 20 72 61 74 65 20 20 20 20 20 20 20   Port rate         
3a 20 31 32 30 30 0d 0a 0d 43 6f 6e 73 6f 6c 65   : 1200...Console  
20 50 6f 72 74 20 72 61 74 65 20 20 20 3a 20 31    Port rate   : 1  
31 35 32 30 30 0d 0a 0d 43 6f 6d 6d 61 6e 64 20   15200...Command   
63 6f 6d 70 6c 65 74 65 64 20 73 75 63 63 65 73   completed succes  
73 66 75 6c 6c 79 0d 0a 0d 24 24 0d 0a 0d 70 79   sfully...$$...py  
6c 6f 6e 5f 64 65 62 75 67 3e                     lon_debug>        

67 65 74 70 77 72 0a                              getpwr.           

67 65 74 70 77 72 0a 0d 40 0d 0d 0a 34 39 32 34   getpwr..@...4924  
33 23 20 20 20 20 20 20 30 23 20 20 32 34 39 30   3#      0#  2490  
30 23 20 32 33 32 35 31 23 20 49 64 6c 65 23 20   0# 23251# Idle#   
4e 6f 72 6d 61 6c 23 20 4e 6f 72 6d 61 6c 23 20   Normal# Normal#   
4e 6f 72 6d 61 6c 23 20 0d 0d 0a 33 32 38 33 23   Normal# ...3283#  
20 20 32 32 36 30 30 23 20 4e 6f 72 6d 61 6c 23     22600# Normal#  
20 4e 6f 72 6d 61 6c 23 20 0d 0d 0a 33 32 38 33    Normal# ...3283  
23 20 20 32 32 36 30 30 23 20 4e 6f 72 6d 61 6c   #  22600# Normal  
23 20 4e 6f 72 6d 61 6c 23 20 0d 0d 0a 33 32 38   # Normal# ...328  
32 23 20 20 32 32 36 30 30 23 20 4e 6f 72 6d 61   2#  22600# Norma  
6c 23 20 4e 6f 72 6d 61 6c 23 20 0d 0d 0a 33 32   l# Normal# ...32  
38 33 23 20 20 32 32 36 30 30 23 20 4e 6f 72 6d   83#  22600# Norm  
61 6c 23 20 4e 6f 72 6d 61 6c 23 20 0d 0d 0a 33   al# Normal# ...3  
32 38 32 23 20 20 32 32 36 30 30 23 20 4e 6f 72   282#  22600# Nor  
6d 61 6c 23 20 4e 6f 72 6d 61 6c 23 20 0d 0d 0a   mal# Normal# ...  
33 32 38 33 23 20 20 32 32 37 30 30 23 20 4e 6f   3283#  22700# No  
72 6d 61 6c 23 20 4e 6f 72 6d 61 6c 23 20 0d 0d   rmal# Normal# ..  
0a 33 32 38 33 23 20 20 32 32 37 30 30 23 20 4e   .3283#  22700# N  
6f 72 6d 61 6c 23 20 4e 6f 72 6d 61 6c 23 20 0d   ormal# Normal# .  
0d 0a 33 32 38 33 23 20 20 32 32 37 30 30 23 20   ..3283#  22700#   
4e 6f 72 6d 61 6c 23 20 4e 6f 72 6d 61 6c 23 20   Normal# Normal#   
0d 0d 0a 33 32 38 33 23 20 20 32 32 37 30 30 23   ...3283#  22700#  
20 4e 6f 72 6d 61 6c 23 20 4e 6f 72 6d 61 6c 23    Normal# Normal#  
20 0d 0d 0a 33 32 38 33 23 20 20 32 32 37 30 30    ...3283#  22700  
23 20 4e 6f 72 6d 61 6c 23 20 4e 6f 72 6d 61 6c   # Normal# Normal  
23 20 0d 0d 0a 33 32 38 33 23 20 20 32 32 37 30   # ...3283#  2270  
30 23 20 4e 6f 72 6d 61 6c 23 20 4e 6f 72 6d 61   0# Normal# Norma  
6c 23 20 0d 0d 0a 33 32 38 33 23 20 20 32 32 37   l# ...3283#  227  
30 30 23 20 4e 6f 72 6d 61 6c 23 20 4e 6f 72 6d   00# Normal# Norm  
61 6c 23 20 0d 0d 0a 33 32 38 33 23 20 20 32 32   al# ...3283#  22  
37 30 30 23 20 4e 6f 72 6d 61 6c 23 20 4e 6f 72   700# Normal# Nor  
6d 61 6c 23 20 0d 0d 0a 33 32 38 33 23 20 20 32   mal# ...3283#  2  
32 37 30 30 23 20 4e 6f 72 6d 61 6c 23 20 4e 6f   2700# Normal# No  
72 6d 61 6c 23 20 0d 0d 0a 33 32 38 33 23 20 20   rmal# ...3283#    
32 32 37 30 30 23 20 4e 6f 72 6d 61 6c 23 20 4e   22700# Normal# N  
6f 72 6d 61 6c 23 20 0d 0d 0a 30 23 0d 0d 0a 30   ormal# ...0#...0  
23 0d 0a 0d 43 6f 6d 6d 61 6e 64 20 63 6f 6d 70   #...Command comp  
6c 65 74 65 64 20 73 75 63 63 65 73 73 66 75 6c   leted successful  
6c 79 0d 0a 0d 24 24 0d 0a 0d 70 79 6c 6f 6e 5f   ly...$$...pylon_  
64 65 62 75 67 3e                                 debug>   

74 6f 74 61 6c 63 6f 75 6c 0a                     totalcoul.        

74 6f 74 61 6c 63 6f 75 6c 0a 0d 40 0d 0d 0a 20   totalcoul..@...   
54 6f 74 61 6c 20 43 6f 75 6c 3a 20 35 30 30 30   Total Coul: 5000  
30 20 6d 41 48 0d 0a 0d 43 6f 6d 6d 61 6e 64 20   0 mAH...Command   
63 6f 6d 70 6c 65 74 65 64 20 73 75 63 63 65 73   completed succes  
73 66 75 6c 6c 79 0d 0a 0d 24 24 0d 0a 0d 70 79   sfully...$$...py  
6c 6f 6e 5f 64 65 62 75 67 3e                     lon_debug>   

I got the same responses from the D1-mini and ESP32 projects above. Note that for these types of devices, the logic level converter is not an optional interface but is absolutely necessary. Without it you may have a not valid communication. I never used Raspberry, so I don't know if it needs. If rasp is rs-232 directly connected, you don't need converter, because it is as as a pc com.

creativtransfer commented 2 years ago

Thank you. I have a cheap USB to RS232 converter such as this one: https://de.aliexpress.com/item/488848564.html with an RJ11 connector crimped to the end. I am getting data, but compared to your output it's a mess. No readable ASCII characters. Using 115200baud, I get a reply which repeats itself but it looks like this. When I have some time, I'll try to add the logic-level converter and try again.

From what I see, you are not appending a checksum (CRC) to the command, just finishing with 0x0a

This is the reply I get (115200baud): '!#�0���巷���#)#!#�9!%%=#7����@��DD4Ŀ��

received dezimal values 33 168 64 145 148 68 4 68 52 172 229 127 229 229 235 85 35 41 35 33 17 35 191 57 33 37 37 61 35 55 191 177 133 168 64 145 148 68 4 68 52 196 ...

fotosettore commented 2 years ago

Unlike other protocols, such as that of BMS SEPLOS, that of pylontech fortunately is a simpler and more immediate protocol but above all it responds with ascii characters, as for example does the MPP SOLAR inverters. Pylon protocol does not need CRC but only a LF (0x0A). I repeat : I don't know how rasp works but remember that if you communicate with a RS232, it is already a 5V signal. If you have not good answers, before of all test commands on a pc, using the software I advided in last post.

44matrix commented 2 years ago

Hi, I built one of these monitors a couple of years ago and it worked fine with my UC3000 batteries as described in the original design using the RJ11 port. End of last year I fited a new UC3000C battery as master and had to convert to the 8 pin 'Console' port. I rewired the cable and it started working but was erratic. It then refused to read at all and I abandonned it until this month. On investigation, the RS232 signals were barely 8-10volts pk/pk and the MQTT was trying to reconnect every cycle of the program. I also discovered from the web that the UC2000/3000C batteries do not need the 1200/115200 codes and rate change to wake them, they are ready to communicate at 115200 on startup. I have commented out 'void wakeUpConsole and void switchBaud' sections of the code and added this into the void loop: - void loop() { // millis delay check below runs 'mqttLoop' every 30 seconds Client.loop(); if (millis() - lastMillis >= 30*1000UL) // >= 30 sec period { lastMillis = millis(); // reset ready for next loop mqttLoop(); } ArduinoOTA.handle(); server.handleClient(); timer.run();_ code from this point is original -- The erratic connection problem was fixed by including a 3.3v to 5v level shifter (cost 99p) between the ESP and the MAX3232 as in the attached diagram. This had the effect of increasing the RS232 signal voltages sent to the battery to around 12/15volts pk/pk, much more' in spec' and a very solid connection. There have been some changes to the original project that accomodate the new battery types since I last 'checked in' (November 2021) and reading the above comments, the discoveries I made in adapting my original build may help resolve some of the problems experienced by others. image Hope this helps others connecting to UC2000/3000C batteries. Usual repeat of warnings - proceed at your own risk!

Was having issue and getting ????? - with setup for Pylontech US3000C - but using Max3232 and level converter as per schematic shown resolved my issue.

dxoverdy commented 2 years ago

Can someone explain to me why those mini RS232 boards only have the +/- drilled? It drove me insane getting it soldered. (Didn't realise I had the temp on the iron too high from a previous job - took the pad straight off :(

44matrix commented 2 years ago

Don't use that - When I used that it did not work!

Only worked with the Sparkfun chip. Not sure why.

NS

Get Outlook for Androidhttps://aka.ms/AAb9ysg


From: Daniel Young @.> Sent: Monday, November 7, 2022 4:01:20 PM To: irekzielinski/Pylontech-Battery-Monitoring @.> Cc: 44matrix @.>; Comment @.> Subject: Re: [irekzielinski/Pylontech-Battery-Monitoring] Response is only ???? (#1)

Can someone explain to me why those mini RS232 boards only have the +/- drilled? It drove me insane getting it soldered. (Didn't realise I had the temp on the iron too high from a previous job - took the pad straight off :(

— Reply to this email directly, view it on GitHubhttps://github.com/irekzielinski/Pylontech-Battery-Monitoring/issues/1#issuecomment-1305826172, or unsubscribehttps://github.com/notifications/unsubscribe-auth/A3Q7JVEQCOMXPDPJSUVZOHDWHERVBANCNFSM4JA4HRRQ. You are receiving this because you commented.Message ID: @.***>

dxoverdy commented 2 years ago

OK I've done the circuit by @Wibet10 however the first time I powered up the MAX3232 burnt out. Yes it was a cheap one but exactly the one in the drawing.

Taking the rest of the circuit out of the equation, if I put just 5V and GND from the D1 mini to a new MAX3232, my next one got hot as hell within a second or two.

If I run one on the 3V it doesn't get hot, suggesting fundamentally it's a 3V chip. Because the MAX3232 is digital to analogue, can I presume putting the logic shifter on the RS232 side won't work - i.e. amplify/reduce the RS232 side, not the TTL?

I'm a bit at a loss now. They are all sold as 3-5V chips but i've got heat out of a basic cheap piece of junk and also some serious heat when running at 5V out of this: https://www.ebay.co.uk/itm/353203562315

Thanks for any help.

44matrix commented 2 years ago

This thing is simple - if you get Heat, then you have some wiring issue. I used cheap max3232 chip and did not work until I used the level shifter and exact rs232 chip that was used by original design.

I will send you the drawing.

Regards, NS

Get Outlook for Androidhttps://aka.ms/AAb9ysg


From: Daniel Young @.> Sent: Friday, November 11, 2022 5:52:19 PM To: irekzielinski/Pylontech-Battery-Monitoring @.> Cc: 44matrix @.>; Comment @.> Subject: Re: [irekzielinski/Pylontech-Battery-Monitoring] Response is only ???? (#1)

OK I've done the circuit by @Wibet10https://github.com/Wibet10 however the first time I powered up the MAX3232 burnt out. Yes it was a cheap one but exactly the one in the drawing.

Taking the rest of the circuit out of the equation, if I put just 5V and GND from the D1 mini to a new MAX3232, my next one got hot as hell within a second or two.

If I run one on the 3V it doesn't get hot, suggesting fundamentally it's a 3V chip. Because the MAX3232 is digital to analogue, can I presume putting the logic shifter on the RS232 side won't work - i.e. amplify/reduce the RS232 side, not the TTL?

I'm a bit at a loss now. They are all sold as 3-5V chips but i've got heat out of a basic cheap piece of junk and also some serious heat when running at 5V out of this: https://www.ebay.co.uk/itm/353203562315

Thanks for any help.

— Reply to this email directly, view it on GitHubhttps://github.com/irekzielinski/Pylontech-Battery-Monitoring/issues/1#issuecomment-1311994475, or unsubscribehttps://github.com/notifications/unsubscribe-auth/A3Q7JVCWP5EDVDMOQUHDRM3WH2BVHANCNFSM4JA4HRRQ. You are receiving this because you commented.Message ID: @.***>

dxoverdy commented 2 years ago

Any drawing or advice gratefully received 👍

I’m not sure how I could get my test case of a separate d1 mini and two jumper wires from 5v and Gnd to a stand-alone max3232 (which got hot) wrong by the way 😂. Reading online it could be latching?

44matrix commented 2 years ago

Hi All, This worked fine for me and I used original chip SparkFun MAX3232 (could not solder the cheapo little Ebay Max3232 chip properly - so I gave up as I have sausage fingers). I am using US3000C batteries (two so far will be adding #3 within next few days) - although I can see information about my batteries - sometimes it is very delayed via SolisCloud App - and it does not show Battery Temperature. (Had lots of fun getting all this into HTML web page from JSON output.

My little site shows the current data and only updates the data only with text being static just like original design. I aim to add Battery Charge count to my display to keep track of number of Charge cycle my batteries have endured so far. But with only two - so far I have seen the count @ ~60 and these have been in use for about 80-100 days so far. So I used FETCH() function and had to enable CORS in the D1 code. every time I post data. Qdos to original designer - I will enhance as much as possible and help anyone else if I can.

Regards,NS On Saturday, 12 November 2022 at 08:08:43 GMT, Daniel Young @.***> wrote:

Any drawing or advice gratefully received 👍

I’m not sure how I could get my test case of a separate d1 mini and two jumper wires from 5v and Gnd to a stand-alone max3232 (which got hot) wrong by the way 😂 . Reading online it could be latching?

— Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you commented.Message ID: @.***>

Wibet10 commented 2 years ago

Hi, You seem to be having some of the same problems I saw when trying to get my original build going. See diagram Feb 2022. Here are some of the empirical facts I discovered from my various attempts;

Not all D1's are the same (in circuitry and quality) - some have very feeble 3.3V regulators which barely run the ESP and most have components between the USB port and the 5v rail pins. Therefore running any tests of intefaces using the USB to power the circuit may well fail even if the circuit is wired correctly. Be aware!

All D1's have 3.3v data and serial pins, pulling them up to 5v is almost certain to eventually kill or damage them. I have tried using LEDs and resistors to buffer several types of 5v interfaces to 3.3v data pins but this is unreliable on most versions of D1's (I found that it may work for a while but it usually becomes unreliable).

The external 5v supply is best fed directly onto the 5v pins of the ESP, not via the USB and the 5v supply needs a local electrolytic capacitor to smooth and buffer the supply. (sometimes a parallel ceramic 0.1uF is also needed) The MAX3232 has a voltage pump circuit which will introduce current spikes onto the 5v supply and can be a cause of latching. These spikes may also account for some random ESP resets I saw before including the 5v supply capacitors.

The logic level shifter is the most reliable way I have found to interface the 3.3v data inputs/outputs of the ESP to the MAX3232 serial driver (and many other 5v powered interface e.g. relays, Solid State mains switches etc.). On this bidirectional version of the level shifter, the Ground connection is common to both sides but the 3.3V and 5v sides are isolated. In my diagram, the electrolytic and ceramic capacitors are present on the 3.3V supply to help the ESP's on board regulator cope with the extra demand of the level shifter & serial interface (I do this now as a matter of course).

Remember that this version of the MAX3232 has two separate TTL to RS232 interfaces on the board, one on the top and another on the underside. The +v and -v pads are thro-hole plated so that both sides are powered but the in and out connections are totally separate for the top and bottom circuits. Be careful to get the send/receive connected correctly (ESP into RS232 end will likely destroy one or both!).

The MAX3232 chip is a TTL(0v<5v) to RS232 converter that has a voltage pump incorporated to convert/boost the TTL input (0v~5v) to RS232 levels +v>0v>-v . The chips voltage spec quotes 3v to 5v which is why they are often sold as 3.3v compatible, the best ones probably are but the cheap ones - it's just pot luck! I had some overheating problems with the first few MAX boards I tried before using the logic level shifters. The 4 off boards (8 circuits) I tried running at 3.3v, most just got hot or appeared dead. When the voltage pump stops switching it gets hot, this is what they refer to as latching eventually it fails - probably caused by insufficient voltage/power?

When I did get one MAX 3232 working, powered via 3.3v from the ESP - the output voltages at the serial side was approximately +4v to -4v around the ground (0v) - 8v pk-pk - this is the very minimum for the RS232 spec. (it did not work reliably with my Pylon 3000 battery interface) When connecting the MAX3232 to 5V and driving it via the level shifter from the ESP the RS232 voltages achived were +7.5v>0v>-7.5v - 15v pk-pk - this is comfortably in the RS232 spec. These voltages can be checked on an oscilloscope but they only change when the unit is sending or recieving data. (this configuration works with the Pylon 3000 & 3000C battery ok)

Lastly the 100ohm termination resistor - this terminates the serial line output of the RS232 and prevents any excessive voltage overshoot from the voltage pump in the MAX3232. Possibly another cause of latching and overheating? Serial transmission lines should always be terminated correctly.

After resolving the above issues I found that a few of the circuits on the MAX3232 boards I had been experimenting with did still work but it had been the factors above that made it appear that they had failed, some circuits were actually dead tho', I don't know if I had killed them while experimenting or they were DOA? If you have correctly powered the MAX3232 with 5v, connected it up the right way around, drive it via a level shifter from the ESP, have terminated the serial line with 100ohms and have adequately buffered the 5v & 3.3v supplies then it should not get hot (when idle or when signalling) and should actually send balanced +v>0v>-v signals into the 100ohm resistor.

I have not tried any other type of MAX3232 driver board other than the basic type shown in my diagram but if they are using the same chip they will have the same limitations. My very cheap MAX3232 has been running reliably now for more than 12 months in its latest incarnation on the 3000C batteries (another 18 months before that on the 3000 series batteries) so it appears to be stable and reliable.

I hope that these pointers may help you locate your problem areas and get your interfaces working.

dxoverdy commented 2 years ago

Thank you for your comprehensive guides and explanations.

One thing that probably explains why my first mini max3232 didn’t communicate, I’d burnt off the pad so just soldered to the underside thinking it was one of the same. Lol.

In the first instance before all this I built according to the original spec as part of this project. I used all 3.3v and an all in one rs/ttl board. That same board is pictured later. When using 3.3 logic it didn’t get hot but I only got ????. I tried the 5v supply but 3.3v serial pins and that caused it to get mad hot and equally didn’t work which led me to this thread.

I built your circuit on some prototyping board including the caps and what not but the small max3232s just got mad hot, I totally cooked one of them. When I took everything out of the equation and just gave it 5v and gnd from the d1 it got hot, indicating like you say, that the 5v out probably can’t be trusted. And I am using budget spec d1 minis. £3 ish.

I’ve just prototyped the following, I’ve not tested it yet, but essentially that same ttl daughterboard is now on the same 5v as the d1 but with the logic level introduced on tx/rx. Obviously this is notwithstanding your supply advice and I’m not sure if this board terminates the serial as advised… but it’s flashing tx continuously (probably part of the sketch but I’ve not started looking at rewriting that yet until I at least had a sign of life) but it is running cold and my multimeter sees a blip on the rx pin of the rj11.

Which incidentally is probably the cause of all my grief in the first place as the original pic on here indicated straight through pins 2 and 3 however that’s been confirmed as being wrong and they should have crossed.

7AA494DD-6373-4CE2-9312-E748FCD1D358

I’ll drive this over to my dads later and give it a quick test. I’m on an AlphaESS system so I leverage modbus/rs485 for all the info, he wants to do the same on this.

Thanks again

hardweb-it commented 1 year ago

I've two US2000C. Using a Wemos D1 mini clone, I was getting al zeroes at /jsonOut values. I only switch the 3v3 to 5v to power MAX2323, without any logic converter addon, and then it worked!

{"soc": 73, "temp": 32000, "currentDC": 22489, "avgVoltage": 50587, "baseState": "Charge", "batteryCount": 2, "powerDC": 1137, "estPowerAC": 1205, "isNormal": true}