From 3679ec4c088a05fe4d21043d0164e807942b0453 Mon Sep 17 00:00:00 2001 From: SteWers Date: Tue, 8 Feb 2022 20:55:57 +0100 Subject: [PATCH 1/3] [Solax X1] Rework request and respond processing Complete rework of the request cycle and the respond processing. This is more reliable and reusable for more and further requests. Right now the serial number of the converter is requested and displayed in the WebUI. --- tasmota/xnrg_12_solaxX1.ino | 316 ++++++++++++++++-------------------- 1 file changed, 139 insertions(+), 177 deletions(-) diff --git a/tasmota/xnrg_12_solaxX1.ino b/tasmota/xnrg_12_solaxX1.ino index fa925e519..6f26bf4f6 100644 --- a/tasmota/xnrg_12_solaxX1.ino +++ b/tasmota/xnrg_12_solaxX1.ino @@ -1,7 +1,8 @@ /* xnrg_12_solaxX1.ino - Solax X1 inverter RS485 support for Tasmota - Copyright (C) 2021 Pablo Zerón + Copyright (C) 2021 by Pablo Zerón + Copyright (C) 2022 by Stefan Wershoven This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -35,12 +36,6 @@ #include -enum solaxX1_Error -{ - solaxX1_ERR_NO_ERROR, - solaxX1_ERR_CRC_ERROR -}; - union { uint32_t ErrMessage; struct { @@ -90,10 +85,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; -/*********************************************************************************************/ - -TasmotaSerial *solaxX1Serial; - struct SOLAXX1 { int16_t temperature = 0; float energy_today = 0; @@ -104,25 +95,10 @@ struct SOLAXX1 { uint32_t runtime_total = 0; float dc1_power = 0; float dc2_power = 0; - int16_t runMode = 0; uint32_t errorCode = 0; } solaxX1; -union { - uint8_t status; - struct { - uint8_t freeBit7:1; // Bit7 - uint8_t freeBit6:1; // Bit6 - uint8_t freeBit5:1; // Bit5 - uint8_t queryOffline:1; // Bit4 - uint8_t queryOfflineSend:1; // Bit3 - uint8_t hasAddress:1; // Bit2 - uint8_t inverterAddressSend:1; // Bit1 - uint8_t inverterSnReceived:1; // Bit0 - }; -} protocolStatus; - uint8_t header[2] = {0xAA, 0x55}; uint8_t source[2] = {0x00, 0x00}; uint8_t destination[2] = {0x00, 0x00}; @@ -131,15 +107,16 @@ uint8_t functionCode[1] = {0x00}; uint8_t dataLength[1] = {0x00}; uint8_t data[16] = {0}; +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" /*********************************************************************************************/ -bool solaxX1_RS485ReceiveReady(void) -{ - return (solaxX1Serial->available() > 1); -} - void solaxX1_RS485Send(uint16_t msgLen) { memcpy(message, header, 2); @@ -151,8 +128,7 @@ void solaxX1_RS485Send(uint16_t msgLen) memcpy(message + 9, data, sizeof(data)); uint16_t crc = solaxX1_calculateCRC(message, msgLen); // calculate out crc bytes - while (solaxX1Serial->available() > 0) - { // read serial if any old data is available + while (solaxX1Serial->available() > 0) { // read serial if any old data is available solaxX1Serial->read(); } @@ -171,12 +147,11 @@ void solaxX1_RS485Send(uint16_t msgLen) AddLogBuffer(LOG_LEVEL_DEBUG_MORE, message, msgLen); } -uint8_t solaxX1_RS485Receive(uint8_t *value) +bool solaxX1_RS485Receive(uint8_t *value) { uint8_t len = 0; - while (solaxX1Serial->available() > 0) - { + while (solaxX1Serial->available() > 0) { value[len++] = (uint8_t)solaxX1Serial->read(); } @@ -184,14 +159,7 @@ uint8_t solaxX1_RS485Receive(uint8_t *value) uint16_t crc = solaxX1_calculateCRC(value, len - 2); // calculate out crc bytes - if (value[len - 1] == lowByte(crc) && value[len - 2] == highByte(crc)) - { // check calc crc with received crc - return solaxX1_ERR_NO_ERROR; - } - else - { - return solaxX1_ERR_CRC_ERROR; - } + return !(value[len - 1] == lowByte(crc) && value[len - 2] == highByte(crc)); } uint16_t solaxX1_calculateCRC(uint8_t *bExternTxPackage, uint8_t bLen) @@ -200,13 +168,23 @@ uint16_t solaxX1_calculateCRC(uint8_t *bExternTxPackage, uint8_t bLen) uint16_t wChkSum; wChkSum = 0; - for (i = 0; i < bLen; i++) - { + for (i = 0; i < bLen; i++) { wChkSum = wChkSum + bExternTxPackage[i]; } return wChkSum; } +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); +} + void solaxX1_SendInverterAddress(void) { source[0] = 0x00; @@ -230,6 +208,17 @@ void solaxX1_QueryLiveData(void) solaxX1_RS485Send(9); } +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); +} + uint8_t solaxX1_ParseErrorCode(uint32_t code){ ErrCode.ErrMessage = code; @@ -247,152 +236,124 @@ uint8_t solaxX1_ParseErrorCode(uint32_t code){ /*********************************************************************************************/ -uint8_t solaxX1_send_retry = 20; -uint8_t solaxX1_queryData_count = 0; - void solaxX1250MSecond(void) // Every 250 milliseconds { - uint8_t value[61] = {0}; - bool data_ready = solaxX1_RS485ReceiveReady(); + uint8_t value[70] = {0}; + uint8_t i; - if (data_ready) - { - if (protocolStatus.hasAddress) - { - uint8_t error = solaxX1_RS485Receive(value); - if (error) - { - DEBUG_SENSOR_LOG(PSTR("SX1: Data response CRC error")); - } - else - { - solaxX1_send_retry = 20; - 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.dc1_power = solaxX1.dc1_voltage * solaxX1.dc1_current; - solaxX1.dc2_power = solaxX1.dc2_voltage * solaxX1.dc2_current; - - EnergyUpdateTotal(); // 484.708 kWh - } - } else { // end hasAddress - // check address confirmation from inverter - if (protocolStatus.inverterAddressSend) - { - uint8_t error = solaxX1_RS485Receive(value); - if (error) - { - DEBUG_SENSOR_LOG(PSTR("SX1: Address confirmation response CRC error")); - } - else - { - if (value[6] == 0x10 && value[7] == 0x81 && value[9] == 0x06) - { - DEBUG_SENSOR_LOG(PSTR("SX1: Set hasAddress")); - protocolStatus.status = 0b00100000; // hasAddress - } - } - } - - // Check inverter serial number and send the set address request - if (protocolStatus.queryOfflineSend) - { - uint8_t error = solaxX1_RS485Receive(value); - if (error) - { - DEBUG_SENSOR_LOG(PSTR("SX1: Query Offline response CRC error")); - } - else - { - // Serial number from query response - if (value[6] == 0x10 && value[7] == 0x80 && protocolStatus.inverterSnReceived == false) - { - for (uint8_t i = 9; i <= 22; i++) - { - data[i - 9] = value[i]; - } - solaxX1_SendInverterAddress(); - protocolStatus.status = 0b1100000; // inverterSnReceived and inverterAddressSend - DEBUG_SENSOR_LOG(PSTR("SX1: Set inverterSnReceived and inverterAddressSend")); - } - } - } + if (solaxX1Serial->available()) { + if (solaxX1_RS485Receive(value)) { // CRC-error -> no further action + DEBUG_SENSOR_LOG(PSTR("SX1: Data response CRC error")); + return; } - } + + solaxX1_send_retry = 20; // Inverter is responding - if (protocolStatus.hasAddress) { - if (solaxX1_queryData_count <= 0) { + if (value[0] != 0xAA || value[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)" + 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.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]; + } + 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)" + + 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]; + } + 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; + 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; - DEBUG_SENSOR_LOG(PSTR("SX1: Send Retry count: %d"), solaxX1_send_retry); - solaxX1_QueryLiveData(); - } + 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")); + solaxX1_QueryIDData(); + } + solaxX1_QueryID_count++; // query ID every 256th time + } // end normal periodically query solaxX1_queryData_count--; - } - - // request to the inverter the serial number if offline - if (protocolStatus.queryOffline) - { - // We sent the message to query inverters in offline status - source[0] = 0x01; - destination[1] = 0x00; - controlCode[0] = 0x10; - functionCode[0] = 0x00; - dataLength[0] = 0x00; - solaxX1_RS485Send(9); - protocolStatus.status = 0b00010000; // queryOfflineSend - solaxX1_send_retry = 20; - DEBUG_SENSOR_LOG(PSTR("SX1: Query Offline Send")); - } - - if (solaxX1_send_retry == 0) { - if (protocolStatus.hasAddress) { - protocolStatus.status = 0b00001000; // queryOffline + if (!solaxX1_send_retry) { // Inverter went "off" + solaxX1_send_retry = 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) - } else { - if (protocolStatus.queryOfflineSend) { - protocolStatus.status = 0b00001000; // queryOffline - DEBUG_SENSOR_LOG(PSTR("SX1: Set Query Offline")); - } + AddressAssigned = false; + } // end Inverter went "off" + } else { // sent query for inverters in offline status + if (!solaxX1_send_retry) { solaxX1_send_retry = 20; + DEBUG_SENSOR_LOG(PSTR("SX1: Sent query for inverters in offline state")); + solaxX1_QueryOfflineInverters(); } } + solaxX1_send_retry--; - if (!data_ready && solaxX1_send_retry > 0) { solaxX1_send_retry--; } +return; } void solaxX1SnsInit(void) { - AddLog(LOG_LEVEL_DEBUG, PSTR("SX1: Solax X1 Inverter Init")); - AddLog(LOG_LEVEL_DEBUG, PSTR("SX1: RX-pin: %d, TX-pin: %d, RTS-pin: %d"), Pin(GPIO_SOLAXX1_RX), Pin(GPIO_SOLAXX1_TX), Pin(GPIO_SOLAXX1_RTS)); -// DEBUG_SENSOR_LOG(PSTR("SX1: RX pin: %d, TX pin: %d"), Pin(GPIO_SOLAXX1_RX), Pin(GPIO_SOLAXX1_TX)); - protocolStatus.status = 0b00100000; // hasAddress - + 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); if (solaxX1Serial->begin(SOLAXX1_SPEED)) { if (solaxX1Serial->hardwareSerial()) { ClaimSerial(); } @@ -426,7 +387,8 @@ const char HTTP_SNS_solaxX1_DATA2[] PROGMEM = const char HTTP_SNS_solaxX1_DATA3[] PROGMEM = "{s}" D_SOLAX_X1 " " D_UPTIME "{m}%s " D_UNIT_HOUR "{e}" "{s}" D_SOLAX_X1 " " D_STATUS "{m}%s" - "{s}" D_SOLAX_X1 " " D_ERROR "{m}%s"; + "{s}" D_SOLAX_X1 " " D_ERROR "{m}%s" + "{s}" D_SOLAX_X1 " Inverter SN{m}%s"; #endif // USE_WEBSERVER void solaxX1Show(bool json) @@ -452,8 +414,7 @@ void solaxX1Show(bool json) char status[33]; GetTextIndexed(status, sizeof(status), solaxX1.runMode + 1, kSolaxMode); - if (json) - { + if (json) { ResponseAppend_P(PSTR(",\"" D_JSON_SOLAR_POWER "\":%s,\"" D_JSON_PV1_VOLTAGE "\":%s,\"" D_JSON_PV1_CURRENT "\":%s,\"" D_JSON_PV1_POWER "\":%s"), solar_power, pv1_voltage, pv1_current, pv1_power); #ifdef SOLAXX1_PV2 @@ -477,7 +438,8 @@ void solaxX1Show(bool json) WSContentSend_Temp(D_SOLAX_X1, solaxX1.temperature); char errorCodeString[33]; WSContentSend_PD(HTTP_SNS_solaxX1_DATA3, runtime, status, - GetTextIndexed(errorCodeString, sizeof(errorCodeString), solaxX1_ParseErrorCode(solaxX1.errorCode), kSolaxError)); + GetTextIndexed(errorCodeString, sizeof(errorCodeString), solaxX1_ParseErrorCode(solaxX1.errorCode), kSolaxError), + solaxX1SerialNumber); #endif // USE_WEBSERVER } } From 57b57dcf2507a3f4f795d505e68c783b1bde4909 Mon Sep 17 00:00:00 2001 From: SteWers Date: Wed, 16 Feb 2022 19:33:57 +0100 Subject: [PATCH 2/3] [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; From 2bdec2d9fc24fe80b48514858068cdc071776bdb Mon Sep 17 00:00:00 2001 From: SteWers Date: Thu, 17 Feb 2022 18:59:44 +0100 Subject: [PATCH 3/3] [Solax X1] rework 02/2022 final 2 - removed command index - new syntax: on demand request of IDinfo data via command `EnergyConfig ReadIDinfo` - new syntax: on demand request of config data via command `EnergyConfig ReadConfig` - replaced `dtostrfd()` with `%*_f` --- tasmota/xnrg_12_solaxX1.ino | 108 ++++++++++++++++++------------------ 1 file changed, 53 insertions(+), 55 deletions(-) diff --git a/tasmota/xnrg_12_solaxX1.ino b/tasmota/xnrg_12_solaxX1.ino index 8e94bcb08..9be2e7194 100644 --- a/tasmota/xnrg_12_solaxX1.ino +++ b/tasmota/xnrg_12_solaxX1.ino @@ -264,7 +264,7 @@ void solaxX1_250MSecond(void) // Every 250 milliseconds uint8_t DataRead[80] = {0}; uint8_t TempData[16] = {0}; char TempDataChar[16]; - uint8_t i; + float TempFloat; if (solaxX1Serial->available()) { if (solaxX1_RS485Receive(DataRead)) { // CRC-error -> no further action @@ -282,18 +282,18 @@ void solaxX1_250MSecond(void) // Every 250 milliseconds if (DataRead[6] == 0x11 && DataRead[7] == 0x82) { // received "Response for query (live data)" Energy.data_valid[0] = 0; 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 + solaxX1.energy_today = ((DataRead[11] << 8) | DataRead[12]) * 0.1f; // Energy Today + solaxX1.dc1_voltage = ((DataRead[13] << 8) | DataRead[14]) * 0.1f; // PV1 Voltage + solaxX1.dc2_voltage = ((DataRead[15] << 8) | DataRead[16]) * 0.1f; // PV2 Voltage + solaxX1.dc1_current = ((DataRead[17] << 8) | DataRead[18]) * 0.1f; // PV1 Current + solaxX1.dc2_current = ((DataRead[19] << 8) | DataRead[20]) * 0.1f; // PV2 Current + Energy.current[0] = ((DataRead[21] << 8) | DataRead[22]) * 0.1f; // AC Current + Energy.voltage[0] = ((DataRead[23] << 8) | DataRead[24]) * 0.1f; // AC Voltage + Energy.frequency[0] = ((DataRead[25] << 8) | DataRead[26]) * 0.01f; // AC Frequency + Energy.active_power[0] = ((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 + Energy.import_active[0] = ((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 @@ -336,56 +336,56 @@ void solaxX1_250MSecond(void) // Every 250 milliseconds 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); + TempFloat = ((DataRead[9] << 8) | DataRead[10]) * 0.1f; + AddLog(LOG_LEVEL_INFO, PSTR("SX1: new wVpvStart: %1_f V (Inverter launch voltage threshold)"), &TempFloat); 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); + TempFloat = ((DataRead[13] << 8) | DataRead[14]) * 0.1f; + AddLog(LOG_LEVEL_INFO, PSTR("SX1: wVacMinProtect: %1_f V (allowed minimum grid voltage)"), &TempFloat); + TempFloat = ((DataRead[15] << 8) | DataRead[16]) * 0.1f; + AddLog(LOG_LEVEL_INFO, PSTR("SX1: wVacMaxProtect: %1_f V (allowed maximum grid voltage)"), &TempFloat); + TempFloat = ((DataRead[17] << 8) | DataRead[18]) * 0.01f; + AddLog(LOG_LEVEL_INFO, PSTR("SX1: wFacMinProtect: %2_f Hz (allowed minimum grid frequency)"), &TempFloat); + TempFloat = ((DataRead[19] << 8) | DataRead[20]) * 0.01f; + AddLog(LOG_LEVEL_INFO, PSTR("SX1: wFacMaxProtect: %2_f Hz (allowed maximum grid frequency)"), &TempFloat); 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); + TempFloat = ((DataRead[23] << 8) | DataRead[24]) * 0.1f; + AddLog(LOG_LEVEL_INFO, PSTR("SX1: wGrid10MinAvgProtect: %1_f V (10 minutes over voltage protect)"), &TempFloat); + TempFloat = ((DataRead[25] << 8) | DataRead[26]) * 0.1f; + AddLog(LOG_LEVEL_INFO, PSTR("SX1: wVacMinSlowProtect: %1_f V (grid undervoltage protect value)"), &TempFloat); + TempFloat = ((DataRead[27] << 8) | DataRead[28]) * 0.1f; + AddLog(LOG_LEVEL_INFO, PSTR("SX1: wVacMaxSlowProtect: %1_f V (grid overvoltage protect value)"), &TempFloat); + TempFloat = ((DataRead[29] << 8) | DataRead[30]) * 0.01f; + AddLog(LOG_LEVEL_INFO, PSTR("SX1: wFacMinSlowProtect: %2_f Hz (grid underfrequency protect value)"), &TempFloat); + TempFloat = ((DataRead[31] << 8) | DataRead[32]) * 0.01f; + AddLog(LOG_LEVEL_INFO, PSTR("SX1: wFacMaxSlowProtect: %2_f Hz (grid overfrequency protect value)"), &TempFloat); 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); + TempFloat = DataRead[36] * 0.01f; + AddLog(LOG_LEVEL_INFO, PSTR("SX1: wPowerfactor_data: %2_f"), &TempFloat); + TempFloat = DataRead[37] * 0.01f; + AddLog(LOG_LEVEL_INFO, PSTR("SX1: wUpperLimit: %2_f (overexcite limits)"), &TempFloat); + TempFloat = DataRead[38] * 0.01f; + AddLog(LOG_LEVEL_INFO, PSTR("SX1: wLowerLimit: %2_f (underexcite limits)"), &TempFloat); + TempFloat = DataRead[39] * 0.01f; + AddLog(LOG_LEVEL_INFO, PSTR("SX1: wPowerLow: %2_f (power ratio change upper limits)"), &TempFloat); + TempFloat = DataRead[40] * 0.01f; + AddLog(LOG_LEVEL_INFO, PSTR("SX1: wPowerUp: %2_f (power ratio change lower limits)"), &TempFloat); 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); + TempFloat = ((DataRead[43] << 8) | DataRead[44]) * 0.01f; + AddLog(LOG_LEVEL_INFO, PSTR("SX1: WFreqSetPoint: %2_f Hz (Over Frequency drop output setpoint)"), &TempFloat); 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); + TempFloat = ((DataRead[53] << 8) | DataRead[54]) * 0.01f; + AddLog(LOG_LEVEL_INFO, PSTR("SX1: WWgra: %2_f %%"), &TempFloat); + TempFloat = ((DataRead[55] << 8) | DataRead[56]) * 0.1f; + AddLog(LOG_LEVEL_INFO, PSTR("SX1: wWv2: %1_f V"), &TempFloat); + TempFloat = ((DataRead[57] << 8) | DataRead[58]) * 0.1f; + AddLog(LOG_LEVEL_INFO, PSTR("SX1: wWv3: %1_f V"), &TempFloat); + TempFloat = ((DataRead[59] << 8) | DataRead[60]) * 0.1f; + AddLog(LOG_LEVEL_INFO, PSTR("SX1: wWv4: %1_f V"), &TempFloat); 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]); @@ -596,9 +596,7 @@ bool Xnrg12(uint8_t function) solaxX1_DrvInit(); break; case FUNC_COMMAND: - if (XNRG_12 == XdrvMailbox.index) { // "EnergyConfig12 " - result = SolaxX1_cmd(); - } + result = SolaxX1_cmd(); break; } return result;