//----------------------------------------------------------------------------- // 2024 Ahoy, https://ahoydtu.de // Creative Commons - http://creativecommons.org/licenses/by-nc-sa/4.0/deed //----------------------------------------------------------------------------- #ifndef __SETTINGS_H__ #define __SETTINGS_H__ #if defined(F) && defined(ESP32) #undef F #define F(sl) (sl) #endif #include #include #include #include #include "../defines.h" #include "../utils/dbg.h" #include "../utils/helper.h" #if defined(ESP32) #define MAX_ALLOWED_BUF_SIZE ESP.getMaxAllocHeap() - 1024 #else #define MAX_ALLOWED_BUF_SIZE ESP.getMaxFreeBlockSize() - 1024 #endif /** * More info: * https://arduino-esp8266.readthedocs.io/en/latest/filesystem.html#flash-layout * */ #define CONFIG_VERSION 11 #define PROT_MASK_INDEX 0x0001 #define PROT_MASK_LIVE 0x0002 #define PROT_MASK_SERIAL 0x0004 #define PROT_MASK_SETUP 0x0008 #define PROT_MASK_UPDATE 0x0010 #define PROT_MASK_SYSTEM 0x0020 #define PROT_MASK_HISTORY 0x0040 #define PROT_MASK_API 0x0080 #define PROT_MASK_MQTT 0x0100 #define DEF_PROT_INDEX 0x0001 #define DEF_PROT_LIVE 0x0000 #define DEF_PROT_SERIAL 0x0004 #define DEF_PROT_SETUP 0x0008 #define DEF_PROT_UPDATE 0x0010 #define DEF_PROT_SYSTEM 0x0020 #define DEF_PROT_HISTORY 0x0000 #define DEF_PROT_API 0x0000 #define DEF_PROT_MQTT 0x0000 #define SSID_LEN 32 #define PWD_LEN 64 #define DEVNAME_LEN 16 #define NTP_ADDR_LEN 32 // DNS Name #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 #define MQTT_PWD_LEN 65 #define MQTT_TOPIC_LEN 65 #define MQTT_MAX_PACKET_SIZE 384 #if defined(PLUGIN_ZEROEXPORT) #define ZEXPORT_ADDR_LEN 100 // Zero-Export Address #endif /*PLUGIN_ZEROEXPORT*/ typedef struct { uint8_t ip[4]; // ip address uint8_t mask[4]; // sub mask uint8_t dns1[4]; // dns 1 uint8_t dns2[4]; // dns 2 uint8_t gateway[4]; // standard gateway } cfgIp_t; #if defined(ETHERNET) typedef struct { bool enabled; uint8_t pinCs; uint8_t pinSclk; uint8_t pinMiso; uint8_t pinMosi; uint8_t pinIrq; uint8_t pinRst; } cfgEth_t; #endif typedef struct { char deviceName[DEVNAME_LEN]; char adminPwd[PWD_LEN]; uint16_t protectionMask; bool darkMode; bool schedReboot; uint8_t region; int8_t timezone; char apPwd[PWD_LEN]; #if !defined(ETHERNET) // wifi char stationSsid[SSID_LEN]; char stationPwd[PWD_LEN]; bool isHidden; #else cfgEth_t eth; #endif /* !defined(ETHERNET) */ cfgIp_t ip; } cfgSys_t; typedef struct { bool enabled; uint8_t pinCs; uint8_t pinCe; uint8_t pinIrq; uint8_t pinMiso; uint8_t pinMosi; uint8_t pinSclk; } cfgNrf24_t; typedef struct { bool enabled; uint8_t pinSclk; uint8_t pinSdio; uint8_t pinCsb; uint8_t pinFcsb; uint8_t pinIrq; } cfgCmt_t; typedef struct { char addr[NTP_ADDR_LEN]; uint16_t port; uint16_t interval; // in minutes } cfgNtp_t; typedef struct { float lat; float lon; int16_t offsetSecMorning; int16_t offsetSecEvening; } cfgSun_t; typedef struct { bool showIv; bool debug; bool privacyLog; bool printWholeTrace; bool log2mqtt; } cfgSerial_t; typedef struct { uint8_t led[3]; // LED pins bool high_active; // determines if LEDs are high or low active uint8_t luminance; // luminance of LED } cfgLed_t; typedef struct { char broker[MQTT_ADDR_LEN]; uint16_t port; char clientId[MQTT_CLIENTID_LEN]; char user[MQTT_USER_LEN]; char pwd[MQTT_PWD_LEN]; char topic[MQTT_TOPIC_LEN]; bool json; uint16_t interval; bool enableRetain; } cfgMqtt_t; typedef struct { bool enabled; char name[MAX_NAME_LENGTH]; serial_u serial; uint16_t chMaxPwr[6]; double yieldCor[6]; // YieldTotal correction value char chName[6][MAX_NAME_LENGTH]; uint8_t frequency; uint8_t powerLevel; bool disNightCom; // disable night communication } cfgIv_t; typedef struct { // bool enabled; cfgIv_t iv[MAX_NUM_INVERTERS]; uint16_t sendInterval; bool rstValsAtMidNight; bool rstValsNotAvail; bool rstValsCommStop; bool rstValsCommStart; bool rstIncludeMaxVals; bool startWithoutTime; bool readGrid; } cfgInst_t; #if defined(PLUGIN_DISPLAY) typedef struct { uint8_t type; bool pwrSaveAtIvOffline; uint8_t screenSaver; uint8_t graph_ratio; uint8_t graph_size; uint8_t rot; //uint16_t wakeUp; //uint16_t sleepAt; uint8_t contrast; uint8_t disp_data; uint8_t disp_clk; uint8_t disp_cs; uint8_t disp_reset; uint8_t disp_busy; uint8_t disp_dc; uint8_t pirPin; } display_t; #endif // Plugin ZeroExport #if defined(PLUGIN_ZEROEXPORT) #define ZEROEXPORT_MAX_QUEUE_ENTRIES 64 #define ZEROEXPORT_MAX_GROUPS 8 #define ZEROEXPORT_GROUP_MAX_LEN_NAME 25 #define ZEROEXPORT_GROUP_MAX_LEN_PM_SRC 100 #define ZEROEXPORT_GROUP_MAX_LEN_PM_JSONPATH 100 #define ZEROEXPORT_GROUP_MAX_LEN_PM_USER 25 #define ZEROEXPORT_GROUP_MAX_LEN_PM_PASS 25 #define ZEROEXPORT_GROUP_MAX_LEN_BATT_TOPIC 100 #define ZEROEXPORT_GROUP_MAX_INVERTERS 3 #define ZEROEXPORT_POWERMETER_MAX_ERRORS 5 #define ZEROEXPORT_GROUP_WR_LIMIT_MIN_DIFF 2 #define ZEROEXPORT_POWERMETER_SHELLY //#define ZEROEXPORT_POWERMETER_TASMOTA #define ZEROEXPORT_POWERMETER_MQTT //#define ZEROEXPORT_POWERMETER_HICHI #define ZEROEXPORT_POWERMETER_TIBBER #define ZEROEXPORT_POWERMETER_SHRDZM typedef enum { None = 0, Shelly = 1, Tasmota = 2, Mqtt = 3, Hichi = 4, Tibber = 5, Shrdzm = 6, } zeroExportPowermeterType_t; typedef enum { Sum = 0, L1 = 1, L2 = 2, L3 = 3, } zeroExportPowermeterTarget; typedef enum { none = 0, invUdc, mqttU, mqttSoC } zeroExportBatteryCfg; typedef enum { doNone = 0, doRestart, doTurnOn, doTurnOff, doActivePowerContr, } zeroExportAction_t; typedef struct { uint8_t group; uint8_t inv; uint8_t id; } zeroExportQueue_t; typedef struct { bool enabled; int8_t id; uint16_t powerMin; uint16_t powerMax; bool turnOff; // zeroExportAction_t action; int8_t actionTimer; unsigned long actionTimestamp; uint16_t power; uint16_t MaxPower; int32_t limit; int32_t limitNew; uint8_t waitAck; // float dcVoltage; } zeroExportGroupInverter_t; typedef struct { // General bool enabled; bool sleep; char name[ZEROEXPORT_GROUP_MAX_LEN_NAME]; // Powermeter uint8_t pm_refresh; unsigned long pm_peviousTsp; uint8_t pm_type; char pm_src[ZEROEXPORT_GROUP_MAX_LEN_PM_SRC]; char pm_jsonPath[ZEROEXPORT_GROUP_MAX_LEN_PM_JSONPATH]; char pm_user[ZEROEXPORT_GROUP_MAX_LEN_PM_USER]; char pm_pass[ZEROEXPORT_GROUP_MAX_LEN_PM_PASS]; uint8_t pm_target; // Inverters zeroExportGroupInverter_t inverters[ZEROEXPORT_GROUP_MAX_INVERTERS]; // Battery uint8_t battCfg; char battTopic[ZEROEXPORT_GROUP_MAX_LEN_BATT_TOPIC]; float battValue; float battLimitOn; float battLimitOff; // Advanced int16_t setPoint; bool minimum; float power; uint8_t powerTolerance; uint16_t powerMax; // unsigned long lastRun; unsigned long lastRefresh; uint16_t wait; bool battSwitchInit; bool battSwitch; // PID controller float eSum; float eOld; uint8_t Kp; uint8_t Ki; uint8_t Kd; float y; } zeroExportGroup_t; typedef struct { bool enabled; bool sleep; bool log_over_webserial; bool log_over_mqtt; bool debug; zeroExportGroup_t groups[ZEROEXPORT_MAX_GROUPS]; } zeroExport_t; #endif // Plugin ZeroExport - Ende typedef struct { #if defined(PLUGIN_DISPLAY) display_t display; #endif char customLink[MAX_CUSTOM_LINK_LEN]; char customLinkText[MAX_CUSTOM_LINK_TEXT_LEN]; #if defined(PLUGIN_ZEROEXPORT) zeroExport_t zeroExport; #endif } plugins_t; typedef struct { cfgSys_t sys; cfgNrf24_t nrf; cfgCmt_t cmt; cfgNtp_t ntp; cfgSun_t sun; cfgSerial_t serial; cfgMqtt_t mqtt; cfgLed_t led; cfgInst_t inst; plugins_t plugin; bool valid; uint16_t configVersion; } settings_t; class settings { public: settings() { std::fill(reinterpret_cast(&mCfg), reinterpret_cast(&mCfg) + sizeof(mCfg), 0); } void setup(settings_t *&c) { DPRINTLN(DBG_INFO, F("Initializing FS ..")); c = &mCfg; mCfg.valid = false; #if !defined(ESP32) LittleFSConfig cfg; cfg.setAutoFormat(false); LittleFS.setConfig(cfg); #define LITTLFS_TRUE #define LITTLFS_FALSE #else #define LITTLFS_TRUE true #define LITTLFS_FALSE false #endif if(!LittleFS.begin(LITTLFS_FALSE)) { DPRINTLN(DBG_INFO, F(".. format ..")); LittleFS.format(); if(LittleFS.begin(LITTLFS_TRUE)) { DPRINTLN(DBG_INFO, F(".. success")); } else { DPRINTLN(DBG_INFO, F(".. failed")); } } else DPRINTLN(DBG_INFO, F(" .. done")); readSettings("/settings.json"); } // should be used before OTA void stop() { LittleFS.end(); DPRINTLN(DBG_INFO, F("FS stopped")); } inline bool getLastSaveSucceed() { return mLastSaveSucceed; } void getInfo(uint32_t *used, uint32_t *size) { #if !defined(ESP32) FSInfo info; LittleFS.info(info); *used = info.usedBytes; *size = info.totalBytes; DPRINTLN(DBG_INFO, F("-- FILESYSTEM INFO --")); DPRINTLN(DBG_INFO, String(info.usedBytes) + F(" of ") + String(info.totalBytes) + F(" used")); #else DPRINTLN(DBG_WARN, F("not supported by ESP32")); #endif } bool readSettings(const char* path) { loadDefaults(); File fp = LittleFS.open(path, "r"); if(!fp) DPRINTLN(DBG_WARN, F("failed to load json, using default config")); else { //DPRINTLN(DBG_INFO, fp.readString()); //fp.seek(0, SeekSet); DynamicJsonDocument root(MAX_ALLOWED_BUF_SIZE); DeserializationError err = deserializeJson(root, fp); root.shrinkToFit(); if(!err && (root.size() > 0)) { mCfg.valid = true; if(root.containsKey(F("wifi"))) jsonNetwork(root[F("wifi")]); if(root.containsKey(F("nrf"))) jsonNrf(root[F("nrf")]); #if defined(ESP32) if(root.containsKey(F("cmt"))) jsonCmt(root[F("cmt")]); #endif if(root.containsKey(F("ntp"))) jsonNtp(root[F("ntp")]); 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("led"))) jsonLed(root[F("led")]); if(root.containsKey(F("plugin"))) jsonPlugin(root[F("plugin")]); if(root.containsKey(F("inst"))) jsonInst(root[F("inst")]); getConfigVersion(root.as()); } else { Serial.println(F("failed to parse json, using default config")); } fp.close(); } return mCfg.valid; } bool saveSettings() { DPRINTLN(DBG_DEBUG, F("save settings")); DynamicJsonDocument json(MAX_ALLOWED_BUF_SIZE); JsonObject root = json.to(); json[F("version")] = CONFIG_VERSION; jsonNetwork(root[F("wifi")].to(), true); jsonNrf(root[F("nrf")].to(), true); #if defined(ESP32) jsonCmt(root[F("cmt")].to(), true); #endif jsonNtp(root[F("ntp")].to(), true); jsonSun(root[F("sun")].to(), true); jsonSerial(root[F("serial")].to(), true); jsonMqtt(root[F("mqtt")].to(), true); jsonLed(root[F("led")].to(), true); jsonPlugin(root[F("plugin")].to(), true); jsonInst(root[F("inst")].to(), true); DPRINT(DBG_INFO, F("memory usage: ")); DBGPRINTLN(String(json.memoryUsage())); DPRINT(DBG_INFO, F("capacity: ")); DBGPRINTLN(String(json.capacity())); DPRINT(DBG_INFO, F("max alloc: ")); DBGPRINTLN(String(MAX_ALLOWED_BUF_SIZE)); if(json.overflowed()) { DPRINTLN(DBG_ERROR, F("buffer too small!")); mLastSaveSucceed = false; return false; } File fp = LittleFS.open("/settings.json", "w"); if(!fp) { DPRINTLN(DBG_ERROR, F("can't open settings file!")); mLastSaveSucceed = false; return false; } if(0 == serializeJson(root, fp)) { DPRINTLN(DBG_ERROR, F("can't write settings file!")); mLastSaveSucceed = false; return false; } fp.close(); DPRINTLN(DBG_INFO, F("settings saved")); mLastSaveSucceed = true; return true; } bool eraseSettings(bool eraseWifi = false) { if(true == eraseWifi) return LittleFS.format(); loadDefaults(!eraseWifi); return saveSettings(); } private: void loadDefaults(bool keepWifi = false) { DPRINTLN(DBG_VERBOSE, F("loadDefaults")); cfgSys_t tmp; if(keepWifi) { // copy contents which should not be deleted memset(&tmp.adminPwd, 0, PWD_LEN); memcpy(&tmp, &mCfg.sys, sizeof(cfgSys_t)); } // erase all settings and reset to default std::fill(reinterpret_cast(&mCfg), reinterpret_cast(&mCfg) + sizeof(mCfg), 0); mCfg.sys.protectionMask = DEF_PROT_INDEX | DEF_PROT_LIVE | DEF_PROT_SERIAL | DEF_PROT_SETUP | DEF_PROT_UPDATE | DEF_PROT_SYSTEM | DEF_PROT_API | DEF_PROT_MQTT | DEF_PROT_HISTORY; mCfg.sys.darkMode = false; mCfg.sys.schedReboot = false; // restore temp settings if(keepWifi) memcpy(&mCfg.sys, &tmp, sizeof(cfgSys_t)); #if !defined(ETHERNET) else { snprintf(mCfg.sys.stationSsid, SSID_LEN, FB_WIFI_SSID); snprintf(mCfg.sys.stationPwd, PWD_LEN, FB_WIFI_PWD); mCfg.sys.isHidden = false; } #endif snprintf(mCfg.sys.apPwd, PWD_LEN, WIFI_AP_PWD); #if defined(ETHERNET) #if defined(DEF_ETH_ENABLED) mCfg.sys.eth.enabled = true; #else mCfg.sys.eth.enabled = false; #endif mCfg.sys.eth.pinCs = DEF_ETH_CS_PIN; mCfg.sys.eth.pinSclk = DEF_ETH_SCK_PIN; mCfg.sys.eth.pinMiso = DEF_ETH_MISO_PIN; mCfg.sys.eth.pinMosi = DEF_ETH_MOSI_PIN; mCfg.sys.eth.pinIrq = DEF_ETH_IRQ_PIN; mCfg.sys.eth.pinRst = DEF_ETH_RST_PIN; #endif snprintf(mCfg.sys.deviceName, DEVNAME_LEN, DEF_DEVICE_NAME); mCfg.sys.region = 0; // Europe mCfg.sys.timezone = 1; mCfg.nrf.pinCs = DEF_NRF_CS_PIN; mCfg.nrf.pinCe = DEF_NRF_CE_PIN; mCfg.nrf.pinIrq = DEF_NRF_IRQ_PIN; mCfg.nrf.pinMiso = DEF_NRF_MISO_PIN; mCfg.nrf.pinMosi = DEF_NRF_MOSI_PIN; mCfg.nrf.pinSclk = DEF_NRF_SCLK_PIN; #if defined(ETHERNET) mCfg.nrf.enabled = false; #else mCfg.nrf.enabled = true; #endif #if defined(ESP32) mCfg.cmt.pinSclk = DEF_CMT_SCLK; mCfg.cmt.pinSdio = DEF_CMT_SDIO; mCfg.cmt.pinCsb = DEF_CMT_CSB; mCfg.cmt.pinFcsb = DEF_CMT_FCSB; mCfg.cmt.pinIrq = DEF_CMT_IRQ; #else mCfg.cmt.pinSclk = DEF_PIN_OFF; mCfg.cmt.pinSdio = DEF_PIN_OFF; mCfg.cmt.pinCsb = DEF_PIN_OFF; mCfg.cmt.pinFcsb = DEF_PIN_OFF; mCfg.cmt.pinIrq = DEF_PIN_OFF; #endif mCfg.cmt.enabled = false; snprintf(mCfg.ntp.addr, NTP_ADDR_LEN, "%s", DEF_NTP_SERVER_NAME); mCfg.ntp.port = DEF_NTP_PORT; mCfg.ntp.interval = 720; mCfg.sun.lat = 51.1; // mid of Germany mCfg.sun.lon = 10.5; // mid of Germany mCfg.sun.offsetSecMorning = 0; mCfg.sun.offsetSecEvening = 0; mCfg.serial.showIv = false; mCfg.serial.debug = false; mCfg.serial.privacyLog = true; mCfg.serial.printWholeTrace = false; mCfg.serial.log2mqtt = false; mCfg.mqtt.port = DEF_MQTT_PORT; snprintf(mCfg.mqtt.broker, MQTT_ADDR_LEN, "%s", DEF_MQTT_BROKER); snprintf(mCfg.mqtt.user, MQTT_USER_LEN, "%s", DEF_MQTT_USER); snprintf(mCfg.mqtt.pwd, MQTT_PWD_LEN, "%s", DEF_MQTT_PWD); snprintf(mCfg.mqtt.topic, MQTT_TOPIC_LEN, "%s", DEF_MQTT_TOPIC); mCfg.mqtt.interval = 0; // off mCfg.mqtt.json = false; // off mCfg.mqtt.enableRetain = true; mCfg.inst.sendInterval = SEND_INTERVAL; mCfg.inst.rstValsAtMidNight = false; mCfg.inst.rstValsNotAvail = false; mCfg.inst.rstValsCommStop = false; mCfg.inst.rstValsCommStart = false; mCfg.inst.startWithoutTime = false; mCfg.inst.rstIncludeMaxVals = false; mCfg.inst.readGrid = true; for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i++) { mCfg.inst.iv[i].powerLevel = 0xff; // impossible high value mCfg.inst.iv[i].frequency = 0x12; // 863MHz (minimum allowed frequency) mCfg.inst.iv[i].disNightCom = false; } mCfg.led.led[0] = DEF_LED0; mCfg.led.led[1] = DEF_LED1; mCfg.led.led[2] = DEF_LED2; mCfg.led.high_active = LED_HIGH_ACTIVE; mCfg.led.luminance = 255; #if defined(PLUGIN_DISPLAY) mCfg.plugin.display.pwrSaveAtIvOffline = false; mCfg.plugin.display.contrast = 140; mCfg.plugin.display.screenSaver = 1; // default: 1 .. pixelshift for OLED for downward compatibility mCfg.plugin.display.graph_ratio = 0; mCfg.plugin.display.graph_size = 2; mCfg.plugin.display.rot = 0; mCfg.plugin.display.disp_data = DEF_PIN_OFF; // SDA mCfg.plugin.display.disp_clk = DEF_PIN_OFF; // SCL mCfg.plugin.display.disp_cs = DEF_PIN_OFF; mCfg.plugin.display.disp_reset = DEF_PIN_OFF; mCfg.plugin.display.disp_busy = DEF_PIN_OFF; mCfg.plugin.display.disp_dc = DEF_PIN_OFF; mCfg.plugin.display.pirPin = DEF_PIN_OFF; #endif // Plugin ZeroExport #if defined(PLUGIN_ZEROEXPORT) mCfg.plugin.zeroExport.enabled = false; mCfg.plugin.zeroExport.sleep = false; mCfg.plugin.zeroExport.log_over_webserial = false; mCfg.plugin.zeroExport.log_over_mqtt = false; mCfg.plugin.zeroExport.debug = false; for(uint8_t group = 0; group < ZEROEXPORT_MAX_GROUPS; group++) { // General mCfg.plugin.zeroExport.groups[group].enabled = false; mCfg.plugin.zeroExport.groups[group].sleep = false; snprintf(mCfg.plugin.zeroExport.groups[group].name, ZEROEXPORT_GROUP_MAX_LEN_NAME, "%s", DEF_ZEXPORT); // Powermeter mCfg.plugin.zeroExport.groups[group].pm_refresh = 5; mCfg.plugin.zeroExport.groups[group].pm_peviousTsp = 0; mCfg.plugin.zeroExport.groups[group].pm_type = zeroExportPowermeterType_t::None; snprintf(mCfg.plugin.zeroExport.groups[group].pm_src, ZEROEXPORT_GROUP_MAX_LEN_PM_SRC, "%s", DEF_ZEXPORT); snprintf(mCfg.plugin.zeroExport.groups[group].pm_jsonPath, ZEROEXPORT_GROUP_MAX_LEN_PM_JSONPATH, "%s", DEF_ZEXPORT); snprintf(mCfg.plugin.zeroExport.groups[group].pm_user, ZEROEXPORT_GROUP_MAX_LEN_PM_USER, "%s", DEF_ZEXPORT); snprintf(mCfg.plugin.zeroExport.groups[group].pm_pass, ZEROEXPORT_GROUP_MAX_LEN_PM_PASS, "%s", DEF_ZEXPORT); mCfg.plugin.zeroExport.groups[group].pm_target = zeroExportPowermeterTarget::Sum; // Inverters for(uint8_t inv = 0; inv < ZEROEXPORT_GROUP_MAX_INVERTERS; inv++) { mCfg.plugin.zeroExport.groups[group].inverters[inv].enabled = false; mCfg.plugin.zeroExport.groups[group].inverters[inv].id = -1; mCfg.plugin.zeroExport.groups[group].inverters[inv].powerMin = 10; mCfg.plugin.zeroExport.groups[group].inverters[inv].powerMax = 600; mCfg.plugin.zeroExport.groups[group].inverters[inv].turnOff = false; // mCfg.plugin.zeroExport.groups[group].inverters[inv].waitAck = 0; mCfg.plugin.zeroExport.groups[group].inverters[inv].action = zeroExportAction_t::doNone; mCfg.plugin.zeroExport.groups[group].inverters[inv].actionTimer = 0;; mCfg.plugin.zeroExport.groups[group].inverters[inv].dcVoltage = 0; mCfg.plugin.zeroExport.groups[group].inverters[inv].limit = 0; mCfg.plugin.zeroExport.groups[group].inverters[inv].limitNew = 0; } // Battery mCfg.plugin.zeroExport.groups[group].battCfg = zeroExportBatteryCfg::none; snprintf(mCfg.plugin.zeroExport.groups[group].battTopic, ZEROEXPORT_GROUP_MAX_LEN_BATT_TOPIC, "%s", DEF_ZEXPORT); mCfg.plugin.zeroExport.groups[group].battLimitOn = 0; mCfg.plugin.zeroExport.groups[group].battLimitOff = 0; // Advanced mCfg.plugin.zeroExport.groups[group].setPoint = 0; mCfg.plugin.zeroExport.groups[group].minimum = true; mCfg.plugin.zeroExport.groups[group].powerTolerance = 10; mCfg.plugin.zeroExport.groups[group].powerMax = 600; mCfg.plugin.zeroExport.groups[group].Kp = 50; mCfg.plugin.zeroExport.groups[group].Ki = 0; mCfg.plugin.zeroExport.groups[group].Kd = 0; // mCfg.plugin.zeroExport.groups[group].lastRun = 0; mCfg.plugin.zeroExport.groups[group].lastRefresh = 0; mCfg.plugin.zeroExport.groups[group].wait = 60000; // mCfg.plugin.zeroExport.groups[group].pm_P = 0; // mCfg.plugin.zeroExport.groups[group].pm_P1 = 0; // mCfg.plugin.zeroExport.groups[group].pm_P2 = 0; // mCfg.plugin.zeroExport.groups[group].pm_P3 = 0; mCfg.plugin.zeroExport.groups[group].battSwitchInit = false; mCfg.plugin.zeroExport.groups[group].battSwitch = false; mCfg.plugin.zeroExport.groups[group].power = 0; } // snprintf(mCfg.plugin.zeroExport.monitor_url, ZEXPORT_ADDR_LEN, "%s", DEF_ZEXPORT); // snprintf(mCfg.plugin.zeroExport.tibber_pw, ZEXPORT_ADDR_LEN, "%s", DEF_ZEXPORT); // snprintf(mCfg.plugin.zeroExport.json_path, ZEXPORT_ADDR_LEN, "%s", DEF_ZEXPORT); // mCfg.plugin.zeroExport.enabled = false; // mCfg.plugin.zeroExport.count_avg = 10; // mCfg.plugin.zeroExport.lastTime = millis(); // do not change! // mCfg.plugin.zeroExport.query_device = 1; // Standard shelly // mCfg.plugin.zeroExport.power_avg = 10; // mCfg.plugin.zeroExport.Iv = 0; // mCfg.plugin.zeroExport.max_power = 600; // Max 600W to stay safe // mCfg.plugin.zeroExport.two_percent = true; // uint8_t ip; // uint8_t url; // bool login; // uint8_t username; // uint8_t password; // snprintf(mCfg.plugin.zeroExport.monitor_url, ZEXPORT_ADDR_LEN, "%s", DEF_ZEXPORT); // snprintf(mCfg.plugin.zeroExport.monitor_url, ZEXPORT_ADDR_LEN, "%s", DEF_ZEXPORT); // snprintf(mCfg.plugin.zeroExport.monitor_url, ZEXPORT_ADDR_LEN, "%s", DEF_ZEXPORT); // uint8_t group; // uint8_t phase; // mCfg.plugin.zeroExport.groups[group].powermeter.nextRun = 0; // mCfg.plugin.zeroExport.groups[group].powermeter.interval = 10000; // mCfg.plugin.zeroExport.groups[group].powermeter.error = 0; // mCfg.plugin.zeroExport.groups[group].powermeter.power = 0; // mCfg.plugin.zeroExport.groups[group].inverters[inv].waitingTime = 0; // mCfg.plugin.zeroExport.groups[group].inverters[inv].limit = -1; // mCfg.plugin.zeroExport.groups[group].inverters[inv].limitAck = false; // Plugin ZeroExport - Ende #endif } void loadAddedDefaults() { for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i++) { if(mCfg.configVersion < 1) { mCfg.inst.iv[i].powerLevel = 0xff; // impossible high value mCfg.inst.iv[i].frequency = 0x0; // 860MHz (backward compatibility) } if(mCfg.configVersion < 2) { mCfg.inst.iv[i].disNightCom = false; } if(mCfg.configVersion < 3) { mCfg.serial.printWholeTrace = false; } if(mCfg.configVersion < 5) { mCfg.inst.sendInterval = SEND_INTERVAL; mCfg.serial.printWholeTrace = false; } if(mCfg.configVersion < 6) { mCfg.inst.readGrid = true; } if(mCfg.configVersion < 7) { mCfg.led.luminance = 255; } if(mCfg.configVersion < 8) { mCfg.sun.offsetSecEvening = mCfg.sun.offsetSecMorning; } if(mCfg.configVersion < 10) { mCfg.sys.region = 0; // Europe mCfg.sys.timezone = 1; } if(mCfg.configVersion < 11) { mCfg.serial.log2mqtt = false; } } } void getConfigVersion(JsonObject obj) { getVal(obj, F("version"), &mCfg.configVersion); DPRINT(DBG_INFO, F("Config Version: ")); DBGPRINTLN(String(mCfg.configVersion)); if(CONFIG_VERSION != mCfg.configVersion) loadAddedDefaults(); } void jsonNetwork(JsonObject obj, bool set = false) { if(set) { char buf[16]; #if !defined(ETHERNET) obj[F("ssid")] = mCfg.sys.stationSsid; obj[F("pwd")] = mCfg.sys.stationPwd; obj[F("ap_pwd")] = mCfg.sys.apPwd; obj[F("hidd")] = (bool) mCfg.sys.isHidden; #endif /* !defined(ETHERNET) */ obj[F("dev")] = mCfg.sys.deviceName; obj[F("adm")] = mCfg.sys.adminPwd; obj[F("prot_mask")] = mCfg.sys.protectionMask; obj[F("dark")] = mCfg.sys.darkMode; obj[F("reb")] = (bool) mCfg.sys.schedReboot; obj[F("region")] = mCfg.sys.region; obj[F("timezone")] = mCfg.sys.timezone; 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); #if defined(ETHERNET) obj[F("en")] = mCfg.sys.eth.enabled; obj[F("cs")] = mCfg.sys.eth.pinCs; obj[F("sclk")] = mCfg.sys.eth.pinSclk; obj[F("miso")] = mCfg.sys.eth.pinMiso; obj[F("mosi")] = mCfg.sys.eth.pinMosi; obj[F("irq")] = mCfg.sys.eth.pinIrq; obj[F("rst")] = mCfg.sys.eth.pinRst; #endif } else { #if !defined(ETHERNET) getChar(obj, F("ssid"), mCfg.sys.stationSsid, SSID_LEN); getChar(obj, F("pwd"), mCfg.sys.stationPwd, PWD_LEN); getChar(obj, F("ap_pwd"), mCfg.sys.apPwd, PWD_LEN); getVal(obj, F("hidd"), &mCfg.sys.isHidden); #endif /* !defined(ETHERNET) */ getChar(obj, F("dev"), mCfg.sys.deviceName, DEVNAME_LEN); getChar(obj, F("adm"), mCfg.sys.adminPwd, PWD_LEN); getVal(obj, F("prot_mask"), &mCfg.sys.protectionMask); getVal(obj, F("dark"), &mCfg.sys.darkMode); getVal(obj, F("reb"), &mCfg.sys.schedReboot); getVal(obj, F("region"), &mCfg.sys.region); getVal(obj, F("timezone"), &mCfg.sys.timezone); 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 | DEF_PROT_UPDATE | DEF_PROT_SYSTEM | DEF_PROT_API | DEF_PROT_MQTT | DEF_PROT_HISTORY; #if defined(ETHERNET) getVal(obj, F("en"), &mCfg.sys.eth.enabled); getVal(obj, F("cs"), &mCfg.sys.eth.pinCs); getVal(obj, F("sclk"), &mCfg.sys.eth.pinSclk); getVal(obj, F("miso"), &mCfg.sys.eth.pinMiso); getVal(obj, F("mosi"), &mCfg.sys.eth.pinMosi); getVal(obj, F("irq"), &mCfg.sys.eth.pinIrq); getVal(obj, F("rst"), &mCfg.sys.eth.pinRst); #endif } } void jsonNrf(JsonObject obj, bool set = false) { if(set) { obj[F("cs")] = mCfg.nrf.pinCs; obj[F("ce")] = mCfg.nrf.pinCe; obj[F("irq")] = mCfg.nrf.pinIrq; obj[F("sclk")] = mCfg.nrf.pinSclk; obj[F("mosi")] = mCfg.nrf.pinMosi; obj[F("miso")] = mCfg.nrf.pinMiso; obj[F("en")] = (bool) mCfg.nrf.enabled; } else { getVal(obj, F("cs"), &mCfg.nrf.pinCs); getVal(obj, F("ce"), &mCfg.nrf.pinCe); getVal(obj, F("irq"), &mCfg.nrf.pinIrq); getVal(obj, F("sclk"), &mCfg.nrf.pinSclk); getVal(obj, F("mosi"), &mCfg.nrf.pinMosi); getVal(obj, F("miso"), &mCfg.nrf.pinMiso); #if !defined(ESP32) mCfg.nrf.enabled = true; // ESP8266, read always as enabled #else mCfg.nrf.enabled = (bool) obj[F("en")]; #endif if((obj[F("cs")] == obj[F("ce")])) { mCfg.nrf.pinCs = DEF_NRF_CS_PIN; mCfg.nrf.pinCe = DEF_NRF_CE_PIN; mCfg.nrf.pinIrq = DEF_NRF_IRQ_PIN; mCfg.nrf.pinSclk = DEF_NRF_SCLK_PIN; mCfg.nrf.pinMosi = DEF_NRF_MOSI_PIN; mCfg.nrf.pinMiso = DEF_NRF_MISO_PIN; } } } #if defined(ESP32) void jsonCmt(JsonObject obj, bool set = false) { if(set) { obj[F("csb")] = mCfg.cmt.pinCsb; obj[F("fcsb")] = mCfg.cmt.pinFcsb; obj[F("irq")] = mCfg.cmt.pinIrq; obj[F("dio")] = mCfg.cmt.pinSdio; obj[F("clk")] = mCfg.cmt.pinSclk; obj[F("en")] = (bool) mCfg.cmt.enabled; } else { mCfg.cmt.pinCsb = obj[F("csb")]; mCfg.cmt.pinFcsb = obj[F("fcsb")]; mCfg.cmt.pinIrq = obj[F("irq")]; mCfg.cmt.pinSdio = obj[F("dio")]; mCfg.cmt.pinSclk = obj[F("clk")]; mCfg.cmt.enabled = (bool) obj[F("en")]; if(0 == mCfg.cmt.pinSclk) mCfg.cmt.pinSclk = DEF_CMT_SCLK; if(0 == mCfg.cmt.pinSdio) mCfg.cmt.pinSdio = DEF_CMT_SDIO; } } #endif void jsonNtp(JsonObject obj, bool set = false) { if(set) { obj[F("addr")] = mCfg.ntp.addr; obj[F("port")] = mCfg.ntp.port; obj[F("intvl")] = mCfg.ntp.interval; } else { getChar(obj, F("addr"), mCfg.ntp.addr, NTP_ADDR_LEN); getVal(obj, F("port"), &mCfg.ntp.port); getVal(obj, F("intvl"), &mCfg.ntp.interval); if(mCfg.ntp.interval < 5) // minimum 5 minutes mCfg.ntp.interval = 720; // default -> 12 hours } } void jsonSun(JsonObject obj, bool set = false) { if(set) { obj[F("lat")] = mCfg.sun.lat; obj[F("lon")] = mCfg.sun.lon; obj[F("offs")] = mCfg.sun.offsetSecMorning; obj[F("offsEve")] = mCfg.sun.offsetSecEvening; } else { getVal(obj, F("lat"), &mCfg.sun.lat); getVal(obj, F("lon"), &mCfg.sun.lon); getVal(obj, F("offs"), &mCfg.sun.offsetSecMorning); getVal(obj, F("offsEve"), &mCfg.sun.offsetSecEvening); } } void jsonSerial(JsonObject obj, bool set = false) { if(set) { obj[F("show")] = mCfg.serial.showIv; obj[F("debug")] = mCfg.serial.debug; obj[F("prv")] = (bool) mCfg.serial.privacyLog; obj[F("trc")] = (bool) mCfg.serial.printWholeTrace; obj[F("mqtt")] = (bool) mCfg.serial.log2mqtt; } else { getVal(obj, F("show"), &mCfg.serial.showIv); getVal(obj, F("debug"), &mCfg.serial.debug); getVal(obj, F("prv"), &mCfg.serial.privacyLog); getVal(obj, F("trc"), &mCfg.serial.printWholeTrace); getVal(obj, F("mqtt"), &mCfg.serial.log2mqtt); } } void jsonMqtt(JsonObject obj, bool set = false) { if(set) { obj[F("broker")] = mCfg.mqtt.broker; obj[F("port")] = mCfg.mqtt.port; obj[F("clientId")] = mCfg.mqtt.clientId; obj[F("user")] = mCfg.mqtt.user; obj[F("pwd")] = mCfg.mqtt.pwd; obj[F("topic")] = mCfg.mqtt.topic; obj[F("json")] = mCfg.mqtt.json; obj[F("intvl")] = mCfg.mqtt.interval; obj[F("retain")] = mCfg.mqtt.enableRetain; } else { getVal(obj, F("port"), &mCfg.mqtt.port); getVal(obj, F("intvl"), &mCfg.mqtt.interval); getVal(obj, F("json"), &mCfg.mqtt.json); getChar(obj, F("broker"), mCfg.mqtt.broker, MQTT_ADDR_LEN); getChar(obj, F("user"), mCfg.mqtt.user, MQTT_USER_LEN); getChar(obj, F("clientId"), mCfg.mqtt.clientId, MQTT_CLIENTID_LEN); getChar(obj, F("pwd"), mCfg.mqtt.pwd, MQTT_PWD_LEN); getChar(obj, F("topic"), mCfg.mqtt.topic, MQTT_TOPIC_LEN); getVal(obj, F("retain"), &mCfg.mqtt.enableRetain); } } void jsonLed(JsonObject obj, bool set = false) { if(set) { obj[F("0")] = mCfg.led.led[0]; obj[F("1")] = mCfg.led.led[1]; obj[F("2")] = mCfg.led.led[2]; obj[F("act_high")] = mCfg.led.high_active; obj[F("lum")] = mCfg.led.luminance; } else { getVal(obj, F("0"), &mCfg.led.led[0]); getVal(obj, F("1"), &mCfg.led.led[1]); getVal(obj, F("2"), &mCfg.led.led[2]); getVal(obj, F("act_high"), &mCfg.led.high_active); getVal(obj, F("lum"), &mCfg.led.luminance); } } // Plugin ZeroExport #if defined(PLUGIN_ZEROEXPORT) void jsonZeroExportGroupInverter(JsonObject obj, uint8_t group, uint8_t inv, bool set = false) { if(set) { obj[F("enabled")] = mCfg.plugin.zeroExport.groups[group].inverters[inv].enabled; obj[F("id")] = mCfg.plugin.zeroExport.groups[group].inverters[inv].id; obj[F("powerMin")] = mCfg.plugin.zeroExport.groups[group].inverters[inv].powerMin; obj[F("powerMax")] = mCfg.plugin.zeroExport.groups[group].inverters[inv].powerMax; obj[F("turnOff")] = mCfg.plugin.zeroExport.groups[group].inverters[inv].turnOff; } else { if (obj.containsKey(F("enabled"))) getVal(obj, F("enabled"), &mCfg.plugin.zeroExport.groups[group].inverters[inv].enabled); if (obj.containsKey(F("id"))) getVal(obj, F("id"), &mCfg.plugin.zeroExport.groups[group].inverters[inv].id); if (obj.containsKey(F("powerMin"))) getVal(obj, F("powerMin"), &mCfg.plugin.zeroExport.groups[group].inverters[inv].powerMin); if (obj.containsKey(F("powerMax"))) getVal(obj, F("powerMax"), &mCfg.plugin.zeroExport.groups[group].inverters[inv].powerMax); if (obj.containsKey(F("turnOff"))) getVal(obj, F("turnOff"), &mCfg.plugin.zeroExport.groups[group].inverters[inv].turnOff); } } void jsonZeroExportGroup(JsonObject obj, uint8_t group, bool set = false) { if(set) { // General obj[F("enabled")] = mCfg.plugin.zeroExport.groups[group].enabled; obj[F("name")] = mCfg.plugin.zeroExport.groups[group].name; // Powermeter obj[F("pm_refresh")] = mCfg.plugin.zeroExport.groups[group].pm_refresh; obj[F("pm_type")] = mCfg.plugin.zeroExport.groups[group].pm_type; obj[F("pm_src")] = mCfg.plugin.zeroExport.groups[group].pm_src; obj[F("pm_jsonPath")] = mCfg.plugin.zeroExport.groups[group].pm_jsonPath; obj[F("pm_user")] = mCfg.plugin.zeroExport.groups[group].pm_user; obj[F("pm_pass")] = mCfg.plugin.zeroExport.groups[group].pm_pass; obj[F("pm_target")] = mCfg.plugin.zeroExport.groups[group].pm_target; // Inverters JsonArray invArr = obj.createNestedArray(F("inverters")); for(uint8_t inv = 0; inv < ZEROEXPORT_GROUP_MAX_INVERTERS; inv++) { jsonZeroExportGroupInverter(invArr.createNestedObject(), group, inv, set); } // Battery obj[F("battCfg")] = mCfg.plugin.zeroExport.groups[group].battCfg; obj[F("battTopic")] = mCfg.plugin.zeroExport.groups[group].battTopic; obj[F("battLimitOn")] = mCfg.plugin.zeroExport.groups[group].battLimitOn; obj[F("battLimitOff")] = mCfg.plugin.zeroExport.groups[group].battLimitOff; // Advanced obj[F("setPoint")] = mCfg.plugin.zeroExport.groups[group].setPoint; obj[F("minimum")] = mCfg.plugin.zeroExport.groups[group].minimum; obj[F("powerTolerance")] = mCfg.plugin.zeroExport.groups[group].powerTolerance; obj[F("powerMax")] = mCfg.plugin.zeroExport.groups[group].powerMax; obj[F("Kp")] = mCfg.plugin.zeroExport.groups[group].Kp; obj[F("Ki")] = mCfg.plugin.zeroExport.groups[group].Ki; obj[F("Kd")] = mCfg.plugin.zeroExport.groups[group].Kd; } else { // General if (obj.containsKey(F("enabled"))) getVal(obj, F("enabled"), &mCfg.plugin.zeroExport.groups[group].enabled); if (obj.containsKey(F("name"))) getChar(obj, F("name"), mCfg.plugin.zeroExport.groups[group].name, ZEXPORT_ADDR_LEN); // Powermeter if (obj.containsKey(F("pm_refresh"))) getVal(obj, F("pm_refresh"), &mCfg.plugin.zeroExport.groups[group].pm_refresh); if (obj.containsKey(F("pm_type"))) getVal(obj, F("pm_type"), &mCfg.plugin.zeroExport.groups[group].pm_type); if (obj.containsKey(F("pm_src"))) getChar(obj, F("pm_src"), mCfg.plugin.zeroExport.groups[group].pm_src, ZEROEXPORT_GROUP_MAX_LEN_PM_SRC ); if (obj.containsKey(F("pm_jsonPath"))) getChar(obj, F("pm_jsonPath"), mCfg.plugin.zeroExport.groups[group].pm_jsonPath, ZEROEXPORT_GROUP_MAX_LEN_PM_JSONPATH); if (obj.containsKey(F("pm_user"))) getChar(obj, F("pm_user"), mCfg.plugin.zeroExport.groups[group].pm_user, ZEROEXPORT_GROUP_MAX_LEN_PM_USER); if (obj.containsKey(F("pm_pass"))) getChar(obj, F("pm_pass"), mCfg.plugin.zeroExport.groups[group].pm_pass, ZEROEXPORT_GROUP_MAX_LEN_PM_PASS); if (obj.containsKey(F("pm_target"))) getVal(obj, F("pm_target"), &mCfg.plugin.zeroExport.groups[group].pm_target); // Inverters if (obj.containsKey(F("inverters"))) { for(uint8_t inv = 0; inv < ZEROEXPORT_GROUP_MAX_INVERTERS; inv++) { jsonZeroExportGroupInverter(obj[F("inverters")][inv], group, inv, set); } } // Battery if (obj.containsKey(F("battCfg"))) getVal(obj, F("battCfg"), &mCfg.plugin.zeroExport.groups[group].battCfg); if (obj.containsKey(F("battTopic"))) getChar(obj, F("battTopic"), mCfg.plugin.zeroExport.groups[group].battTopic, ZEROEXPORT_GROUP_MAX_LEN_BATT_TOPIC); if (obj.containsKey(F("battLimitOn"))) getVal(obj, F("battLimitOn"), &mCfg.plugin.zeroExport.groups[group].battLimitOn); if (obj.containsKey(F("battLimitOff"))) getVal(obj, F("battLimitOff"), &mCfg.plugin.zeroExport.groups[group].battLimitOff); // Advanced if (obj.containsKey(F("setPoint"))) getVal(obj, F("setPoint"), &mCfg.plugin.zeroExport.groups[group].setPoint); if (obj.containsKey(F("minimum"))) getVal(obj, F("minimum"), &mCfg.plugin.zeroExport.groups[group].minimum); if (obj.containsKey(F("powerTolerance"))) getVal(obj, F("powerTolerance"), &mCfg.plugin.zeroExport.groups[group].powerTolerance); if (obj.containsKey(F("powerMax"))) getVal(obj, F("powerMax"), &mCfg.plugin.zeroExport.groups[group].powerMax); if (obj.containsKey(F("Kp"))) getVal(obj, F("Kp"), &mCfg.plugin.zeroExport.groups[group].Kp); if (obj.containsKey(F("Ki"))) getVal(obj, F("Ki"), &mCfg.plugin.zeroExport.groups[group].Ki); if (obj.containsKey(F("Kd"))) getVal(obj, F("Kd"), &mCfg.plugin.zeroExport.groups[group].Kd); } } void jsonZeroExport(JsonObject obj, bool set = false) { if(set) { obj[F("enabled")] = mCfg.plugin.zeroExport.enabled; obj[F("log_over_webserial")] = mCfg.plugin.zeroExport.log_over_webserial; obj[F("log_over_mqtt")] = mCfg.plugin.zeroExport.log_over_mqtt; obj[F("debug")] = mCfg.plugin.zeroExport.debug; JsonArray grpArr = obj.createNestedArray(F("groups")); for(uint8_t group = 0; group < ZEROEXPORT_MAX_GROUPS; group++) { jsonZeroExportGroup(grpArr.createNestedObject(), group, set); } } else { if (obj.containsKey(F("enabled"))) getVal(obj, F("enabled"), &mCfg.plugin.zeroExport.enabled); if (obj.containsKey(F("log_over_webserial"))) getVal(obj, F("log_over_webserial"), &mCfg.plugin.zeroExport.log_over_webserial); if (obj.containsKey(F("log_over_mqtt"))) getVal(obj, F("log_over_mqtt"), &mCfg.plugin.zeroExport.log_over_mqtt); if (obj.containsKey(F("debug"))) getVal(obj, F("debug"), &mCfg.plugin.zeroExport.debug); if (obj.containsKey(F("groups"))) { for(uint8_t group = 0; group < ZEROEXPORT_MAX_GROUPS; group++) { jsonZeroExportGroup(obj[F("groups")][group], group, set); } } } } #endif // Plugin ZeroExport - Ende void jsonPlugin(JsonObject obj, bool set = false) { if(set) { #if defined(PLUGIN_DISPLAY) JsonObject disp = obj.createNestedObject("disp"); disp[F("type")] = mCfg.plugin.display.type; disp[F("pwrSafe")] = (bool)mCfg.plugin.display.pwrSaveAtIvOffline; disp[F("screenSaver")] = mCfg.plugin.display.screenSaver; disp[F("graph_ratio")] = mCfg.plugin.display.graph_ratio; disp[F("graph_size")] = mCfg.plugin.display.graph_size; disp[F("rotation")] = mCfg.plugin.display.rot; //disp[F("wake")] = mCfg.plugin.display.wakeUp; //disp[F("sleep")] = mCfg.plugin.display.sleepAt; disp[F("contrast")] = mCfg.plugin.display.contrast; disp[F("data")] = mCfg.plugin.display.disp_data; disp[F("clock")] = mCfg.plugin.display.disp_clk; disp[F("cs")] = mCfg.plugin.display.disp_cs; disp[F("reset")] = mCfg.plugin.display.disp_reset; disp[F("busy")] = mCfg.plugin.display.disp_busy; disp[F("dc")] = mCfg.plugin.display.disp_dc; disp[F("pirPin")] = mCfg.plugin.display.pirPin; #endif obj[F("cst_lnk")] = mCfg.plugin.customLink; obj[F("cst_lnk_txt")] = mCfg.plugin.customLinkText; // Plugin ZeroExport #if defined(PLUGIN_ZEROEXPORT) jsonZeroExport(obj.createNestedObject("zeroExport"), set); #endif // Plugin ZeroExport - Ende } else { #if defined(PLUGIN_DISPLAY) JsonObject disp = obj["disp"]; getVal(disp, F("type"), &mCfg.plugin.display.type); getVal(disp, F("pwrSafe"), &mCfg.plugin.display.pwrSaveAtIvOffline); getVal(disp, F("screenSaver"), &mCfg.plugin.display.screenSaver); getVal(disp, F("graph_ratio"), &mCfg.plugin.display.graph_ratio); getVal(disp, F("graph_size"), &mCfg.plugin.display.graph_size); getVal(disp, F("rotation"), &mCfg.plugin.display.rot); //mCfg.plugin.display.wakeUp = disp[F("wake")]; //mCfg.plugin.display.sleepAt = disp[F("sleep")]; getVal(disp, F("contrast"), &mCfg.plugin.display.contrast); getVal(disp, F("data"), &mCfg.plugin.display.disp_data); getVal(disp, F("clock"), &mCfg.plugin.display.disp_clk); getVal(disp, F("cs"), &mCfg.plugin.display.disp_cs); getVal(disp, F("reset"), &mCfg.plugin.display.disp_reset); getVal(disp, F("busy"), &mCfg.plugin.display.disp_busy); getVal(disp, F("dc"), &mCfg.plugin.display.disp_dc); getVal(disp, F("pirPin"), &mCfg.plugin.display.pirPin); #endif getChar(obj, F("cst_lnk"), mCfg.plugin.customLink, MAX_CUSTOM_LINK_LEN); getChar(obj, F("cst_lnk_txt"), mCfg.plugin.customLinkText, MAX_CUSTOM_LINK_TEXT_LEN); // Plugin ZeroExport #if defined(PLUGIN_ZEROEXPORT) jsonZeroExport(obj["zeroExport"], set); #endif // Plugin ZeroExport - Ende } } void jsonInst(JsonObject obj, bool set = false) { if(set) { obj[F("intvl")] = mCfg.inst.sendInterval; // obj[F("en")] = (bool)mCfg.inst.enabled; obj[F("rstMidNight")] = (bool)mCfg.inst.rstValsAtMidNight; obj[F("rstNotAvail")] = (bool)mCfg.inst.rstValsNotAvail; obj[F("rstComStop")] = (bool)mCfg.inst.rstValsCommStop; obj[F("rstComStart")] = (bool)mCfg.inst.rstValsCommStart; obj[F("strtWthtTime")] = (bool)mCfg.inst.startWithoutTime; obj[F("rstMaxMidNight")] = (bool)mCfg.inst.rstIncludeMaxVals; obj[F("rdGrid")] = (bool)mCfg.inst.readGrid; } else { getVal(obj, F("intvl"), &mCfg.inst.sendInterval); // getVal(obj, F("en"), &mCfg.inst.enabled); getVal(obj, F("rstMidNight"), &mCfg.inst.rstValsAtMidNight); getVal(obj, F("rstNotAvail"), &mCfg.inst.rstValsNotAvail); getVal(obj, F("rstComStop"), &mCfg.inst.rstValsCommStop); getVal(obj, F("rstComStart"), &mCfg.inst.rstValsCommStart); getVal(obj, F("strtWthtTime"), &mCfg.inst.startWithoutTime); getVal(obj, F("rstMaxMidNight"), &mCfg.inst.rstIncludeMaxVals); getVal(obj, F("rdGrid"), &mCfg.inst.readGrid); } JsonArray ivArr; if(set) ivArr = obj.createNestedArray(F("iv")); for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i++) { if(set) { if(mCfg.inst.iv[i].serial.u64 != 0ULL) jsonIv(ivArr.createNestedObject(), &mCfg.inst.iv[i], true); } else if(!obj[F("iv")][i].isNull()) jsonIv(obj[F("iv")][i], &mCfg.inst.iv[i]); } } void jsonIv(JsonObject obj, cfgIv_t *cfg, bool set = false) { if(set) { obj[F("en")] = (bool)cfg->enabled; obj[F("name")] = cfg->name; obj[F("sn")] = cfg->serial.u64; obj[F("freq")] = cfg->frequency; obj[F("pa")] = cfg->powerLevel; obj[F("dis")] = cfg->disNightCom; for(uint8_t i = 0; i < 6; i++) { obj[F("yield")][i] = cfg->yieldCor[i]; obj[F("pwr")][i] = cfg->chMaxPwr[i]; obj[F("chName")][i] = cfg->chName[i]; } } else { getVal(obj, F("en"), &cfg->enabled); getChar(obj, F("name"), cfg->name, MAX_NAME_LENGTH); getVal(obj, F("sn"), &cfg->serial.u64); getVal(obj, F("freq"), &cfg->frequency); getVal(obj, F("pa"), &cfg->powerLevel); getVal(obj, F("dis"), &cfg->disNightCom); uint8_t size = 4; if(obj.containsKey(F("pwr"))) size = obj[F("pwr")].size(); for(uint8_t i = 0; i < size; i++) { if(obj.containsKey(F("yield"))) cfg->yieldCor[i] = obj[F("yield")][i]; if(obj.containsKey(F("pwr"))) cfg->chMaxPwr[i] = obj[F("pwr")][i]; if(obj.containsKey(F("chName"))) snprintf(cfg->chName[i], MAX_NAME_LENGTH, "%s", obj[F("chName")][i].as()); } } } #if defined(ESP32) void getChar(JsonObject obj, const char *key, char *dst, int maxLen) { if(obj.containsKey(key)) { snprintf(dst, maxLen, "%s", obj[key].as()); dst[maxLen-1] = '\0'; } } template void getVal(JsonObject obj, const char *key, T *dst) { if(obj.containsKey(key)) *dst = obj[key]; } #else void getChar(JsonObject obj, const __FlashStringHelper *key, char *dst, int maxLen) { if(obj.containsKey(key)) { snprintf(dst, maxLen, "%s", obj[key].as()); dst[maxLen-1] = '\0'; } } template void getVal(JsonObject obj, const __FlashStringHelper *key, T *dst) { if(obj.containsKey(key)) *dst = obj[key]; } #endif private: settings_t mCfg; bool mLastSaveSucceed = 0; }; #endif /*__SETTINGS_H__*/