Moin,
kennt sich hier jemand mit der RS485 Kommunikation vom Seplos aus?
Ich habe mir eine Platine gebaut mit einem MAX3485 und einem ESP32 um das Seplos auszulesen.
Meine ersten Erfolge waren, dass irgendwas zurück kam wo ich den String ~20004642E00200FD37\r (Telemetriedaten) an das BMS geschickt habe.
Die Antwort sollte etwas umfangreicher sein. Allerdings kommt nur mist zurück, der keinen Sinn ergibt.
Laut einem Github Projekt https://github.com/syssi/esphome-seplos-bms nutze ich die passenenden Offsetz von der Rückgabe vom BMS. Da wo eigentlich die Zellenanzahl stehen sollte (Byte 8 ist auch im Protokoll 2.0 Dokument so erläutert) kommt bei mir nur 49 zurück.
Es gibt zwar das Projekt vom BavarianSuperGuy, aber ich brauche mehr funktionen als dem sein Projekt kann. Er ist aber leider auch zu Eingebildet und gibt seinen Sourcecode nicht raus.
Meine größte hoffnung wäre jetzt, jemand hat ein Projekt auf lager, am besten in C (Oder Arduino) für den ESP8266 oder ESP32, was ich Adaptieren könnte.
Ich habe mir um das DalyBMS auszulesen schon viel mühe gemacht. https://github.com/MeisterQ/Daly_SMA_Gateway
Aber beim Seplos komme ich aktuell nicht weiter..
Auskennen ist sicherlich zu viel gesagt, aber ich hab die letzten Tage gerade eine Umsetzung in Python implementiert, weil ich mit den Informationen was auf CAN vorhanden ist nicht zufrieden war und hab das deswegen in den dbus-serialbattery-Treiber für Victron eingebaut. Das Protokoll ist krude, weil die Hex-Codes der einzelnen Bytes jeweils nochmal ASCII-kodiert. Das heißt Deine 49 ist vermutlich das erste Byte (Ascii "1"), das nächste wird bei einem 16-Zell BMS dann eine 48 sein ("0"), macht 0x10 = 16.
Auslesen ist bei mir mehr oder weniger vollständig, mich würde jetzt noch die schreibende Seite interessieren, also das Setzen von Limits z.B. Dazu hab ich aber leider keine Doku gefunden.
Hi,
schau mal hier:
Da gibts auch eine Implementierung für das Seplos BMS. Ist Teil eine größeren Projekts, das sich eigentlich damit beschäftigte, SUN 100 Inverter zu steuern.
Unterstützt neben Seplos auch QUCC und JK BMS.
Ist für einen ESP32 gemacht.
Viel Spaß damit
Gruß
Karsten
Habe mitlerweile gute Fortschritte gemacht, weil der BSC gut Dokumentiert ist.
Daran kann man sich orientieren.
Hallo zusammen,
ich wollte das SEPLOS BMS über einen RS485 to Ethernet converter in ioBroker integrieren und da es nichts fertiges gab, hab ich was gebastelt. Dazu habe ich erst einmal eine Excel Datei erstellt, um den String manuell decodieren zu können. Der String wird momentan nicht automatisch erzeugt, sondern es wird "stumpf" ein String hingeschickt und die Auswertung wird stumpf zerhackt und in ioBroker Objekte übertragen. Da ich kein Javascript kann, sind Verbesserungsvorschläge gerne gesehen.
Anbei die Byte-Zuordnungsliste, vielleicht hilft sie ja noch anderen (danke an @MeisterQ - habe es vom BSC abgeschaut):
Beispielantwort:
~2000460010960000100CF10CF10CF10CF00CF10CF10CF10CF10CF00CF20CF10CF20CF00CF30CF10CF2060B5C0B5A0B5D0B590B8C0B67FC2A14B560440A6D6003706D60000803E814B60021000003D70338DCBD
byte | definition | ascii | value | |
0 | startidentifier | ~ | ||
1 | protocol version | 20 | 2,0 | |
2 | device address | 00 | 0 | |
3 | device type | 46 | ||
4 | function code | 00 | ||
5 | data length checksum | 10 | ||
6 | data length | 96 | ||
7 | data flag | 00 | ||
8 | command group | 00 | ||
9 | Number of cells | 10 | 16 | |
10 | cell 1 voltage high byte | 0C | 3,313 | |
11 | cell 1 voltage low byte | F1 | ||
12 | cell 2 voltage high byte | 0C | 3,313 | |
13 | cell 2 voltage low byte | F1 | ||
14 | cell 3 voltage high byte | 0C | 3,313 | |
15 | cell 3 voltage low byte | F1 | ||
16 | cell 4 voltage high byte | 0C | 3,312 | |
17 | cell 4 voltage low byte | F0 | ||
18 | cell 5 voltage high byte | 0C | 3,313 | |
19 | cell 5 voltage low byte | F1 | ||
20 | cell 6 voltage high byte | 0C | 3,313 | |
21 | cell 6 voltage low byte | F1 | ||
22 | cell 7 voltage high byte | 0C | 3,313 | |
23 | cell 7 voltage low byte | F1 | ||
24 | cell 8 voltage high byte | 0C | 3,313 | |
25 | cell 8 voltage low byte | F1 | ||
26 | cell 9 voltage high byte | 0C | 3,312 | |
27 | cell 9 voltage low byte | F0 | ||
28 | cell 10 voltage high byte | 0C | 3,314 | |
29 | cell 10 voltage low byte | F2 | ||
30 | cell 11 voltage high byte | 0C | 3,313 | |
31 | cell 11 voltage low byte | F1 | ||
32 | cell 12 voltage high byte | 0C | 3,314 | |
33 | cell 12 voltage low byte | F2 | ||
34 | cell 13 voltage high byte | 0C | 3,312 | |
35 | cell 13 voltage low byte | F0 | ||
36 | cell 14 voltage high byte | 0C | 3,315 | |
37 | cell 14 voltage low byte | F3 | ||
38 | cell 15 voltage high byte | 0C | 3,313 | |
39 | cell 15 voltage low byte | F1 | ||
40 | cell 16 voltage high byte | 0C | 3,314 | |
41 | cell 16 voltage low byte | F2 | ||
42 | number of temperatures | 06 | 6 | |
43 | Temperature sensor 1 high byte | 0B | 17,7 | |
44 | Temperature sensor 1 low byte | 5C | ||
45 | Temperature sensor 2 high byte | 0B | 17,5 | |
46 | Temperature sensor 2 low byte | 5A | ||
47 | Temperature sensor 3 high byte | 0B | 17,8 | |
48 | Temperature sensor 3 low byte | 5D | ||
49 | Temperature sensor 4 high byte | 0B | 17,4 | |
50 | Temperature sensor 4 low byte | 59 | ||
51 | enviroment sensor high byte | 0B | 22,5 | |
52 | enviroment sensor low byte | 8C | ||
53 | mosfet high byte | 0B | 18,8 | |
54 | mosfert sensor low byte | 67 | ||
55 | charge/discharge current high byte | FC | -9,82 | |
56 | charge/discharge current low byte | 2A | ||
57 | total battery voltage high byte | 14 | 53,01 | |
58 | total battery voltage low byte | B5 | ||
59 | remaining capacity high byte | 60 | 246,44 | |
60 | remaining capacity low byte | 44 | ||
61 | custom number | 0A | ||
62 | battery capacity high byte | 6D | 280,00 | |
63 | battery capacity low byte | 60 | ||
64 | state of charge high byte | 03 | 88,00 | |
65 | state of charge low byte | 70 | ||
66 | rated capacyty high byte | 6D | 280,00 | |
67 | rated capacyty low byte | 60 | ||
68 | number of cycles high byte | 00 | 8 | |
69 | number of cycles low byte | 08 | ||
70 | state of health high byte | 03 | 100,00 | |
71 | state of health low byte | E8 | ||
72 | port voltage high byte | 14 | 53,02 | |
73 | port voltage low byte | B6 | ||
74 | reserved | 00 | ||
75 | reserved | 21 | ||
76 | reserved | 00 | ||
77 | reserved | 00 | ||
78 | reserved | 03 | ||
79 | reserved | D7 | ||
80 | reserved | 03 | ||
81 | reserved | 38 | ||
82 | reserved | DC | ||
83 | reserved | BD |
SEPLOS RS485 decoder.xlsx (12.8 KB)
Und das Script für ioBroker:
schedule("*/15 * * * * *", async function () { //schedule({second: [00,30]}, function() { const net = require('net'); // Define the TCP connection details const ipAddress = "10.1.1.202"; const port = 20003; const stringToSend = "~20004642E00200FD37\r"; // define helper variables var startIndex = 0; var TeilString = ''; var SeplosNameSpace = 'SEPLOS.'; // Convert the string to HEX data var hexDataToSend = Buffer.from(stringToSend, 'ascii'); // Create a new TCP client socket const client = new net.Socket(); // Connect to the specified IP and port client.connect(port, ipAddress, () => { // console.log('Connected to the server'); // Send the HEX data to the server client.write(hexDataToSend); // Output the data being sent // console.log('Data sent:', hexDataToSend.toString('hex')); // log(hexDataToSend.toString('hex')) }); // Listen for data received from the server client.on('data', (data) => { const receivedData = data.toString(); //log(receivedData); // evaluate received data if (receivedData.lenght > 22 || true) { // cell voltages for (let i = 1; i <= 16; i++) { let decimalNummer = 0.0; startIndex = 15 + i*4; TeilString = receivedData.substr(startIndex,4); decimalNummer = parseInt(TeilString, 16)/1000.0; // log('Cellnumber '+i.toString().padStart(2, '0')+': '+decimalNummer); // create / set the objects let varName = SeplosNameSpace + 'voltageCell'+i.toString().padStart(2, '0'); if ( !existsState(varName)) { createState(varName,decimalNummer, { type: "number", role: "value", unit: "V", read: true, write: true }); } setState(varName,decimalNummer); } // temperatures for (let i = 1; i <= 6; i++) { let decimalNummer = 0.0; startIndex = 81 + i*4; TeilString = receivedData.substr(startIndex,4); decimalNummer = (parseInt(TeilString, 16)-2731)/10.0; //log('temperature '+i.toString().padStart(2, '0')+': '+decimalNummer); // create / set the objects let varName = SeplosNameSpace + 'temperature'+i.toString().padStart(2, '0'); if ( !existsState(varName)) { createState(varName,decimalNummer, { type: "number", role: "value", unit: "°C", read: true, write: true }); } setState(varName,decimalNummer); } //charge / discharge current let decimalNummer = 0.0; startIndex = 109; TeilString = receivedData.substr(startIndex,4); decimalNummer = (parseInt(TeilString, 16)); // convert unsigned int to signed if ((decimalNummer & 0x8000) > 0) { decimalNummer = decimalNummer - 0x10000; } decimalNummer = decimalNummer / 100.0; //log('batteryCurrent: '+decimalNummer+' - teilstring:'+ TeilString); // create / set the objects let varName = SeplosNameSpace + 'batteryCurrent'; if ( !existsState(varName)) { createState(varName,decimalNummer, { type: "number", role: "value", unit: "A", read: true, write: true }); } setState(varName,decimalNummer); //total pack voltage startIndex = 113; TeilString = receivedData.substr(startIndex,4); decimalNummer = (parseInt(TeilString, 16))/100; //log('TotalPackVoltage: '+decimalNummer); // create / set the objects varName = SeplosNameSpace + 'TotalPackVoltage'; if ( !existsState(varName)) { createState(varName,decimalNummer, { type: "number", role: "value", unit: "V", read: true, write: true }); } setState(varName,decimalNummer); // remeinaing capacity startIndex = 117; TeilString = receivedData.substr(startIndex,4); decimalNummer = (parseInt(TeilString, 16))/100.0; //log('RemainingCapacity: '+decimalNummer); // create / set the objects varName = SeplosNameSpace + 'RemainingCapacity'; if ( !existsState(varName)) { createState(varName,decimalNummer, { type: "number", role: "value", unit: "Ah", read: true, write: true }); } setState(varName,decimalNummer); // battery capacity startIndex = 123; TeilString = receivedData.substr(startIndex,4); decimalNummer = (parseInt(TeilString, 16))/100.0; //log('batteryCapacity: '+decimalNummer); // create / set the objects varName = SeplosNameSpace + 'batteryCapacity'; if ( !existsState(varName)) { createState(varName,decimalNummer, { type: "number", role: "value", unit: "Ah", read: true, write: true }); } setState(varName,decimalNummer); // state of charge startIndex = 127; TeilString = receivedData.substr(startIndex,4); decimalNummer = (parseInt(TeilString, 16))/10.0; //log('SOC: '+decimalNummer); // create / set the objects varName = SeplosNameSpace + 'SOC'; if ( !existsState(varName)) { createState(varName,decimalNummer, { type: "number", role: "value", unit: "%", read: true, write: true }); } setState(varName,decimalNummer); // number of cycles startIndex = 135; TeilString = receivedData.substr(startIndex,4); decimalNummer = (parseInt(TeilString, 16)); //log('numberOfCycles: '+decimalNummer); // create / set the objects varName = SeplosNameSpace + 'numberOfCycles'; if ( !existsState(varName)) { createState(varName,decimalNummer, { type: "number", role: "value", unit: "", read: true, write: true }); } setState(varName,decimalNummer); // state of health startIndex = 139; TeilString = receivedData.substr(startIndex,4); decimalNummer = (parseInt(TeilString, 16))/10.0; //log('SOH: '+decimalNummer); // create / set the objects varName = SeplosNameSpace + 'SOH'; if ( !existsState(varName)) { createState(varName,decimalNummer, { type: "number", role: "value", unit: "%", read: true, write: true }); } setState(varName,decimalNummer); // port voltage startIndex = 143; TeilString = receivedData.substr(startIndex,4); decimalNummer = (parseInt(TeilString, 16))/100.0; // log('portVoltage: '+decimalNummer); // create / set the objects varName = SeplosNameSpace + 'portVoltage'; if ( !existsState(varName)) { createState(varName,decimalNummer, { type: "number", role: "value", unit: "V", read: true, write: true }); } setState(varName,decimalNummer); } // Close the connection after receiving the response (if needed) client.end(); }); // Handle any errors that occur during the connection client.on('error', (err) => { console.error('Error:', err); }); // Handle the 'close' event (connection closed) client.on('close', () => { //console.log('Connection closed'); }); });
Yeah super danke das werde ich testen!
Klappt das auch wenn man 2 seplos Packs hat mit dem Protokoll?
Ohne Anpassung nicht. Also die Auswertung an und für sich schon, aber nicht das ganze Skript.
Habe mir jetzt den Aufbau des Befehls nicht en Detail angeschaut, aber ich denke, du musst ihm sagen, gib mir die Telemetriedaten von Pack 1, dann auswerten, dann Pack 2 abfragen. Und dann bei der Auswertung die pack Nummer mit berücksichtigen und den namespace um diese Nummer ergänzen.
Wie würde denn der Abfragestring fürs zweite Pack aussehen?
Pack1: ist da irgendwo eine Id versteckt?
const stringToSend = "~20004642E00200FD37\r";
Ich denke den Rest könnte ich hinbekommen ?
Soweit ich weiß kann man alle Slave Packs (1, 2, 3, etc) mit einem rs485 to eth adapter auslesen.
Das Masterpack muss mit einem separaten Adapter abgefragt werden.
MeisterQ hat ja schon den Hinweis auf den BatterySafetyController gegeben, dort findest du u.a. folgenden Code:
static void getDataFromBms(uint8_t address, uint8_t function) { /* Beispieldaten * ->: 7E 32 30 30 30 34 36 34 32 45 30 30 32 30 30 46 44 33 37 0D * <-: 7E 32 30 30 30 34 36 30 30 31 30 39 36 30 30 30 31 31 30 30 43 43 30 30 43 43 33 30 43 43 32 30 43 42 46 30 43 43 33 30 43 43 30 30 43 43 30 30 43 43 31 30 43 43 31 30 43 43 30 30 43 43 32 30 43 43 33 30 43 43 37 30 43 43 35 30 43 43 35 30 43 43 36 30 36 30 42 36 46 30 42 37 32 30 42 37 32 30 42 37 31 30 42 39 36 30 42 37 43 46 44 37 46 31 34 36 41 32 38 33 45 30 41 34 45 32 30 30 32 30 33 34 45 32 30 30 30 31 35 30 33 45 38 31 34 36 43 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 44 44 30 31 0D */ uint16_t lenid = lCrc(2); const uint16_t frame_len = 7; uint8_t u8_lData[9]; uint8_t u8_lSendData[20]; u8_lData[0]=0x20; // VER u8_lData[1]=address; // ADDR u8_lData[2]=0x46; // CID1 u8_lData[3]=function; // CID2 (0x42) u8_lData[4]=(lenid >> 8); // LCHKSUM (0xE0) u8_lData[5]=(lenid >> 0); // LENGTH (0x02) u8_lData[6]=0x0; // VALUE (0x00) convertByteToAsciiHex(&u8_lSendData[1], &u8_lData[0], frame_len); uint16_t crc = calcCrc(&u8_lSendData[1], frame_len*2); #ifdef SEPLOS_DEBUG ESP_LOGD(TAG,"crc=%i", crc); #endif u8_lData[7]=(crc >> 8); // CHKSUM (0xFD) u8_lData[8]=(crc >> 0); // CHKSUM (0x37) convertByteToAsciiHex(&u8_lSendData[15], &u8_lData[7], 2); u8_lSendData[0]=0x7E; // SOF (0x7E) u8_lSendData[19]=0x0D; // EOF (0x0D)
(...) unter bsc_fw/src/devices/SeplosBms.cpp at main · shining-man/bsc_fw · GitHub
Musst halt aufpassen, dass im Beispiel noch auf die Umwandlung von ASCII und Hex zu achten ist.
Ich kann das leider nicht testen, da ich nur ein Pack in Betrieb habe.
Als Gateway habe ich ein pusr iot RS485 zu Ethernet gateway. Dafür gibt es virtuelle COM Port Treiber, d.h. der stimuliert einen COM Port über Netzwerk und kann dann mit der Originalsoftware einem Befehl Absenden und mitschreiben.
Dann kann man mit PuTTY oder PacketSender selbst Befehle senden (letzteres Programm kann ich sehr empfehlen!!!).
Vielleicht helfen dir die Brotkrumen ein wenig.
Viele Grüße!
Hi,
ich habe ein komplett funktionierendes Programm, wo die Daten per JSON über MQTT gepollt werden mitlerweile.
Es geht allerdings nur ein BMS bisher.
Wenn jemand interesse hat, gerne Melden.
Das ganze läuft auf einem ESP32 und nutzt einen MAX485
Im SEPLOS BMS Communication Protocol Handbuch findest du in Tabelle 7 die Telemetrie-Anfrage für die verschiedenen Adressen:
Address | Telemetry Command Info Frame (ASCII) | ASCII -> Zeichen |
---|---|---|
0 | 7E 32 30 30 30 34 36 34 32 45 30 30 32 30 30 46 44 33 37 0D | ~20004642E00200FD37 |
1 | 7E 32 30 30 31 34 36 34 32 45 30 30 32 30 31 46 44 33 35 0D | ~20014642E00201FD35 |
2 | 7E 32 30 30 32 34 36 34 32 45 30 30 32 30 32 46 44 33 33 0D | ~20024642E00202FD33 |
3 | 7E 32 30 30 33 34 36 34 32 45 30 30 32 30 33 46 44 33 31 0D | ~20034642E00203FD31 |
4 | 7E 32 30 30 34 34 36 34 32 45 30 30 32 30 34 46 44 32 46 0D | ~20044642E00204FD2F |
5 | 7E 32 30 30 35 34 36 34 32 45 30 30 32 30 35 46 44 32 44 0D | ~20054642E00205FD2D |
6 | 7E 32 30 30 36 34 36 34 32 45 30 30 32 30 36 46 44 32 42 0D | ~20064642E00206FD2B |
7 | 7E 32 30 30 37 34 36 34 32 45 30 30 32 30 37 46 44 32 39 0D | ~20074642E00207FD29 |
8 | 7E 32 30 30 38 34 36 34 32 45 30 30 32 30 38 46 44 32 37 0D | ~20084642E00208FD27 |
9 | 7E 32 30 30 39 34 36 34 32 45 30 30 32 30 39 46 44 32 35 0D | ~20094642E00209FD25 |
10 | 7E 32 30 30 41 34 36 34 32 45 30 30 32 30 41 46 44 31 35 0D | ~200A4642E0020AFD15 |
11 | 7E 32 30 30 42 34 36 34 32 45 30 30 32 30 42 46 44 31 33 0D | ~200B4642E0020BFD13 |
12 | 7E 32 30 30 43 34 36 34 32 45 30 30 32 30 43 46 44 31 31 0D | ~200C4642E0020CFD11 |
13 | 7E 32 30 30 44 34 36 34 32 45 30 30 32 30 44 46 44 30 46 0D | ~200D4642E0020DFD0F |
14 | 7E 32 30 30 45 34 36 34 32 45 30 30 32 30 45 46 44 30 44 0D | ~200E4642E0020EFD0D |
15 | 7E 32 30 30 46 34 36 34 32 45 30 30 32 30 46 46 44 30 42 0D | ~200F4642E0020FFD0B |
Viele Grüße
Hallo zusammen,
Ich versuche seit längerem das Seplos BMS und Home Assistant über einen ESP32 auszulesen mit einem rs485 zu TTL Adapter.
Leider komme ich irgendwie nicht weiter.
Könnte mir jemand mal seine funktionierende yaml für ESPHome zur Verfügung stellen?
@meisterq würde dein Programm hier funktionieren?
Vielen Dank
Viele Grüße
@marsol
Mein Programm stellt die Daten im JSON Format über MQTT bereit. Weiß nicht ob dir das hilft, da ich IObroker nutze.
Nutze seit einem halben Jahr GitHub - Lu-Fi/tasmota-seplos-driver - ist auch hier im Forum besprochen. Die Daten bekomme ich per MQTT und verarbeite sie in IP Symcon weiter. Passt.
Da hier gerade von IPSymcon die Rede ist. Da habe ich auch ein Modul geschrieben. Hast du ein oder zwei Batterien installiert.
Ich habe nämlich das Problem, dass ich ich per CAN mit dem WR Kommuniziere und die beiden Batterien die ich habe über RS485 verbunden sind. Sobald ich aber CAN aktiviere sehe ich über RS485 nur noch eine Batterie.
@thomasw69 nur eine Batterie leider, kann daher dazu nichts sagen...
Ich habe 2 Batterien dran sobald ich beide verbinde per rs485 kann ich die Batterien nicht mehr auslesen.
bekomme dann nur die Batterie Daten vom Deye. In ips
ich hatte meinen Ansprechpartner bei Seplos mal gefragt, was man machen kann. Als erstes kam diese Antwort.
When you use 2 battery packs to set up the master and slave machines in parallel, you can only use RS485 to see the data of all slave machines but the data of the master machine cannot be seen. This is caused by the communication logic of the BMS. If you want to see the data of the host, you can check it through the Bluetooth App, or dial the dial of the host of the BMS back to the original dial, and then you can use RS485 to view the data of the battery packDas ist natürlich nicht sehr erfreulich. Ich habe dann nochmal nachgehakt und dann kam das.
Or we have a data collection board,do you want it?Da bin ich natürlich neugierig geworden. Ich hab erst mal mein Interesse bekundet und um technische Informationen gebeten. Mal schauen, was da kommt.
Es gibt von Seplos ein Data-Monitoring Board, welches man direkt in China für 80€ zzgl Steuern. usw. bestellen kann. Damit soll man mehrere Akkus verbinden und sich die Daten einerseits in Summe Visualisieren, andererseits auch komplett per RS485 auslesen können. Ich hab das mal bestellt und die chinesische Bedienungsanleitung mit Deepl übersetzen lassen. Dummerweise ist die PDF 3,2MB groß und hier kann ich nur bis 2,9MB hoch laden.
EDIT: hab es auf Filehorst hoch geladen https://filehorst.de/d/enJoJmGJ
Oh, ein ESP und RS485 Treiber tun das auch... na ja, wer nicht Basteln will, eine Lösung, aber dann kann man für 75€ auch ein BSC bestellen, (Bestellung läuft dieses WE aus) der kann noch viel mehr...