From 82e1ceea251bad96709a8dce54e2a4922935f538 Mon Sep 17 00:00:00 2001 From: DanielR92 Date: Mon, 28 Aug 2023 17:18:38 +0200 Subject: [PATCH] first test --- ahoy.code-workspace | 3 - src/app.cpp | 9 +++ src/app.h | 4 ++ src/config/config.h | 3 + src/config/settings.h | 44 +++++++++++---- src/defines.h | 2 + src/plugins/zeroExport/zeroExport.cpp | 0 src/plugins/zeroExport/zeroExport.h | 81 +++++++++++++++++++++++++++ src/web/RestApi.h | 6 ++ src/web/html/setup.html | 38 ++++++++++++- src/web/web.h | 9 +++ 11 files changed, 185 insertions(+), 14 deletions(-) create mode 100644 src/plugins/zeroExport/zeroExport.cpp create mode 100644 src/plugins/zeroExport/zeroExport.h diff --git a/ahoy.code-workspace b/ahoy.code-workspace index 20d5909e..a1baeb11 100644 --- a/ahoy.code-workspace +++ b/ahoy.code-workspace @@ -2,9 +2,6 @@ "folders": [ { "path": "." - }, - { - "path": "src" } ], "settings": { diff --git a/src/app.cpp b/src/app.cpp index 4fd1a6a6..61f3ada1 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -119,6 +119,11 @@ void app::setup() { mPubSerial.setup(mConfig, &mSys, &mTimestamp); + // ZeroExport + if (mConfig->plugin.zexport.enabled) { + mzExport.setup(&mConfig->plugin.zexport, &mSys, mConfig); + } + #if !defined(ETHERNET) //mImprov.setup(this, mConfig->sys.deviceName, mVersion); #endif @@ -246,6 +251,10 @@ void app::regularTickers(void) { // Plugins if (mConfig->plugin.display.type != 0) everySec(std::bind(&DisplayType::tickerSecond, &mDisplay), "disp"); + // Plugins + if (mConfig->plugin.zexport.enabled) + everyMin(std::bind(&ZeroExportType::tickerSecond, &mzExport), "zExport"); + every(std::bind(&PubSerialType::tick, &mPubSerial), mConfig->serial.interval, "uart"); #if !defined(ETHERNET) //everySec([this]() { mImprov.tickSerial(); }, "impro"); diff --git a/src/app.h b/src/app.h index fb3da7f6..78a3df57 100644 --- a/src/app.h +++ b/src/app.h @@ -55,7 +55,10 @@ typedef PubSerial PubSerialType; // PLUGINS #include "plugins/Display/Display.h" +#include "plugins/zeroExport/zeroExport.h" + typedef Display DisplayType; +typedef ZeroExport ZeroExportType; class app : public IApp, public ah::Scheduler { public: @@ -361,6 +364,7 @@ class app : public IApp, public ah::Scheduler { // plugins DisplayType mDisplay; + ZeroExportType mzExport; }; #endif /*__APP_H__*/ diff --git a/src/config/config.h b/src/config/config.h index 397ad15a..eae05cd6 100644 --- a/src/config/config.h +++ b/src/config/config.h @@ -184,6 +184,9 @@ // default MQTT broker uri #define DEF_MQTT_BROKER "\0" +// default zero-export uri +#define DEF_ZEXPORT "\0" + // default MQTT port #define DEF_MQTT_PORT 1883 diff --git a/src/config/settings.h b/src/config/settings.h index 99da3265..7795e420 100644 --- a/src/config/settings.h +++ b/src/config/settings.h @@ -134,6 +134,11 @@ typedef struct { uint16_t interval; } cfgMqtt_t; +typedef struct { + char monitor_ip[ZEXPORT_ADDR_LEN]; + bool enabled; +} cfgzeroExport_t; + typedef struct { bool enabled; char name[MAX_NAME_LENGTH]; @@ -173,6 +178,7 @@ typedef struct { typedef struct { display_t display; + cfgzeroExport_t zexport; } plugins_t; typedef struct { @@ -280,6 +286,7 @@ class settings { if(root.containsKey(F("sun"))) jsonSun(root[F("sun")]); if(root.containsKey(F("serial"))) jsonSerial(root[F("serial")]); if(root.containsKey(F("mqtt"))) jsonMqtt(root[F("mqtt")]); + if(root.containsKey(F("zeroExport"))) jsonzeroExport(root[F("zeroExport")]); if(root.containsKey(F("led"))) jsonLed(root[F("led")]); if(root.containsKey(F("plugin"))) jsonPlugin(root[F("plugin")]); if(root.containsKey(F("inst"))) jsonInst(root[F("inst")]); @@ -310,6 +317,7 @@ class settings { jsonLed(root.createNestedObject(F("led")), true); jsonPlugin(root.createNestedObject(F("plugin")), true); jsonInst(root.createNestedObject(F("inst")), true); + jsonzeroExport(root.createNestedObject(F("zeroExport")), true); DPRINT(DBG_INFO, F("memory usage: ")); DBGPRINTLN(String(json.memoryUsage())); @@ -425,6 +433,9 @@ class settings { snprintf(mCfg.mqtt.topic, MQTT_TOPIC_LEN, "%s", DEF_MQTT_TOPIC); mCfg.mqtt.interval = 0; // off + snprintf(mCfg.plugin.zexport.monitor_ip, ZEXPORT_ADDR_LEN, "%s", DEF_ZEXPORT); + mCfg.plugin.zexport.enabled = false; + mCfg.inst.rstYieldMidNight = false; mCfg.inst.rstValsNotAvail = false; mCfg.inst.rstValsCommStop = false; @@ -464,11 +475,12 @@ class settings { obj[F("prot_mask")] = mCfg.sys.protectionMask; obj[F("dark")] = mCfg.sys.darkMode; obj[F("reb")] = (bool) mCfg.sys.schedReboot; - ah::ip2Char(mCfg.sys.ip.ip, buf); obj[F("ip")] = String(buf); - ah::ip2Char(mCfg.sys.ip.mask, buf); obj[F("mask")] = String(buf); - ah::ip2Char(mCfg.sys.ip.dns1, buf); obj[F("dns1")] = String(buf); - ah::ip2Char(mCfg.sys.ip.dns2, buf); obj[F("dns2")] = String(buf); - ah::ip2Char(mCfg.sys.ip.gateway, buf); obj[F("gtwy")] = String(buf); + ah::ip2Char(mCfg.sys.ip.ip, buf); obj[F("ip")] = String(buf); + ah::ip2Char(mCfg.sys.ip.mask, buf); obj[F("mask")] = String(buf); + ah::ip2Char(mCfg.sys.ip.dns1, buf); obj[F("dns1")] = String(buf); + ah::ip2Char(mCfg.sys.ip.dns2, buf); obj[F("dns2")] = String(buf); + ah::ip2Char(mCfg.sys.ip.gateway, buf); obj[F("gtwy")] = String(buf); + } else { #if !defined(ETHERNET) getChar(obj, F("ssid"), mCfg.sys.stationSsid, SSID_LEN); @@ -481,11 +493,11 @@ class settings { getVal(obj, F("prot_mask"), &mCfg.sys.protectionMask); getVal(obj, F("dark"), &mCfg.sys.darkMode); getVal(obj, F("reb"), &mCfg.sys.schedReboot); - if(obj.containsKey(F("ip"))) ah::ip2Arr(mCfg.sys.ip.ip, obj[F("ip")].as()); - if(obj.containsKey(F("mask"))) ah::ip2Arr(mCfg.sys.ip.mask, obj[F("mask")].as()); - if(obj.containsKey(F("dns1"))) ah::ip2Arr(mCfg.sys.ip.dns1, obj[F("dns1")].as()); - if(obj.containsKey(F("dns2"))) ah::ip2Arr(mCfg.sys.ip.dns2, obj[F("dns2")].as()); - if(obj.containsKey(F("gtwy"))) ah::ip2Arr(mCfg.sys.ip.gateway, obj[F("gtwy")].as()); + if(obj.containsKey(F("ip"))) ah::ip2Arr(mCfg.sys.ip.ip, obj[F("ip")].as()); + if(obj.containsKey(F("mask"))) ah::ip2Arr(mCfg.sys.ip.mask, obj[F("mask")].as()); + if(obj.containsKey(F("dns1"))) ah::ip2Arr(mCfg.sys.ip.dns1, obj[F("dns1")].as()); + if(obj.containsKey(F("dns2"))) ah::ip2Arr(mCfg.sys.ip.dns2, obj[F("dns2")].as()); + if(obj.containsKey(F("gtwy"))) ah::ip2Arr(mCfg.sys.ip.gateway, obj[F("gtwy")].as()); if(mCfg.sys.protectionMask == 0) mCfg.sys.protectionMask = DEF_PROT_INDEX | DEF_PROT_LIVE | DEF_PROT_SERIAL | DEF_PROT_SETUP @@ -605,6 +617,18 @@ class settings { } } + void jsonzeroExport(JsonObject obj, bool set = false) { + if(set) { + obj[F("en_zeroexport")] = (bool) mCfg.plugin.zexport.enabled; + obj[F("monitor_ipAddr")] = mCfg.plugin.zexport.monitor_ip; + } + else + { + getVal(obj, F("en_zeroexport"), &mCfg.plugin.zexport.enabled); + getChar(obj, F("monitor_ipAddr"), mCfg.plugin.zexport.monitor_ip, ZEXPORT_ADDR_LEN); + } + } + void jsonLed(JsonObject obj, bool set = false) { if(set) { obj[F("0")] = mCfg.led.led0; diff --git a/src/defines.h b/src/defines.h index a5df274b..8b9bf61c 100644 --- a/src/defines.h +++ b/src/defines.h @@ -84,6 +84,8 @@ enum {MQTT_STATUS_OFFLINE = 0, MQTT_STATUS_PARTIAL, MQTT_STATUS_ONLINE}; #define DEVNAME_LEN 16 #define NTP_ADDR_LEN 32 // DNS Name +#define ZEXPORT_ADDR_LEN 64 // Zero-Export Address + #define MQTT_ADDR_LEN 64 // DNS Name #define MQTT_CLIENTID_LEN 22 // number of chars is limited to 23 up to v3.1 of MQTT #define MQTT_USER_LEN 65 // there is another byte necessary for \0 diff --git a/src/plugins/zeroExport/zeroExport.cpp b/src/plugins/zeroExport/zeroExport.cpp new file mode 100644 index 00000000..e69de29b diff --git a/src/plugins/zeroExport/zeroExport.h b/src/plugins/zeroExport/zeroExport.h new file mode 100644 index 00000000..0594917c --- /dev/null +++ b/src/plugins/zeroExport/zeroExport.h @@ -0,0 +1,81 @@ +#ifndef __ZEROEXPORT__ +#define __ZEROEXPORT__ + +#include + +template + +class ZeroExport { + public: + ZeroExport() { } + + WiFiClientSecure client; + const uint8_t fingerprint[20] = {0x5A, 0xCF, 0xFE, 0xF0, 0xF1, 0xA6, 0xF4, 0x5F, 0xD2, 0x11, 0x11, 0xC6, 0x1D, 0x2F, 0x0E, 0xBC, 0x39, 0x8D, 0x50, 0xE0}; + + void setup(cfgzeroExport_t *cfg, HMSYSTEM *sys, settings_t *config) { + mCfg = cfg; + mSys = sys; + mConfig = config; + } + + void payloadEventListener(uint8_t cmd) { + mNewPayload = true; + } + + void tickerSecond() { + if (mNewPayload || ((++mLoopCnt % 10) == 0)) { + mNewPayload = false; + mLoopCnt = 0; + zero(); + } + } + + private: + void zero() { + char host[20]; + const char* meter = "/emeter/0"; + sprintf(host, mCfg->monitor_ip, meter); + + std::unique_ptrclient(new BearSSL::WiFiClientSecure); + client->setFingerprint(fingerprint); + HTTPClient https; + + DPRINTLN(DBG_INFO, host); + if (https.begin(*client, host)) // HTTPS + { + DPRINTLN(DBG_INFO, F("[HTTPS] GET...\n")); + // start connection and send HTTP header + int httpCode = https.GET(); + + // httpCode will be negative on error + if (httpCode > 0) { + // file found at server + if (httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_MOVED_PERMANENTLY) { + String payload = https.getString(); + DPRINTLN(DBG_INFO, payload); + } + } else { + DPRINTLN(DBG_INFO, https.errorToString(httpCode).c_str()); + } + + https.end(); + } + else + { + DPRINTLN(DBG_INFO, F("[HTTPS] Unable to connect\n")); + } + } + + // private member variables + bool mNewPayload; + uint8_t mLoopCnt; + uint32_t *mUtcTs; + const char *mVersion; + cfgzeroExport_t *mCfg; + + settings_t *mConfig; + HMSYSTEM *mSys; + uint16_t mRefreshCycle; +}; + +#endif /*__ZEROEXPORT__*/ \ No newline at end of file diff --git a/src/web/RestApi.h b/src/web/RestApi.h index 270cb372..695a6b9b 100644 --- a/src/web/RestApi.h +++ b/src/web/RestApi.h @@ -465,6 +465,11 @@ class RestApi { obj[F("interval")] = String(mConfig->mqtt.interval); } + void getzeroExport(JsonObject obj) { + obj[F("en_zeroexport")] = (bool) mConfig->plugin.zexport.enabled; + obj[F("monitor_ipAddr")] = String(mConfig->plugin.zexport.monitor_ip); + } + void getNtp(JsonObject obj) { obj[F("addr")] = String(mConfig->ntp.addr); obj[F("port")] = String(mConfig->ntp.port); @@ -588,6 +593,7 @@ class RestApi { getSysInfo(request, obj.createNestedObject(F("system"))); //getInverterList(obj.createNestedObject(F("inverter"))); getMqtt(obj.createNestedObject(F("mqtt"))); + getzeroExport(obj.createNestedObject(F("zeroExport"))); getNtp(obj.createNestedObject(F("ntp"))); getSun(obj.createNestedObject(F("sun"))); getPinout(obj.createNestedObject(F("pinout"))); diff --git a/src/web/html/setup.html b/src/web/html/setup.html index 94c3d461..f5c2289a 100644 --- a/src/web/html/setup.html +++ b/src/web/html/setup.html @@ -10,6 +10,7 @@
+
@@ -55,6 +56,7 @@
+
@@ -122,6 +124,7 @@
+
@@ -135,6 +138,7 @@
+
@@ -187,6 +191,7 @@
+
@@ -218,6 +223,7 @@
+
@@ -241,6 +247,7 @@
+
@@ -284,6 +291,7 @@
+
@@ -307,6 +315,25 @@
+ + +
+
+ Zero Export +
+ +
+
Enable zero export
+
+
+ +
+
Monitor IP:
+
+
+
+
+
Reboot device after successful save
@@ -691,7 +718,7 @@ } function parseStaticIp(obj) { - for(var i of [["ipAddr", "ip"], ["ipMask", "mask"], ["ipDns1", "dns1"], ["ipDns2", "dns2"], ["ipGateway", "gateway"]]) + for(var i of [["ipAddr", "ip"], ["ipMask", "mask"], ["ipDns1", "dns1"], ["ipDns2", "dns2"], ["ipGateway", "gateway"], ["monitor_ipAddr", "mon_ip"]]) if(null != obj[i[1]]) document.getElementsByName(i[0])[0].value = obj[i[1]]; } @@ -897,6 +924,14 @@ document.getElementById("date").innerHTML = toIsoDateStr((new Date((++ts) * 1000))); } + function parsezeroExport(obj) { + // keep display types grouped + var opts = [[0, "None"], [1, "Shelly EM3"]]; + + document.getElementsByName("monitor_ipAddr")[0].value = obj["monitor_ipAddr"]; + + } + function parse(root) { if(null != root) { parseSys(root["system"]); @@ -911,6 +946,7 @@ parseCmtRadio(root["radioCmt"], root["system"]["esp_type"], root["system"]); parseSerial(root["serial"]); parseDisplay(root["display"], root["system"]["esp_type"], root["system"]); + parsezeroExport(root["zeroExport"]); getAjax("/api/inverter/list", parseIv); } } diff --git a/src/web/web.h b/src/web/web.h index f1a14e2b..30127408 100644 --- a/src/web/web.h +++ b/src/web/web.h @@ -603,6 +603,15 @@ class Web { mConfig->mqtt.port = request->arg("mqttPort").toInt(); mConfig->mqtt.interval = request->arg("mqttInterval").toInt(); + // zero-export + mConfig->plugin.zexport.enabled = (request->arg("en_zeroexport") == "on"); + if (request->arg("monitor_ipAddr") != "") { + String addr = request->arg("monitor_ipAddr"); + addr.trim(); + addr.toCharArray(mConfig->plugin.zexport.monitor_ip, ZEXPORT_ADDR_LEN); + } else + mConfig->plugin.zexport.monitor_ip[0] = '\0'; + // serial console if (request->arg("serIntvl") != "") { mConfig->serial.interval = request->arg("serIntvl").toInt() & 0xffff;