Seplos per RS485 auslesen

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
Hier die Excel:

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');
});
});
1 „Gefällt mir“

@dadeppa

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:

Table 7 Telemetry command examples:
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
Ich habe mal die "Zeichen-Konvertierung" hier als Spalte ergänzt. Wenn du die sendest, dann solltest du die jeweiligen infos bekommen. Man könnte den Sendestring zwar auch berechnen... aber ich hab's "stumpf" implementiert...

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.

@mola

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 pack
Das 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.
2 „Gefällt mir“

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

1 „Gefällt mir“

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...

1 „Gefällt mir“