diff --git a/ChangeLog.md b/ChangeLog.md
index f070e1c..6f523d9 100644
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -1,5 +1,8 @@
Release 3.2.2:
- add GoodWe Support, by TigerGrey (#58)
+ - add support for id and livedate on same functioncode, by TigerGrey (#58)
+ - add CRC Check to validate response (#43)
+ - add data length check to validate response (#43)
Release 3.2.1:
- add modbusprotocoll doc for Solax-X1/X3 G4 (incl. write register) (thanks to @lattemacchiato) (#64)
diff --git a/data/web/modbusconfig.html b/data/web/modbusconfig.html
index cfc0b1c..e1c3802 100644
--- a/data/web/modbusconfig.html
+++ b/data/web/modbusconfig.html
@@ -67,16 +67,29 @@
Enable OpenWB Compatibility |
-
-
-
-
-
- |
-
+
+
+
+
+
+ |
+
+
+
+ Enable CRC Check |
+
+
+
+
+
+ |
+
Enable Set Commands over MQTT (security issue) |
diff --git a/src/modbus.cpp b/src/modbus.cpp
index 237e0b7..fd4c45a 100644
--- a/src/modbus.cpp
+++ b/src/modbus.cpp
@@ -3,7 +3,7 @@
/*******************************************************
* Constructor
*******************************************************/
-modbus::modbus() : Baudrate(19200), LastTxLiveData(0), LastTxIdData(0), LastTxInverter(0) {
+modbus::modbus() : Baudrate(19200), enableCrcCheck(true), LastTxLiveData(0), LastTxIdData(0), LastTxInverter(0) {
DataFrame = new std::vector{};
SaveIdDataframe = new std::vector{};
SaveLiveDataframe = new std::vector{};
@@ -260,21 +260,21 @@ void modbus::LoadInverterConfigFromJson() {
}
}
- this->Conf_LiveDataFunctionCode = this->String2Byte(doc[this->InverterType.name]["config"]["LiveDataFunctionCode"].as());
- this->Conf_IdDataFunctionCode = this->String2Byte(doc[this->InverterType.name]["config"]["IdDataFunctionCode"].as());
+ //this->Conf_LiveDataFunctionCode = this->String2Byte(doc[this->InverterType.name]["config"]["LiveDataFunctionCode"].as());
+ //this->Conf_IdDataFunctionCode = this->String2Byte(doc[this->InverterType.name]["config"]["IdDataFunctionCode"].as());
this->Conf_LiveDataErrorCode = this->String2Byte(doc[this->InverterType.name]["config"]["LiveDataErrorCode"].as());
this->Conf_IdDataErrorCode = this->String2Byte(doc[this->InverterType.name]["config"]["IdDataErrorCode"].as());
- this->Conf_LiveDataSuccessCode = this->String2Byte(doc[this->InverterType.name]["config"]["LiveDataSuccessCode"].as());
- this->Conf_IdDataSuccessCode = this->String2Byte(doc[this->InverterType.name]["config"]["IdDataSuccessCode"].as());
+ //this->Conf_LiveDataSuccessCode = this->String2Byte(doc[this->InverterType.name]["config"]["LiveDataSuccessCode"].as());
+ //this->Conf_IdDataSuccessCode = this->String2Byte(doc[this->InverterType.name]["config"]["IdDataSuccessCode"].as());
this->Conf_ClientIdPos = int(doc[this->InverterType.name]["config"]["ClientIdPos"]);
- this->Conf_LiveDataStartsAtPos = int(doc[this->InverterType.name]["config"]["LiveDataStartsAtPos"]);
- this->Conf_IdDataStartsAtPos = int(doc[this->InverterType.name]["config"]["IdDataStartsAtPos"]);
+ //this->Conf_LiveDataStartsAtPos = int(doc[this->InverterType.name]["config"]["LiveDataStartsAtPos"]);
+ //this->Conf_IdDataStartsAtPos = int(doc[this->InverterType.name]["config"]["IdDataStartsAtPos"]);
this->Conf_LiveDataErrorPos = int(doc[this->InverterType.name]["config"]["LiveDataErrorPos"]);
this->Conf_IdDataErrorPos = int(doc[this->InverterType.name]["config"]["IdDataErrorPos"]);
- this->Conf_LiveDataSuccessPos = int(doc[this->InverterType.name]["config"]["LiveDataSuccessPos"]);
- this->Conf_IdDataSuccessPos = int(doc[this->InverterType.name]["config"]["IdDataSuccessPos"]);
- this->Conf_IdDataFunctionCodePos = int(doc[this->InverterType.name]["config"]["IdDataFunctionCodePos"]);
- this->Conf_LiveDataFunctionCodePos= int(doc[this->InverterType.name]["config"]["LiveDataFunctionCodePos"]);
+ //this->Conf_LiveDataSuccessPos = int(doc[this->InverterType.name]["config"]["LiveDataSuccessPos"]);
+ //this->Conf_IdDataSuccessPos = int(doc[this->InverterType.name]["config"]["IdDataSuccessPos"]);
+ //this->Conf_IdDataFunctionCodePos = int(doc[this->InverterType.name]["config"]["IdDataFunctionCodePos"]);
+ //this->Conf_LiveDataFunctionCodePos= int(doc[this->InverterType.name]["config"]["LiveDataFunctionCodePos"]);
Conf_RequestLiveData->clear();
for (JsonArray arr : doc[this->InverterType.name]["config"]["RequestLiveData"].as()) {
@@ -284,7 +284,7 @@ void modbus::LoadInverterConfigFromJson() {
byte e = this->String2Byte(x);
t.push_back(e);
}
- Serial.println();
+ t.push_back(DATAISLIVE); // last byte is datatype
Conf_RequestLiveData->push_back(t);
}
@@ -293,6 +293,7 @@ void modbus::LoadInverterConfigFromJson() {
byte e = this->String2Byte(elem);
Conf_RequestIdData->push_back(e);
}
+ Conf_RequestIdData->push_back(DATAISID); // last byte is datatype
if (regfile) { regfile.close(); }
@@ -377,6 +378,7 @@ void modbus::QueryQueueToInverter() {
enum rwtype_t {READ, WRITE, NUL};
rwtype_t rwtype;
std::vector m = {};
+ byte rememberdatatype = 0;
if (!this->SetQueue->isEmpty()) {
rwtype = WRITE;
@@ -385,6 +387,8 @@ void modbus::QueryQueueToInverter() {
else if (!this->ReadQueue->isEmpty()) {
rwtype = READ;
m = this->ReadQueue->dequeue();
+ rememberdatatype = m[m.size()-1]; // store current datatype
+ m.pop_back(); // shorten queue item to original size
}
else { rwtype = NUL; }
@@ -425,6 +429,7 @@ void modbus::QueryQueueToInverter() {
else if (rwtype == READ) {
this->ReceiveReadData();
if (this->ReadQueue->isEmpty()) {
+ this->DataFrame->push_back(rememberdatatype); // add datatype at the end of the received frame
this->ParseData();
}
}
@@ -471,6 +476,7 @@ bool modbus::ReceiveSetData(std::vector* SendHexFrame) {
void modbus::ReceiveReadData() {
char dbg[100] = {0};
memset(dbg, 0, sizeof(dbg));
+ size_t dataFrameStartPos = this->DataFrame->size();
if (Config->GetDebugLevel() >=3) {Serial.println("Read Data from Queue: ");}
@@ -482,21 +488,57 @@ void modbus::ReceiveReadData() {
this->DataFrame->push_back(d);
if (Config->GetDebugLevel() >=4) {Serial.print(PrintHex(d)); Serial.print(" ");}
delay(1); // keep this! Loosing bytes possible if too fast
- }
+ }
if (Config->GetDebugLevel() >=4) {Serial.println();}
- if (this->DataFrame->size() > 5 &&
- this->DataFrame->at(this->Conf_ClientIdPos) == this->ClientID &&
- this->DataFrame->at(this->Conf_IdDataErrorPos) != this->Conf_IdDataErrorCode &&
- this->DataFrame->at(this->Conf_LiveDataErrorPos) != this->Conf_LiveDataErrorCode) {
+ bool valid = true;
+
+ if (this->DataFrame->size()-dataFrameStartPos > 5 &&
+ this->DataFrame->at(dataFrameStartPos+this->Conf_ClientIdPos) == this->ClientID &&
+ this->DataFrame->at(dataFrameStartPos+this->Conf_IdDataErrorPos) != this->Conf_IdDataErrorCode &&
+ this->DataFrame->at(dataFrameStartPos+this->Conf_LiveDataErrorPos) != this->Conf_LiveDataErrorCode) {
+
+ if (Config->GetDebugLevel() >=4) Serial.println("ErrorCode passed, OK");
+
+ if (this->enableCrcCheck) {
+ //CRC Check
+ uint16_t crc = this->Calc_CRC(this->DataFrame, dataFrameStartPos, this->DataFrame->size()-2);
+
+ if (Config->GetDebugLevel() >=4) {
+ Serial.printf("Received CRC: 0x%02X 0x%02X\n", this->DataFrame->at(this->DataFrame->size()-2), this->DataFrame->at(this->DataFrame->size()-1));
+ Serial.printf("Calculated CRC: 0x%02X 0x%02X\n", lowByte(crc), highByte(crc));
+ }
+
+ if (this->DataFrame->at(this->DataFrame->size()-2) != lowByte(crc) ||
+ this->DataFrame->at(this->DataFrame->size()-1) != highByte(crc)) {
+ valid = false;
+ if (Config->GetDebugLevel() >=2) Serial.println("CRC check failed!");
+ }
+
+ // Check datalength
+ if (this->DataFrame->at(dataFrameStartPos+2) != this->DataFrame->size()-dataFrameStartPos-5) {
+ valid = false;
+ if (Config->GetDebugLevel() >=2) Serial.printf("data length check failed, should be %d but is %d\n", this->DataFrame->at(dataFrameStartPos+2), this->DataFrame->size()-dataFrameStartPos-5);
+ }
+ }
+ } else { valid = false; }
+
+ if (valid) {
// Dataframe valid
if (Config->GetDebugLevel() >=3) {
sprintf(dbg, "Dataframe valid, Dateframe size: %d bytes", this->DataFrame->size());
Serial.println(dbg);
}
+
} else {
if (Config->GetDebugLevel() >=2) {Serial.println("Dataframe invalid");}
+ // clear dataframe, clear ReadQueue to start from fresh
+ this->DataFrame->clear();
+ for (unsigned int n = 0; n < this->ReadQueue->itemCount(); n++) {
+ this->ReadQueue->dequeue();
+ }
}
+
} else {
if (Config->GetDebugLevel() >=2) {Serial.println("no response from client");}
}
@@ -554,19 +596,16 @@ void modbus::ParseData() {
String RequestType = "";
char dbg[100] = {0};
memset(dbg, 0, sizeof(dbg));
-
- if (Config->GetDebugLevel() >=3) {
- sprintf(dbg, "Parse %d Bytes of data", this->DataFrame->size());
- Serial.println(dbg);
- }
+ byte tempbyte;
- if (this->DataFrame->size() == 0) {
+ if (this->DataFrame->size() <= 1) {
// TODO
// ***********************************************
// do some tests if client isnĀ“t connected, dataframe is empty
// ***********************************************
#ifdef DEBUGMODE
+ this->DataFrame->clear();
if (Config->GetDebugLevel() >=3) {Serial.println("Start parsing in testmode, use some testdata instead real live data :)");}
// Solar-KTL
@@ -576,37 +615,42 @@ void modbus::ParseData() {
//byte ReadBuffer[] = {0x01, 0x04, 0x80, 0x12, 0x34, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x09, 0x64, 0x09, 0x67, 0x09, 0x6B, 0x13, 0x8C, 0x13, 0x8D, 0x13, 0x8B, 0x00, 0x27, 0x00, 0x28, 0x00, 0x28, 0x00, 0x2C, 0x0B, 0x54, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x0D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x70, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5B, 0xC3};
//Solax X1
- byte ReadBuffer[] = {0x01, 0x04, 0xA6, 0x08, 0xF4, 0x00, 0x0D, 0x01, 0x0D, 0x0A, 0x26, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x13, 0x8B, 0x00, 0x1C, 0x00, 0x02, 0x01, 0x1B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE6, 0x00, 0x00, 0x00, 0x46, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x09, 0x17, 0x04, 0x56};
- //byte ReadBuffer[] = {0x01, 0x03, 0x28, 0x48, 0x34, 0x35, 0x30, 0x32, 0x41, 0x49, 0x34, 0x34, 0x35, 0x39, 0x30, 0x30, 0x35, 0x73, 0x6F, 0x6C, 0x61, 0x78, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x4A, 0xA0};
+ //byte ReadBuffer[] = {0x01,0x04,0xEE,0x09,0x29,0x00,0x5E,0x08,0x9E,0x0B,0xFA,0x0B,0x44,0x00,0x16,0x00,0x39,0x13,0x8A,0x00,0x26,0x00,0x02,0x02,0xB8,0x06,0x74,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x05,0xB6,0x00,0x00,0x03,0xAC,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0A,0x00,0x00,0x6E,0xFB,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0E,0x33,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xB9,0xA4,0x01,0x04,0xEE,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x59,0x77,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x69,0x00,0x00,0x86,0x6B,0x00,0x01,0x00,0x0B,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x70,0x00,0x00,0x03,0xF2,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0C,0xB9,0x02};
+ //byte ReadBuffer[] = {0x01,0x04,0xEE,0x09,0x29,0x00,0x5E,0x08,0x9E,0x0B,0xFA,0x0B,0x44,0x00,0x16,0x00,0x39,0x13,0x8A,0x00,0x26,0x00,0x02,0x02,0xB8,0x06,0x74,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x05,0xB6,0x00,0x00,0x03,0xAC,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0A,0x00,0x00,0x6E,0xFB,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0E,0x33,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xB9,0xA4,0x02};
+ byte ReadBuffer[] = {0x01,0x03,0x28,0x48,0x34,0x35,0x30,0x32,0x41,0x49,0x34,0x34,0x35,0x39,0x30,0x30,0x35,0x73,0x6F,0x6C,0x61,0x78,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x4A,0xA0, 0x01};
- //Growatt
- //byte ReadBuffer[] = {0x01,0x04,0xEE,0x00,0x06,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x02,0x05,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x79,0x01,0x13,0x88,0x10,0x02,0x00,0x2A,0x00,0x00,0x79,0x7D,0x10,0x08,0x00,0x2B,0x00,0x00,0x00,0x00,0x0F,0xCE,0x00,0x2A,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x5B,0x00,0x00,0x05,0xE6,0x00,0x1D,0x2C,0x06,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x54,0x01,0x47,0x01,0x69,0x00,0x00,0x0A,0x02,0x1C,0x1F,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x83,0x00,0x00,0x02,0xE8,0x00,0x00,0x00,0x00,0x00,0x00,0x7A,0x44};
+ //Growatt IDData
+ //byte ReadBuffer[] = {0x01,0x03,0xEE,0x00,0x01,0x00,0xBD,0xFF,0xFF,0x00,0x64,0x00,0x00,0x27,0x10,0x00,0x00,0x9C,0x40,0x06,0x40,0x44,0x4E,0x31,0x2E,0x30,0x00,0x5A,0x42,0x44,0x42,0x00,0x02,0x00,0x02,0x00,0x00,0x06,0x40,0x00,0x3C,0x00,0x3C,0x00,0x5A,0x00,0x5A,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x64,0x00,0x00,0x00,0x00,0x20,0x20,0x20,0x50,0x56,0x20,0x49,0x6E,0x76,0x65,0x72,0x74,0x65,0x72,0x20,0x20,0x00,0x00,0x15,0x18,0x02,0x03,0x07,0xE8,0x00,0x07,0x00,0x13,0x00,0x08,0x00,0x10,0x00,0x05,0x00,0x05,0x0C,0x73,0x13,0x73,0x12,0x8E,0x14,0x1E,0x07,0x00,0x13,0x73,0x12,0x8E,0x14,0x1E,0x07,0x00,0x13,0x73,0x12,0x8E,0x14,0x1E,0x0D,0x61,0x10,0xF6,0x12,0x9D,0x13,0x8D,0x00,0x98,0x00,0x32,0x00,0x32,0x00,0x32,0x00,0x32,0x00,0x32,0x00,0x32,0x00,0x32,0x00,0x32,0x00,0x32,0x00,0x32,0x00,0x32,0x11,0x1E,0x00,0x00,0x44,0x4E,0x41,0x41,0x30,0x31,0x35,0x31,0x30,0x30,0x30,0x32,0x01,0x31,0x00,0x00,0x00,0x00,0x13,0x9C,0x00,0x32,0x10,0x07,0x10,0xA6,0x0F,0x18,0x0E,0x79,0x00,0x14,0x00,0x05,0x10,0x57,0x0F,0x90,0x26,0xFD,0x26,0xFD,0x26,0xA5,0x27,0xC1,0x27,0xC1,0x28,0x21,0x00,0x0A,0x00,0x00,0x01,0xE4,0x00,0xFF,0x4E,0x20,0x00,0xFF,0x4E,0x20,0x00,0xFF,0x4E,0x20,0x00,0xFF,0x4E,0x20,0x07,0x00,0xFC,0xE5};
- for (uint8_t i = 0; iDataFrame->push_back(ReadBuffer[i]);
- if (Config->GetDebugLevel() >=4) {Serial.print(PrintHex(ReadBuffer[i])); Serial.print(" ");}
}
-
- //byte ReadBuffer2[] = {0x01,0x04,0xEE,0x00,0x06,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x82,0x78,0x00,0x00,0x00,0x00,0x0A,0x05,0x00,0x1B,0x00,0x00,0x00,0x00,0x05,0x3A,0xFE,0x79,0xFC,0x49,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x09,0x43,0x02,0x90,0x00,0x44,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x82,0x78,0x00,0x00,0x04,0x09,0x04,0x17,0x03,0xFB,0x00,0x00,0x82,0x78,0x00,0x00,0x00,0xEF,0x00,0x06,0x1C,0x1C,0x00,0xFA,0x00,0x00,0x00,0x26,0x00,0x00,0x06,0xC1,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x09,0x00,0x00,0x00,0x57,0x00,0x00,0x01,0x5D,0x00,0x00,0x00,0x6E,0x00,0x00,0x01,0xB9,0x00,0x00,0x00,0x6F,0x00,0x00,0x07,0x5F,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x09,0x2F,0x00,0x00,0x00,0x00,0x00,0x00,0x09,0x1A,0x00,0x00,0x00,0x00,0x00,0x00,0x09,0x0B,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x82,0x00,0x00,0x00,0x00,0x00,0x1B,0x0A,0x05,0xFB,0x00,0x01,0x20,0x00,0xFA,0x04,0xE2,0x11,0xBC,0x00,0x00,0x00,0x18,0x00,0x03,0x00,0x64,0x0B,0x18,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x0B,0x18,0x09,0x38,0x0C,0x95,0x0C,0x7D,0x04,0x2F,0x05,0x49,0x00,0xEF,0x00,0xDA,0x01,0x04,0x05,0x11,0x00,0x50,0xA4,0x20};
- //for (uint8_t i = 0; iDataFrame->push_back(ReadBuffer2[i]);
- // if (Config->GetDebugLevel() >=4) {Serial.print(PrintHex(ReadBuffer2[i])); Serial.print(" ");}
- //}
-
- if (Config->GetDebugLevel() >=4) { Serial.println(); }
+ if (Config->GetDebugLevel() >=4) { Serial.printf("%s\n", this->PrintDataFrame(this->DataFrame).c_str()); }
#endif
// ***********************************************
- }
-
- if (this->DataFrame->size() > 0) {
+ }
+
+ if (this->DataFrame->size() > 1) {
+ tempbyte = this->DataFrame->at(this->DataFrame->size()-1);
+ this->DataFrame->pop_back(); // shorten to original size
// setup RequestType
- if (this->DataFrame->at(this->Conf_IdDataFunctionCodePos) == this->Conf_IdDataFunctionCode) {
+ if (tempbyte == DATAISID) {
RequestType = "id";
- } else if (this->DataFrame->at(this->Conf_LiveDataFunctionCodePos) == this->Conf_LiveDataFunctionCode) {
+ }
+ else if (tempbyte == DATAISLIVE) {
RequestType = "livedata";
}
+ if (Config->GetDebugLevel() >=3) Serial.printf("parse %d bytes of data\n", this->DataFrame->size());
+ if (Config->GetDebugLevel() >=4) Serial.printf("identified datatype: %s\n", RequestType.c_str());
+
File regfile = LittleFS.open("/regs/"+this->InverterType.filename);
if (!regfile) {
if (Config->GetDebugLevel() >=0) {Serial.println("failed to open ModbusConfig.json file for writing");}
@@ -661,8 +705,7 @@ void modbus::ParseData() {
posArray = elem["position"].as();
} else {
if (Config->GetDebugLevel() >=1) {
- sprintf(dbg, "Error: for Name '%s' no position array found", d.Name);
- Serial.println(dbg);
+ Serial.printf("Error: for Name '%s' no position array found", d.Name);
}
continue;
}
@@ -826,19 +869,39 @@ void modbus::ChangeRegItem(std::vector* vector, reg_t item) {
*******************************************************/
uint16_t modbus::Calc_CRC(uint8_t* message, uint8_t len) {
uint16_t crc = 0xFFFF;
- for (int pos = 0; pos < len; pos++) {
+ for (uint8_t pos = 0; pos < len; pos++) {
crc ^= (uint16_t)message[pos]; // XOR byte into least sig. byte of crc
- for (int i = 8; i != 0; i--) { // Loop over each bit
- if ((crc & 0x0001) != 0) { // If the LSB is set
- crc >>= 1; // Shift right and XOR 0xA001
+ for (int i = 8; i != 0; i--) { // Loop over each bit
+ if ((crc & 0x0001) != 0) { // If the LSB is set
+ crc >>= 1; // Shift right and XOR 0xA001
+ crc ^= 0xA001;
+ }
+ else // Else LSB is not set
+ crc >>= 1; // Just shift right
+ }
+ }
+ return crc;
+}
+
+uint16_t modbus::Calc_CRC(std::vector* message, uint16_t startpos, uint16_t endpos) {
+ uint16_t crc = 0xFFFF;
+// Serial.print("Calc_CRC of: ");
+ for (uint16_t pos = startpos; pos < endpos; pos++) {
+ crc ^= (uint16_t)message->at(pos); // XOR byte into least sig. byte of crc
+ for (int i = 8; i != 0; i--) { // Loop over each bit
+ if ((crc & 0x0001) != 0) { // If the LSB is set
+ crc >>= 1; // Shift right and XOR 0xA001
crc ^= 0xA001;
}
- else // Else LSB is not set
- crc >>= 1; // Just shift right
+ else // Else LSB is not set
+ crc >>= 1; // Just shift right
}
+// Serial.printf("%s ",this->PrintHex(message->at(pos)).c_str());
}
+// Serial.println();
+// Serial.printf("Calculated CRC (%d values): 0x%02X 0x%02X\n", endpos-startpos, highByte(crc), lowByte(crc));
return crc;
-}
+}
/*******************************************************
* friendly output of hex nums
@@ -1121,6 +1184,7 @@ void modbus::LoadJsonConfig(bool firstrun) {
if (doc["data"].containsKey("txintervalid")) { this->TxIntervalIdData = (int)(doc["data"]["txintervalid"]);} else {this->TxIntervalIdData = 3600;}
if (doc["data"].containsKey("enable_openwbtopic")){ this->Conf_EnableOpenWBTopic = (doc["data"]["enable_openwbtopic"]).as();} else { this->Conf_EnableOpenWBTopic = false; }
if (doc["data"].containsKey("enable_setters")) { this->Conf_EnableSetters = (doc["data"]["enable_setters"]).as();} else { this->Conf_EnableSetters = false; }
+ if (doc["data"].containsKey("enableCrcCheck")) { this->enableCrcCheck = (doc["data"]["enableCrcCheck"]).as();} else { this->enableCrcCheck = true; }
if (doc["data"].containsKey("invertertype")) {
bool found = false;
@@ -1265,6 +1329,7 @@ void modbus::GetInitData(AsyncResponseStream *response) {
json["data"]["txintervallive"] = this->TxIntervalLiveData;
json["data"]["txintervalid"] = this->TxIntervalIdData;
json["data"]["enable_openwbtopic"] = ((this->Conf_EnableOpenWBTopic)?1:0);
+ json["data"]["enableCrcCheck"] = ((this->enableCrcCheck)?1:0);
json["data"]["enable_setters"] = ((this->Conf_EnableSetters)?1:0);
JsonArray inverters = json["data"]["inverters"].to();
diff --git a/src/modbus.h b/src/modbus.h
index 27d9f1d..ed649a9 100644
--- a/src/modbus.h
+++ b/src/modbus.h
@@ -37,6 +37,8 @@ class modbus {
#define RS485Transmit HIGH
#define RS485Receive LOW
+ #define DATAISID (byte) 0x01
+ #define DATAISLIVE (byte) 0x02
public:
modbus();
@@ -69,6 +71,7 @@ class modbus {
uint16_t TxIntervalLiveData; // 5
uint16_t TxIntervalIdData; // 3600
regfiles_t InverterType; //Solax-X1
+ bool enableCrcCheck; //check CRC of invertr answer
unsigned long LastTxLiveData = 0;
unsigned long LastTxIdData = 0;
@@ -87,6 +90,7 @@ class modbus {
String PrintDataFrame(std::vector* frame);
String PrintDataFrame(byte* frame, uint8_t len);
uint16_t Calc_CRC(uint8_t* message, uint8_t len);
+ uint16_t Calc_CRC(std::vector* message, uint16_t startpos, uint16_t endpos);
int JsonPosArrayToInt(JsonArray posArray, JsonArray posArray2);
void QueryLiveData();
void QueryIdData();
@@ -109,20 +113,20 @@ class modbus {
std::vector>* Conf_RequestLiveData;
std::vector* Conf_RequestIdData;
uint8_t Conf_ClientIdPos;
- uint8_t Conf_LiveDataStartsAtPos;
- uint8_t Conf_IdDataStartsAtPos;
+ //uint8_t Conf_LiveDataStartsAtPos;
+ //uint8_t Conf_IdDataStartsAtPos;
uint8_t Conf_LiveDataErrorPos;
byte Conf_LiveDataErrorCode;
uint8_t Conf_IdDataErrorPos;
byte Conf_IdDataErrorCode;
- uint8_t Conf_LiveDataSuccessPos;
- byte Conf_LiveDataSuccessCode;
- uint8_t Conf_IdDataSuccessPos;
- byte Conf_IdDataSuccessCode;
- byte Conf_LiveDataFunctionCode;
- byte Conf_IdDataFunctionCode;
- uint8_t Conf_LiveDataFunctionCodePos;
- uint8_t Conf_IdDataFunctionCodePos;
+ //uint8_t Conf_LiveDataSuccessPos;
+ //byte Conf_LiveDataSuccessCode;
+ //uint8_t Conf_IdDataSuccessPos;
+ //byte Conf_IdDataSuccessCode;
+ //byte Conf_LiveDataFunctionCode;
+ //byte Conf_IdDataFunctionCode;
+ //uint8_t Conf_LiveDataFunctionCodePos;
+ //uint8_t Conf_IdDataFunctionCodePos;
bool Conf_EnableOpenWBTopic;
bool Conf_EnableSetters;