diff --git a/tools/nano/AhoyUL/.gitignore b/tools/nano/AhoyUL/.gitignore index c04a05f4..24347bc7 100644 --- a/tools/nano/AhoyUL/.gitignore +++ b/tools/nano/AhoyUL/.gitignore @@ -6,5 +6,5 @@ .log config_override.h smac_examples.txt - +HmDecoder.* diff --git a/tools/nano/AhoyUL/src/utils_serial.h b/tools/nano/AhoyUL/src/SerialUtils.h similarity index 99% rename from tools/nano/AhoyUL/src/utils_serial.h rename to tools/nano/AhoyUL/src/SerialUtils.h index d898b411..4ed7d5b3 100644 --- a/tools/nano/AhoyUL/src/utils_serial.h +++ b/tools/nano/AhoyUL/src/SerialUtils.h @@ -8,6 +8,11 @@ #include "config.h" #include "dbg.h" + + +/** + * Class for serial functions (read, eval, etc...) +*/ class SerialUtils { public: SerialUtils() { diff --git a/tools/nano/AhoyUL/src/config.h b/tools/nano/AhoyUL/src/config.h index aa522ab1..46a02e4d 100644 --- a/tools/nano/AhoyUL/src/config.h +++ b/tools/nano/AhoyUL/src/config.h @@ -46,7 +46,9 @@ #define SEND_INTERVAL (60) //send interval if Rx OK #define SEND_NOSIGNAL_SHORT (10) //short send interval if no RX (used initial sync or signal loss) #define SEND_REPEAT (6) //number of tries of short send interval to sync faster to inverter after night -#define SEND_NOSIGNAL_LONG (20*60) //long TX interval whne no SIGNAL for long time, e.g. over night +#define SEND_NOSIGNAL_LONG (20*60) //long TX interval when no SIGNAL for long time, e.g. over night + +#define INVERTER_TIMEOUT_sec (SEND_INTERVAL * 2) //max duration of send to one inverter // maximum human readable inverter name length #define MAX_NAME_LENGTH 16 diff --git a/tools/nano/AhoyUL/src/hmDecode.h b/tools/nano/AhoyUL/src/hmDecode.h deleted file mode 100644 index e3d27154..00000000 --- a/tools/nano/AhoyUL/src/hmDecode.h +++ /dev/null @@ -1,380 +0,0 @@ -// -// todo: map hmDefines.h into this hmDecode.h class, no yet used and working -// - -#include -#include -#include - -#include "config.h" -#include "dbg.h" - -class HmDecode { - public: - HmDecode() { - // some member vars here - } - - ~HmDecode() {} - - union serial_u { - uint64_t u64; - uint8_t b[8]; - }; - - // units - enum { UNIT_V = 0, - UNIT_A, - UNIT_W, - UNIT_WH, - UNIT_KWH, - UNIT_HZ, - UNIT_C, - UNIT_PCT, - UNIT_VAR, - UNIT_NONE }; - // const char* const units[] = {" V", " A", " W", " Wh", " kWh", " Hz", " °C", " %", " var", ""}; - const char PGM_units[][6] PROGMEM{" V ", " A ", " W ", " Wh ", " kWh ", " Hz ", " °C ", " % ", " var ", " "}; // one hidden byte needed at the end for '\0' - - // field types - enum { FLD_UDC = 0, - FLD_IDC, - FLD_PDC, - FLD_YD, - FLD_YW, - FLD_YT, - FLD_UAC, - FLD_IAC, - FLD_PAC, - FLD_F, - FLD_T, - FLD_PF, - FLD_EFF, - FLD_IRR, - FLD_Q, - FLD_EVT, - FLD_FW_VERSION, - FLD_FW_BUILD_YEAR, - FLD_FW_BUILD_MONTH_DAY, - FLD_FW_BUILD_HOUR_MINUTE, - FLD_HW_ID, - FLD_ACT_ACTIVE_PWR_LIMIT, - /*FLD_ACT_REACTIVE_PWR_LIMIT, FLD_ACT_PF,*/ FLD_LAST_ALARM_CODE }; - - // PGM Flash memory usage instead of RAM for ARDUINO NANO, idea given by Nick Gammon's great webpage http://gammon.com.au/progmem - const char PGM_fields[/*NUM ELEMENTS*/][25] PROGMEM{ - {"U_DC"}, - {"I_DC"}, - {"P_DC"}, - {"YieldDay"}, - {"YieldWeek"}, - {"YieldTotal"}, - {"U_AC"}, - {"I_AC"}, - {"P_AC"}, - {"F_AC"}, - {"Temp"}, - {"PF_AC"}, - {"Efficiency"}, - {"Irradiation"}, - {"Q_AC"}, - {"ALARM_MES_ID"}, - {"FWVersion"}, - {"FWBuildYear"}, - {"FWBuildMonthDay"}, - {"FWBuildHourMinute"}, - {"HWPartId"}, - {"active PowerLimit"}, - /* {"reactive PowerLimit"}, - {"Powerfactor"}*/ - {"LastAlarmCode"}, - }; - - // const char* const fields[] = {"U_DC", "I_DC", "P_DC", "YieldDay", "YieldWeek", "YieldTotal", - // "U_AC", "I_AC", "P_AC", "F_AC", "Temp", "PF_AC", "Efficiency", "Irradiation","Q_AC", - // "ALARM_MES_ID","FWVersion","FWBuildYear","FWBuildMonthDay","FWBuildHourMinute","HWPartId", - // "active PowerLimit", /*"reactive PowerLimit","Powerfactor",*/ "LastAlarmCode"}; - - const char* const notAvail = "n/a"; - - // mqtt discovery device classes - enum { DEVICE_CLS_NONE = 0, - DEVICE_CLS_CURRENT, - DEVICE_CLS_ENERGY, - DEVICE_CLS_PWR, - DEVICE_CLS_VOLTAGE, - DEVICE_CLS_FREQ, - DEVICE_CLS_TEMP }; - - const char* const deviceClasses[] = {0, "current", "energy", "power", "voltage", "frequency", "temperature"}; - - enum { STATE_CLS_NONE = 0, - STATE_CLS_MEASUREMENT, - STATE_CLS_TOTAL_INCREASING }; - - const char* const stateClasses[] = {0, "measurement", "total_increasing"}; - - typedef struct { - uint8_t fieldId; // field id - uint8_t deviceClsId; // device class - uint8_t stateClsId; // state class - } byteAssign_fieldDeviceClass; - - const byteAssign_fieldDeviceClass deviceFieldAssignment[] = { - {FLD_UDC, DEVICE_CLS_VOLTAGE, STATE_CLS_MEASUREMENT}, - {FLD_IDC, DEVICE_CLS_CURRENT, STATE_CLS_MEASUREMENT}, - {FLD_PDC, DEVICE_CLS_PWR, STATE_CLS_MEASUREMENT}, - {FLD_YD, DEVICE_CLS_ENERGY, STATE_CLS_TOTAL_INCREASING}, - {FLD_YW, DEVICE_CLS_ENERGY, STATE_CLS_TOTAL_INCREASING}, - {FLD_YT, DEVICE_CLS_ENERGY, STATE_CLS_TOTAL_INCREASING}, - {FLD_UAC, DEVICE_CLS_VOLTAGE, STATE_CLS_MEASUREMENT}, - {FLD_IAC, DEVICE_CLS_CURRENT, STATE_CLS_MEASUREMENT}, - {FLD_PAC, DEVICE_CLS_PWR, STATE_CLS_MEASUREMENT}, - {FLD_F, DEVICE_CLS_FREQ, STATE_CLS_NONE}, - {FLD_T, DEVICE_CLS_TEMP, STATE_CLS_MEASUREMENT}, - {FLD_PF, DEVICE_CLS_NONE, STATE_CLS_NONE}, - {FLD_EFF, DEVICE_CLS_NONE, STATE_CLS_NONE}, - {FLD_IRR, DEVICE_CLS_NONE, STATE_CLS_NONE}}; -#define DEVICE_CLS_ASSIGN_LIST_LEN (sizeof(deviceFieldAssignment) / sizeof(byteAssign_fieldDeviceClass)) - - // indices to calculation functions, defined in hmInverter.h - enum { CALC_YT_CH0 = 0, - CALC_YD_CH0, - CALC_UDC_CH, - CALC_PDC_CH0, - CALC_EFF_CH0, - CALC_IRR_CH }; - enum { CMD_CALC = 0xffff }; - - // CH0 is default channel (freq, ac, temp) - enum { CH0 = 0, - CH1, - CH2, - CH3, - CH4 }; - - enum { INV_TYPE_1CH = 0, - INV_TYPE_2CH, - INV_TYPE_4CH }; - - typedef struct { - uint8_t fieldId; // field id - uint8_t unitId; // uint id - uint8_t ch; // channel 0 - 4 - uint8_t start; // pos of first byte in buffer - uint8_t num; // number of bytes in buffer - uint16_t div; // divisor / calc command - } byteAssign_t; - - /** - * indices are built for the buffer starting with cmd-id in first byte - * (complete payload in buffer) - * */ - - //------------------------------------- - // HM-Series - //------------------------------------- - const byteAssign_t InfoAssignment[] = { - {FLD_FW_VERSION, UNIT_NONE, CH0, 0, 2, 1}, - {FLD_FW_BUILD_YEAR, UNIT_NONE, CH0, 2, 2, 1}, - {FLD_FW_BUILD_MONTH_DAY, UNIT_NONE, CH0, 4, 2, 1}, - {FLD_FW_BUILD_HOUR_MINUTE, UNIT_NONE, CH0, 6, 2, 1}, - {FLD_HW_ID, UNIT_NONE, CH0, 8, 2, 1}}; -#define HMINFO_LIST_LEN (sizeof(InfoAssignment) / sizeof(byteAssign_t)) -#define HMINFO_PAYLOAD_LEN 14 - - const byteAssign_t SystemConfigParaAssignment[] = { - {FLD_ACT_ACTIVE_PWR_LIMIT, UNIT_PCT, CH0, 2, 2, 10} /*, -{ FLD_ACT_REACTIVE_PWR_LIMIT, UNIT_PCT, CH0, 4, 2, 10 }, -{ FLD_ACT_PF, UNIT_NONE, CH0, 6, 2, 1000 }*/ - }; -#define HMSYSTEM_LIST_LEN (sizeof(SystemConfigParaAssignment) / sizeof(byteAssign_t)) -#define HMSYSTEM_PAYLOAD_LEN 14 - - const byteAssign_t AlarmDataAssignment[] = { - {FLD_LAST_ALARM_CODE, UNIT_NONE, CH0, 0, 2, 1}}; -#define HMALARMDATA_LIST_LEN (sizeof(AlarmDataAssignment) / sizeof(byteAssign_t)) -#define HMALARMDATA_PAYLOAD_LEN 0 // 0: means check is off - - //------------------------------------- - // HM300, HM350, HM400 - //------------------------------------- - const byteAssign_t hm1chAssignment[] = { - {FLD_UDC, UNIT_V, CH1, 2, 2, 10}, - {FLD_IDC, UNIT_A, CH1, 4, 2, 100}, - {FLD_PDC, UNIT_W, CH1, 6, 2, 10}, - {FLD_YD, UNIT_WH, CH1, 12, 2, 1}, - {FLD_YT, UNIT_KWH, CH1, 8, 4, 1000}, - {FLD_IRR, UNIT_PCT, CH1, CALC_IRR_CH, CH1, CMD_CALC}, - - {FLD_UAC, UNIT_V, CH0, 14, 2, 10}, - {FLD_IAC, UNIT_A, CH0, 22, 2, 100}, - {FLD_PAC, UNIT_W, CH0, 18, 2, 10}, - {FLD_Q, UNIT_VAR, CH0, 20, 2, 10}, - {FLD_F, UNIT_HZ, CH0, 16, 2, 100}, - {FLD_PF, UNIT_NONE, CH0, 24, 2, 1000}, - {FLD_T, UNIT_C, CH0, 26, 2, 10}, - {FLD_EVT, UNIT_NONE, CH0, 28, 2, 1}, - {FLD_YD, UNIT_WH, CH0, CALC_YD_CH0, 0, CMD_CALC}, - {FLD_YT, UNIT_KWH, CH0, CALC_YT_CH0, 0, CMD_CALC}, - {FLD_PDC, UNIT_W, CH0, CALC_PDC_CH0, 0, CMD_CALC}, - {FLD_EFF, UNIT_PCT, CH0, CALC_EFF_CH0, 0, CMD_CALC}}; -#define HM1CH_LIST_LEN (sizeof(hm1chAssignment) / sizeof(byteAssign_t)) -#define HM1CH_PAYLOAD_LEN 30 - - //------------------------------------- - // HM600, HM700, HM800 - //------------------------------------- - const byteAssign_t hm2chAssignment[] = { - {FLD_UDC, UNIT_V, CH1, 2, 2, 10}, - {FLD_IDC, UNIT_A, CH1, 4, 2, 100}, - {FLD_PDC, UNIT_W, CH1, 6, 2, 10}, - {FLD_YD, UNIT_WH, CH1, 22, 2, 1}, - {FLD_YT, UNIT_KWH, CH1, 14, 4, 1000}, - {FLD_IRR, UNIT_PCT, CH1, CALC_IRR_CH, CH1, CMD_CALC}, - - {FLD_UDC, UNIT_V, CH2, 8, 2, 10}, - {FLD_IDC, UNIT_A, CH2, 10, 2, 100}, - {FLD_PDC, UNIT_W, CH2, 12, 2, 10}, - {FLD_YD, UNIT_WH, CH2, 24, 2, 1}, - {FLD_YT, UNIT_KWH, CH2, 18, 4, 1000}, - {FLD_IRR, UNIT_PCT, CH2, CALC_IRR_CH, CH2, CMD_CALC}, - - {FLD_UAC, UNIT_V, CH0, 26, 2, 10}, - {FLD_IAC, UNIT_A, CH0, 34, 2, 100}, - {FLD_PAC, UNIT_W, CH0, 30, 2, 10}, - {FLD_Q, UNIT_VAR, CH0, 32, 2, 10}, - {FLD_F, UNIT_HZ, CH0, 28, 2, 100}, - {FLD_PF, UNIT_NONE, CH0, 36, 2, 1000}, - {FLD_T, UNIT_C, CH0, 38, 2, 10}, - {FLD_EVT, UNIT_NONE, CH0, 40, 2, 1}, - {FLD_YD, UNIT_WH, CH0, CALC_YD_CH0, 0, CMD_CALC}, - {FLD_YT, UNIT_KWH, CH0, CALC_YT_CH0, 0, CMD_CALC}, - {FLD_PDC, UNIT_W, CH0, CALC_PDC_CH0, 0, CMD_CALC}, - {FLD_EFF, UNIT_PCT, CH0, CALC_EFF_CH0, 0, CMD_CALC} - - }; -#define HM2CH_LIST_LEN (sizeof(hm2chAssignment) / sizeof(byteAssign_t)) -#define HM2CH_PAYLOAD_LEN 42 - - //------------------------------------- - // HM1200, HM1500 - //------------------------------------- - const byteAssign_t hm4chAssignment[] = { - {FLD_UDC, UNIT_V, CH1, 2, 2, 10}, - {FLD_IDC, UNIT_A, CH1, 4, 2, 100}, - {FLD_PDC, UNIT_W, CH1, 8, 2, 10}, - {FLD_YD, UNIT_WH, CH1, 20, 2, 1}, - {FLD_YT, UNIT_KWH, CH1, 12, 4, 1000}, - {FLD_IRR, UNIT_PCT, CH1, CALC_IRR_CH, CH1, CMD_CALC}, - - {FLD_UDC, UNIT_V, CH2, CALC_UDC_CH, CH1, CMD_CALC}, - {FLD_IDC, UNIT_A, CH2, 6, 2, 100}, - {FLD_PDC, UNIT_W, CH2, 10, 2, 10}, - {FLD_YD, UNIT_WH, CH2, 22, 2, 1}, - {FLD_YT, UNIT_KWH, CH2, 16, 4, 1000}, - {FLD_IRR, UNIT_PCT, CH2, CALC_IRR_CH, CH2, CMD_CALC}, - - {FLD_UDC, UNIT_V, CH3, 24, 2, 10}, - {FLD_IDC, UNIT_A, CH3, 26, 2, 100}, - {FLD_PDC, UNIT_W, CH3, 30, 2, 10}, - {FLD_YD, UNIT_WH, CH3, 42, 2, 1}, - {FLD_YT, UNIT_KWH, CH3, 34, 4, 1000}, - {FLD_IRR, UNIT_PCT, CH3, CALC_IRR_CH, CH3, CMD_CALC}, - - {FLD_UDC, UNIT_V, CH4, CALC_UDC_CH, CH3, CMD_CALC}, - {FLD_IDC, UNIT_A, CH4, 28, 2, 100}, - {FLD_PDC, UNIT_W, CH4, 32, 2, 10}, - {FLD_YD, UNIT_WH, CH4, 44, 2, 1}, - {FLD_YT, UNIT_KWH, CH4, 38, 4, 1000}, - {FLD_IRR, UNIT_PCT, CH4, CALC_IRR_CH, CH4, CMD_CALC}, - - {FLD_UAC, UNIT_V, CH0, 46, 2, 10}, - {FLD_IAC, UNIT_A, CH0, 54, 2, 100}, - {FLD_PAC, UNIT_W, CH0, 50, 2, 10}, - {FLD_Q, UNIT_VAR, CH0, 52, 2, 10}, - {FLD_F, UNIT_HZ, CH0, 48, 2, 100}, - {FLD_PF, UNIT_NONE, CH0, 56, 2, 1000}, - {FLD_T, UNIT_C, CH0, 58, 2, 10}, - {FLD_EVT, UNIT_NONE, CH0, 60, 2, 1}, - {FLD_YD, UNIT_WH, CH0, CALC_YD_CH0, 0, CMD_CALC}, - {FLD_YT, UNIT_KWH, CH0, CALC_YT_CH0, 0, CMD_CALC}, - {FLD_PDC, UNIT_W, CH0, CALC_PDC_CH0, 0, CMD_CALC}, - {FLD_EFF, UNIT_PCT, CH0, CALC_EFF_CH0, 0, CMD_CALC}}; -#define HM4CH_LIST_LEN (sizeof(hm4chAssignment) / sizeof(byteAssign_t)) -#define HM4CH_PAYLOAD_LEN 62 - - /** - * simple decoding of 2ch HM-inverter only - */ - static void decode_userPayload(uint8_t _cmd, uint8_t* _user_payload, uint8_t _ulen, uint32_t _ts, char* _strout, uint8_t _strlen, uint16_t _invtype, uint32_t _plateID) { - volatile uint32_t _val = 0L; - - #if defined(ESP8266) - // for esp8266 environment - byteAssign_t _bp; // volatile byteAssign_t not compiling ESP8266 - #else - volatile byteAssign_t _bp; // must be volatile for Arduino-Nano, otherwise _bp.ch stays zero - #endif - - volatile uint8_t _x; - volatile uint8_t _end; - volatile float _fval; - volatile uint8_t _tmp8 = 0xff; - - //_str80[80] = '\0'; - snprintf_P(_strout, _strlen, PSTR("\ndata age: %d sec"), (millis() - _ts) / 1000); - Serial.print(_strout); - - if (_cmd == 0x95 and _ulen == 42) { - //!!! simple HM600/700/800 2ch decoding for cmd=0x95 only !!!! - - for (_x = 0; _x < HM2CH_LIST_LEN; _x++) { - // read values from given positions in payload - _bp = hm2chAssignment[_x]; - _val = 0L; - _end = _bp.start + _bp.num; - if (_tmp8 != _bp.ch) { - snprintf_P(_strout, _strlen, PSTR("\nHM800/%04X%08lX/ch%02d "), _invtype, _plateID, _bp.ch); // must be a small "l" in %08lX for Arduino-Nano - Serial.print(_strout); - // snprintf_P(_strout, _strlen, PSTR("ch%02d/"), _bp.ch); - // Serial.print(_strout); - } - _tmp8 = _bp.ch; - - if (CMD_CALC != _bp.div && _bp.div != 0) { - strncpy_P(_strout, &(PGM_fields[_bp.fieldId][0]), _strlen); - Serial.print(_strout); - Serial.print(F(": ")); - - do { - _val <<= 8; - _val |= _user_payload[_bp.start]; - } while (++_bp.start != _end); - _fval = (int)_val / (float)_bp.div; - - if (_bp.unitId == UNIT_NONE) { - Serial.print(_val); - Serial.print(F(" ")); - continue; - } - Serial.print(_fval, 2); // arduino nano does not sprintf(float values), but print() does - // Serial.print(units[_bp.unitId]); - strncpy_P(_strout, &PGM_units[_bp.unitId][0], _strlen); - Serial.print(_strout); - - } else { - // do calculations - // Serial.print(F("not yet")); - } - } // end for() - Serial.println(); - - } else { - Serial.print(F("NO DECODER ")); - Serial.print(_cmd, HEX); - } - } // end decodePayload() - - private: -}; \ No newline at end of file diff --git a/tools/nano/AhoyUL/src/hmDecoder.h b/tools/nano/AhoyUL/src/hmDecoder.h new file mode 100644 index 00000000..83d14d7f --- /dev/null +++ b/tools/nano/AhoyUL/src/hmDecoder.h @@ -0,0 +1,394 @@ +//----------------------------------------------------------------------------- +// 2022 Ahoy, https://github.com/lumpapu/ahoy +// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/ +//----------------------------------------------------------------------------- + +// 2022 mb using PROGMEM for lage char* arrays, used and tested for HM800 decoding (2channel), no calculation yet +// - strip down of hmDefines.h and rename to hmDecode.h + +// todo: make a decoding class out of this + +#ifndef __HM_DEFINES_H__ +#define __HM_DEFINES_H__ + +#include "dbg.h" + +union serial_u { + uint64_t u64; + uint8_t b[8]; +}; + +// units +enum { UNIT_V = 0, + UNIT_A, + UNIT_W, + UNIT_WH, + UNIT_KWH, + UNIT_HZ, + UNIT_C, + UNIT_PCT, + UNIT_VAR, + UNIT_NONE }; +// const char* const units[] = {" V", " A", " W", " Wh", " kWh", " Hz", " °C", " %", " var", ""}; +const char PGM_units[][6] PROGMEM = {" V ", " A ", " W ", " Wh ", " kWh ", " Hz ", " °C ", " % ", " var ", " "}; // one hidden byte needed at the end for '\0' + +// field types +enum { FLD_UDC = 0, + FLD_IDC, + FLD_PDC, + FLD_YD, + FLD_YW, + FLD_YT, + FLD_UAC, + FLD_IAC, + FLD_PAC, + FLD_F, + FLD_T, + FLD_PF, + FLD_EFF, + FLD_IRR, + FLD_Q, + FLD_EVT, + FLD_FW_VERSION, + FLD_FW_BUILD_YEAR, + FLD_FW_BUILD_MONTH_DAY, + FLD_FW_BUILD_HOUR_MINUTE, + FLD_HW_ID, + FLD_ACT_ACTIVE_PWR_LIMIT, + /*FLD_ACT_REACTIVE_PWR_LIMIT, FLD_ACT_PF,*/ FLD_LAST_ALARM_CODE }; + +// PGM Flash memory usage instead of RAM for ARDUINO NANO, idea given by Nick Gammon's great webpage http://gammon.com.au/progmem +const char PGM_fields[/*NUM ELEMENTS*/][25] PROGMEM = { + {"U_DC"}, + {"I_DC"}, + {"P_DC"}, + {"YieldDay"}, + {"YieldWeek"}, + {"YieldTotal"}, + {"U_AC"}, + {"I_AC"}, + {"P_AC"}, + {"F_AC"}, + {"Temp"}, + {"PF_AC"}, + {"Efficiency"}, + {"Irradiation"}, + {"Q_AC"}, + {"ALARM_MES_ID"}, + {"FWVersion"}, + {"FWBuildYear"}, + {"FWBuildMonthDay"}, + {"FWBuildHourMinute"}, + {"HWPartId"}, + {"active PowerLimit"}, + /* {"reactive PowerLimit"}, + {"Powerfactor"}*/ + {"LastAlarmCode"}, +}; + + +const char PGM_invname[][8] PROGMEM = { "HM400", "HM600", "HM800", "HM1200", "HM1500"}; + +// const char* const fields[] = {"U_DC", "I_DC", "P_DC", "YieldDay", "YieldWeek", "YieldTotal", +// "U_AC", "I_AC", "P_AC", "F_AC", "Temp", "PF_AC", "Efficiency", "Irradiation","Q_AC", +// "ALARM_MES_ID","FWVersion","FWBuildYear","FWBuildMonthDay","FWBuildHourMinute","HWPartId", +// "active PowerLimit", /*"reactive PowerLimit","Powerfactor",*/ "LastAlarmCode"}; + +const char* const notAvail = "n/a"; + +// mqtt discovery device classes +enum { DEVICE_CLS_NONE = 0, + DEVICE_CLS_CURRENT, + DEVICE_CLS_ENERGY, + DEVICE_CLS_PWR, + DEVICE_CLS_VOLTAGE, + DEVICE_CLS_FREQ, + DEVICE_CLS_TEMP }; +const char* const deviceClasses[] = {0, "current", "energy", "power", "voltage", "frequency", "temperature"}; +enum { STATE_CLS_NONE = 0, + STATE_CLS_MEASUREMENT, + STATE_CLS_TOTAL_INCREASING }; +const char* const stateClasses[] = {0, "measurement", "total_increasing"}; +typedef struct { + uint8_t fieldId; // field id + uint8_t deviceClsId; // device class + uint8_t stateClsId; // state class +} byteAssign_fieldDeviceClass; +const byteAssign_fieldDeviceClass deviceFieldAssignment[] = { + {FLD_UDC, DEVICE_CLS_VOLTAGE, STATE_CLS_MEASUREMENT}, + {FLD_IDC, DEVICE_CLS_CURRENT, STATE_CLS_MEASUREMENT}, + {FLD_PDC, DEVICE_CLS_PWR, STATE_CLS_MEASUREMENT}, + {FLD_YD, DEVICE_CLS_ENERGY, STATE_CLS_TOTAL_INCREASING}, + {FLD_YW, DEVICE_CLS_ENERGY, STATE_CLS_TOTAL_INCREASING}, + {FLD_YT, DEVICE_CLS_ENERGY, STATE_CLS_TOTAL_INCREASING}, + {FLD_UAC, DEVICE_CLS_VOLTAGE, STATE_CLS_MEASUREMENT}, + {FLD_IAC, DEVICE_CLS_CURRENT, STATE_CLS_MEASUREMENT}, + {FLD_PAC, DEVICE_CLS_PWR, STATE_CLS_MEASUREMENT}, + {FLD_F, DEVICE_CLS_FREQ, STATE_CLS_NONE}, + {FLD_T, DEVICE_CLS_TEMP, STATE_CLS_MEASUREMENT}, + {FLD_PF, DEVICE_CLS_NONE, STATE_CLS_NONE}, + {FLD_EFF, DEVICE_CLS_NONE, STATE_CLS_NONE}, + {FLD_IRR, DEVICE_CLS_NONE, STATE_CLS_NONE}}; +#define DEVICE_CLS_ASSIGN_LIST_LEN (sizeof(deviceFieldAssignment) / sizeof(byteAssign_fieldDeviceClass)) + +// indices to calculation functions, defined in hmInverter.h +enum { CALC_YT_CH0 = 0, + CALC_YD_CH0, + CALC_UDC_CH, + CALC_PDC_CH0, + CALC_EFF_CH0, + CALC_IRR_CH }; +enum { CMD_CALC = 0xffff }; + +// CH0 is default channel (freq, ac, temp) +enum { CH0 = 0, + CH1, + CH2, + CH3, + CH4 }; + +enum { INV_TYPE_1CH = 0, + INV_TYPE_2CH, + INV_TYPE_4CH }; + +typedef struct { + uint8_t fieldId; // field id + uint8_t unitId; // uint id + uint8_t ch; // channel 0 - 4 + uint8_t start; // pos of first byte in buffer + uint8_t num; // number of bytes in buffer + uint16_t div; // divisor / calc command +} byteAssign_t; + +/** + * indices are built for the buffer starting with cmd-id in first byte + * (complete payload in buffer) + * */ + +//------------------------------------- +// HM-Series +//------------------------------------- +const byteAssign_t InfoAssignment[] = { + {FLD_FW_VERSION, UNIT_NONE, CH0, 0, 2, 1}, + {FLD_FW_BUILD_YEAR, UNIT_NONE, CH0, 2, 2, 1}, + {FLD_FW_BUILD_MONTH_DAY, UNIT_NONE, CH0, 4, 2, 1}, + {FLD_FW_BUILD_HOUR_MINUTE, UNIT_NONE, CH0, 6, 2, 1}, + {FLD_HW_ID, UNIT_NONE, CH0, 8, 2, 1}}; +#define HMINFO_LIST_LEN (sizeof(InfoAssignment) / sizeof(byteAssign_t)) +#define HMINFO_PAYLOAD_LEN 14 + +const byteAssign_t SystemConfigParaAssignment[] = { + {FLD_ACT_ACTIVE_PWR_LIMIT, UNIT_PCT, CH0, 2, 2, 10} /*, +{ FLD_ACT_REACTIVE_PWR_LIMIT, UNIT_PCT, CH0, 4, 2, 10 }, +{ FLD_ACT_PF, UNIT_NONE, CH0, 6, 2, 1000 }*/ +}; +#define HMSYSTEM_LIST_LEN (sizeof(SystemConfigParaAssignment) / sizeof(byteAssign_t)) +#define HMSYSTEM_PAYLOAD_LEN 14 + +const byteAssign_t AlarmDataAssignment[] = { + {FLD_LAST_ALARM_CODE, UNIT_NONE, CH0, 0, 2, 1}}; +#define HMALARMDATA_LIST_LEN (sizeof(AlarmDataAssignment) / sizeof(byteAssign_t)) +#define HMALARMDATA_PAYLOAD_LEN 0 // 0: means check is off + +//------------------------------------- +// HM300, HM350, HM400 +//------------------------------------- +const byteAssign_t hm1chAssignment[] = { + {FLD_UDC, UNIT_V, CH1, 2, 2, 10}, + {FLD_IDC, UNIT_A, CH1, 4, 2, 100}, + {FLD_PDC, UNIT_W, CH1, 6, 2, 10}, + {FLD_YD, UNIT_WH, CH1, 12, 2, 1}, + {FLD_YT, UNIT_KWH, CH1, 8, 4, 1000}, + {FLD_IRR, UNIT_PCT, CH1, CALC_IRR_CH, CH1, CMD_CALC}, + + {FLD_UAC, UNIT_V, CH0, 14, 2, 10}, + {FLD_IAC, UNIT_A, CH0, 22, 2, 100}, + {FLD_PAC, UNIT_W, CH0, 18, 2, 10}, + {FLD_Q, UNIT_VAR, CH0, 20, 2, 10}, + {FLD_F, UNIT_HZ, CH0, 16, 2, 100}, + {FLD_PF, UNIT_NONE, CH0, 24, 2, 1000}, + {FLD_T, UNIT_C, CH0, 26, 2, 10}, + {FLD_EVT, UNIT_NONE, CH0, 28, 2, 1}, + {FLD_YD, UNIT_WH, CH0, CALC_YD_CH0, 0, CMD_CALC}, + {FLD_YT, UNIT_KWH, CH0, CALC_YT_CH0, 0, CMD_CALC}, + {FLD_PDC, UNIT_W, CH0, CALC_PDC_CH0, 0, CMD_CALC}, + {FLD_EFF, UNIT_PCT, CH0, CALC_EFF_CH0, 0, CMD_CALC}}; +#define HM1CH_LIST_LEN (sizeof(hm1chAssignment) / sizeof(byteAssign_t)) +#define HM1CH_PAYLOAD_LEN 30 + +//------------------------------------- +// HM600, HM700, HM800 +//------------------------------------- +const byteAssign_t hm2chAssignment[] = { + {FLD_UDC, UNIT_V, CH1, 2, 2, 10}, + {FLD_IDC, UNIT_A, CH1, 4, 2, 100}, + {FLD_PDC, UNIT_W, CH1, 6, 2, 10}, + {FLD_YD, UNIT_WH, CH1, 22, 2, 1}, + {FLD_YT, UNIT_KWH, CH1, 14, 4, 1000}, + {FLD_IRR, UNIT_PCT, CH1, CALC_IRR_CH, CH1, CMD_CALC}, + + {FLD_UDC, UNIT_V, CH2, 8, 2, 10}, + {FLD_IDC, UNIT_A, CH2, 10, 2, 100}, + {FLD_PDC, UNIT_W, CH2, 12, 2, 10}, + {FLD_YD, UNIT_WH, CH2, 24, 2, 1}, + {FLD_YT, UNIT_KWH, CH2, 18, 4, 1000}, + {FLD_IRR, UNIT_PCT, CH2, CALC_IRR_CH, CH2, CMD_CALC}, + + {FLD_UAC, UNIT_V, CH0, 26, 2, 10}, + {FLD_IAC, UNIT_A, CH0, 34, 2, 100}, + {FLD_PAC, UNIT_W, CH0, 30, 2, 10}, + {FLD_Q, UNIT_VAR, CH0, 32, 2, 10}, + {FLD_F, UNIT_HZ, CH0, 28, 2, 100}, + {FLD_PF, UNIT_NONE, CH0, 36, 2, 1000}, + {FLD_T, UNIT_C, CH0, 38, 2, 10}, + {FLD_EVT, UNIT_NONE, CH0, 40, 2, 1}, + {FLD_YD, UNIT_WH, CH0, CALC_YD_CH0, 0, CMD_CALC}, + {FLD_YT, UNIT_KWH, CH0, CALC_YT_CH0, 0, CMD_CALC}, + {FLD_PDC, UNIT_W, CH0, CALC_PDC_CH0, 0, CMD_CALC}, + {FLD_EFF, UNIT_PCT, CH0, CALC_EFF_CH0, 0, CMD_CALC} + +}; +#define HM2CH_LIST_LEN (sizeof(hm2chAssignment) / sizeof(byteAssign_t)) +#define HM2CH_PAYLOAD_LEN 42 + +//------------------------------------- +// HM1200, HM1500 +//------------------------------------- +const byteAssign_t hm4chAssignment[] = { + {FLD_UDC, UNIT_V, CH1, 2, 2, 10}, + {FLD_IDC, UNIT_A, CH1, 4, 2, 100}, + {FLD_PDC, UNIT_W, CH1, 8, 2, 10}, + {FLD_YD, UNIT_WH, CH1, 20, 2, 1}, + {FLD_YT, UNIT_KWH, CH1, 12, 4, 1000}, + {FLD_IRR, UNIT_PCT, CH1, CALC_IRR_CH, CH1, CMD_CALC}, + + {FLD_UDC, UNIT_V, CH2, CALC_UDC_CH, CH1, CMD_CALC}, + {FLD_IDC, UNIT_A, CH2, 6, 2, 100}, + {FLD_PDC, UNIT_W, CH2, 10, 2, 10}, + {FLD_YD, UNIT_WH, CH2, 22, 2, 1}, + {FLD_YT, UNIT_KWH, CH2, 16, 4, 1000}, + {FLD_IRR, UNIT_PCT, CH2, CALC_IRR_CH, CH2, CMD_CALC}, + + {FLD_UDC, UNIT_V, CH3, 24, 2, 10}, + {FLD_IDC, UNIT_A, CH3, 26, 2, 100}, + {FLD_PDC, UNIT_W, CH3, 30, 2, 10}, + {FLD_YD, UNIT_WH, CH3, 42, 2, 1}, + {FLD_YT, UNIT_KWH, CH3, 34, 4, 1000}, + {FLD_IRR, UNIT_PCT, CH3, CALC_IRR_CH, CH3, CMD_CALC}, + + {FLD_UDC, UNIT_V, CH4, CALC_UDC_CH, CH3, CMD_CALC}, + {FLD_IDC, UNIT_A, CH4, 28, 2, 100}, + {FLD_PDC, UNIT_W, CH4, 32, 2, 10}, + {FLD_YD, UNIT_WH, CH4, 44, 2, 1}, + {FLD_YT, UNIT_KWH, CH4, 38, 4, 1000}, + {FLD_IRR, UNIT_PCT, CH4, CALC_IRR_CH, CH4, CMD_CALC}, + + {FLD_UAC, UNIT_V, CH0, 46, 2, 10}, + {FLD_IAC, UNIT_A, CH0, 54, 2, 100}, + {FLD_PAC, UNIT_W, CH0, 50, 2, 10}, + {FLD_Q, UNIT_VAR, CH0, 52, 2, 10}, + {FLD_F, UNIT_HZ, CH0, 48, 2, 100}, + {FLD_PF, UNIT_NONE, CH0, 56, 2, 1000}, + {FLD_T, UNIT_C, CH0, 58, 2, 10}, + {FLD_EVT, UNIT_NONE, CH0, 60, 2, 1}, + {FLD_YD, UNIT_WH, CH0, CALC_YD_CH0, 0, CMD_CALC}, + {FLD_YT, UNIT_KWH, CH0, CALC_YT_CH0, 0, CMD_CALC}, + {FLD_PDC, UNIT_W, CH0, CALC_PDC_CH0, 0, CMD_CALC}, + {FLD_EFF, UNIT_PCT, CH0, CALC_EFF_CH0, 0, CMD_CALC}}; +#define HM4CH_LIST_LEN (sizeof(hm4chAssignment) / sizeof(byteAssign_t)) +#define HM4CH_PAYLOAD_LEN 62 + + +/** + * simple decoding of 1,2,4ch HM-inverter only + */ +static void decode_InfoREQResp(uint8_t _cmd, uint8_t *_user_payload, uint8_t _ulen, uint32_t _ts, char *_strout, uint8_t _strlen, uint16_t _invtype, uint32_t _plateID) { + volatile int32_t _val = 0L; + + #if defined(ESP8266) + // for esp8266 environment + byteAssign_t _bp; //volatile byteAssign_t not compiling ESP8266 + #else + volatile byteAssign_t _bp; //must be volatile for Arduino-Nano, otherwise _bp.ch stays zero + #endif + + volatile uint8_t _x; + volatile uint8_t _end; + volatile float _fval; + volatile uint8_t _tmp8 = 0xff; + + volatile uint8_t LIST_LEN = 0; + volatile uint8_t ixn = 0; + + //_str80[80] = '\0'; + snprintf_P(_strout, _strlen, PSTR("\ndata age: %d sec"), (millis() - _ts) / 1000); + Serial.print(_strout); + + if (_cmd == 0x95) { + + //check given payload len + if(_ulen == HM2CH_PAYLOAD_LEN) { + ixn = 2; + LIST_LEN = HM2CH_LIST_LEN; + } else if (_ulen == HM4CH_PAYLOAD_LEN) { + ixn = 4; + LIST_LEN = HM4CH_LIST_LEN; + } else if (_ulen == HM1CH_PAYLOAD_LEN) { + ixn = 0; + LIST_LEN = HM1CH_LIST_LEN; + } else { + Serial.print(F("ERR Wrong PL_LEN")); + return; + } + + for (_x = 0; _x < LIST_LEN; _x++) { + // read values from given positions in payload + _bp = hm2chAssignment[_x]; + _val = 0L; + _end = _bp.start + _bp.num; + if (_tmp8 != _bp.ch) { + //snprintf_P(_strout, _strlen, PSTR("\nHM800/%04X%08lX/ch%02d "), _invtype, _plateID, _bp.ch); //must be a small "l" in %08lX for Arduino-Nano + snprintf_P(_strout, _strlen, PSTR("\n%S/%04X%08lX/ch%02d "), PGM_invname[ixn],_invtype, _plateID, _bp.ch); //must be a small "l" in %08lX for Arduino-Nano, https://forum.arduino.cc/t/f-macro-and-sprintf/894536/10 + Serial.print(_strout); + } + _tmp8 = _bp.ch; + + if (CMD_CALC != _bp.div && _bp.div != 0) { + strncpy_P(_strout, &(PGM_fields[_bp.fieldId][0]), _strlen); + Serial.print(_strout); + Serial.print(F(": ")); + + do { + _val <<= 8; + _val |= _user_payload[_bp.start]; + } while (++_bp.start != _end); + if (_bp.num > 2) { + _fval = (int32_t)_val / (float)_bp.div; //must be int32 to handle 4bit value of yield total + } else { + _fval = (int16_t)_val / (float)_bp.div; //for negative temparture value it must be int16 + } + + if (_bp.unitId == UNIT_NONE) { + Serial.print(_val); + Serial.print(F(" ")); + continue; + } + Serial.print(_fval, 2); // arduino nano does not sprintf(float values), but print() does + // Serial.print(units[_bp.unitId]); + strncpy_P(_strout, &PGM_units[_bp.unitId][0], _strlen); + Serial.print(_strout); + + } else { + // do calculations + // Serial.print(F("not yet")); + } + } // end for() + Serial.println(); + + } else { + Serial.print(F("NO DECODER ")); + Serial.print(_cmd, HEX); + } +}//end decodePayload() + +#endif /*__HM_DEFINES_H__*/ diff --git a/tools/nano/AhoyUL/src/hmDefines.h b/tools/nano/AhoyUL/src/hmDefines.h deleted file mode 100644 index b6377dd9..00000000 --- a/tools/nano/AhoyUL/src/hmDefines.h +++ /dev/null @@ -1,271 +0,0 @@ -//----------------------------------------------------------------------------- -// 2022 Ahoy, https://github.com/lumpapu/ahoy -// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/ -//----------------------------------------------------------------------------- - -//2022 mb using PROGMEM for lage char* arrays, used and tested for HM800 decoding (2channel), no calculation yet - -//todo: make a decoding class out of this - - -#ifndef __HM_DEFINES_H__ -#define __HM_DEFINES_H__ - -#include "dbg.h" - - -union serial_u { - uint64_t u64; - uint8_t b[8]; -}; - - -// units -enum {UNIT_V = 0, UNIT_A, UNIT_W, UNIT_WH, UNIT_KWH, UNIT_HZ, UNIT_C, UNIT_PCT, UNIT_VAR, UNIT_NONE}; -//const char* const units[] = {" V", " A", " W", " Wh", " kWh", " Hz", " °C", " %", " var", ""}; -const char PGM_units[][6] PROGMEM {" V ", " A ", " W ", " Wh ", " kWh ", " Hz ", " °C ", " % ", " var ", " "}; //one hidden byte needed at the end for '\0' - -// field types -enum {FLD_UDC = 0, FLD_IDC, FLD_PDC, FLD_YD, FLD_YW, FLD_YT, - FLD_UAC, FLD_IAC, FLD_PAC, FLD_F, FLD_T, FLD_PF, FLD_EFF, - FLD_IRR, FLD_Q, FLD_EVT, FLD_FW_VERSION, FLD_FW_BUILD_YEAR, - FLD_FW_BUILD_MONTH_DAY, FLD_FW_BUILD_HOUR_MINUTE, FLD_HW_ID, - FLD_ACT_ACTIVE_PWR_LIMIT, /*FLD_ACT_REACTIVE_PWR_LIMIT, FLD_ACT_PF,*/ FLD_LAST_ALARM_CODE}; - -//PGM Flash memory usage instead of RAM for ARDUINO NANO, idea given by Nick Gammon's great webpage http://gammon.com.au/progmem -const char PGM_fields[/*NUM ELEMENTS*/][25] PROGMEM { - {"U_DC"}, - {"I_DC"}, - {"P_DC"}, - {"YieldDay"}, - {"YieldWeek"}, - {"YieldTotal"}, - {"U_AC"}, - {"I_AC"}, - {"P_AC"}, - {"F_AC"}, - {"Temp"}, - {"PF_AC"}, - {"Efficiency"}, - {"Irradiation"}, - {"Q_AC"}, - {"ALARM_MES_ID"}, - {"FWVersion"}, - {"FWBuildYear"}, - {"FWBuildMonthDay"}, - {"FWBuildHourMinute"}, - {"HWPartId"}, - {"active PowerLimit"}, - /* {"reactive PowerLimit"}, - {"Powerfactor"}*/ - {"LastAlarmCode"}, -}; - - -// const char* const fields[] = {"U_DC", "I_DC", "P_DC", "YieldDay", "YieldWeek", "YieldTotal", -// "U_AC", "I_AC", "P_AC", "F_AC", "Temp", "PF_AC", "Efficiency", "Irradiation","Q_AC", -// "ALARM_MES_ID","FWVersion","FWBuildYear","FWBuildMonthDay","FWBuildHourMinute","HWPartId", -// "active PowerLimit", /*"reactive PowerLimit","Powerfactor",*/ "LastAlarmCode"}; - -const char* const notAvail = "n/a"; - -// mqtt discovery device classes -enum {DEVICE_CLS_NONE = 0, DEVICE_CLS_CURRENT, DEVICE_CLS_ENERGY, DEVICE_CLS_PWR, DEVICE_CLS_VOLTAGE, DEVICE_CLS_FREQ, DEVICE_CLS_TEMP}; -const char* const deviceClasses[] = {0, "current", "energy", "power", "voltage", "frequency", "temperature"}; -enum {STATE_CLS_NONE = 0, STATE_CLS_MEASUREMENT, STATE_CLS_TOTAL_INCREASING}; -const char* const stateClasses[] = {0, "measurement", "total_increasing"}; -typedef struct { - uint8_t fieldId; // field id - uint8_t deviceClsId; // device class - uint8_t stateClsId; // state class -} byteAssign_fieldDeviceClass; -const byteAssign_fieldDeviceClass deviceFieldAssignment[] = { - {FLD_UDC, DEVICE_CLS_VOLTAGE, STATE_CLS_MEASUREMENT}, - {FLD_IDC, DEVICE_CLS_CURRENT, STATE_CLS_MEASUREMENT}, - {FLD_PDC, DEVICE_CLS_PWR, STATE_CLS_MEASUREMENT}, - {FLD_YD, DEVICE_CLS_ENERGY, STATE_CLS_TOTAL_INCREASING}, - {FLD_YW, DEVICE_CLS_ENERGY, STATE_CLS_TOTAL_INCREASING}, - {FLD_YT, DEVICE_CLS_ENERGY, STATE_CLS_TOTAL_INCREASING}, - {FLD_UAC, DEVICE_CLS_VOLTAGE, STATE_CLS_MEASUREMENT}, - {FLD_IAC, DEVICE_CLS_CURRENT, STATE_CLS_MEASUREMENT}, - {FLD_PAC, DEVICE_CLS_PWR, STATE_CLS_MEASUREMENT}, - {FLD_F, DEVICE_CLS_FREQ, STATE_CLS_NONE}, - {FLD_T, DEVICE_CLS_TEMP, STATE_CLS_MEASUREMENT}, - {FLD_PF, DEVICE_CLS_NONE, STATE_CLS_NONE}, - {FLD_EFF, DEVICE_CLS_NONE, STATE_CLS_NONE}, - {FLD_IRR, DEVICE_CLS_NONE, STATE_CLS_NONE} -}; -#define DEVICE_CLS_ASSIGN_LIST_LEN (sizeof(deviceFieldAssignment) / sizeof(byteAssign_fieldDeviceClass)) - -// indices to calculation functions, defined in hmInverter.h -enum {CALC_YT_CH0 = 0, CALC_YD_CH0, CALC_UDC_CH, CALC_PDC_CH0, CALC_EFF_CH0, CALC_IRR_CH}; -enum {CMD_CALC = 0xffff}; - - -// CH0 is default channel (freq, ac, temp) -enum {CH0 = 0, CH1, CH2, CH3, CH4}; - -enum {INV_TYPE_1CH = 0, INV_TYPE_2CH, INV_TYPE_4CH}; - - -typedef struct { - uint8_t fieldId; // field id - uint8_t unitId; // uint id - uint8_t ch; // channel 0 - 4 - uint8_t start; // pos of first byte in buffer - uint8_t num; // number of bytes in buffer - uint16_t div; // divisor / calc command -} byteAssign_t; - - -/** - * indices are built for the buffer starting with cmd-id in first byte - * (complete payload in buffer) - * */ - -//------------------------------------- -// HM-Series -//------------------------------------- -const byteAssign_t InfoAssignment[] = { - { FLD_FW_VERSION, UNIT_NONE, CH0, 0, 2, 1 }, - { FLD_FW_BUILD_YEAR, UNIT_NONE, CH0, 2, 2, 1 }, - { FLD_FW_BUILD_MONTH_DAY, UNIT_NONE, CH0, 4, 2, 1 }, - { FLD_FW_BUILD_HOUR_MINUTE, UNIT_NONE, CH0, 6, 2, 1 }, - { FLD_HW_ID, UNIT_NONE, CH0, 8, 2, 1 } -}; -#define HMINFO_LIST_LEN (sizeof(InfoAssignment) / sizeof(byteAssign_t)) -#define HMINFO_PAYLOAD_LEN 14 - -const byteAssign_t SystemConfigParaAssignment[] = { - { FLD_ACT_ACTIVE_PWR_LIMIT, UNIT_PCT, CH0, 2, 2, 10 }/*, - { FLD_ACT_REACTIVE_PWR_LIMIT, UNIT_PCT, CH0, 4, 2, 10 }, - { FLD_ACT_PF, UNIT_NONE, CH0, 6, 2, 1000 }*/ -}; -#define HMSYSTEM_LIST_LEN (sizeof(SystemConfigParaAssignment) / sizeof(byteAssign_t)) -#define HMSYSTEM_PAYLOAD_LEN 14 - -const byteAssign_t AlarmDataAssignment[] = { - { FLD_LAST_ALARM_CODE, UNIT_NONE, CH0, 0, 2, 1 } -}; -#define HMALARMDATA_LIST_LEN (sizeof(AlarmDataAssignment) / sizeof(byteAssign_t)) -#define HMALARMDATA_PAYLOAD_LEN 0 // 0: means check is off - - - -//------------------------------------- -// HM300, HM350, HM400 -//------------------------------------- -const byteAssign_t hm1chAssignment[] = { - { FLD_UDC, UNIT_V, CH1, 2, 2, 10 }, - { FLD_IDC, UNIT_A, CH1, 4, 2, 100 }, - { FLD_PDC, UNIT_W, CH1, 6, 2, 10 }, - { FLD_YD, UNIT_WH, CH1, 12, 2, 1 }, - { FLD_YT, UNIT_KWH, CH1, 8, 4, 1000 }, - { FLD_IRR, UNIT_PCT, CH1, CALC_IRR_CH, CH1, CMD_CALC }, - - { FLD_UAC, UNIT_V, CH0, 14, 2, 10 }, - { FLD_IAC, UNIT_A, CH0, 22, 2, 100 }, - { FLD_PAC, UNIT_W, CH0, 18, 2, 10 }, - { FLD_Q, UNIT_VAR, CH0, 20, 2, 10 }, - { FLD_F, UNIT_HZ, CH0, 16, 2, 100 }, - { FLD_PF, UNIT_NONE, CH0, 24, 2, 1000 }, - { FLD_T, UNIT_C, CH0, 26, 2, 10 }, - { FLD_EVT, UNIT_NONE, CH0, 28, 2, 1 }, - { FLD_YD, UNIT_WH, CH0, CALC_YD_CH0, 0, CMD_CALC }, - { FLD_YT, UNIT_KWH, CH0, CALC_YT_CH0, 0, CMD_CALC }, - { FLD_PDC, UNIT_W, CH0, CALC_PDC_CH0, 0, CMD_CALC }, - { FLD_EFF, UNIT_PCT, CH0, CALC_EFF_CH0, 0, CMD_CALC } -}; -#define HM1CH_LIST_LEN (sizeof(hm1chAssignment) / sizeof(byteAssign_t)) -#define HM1CH_PAYLOAD_LEN 30 - - -//------------------------------------- -// HM600, HM700, HM800 -//------------------------------------- -const byteAssign_t hm2chAssignment[] = { - { FLD_UDC, UNIT_V, CH1, 2, 2, 10 }, - { FLD_IDC, UNIT_A, CH1, 4, 2, 100 }, - { FLD_PDC, UNIT_W, CH1, 6, 2, 10 }, - { FLD_YD, UNIT_WH, CH1, 22, 2, 1 }, - { FLD_YT, UNIT_KWH, CH1, 14, 4, 1000 }, - { FLD_IRR, UNIT_PCT, CH1, CALC_IRR_CH, CH1, CMD_CALC }, - - { FLD_UDC, UNIT_V, CH2, 8, 2, 10 }, - { FLD_IDC, UNIT_A, CH2, 10, 2, 100 }, - { FLD_PDC, UNIT_W, CH2, 12, 2, 10 }, - { FLD_YD, UNIT_WH, CH2, 24, 2, 1 }, - { FLD_YT, UNIT_KWH, CH2, 18, 4, 1000 }, - { FLD_IRR, UNIT_PCT, CH2, CALC_IRR_CH, CH2, CMD_CALC }, - - { FLD_UAC, UNIT_V, CH0, 26, 2, 10 }, - { FLD_IAC, UNIT_A, CH0, 34, 2, 100 }, - { FLD_PAC, UNIT_W, CH0, 30, 2, 10 }, - { FLD_Q, UNIT_VAR, CH0, 32, 2, 10 }, - { FLD_F, UNIT_HZ, CH0, 28, 2, 100 }, - { FLD_PF, UNIT_NONE, CH0, 36, 2, 1000 }, - { FLD_T, UNIT_C, CH0, 38, 2, 10 }, - { FLD_EVT, UNIT_NONE, CH0, 40, 2, 1 }, - { FLD_YD, UNIT_WH, CH0, CALC_YD_CH0, 0, CMD_CALC }, - { FLD_YT, UNIT_KWH, CH0, CALC_YT_CH0, 0, CMD_CALC }, - { FLD_PDC, UNIT_W, CH0, CALC_PDC_CH0, 0, CMD_CALC }, - { FLD_EFF, UNIT_PCT, CH0, CALC_EFF_CH0, 0, CMD_CALC } - -}; -#define HM2CH_LIST_LEN (sizeof(hm2chAssignment) / sizeof(byteAssign_t)) -#define HM2CH_PAYLOAD_LEN 42 - - -//------------------------------------- -// HM1200, HM1500 -//------------------------------------- -const byteAssign_t hm4chAssignment[] = { - { FLD_UDC, UNIT_V, CH1, 2, 2, 10 }, - { FLD_IDC, UNIT_A, CH1, 4, 2, 100 }, - { FLD_PDC, UNIT_W, CH1, 8, 2, 10 }, - { FLD_YD, UNIT_WH, CH1, 20, 2, 1 }, - { FLD_YT, UNIT_KWH, CH1, 12, 4, 1000 }, - { FLD_IRR, UNIT_PCT, CH1, CALC_IRR_CH, CH1, CMD_CALC }, - - { FLD_UDC, UNIT_V, CH2, CALC_UDC_CH, CH1, CMD_CALC }, - { FLD_IDC, UNIT_A, CH2, 6, 2, 100 }, - { FLD_PDC, UNIT_W, CH2, 10, 2, 10 }, - { FLD_YD, UNIT_WH, CH2, 22, 2, 1 }, - { FLD_YT, UNIT_KWH, CH2, 16, 4, 1000 }, - { FLD_IRR, UNIT_PCT, CH2, CALC_IRR_CH, CH2, CMD_CALC }, - - { FLD_UDC, UNIT_V, CH3, 24, 2, 10 }, - { FLD_IDC, UNIT_A, CH3, 26, 2, 100 }, - { FLD_PDC, UNIT_W, CH3, 30, 2, 10 }, - { FLD_YD, UNIT_WH, CH3, 42, 2, 1 }, - { FLD_YT, UNIT_KWH, CH3, 34, 4, 1000 }, - { FLD_IRR, UNIT_PCT, CH3, CALC_IRR_CH, CH3, CMD_CALC }, - - { FLD_UDC, UNIT_V, CH4, CALC_UDC_CH, CH3, CMD_CALC }, - { FLD_IDC, UNIT_A, CH4, 28, 2, 100 }, - { FLD_PDC, UNIT_W, CH4, 32, 2, 10 }, - { FLD_YD, UNIT_WH, CH4, 44, 2, 1 }, - { FLD_YT, UNIT_KWH, CH4, 38, 4, 1000 }, - { FLD_IRR, UNIT_PCT, CH4, CALC_IRR_CH, CH4, CMD_CALC }, - - { FLD_UAC, UNIT_V, CH0, 46, 2, 10 }, - { FLD_IAC, UNIT_A, CH0, 54, 2, 100 }, - { FLD_PAC, UNIT_W, CH0, 50, 2, 10 }, - { FLD_Q, UNIT_VAR, CH0, 52, 2, 10 }, - { FLD_F, UNIT_HZ, CH0, 48, 2, 100 }, - { FLD_PF, UNIT_NONE, CH0, 56, 2, 1000 }, - { FLD_T, UNIT_C, CH0, 58, 2, 10 }, - { FLD_EVT, UNIT_NONE, CH0, 60, 2, 1 }, - { FLD_YD, UNIT_WH, CH0, CALC_YD_CH0, 0, CMD_CALC }, - { FLD_YT, UNIT_KWH, CH0, CALC_YT_CH0, 0, CMD_CALC }, - { FLD_PDC, UNIT_W, CH0, CALC_PDC_CH0, 0, CMD_CALC }, - { FLD_EFF, UNIT_PCT, CH0, CALC_EFF_CH0, 0, CMD_CALC } -}; -#define HM4CH_LIST_LEN (sizeof(hm4chAssignment) / sizeof(byteAssign_t)) -#define HM4CH_PAYLOAD_LEN 62 - - - - - -#endif /*__HM_DEFINES_H__*/ diff --git a/tools/nano/AhoyUL/src/hmRadio.h b/tools/nano/AhoyUL/src/hmRadio.h index d86e4c89..9c6357de 100644 --- a/tools/nano/AhoyUL/src/hmRadio.h +++ b/tools/nano/AhoyUL/src/hmRadio.h @@ -6,6 +6,7 @@ // 2022 mb modified for AHOY-UL (Hoymiles Arduino Nano, USB light IF) // - RF handling and function sendPacket_raw() without automatic channel increment // - loop() saves new packet fragments only to save space +// 2023 mb: add parameter inverter-ix to each send-function, to be able to handle the Tx/Rx channel per inverter, otherwise sync needed after each inv switch #ifndef __RADIO_H__ #define __RADIO_H__ @@ -61,10 +62,11 @@ template full()) { _status = true; - p = mBufCtrl->getFront(); // init pointer address with circular buffer space - p->rfch = mRxCh; + p = mBufCtrl->getFront(); // init pointer address with circular buffer space + p->rfch = mRxCh; // save the rx-channel in packet-struct p->plen = mNrf24.getPayloadSize(); // p->plen = mNrf24.getDynamicPayloadSize(); //is not the real RF payload fragment (packet) length, calculate later if (p->plen < 0) { @@ -209,16 +215,16 @@ class HmRadio { mIrqRcvd = true; } - uint8_t setDefaultChannels(void) { - // DPRINTLN(DBG_VERBOSE, F("hmRadio.h:setDefaultChannels")); - mTxChIdx = 2; // Start TX with 40 - mRxChIdx = 4; // Start RX with 75 - return mRfChLst[mTxChIdx]; - } + // uint8_t setDefaultChannels(void) { + // // DPRINTLN(DBG_VERBOSE, F("hmRadio.h:setDefaultChannels")); + // mTxChIdx[] = 2; // Start TX with 40 + // mRxChIdx = 4; // Start RX with 75 + // return mRfChLst[mTxChIdx]; + // } - void sendControlPacket(uint8_t *_radio_id, uint8_t cmd, uint16_t *data) { + void sendControlPacket(uint8_t *_radio_id, uint8_t _inv_ix, uint8_t cmd, uint16_t *data) { //DPRINTLN(DBG_INFO, F("hmRadio:sendControlPacket")); - sendCmdPacket(_radio_id, TX_REQ_DEVCONTROL, ALL_FRAMES, false); // 0x80 implementation as original DTU code + sendCmdPacket(_radio_id, _inv_ix, TX_REQ_DEVCONTROL, ALL_FRAMES, false); // 0x80 implementation as original DTU code int cnt = 0; mTxBuf[10] = cmd; // cmd --> 0x0b => Type_ActivePowerContr, 0 on, 1 off, 2 restart, 12 reactive power, 13 power factor mTxBuf[10 + (++cnt)] = 0x00; @@ -236,12 +242,12 @@ class HmRadio { cnt += 1; mTxBuf[10 + cnt] = Hoymiles::crc8(mTxBuf, 10 + cnt); - sendPacket(_radio_id, mTxBuf, 10 + (++cnt), true); + sendPacket(_radio_id, _inv_ix, mTxBuf, 10 + (++cnt), true); } - void sendTimePacket(uint8_t *_radio_id, uint8_t cmd, uint32_t ts, uint16_t alarmMesId) { + void sendTimePacket(uint8_t *_radio_id, uint8_t _inv_ix, uint8_t cmd, uint32_t ts, uint16_t alarmMesId) { // DPRINTLN(DBG_VERBOSE, F("hmRadio.h:sendTimePacket")); - sendCmdPacket(_radio_id, TX_REQ_INFO, ALL_FRAMES, false); + sendCmdPacket(_radio_id, _inv_ix, TX_REQ_INFO, ALL_FRAMES, false); mTxBuf[10] = cmd; // cid mTxBuf[11] = 0x00; CP_U32_LittleEndian(&mTxBuf[12], ts); //?? adapt for atmega/nano @@ -259,10 +265,10 @@ class HmRadio { mTxBuf[25] = (crc)&0xff; mTxBuf[26] = Hoymiles::crc8(mTxBuf, 26); - sendPacket(_radio_id, mTxBuf, 27, true); + sendPacket(_radio_id, _inv_ix, mTxBuf, 27, true); } - void sendCmdPacket(uint8_t *_radio_id, uint8_t mid, uint8_t pid, bool calcCrc = true) { + void sendCmdPacket(uint8_t *_radio_id, uint8_t _inv_ix, uint8_t mid, uint8_t pid, bool calcCrc = true) { // DPRINTLN(DBG_VERBOSE, F("hmRadio.h:sendCmdPacket")); memset(mTxBuf, 0, MAX_RF_PAYLOAD_SIZE); mTxBuf[0] = mid; // message id @@ -272,7 +278,7 @@ class HmRadio { mTxBuf[9] = pid; if (calcCrc) { mTxBuf[10] = Hoymiles::crc8(mTxBuf, 10); - sendPacket(_radio_id, mTxBuf, 11, false); + sendPacket(_radio_id, _inv_ix, mTxBuf, 11, false); } } @@ -303,21 +309,21 @@ class HmRadio { return valid; } - bool switchRxCh(uint16_t addLoop = 0) { - DPRINT(DBG_DEBUG, F("hmRadio.h:switchRxCh: try")); - // DPRINTLN(DBG_VERBOSE, F("R")); - - mRxLoopCnt += addLoop; - if (mRxLoopCnt != 0) { - mRxLoopCnt--; - DISABLE_IRQ; - mNrf24.stopListening(); - mNrf24.setChannel(getRxNxtChannel()); - mNrf24.startListening(); - RESTORE_IRQ; - } - return (0 == mRxLoopCnt); // receive finished - } + // bool switchRxCh(uint16_t addLoop = 0) { + // DPRINT(DBG_DEBUG, F("hmRadio.h:switchRxCh: try")); + // // DPRINTLN(DBG_VERBOSE, F("R")); + + // mRxLoopCnt += addLoop; + // if (mRxLoopCnt != 0) { + // mRxLoopCnt--; + // DISABLE_IRQ; + // mNrf24.stopListening(); + // mNrf24.setChannel(getRxNxtChannel()); + // mNrf24.startListening(); + // RESTORE_IRQ; + // } + // return (0 == mRxLoopCnt); // receive finished + // } /** * Hex string output of uint8_t array @@ -412,12 +418,12 @@ class HmRadio { uint8_t ix; ix = getChanIdx(_txch); ix = (ix + 2) % RF_CHANNELS; - return mRfChLst[mRxChIdx]; + return mRfChLst[ix]; } - uint8_t getRxChan() { - return mRfChLst[mRxChIdx]; - } + // uint8_t getRxChan() { + // return mRfChLst[mRxChIdx]; + // } uint8_t getChanIdx(uint8_t _channel) { // uses global channel list @@ -428,8 +434,9 @@ class HmRadio { return ix; } + //saves the RxChIdx for inverter-ix zero void setRxChanIdx(uint8_t ix) { - mRxChIdx = ix; + mRxChIdx[0] = ix; } void print_radio_details() { @@ -456,18 +463,18 @@ class HmRadio { /** * That is the actual send function */ - bool sendPacket(uint8_t *_radio_id, uint8_t buf[], uint8_t len, bool clear = false, bool doSend = true) { + bool sendPacket(uint8_t *_radio_id, uint8_t _inv_ix, uint8_t buf[], uint8_t len, bool clear = false, bool doSend = true) { // DPRINT(DBG_DEBUG, F("hmRadio.h:sendPacket")); //bool _tx_ok = 0; // could also query the num of tx retries as uplink quality indication, no lib interface seen yet uint8_t _arc = 0; - // DPRINTLN(DBG_VERBOSE, "sent packet: #" + String(mSendCnt)); // dumpBuf("SEN ", buf, len); + mTxCh = mRfChLst[mTxChIdx[_inv_ix]]; if (mSerialDebug) { DPRINT(DBG_INFO, F("TX Ch")); - if (mRfChLst[mTxChIdx] < 10) _DPRINT(DBG_INFO, F("0")); - _DPRINT(DBG_INFO, mRfChLst[mTxChIdx]); + if (mTxCh < 10) _DPRINT(DBG_INFO, F("0")); + _DPRINT(DBG_INFO, mTxCh); _DPRINT(DBG_INFO, F(" ")); _DPRINT(DBG_INFO, len); _DPRINT(DBG_INFO, F("B | ")); @@ -477,16 +484,15 @@ class HmRadio { dumpBuf(DBG_VERBOSE, NULL, _radio_id, 5); // DPRINT(DBG_INFO, "TX iv-ID 0x"); // DHEX((uint32_t)(invId>>32)); Serial.print(F(" ")); DHEX((uint32_t)(invId)); - delay(20); // wait serial data sending before RF transmission, otherwise missing first RF packet + delay(20); // wait serial data sending before RF transmission, otherwise missing first RF packet } DISABLE_IRQ; mNrf24.stopListening(); if (clear) mRxLoopCnt = RF_LOOP_CNT; - mNrf24.setChannel(mRfChLst[mTxChIdx]); - mTxCh = getTxNxtChannel(); // prepare Tx channel for next packet - + mNrf24.setChannel(mTxCh); // set Tx channel in nRF24 + // mTxCh = getTxNxtChannel(_inv_ix); // prepare Tx channel for next packet // mNrf24.openWritingPipe(invId); // TODO: deprecated, !!!! invID must be given in big endian uint8_t* mNrf24.openWritingPipe(_radio_id); mNrf24.setCRCLength(RF24_CRC_16); @@ -494,13 +500,13 @@ class HmRadio { mNrf24.setAutoAck(true); mNrf24.setRetries(3, 15); // 3*250us and 15 loops -> 11.25ms, I guess Hoymiles has disabled autoack, thus always max loops send if (doSend) mNrf24.write(buf, len); // only send in case of send-flag true, _tx_ok seems to be always false - //_arc = mNrf24.getARC(); // is always 15, hoymiles receiver might have autoack=false - + //_arc = mNrf24.getARC(); // is always 15, hoymiles receiver might have autoack=false // Try to avoid zero payload acks (has no effect) - mNrf24.openWritingPipe(DUMMY_RADIO_ID); // TODO: why dummy radio id?, deprecated - // mRxChIdx = 0; - mNrf24.setChannel(mRfChLst[mRxChIdx]); // switch to Rx channel that matches to Tx channel - mRxCh = mRfChLst[mRxChIdx]; + mNrf24.openWritingPipe(DUMMY_RADIO_ID); // TODO: why dummy radio id?, deprecated + + // prepare Rx + mRxCh = mRfChLst[mRxChIdx[_inv_ix]]; // switch to Rx channel that matches to last Tx channel + mNrf24.setChannel(mRxCh); mNrf24.setAutoAck(false); mNrf24.setRetries(0, 0); mNrf24.disableDynamicPayloads(); @@ -510,42 +516,46 @@ class HmRadio { if (mSerialDebug) { //_DPRINT(DBG_INFO, F(" ->ARC ")); _DPRINT(DBG_INFO, _arc); - DPRINT(DBG_VERBOSE, "RX Ch"); - if (mRfChLst[mRxChIdx] < 10) _DPRINT(DBG_VERBOSE, F("0")); - _DPRINT(DBG_VERBOSE, mRfChLst[mRxChIdx]); + if (mRxCh < 10) _DPRINT(DBG_VERBOSE, F("0")); + _DPRINT(DBG_VERBOSE, mRxCh); _DPRINT(DBG_VERBOSE, F(" wait")); - getRxNxtChannel(); // prepare Rx channel for next packet + //getRxNxtChannel(); // prepare Rx channel for next packet } + incr_mTxRxChIdx(_inv_ix); mSendCnt++; return true; } - uint8_t getTxNxtChannel(void) { - if (++mTxChIdx >= RF_CHANNELS) - mTxChIdx = 0; - // DPRINT(DBG_DEBUG, F("next TX Ch")); - //_DPRINT(DBG_DEBUG, mRfChLst[mTxChIdx]); - return mRfChLst[mTxChIdx]; - } - - uint8_t getRxNxtChannel(void) { - if (++mRxChIdx >= RF_CHANNELS) - mRxChIdx = 0; - // PRINT(DBG_DEBUG, F("next RX Ch")); - //_DPRINT(DBG_DEBUG, mRfChLst[mRxChIdx]); - return mRfChLst[mRxChIdx]; - } - - void setChanIdx(uint8_t _txch) { - mTxChIdx = getChanIdx(_txch); - mRxChIdx = (mTxChIdx + 2) % RF_CHANNELS; + void incr_mTxRxChIdx(uint8_t _inv_ix) { + if (++mTxChIdx[_inv_ix] >= RF_CHANNELS) + mTxChIdx[_inv_ix] = 0; + if (++mRxChIdx[_inv_ix] >= RF_CHANNELS) + mRxChIdx[_inv_ix] = 0; + return; } - uint8_t getRxChannel(void) { - return mRxCh; - } + // uint8_t getTxNxtChannel(void) { + // if (++mTxChIdx >= RF_CHANNELS) + // mTxChIdx = 0; + // // DPRINT(DBG_DEBUG, F("next TX Ch")); + // //_DPRINT(DBG_DEBUG, mRfChLst[mTxChIdx]); + // return mRfChLst[mTxChIdx]; + // } + + // uint8_t getRxNxtChannel(void) { + // if (++mRxChIdx >= RF_CHANNELS) + // mRxChIdx = 0; + // // PRINT(DBG_DEBUG, F("next RX Ch")); + // //_DPRINT(DBG_DEBUG, mRfChLst[mRxChIdx]); + // return mRfChLst[mRxChIdx]; + // } + + // void setChanIdx(uint8_t _txch) { + // mTxChIdx = getChanIdx(_txch); + // mRxChIdx = (mTxChIdx + 2) % RF_CHANNELS; + // } }; // end class diff --git a/tools/nano/AhoyUL/src/main.cpp b/tools/nano/AhoyUL/src/main.cpp index b73ef0b2..b5857858 100644 --- a/tools/nano/AhoyUL/src/main.cpp +++ b/tools/nano/AhoyUL/src/main.cpp @@ -24,9 +24,10 @@ #include "config.h" #include "dbg.h" #include "defines.h" -#include "hmDefines.h" +// #include "hmDefines.h" +#include "SerialUtils.h" +#include "hmDecoder.h" #include "hmRadio.h" -#include "utils_serial.h" typedef CircularBuffer BufferType; typedef HmRadio RadioType; @@ -36,7 +37,7 @@ typedef HmRadio RadioType; // declaration of functions static void resetSystem(void); static void loadDefaultConfig(config_t *); -void mSwitchCasesSer(char); +void mHandleCases_of_SerialInput(char); // utility function static int availableMemory(void); static void swap_bytes(uint8_t *, uint32_t); @@ -55,7 +56,7 @@ static bool resetPayload(invPayload_t *); static bool out_uart_smac_resp_OK(invPayload_t *); static bool out_uart_smac_resp_ERR(invPayload_t *); static uint8_t collect_and_return_userPayload(invPayload_t *, uint8_t *, uint8_t); -static void decode_userPayload(uint8_t, uint8_t *, uint8_t, uint32_t, char *, uint8_t, uint16_t, uint32_t); +static void decode_InfoREQResp(uint8_t, uint8_t *, uint8_t, uint32_t, char *, uint8_t, uint16_t, uint32_t); // in file hmDecoder.h, class did not work with PROGMEM // interrupt handler static void handleIntr(void); @@ -86,7 +87,9 @@ static RadioType hmRadio; // static uint8_t radio_id[5]; //todo: use the mPayload[].id field ,this defines the radio-id (domain) of the rf24 transmission, will be derived from inverter id // static uint64_t radio_id64 = 0ULL; -#define DEF_VERSION "\n version 2023-01-27 22:42" +// static HmDecoder Decoder; + +#define DEF_VERSION "\n version 2023-03-25 23:35" #define P(x) (__FlashStringHelper *)(x) // PROGMEM-Makro for variables static const char COMPILE_DATE[] PROGMEM = {__DATE__}; static const char COMPILE_TIME[] PROGMEM = {__TIME__}; @@ -108,30 +111,32 @@ static uint8_t rxch; // keeps the current RX channel // volatile static uint32_t current_millis = 0; static volatile uint32_t timer1_millis = 0L; // general loop timer -static volatile uint32_t timer2_millis = 0L; // send Request timer -static volatile uint32_t lastRx_millis = 0L; // last valid Rx packet fragment timer -static volatile uint32_t tcmd_millis = 0L; // timer for smac-cmd start +static volatile uint32_t timer2_millis[MAX_NUM_INVERTERS]; // send Request timer +static volatile uint32_t lastRx_millis[MAX_NUM_INVERTERS]; // last valid Rx packet fragment timer +static volatile uint32_t tcmd_millis = 0L; // timer for smac-cmd start +static volatile uint32_t lastInvChange_millis = 0L; // timer for last inverter change #define ONE_SECOND (1000L) static uint8_t c_loop = 0; -static volatile int m_sread_len = 0; // UART read length +static volatile int m_sread_len = 0; // UART read length // static volatile bool rxRdy = false; //will be set true on first receive packet during sending interval, reset to false before sending static uint8_t m_inv_ix = 0; static bool payload_used[MAX_NUM_INVERTERS]; // static bool saveMACPacket = false; // when true the the whole MAC packet with address is kept, remove the MAC for CRC calc -static bool automode = true; // defines automode -static bool sendNow = false; // defines whether Inverter Request-command shall be send immediatly -static bool doDecode = true; // controls basic decoding -static bool showMAC = true; // controls whether rMAC response is shown -static bool smac_send = false; // flag indicates that sMAC-cmd was send +static bool automode = true; // defines automode +static bool sendNow = false; // defines whether Inverter Request-command shall be send immediatly +static bool doDecode = true; // controls basic decoding +static bool showMAC = true; // controls whether rMAC response is shown +static bool smac_send = false; // flag indicates that sMAC-cmd was send static volatile uint16_t tmp16 = 0; static uint8_t tmp8 = 0; static uint8_t tmp81 = 0; static uint32_t tmp32 = 0; // static uint32_t min_SEND_SYNC_msec = MIN_SEND_INTERVAL_ms; -static uint8_t mCountdown_noSignal = SEND_REPEAT; // counter down of number of Inverter Requests if no response received, in dark or no signal -static volatile uint32_t mSendInterval_ms = SEND_NOSIGNAL_SHORT * ONE_SECOND; -static volatile uint32_t polling_req_msec = SEND_INTERVAL * ONE_SECOND; // will be set via serial interface +static uint8_t mCountdown_noSignal[MAX_NUM_INVERTERS]; // counter down of number of Inverter Requests if no response received, in dark or no signal +static volatile uint32_t mSendInterval_ms[MAX_NUM_INVERTERS]; +static volatile uint32_t polling_req_msec[MAX_NUM_INVERTERS]; // will be set via serial interface +static volatile boolean m_next_invIX = false; //after successful receiving of one inverter switch to the next static bool iv_devControlReq = false; // this is one kind of requests, see devControlCmds static uint8_t iv_devControlCmd = ActivePowerContr; // for now the only devControlCmd @@ -176,6 +181,13 @@ void setup() { mPayload[0].invId[0] = (uint8_t)0x01; swap_bytes(&mPayload[0].invId[1], (uint32_t)IV1_RADIO_ID); // high byte is at lowest index mPayload[0].invType = (uint16_t)(IV1_RADIO_ID >> 32); // keep just upper 6 and 5th byte (e.g.0x1141) of interter plate id for type + + //2nd inverter +#if defined(IV2_RADIO_ID) + mPayload[1].invId[0] = (uint8_t)0x01; + swap_bytes(&mPayload[1].invId[1], (uint32_t)IV2_RADIO_ID); // high byte is at lowest index + mPayload[1].invType = (uint16_t)(IV2_RADIO_ID >> 32); // keep just upper 6 and 5th byte (e.g.0x1141) of interter plate id for type +#endif // hmRadio.dumpBuf("\nsetup InvID ", &mPayload[0].invId[0], 5, DBG_DEBUG); @@ -184,6 +196,14 @@ void setup() { // todo: load Inverter decoder depending on InvID + //some variables per inverter index + for (m_inv_ix = 0; m_inv_ix < MAX_NUM_INVERTERS; m_inv_ix++) { + timer2_millis[m_inv_ix] = 0L; + lastRx_millis[m_inv_ix] = 0L; + mCountdown_noSignal[m_inv_ix] = SEND_REPEAT; + mSendInterval_ms[m_inv_ix] = SEND_NOSIGNAL_SHORT * ONE_SECOND; + polling_req_msec[m_inv_ix] = SEND_INTERVAL * ONE_SECOND; + } m_inv_ix = 0; // first inverter index // global var for all inverter @@ -202,6 +222,8 @@ void setup() { void loop() { // the inverter id/ix I shall be handled in a loop if more than one inverter is registered for periodic polling + + // counting the time reference of seconds if (millis() - timer1_millis >= ONE_SECOND) { timer1_millis = millis(); @@ -220,41 +242,64 @@ void loop() { if (Serial.available()) { // wait char inSer = Serial.read(); - mSwitchCasesSer(inSer); - } // end if serial... + mHandleCases_of_SerialInput(inSer); + } // end if serial... // automode RF-Tx-trigger if (automode) { + + // increment the the inverter-index + if ( m_next_invIX || (millis() > INVERTER_TIMEOUT_sec * 1000 && (millis() - lastInvChange_millis) > INVERTER_TIMEOUT_sec * 1000)) { + lastInvChange_millis = millis(); + m_next_invIX = false; + DPRINT(DBG_INFO, F("Change inverter index to ")); + tmp8 = m_inv_ix; //save last valid ix value + m_inv_ix++; + if (m_inv_ix >= MAX_NUM_INVERTERS) { + m_inv_ix = 0; + } + + if (mPayload[m_inv_ix].invType == 0x0000) { + //inverter index is invalid + m_inv_ix = tmp8; + + } else { + //is changed inverter index, make init for new inverter + + } + _DPRINT(DBG_INFO, m_inv_ix); + } + // slow down the sending if no Rx for long time - if (millis() < (SEND_INTERVAL * ONE_SECOND) && !lastRx_millis) { + if (millis() < (SEND_INTERVAL * ONE_SECOND) && !lastRx_millis[m_inv_ix]) { // initial fast send as long as no rx - mSendInterval_ms = (SEND_NOSIGNAL_SHORT * ONE_SECOND); + mSendInterval_ms[m_inv_ix] = (SEND_NOSIGNAL_SHORT * ONE_SECOND); - } else if (millis() - lastRx_millis > (8 * polling_req_msec)) { + } else if (millis() - lastRx_millis[m_inv_ix] > (8 * polling_req_msec[m_inv_ix])) { // no Rx in time reduce Tx polling - if (mCountdown_noSignal) { + if (mCountdown_noSignal[m_inv_ix] ) { // keep fast Tx on initial no-signal for countdown > 0 - mSendInterval_ms = (SEND_NOSIGNAL_SHORT * ONE_SECOND); + mSendInterval_ms[m_inv_ix] = (SEND_NOSIGNAL_SHORT * ONE_SECOND); } else { // large no signal period - mSendInterval_ms = (SEND_NOSIGNAL_LONG * ONE_SECOND); + mSendInterval_ms[m_inv_ix] = (SEND_NOSIGNAL_LONG * ONE_SECOND); } } else { // rx OK keep default Tx polling - mSendInterval_ms = polling_req_msec; - mCountdown_noSignal = SEND_REPEAT; + mSendInterval_ms[m_inv_ix] = polling_req_msec[m_inv_ix]; + mCountdown_noSignal[m_inv_ix] = SEND_REPEAT; } // trigger Tx if long period not send - if ((millis() - timer2_millis >= (SEND_NOSIGNAL_LONG * ONE_SECOND))) { + if ((millis() - timer2_millis[m_inv_ix] >= (SEND_NOSIGNAL_LONG * ONE_SECOND))) { // no send for long time, trigger fast send for countdown time - mCountdown_noSignal = SEND_REPEAT; + mCountdown_noSignal[m_inv_ix] = SEND_REPEAT; } // todo: add queue of cmds or schedule simple device control request for power limit values - if (sendNow || (millis() - timer2_millis > mSendInterval_ms)) { - timer2_millis = millis(); - tcmd_millis = timer2_millis; + if (sendNow || (millis() - timer2_millis[m_inv_ix] > mSendInterval_ms[m_inv_ix])) { + timer2_millis[m_inv_ix] = millis(); + tcmd_millis = timer2_millis[m_inv_ix]; smac_send |= showMAC; // DISABLE_IRQ; payload_used[m_inv_ix] = !resetPayload(&mPayload[m_inv_ix]); @@ -262,16 +307,16 @@ void loop() { mPayload[m_inv_ix].receive = false; mPayload[m_inv_ix].requested = true; // assume the previous sending is finished because of scheduled timing, therefore no check if still true sendNow = false; - if (mCountdown_noSignal) mCountdown_noSignal--; - + if (mCountdown_noSignal[m_inv_ix]) mCountdown_noSignal[m_inv_ix]--; + if (iv_devControlReq) { // send devControlReq - hmRadio.sendControlPacket(&mPayload[m_inv_ix].invId[0], iv_devControlCmd, iv_powerLimit); + hmRadio.sendControlPacket(&mPayload[m_inv_ix].invId[0], m_inv_ix, iv_devControlCmd, iv_powerLimit); iv_devControlReq = false; // todo: use timer that it is not send to quick (e.g. max once per 5sec) } else { // send regular info request - hmRadio.sendTimePacket(&mPayload[m_inv_ix].invId[0], 0x0B, mTimestamp, 0x0000); + hmRadio.sendTimePacket(&mPayload[m_inv_ix].invId[0], m_inv_ix, 0x0B, mTimestamp, 0x0000); } // RESTORE_IRQ; } @@ -284,6 +329,7 @@ void loop() { if (!packet_Buffer.empty()) { packet_t *p; p = packet_Buffer.getBack(); + if (hmRadio.checkPaketCrc(&p->data[0], &p->plen, p->rfch)) { // process buffer only on first occurrence DPRINT(DBG_INFO, F("RX Ch")); @@ -295,15 +341,18 @@ void loop() { hmRadio.dumpBuf(DBG_INFO, NULL, p->data, p->plen); // mFrameCnt++; - if (p->plen) { - // no need to get the m_inv_ix, because only desired inverter will answer, payload buffer is CRC protected - // m_inv_ix = getInvID(mPayload, MAX_NUM_INVERTERS, &p->data[0]); - if (m_inv_ix >= 0 && m_inv_ix < MAX_NUM_INVERTERS) { - if (copyPacket2Payload(&mPayload[m_inv_ix], m_inv_ix, p, mPayload[m_inv_ix].isMACPacket)) { - lastRx_millis = millis(); + // check received inv-ID per packet packet is matching with requested inv-ID (important if several inverter are queried by different data request units (DRU) like this one) + // actually seen that other inverter id packets are also received if requested by different DRUs + if (check_array(&p->data[1], &mPayload[m_inv_ix].invId[1], 4)) { + //data of received InvID matches with requested ID + if (p->plen) { + if (m_inv_ix >= 0 && m_inv_ix < MAX_NUM_INVERTERS) { + if (copyPacket2Payload(&mPayload[m_inv_ix], m_inv_ix, p, mPayload[m_inv_ix].isMACPacket)) { + lastRx_millis[m_inv_ix] = millis(); + } } - } - } // end if(plen) + } // end if(plen) + } // end check_array() } // end if(checkPacketCRC) packet_Buffer.popBack(); // remove last entry after eval and print DPRINT(DBG_DEBUG, F("Records in p_B ")); @@ -313,7 +362,7 @@ void loop() { // handle output of data, if ready, based on tx-timer // todo: make one timer for tx an rx and make shorter timeout // todo: don't call this section if listening mode only, because processPayload() triggers retransmission - if ((millis() - timer2_millis >= 450) && packet_Buffer.empty() && mPayload[m_inv_ix].requested && mPayload[m_inv_ix].receive) { + if ((millis() - timer2_millis[m_inv_ix] >= 450) && packet_Buffer.empty() && mPayload[m_inv_ix].requested && mPayload[m_inv_ix].receive) { // process payload // after 450ms all packets shall have been copied successfully to mPayload structure, run only if requested and some data received after REQ-trigger @@ -331,7 +380,9 @@ void loop() { if (tmp8 == 42 && doDecode) { tmp32 = swap_bytes(&mPayload[m_inv_ix].invId[1]); - decode_userPayload(mPayload[m_inv_ix].txId, &user_payload[0], tmp8, user_pl_ts, m_strout_p, MAX_STRING_LEN, mPayload[m_inv_ix].invType, tmp32); + decode_InfoREQResp(mPayload[m_inv_ix].txId, &user_payload[0], tmp8, user_pl_ts, m_strout_p, MAX_STRING_LEN, mPayload[m_inv_ix].invType, tmp32); + m_next_invIX = true; //enables next inverter request + // Decoder.decode_InfoREQResp(mPayload[m_inv_ix].txId, &user_payload[0], tmp8, user_pl_ts, m_strout_p, MAX_STRING_LEN, mPayload[m_inv_ix].invType, tmp32); } } } @@ -407,7 +458,7 @@ static void swap_bytes(uint8_t *_buf, uint32_t _v) { return; } -//endian change of uint32_t, works back and forth by doing twice +// endian change of uint32_t, works back and forth by doing twice static uint32_t swap_bytes(uint32_t _v) { volatile uint32_t _res; _res = ((_v >> 24) & 0x000000ff) | ((_v >> 8) & 0x0000ff00) | ((_v << 8) & 0x00ff0000) | ((_v << 24) & 0xff000000); @@ -418,25 +469,24 @@ static uint32_t swap_bytes(uint32_t _v) { static uint32_t swap_bytes(uint8_t *_inbuf) { volatile uint32_t _res = 0x00L; volatile uint8_t _i = 0; - for (_i = 0; _i<4; _i++) { + for (_i = 0; _i < 4; _i++) { _res = _res << 8; _res |= _inbuf[_i]; } return _res; } - ////////////////// handle payload function, maybe later as own class ////////////////// /** * compares the two arrays and returns true when equal */ -// static bool check_array(uint8_t *invID, uint8_t *rcvID, uint8_t _len) { -// uint8_t i; -// for (i = 0; i < _len; i++) { -// if (invID[i] != rcvID[i]) return false; -// } -// return true; -// } +static bool check_array(uint8_t *invID, uint8_t *rcvID, uint8_t _len) { + uint8_t i; + for (i = 0; i < _len; i++) { + if (invID[i] != rcvID[i]) return false; + } + return true; +} /** * gets the payload index that matches to the received ID or the first empty payload structure possition, @@ -555,7 +605,7 @@ static bool copyPacket2Payload(invPayload_t *_payload, uint8_t _invx, packet_t * } // end savePayload() /** - * saves copies one received payload-packet into inverter memory structure + * saves copies of one received payload-packet into inverter memory structure */ static bool savePayloadfragment(invPayload_t *_payload, packet_t *_p, uint8_t _pid, uint8_t _ix) { volatile bool _res = false; @@ -607,8 +657,8 @@ static bool processPayload(invPayload_t *_payload, config_t *_mconfig, bool retr _payload->retransmits++; DPRINT(DBG_DEBUG, F(" req fragment ")); _DPRINTHEX(DBG_DEBUG, (uint8_t)(_reqfragment & 0x7f)); - hmRadio.sendCmdPacket(_payload->invId, TX_REQ_INFO, _reqfragment, true); - timer2_millis = millis(); // set sending timer2 + hmRadio.sendCmdPacket(_payload->invId, m_inv_ix, TX_REQ_INFO, _reqfragment, true); + timer2_millis[m_inv_ix] = millis(); // set sending timer2 // leaves with false to quickly receive again // so far the Tx and Rx frequency is handled globally, it shall be individually per inverter ID? } @@ -623,7 +673,7 @@ static bool processPayload(invPayload_t *_payload, config_t *_mconfig, bool retr } // end processPayload() /** - * checks the paypload of all received packet-fragments via CRC16 and length + * checks the paypload of all received packet-fragments via CRC16 and length, make sure before that all fragments come from same inverter * returns: * - 0x00 == OK if all fragments received and CRC OK, * - PID + 0x81 can be used directly to request the first next missing packet @@ -681,7 +731,7 @@ static uint8_t checkPayload(invPayload_t *_payload) { _DPRINTHEX(DBG_ERROR, crc); DPRINT(DBG_ERROR, F(" crcRcv ")); _DPRINTHEX(DBG_ERROR, crcRcv); - // wrong CRC16 over all packets, must actually never happen for correct packets, must be programming bug + // wrong CRC16 over all packets, must actually never happen for correct packets, but if different inverter have answered and not sorted out the unrequested data fragments DPRINT(DBG_ERROR, F(" cPL wrong req. ")); _DPRINTHEX(DBG_ERROR, (uint8_t)(i + 0x81)); _payload->receive = false; // stop further eval @@ -708,7 +758,7 @@ static bool out_uart_smac_resp_OK(invPayload_t *_payload) { Serial.print(_payload->rxChIdx); Serial.println(F(":{")); for (uint8_t i = 0; i < (_payload->maxPackId); i++) { - //hmRadio.dumpBuf(NULL, &_payload->data[i][0], _payload->len[i]); + // hmRadio.dumpBuf(NULL, &_payload->data[i][0], _payload->len[i]); utSer.print_bytes(&_payload->data[i][0], _payload->len[i], "", true); if (i != _payload->maxPackId - 1) Serial.println(F(":")); @@ -755,335 +805,264 @@ static uint8_t collect_and_return_userPayload(invPayload_t *_payload, uint8_t *_ Serial.print(F("\nrPayload(")); Serial.print(_offs); Serial.print(F("): ")); - //hmRadio.dumpBuf(NULL, _user_payload, _offs); + // hmRadio.dumpBuf(NULL, _user_payload, _offs); utSer.print_bytes(_user_payload, _offs, "", true, ":\n"); return _offs; } // end if returnPaylout -/** - * simple decoding of 2ch HM-inverter only - */ -static void decode_userPayload(uint8_t _cmd, uint8_t *_user_payload, uint8_t _ulen, uint32_t _ts, char *_strout, uint8_t _strlen, uint16_t _invtype, uint32_t _plateID) { - volatile uint32_t _val = 0L; - - #if defined(ESP8266) - // for esp8266 environment - byteAssign_t _bp; //volatile byteAssign_t not compiling ESP8266 - #else - volatile byteAssign_t _bp; //must be volatile for Arduino-Nano, otherwise _bp.ch stays zero - #endif - - volatile uint8_t _x; - volatile uint8_t _end; - volatile float _fval; - volatile uint8_t _tmp8 = 0xff; - - //_str80[80] = '\0'; - snprintf_P(_strout, _strlen, PSTR("\ndata age: %d sec"), (millis() - _ts) / 1000); - Serial.print(_strout); - - if (_cmd == 0x95 and _ulen == 42) { - //!!! simple HM600/700/800 2ch decoding for cmd=0x95 only !!!! - - for (_x = 0; _x < HM2CH_LIST_LEN; _x++) { - // read values from given positions in payload - _bp = hm2chAssignment[_x]; - _val = 0L; - _end = _bp.start + _bp.num; - if (_tmp8 != _bp.ch) { - snprintf_P(_strout, _strlen, PSTR("\nHM800/%04X%08lX/ch%02d "), _invtype, _plateID, _bp.ch); //must be a small "l" in %08lX for Arduino-Nano - Serial.print(_strout); - // snprintf_P(_strout, _strlen, PSTR("ch%02d/"), _bp.ch); - // Serial.print(_strout); - } - _tmp8 = _bp.ch; - - if (CMD_CALC != _bp.div && _bp.div != 0) { - strncpy_P(_strout, &(PGM_fields[_bp.fieldId][0]), _strlen); - Serial.print(_strout); - Serial.print(F(": ")); - - do { - _val <<= 8; - _val |= _user_payload[_bp.start]; - } while (++_bp.start != _end); - _fval = (int)_val / (float)_bp.div; - - if (_bp.unitId == UNIT_NONE) { - Serial.print(_val); - Serial.print(F(" ")); - continue; - } - Serial.print(_fval, 2); // arduino nano does not sprintf(float values), but print() does - // Serial.print(units[_bp.unitId]); - strncpy_P(_strout, &PGM_units[_bp.unitId][0], _strlen); - Serial.print(_strout); - - } else { - // do calculations - // Serial.print(F("not yet")); - } - } // end for() - Serial.println(); - - } else { - Serial.print(F("NO DECODER ")); - Serial.print(_cmd, HEX); - } -}//end decodePayload() - - /////////////////////////////////////////////////////////// // -// handling of serial commands starting with first char _inSer, it uses many global variables to shorten function parameter handover +// handling of serial input commands starting with first char _inSer, it uses many global variables to shorten function parameter handover // -void mSwitchCasesSer(char _inSer) { +void mHandleCases_of_SerialInput(char _inSer) { switch (_inSer) { - case (char)'a': { - // enable automode with REQ polling interval via a10 => 10sec, a100 => 100sec or other range 5....3600sec - // extend automode with polling period in sec, inverter ix and id, all numerial value are optional from the back - // cmd: a[[[:]:]:<12digit invID>:] e.g. a:120:1:081511223344:, also polling only a:120: and polling and inv_ix via a:120:1 - automode = true; - m_inv_ix = 0; - uint8_t invIX = 0; - uint16_t invType = 0x4711; - uint8_t invID5[5] = {0x01, 0x44, 0x33, 0x22, 0x11}; - mParams = utSer.getParamsBuf(); - tmp8 = utSer.read_uart_cmd_param(mParams); - mCountdown_noSignal = SEND_REPEAT; //trigger sending if before no resonse - - if (tmp8 > 0) { - // get polling interval from first parameter - tmp81 = strlen(&mParams[0][0]); - if (tmp81 > 0 && tmp81 < 5) { - polling_req_msec = 1000 * utSer.uart_eval_decimal_val(F("auto poll sec "), &mParams[0][0], 5, 5, 3600, 1); - } - - if (tmp8 > 2) { - // get inverter index and inverter ID from parameter 2 and 3 - if (utSer.uart_cmd_add_inverter_parsing(&mParams[1], MAX_SER_PARAM, &invIX, &invType, &invID5[0])) { - // write to inverter structure at given index - mPayload[invIX].invType = invType; - memcpy(mPayload[invIX].invId, &invID5[0], 5); - m_inv_ix = invIX; - DPRINT(DBG_INFO, F(" OK")); - // todo: save inverter list to eeprom depending on 4th parameter (e.g ":eep:" ) - } // end if() - - } else if (tmp8 > 1) { - // try to get and set the inverter-index only - tmp81 = (uint8_t)(utSer.uart_eval_decimal_val(NULL, &mParams[1][0], tmp8, 0, MAX_NUM_INVERTERS, 1)); - if (mPayload[tmp81].invType != 0x0000) { - m_inv_ix = tmp81; - DPRINT(DBG_INFO, F(" inv_ix ")); - _DPRINT(DBG_INFO, m_inv_ix); - DPRINT(DBG_INFO, F(" OK")); - } else { - DPRINT(DBG_INFO, F(" ERR")); - } // end if-else - } // end if(tmp8 > 2)-else - } // end if(tmp8 > 0) - Serial.println(); - break; - } // end case a - - case (char)'d': { - // trigger decoding, enable periodic decoding via "d1" and disable via "d0" - Serial.print(F("\nd")); - // simple decoding, can only handle the current active inverter index, maybe erased if new data arrive, switch other inverter via interter indexing in automode settings - tmp32 = swap_bytes(&mPayload[m_inv_ix].invId[1]); - //Serial.print("\nd tmp32 hex: "); Serial.print(tmp32,HEX); - decode_userPayload(TX_REQ_INFO + 0x80, user_payload, 42, user_pl_ts, utSer.mStrOutBuf, MAX_STRING_LEN, mPayload[m_inv_ix].invType, tmp32); - m_sread_len = utSer.serBlockRead_ms(utSer.mSerBuffer); - doDecode = (bool)utSer.uart_eval_decimal_val(F("decoding "), utSer.mSerBuffer, m_sread_len, 0, 255, 1); - break; - } // end case d - - case (char)'s': { - // sending smac commands which are send to the inverter, same format as from inverter, see details in funct. eval_uart_smac_cmd(...) - // e.g. "smac:ch03:958180....:rc40:" -- ch03 for Tx channel, , -- rc40 for rx channel 40, if rx channel is left then default tx_ix+2 - - // todo: not yet tested much - - if (automode == true) { - automode = false; - DPRINT(DBG_INFO, F("automode ")); - _DPRINT(DBG_INFO, automode); + case (char)'a': { + // enable automode with REQ polling interval via a10 => 10sec, a100 => 100sec or other range 5....3600sec + // extend automode with polling period in sec, inverter ix and id, all numerial value are optional from the back + // cmd: a[[[:]:]:<12digit invID>:] e.g. a:120:1:081511223344:, also polling only a:120: and polling and inv_ix via a:120:1 + automode = true; + //m_inv_ix = 0; + uint8_t invIX = 0; + uint16_t invType = 0x4711; + uint8_t invID5[5] = {0x01, 0x44, 0x33, 0x22, 0x11}; + mParams = utSer.getParamsBuf(); + tmp8 = utSer.read_uart_cmd_param(mParams); + + if (tmp8 > 0) { + // get polling interval from first parameter + tmp81 = strlen(&mParams[0][0]); + if (tmp81 > 0 && tmp81 < 5) { + //polling_req_msec[m_inv_ix] = 1000 * utSer.uart_eval_decimal_val(F("auto poll sec "), &mParams[0][0], 5, 5, 3600, 1); + tmp32 = 1000 * utSer.uart_eval_decimal_val(F("auto poll sec "), &mParams[0][0], 5, 5, 3600, 1); } - mParams = utSer.getParamsBuf(); - tmp8 = utSer.read_uart_cmd_param(mParams); - if (tmp8 > 0) { - if (strstr(&mParams[0][0], "mac")) { - if (utSer.uart_cmd_smac_request_parsing(mParams, tmp8, &rfTX_packet, &rxch)) { - if (rxch == 0) { - // if rxchannel not given, then set automatically - rxch = hmRadio.getRxChannel(rfTX_packet.rfch); - } - hmRadio.setRxChanIdx(hmRadio.getChanIdx(rxch)); - - // compare inv-id from packet data with all registered inv-id of the payload_t struct array - m_inv_ix = getInvIX(mPayload, MAX_NUM_INVERTERS, &rfTX_packet.data[0]); - if (m_inv_ix == 0xFF) { - DPRINT(DBG_DEBUG, F("inv_id no match")); - m_inv_ix = MAX_NUM_INVERTERS - 1; // use last possition - } - DPRINT(DBG_DEBUG, F("m_inv_ix ")); - _DPRINT(DBG_DEBUG, m_inv_ix); - tcmd_millis = millis(); - smac_send = true; - payload_used[m_inv_ix] = !resetPayload(&mPayload[m_inv_ix]); - mPayload[m_inv_ix].isMACPacket = true; // MAC must be enabled to show the full MAC packet, no need for user_payload - mPayload[m_inv_ix].receive = false; - hmRadio.sendPacket_raw(&mPayload[m_inv_ix].invId[0], &rfTX_packet, rxch); // 2022-10-30: byte array transfer working first time - mPayload[m_inv_ix].requested = true; - mPayload[m_inv_ix].invType = 0x1111; // used as dummy type, decode works external only with known type - - } // end if(utSer.uart_cmd_smac_request_parser(...)) - } // end if(mac) - } // end if(tmp8) - Serial.println(); - break; - } // end case s - - case (char)'i': { - // inverter handling cmds for automode - uint8_t invIX = 0; - uint16_t invType = 0x4711; - uint8_t invID5[5] = {0x01, 0x44, 0x33, 0x22, 0x11}; - // cmd tokens will be written in mParams-pointer array - mParams = utSer.getParamsBuf(); - tmp8 = utSer.read_uart_cmd_param(mParams); - if (tmp8 > 0) { - if (strstr(&mParams[0][0], "add")) { - // cmd to add a new inverter at index possition - // /> iadd:01:1144 11223344: - register new inverter at index 01 and plate id - // - if (utSer.uart_cmd_add_inverter_parsing(++mParams, tmp8, &invIX, &invType, &invID5[0])) { - // write to inverter structure at given index - mPayload[invIX].invType = invType; - memcpy(mPayload[invIX].invId, invID5, 5); - // todo: extend inverter list to eeprom + if (tmp8 > 2) { + // get inverter index and inverter ID from parameter 2 and 3 + if (utSer.uart_cmd_add_inverter_parsing(&mParams[1], MAX_SER_PARAM, &invIX, &invType, &invID5[0])) { + // write to inverter structure at given index + mPayload[invIX].invType = invType; + memcpy(mPayload[invIX].invId, &invID5[0], 5); + m_inv_ix = invIX; + DPRINT(DBG_INFO, F(" OK")); + // todo: save inverter list to eeprom depending on 4th parameter (e.g ":eep:" ) + } // end if() + + } else if (tmp8 > 1) { + // try to get and set the inverter-index only + tmp81 = (uint8_t)(utSer.uart_eval_decimal_val(NULL, &mParams[1][0], tmp8, 0, MAX_NUM_INVERTERS, 1)); + if (mPayload[tmp81].invType != 0x0000) { + m_inv_ix = tmp81; + DPRINT(DBG_INFO, F(" inv_ix ")); + _DPRINT(DBG_INFO, m_inv_ix); + DPRINT(DBG_INFO, F(" OK")); + } else { + DPRINT(DBG_INFO, F(" ERR")); + } // end if-else + } // end if(tmp8 > 2)-else + } // end if(tmp8 > 0) + Serial.println(); + polling_req_msec[m_inv_ix] = tmp32; + mCountdown_noSignal[m_inv_ix] = SEND_REPEAT; // trigger sending if before no resonse + + break; + } // end case a + + case (char)'d': { + // trigger decoding, enable periodic decoding via "d1" and disable via "d0" + Serial.print(F("\nd")); + // simple decoding, can only handle the current active inverter index, maybe erased if new data arrive, switch other inverter via interter indexing in automode settings + tmp32 = swap_bytes(&mPayload[m_inv_ix].invId[1]); + // Serial.print("\nd tmp32 hex: "); Serial.print(tmp32,HEX); + decode_InfoREQResp(TX_REQ_INFO + 0x80, user_payload, 42, user_pl_ts, utSer.mStrOutBuf, MAX_STRING_LEN, mPayload[m_inv_ix].invType, tmp32); + m_sread_len = utSer.serBlockRead_ms(utSer.mSerBuffer); + doDecode = (bool)utSer.uart_eval_decimal_val(F("decoding "), utSer.mSerBuffer, m_sread_len, 0, 255, 1); + break; + } // end case d + + case (char)'s': { + // sending smac commands which are send to the inverter, same format as from inverter, see details in funct. eval_uart_smac_cmd(...) + // e.g. "smac:ch03:958180....:rc40:" -- ch03 for Tx channel, , -- rc40 for rx channel 40, if rx channel is left then default tx_ix+2 + + // todo: not yet tested much + + if (automode == true) { + automode = false; + DPRINT(DBG_INFO, F("automode ")); + _DPRINT(DBG_INFO, automode); + } + + mParams = utSer.getParamsBuf(); + tmp8 = utSer.read_uart_cmd_param(mParams); + if (tmp8 > 0) { + if (strstr(&mParams[0][0], "mac")) { + if (utSer.uart_cmd_smac_request_parsing(mParams, tmp8, &rfTX_packet, &rxch)) { + if (rxch == 0) { + // if rxchannel not given, then set automatically + rxch = hmRadio.getRxChannel(rfTX_packet.rfch); } + hmRadio.setRxChanIdx(hmRadio.getChanIdx(rxch)); - } else if (strstr(&mParams[0][0], "lst")) { - // match cmd to list inverter which are registered - // /> invlist: -- list content of all index possitions - // - Serial.print(F("\ninv List:")); - for (uint8_t x = 0; x < MAX_NUM_INVERTERS; x++) { - snprintf_P(utSer.getStrOutBuf(), MAX_STRING_LEN, PSTR("\n %d: %04X "), x, mPayload[x].invType); - Serial.print(utSer.getStrOutBuf()); - utSer.print_bytes(mPayload[x].invId, 5, " ", true); - } // end for() - - } else if (strstr(&mParams[0][0], "del")) { - // cmd to delete inverter from inverter list via given index - // /> invdel:01: -- delete index 01 from list - // - if (utSer.uart_cmd_del_inverter_parsing(mParams, tmp8, &invIX)) { - mPayload[invIX].invType = 0x0000; + // compare inv-id from packet data with all registered inv-id of the payload_t struct array + m_inv_ix = getInvIX(mPayload, MAX_NUM_INVERTERS, &rfTX_packet.data[0]); + if (m_inv_ix == 0xFF) { + DPRINT(DBG_DEBUG, F("inv_id no match")); + m_inv_ix = MAX_NUM_INVERTERS - 1; // use last possition } + DPRINT(DBG_DEBUG, F("m_inv_ix ")); + _DPRINT(DBG_DEBUG, m_inv_ix); + tcmd_millis = millis(); + smac_send = true; + payload_used[m_inv_ix] = !resetPayload(&mPayload[m_inv_ix]); + mPayload[m_inv_ix].isMACPacket = true; // MAC must be enabled to show the full MAC packet, no need for user_payload + mPayload[m_inv_ix].receive = false; + hmRadio.sendPacket_raw(&mPayload[m_inv_ix].invId[0], &rfTX_packet, rxch); // 2022-10-30: byte array transfer working first time + mPayload[m_inv_ix].requested = true; + mPayload[m_inv_ix].invType = 0x1111; // used as dummy type, decode works external only with known type + + } // end if(utSer.uart_cmd_smac_request_parser(...)) + } // end if(mac) + } // end if(tmp8) + Serial.println(); + break; + } // end case s + + case (char)'i': { + // inverter handling cmds for automode + uint8_t invIX = 0; + uint16_t invType = 0x4711; + uint8_t invID5[5] = {0x01, 0x44, 0x33, 0x22, 0x11}; + // cmd tokens will be written in mParams-pointer array + mParams = utSer.getParamsBuf(); + tmp8 = utSer.read_uart_cmd_param(mParams); + if (tmp8 > 0) { + if (strstr(&mParams[0][0], "add")) { + // cmd to add a new inverter at index possition + // /> iadd:01:1144 11223344: - register new inverter at index 01 and plate id + // + if (utSer.uart_cmd_add_inverter_parsing(++mParams, tmp8, &invIX, &invType, &invID5[0])) { + // write to inverter structure at given index + mPayload[invIX].invType = invType; + memcpy(mPayload[invIX].invId, invID5, 5); + // todo: extend inverter list to eeprom + } - } else { - // no cmd match + } else if (strstr(&mParams[0][0], "lst")) { + // match cmd to list inverter which are registered + // /> invlist: -- list content of all index possitions + // + Serial.print(F("\ninv List:")); + for (uint8_t x = 0; x < MAX_NUM_INVERTERS; x++) { + snprintf_P(utSer.getStrOutBuf(), MAX_STRING_LEN, PSTR("\n %d: %04X "), x, mPayload[x].invType); + Serial.print(utSer.getStrOutBuf()); + utSer.print_bytes(mPayload[x].invId, 5, " ", true); + } // end for() + + } else if (strstr(&mParams[0][0], "del")) { + // cmd to delete inverter from inverter list via given index + // /> invdel:01: -- delete index 01 from list + // + if (utSer.uart_cmd_del_inverter_parsing(mParams, tmp8, &invIX)) { + mPayload[invIX].invType = 0x0000; } - } - Serial.println(); - break; - } // end case i - - case (char)'c': { - // scans all channels for 1bit RDP (RSSI) value and print result e.g c100 scans in a loop of 100 iterations - // Serial.print(F("\nc OK ")); - mParams = utSer.getParamsBuf(); - tmp8 = utSer.read_uart_cmd_param(mParams); - if (tmp8 > 0) { - tmp81 = (uint8_t)(utSer.uart_eval_decimal_val(F("scan "), &mParams[0][0], 5, 1, 255, 1)); - while (tmp81--) { - hmRadio.scanRF(); - if (Serial.available()) { - // wait char - _inSer = Serial.read(); - if (_inSer >= '\n') break; // from while() - } // end if - } // end while() - } - Serial.println(); - break; - } - case (char)'m': { - // enable/disable show MACmessages via "m0" or "m1" - Serial.print(F("\nm")); - m_sread_len = utSer.serBlockRead_ms(utSer.mSerBuffer); - showMAC = (bool)utSer.uart_eval_decimal_val(F("showMAC "), utSer.mSerBuffer, m_sread_len, 0, 255, 1); - break; - } // end case m - - case (char)'r': { - mParams = utSer.getParamsBuf(); - tmp8 = utSer.read_uart_cmd_param(mParams, 2); - if (strstr(&mParams[0][0], "xch")) { - // sets rx channel via e.g via cmd rxch:63: - rxch = utSer.uart_eval_decimal_val(F("rx chan "), &mParams[1][0], 3, 0, 125, 1); // use first parameter after cmd } else { - // query current radio information via "r" - hmRadio.print_radio_details(); - } // end if-else() - break; + // no cmd match + } } + Serial.println(); + break; + } // end case i + + case (char)'c': { + // scans all channels for 1bit RDP (RSSI) value and print result e.g c100 scans in a loop of 100 iterations + // Serial.print(F("\nc OK ")); + mParams = utSer.getParamsBuf(); + tmp8 = utSer.read_uart_cmd_param(mParams); + if (tmp8 > 0) { + tmp81 = (uint8_t)(utSer.uart_eval_decimal_val(F("scan "), &mParams[0][0], 5, 1, 255, 1)); + while (tmp81--) { + hmRadio.scanRF(); + if (Serial.available()) { + // wait char + _inSer = Serial.read(); + if (_inSer >= '\n') break; // from while() + } // end if + } // end while() + } + Serial.println(); + break; + } - case (char)'p': { - // set or query power limit from inverter, parameter must be separated by ":" - // todo: extend para-check to reactive powerlimit "%var" and "var" - - mParams = utSer.getParamsBuf(); - tmp8 = utSer.read_uart_cmd_param(mParams, 3); - if (strstr(&mParams[0][0], "?")) { - // cmd:/ "p?" - // query power setting - // not sure there is a query request - DPRINTLN(DBG_DEBUG, F("query: not yet, break")); - break; - - } else if (strstr(&mParams[0][0], "%")) { - // make non persistent settings only - // set relative power - iv_powerLimit[0] = (uint16_t)utSer.uart_eval_decimal_val(F("Prel "), &mParams[0][0], 4, 0, 100, 1); - iv_powerLimit[1] = RelativNonPersistent; - } - if (strstr(&mParams[0][0], "w")) { - // make non persistent settings only - // set absolute power - iv_powerLimit[0] = (uint16_t)utSer.uart_eval_decimal_val(F("Pabs "), &mParams[0][0], 5, 0, 65000, 1); - iv_powerLimit[1] = AbsolutNonPersistent; - } // end if-else() - - // if (!iv_devControlReq) { - iv_devControlReq = true; // defines type of message, not that something is pending - sendNow = true; - //} - break; - } // end p - - case (char)'t': { - // set the time sec since Jan-01 1970 (UNIX epoch time) as decimal String value e.g. "t1612345678:" for Feb-03 2021 9:47:58 - // timestamp is only used for sending packet timer, but not for the timing of Tx/Rx scheduling etc... - // ther is no need of exact timing, it must only increase (change) in REQ_INFO_CMDs - m_sread_len = utSer.serBlockRead_ms(utSer.getInBuf()); - mTimestamp = utSer.uart_eval_decimal_val(F("time set "), utSer.getInBuf(), m_sread_len, 10 * 3600, 0xFFFFFFFF, 1); - break; - } // end case t + case (char)'m': { + // enable/disable show MACmessages via "m0" or "m1" + Serial.print(F("\nm")); + m_sread_len = utSer.serBlockRead_ms(utSer.mSerBuffer); + showMAC = (bool)utSer.uart_eval_decimal_val(F("showMAC "), utSer.mSerBuffer, m_sread_len, 0, 255, 1); + break; + } // end case m + + case (char)'r': { + mParams = utSer.getParamsBuf(); + tmp8 = utSer.read_uart_cmd_param(mParams, 2); + if (strstr(&mParams[0][0], "xch")) { + // sets rx channel via e.g via cmd rxch:63: + rxch = utSer.uart_eval_decimal_val(F("rx chan "), &mParams[1][0], 3, 0, 125, 1); // use first parameter after cmd + } else { + // query current radio information via "r" + hmRadio.print_radio_details(); + } // end if-else() + break; + } - case (char)'?': { - Serial.println(F("\ncmds: a:, c_, d, iadd:, idel:, ilst:, m_, p:, s, smac:, rxch_, t_, ?")); + case (char)'p': { + // set or query power limit from inverter, parameter must be separated by ":" + // todo: extend para-check to reactive powerlimit "%var" and "var" + + mParams = utSer.getParamsBuf(); + tmp8 = utSer.read_uart_cmd_param(mParams, 3); + if (strstr(&mParams[0][0], "?")) { + // cmd:/ "p?" + // query power setting + // not sure there is a query request + DPRINTLN(DBG_DEBUG, F("query: not yet, break")); break; - } // end case '?' - - } // end switch-case -}//end mSwitchCases() + } else if (strstr(&mParams[0][0], "%")) { + // make non persistent settings only + // set relative power + iv_powerLimit[0] = (uint16_t)utSer.uart_eval_decimal_val(F("Prel "), &mParams[0][0], 4, 0, 100, 1); + iv_powerLimit[1] = RelativNonPersistent; + } + if (strstr(&mParams[0][0], "w")) { + // make non persistent settings only + // set absolute power + iv_powerLimit[0] = (uint16_t)utSer.uart_eval_decimal_val(F("Pabs "), &mParams[0][0], 5, 0, 65000, 1); + iv_powerLimit[1] = AbsolutNonPersistent; + } // end if-else() + + // if (!iv_devControlReq) { + iv_devControlReq = true; // defines type of message, not that something is pending + sendNow = true; + //} + break; + } // end p + + case (char)'t': { + // set the time sec since Jan-01 1970 (UNIX epoch time) as decimal String value e.g. "t1612345678:" for Feb-03 2021 9:47:58 + // timestamp is only used for sending packet timer, but not for the timing of Tx/Rx scheduling etc... + // ther is no need of exact timing, it must only increase (change) in REQ_INFO_CMDs + m_sread_len = utSer.serBlockRead_ms(utSer.getInBuf()); + mTimestamp = utSer.uart_eval_decimal_val(F("time set "), utSer.getInBuf(), m_sread_len, 10 * 3600, 0xFFFFFFFF, 1); + break; + } // end case t + + case (char)'?': { + Serial.println(F("\ncmds: a:, c_, d, iadd:, idel:, ilst:, m_, p:, s, smac:, rxch_, t_, ?")); + break; + } // end case '?' + + } // end switch-case +} // end mSwitchCases() /////////////////////////////////////////////////////////// //