From 57b57dcf2507a3f4f795d505e68c783b1bde4909 Mon Sep 17 00:00:00 2001 From: SteWers Date: Wed, 16 Feb 2022 19:33:57 +0100 Subject: [PATCH] [Solax X1] rework 02/2022 final - rework of the request cycle - more reliable and reusable respond processing for more and further requests - periodically request of IDinfo data to get the converters serial number and display it on the WebUI - restructure the usage and names of variables; especially the global ones, to prevent naming conflicts - on demand request of IDinfo data via command `EnergyConfig12 ReadIDinfo` - on demand request of config data via command `EnergyConfig12 ReadConfig` - many other code optimisations --- tasmota/xnrg_12_solaxX1.ino | 444 +++++++++++++++++++++++------------- 1 file changed, 287 insertions(+), 157 deletions(-) diff --git a/tasmota/xnrg_12_solaxX1.ino b/tasmota/xnrg_12_solaxX1.ino index 6f26bf4f6..8e94bcb08 100644 --- a/tasmota/xnrg_12_solaxX1.ino +++ b/tasmota/xnrg_12_solaxX1.ino @@ -36,6 +36,19 @@ #include +const char kSolaxMode[] PROGMEM = + D_OFF "|" D_SOLAX_MODE_0 "|" D_SOLAX_MODE_1 "|" D_SOLAX_MODE_2 "|" D_SOLAX_MODE_3 "|" D_SOLAX_MODE_4 "|" + D_SOLAX_MODE_5 "|" D_SOLAX_MODE_6; + +const char kSolaxError[] PROGMEM = + D_SOLAX_ERROR_0 "|" D_SOLAX_ERROR_1 "|" D_SOLAX_ERROR_2 "|" D_SOLAX_ERROR_3 "|" D_SOLAX_ERROR_4 "|" D_SOLAX_ERROR_5 "|" + D_SOLAX_ERROR_6 "|" D_SOLAX_ERROR_7 "|" D_SOLAX_ERROR_8; + +const char kSolaxSafetyType[] PROGMEM = + "VDE0126|ARN4105|AS4777_AU|G98/1|C10/11|OVE/ONORME8001|EN50438_NL|EN50438_DK|CEB|CEI021|NRS097_2_1|" + "VDE0126_Gr_Is|UTE_C15_712|IEC61727|G99/1|VDE0126_Gr_Co|France_VFR2014|C15_712_is_50|C15_712_is_60|" + "AS4777_NZ|RD1699|Chile|EN50438_Ireland|Philippines|Czech_PPDS|Czech_50438"; + union { uint32_t ErrMessage; struct { @@ -76,16 +89,9 @@ union { uint8_t OtherDeviceFault:1;//30 uint8_t ErrBit31:1;//31 }; -} ErrCode; +} solaxX1_ErrCode; -const char kSolaxMode[] PROGMEM = D_OFF "|" D_SOLAX_MODE_0 "|" D_SOLAX_MODE_1 "|" D_SOLAX_MODE_2 "|" D_SOLAX_MODE_3 "|" - D_SOLAX_MODE_4 "|" D_SOLAX_MODE_5 "|" D_SOLAX_MODE_6; - -const char kSolaxError[] PROGMEM = - D_SOLAX_ERROR_0 "|" D_SOLAX_ERROR_1 "|" D_SOLAX_ERROR_2 "|" D_SOLAX_ERROR_3 "|" D_SOLAX_ERROR_4 "|" D_SOLAX_ERROR_5 "|" - D_SOLAX_ERROR_6 "|" D_SOLAX_ERROR_7 "|" D_SOLAX_ERROR_8; - -struct SOLAXX1 { +struct SOLAXX1_LIVEDATA { int16_t temperature = 0; float energy_today = 0; float dc1_voltage = 0; @@ -97,261 +103,360 @@ struct SOLAXX1 { float dc2_power = 0; int16_t runMode = 0; uint32_t errorCode = 0; + uint8_t SerialNumber[16] = {0x6e, 0x2f, 0x61}; // "n/a" } solaxX1; -uint8_t header[2] = {0xAA, 0x55}; -uint8_t source[2] = {0x00, 0x00}; -uint8_t destination[2] = {0x00, 0x00}; -uint8_t controlCode[1] = {0x00}; -uint8_t functionCode[1] = {0x00}; -uint8_t dataLength[1] = {0x00}; -uint8_t data[16] = {0}; +struct SOLAXX1_GLOBALDATA { + bool AddressAssigned = true; + uint8_t SendRetry_count = 20; + uint8_t QueryData_count = 0; + uint8_t QueryID_count = 240; + bool Command_QueryID = false;; + bool Command_QueryConfig = false; +} solaxX1_global; + +struct SOLAXX1_SENDDATA { + uint8_t Header[2] = {0xAA, 0x55}; + uint8_t Source[2] = {0x00, 0x00}; + uint8_t Destination[2] = {0x00, 0x00}; + uint8_t ControlCode[1] = {0x00}; + uint8_t FunctionCode[1] = {0x00}; + uint8_t DataLength[1] = {0x00}; + uint8_t Payload[16] = {0}; +} solaxX1_SendData; TasmotaSerial *solaxX1Serial; -uint8_t message[30]; -bool AddressAssigned = true; -uint8_t solaxX1_send_retry = 20; -uint8_t solaxX1_queryData_count = 0; -uint8_t solaxX1_QueryID_count = 240; -uint8_t solaxX1SerialNumber[16] = {0x6e, 0x2f, 0x61}; // "n/a" /*********************************************************************************************/ -void solaxX1_RS485Send(uint16_t msgLen) +void solaxX1_RS485Send(void) { - memcpy(message, header, 2); - memcpy(message + 2, source, 2); - memcpy(message + 4, destination, 2); - memcpy(message + 6, controlCode, 1); - memcpy(message + 7, functionCode, 1); - memcpy(message + 8, dataLength, 1); - memcpy(message + 9, data, sizeof(data)); - uint16_t crc = solaxX1_calculateCRC(message, msgLen); // calculate out crc bytes - + uint8_t message[30]; + memcpy(message, solaxX1_SendData.Header, 2); + memcpy(message + 2, solaxX1_SendData.Source, 2); + memcpy(message + 4, solaxX1_SendData.Destination, 2); + memcpy(message + 6, solaxX1_SendData.ControlCode, 1); + memcpy(message + 7, solaxX1_SendData.FunctionCode, 1); + memcpy(message + 8, solaxX1_SendData.DataLength, 1); + memcpy(message + 9, solaxX1_SendData.Payload, sizeof(solaxX1_SendData.Payload)); + uint16_t crc = solaxX1_calculateCRC(message, 9 + solaxX1_SendData.DataLength[0]); // calculate out crc bytes while (solaxX1Serial->available() > 0) { // read serial if any old data is available solaxX1Serial->read(); } - if (PinUsed(GPIO_SOLAXX1_RTS)) { digitalWrite(Pin(GPIO_SOLAXX1_RTS), HIGH); } solaxX1Serial->flush(); - solaxX1Serial->write(message, msgLen); + solaxX1Serial->write(message, 9 + solaxX1_SendData.DataLength[0]); solaxX1Serial->write(highByte(crc)); solaxX1Serial->write(lowByte(crc)); solaxX1Serial->flush(); if (PinUsed(GPIO_SOLAXX1_RTS)) { digitalWrite(Pin(GPIO_SOLAXX1_RTS), LOW); } - - AddLogBuffer(LOG_LEVEL_DEBUG_MORE, message, msgLen); + AddLogBuffer(LOG_LEVEL_DEBUG_MORE, message, 9 + solaxX1_SendData.DataLength[0]); } -bool solaxX1_RS485Receive(uint8_t *value) +bool solaxX1_RS485Receive(uint8_t *ReadBuffer) { uint8_t len = 0; - while (solaxX1Serial->available() > 0) { - value[len++] = (uint8_t)solaxX1Serial->read(); + ReadBuffer[len++] = (uint8_t)solaxX1Serial->read(); } - - AddLogBuffer(LOG_LEVEL_DEBUG_MORE, value, len); - - uint16_t crc = solaxX1_calculateCRC(value, len - 2); // calculate out crc bytes - - return !(value[len - 1] == lowByte(crc) && value[len - 2] == highByte(crc)); + AddLogBuffer(LOG_LEVEL_DEBUG_MORE, ReadBuffer, len); + uint16_t crc = solaxX1_calculateCRC(ReadBuffer, len - 2); // calculate out crc bytes + return !(ReadBuffer[len - 1] == lowByte(crc) && ReadBuffer[len - 2] == highByte(crc)); } uint16_t solaxX1_calculateCRC(uint8_t *bExternTxPackage, uint8_t bLen) { uint8_t i; - uint16_t wChkSum; - wChkSum = 0; - + uint16_t wChkSum = 0; for (i = 0; i < bLen; i++) { wChkSum = wChkSum + bExternTxPackage[i]; } return wChkSum; } +void solaxX1_ExtractText(uint8_t *DataIn, uint8_t *DataOut, uint8_t Begin, uint8_t End) +{ + uint8_t i; + for (i = Begin; i <= End; i++) { + DataOut[i - Begin] = DataIn[i]; + } + DataOut[End - Begin + 1] = 0; +} + void solaxX1_QueryOfflineInverters(void) { - source[0] = 0x01; - destination[0] = 0x00; - destination[1] = 0x00; - controlCode[0] = 0x10; - functionCode[0] = 0x00; - dataLength[0] = 0x00; - solaxX1_RS485Send(9); + solaxX1_SendData.Source[0] = 0x01; + solaxX1_SendData.Destination[0] = 0x00; + solaxX1_SendData.Destination[1] = 0x00; + solaxX1_SendData.ControlCode[0] = 0x10; + solaxX1_SendData.FunctionCode[0] = 0x00; + solaxX1_SendData.DataLength[0] = 0x00; + solaxX1_RS485Send(); } void solaxX1_SendInverterAddress(void) { - source[0] = 0x00; - destination[0] = 0x00; - destination[1] = 0x00; - controlCode[0] = 0x10; - functionCode[0] = 0x01; - dataLength[0] = 0x0F; - data[14] = INVERTER_ADDRESS; // Inverter Address, It must be unique in case of more inverters in the same rs485 net. - solaxX1_RS485Send(24); + solaxX1_SendData.Source[0] = 0x00; + solaxX1_SendData.Destination[0] = 0x00; + solaxX1_SendData.Destination[1] = 0x00; + solaxX1_SendData.ControlCode[0] = 0x10; + solaxX1_SendData.FunctionCode[0] = 0x01; + solaxX1_SendData.DataLength[0] = 0x0F; + solaxX1_SendData.Payload[14] = INVERTER_ADDRESS; // Inverter Address, It must be unique in case of more inverters in the same rs485 net. + solaxX1_RS485Send(); } void solaxX1_QueryLiveData(void) { - source[0] = 0x01; - destination[0] = 0x00; - destination[1] = INVERTER_ADDRESS; - controlCode[0] = 0x11; - functionCode[0] = 0x02; - dataLength[0] = 0x00; - solaxX1_RS485Send(9); + solaxX1_SendData.Source[0] = 0x01; + solaxX1_SendData.Destination[0] = 0x00; + solaxX1_SendData.Destination[1] = INVERTER_ADDRESS; + solaxX1_SendData.ControlCode[0] = 0x11; + solaxX1_SendData.FunctionCode[0] = 0x02; + solaxX1_SendData.DataLength[0] = 0x00; + solaxX1_RS485Send(); } void solaxX1_QueryIDData(void) { - source[0] = 0x01; - destination[0] = 0x00; - destination[1] = INVERTER_ADDRESS; - controlCode[0] = 0x11; - functionCode[0] = 0x03; - dataLength[0] = 0x00; - solaxX1_RS485Send(9); + solaxX1_SendData.Source[0] = 0x01; + solaxX1_SendData.Destination[0] = 0x00; + solaxX1_SendData.Destination[1] = INVERTER_ADDRESS; + solaxX1_SendData.ControlCode[0] = 0x11; + solaxX1_SendData.FunctionCode[0] = 0x03; + solaxX1_SendData.DataLength[0] = 0x00; + solaxX1_RS485Send(); +} + +void solaxX1_QueryConfigData(void) +{ + solaxX1_SendData.Source[0] = 0x01; + solaxX1_SendData.Destination[0] = 0x00; + solaxX1_SendData.Destination[1] = INVERTER_ADDRESS; + solaxX1_SendData.ControlCode[0] = 0x11; + solaxX1_SendData.FunctionCode[0] = 0x04; + solaxX1_SendData.DataLength[0] = 0x00; + solaxX1_RS485Send(); } uint8_t solaxX1_ParseErrorCode(uint32_t code){ - ErrCode.ErrMessage = code; - + solaxX1_ErrCode.ErrMessage = code; if (code == 0) return 0; - if (ErrCode.MainsLostFault) return 1; - if (ErrCode.GridVoltFault) return 2; - if (ErrCode.GridFreqFault) return 3; - if (ErrCode.PvVoltFault) return 4; - if (ErrCode.IsolationFault) return 5; - if (ErrCode.TemperatureOverFault) return 6; - if (ErrCode.FanFault) return 7; - if (ErrCode.OtherDeviceFault) return 8; + if (solaxX1_ErrCode.MainsLostFault) return 1; + if (solaxX1_ErrCode.GridVoltFault) return 2; + if (solaxX1_ErrCode.GridFreqFault) return 3; + if (solaxX1_ErrCode.PvVoltFault) return 4; + if (solaxX1_ErrCode.IsolationFault) return 5; + if (solaxX1_ErrCode.TemperatureOverFault) return 6; + if (solaxX1_ErrCode.FanFault) return 7; + if (solaxX1_ErrCode.OtherDeviceFault) return 8; return 0; } /*********************************************************************************************/ -void solaxX1250MSecond(void) // Every 250 milliseconds +void solaxX1_250MSecond(void) // Every 250 milliseconds { - uint8_t value[70] = {0}; + uint8_t DataRead[80] = {0}; + uint8_t TempData[16] = {0}; + char TempDataChar[16]; uint8_t i; if (solaxX1Serial->available()) { - if (solaxX1_RS485Receive(value)) { // CRC-error -> no further action + if (solaxX1_RS485Receive(DataRead)) { // CRC-error -> no further action DEBUG_SENSOR_LOG(PSTR("SX1: Data response CRC error")); return; } - solaxX1_send_retry = 20; // Inverter is responding + solaxX1_global.SendRetry_count = 20; // Inverter is responding - if (value[0] != 0xAA || value[1] != 0x55) { // Check for header + if (DataRead[0] != 0xAA || DataRead[1] != 0x55) { // Check for header DEBUG_SENSOR_LOG(PSTR("SX1: Check for header failed")); return; } - if (value[6] == 0x11 && value[7] == 0x82) { // received "Response for query (live data)" + if (DataRead[6] == 0x11 && DataRead[7] == 0x82) { // received "Response for query (live data)" Energy.data_valid[0] = 0; - - solaxX1.temperature = (value[9] << 8) | value[10]; // Temperature - solaxX1.energy_today = (float)((value[11] << 8) | value[12]) * 0.1f; // Energy Today - solaxX1.dc1_voltage = (float)((value[13] << 8) | value[14]) * 0.1f; // PV1 Voltage - solaxX1.dc2_voltage = (float)((value[15] << 8) | value[16]) * 0.1f; // PV2 Voltage - solaxX1.dc1_current = (float)((value[17] << 8) | value[18]) * 0.1f; // PV1 Current - solaxX1.dc2_current = (float)((value[19] << 8) | value[20]) * 0.1f; // PV2 Current - Energy.current[0] = (float)((value[21] << 8) | value[22]) * 0.1f; // AC Current - Energy.voltage[0] = (float)((value[23] << 8) | value[24]) * 0.1f; // AC Voltage - Energy.frequency[0] = (float)((value[25] << 8) | value[26]) * 0.01f; // AC Frequency - Energy.active_power[0] = (float)((value[27] << 8) | value[28]); // AC Power - //temporal = (float)((value[29] << 8) | value[30]) * 0.1f; // Not Used - Energy.import_active[0] = (float)((value[31] << 24) | (value[32] << 16) | (value[33] << 8) | value[34]) * 0.1f; // Energy Total - solaxX1.runtime_total = ((value[35] << 24) | (value[36] << 16) | (value[37] << 8) | value[38]); // Work Time Total - solaxX1.runMode = (value[39] << 8) | value[40]; // Work mode - //temporal = (float)((value[41] << 8) | value[42]); // Grid voltage fault value 0.1V - //temporal = (float)((value[43] << 8) | value[44]); // Gird frequency fault value 0.01Hz - //temporal = (float)((value[45] << 8) | value[46]); // Dc injection fault value 1mA - //temporal = (float)((value[47] << 8) | value[48]); // Temperature fault value - //temporal = (float)((value[49] << 8) | value[50]); // Pv1 voltage fault value 0.1V - //temporal = (float)((value[51] << 8) | value[52]); // Pv2 voltage fault value 0.1V - //temporal = (float)((value[53] << 8) | value[54]); // GFC fault value - solaxX1.errorCode = (value[58] << 24) | (value[57] << 16) | (value[56] << 8) | value[55]; // Error Code - + solaxX1.temperature = (DataRead[9] << 8) | DataRead[10]; // Temperature + solaxX1.energy_today = (float)((DataRead[11] << 8) | DataRead[12]) * 0.1f; // Energy Today + solaxX1.dc1_voltage = (float)((DataRead[13] << 8) | DataRead[14]) * 0.1f; // PV1 Voltage + solaxX1.dc2_voltage = (float)((DataRead[15] << 8) | DataRead[16]) * 0.1f; // PV2 Voltage + solaxX1.dc1_current = (float)((DataRead[17] << 8) | DataRead[18]) * 0.1f; // PV1 Current + solaxX1.dc2_current = (float)((DataRead[19] << 8) | DataRead[20]) * 0.1f; // PV2 Current + Energy.current[0] = (float)((DataRead[21] << 8) | DataRead[22]) * 0.1f; // AC Current + Energy.voltage[0] = (float)((DataRead[23] << 8) | DataRead[24]) * 0.1f; // AC Voltage + Energy.frequency[0] = (float)((DataRead[25] << 8) | DataRead[26]) * 0.01f; // AC Frequency + Energy.active_power[0] = (float)((DataRead[27] << 8) | DataRead[28]); // AC Power + //temporal = (float)((DataRead[29] << 8) | DataRead[30]) * 0.1f; // Not Used + Energy.import_active[0] = (float)((DataRead[31] << 24) | (DataRead[32] << 16) | (DataRead[33] << 8) | DataRead[34]) * 0.1f; // Energy Total + solaxX1.runtime_total = ((DataRead[35] << 24) | (DataRead[36] << 16) | (DataRead[37] << 8) | DataRead[38]); // Work Time Total + solaxX1.runMode = (DataRead[39] << 8) | DataRead[40]; // Work mode + //temporal = (float)((DataRead[41] << 8) | DataRead[42]); // Grid voltage fault value 0.1V + //temporal = (float)((DataRead[43] << 8) | DataRead[44]); // Gird frequency fault value 0.01Hz + //temporal = (float)((DataRead[45] << 8) | DataRead[46]); // Dc injection fault value 1mA + //temporal = (float)((DataRead[47] << 8) | DataRead[48]); // Temperature fault value + //temporal = (float)((DataRead[49] << 8) | DataRead[50]); // Pv1 voltage fault value 0.1V + //temporal = (float)((DataRead[51] << 8) | DataRead[52]); // Pv2 voltage fault value 0.1V + //temporal = (float)((DataRead[53] << 8) | DataRead[54]); // GFC fault value + solaxX1.errorCode = (DataRead[58] << 24) | (DataRead[57] << 16) | (DataRead[56] << 8) | DataRead[55]; // Error Code solaxX1.dc1_power = solaxX1.dc1_voltage * solaxX1.dc1_current; solaxX1.dc2_power = solaxX1.dc2_voltage * solaxX1.dc2_current; - EnergyUpdateTotal(); // 484.708 kWh DEBUG_SENSOR_LOG(PSTR("SX1: received live data")); return; } // end received "Response for query (live data)" - if (value[6] == 0x11 && value[7] == 0x83) { // received "Response for query (ID data)" - for (i = 49; i <= 62; i++) { // get "real" serial number - solaxX1SerialNumber[i - 49] = value[i]; + if (DataRead[6] == 0x11 && DataRead[7] == 0x83) { // received "Response for query (ID data)" + solaxX1_ExtractText(DataRead, solaxX1.SerialNumber, 49, 62); // extract "real" serial number + if (solaxX1_global.Command_QueryID) { + AddLog(LOG_LEVEL_INFO, PSTR("SX1: Inverter phases: %d"),DataRead[9]); // number of phases + solaxX1_ExtractText(DataRead, TempData, 10, 15); // extract rated bus power (my be empty) + AddLog(LOG_LEVEL_INFO, PSTR("SX1: Inverter rated bus power: %s"),(char*)TempData); + solaxX1_ExtractText(DataRead, TempData, 16, 20); // extract firmware version + AddLog(LOG_LEVEL_INFO, PSTR("SX1: Inverter firmware version: %s"),(char*)TempData); + solaxX1_ExtractText(DataRead, TempData, 21, 34); // extract module name (my be empty) + AddLog(LOG_LEVEL_INFO, PSTR("SX1: Inverter module name: %s"),(char*)TempData); + solaxX1_ExtractText(DataRead, TempData, 35, 48); // extract factory name + AddLog(LOG_LEVEL_INFO, PSTR("SX1: Inverter factory name: %s"),(char*)TempData); + AddLog(LOG_LEVEL_INFO, PSTR("SX1: Inverter serial number: %s"),(char*)solaxX1.SerialNumber); + solaxX1_ExtractText(DataRead, TempData, 63, 66); // extract rated bus voltage + AddLog(LOG_LEVEL_INFO, PSTR("SX1: Inverter rated bus voltage: %s"),(char*)TempData); + solaxX1_global.Command_QueryID = false; + } else { + AddLog(LOG_LEVEL_DEBUG, PSTR("SX1: Inverter serial number: %s"),(char*)solaxX1.SerialNumber); } - AddLog(LOG_LEVEL_INFO, PSTR("SX1: Inverter serial number: %s"),(char*)solaxX1SerialNumber); DEBUG_SENSOR_LOG(PSTR("SX1: received ID data")); return; - } // end received "Response for query (ID data)" + } // end received "Response for query (ID data)" - if (value[6] == 0x10 && value[7] == 0x80) { // received "register request" - solaxX1_queryData_count = 5; // give time for next query - for (i = 9; i <= 22; i++) { // store serial number for register - data[i - 9] = value[i]; + if (DataRead[6] == 0x11 && DataRead[7] == 0x84) { // received "Response for query (config data)" + if (solaxX1_global.Command_QueryConfig) { + // This values are displayed as they were received from the inverter. They are not interpreted in any way. + dtostrfd(((DataRead[9] << 8) | DataRead[10]) * 0.1f, 1, TempDataChar); + AddLog(LOG_LEVEL_INFO, PSTR("SX1: wVpvStart: %s V (Inverter launch voltage threshold)"), TempDataChar); + AddLog(LOG_LEVEL_INFO, PSTR("SX1: wTimeStart: %d sec (launch wait time)"), (DataRead[11] << 8) | DataRead[12]); + dtostrfd(((DataRead[13] << 8) | DataRead[14]) * 0.1f, 1, TempDataChar); + AddLog(LOG_LEVEL_INFO, PSTR("SX1: wVacMinProtect: %s V (allowed minimum grid voltage)"), TempDataChar); + dtostrfd(((DataRead[15] << 8) | DataRead[16]) * 0.1f, 1, TempDataChar); + AddLog(LOG_LEVEL_INFO, PSTR("SX1: wVacMaxProtect: %s V (allowed maximum grid voltage)"), TempDataChar); + dtostrfd(((DataRead[17] << 8) | DataRead[18]) * 0.01f, 2, TempDataChar); + AddLog(LOG_LEVEL_INFO, PSTR("SX1: wFacMinProtect: %s Hz (allowed minimum grid frequency)"), TempDataChar); + dtostrfd(((DataRead[19] << 8) | DataRead[20]) * 0.01f, 2, TempDataChar); + AddLog(LOG_LEVEL_INFO, PSTR("SX1: wFacMaxProtect: %s Hz (allowed maximum grid frequency)"), TempDataChar); + AddLog(LOG_LEVEL_INFO, PSTR("SX1: wDciLimits: %d mA (DC component limits)"), (DataRead[21] << 8) | DataRead[22]); + dtostrfd(((DataRead[23] << 8) | DataRead[24]) * 0.1f, 1, TempDataChar); + AddLog(LOG_LEVEL_INFO, PSTR("SX1: wGrid10MinAvgProtect: %s V (10 minutes over voltage protect)"), TempDataChar); + dtostrfd(((DataRead[25] << 8) | DataRead[26]) * 0.1f, 1, TempDataChar); + AddLog(LOG_LEVEL_INFO, PSTR("SX1: wVacMinSlowProtect: %s V (grid undervoltage protect value)"), TempDataChar); + dtostrfd(((DataRead[27] << 8) | DataRead[28]) * 0.1f, 1, TempDataChar); + AddLog(LOG_LEVEL_INFO, PSTR("SX1: wVacMaxSlowProtect: %s V (grid overvoltage protect value)"), TempDataChar); + dtostrfd(((DataRead[29] << 8) | DataRead[30]) * 0.01f, 2, TempDataChar); + AddLog(LOG_LEVEL_INFO, PSTR("SX1: wFacMinSlowProtect: %s Hz (grid underfrequency protect value)"), TempDataChar); + dtostrfd(((DataRead[31] << 8) | DataRead[32]) * 0.01f, 2, TempDataChar); + AddLog(LOG_LEVEL_INFO, PSTR("SX1: wFacMaxSlowProtect: %s Hz (grid overfrequency protect value)"), TempDataChar); + GetTextIndexed(TempDataChar, sizeof(TempDataChar), (DataRead[33] << 8) | DataRead[34], kSolaxSafetyType); + AddLog(LOG_LEVEL_INFO, PSTR("SX1: wSafety: %d ≙ %s"), (DataRead[33] << 8) | DataRead[34], TempDataChar); + AddLog(LOG_LEVEL_INFO, PSTR("SX1: wPowerfactor_mode: %d"), DataRead[35]); + dtostrfd(DataRead[36] * 0.01f, 2, TempDataChar); + AddLog(LOG_LEVEL_INFO, PSTR("SX1: wPowerfactor_data: %s"), TempDataChar); + dtostrfd(DataRead[37] * 0.01f, 2, TempDataChar); + AddLog(LOG_LEVEL_INFO, PSTR("SX1: wUpperLimit: %s (overexcite limits)"), TempDataChar); + dtostrfd(DataRead[38] * 0.01f, 2, TempDataChar); + AddLog(LOG_LEVEL_INFO, PSTR("SX1: wLowerLimit: %s (underexcite limits)"), TempDataChar); + dtostrfd(DataRead[39] * 0.01f, 2, TempDataChar); + AddLog(LOG_LEVEL_INFO, PSTR("SX1: wPowerLow: %s (power ratio change upper limits)"), TempDataChar); + dtostrfd(DataRead[40] * 0.01f, 2, TempDataChar); + AddLog(LOG_LEVEL_INFO, PSTR("SX1: wPowerUp: %s (power ratio change lower limits)"), TempDataChar); + AddLog(LOG_LEVEL_INFO, PSTR("SX1: Qpower_set: %d"), (DataRead[41] << 8) | DataRead[42]); + dtostrfd(((DataRead[43] << 8) | DataRead[44]) * 0.01f, 2, TempDataChar); + AddLog(LOG_LEVEL_INFO, PSTR("SX1: WFreqSetPoint: %s Hz (Over Frequency drop output setpoint)"), TempDataChar); + AddLog(LOG_LEVEL_INFO, PSTR("SX1: WFreqDroopRate: %d %% (drop output slope)"), (DataRead[45] << 8) | DataRead[46]); + AddLog(LOG_LEVEL_INFO, PSTR("SX1: QuVupRate: %d %% (Q(U) curve up set point)"), (DataRead[47] << 8) | DataRead[48]); + AddLog(LOG_LEVEL_INFO, PSTR("SX1: QuVlowRate: %d %% (Q(U) curve low set point)"), (DataRead[49] << 8) | DataRead[50]); + AddLog(LOG_LEVEL_INFO, PSTR("SX1: WPowerLimitsPercent: %d %%"), (DataRead[51] << 8) | DataRead[52]); + dtostrfd(((DataRead[53] << 8) | DataRead[54]) * 0.01f, 2, TempDataChar); + AddLog(LOG_LEVEL_INFO, PSTR("SX1: WWgra: %s %%"), TempDataChar); + dtostrfd(((DataRead[55] << 8) | DataRead[56]) * 0.1f, 1, TempDataChar); + AddLog(LOG_LEVEL_INFO, PSTR("SX1: wWv2: %s V"), TempDataChar); + dtostrfd(((DataRead[57] << 8) | DataRead[58]) * 0.1f, 1, TempDataChar); + AddLog(LOG_LEVEL_INFO, PSTR("SX1: wWv3: %s V"), TempDataChar); + dtostrfd(((DataRead[59] << 8) | DataRead[60]) * 0.1f, 1, TempDataChar); + AddLog(LOG_LEVEL_INFO, PSTR("SX1: wWv4: %s V"), TempDataChar); + AddLog(LOG_LEVEL_INFO, PSTR("SX1: wQurangeV1: %d %%"), (DataRead[61] << 8) | DataRead[62]); + AddLog(LOG_LEVEL_INFO, PSTR("SX1: wQurangeV4: %d %%"), (DataRead[63] << 8) | DataRead[64]); + AddLog(LOG_LEVEL_INFO, PSTR("SX1: BVoltPowerLimit: %d"), (DataRead[65] << 8) | DataRead[66]); + AddLog(LOG_LEVEL_INFO, PSTR("SX1: WPowerManagerEnable: %d"), (DataRead[67] << 8) | DataRead[68]); + AddLog(LOG_LEVEL_INFO, PSTR("SX1: WGlobalSeachMPPTStrartFlg: %d"), (DataRead[69] << 8) | DataRead[70]); + AddLog(LOG_LEVEL_INFO, PSTR("SX1: WFrqProtectRestrictive: %d"), (DataRead[71] << 8) | DataRead[72]); + AddLog(LOG_LEVEL_INFO, PSTR("SX1: WQuDelayTimer: %d sec"), (DataRead[73] << 8) | DataRead[74]); + AddLog(LOG_LEVEL_INFO, PSTR("SX1: WFreqActivePowerDelayTimer: %d ms"), (DataRead[75] << 8) | DataRead[76]); + + solaxX1_global.Command_QueryConfig = false; } + DEBUG_SENSOR_LOG(PSTR("SX1: received config data")); + return; + } // end received "Response for query (config data)" + + if (DataRead[6] == 0x10 && DataRead[7] == 0x80) { // received "register request" + solaxX1_global.QueryData_count = 5; // give time for next query + solaxX1_ExtractText(DataRead, solaxX1_SendData.Payload, 9, 22); // store serial number for register DEBUG_SENSOR_LOG(PSTR("SX1: received register request and send register address")); solaxX1_SendInverterAddress(); // "send register address" return; } - if (value[6] == 0x10 && value[7] == 0x81 && value[9] == 0x06) { // received "address confirm (ACK)" - solaxX1_queryData_count = 5; // give time for next query - AddressAssigned = true; + if (DataRead[6] == 0x10 && DataRead[7] == 0x81 && DataRead[9] == 0x06) { // received "address confirm (ACK)" + solaxX1_global.QueryData_count = 5; // give time for next query + solaxX1_global.AddressAssigned = true; DEBUG_SENSOR_LOG(PSTR("SX1: received \"address confirm (ACK)\"")); return; } } // end solaxX1Serial->available() -// DEBUG_SENSOR_LOG(PSTR("SX1: AddressAssigned: %d, solaxX1_queryData_count: %d, solaxX1_send_retry: %d, solaxX1_QueryID_count: %d"), AddressAssigned, solaxX1_queryData_count, solaxX1_send_retry, solaxX1_QueryID_count); - if (AddressAssigned) { - if (!solaxX1_queryData_count) { // normal periodically query - solaxX1_queryData_count = 5; - if (solaxX1_QueryID_count) { // normal live query - DEBUG_SENSOR_LOG(PSTR("SX1: Send periodically live query")); - solaxX1_QueryLiveData(); - } else { // normal ID query - DEBUG_SENSOR_LOG(PSTR("SX1: Send periodically ID query")); +// DEBUG_SENSOR_LOG(PSTR("SX1: solaxX1_global.AddressAssigned: %d, solaxX1_global.QueryData_count: %d, solaxX1_global.SendRetry_count: %d, solaxX1_global.QueryID_count: %d"), solaxX1_global.AddressAssigned, solaxX1_global.QueryData_count, solaxX1_global.SendRetry_count, solaxX1_global.QueryID_count); + if (solaxX1_global.AddressAssigned) { + if (!solaxX1_global.QueryData_count) { // normal periodically query + solaxX1_global.QueryData_count = 5; + if (!solaxX1_global.QueryID_count || solaxX1_global.Command_QueryID) { // ID query + DEBUG_SENSOR_LOG(PSTR("SX1: Send ID query")); solaxX1_QueryIDData(); + } else if (solaxX1_global.Command_QueryConfig) { // Config query + DEBUG_SENSOR_LOG(PSTR("SX1: Send config query")); + solaxX1_QueryConfigData(); + } else { // live query + DEBUG_SENSOR_LOG(PSTR("SX1: Send live query")); + solaxX1_QueryLiveData(); } - solaxX1_QueryID_count++; // query ID every 256th time + solaxX1_global.QueryID_count++; // query ID every 256th time } // end normal periodically query - solaxX1_queryData_count--; - if (!solaxX1_send_retry) { // Inverter went "off" - solaxX1_send_retry = 20; + solaxX1_global.QueryData_count--; + if (!solaxX1_global.SendRetry_count) { // Inverter went "off" + solaxX1_global.SendRetry_count = 20; DEBUG_SENSOR_LOG(PSTR("SX1: Inverter went \"off\"")); Energy.data_valid[0] = ENERGY_WATCHDOG; solaxX1.temperature = solaxX1.dc1_voltage = solaxX1.dc2_voltage = solaxX1.dc1_current = solaxX1.dc2_current = solaxX1.dc1_power = 0; solaxX1.dc2_power = Energy.current[0] = Energy.voltage[0] = Energy.frequency[0] = Energy.active_power[0] = 0; solaxX1.runMode = -1; // off(line) - AddressAssigned = false; + solaxX1_global.AddressAssigned = false; } // end Inverter went "off" } else { // sent query for inverters in offline status - if (!solaxX1_send_retry) { - solaxX1_send_retry = 20; + if (!solaxX1_global.SendRetry_count) { + solaxX1_global.SendRetry_count = 20; DEBUG_SENSOR_LOG(PSTR("SX1: Sent query for inverters in offline state")); solaxX1_QueryOfflineInverters(); } } - solaxX1_send_retry--; + solaxX1_global.SendRetry_count--; return; -} +} // end solaxX1_250MSecond -void solaxX1SnsInit(void) +void solaxX1_SnsInit(void) { AddLog(LOG_LEVEL_INFO, PSTR("SX1: Init - RX-pin: %d, TX-pin: %d, RTS-pin: %d"), Pin(GPIO_SOLAXX1_RX), Pin(GPIO_SOLAXX1_TX), Pin(GPIO_SOLAXX1_RTS)); solaxX1Serial = new TasmotaSerial(Pin(GPIO_SOLAXX1_RX), Pin(GPIO_SOLAXX1_TX), 1); @@ -365,13 +470,33 @@ void solaxX1SnsInit(void) } } -void solaxX1DrvInit(void) +void solaxX1_DrvInit(void) { if (PinUsed(GPIO_SOLAXX1_RX) && PinUsed(GPIO_SOLAXX1_TX)) { TasmotaGlobal.energy_driver = XNRG_12; } } +bool SolaxX1_cmd(void) +{ + if (!solaxX1_global.AddressAssigned) { + AddLog(LOG_LEVEL_INFO, PSTR("SX1: No inverter registered")); + return false; + } + + if (!strcasecmp(XdrvMailbox.data, "ReadIDinfo")) { + solaxX1_global.Command_QueryID = true; + AddLog(LOG_LEVEL_INFO, PSTR("SX1: ReadIDinfo sent...")); + return true; + } else if (!strcasecmp(XdrvMailbox.data, "ReadConfig")) { + solaxX1_global.Command_QueryConfig = true; + AddLog(LOG_LEVEL_INFO, PSTR("SX1: ReadConfig sent...")); + return true; + } + AddLog(LOG_LEVEL_INFO, PSTR("SX1: Unknown command: \"%s\""),XdrvMailbox.data); + return false; +} + #ifdef USE_WEBSERVER const char HTTP_SNS_solaxX1_DATA1[] PROGMEM = "{s}" D_SOLAX_X1 " " D_SOLAR_POWER "{m}%s " D_UNIT_WATT "{e}" @@ -391,7 +516,7 @@ const char HTTP_SNS_solaxX1_DATA3[] PROGMEM = "{s}" D_SOLAX_X1 " Inverter SN{m}%s"; #endif // USE_WEBSERVER -void solaxX1Show(bool json) +void solaxX1_Show(bool json) { char solar_power[33]; dtostrfd(solaxX1.dc1_power + solaxX1.dc2_power, Settings->flag2.wattage_resolution, solar_power); @@ -439,7 +564,7 @@ void solaxX1Show(bool json) char errorCodeString[33]; WSContentSend_PD(HTTP_SNS_solaxX1_DATA3, runtime, status, GetTextIndexed(errorCodeString, sizeof(errorCodeString), solaxX1_ParseErrorCode(solaxX1.errorCode), kSolaxError), - solaxX1SerialNumber); + solaxX1.SerialNumber); #endif // USE_WEBSERVER } } @@ -454,21 +579,26 @@ bool Xnrg12(uint8_t function) switch (function) { case FUNC_EVERY_250_MSECOND: - solaxX1250MSecond(); + solaxX1_250MSecond(); break; case FUNC_JSON_APPEND: - solaxX1Show(1); + solaxX1_Show(1); break; #ifdef USE_WEBSERVER case FUNC_WEB_SENSOR: - solaxX1Show(0); + solaxX1_Show(0); break; #endif // USE_WEBSERVER case FUNC_INIT: - solaxX1SnsInit(); + solaxX1_SnsInit(); break; case FUNC_PRE_INIT: - solaxX1DrvInit(); + solaxX1_DrvInit(); + break; + case FUNC_COMMAND: + if (XNRG_12 == XdrvMailbox.index) { // "EnergyConfig12 " + result = SolaxX1_cmd(); + } break; } return result;