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;