diff --git a/src/.vscode/settings.json b/src/.vscode/settings.json index e61851fb..fa166086 100644 --- a/src/.vscode/settings.json +++ b/src/.vscode/settings.json @@ -84,4 +84,5 @@ }, "cmake.configureOnOpen": false, "editor.formatOnSave": false, + "cmake.sourceDirectory": "C:/lpusch/github/ahoy/src/.pio/libdeps/esp32-wroom32-release-prometheus/Adafruit BusIO", } \ No newline at end of file diff --git a/src/defines.h b/src/defines.h index 163e22b5..021b4010 100644 --- a/src/defines.h +++ b/src/defines.h @@ -13,7 +13,7 @@ //------------------------------------- #define VERSION_MAJOR 0 #define VERSION_MINOR 7 -#define VERSION_PATCH 37 +#define VERSION_PATCH 38 //------------------------------------- typedef struct { diff --git a/src/hm/hmDefines.h b/src/hm/hmDefines.h index 7c814857..a040d01b 100644 --- a/src/hm/hmDefines.h +++ b/src/hm/hmDefines.h @@ -10,7 +10,8 @@ #include // inverter generations -enum {IV_HM = 0, IV_MI, IV_HMS, IV_HMT}; +enum {IV_MI = 0, IV_HM, IV_HMS, IV_HMT, IV_UNKNOWN}; +const char* const generationNames[] = {"HM", "MI", "HMS", "HMT", "UNKNOWN"}; // units enum {UNIT_V = 0, UNIT_A, UNIT_W, UNIT_WH, UNIT_KWH, UNIT_HZ, UNIT_C, UNIT_PCT, UNIT_VAR, UNIT_NONE}; @@ -21,20 +22,22 @@ enum {FLD_UDC = 0, FLD_IDC, FLD_PDC, FLD_YD, FLD_YW, FLD_YT, FLD_UAC, FLD_UAC_1N, FLD_UAC_2N, FLD_UAC_3N, FLD_UAC_12, FLD_UAC_23, FLD_UAC_31, FLD_IAC, FLD_IAC_1, FLD_IAC_2, FLD_IAC_3, 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, FLD_MP}; + FLD_FW_BUILD_MONTH_DAY, FLD_FW_BUILD_HOUR_MINUTE, FLD_BOOTLOADER_VER, + FLD_ACT_ACTIVE_PWR_LIMIT, FLD_PART_NUM, FLD_HW_VERSION, FLD_GRID_PROFILE_CODE, + FLD_GRID_PROFILE_VERSION, /*FLD_ACT_REACTIVE_PWR_LIMIT, FLD_ACT_PF,*/ FLD_LAST_ALARM_CODE, FLD_MP}; const char* const fields[] = {"U_DC", "I_DC", "P_DC", "YieldDay", "YieldWeek", "YieldTotal", "U_AC", "U_AC_1N", "U_AC_2N", "U_AC_3N", "UAC_12", "UAC_23", "UAC_31", "I_AC", "IAC_1", "I_AC_2", "I_AC_3", "P_AC", "F_AC", "Temp", "PF_AC", "Efficiency", "Irradiation","Q_AC", - "ALARM_MES_ID","FWVersion","FWBuildYear","FWBuildMonthDay","FWBuildHourMinute","HWPartId", - "active_PowerLimit", /*"reactivePowerLimit","Powerfactor",*/ "LastAlarmCode", "MaxPower"}; + "ALARM_MES_ID","FWVersion","FWBuildYear","FWBuildMonthDay","FWBuildHourMinute","BootloaderVersion", + "active_PowerLimit", "HWPartNumber", "HWVersion", "GridProfileCode", + "GridProfileVersion", /*"reactivePowerLimit","Powerfactor",*/ "LastAlarmCode", "MaxPower"}; const char* const notAvail = "n/a"; const uint8_t fieldUnits[] = {UNIT_V, UNIT_A, UNIT_W, UNIT_WH, UNIT_KWH, UNIT_KWH, UNIT_V, UNIT_V, UNIT_V, UNIT_V, UNIT_V, UNIT_V, UNIT_V, UNIT_A, UNIT_A, UNIT_A, UNIT_A, UNIT_W, UNIT_HZ, UNIT_C, UNIT_NONE, UNIT_PCT, UNIT_PCT, UNIT_VAR, - UNIT_NONE, UNIT_NONE, UNIT_NONE, UNIT_NONE, UNIT_NONE, UNIT_NONE, UNIT_PCT, UNIT_NONE, UNIT_W}; + UNIT_NONE, UNIT_NONE, UNIT_NONE, UNIT_NONE, UNIT_NONE, UNIT_NONE, UNIT_PCT, UNIT_NONE, UNIT_NONE, UNIT_NONE, UNIT_NONE, UNIT_NONE, UNIT_W}; // 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}; @@ -98,11 +101,20 @@ const byteAssign_t InfoAssignment[] = { { 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 } + { FLD_BOOTLOADER_VER, UNIT_NONE, CH0, 8, 2, 1 } }; #define HMINFO_LIST_LEN (sizeof(InfoAssignment) / sizeof(byteAssign_t)) #define HMINFO_PAYLOAD_LEN 14 +const byteAssign_t SimpleInfoAssignment[] = { + { FLD_PART_NUM, UNIT_NONE, CH0, 2, 4, 1 }, + { FLD_HW_VERSION, UNIT_NONE, CH0, 6, 2, 1 }, + { FLD_GRID_PROFILE_CODE, UNIT_NONE, CH0, 8, 2, 1 }, + { FLD_GRID_PROFILE_VERSION, UNIT_NONE, CH0, 10, 2, 1 } +}; +#define HMSIMPLE_INFO_LIST_LEN (sizeof(SimpleInfoAssignment) / sizeof(byteAssign_t)) +#define HMSIMPLE_INFO_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 }, @@ -242,4 +254,45 @@ const byteAssign_t hm4chAssignment[] = { #define HM4CH_PAYLOAD_LEN 62 +typedef struct { + uint32_t hwPart; + uint16_t maxPower; +} devInfo_t; + +const devInfo_t devInfo[] = { + // MI 3rd gen + { 0x100230, 1500 }, + + // HM + { 0x101010, 300 }, + { 0x101020, 350 }, + { 0x101030, 400 }, + { 0x101040, 400 }, + { 0x101110, 600 }, + { 0x101120, 700 }, + { 0x101130, 800 }, + { 0x101140, 800 }, + { 0x101210, 1200 }, + { 0x101230, 1500 }, + + // HMS + { 0x102021, 350 }, + { 0x102041, 400 }, + { 0x101051, 450 }, + { 0x101071, 500 }, + { 0x102111, 600 }, + { 0x102141, 800 }, + { 0x101151, 900 }, + { 0x102171, 1000 }, + { 0x102241, 1600 }, + { 0x101251, 1800 }, + { 0x102251, 1800 }, + { 0x101271, 2000 }, + { 0x102271, 2000 }, + + // HMT + { 0x103311, 1800 }, + { 0x103331, 2250 } +}; + #endif /*__HM_DEFINES_H__*/ diff --git a/src/hm/hmInverter.h b/src/hm/hmInverter.h index cfcd134a..d150160c 100644 --- a/src/hm/hmInverter.h +++ b/src/hm/hmInverter.h @@ -142,6 +142,7 @@ class Inverter { uint8_t channels; // number of PV channels (1-4) record_t recordMeas; // structure for measured values record_t recordInfo; // structure for info values + record_t recordHwInfo; // structure for simple (hardware) info values record_t recordConfig; // structure for system config values record_t recordAlarm; // structure for alarm values //String lastAlarmMsg; @@ -189,7 +190,6 @@ class Inverter { void setQueuedCmdFinished() { if (!_commandQueue.empty()) { - // Will destroy CommandAbstract Class Object (?) _commandQueue.pop(); } } @@ -197,7 +197,6 @@ class Inverter { void clearCmdQueue() { DPRINTLN(DBG_INFO, F("clearCmdQueue")); while (!_commandQueue.empty()) { - // Will destroy CommandAbstract Class Object (?) _commandQueue.pop(); } } @@ -207,6 +206,8 @@ class Inverter { if (ivGen != IV_MI) { if (getFwVersion() == 0) enqueCommand(InverterDevInform_All); // firmware version + else if (getHwVersion() == 0) + enqueCommand(InverterDevInform_Simple); // hardware version enqueCommand(RealTimeRunData_Debug); // live data } else if (ivGen == IV_MI){ if (getFwVersion() == 0) @@ -229,6 +230,7 @@ class Inverter { DPRINTLN(DBG_VERBOSE, F("hmInverter.h:init")); initAssignment(&recordMeas, RealTimeRunData_Debug); initAssignment(&recordInfo, InverterDevInform_All); + initAssignment(&recordHwInfo, InverterDevInform_Simple); initAssignment(&recordConfig, SystemConfigPara); initAssignment(&recordAlarm, AlarmData); toRadioId(); @@ -340,6 +342,10 @@ class Inverter { // eg. fw version ... isConnected = true; } + else if (rec->assign == SimpleInfoAssignment) { + DPRINTLN(DBG_DEBUG, "add simple info"); + // eg. hw version ... + } else if (rec->assign == SystemConfigParaAssignment) { DPRINTLN(DBG_DEBUG, "add config"); if (getPosByChFld(0, FLD_ACT_ACTIVE_PWR_LIMIT, rec) == pos){ @@ -390,6 +396,10 @@ class Inverter { return 0; } + uint32_t getChannelFieldValueInt(uint8_t channel, uint8_t fieldId, record_t<> *rec) { + return (uint32_t) getChannelFieldValue(channel, fieldId, rec); + } + REC_TYP getValue(uint8_t pos, record_t<> *rec) { DPRINTLN(DBG_VERBOSE, F("hmInverter.h:getValue")); if(NULL == rec) @@ -452,8 +462,24 @@ class Inverter { uint16_t getFwVersion() { record_t<> *rec = getRecordStruct(InverterDevInform_All); - uint8_t pos = getPosByChFld(CH0, FLD_FW_VERSION, rec); - return getValue(pos, rec); + return getChannelFieldValue(CH0, FLD_FW_VERSION, rec); + } + + uint16_t getHwVersion() { + record_t<> *rec = getRecordStruct(InverterDevInform_Simple); + return getChannelFieldValue(CH0, FLD_HW_VERSION, rec); + } + + uint16_t getMaxPower() { + record_t<> *rec = getRecordStruct(InverterDevInform_Simple); + if(0 == getChannelFieldValue(CH0, FLD_HW_VERSION, rec)) + return 0; + + for(uint8_t i = 0; i < sizeof(devInfo) / sizeof(devInfo_t); i++) { + if(devInfo[i].hwPart == (getChannelFieldValueInt(CH0, FLD_PART_NUM, rec) >> 8)) + return devInfo[i].maxPower; + } + return 0; } uint32_t getLastTs(record_t<> *rec) { @@ -463,11 +489,12 @@ class Inverter { record_t<> *getRecordStruct(uint8_t cmd) { switch (cmd) { - case RealTimeRunData_Debug: return &recordMeas; // 11 = 0x0b - case InverterDevInform_All: return &recordInfo; // 1 = 0x01 - case SystemConfigPara: return &recordConfig; // 5 = 0x05 - case AlarmData: return &recordAlarm; // 17 = 0x11 - default: break; + case RealTimeRunData_Debug: return &recordMeas; // 11 = 0x0b + case InverterDevInform_Simple: return &recordHwInfo; // 0 = 0x00 + case InverterDevInform_All: return &recordInfo; // 1 = 0x01 + case SystemConfigPara: return &recordConfig; // 5 = 0x05 + case AlarmData: return &recordAlarm; // 17 = 0x11 + default: break; } return NULL; } @@ -535,6 +562,11 @@ class Inverter { rec->assign = (byteAssign_t *)InfoAssignment; rec->pyldLen = HMINFO_PAYLOAD_LEN; break; + case InverterDevInform_Simple: + rec->length = (uint8_t)(HMSIMPLE_INFO_LIST_LEN); + rec->assign = (byteAssign_t *)SimpleInfoAssignment; + rec->pyldLen = HMSIMPLE_INFO_PAYLOAD_LEN; + break; case SystemConfigPara: rec->length = (uint8_t)(HMSYSTEM_LIST_LEN); rec->assign = (byteAssign_t *)SystemConfigParaAssignment; diff --git a/src/web/RestApi.h b/src/web/RestApi.h index ddc16d52..7d08b36b 100644 --- a/src/web/RestApi.h +++ b/src/web/RestApi.h @@ -107,6 +107,8 @@ class RestApi { getInverter(root, request->url().substring(17).toInt()); else if(path.substring(0, 15) == "inverter/alarm/") getIvAlarms(root, request->url().substring(20).toInt()); + else if(path.substring(0, 15) == "inverter/version/") + getIvVersion(root, request->url().substring(20).toInt()); else getNotFound(root, F("http://") + request->host() + F("/api/")); } @@ -421,6 +423,34 @@ class RestApi { } } + void getIvVersion(JsonObject obj, uint8_t id) { + Inverter<> *iv = mSys->getInverterByPos(id); + if(NULL == iv) { + obj[F("error")] = F("inverter not found!"); + return; + } + + record_t<> *rec = iv->getRecordStruct(InverterDevInform_Simple); + + obj[F("name")] = String(iv->config->name); + obj[F("generation")] = iv->ivGen; + obj[F("max_pwr")] = iv->getMaxPower(); + obj[F("part_num")] = iv->getChannelFieldValueInt(CH0, FLD_PART_NUM, rec); + obj[F("hw_ver")] = iv->getChannelFieldValueInt(CH0, FLD_HW_VERSION, rec); + obj[F("prod_cw")] = ((iv->radioId.b[3] & 0x0f) * 10 + ((iv->radioId.b[4] & 0x0f))); + obj[F("prod_year")] = ((iv->radioId.b[3] >> 4) & 0x0f) + 2014; + + + rec = iv->getRecordStruct(InverterDevInform_All); + obj[F("fw_ver")] = iv->getChannelFieldValueInt(CH0, FLD_FW_VERSION, rec); + obj[F("fw_date")] = String(iv->getChannelFieldValueInt(CH0, FLD_FW_BUILD_YEAR, rec)) + + "-" + String(iv->getChannelFieldValueInt(CH0, FLD_FW_BUILD_MONTH_DAY, rec) / 100) + + "-" + String(iv->getChannelFieldValueInt(CH0, FLD_FW_BUILD_MONTH_DAY, rec) % 100); + obj[F("fw_time")] = String(iv->getChannelFieldValueInt(CH0, FLD_FW_BUILD_HOUR_MINUTE, rec) / 100) + + ":" + String(iv->getChannelFieldValueInt(CH0, FLD_FW_BUILD_HOUR_MINUTE, rec) % 100); + obj[F("boot_ver")] = iv->getChannelFieldValueInt(CH0, FLD_BOOTLOADER_VER, rec); + } + void getMqtt(JsonObject obj) { obj[F("broker")] = String(mConfig->mqtt.broker); obj[F("clientId")] = String(mConfig->mqtt.clientId); diff --git a/src/web/html/style.css b/src/web/html/style.css index bf84aaa3..f61b1a18 100644 --- a/src/web/html/style.css +++ b/src/web/html/style.css @@ -318,6 +318,20 @@ p { .col-md-12 { width: 100%; } } +table { + border-collapse: collapse; + width: 100%; +} + +th { + text-align: inherit; +} + +.table td, .table th { + padding: .75rem; + border-bottom: 1px solid var(--nav-bg); +} + #wrapper { min-height: 100%; } diff --git a/src/web/html/visualization.html b/src/web/html/visualization.html index 0194749a..a8048eb3 100644 --- a/src/web/html/visualization.html +++ b/src/web/html/visualization.html @@ -102,7 +102,9 @@ ml("div", {class: "col"}, [ ml("div", {class: "p-2 " + clh}, ml("div", {class: "row"}, [ - ml("div", {class: "col mx-2 mx-md-1"}, obj.name), + ml("div", {class: "col mx-2 mx-md-1"}, ml("span", { class: "pointer", onclick: function() { + getAjax("/api/inverter/version/" + obj.id, parseIvVersion); + }}, obj.name)), ml("div", {class: "col a-c"}, "Power limit " + ((obj.power_limit_read == 65535) ? "n/a" : (obj.power_limit_read + " %"))), ml("div", {class: "col a-c"}, ml("span", { class: "pointer", onclick: function() { getAjax("/api/inverter/alarm/" + obj.id, parseIvAlarm); @@ -250,6 +252,45 @@ modal("Alarms of inverter #" + obj.iv_id, ml("div", {}, html)); } + function parseIvVersion(obj) { + var model; + switch(obj.generation) { + case 0: model = "MI-"; break; + case 1: model = "HM-"; break; + case 2: model = "HMS-"; break; + case 3: model = "HMT-"; break; + default: model = "???-"; break; + } + model += String(obj.max_pwr); + + + var html = ml("table", {class: "table"}, [ + ml("tbody", {}, [ + ml("tr", {}, [ + ml("th", {}, "Model"), + ml("td", {}, model) + ]), + ml("tr", {}, [ + ml("th", {}, "Firmware Version / Build"), + ml("td", {}, String(obj.fw_ver) + " (build: " + String(obj.fw_date) + " " + String(obj.fw_time) + ")") + ]), + ml("tr", {}, [ + ml("th", {}, "Hardware Version / Build"), + ml("td", {}, String(obj.hw_ver) + " (build: " + String(obj.prod_cw) + "/" + String(obj.prod_year) + ")") + ]), + ml("tr", {}, [ + ml("th", {}, "Hardware Number"), + ml("td", {}, String(obj.part_num)) + ]), + ml("tr", {}, [ + ml("th", {}, "Bootloader Version"), + ml("td", {}, String(obj.boot_ver)) + ]) + ]) + ]); + modal("Info for inverter " + obj.name, ml("div", {}, html)); + } + function parse(obj) { if(null != obj) { parseGeneric(obj["generic"]);