From 9e6cae26a7922307fce16deb867b8256b198567a Mon Sep 17 00:00:00 2001 From: lumapu Date: Sun, 24 Apr 2022 20:53:56 +0200 Subject: [PATCH] * increased number of inverters * name, serial and type can be configured using setup.html --- tools/esp8266/app.cpp | 156 ++++++++++++++++++------------ tools/esp8266/app.h | 4 +- tools/esp8266/defines.h | 16 ++- tools/esp8266/hmInverters.h | 5 +- tools/esp8266/hmSystem.h | 26 +++-- tools/esp8266/html/h/setup_html.h | 2 +- tools/esp8266/html/h/style_css.h | 2 +- tools/esp8266/html/setup.html | 1 + tools/esp8266/html/style.css | 14 ++- 9 files changed, 139 insertions(+), 87 deletions(-) diff --git a/tools/esp8266/app.cpp b/tools/esp8266/app.cpp index 4f1c1e3d..fe0d2d23 100644 --- a/tools/esp8266/app.cpp +++ b/tools/esp8266/app.cpp @@ -44,12 +44,21 @@ void app::setup(const char *ssid, const char *pwd, uint32_t timeout) { if(mSettingsValid) { uint16_t interval; uint64_t invSerial; + char invName[MAX_NAME_LENGTH + 1] = {0}; + uint8_t invType; + + // inverter + for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i ++) { + mEep->read(ADDR_INV_ADDR + (i * 8), &invSerial); + mEep->read(ADDR_INV_NAME + (i * MAX_NAME_LENGTH), invName, MAX_NAME_LENGTH); + mEep->read(ADDR_INV_TYPE + i, &invType); + if(0ULL != invSerial) { + mSys->addInverter(invName, invSerial, invType); + Serial.println("add inverter: " + String(invName) + ", SN: " + String(invSerial, HEX) + ", type: " + String(invType)); + } + } - // hoymiles - mEep->read(ADDR_INV0_ADDR, &invSerial); mEep->read(ADDR_INV_INTERVAL, &interval); - mSys->addInverter("HM1200", invSerial, INV_TYPE_HM1200); - if(interval < 1000) interval = 1000; mSendTicker->attach_ms(interval, std::bind(&app::sendTicker, this)); @@ -129,29 +138,32 @@ void app::loop(void) { mFlagSend = false; uint8_t size = 0; - inverter_t *inv = mSys->getInverterByPos(0); + inverter_t *inv; + for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i ++) { + inv = mSys->getInverterByPos(i); + if(NULL != inv) { + //if((mSendCnt % 6) == 0) + size = mSys->Radio.getTimePacket(&inv->radioId.u64, mSendBuf, mTimestamp); + /*else if((mSendCnt % 6) == 1) + size = mSys->Radio.getCmdPacket(&inv->radioId.u64, mSendBuf, 0x15, 0x81); + else if((mSendCnt % 6) == 2) + size = mSys->Radio.getCmdPacket(&inv->radioId.u64, mSendBuf, 0x15, 0x80); + else if((mSendCnt % 6) == 3) + size = mSys->Radio.getCmdPacket(&inv->radioId.u64, mSendBuf, 0x15, 0x83); + else if((mSendCnt % 6) == 4) + size = mSys->Radio.getCmdPacket(&inv->radioId.u64, mSendBuf, 0x15, 0x82); + else if((mSendCnt % 6) == 5) + size = mSys->Radio.getCmdPacket(&inv->radioId.u64, mSendBuf, 0x15, 0x84);*/ - //if((mSendCnt % 6) == 0) - size = mSys->Radio.getTimePacket(&inv->radioId.u64, mSendBuf, mTimestamp); - /*else if((mSendCnt % 6) == 1) - size = mSys->Radio.getCmdPacket(&inv->radioId.u64, mSendBuf, 0x15, 0x81); - else if((mSendCnt % 6) == 2) - size = mSys->Radio.getCmdPacket(&inv->radioId.u64, mSendBuf, 0x15, 0x80); - else if((mSendCnt % 6) == 3) - size = mSys->Radio.getCmdPacket(&inv->radioId.u64, mSendBuf, 0x15, 0x83); - else if((mSendCnt % 6) == 4) - size = mSys->Radio.getCmdPacket(&inv->radioId.u64, mSendBuf, 0x15, 0x82); - else if((mSendCnt % 6) == 5) - size = mSys->Radio.getCmdPacket(&inv->radioId.u64, mSendBuf, 0x15, 0x84);*/ + //Serial.println("sent packet: #" + String(mSendCnt)); + //dumpBuf("SEN ", mSendBuf, size); + sendPacket(inv, mSendBuf, size); + mSendCnt++; - - - //Serial.println("sent packet: #" + String(mSendCnt)); - //dumpBuf("SEN ", mSendBuf, size); - sendPacket(inv, mSendBuf, size); - - mSendCnt++; + delay(20); + } + } } @@ -169,7 +181,7 @@ void app::loop(void) { snprintf(topic, 30, "%s/ch%d/%s", iv->name, iv->assign[i].ch, fields[iv->assign[i].fieldId]); snprintf(val, 10, "%.3f", mSys->getValue(iv, i)); mMqtt.sendMsg(topic, val); - delay(10); + delay(20); } } } @@ -328,25 +340,35 @@ void app::showSetup(void) { html.replace("{VERSION}", String(mVersion)); String inv; - inverter_t *pInv; + uint64_t invSerial; + char invName[MAX_NAME_LENGTH + 1] = {0}; + uint8_t invType; for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i ++) { - pInv = mSys->getInverterByPos(i); + mEep->read(ADDR_INV_ADDR + (i * 8), &invSerial); + mEep->read(ADDR_INV_NAME + (i * MAX_NAME_LENGTH), invName, MAX_NAME_LENGTH); + mEep->read(ADDR_INV_TYPE + i, &invType); inv += "

Inverter "+ String(i) + "

"; inv += ""; inv += "getSerial(pInv), HEX) : ""; - inv += "\"/>"; + if(0ULL != invSerial) + inv += String(invSerial, HEX); + inv += "\"/ maxlength=\"12\">"; inv += ""; inv += "name) : ""; - inv += "\"/>"; + inv += String(invName); + inv += "\"/ maxlength=\"" + String(MAX_NAME_LENGTH) + "\">"; inv += ""; - inv += "type) : ""; - inv += "\"/>"; + inv += ""; } html.replace("{INVERTERS}", String(inv)); @@ -416,45 +438,50 @@ void app::showHoymiles(void) { //----------------------------------------------------------------------------- void app::showLiveData(void) { - String modHtml = "
";
-
-    char topic[20], val[10];
+    String modHtml;
     for(uint8_t id = 0; id < mSys->getNumInverters(); id++) {
         inverter_t *iv = mSys->getInverterByPos(id);
         if(NULL != iv) {
-            /*uint8_t modNum;
+#ifdef LIVEDATA_VISUALIZED
+            uint8_t modNum, pos;
             switch(iv->type) {
                 default:              modNum = 1; break;
                 case INV_TYPE_HM600:  modNum = 2; break;
                 case INV_TYPE_HM1200: modNum = 4; break;
             }
 
-            for(uint8_t mod = 1; mod <= modNum; mod ++) {
-                modHtml += "
CHANNEL " + String(i) + ""; + for(uint8_t ch = 1; ch <= modNum; ch ++) { + modHtml += "
CHANNEL " + String(ch) + ""; for(uint8_t j = 0; j < 5; j++) { - modHtml += ""; switch(j) { - default: modHtml += String(mDecoder->mData.ch_dc[i/2].u); break; - case 1: modHtml += String(mDecoder->mData.ch_dc[i].i); break; - case 2: modHtml += String(mDecoder->mData.ch_dc[i].p); break; - case 3: modHtml += String(mDecoder->mData.ch_dc[i].y_d); break; - case 4: modHtml += String(mDecoder->mData.ch_dc[i].y_t); break; + default: pos = (mSys->getPosByChField(iv, ch, FLD_UDC)); break; + case 1: pos = (mSys->getPosByChField(iv, ch, FLD_IDC)); break; + case 2: pos = (mSys->getPosByChField(iv, ch, FLD_PDC)); break; + case 3: pos = (mSys->getPosByChField(iv, ch, FLD_YD)); break; + case 4: pos = (mSys->getPosByChField(iv, ch, FLD_YT)); break; + } + if(0xff != pos) { + modHtml += "" + String(mSys->getValue(iv, pos)); + modHtml += "" + String(mSys->getUnit(iv, pos)) + ""; + modHtml += "" + String(mSys->getFieldName(iv, pos)) + ""; } - modHtml += "" + unit[j] + ""; - modHtml += "" + info[j] + ""; } modHtml += "
"; - }*/ - + } +#else + // dump all data to web frontend + modHtml = "
";
+            char topic[30], val[10];
             for(uint8_t i = 0; i < iv->listLen; i++) {
-                sprintf(topic, "%s/ch%d/%s", iv->name, iv->assign[i].ch, mSys->getFieldName(iv, i));
-                sprintf(val, "%.3f %s", mSys->getValue(iv, i), mSys->getUnit(iv, i));
+                snprintf(topic, 30, "%s/ch%d/%s", iv->name, iv->assign[i].ch, mSys->getFieldName(iv, i));
+                snprintf(val, 10, "%.3f %s", mSys->getValue(iv, i), mSys->getUnit(iv, i));
                 modHtml += String(topic) + ": " + String(val) + "\n";
             }
+            modHtml += "
"; +#endif } } - modHtml += "
"; mWeb->send(200, "text/html", modHtml); } @@ -481,18 +508,25 @@ void app::saveValues(bool webSend = true) { // inverter serial_u addr; - mWeb->arg("inv0Addr").toCharArray(buf, 20); - addr.u64 = Serial2u64(buf); - mSys->updateSerial(mSys->getInverterByPos(0), addr.u64); + for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i ++) { + // address + mWeb->arg("inv" + String(i) + "Addr").toCharArray(buf, 20); + if(strlen(buf) == 0) + snprintf(buf, 20, "\0"); + addr.u64 = Serial2u64(buf); + mEep->write(ADDR_INV_ADDR + (i * 8), addr.u64); - for(uint8_t i = 0; i < 8; i++) { - Serial.print(String(addr.b[i], HEX) + " "); + // name + mWeb->arg("inv" + String(i) + "Name").toCharArray(buf, 20); + mEep->write(ADDR_INV_NAME + (i * MAX_NAME_LENGTH), buf, MAX_NAME_LENGTH); + + // type + mWeb->arg("inv" + String(i) + "Type").toCharArray(buf, 20); + uint8_t type = atoi(buf); + mEep->write(ADDR_INV_TYPE + (i * MAX_NAME_LENGTH), type); } - Serial.println(); - Serial.println("addr: " + String(addr.u64, HEX)); interval = mWeb->arg("invInterval").toInt(); - mEep->write(ADDR_INV0_ADDR, addr.u64); mEep->write(ADDR_INV_INTERVAL, interval); diff --git a/tools/esp8266/app.h b/tools/esp8266/app.h index 977d0b41..48898dd3 100644 --- a/tools/esp8266/app.h +++ b/tools/esp8266/app.h @@ -15,7 +15,7 @@ typedef HmRadio RadioType; typedef CircularBuffer BufferType; -typedef HmSystem HmSystemType; +typedef HmSystem HmSystemType; class app : public Main { public: @@ -59,6 +59,8 @@ class app : public Main { for(uint8_t i = 0; i < 6; i++) { tmp[0] = val[i*2]; tmp[1] = val[i*2 + 1]; + if((tmp[0] == '\0') || (tmp[1] == '\0')) + break; u64 = strtol(tmp, NULL, 16); ret |= (u64 << ((5-i) << 3)); } diff --git a/tools/esp8266/defines.h b/tools/esp8266/defines.h index f1f5b121..45632063 100644 --- a/tools/esp8266/defines.h +++ b/tools/esp8266/defines.h @@ -16,6 +16,8 @@ //------------------------------------- #define PACKET_BUFFER_SIZE 30 #define MAX_NUM_INVERTERS 3 +#define MAX_NAME_LENGTH 16 +#define LIVEDATA_VISUALIZED // show live data pv-module wise or as dump //------------------------------------- @@ -23,7 +25,7 @@ //------------------------------------- #define VERSION_MAJOR 0 #define VERSION_MINOR 2 -#define VERSION_PATCH 2 +#define VERSION_PATCH 3 //------------------------------------- @@ -34,8 +36,10 @@ #define DEVNAME_LEN 16 #define CRC_LEN 2 -#define INV_ADDR_LEN 8 // uint64_t -#define INV_INTERVAL_LEN 2 // uint16_t +#define INV_ADDR_LEN MAX_NUM_INVERTERS * 8 // uint64_t +#define INV_NAME_LEN MAX_NUM_INVERTERS * MAX_NAME_LENGTH // char[] +#define INV_TYPE_LEN MAX_NUM_INVERTERS * 1 // uint8_t +#define INV_INTERVAL_LEN 2 // uint16_t #define MQTT_ADDR_LEN 4 // IP @@ -49,8 +53,10 @@ #define ADDR_SSID ADDR_START #define ADDR_PWD ADDR_SSID + SSID_LEN #define ADDR_DEVNAME ADDR_PWD + PWD_LEN -#define ADDR_INV0_ADDR ADDR_DEVNAME + DEVNAME_LEN -#define ADDR_INV_INTERVAL ADDR_INV0_ADDR + INV_ADDR_LEN +#define ADDR_INV_ADDR ADDR_DEVNAME + DEVNAME_LEN +#define ADDR_INV_NAME ADDR_INV_ADDR + INV_ADDR_LEN +#define ADDR_INV_TYPE ADDR_INV_NAME + INV_NAME_LEN +#define ADDR_INV_INTERVAL ADDR_INV_TYPE + INV_TYPE_LEN #define ADDR_MQTT_ADDR ADDR_INV_INTERVAL + INV_INTERVAL_LEN #define ADDR_MQTT_USER ADDR_MQTT_ADDR + MQTT_ADDR_LEN diff --git a/tools/esp8266/hmInverters.h b/tools/esp8266/hmInverters.h index 44c89669..45c79167 100644 --- a/tools/esp8266/hmInverters.h +++ b/tools/esp8266/hmInverters.h @@ -21,7 +21,8 @@ enum {CH0 = 0, CH1, CH2, CH3, CH4}; enum {CMD01 = 0x01, CMD02, CMD03, CMD83 = 0x83, CMD84}; enum {INV_TYPE_HM600 = 0, INV_TYPE_HM1200}; - +const char* const invTypes[] = {"HM600", "HM1200"}; +#define NUM_INVERTER_TYPES 2 typedef struct { uint8_t fieldId; // field id @@ -41,7 +42,7 @@ union serial_u { typedef struct { uint8_t id; // unique id - char name[20]; // human readable name, eg. "HM-600.1" + char name[MAX_NAME_LENGTH]; // human readable name, eg. "HM-600.1" uint8_t type; // integer which refers to inverter type byteAssign_t* assign; // type of inverter uint8_t listLen; // length of assignments diff --git a/tools/esp8266/hmSystem.h b/tools/esp8266/hmSystem.h index 2e865ac6..bd573fc6 100644 --- a/tools/esp8266/hmSystem.h +++ b/tools/esp8266/hmSystem.h @@ -31,11 +31,11 @@ class HmSystem { return NULL; } inverter_t *p = &mInverter[mNumInv]; - p->id = mNumInv++; + p->id = mNumInv; p->serial.u64 = serial; p->type = type; uint8_t len = strlen(name); - strncpy(p->name, name, (len > 20) ? 20 : len); + strncpy(p->name, name, (len > MAX_NAME_LENGTH) ? MAX_NAME_LENGTH : len); getAssignment(p); toRadioId(p); @@ -44,8 +44,9 @@ class HmSystem { return NULL; } else { - mRecord = new RECORDTYPE[p->listLen]; - memset(mRecord, 0, sizeof(RECORDTYPE) * p->listLen); + mRecord[p->id] = new RECORDTYPE[p->listLen]; + memset(mRecord[p->id], 0, sizeof(RECORDTYPE) * p->listLen); + mNumInv ++; return p; } } @@ -105,11 +106,20 @@ class HmSystem { val |= buf[ptr]; } while(++ptr != end); - mRecord[pos] = (RECORDTYPE)(val) / (RECORDTYPE)(div); + mRecord[p->id][pos] = (RECORDTYPE)(val) / (RECORDTYPE)(div); } RECORDTYPE getValue(inverter_t *p, uint8_t pos) { - return mRecord[pos]; + return mRecord[p->id][pos]; + } + + uint8_t getPosByChField(inverter_t *p, uint8_t channel, uint8_t fieldId) { + uint8_t pos = 0; + for(; pos < p->listLen; pos++) { + if((p->assign[pos].ch == channel) && (p->assign[pos].fieldId == fieldId)) + break; + } + return (pos >= p->listLen) ? 0xff : pos; } uint8_t getNumInverters(void) { @@ -141,9 +151,9 @@ class HmSystem { } } - inverter_t mInverter[MAX_INVERTER]; // TODO: only one inverter supported!!! + inverter_t mInverter[MAX_INVERTER]; uint8_t mNumInv; - RECORDTYPE *mRecord; + RECORDTYPE *mRecord[MAX_INVERTER]; }; #endif /*__HM_SYSTEM_H__*/ diff --git a/tools/esp8266/html/h/setup_html.h b/tools/esp8266/html/h/setup_html.h index 8dd9a2dc..16db89b7 100644 --- a/tools/esp8266/html/h/setup_html.h +++ b/tools/esp8266/html/h/setup_html.h @@ -1 +1 @@ -String setup_html = "Setup - {DEVICE}

Setup

Enter the credentials to your prefered WiFi station. After rebooting the device tries to connect with this information.

WiFi

Device Host Name

Inverter

{INVERTERS}

MQTT

 

Home

Update Firmware

AHOY - {VERSION}

"; +String setup_html = "Setup - {DEVICE}

Setup

Enter the credentials to your prefered WiFi station. After rebooting the device tries to connect with this information.

WiFi

Device Host Name

Inverter

{INVERTERS}

General

MQTT

 

Home

Update Firmware

AHOY - {VERSION}

"; diff --git a/tools/esp8266/html/h/style_css.h b/tools/esp8266/html/h/style_css.h index 82532bc8..031d688f 100644 --- a/tools/esp8266/html/h/style_css.h +++ b/tools/esp8266/html/h/style_css.h @@ -1 +1 @@ -String style_css = "h1 { margin: 0; padding: 20pt; font-size: 22pt; color: #fff; background-color: #006ec0; display: block; text-transform: uppercase; } html, body { font-family: Arial; margin: 0; padding: 0; } p { text-align: justify; font-size: 13pt; } .des { margin-top: 35px; font-size: 14pt; color: #006ec0; } .subdes { font-size: 13pt; color: #006ec0; margin-left: 7px; } .fw { width: 60px; display: block; float: left; } .color { width: 50px; height: 50px; border: 1px solid #ccc; } .range { width: 300px; } a:link, a:visited { text-decoration: none; font-size: 13pt; color: #006ec0; } a:hover, a:focus { color: #f00; } #content { padding: 15px 15px 60px 15px; } #footer { position: fixed; bottom: 0px; height: 45px; background-color: #006ec0; width: 100%; } #footer p { color: #fff; padding-left: 20px; padding-right: 20px; font-size: 10pt !important; } #footer a { color: #fff; } div.content { background-color: #fff; padding-bottom: 65px; overflow: hidden; } input { padding: 7px; font-size: 13pt; } input.text, input.password { width: 70%; box-sizing: border-box; margin-bottom: 10px; /*float: right;*/ border: 1px solid #ccc; } input.button { background-color: #006ec0; color: #fff; border: 0px; float: right; text-transform: uppercase; } input.cb { margin-bottom: 20px; } label { width: 20%; display: inline-block; font-size: 12pt; padding-right: 10px; margin-left: 10px; } .left { float: left; } .right { float: right; } div.module { display: block; width: 250px; height: 410px; background-color: #006ec0; display: inline-block; position: relative; margin-right: 20px; margin-bottom: 20px; } div.module .value, div.module .info, div.module .header { color: #fff; display: block; width: 100%; text-align: center; } div.module .unit { font-size: 19px; margin-left: 10px; } div.module .value { margin-top: 20px; font-size: 30px; } div.module .info { margin-top: 3px; font-size: 10px; } div.module .header { background-color: #003c80; padding: 10px 0 10px 0; } "; +String style_css = "h1 { margin: 0; padding: 20pt; font-size: 22pt; color: #fff; background-color: #006ec0; display: block; text-transform: uppercase; } html, body { font-family: Arial; margin: 0; padding: 0; } p { text-align: justify; font-size: 13pt; } .des { margin-top: 35px; font-size: 14pt; color: #006ec0; } .subdes { font-size: 13pt; color: #006ec0; margin-left: 7px; } .fw { width: 60px; display: block; float: left; } .color { width: 50px; height: 50px; border: 1px solid #ccc; } .range { width: 300px; } a:link, a:visited { text-decoration: none; font-size: 13pt; color: #006ec0; } a:hover, a:focus { color: #f00; } #content { padding: 15px 15px 60px 15px; } #footer { position: fixed; bottom: 0px; height: 45px; background-color: #006ec0; width: 100%; } #footer p { color: #fff; padding-left: 20px; padding-right: 20px; font-size: 10pt !important; } #footer a { color: #fff; } div.content { background-color: #fff; padding-bottom: 65px; overflow: hidden; } input { padding: 7px; font-size: 13pt; } input.text, input.password { width: 70%; box-sizing: border-box; margin-bottom: 10px; /*float: right;*/ border: 1px solid #ccc; } input.button { background-color: #006ec0; color: #fff; border: 0px; float: right; text-transform: uppercase; } input.cb { margin-bottom: 20px; } label { width: 20%; display: inline-block; font-size: 12pt; padding-right: 10px; margin-left: 10px; } .left { float: left; } .right { float: right; } div.ch { width: 250px; height: 410px; background-color: #006ec0; display: inline-block; margin-right: 20px; margin-bottom: 20px; } div.ch .value, div.ch .info, div.ch .head { color: #fff; display: block; width: 100%; text-align: center; } div.ch .unit { font-size: 19px; margin-left: 10px; } div.ch .value { margin-top: 20px; font-size: 30px; } div.ch .info { margin-top: 3px; font-size: 10px; } div.ch .head { background-color: #003c80; padding: 10px 0 10px 0; } "; diff --git a/tools/esp8266/html/setup.html b/tools/esp8266/html/setup.html index ec7a474a..c63c6bbc 100644 --- a/tools/esp8266/html/setup.html +++ b/tools/esp8266/html/setup.html @@ -24,6 +24,7 @@

Inverter

{INVERTERS}
+

General

diff --git a/tools/esp8266/html/style.css b/tools/esp8266/html/style.css index 573d415f..acb7ec88 100644 --- a/tools/esp8266/html/style.css +++ b/tools/esp8266/html/style.css @@ -127,39 +127,37 @@ label { float: right; } -div.module { - display: block; +div.ch { width: 250px; height: 410px; background-color: #006ec0; display: inline-block; - position: relative; margin-right: 20px; margin-bottom: 20px; } -div.module .value, div.module .info, div.module .header { +div.ch .value, div.ch .info, div.ch .head { color: #fff; display: block; width: 100%; text-align: center; } -div.module .unit { +div.ch .unit { font-size: 19px; margin-left: 10px; } -div.module .value { +div.ch .value { margin-top: 20px; font-size: 30px; } -div.module .info { +div.ch .info { margin-top: 3px; font-size: 10px; } -div.module .header { +div.ch .head { background-color: #003c80; padding: 10px 0 10px 0; }