diff --git a/.github/workflows/compile_development.yml b/.github/workflows/compile_development.yml index 274a6c73..ec435880 100644 --- a/.github/workflows/compile_development.yml +++ b/.github/workflows/compile_development.yml @@ -68,7 +68,7 @@ jobs: - name: Create Artifact uses: actions/upload-artifact@v3 with: - name: ${{ steps.rename-binary-files.outputs.name }}_dev_build + name: ahoydtu_dev path: | src/firmware/* src/User_Manual.md diff --git a/User_Manual.md b/User_Manual.md index 3b51a3e1..8be2d62c 100644 --- a/User_Manual.md +++ b/User_Manual.md @@ -1,4 +1,4 @@ -# User Manual Ahoy DTU (on ESP8266) +# User Manual AhoyDTU (on ESP8266) Version #{VERSION}# ## Introduction See the repository [README.md](Getting_Started.md) @@ -9,9 +9,9 @@ In the initial case or after click "erase settings" the fields for the inverter Set at least the serial number and a name for each inverter, check "reboot after save" and click the "Save" button. -## MQTT Output -The ahoy dtu will publish on the following topics -`//ch0/#` +## MQTT Output +The AhoyDTU will publish on the following topics +`//ch0/#` | Topic | Example Value | Remarks | |---|---|---| @@ -34,7 +34,7 @@ The ahoy dtu will publish on the following topics |PowerLimit | 80.000|actual set point for power limit control AC active power in percent| |LastAlarmCode | 1.000| Last Alarm Code eg. "inverter start"| -`//ch/#` +`//ch/#` `` is in the range 1 to 4 depending on the inverter type @@ -47,7 +47,8 @@ The ahoy dtu will publish on the following topics |YieldTotal | 110.819 | Energy converted to AC since reset Watt hours per module/channel (measured on DC) | |Irradiation |5.65 | ratio DC Power over set maximum power per module/channel in percent | -## Active Power Limit via Setup Page +## Active Power Limit via Serial / Control Page +URL: `/serial` If you leave the field "Active Power Limit" empty during the setup and reboot the ahoy-dtu will set a value of 65535 in the setup. That is the value you have to fill in case you want to operate the inverter without a active power limit. If the value is 65535 or -1 after another reboot the value will be set automatically to "100" and in the drop-down menu "relative in percent persistent" will be set. Of course you can do this also by your self. @@ -68,117 +69,137 @@ after a power cycle of the inverter (P_DC=0 and P_AC=0 for at least 10 seconds) The user has to ensure correct settings. Remember that for the inverters of 3rd generation the relative active power limit is in the range of 2% up to 100%. Also an absolute active power limit below approx. 30 Watt seems to be not meanful because of the control capabilities and reactive power load. -## Active Power Limit via MQTT -The ahoy-dtu subscribes on the topic `/devcontrol/#` if the mqtt broker is set-up correctly. The default topic is `inverter/devcontrol/#`. +## Control via MQTT -To set the active power limit (controled value is the AC Power of the inverter) you have four options. (Only single phase inverters are actually in focus). +### Generic Information -| topic | payload | active power limit in | Condition | -| --------------------------------------------------------------- | ----------- | -------------------------------------------- | -------------- | -| /devcontrol//11 OR /devcontrol//11/0 | [0..65535] | Watt | not persistent | -| /devcontrol//11/256 | [0..65535] | Watt | persistent | -| /devcontrol//11/1 | [2..100] | % | not persistent | -| /devcontrol//11/257 | [2..100] | % | persistent | +The AhoyDTU subscribes on three topics `/ctrl/#`, `/setup` and `/status`. +πŸ‘† `` can be set on setup page, default is `inverter`. πŸ‘† `` is the number of the specific inverter in the setup page. -* First inverter --> `` = 0 -* Second inverter --> `` = 1 -* ... - -### Developer Information MQTT Interface -`/devcontrol///` - -The implementation allows to set any of the available `` Commands: -```C -typedef enum { - TurnOn = 0, // 0x00 - TurnOff = 1, // 0x01 - Restart = 2, // 0x02 - Lock = 3, // 0x03 - Unlock = 4, // 0x04 - ActivePowerContr = 11, // 0x0b - ReactivePowerContr = 12, // 0x0c - PFSet = 13, // 0x0d - CleanState_LockAndAlarm = 20, // 0x14 - SelfInspection = 40, // 0x28, self-inspection of grid-connected protection files - Init = 0xff -} DevControlCmdType; + +### Inverter Power (On / Off) +`/ctrl/power/` with payload `1` for `ON` and `0` for `OFF` +Example: +`inverter/ctrl/power/0` `1` + + +### Inverter restart + +`/ctrl/restart/` +Example: +`inverter/ctrl/restart/0` + + +### Power Limit relative persistent [%] + +`/ctrl/limit_persistent_relative/` with a payload `[2 .. 100]` +Example: +`inverter/ctrl/limit_persistent_relative/0` `70` + + +### Power Limit absolute persistent [Watts] + +`/ctrl/limit_persistent_relative/` with a payload `[0 .. 65535]` +Example: +`inverter/ctrl/limit_persistent_relative/0` `600` + + +### Power Limit relative non persistent [%] + +`/ctrl/limit_nonpersistent_relative/` with a payload `[2 .. 100]` +Example: +`inverter/ctrl/limit_nonpersistent_relative/0` `70` + + +### Power Limit absolute non persistent [Watts] + +`/ctrl/limit_nonpersistent_relative/` with a payload `[0 .. 65535]` +Example: +`inverter/ctrl/limit_nonpersistent_relative/0` `600` + + +## Control via REST API + +### Generic Information + +The rest API works with *JSON* POST requests. All the following instructions must be sent to the `/api` endpoint of the AhoyDTU. + +πŸ‘† `` is the number of the specific inverter in the setup page. + +### Inverter Power (On / Off) + +```json +{ + "id": , + "cmd": "power", + "val": +} ``` -The MQTT payload will be set on first to bytes and ``, which is taken from the topic path will be set on the second two bytes if the corresponding DevControlCmdType supports 4 byte data. -See here the actual implementation to set the send buffer bytes. -```C -void sendControlPacket(uint64_t invId, uint8_t cmd, uint16_t *data) { - sendCmdPacket(invId, TX_REQ_DEVCONTROL, ALL_FRAMES, false); - int cnt = 0; - // cmd --> 0x0b => Type_ActivePowerContr, 0 on, 1 off, 2 restart, 12 reactive power, 13 power factor - mTxBuf[10] = cmd; - mTxBuf[10 + (++cnt)] = 0x00; - if (cmd >= ActivePowerContr && cmd <= PFSet){ - mTxBuf[10 + (++cnt)] = ((data[0] * 10) >> 8) & 0xff; // power limit || high byte from MQTT payload - mTxBuf[10 + (++cnt)] = ((data[0] * 10) ) & 0xff; // power limit || low byte from MQTT payload - mTxBuf[10 + (++cnt)] = ((data[1] ) >> 8) & 0xff; // high byte from MQTT topic value - mTxBuf[10 + (++cnt)] = ((data[1] ) ) & 0xff; // low byte from MQTT topic value - } - // crc control data - uint16_t crc = Hoymiles::crc16(&mTxBuf[10], cnt+1); - mTxBuf[10 + (++cnt)] = (crc >> 8) & 0xff; - mTxBuf[10 + (++cnt)] = (crc ) & 0xff; - // crc over all - cnt +=1; - mTxBuf[10 + cnt] = Hoymiles::crc8(mTxBuf, 10 + cnt); - - sendPacket(invId, mTxBuf, 10 + (++cnt), true); +The `` should be set to `1` for `ON` and `0` for `OFF` + + +### Inverter restart + +```json +{ + "id": , + "cmd": "restart" } ``` -So as example sending any payload on `inverter/devcontrol/0/1` will switch off the inverter. -## Active Power Limit via REST API -It is also implemented to set the power limit via REST API call. Therefore send a POST request to the endpoint /api. -The response will always be a json with {success:true} -The payload shall be a json formated string in the following manner +### Power Limit relative persistent [%] + ```json { - "inverter":, - "tx_request": , - "cmd": , - "payload": , - "payload2": + "id": , + "cmd": "limit_persistent_relative", + "val": } ``` -With the following value ranges +The `VALUE` represents a percent number in a range of `[2 .. 100]` -| Value | range | note | -| --------------------------- | ----------- | ------------------------------- | -| | 81 or 21 | integer uint8, (0x15 or 0x51) | -| | [0...255] | integer uint8, subcmds eg. 0x0b | -| | [0...65535] | uint16 | -| | [0...3] | integer uint8 | +### Power Limit absolute persistent [Watts] -Example to set the active power limit non persistent to 10% ```json { - "inverter":0, - "tx_request": 81, - "cmd": 11, - "payload": 10, - "payload2": 1 + "id": , + "cmd": "limit_persistent_absolute", + "val": } ``` -Example to set the active power limit persistent to 600Watt +The `VALUE` represents watts in a range of `[0 .. 65535]` + + +### Power Limit relative non persistent [%] + ```json { - "inverter":0, - "tx_request": 81, - "cmd": 11, - "payload": 600, - "payload2": 256 + "id": , + "cmd": "limit_nonpersistent_relative", + "val": } ``` +The `VALUE` represents a percent number in a range of `[2 .. 100]` -### Developer Information REST API + +### Power Limit absolute non persistent [Watts] + +```json +{ + "id": , + "cmd": "limit_nonpersistent_absolute", + "val": +} +``` +The `VALUE` represents watts in a range of `[0 .. 65535]` + + + +### Developer Information REST API (obsolete) In the same approach as for MQTT any other SubCmd and also MainCmd can be applied and the response payload can be observed in the serial logs. Eg. request the Alarm-Data from the Alarm-Index 5 from inverter 0 will look like this: ```json { @@ -190,38 +211,15 @@ In the same approach as for MQTT any other SubCmd and also MainCmd can be applie } ``` -## Zero Export Control -* You can use the mqtt topic `/devcontrol//11` with a number as payload (eg. 300 -> 300 Watt) to set the power limit to the published number in Watt. (In regular cases the inverter will use the new set point within one intervall period; to verify this see next bullet) -* You can check the inverter set point for the power limit control on the topic `//ch0/PowerLimit` πŸ‘† This value is ALWAYS in percent of the maximum power limit of the inverter. In regular cases this value will be updated within approx. 15 seconds. (depends on request intervall) -* You can monitor the actual AC power by subscribing to the topic `//ch0/P_AC` πŸ‘† This value is ALWAYS in Watt - -## Issues and Debuging for active power limit settings +## Zero Export Control (needs rework) +* You can use the mqtt topic `/devcontrol//11` with a number as payload (eg. 300 -> 300 Watt) to set the power limit to the published number in Watt. (In regular cases the inverter will use the new set point within one intervall period; to verify this see next bullet) +* You can check the inverter set point for the power limit control on the topic `//ch0/PowerLimit` πŸ‘† This value is ALWAYS in percent of the maximum power limit of the inverter. In regular cases this value will be updated within approx. 15 seconds. (depends on request intervall) +* You can monitor the actual AC power by subscribing to the topic `//ch0/P_AC` πŸ‘† This value is ALWAYS in Watt -Turn on the serial debugging in the setup. Try to have find out if the behavior is deterministic. That means can you reproduce the behavior. Be patient and wait on inverter reactions at least some minutes and beware that the DC-Power is sufficient. - -In case of issues please report: - -1. Version of firmware -2. The output of the serial debug esp. the TX messages starting with "0x51" and the RX messages starting with "0xD1" or "0xF1" -3. Which case you have tried: Setup-Page, MQTT, REST API and at what was shown on the "Visualization Page" at the Location "Limit" -4. The setting means payload, relative, absolute, persistent, not persistent (see tables above) - -**Developer Information General for Active Power Limit** - -⚑The following was verified by field tests and feedback from users - -Internally this values will be set for the second two bytes for MainCmd: 0x51 SubCmd: 0x0b --> DevControl set ActivePowerLimit -```C -typedef enum { - AbsolutNonPersistent = 0x0000, // 0 - RelativNonPersistent = 0x0001, // 1 - AbsolutPersistent = 0x0100, // 256 - RelativPersistent = 0x0101 // 257 -} PowerLimitControlType; -``` ## Firmware Version collection Gather user inverter information here to understand what differs between some inverters. +To get the information open the URL `/api/record/info` on your AhoyDTU. The information will only be present once the AhoyDTU was able to communicate with an inverter. | Name | Inverter Typ | Bootloader V. | FWVersion | FWBuild [YYYY] | FWBuild [MM-DD] | HWPartId | | | | ---------- | ------------ | ------------- | --------- | -------------- | --------------- | --------- | -------- | --------- | diff --git a/src/app.cpp b/src/app.cpp index 782ae853..e7b86283 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -12,6 +12,12 @@ #include #include "utils/sun.h" +//----------------------------------------------------------------------------- +app::app() : ah::Scheduler() { + mWeb = NULL; +} + + //----------------------------------------------------------------------------- void app::setup(uint32_t timeout) { Serial.begin(115200); @@ -39,7 +45,6 @@ void app::setup(uint32_t timeout) { mWifi = new ahoywifi(mConfig); mWifi->setup(timeout, mSettings.getValid()); - mPayload.setup(mSys); mPayload.enableSerialDebug(mConfig->serial.debug); #if !defined(AP_ONLY) @@ -48,6 +53,7 @@ void app::setup(uint32_t timeout) { addListener(EVERY_SEC, std::bind(&PubMqttType::tickerSecond, &mMqtt)); addListener(EVERY_MIN, std::bind(&PubMqttType::tickerMinute, &mMqtt)); addListener(EVERY_HR, std::bind(&PubMqttType::tickerHour, &mMqtt)); + mMqtt.setSubscriptionCb(std::bind(&app::mqttSubRxCb, this, std::placeholders::_1)); } #endif setupLed(); @@ -226,8 +232,6 @@ void app::resetSystem(void) { mSunrise = 0; mSunset = 0; - mHeapStatCnt = 0; - mSendTicker = 0xffff; mTicker = 0; @@ -240,6 +244,12 @@ void app::resetSystem(void) { memset(&mStat, 0, sizeof(statistics_t)); } +//----------------------------------------------------------------------------- +void app::mqttSubRxCb(JsonObject obj) { + if(NULL != mWeb) + mWeb->apiCtrlRequest(obj); +} + //----------------------------------------------------------------------------- void app::setupLed(void) { /** LED connection diagram diff --git a/src/app.h b/src/app.h index 0d5b49cb..038be0c4 100644 --- a/src/app.h +++ b/src/app.h @@ -43,7 +43,7 @@ class web; class app : public ah::Scheduler { public: - app() : ah::Scheduler() {} + app(); ~app() {} void setup(uint32_t timeout); @@ -140,7 +140,7 @@ class app : public ah::Scheduler { private: void resetSystem(void); - void setupMqtt(void); + void mqttSubRxCb(JsonObject obj); void setupLed(void); void updateLed(void); @@ -197,7 +197,6 @@ class app : public ah::Scheduler { } uint32_t mUptimeSecs; - uint8_t mHeapStatCnt; uint32_t mUtcTimestamp; bool mUpdateNtp; diff --git a/src/defines.h b/src/defines.h index ea772a5f..930e4243 100644 --- a/src/defines.h +++ b/src/defines.h @@ -13,7 +13,7 @@ //------------------------------------- #define VERSION_MAJOR 0 #define VERSION_MINOR 5 -#define VERSION_PATCH 42 +#define VERSION_PATCH 43 //------------------------------------- typedef struct { diff --git a/src/publisher/pubMqtt.h b/src/publisher/pubMqtt.h index fb3d8669..2a42501a 100644 --- a/src/publisher/pubMqtt.h +++ b/src/publisher/pubMqtt.h @@ -3,6 +3,8 @@ // Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/ //----------------------------------------------------------------------------- +// https://bert.emelis.net/espMqttClient/ + #ifndef __PUB_MQTT_H__ #define __PUB_MQTT_H__ @@ -20,10 +22,9 @@ #include "../defines.h" #include "../hm/hmSystem.h" - #define QOS_0 0 -//https://bert.emelis.net/espMqttClient/ +typedef std::function subscriptionCb; template class PubMqtt { @@ -32,6 +33,7 @@ class PubMqtt { mRxCnt = 0; mTxCnt = 0; mEnReconnect = false; + mSubscriptionCb = NULL; } ~PubMqtt() { } @@ -47,9 +49,12 @@ class PubMqtt { snprintf(mLwtTopic, MQTT_TOPIC_LEN + 7, "%s/status", mCfgMqtt->topic); + #if defined(ESP8266) mHWifiCon = WiFi.onStationModeGotIP(std::bind(&PubMqtt::onWifiConnect, this, std::placeholders::_1)); mHWifiDiscon = WiFi.onStationModeDisconnected(std::bind(&PubMqtt::onWifiDisconnect, this, std::placeholders::_1)); - + #else + WiFi.onEvent(std::bind(&PubMqtt::onWiFiEvent, this, std::placeholders::_1)); + #endif if((strlen(mCfgMqtt->user) > 0) && (strlen(mCfgMqtt->pwd) > 0)) mClient.setCredentials(mCfgMqtt->user, mCfgMqtt->pwd); @@ -62,7 +67,9 @@ class PubMqtt { } void loop() { + #if defined(ESP8266) mClient.loop(); + #endif } void tickerSecond() { @@ -74,6 +81,7 @@ class PubMqtt { snprintf(val, 12, "%ld", millis() / 1000); publish("uptime", val); publish("wifi_rssi", String(WiFi.RSSI()).c_str()); + publish("free_heap", String(ESP.getFreeHeap()).c_str()); if(!mClient.connected()) { if(mEnReconnect) @@ -102,6 +110,14 @@ class PubMqtt { mClient.subscribe(topic, QOS_0); } + void payloadEventListener(uint8_t cmd) { + mSendList.push(cmd); + } + + void setSubscriptionCb(subscriptionCb cb) { + mSubscriptionCb = cb; + } + inline bool isConnected() { return mClient.connected(); } @@ -114,10 +130,6 @@ class PubMqtt { return mRxCnt; } - void payloadEventListener(uint8_t cmd) { - mSendList.push(cmd); - } - void sendMqttDiscoveryConfig(const char *topic) { DPRINTLN(DBG_VERBOSE, F("sendMqttDiscoveryConfig")); @@ -169,6 +181,7 @@ class PubMqtt { } private: + #if defined(ESP8266) void onWifiConnect(const WiFiEventStationModeGotIP& event) { DPRINTLN(DBG_VERBOSE, F("MQTT connecting")); mClient.connect(); @@ -178,6 +191,24 @@ class PubMqtt { mEnReconnect = false; } + #else + void onWiFiEvent(WiFiEvent_t event) { + switch(event) { + case SYSTEM_EVENT_STA_GOT_IP: + DPRINTLN(DBG_VERBOSE, F("MQTT connecting")); + mClient.connect(); + break; + + case SYSTEM_EVENT_STA_DISCONNECTED: + mEnReconnect = false; + break; + + default: + break; + } + } + #endif + void onConnect(bool sessionPreset) { DPRINTLN(DBG_INFO, F("MQTT connected")); mEnReconnect = true; @@ -185,7 +216,6 @@ class PubMqtt { publish("version", mVersion, true); publish("device", mDevName, true); publish("uptime", "0"); - publish(mLwtTopic, mLwtOnline, true, false); subscribe("ctrl/#"); @@ -221,6 +251,9 @@ class PubMqtt { void onMessage(const espMqttClientTypes::MessageProperties& properties, const char* topic, const uint8_t* payload, size_t len, size_t index, size_t total) { DPRINTLN(DBG_VERBOSE, F("MQTT got topic: ") + String(topic)); + if(NULL == mSubscriptionCb) + return; + char *tpc = new char[strlen(topic) + 1]; uint8_t cnt = 0; DynamicJsonDocument json(128); @@ -247,7 +280,7 @@ class PubMqtt { } else if(0 == strncmp(p, "setup", 5)) { if(NULL != (p = strtok(NULL, "/"))) { root[F("path")] = F("setup"); - root[F("setup")] = p; + root[F("cmd")] = p; } } else if(0 == strncmp(p, "status", 6)) { if(NULL != (p = strtok(NULL, "/"))) { @@ -257,16 +290,18 @@ class PubMqtt { } } else if(1 == cnt) { - root["id"] = atoi(p); + root[F("id")] = atoi(p); } p = strtok(NULL, "/"); cnt++; } delete[] tpc; - char out[128]; + /*char out[128]; serializeJson(root, out, 128); - DPRINTLN(DBG_INFO, "json: " + String(out)); + DPRINTLN(DBG_INFO, "json: " + String(out));*/ + if(NULL != mSubscriptionCb) + (mSubscriptionCb)(root); mRxCnt++; } @@ -396,112 +431,11 @@ class PubMqtt { } } -// void cbMqtt(char *topic, byte *payload, unsigned int length) { -// // callback handling on subscribed devcontrol topic -// DPRINTLN(DBG_INFO, F("cbMqtt")); -// // subcribed topics are mTopic + "/devcontrol/#" where # is / -// // eg. mypvsolar/devcontrol/1/11 with payload "400" --> inverter 1 active power limit 400 Watt -// const char *token = strtok(topic, "/"); -// while (token != NULL) { -// if (strcmp(token, "devcontrol") == 0) { -// token = strtok(NULL, "/"); -// uint8_t iv_id = std::stoi(token); -// -// if (iv_id >= 0 && iv_id <= MAX_NUM_INVERTERS) { -// Inverter<> *iv = mSys->getInverterByPos(iv_id); -// if (NULL != iv) { -// if (!iv->devControlRequest) { // still pending -// token = strtok(NULL, "/"); -// -// switch (std::stoi(token)) { -// // Active Power Control -// case ActivePowerContr: -// token = strtok(NULL, "/"); // get ControlMode aka "PowerPF.Desc" in DTU-Pro Code from topic string -// if (token == NULL) // default via mqtt ommit the LimitControlMode -// iv->powerLimit[1] = AbsolutNonPersistent; -// else -// iv->powerLimit[1] = std::stoi(token); -// if (length <= 5) { // if (std::stoi((char*)payload) > 0) more error handling powerlimit needed? -// if (iv->powerLimit[1] >= AbsolutNonPersistent && iv->powerLimit[1] <= RelativPersistent) { -// iv->devControlCmd = ActivePowerContr; -// iv->powerLimit[0] = std::stoi(std::string((char *)payload, (unsigned int)length)); // THX to @silversurfer -// /*if (iv->powerLimit[1] & 0x0001) -// DPRINTLN(DBG_INFO, F("Power limit for inverter ") + String(iv->id) + F(" set to ") + String(iv->powerLimit[0]) + F("%")); -// else -// DPRINTLN(DBG_INFO, F("Power limit for inverter ") + String(iv->id) + F(" set to ") + String(iv->powerLimit[0]) + F("W"));*/ -// -// DPRINTLN(DBG_INFO, F("Power limit for inverter ") + String(iv->id) + F(" set to ") + String(iv->powerLimit[0]) + String(iv->powerLimit[1] & 0x0001) ? F("%") : F("W")); -// } -// iv->devControlRequest = true; -// } else { -// DPRINTLN(DBG_INFO, F("Invalid mqtt payload recevied: ") + String((char *)payload)); -// } -// break; -// -// // Turn On -// case TurnOn: -// iv->devControlCmd = TurnOn; -// DPRINTLN(DBG_INFO, F("Turn on inverter ") + String(iv->id)); -// iv->devControlRequest = true; -// break; -// -// // Turn Off -// case TurnOff: -// iv->devControlCmd = TurnOff; -// DPRINTLN(DBG_INFO, F("Turn off inverter ") + String(iv->id)); -// iv->devControlRequest = true; -// break; -// -// // Restart -// case Restart: -// iv->devControlCmd = Restart; -// DPRINTLN(DBG_INFO, F("Restart inverter ") + String(iv->id)); -// iv->devControlRequest = true; -// break; -// -// // Reactive Power Control -// case ReactivePowerContr: -// iv->devControlCmd = ReactivePowerContr; -// if (true) { // if (std::stoi((char*)payload) > 0) error handling powerlimit needed? -// iv->devControlCmd = ReactivePowerContr; -// iv->powerLimit[0] = std::stoi(std::string((char *)payload, (unsigned int)length)); -// iv->powerLimit[1] = 0x0000; // if reactivepower limit is set via external interface --> set it temporay -// DPRINTLN(DBG_DEBUG, F("Reactivepower limit for inverter ") + String(iv->id) + F(" set to ") + String(iv->powerLimit[0]) + F("W")); -// iv->devControlRequest = true; -// } -// break; -// -// // Set Power Factor -// case PFSet: -// // iv->devControlCmd = PFSet; -// // uint16_t power_factor = std::stoi(strtok(NULL, "/")); -// DPRINTLN(DBG_INFO, F("Set Power Factor not implemented for inverter ") + String(iv->id)); -// break; -// -// // CleanState lock & alarm -// case CleanState_LockAndAlarm: -// iv->devControlCmd = CleanState_LockAndAlarm; -// DPRINTLN(DBG_INFO, F("CleanState lock & alarm for inverter ") + String(iv->id)); -// iv->devControlRequest = true; -// break; -// -// default: -// DPRINTLN(DBG_INFO, "Not implemented"); -// break; -// } -// } -// } -// } -// break; -// } -// token = strtok(NULL, "/"); -// } -// DPRINTLN(DBG_INFO, F("app::cbMqtt finished")); -// } - espMqttClient mClient; cfgMqtt_t *mCfgMqtt; + #if defined(ESP8266) WiFiEventHandler mHWifiCon, mHWifiDiscon; + #endif uint32_t *mSunrise, *mSunset; HMSYSTEM *mSys; @@ -509,12 +443,12 @@ class PubMqtt { uint32_t mRxCnt, mTxCnt; std::queue mSendList; bool mEnReconnect; + subscriptionCb mSubscriptionCb; // last will topic and payload must be available trough lifetime of 'espMqttClient' char mLwtTopic[MQTT_TOPIC_LEN+7]; const char* mLwtOnline = "online"; const char* mLwtOffline = "offline"; - const char *mDevName, *mVersion; }; diff --git a/src/web/html/index.html b/src/web/html/index.html index c2d25038..96c92feb 100644 --- a/src/web/html/index.html +++ b/src/web/html/index.html @@ -97,7 +97,7 @@ var date = new Date(); var obj = new Object(); obj.cmd = "set_time"; - obj.ts = parseInt(date.getTime() / 1000); + obj.val = parseInt(date.getTime() / 1000); getAjax("/api/setup", apiCb, "POST", JSON.stringify(obj)); } diff --git a/src/web/html/serial.html b/src/web/html/serial.html index b36d2350..c7e3dd8b 100644 --- a/src/web/html/serial.html +++ b/src/web/html/serial.html @@ -45,15 +45,15 @@


- + - - - - - - + + + +
@@ -152,7 +152,6 @@ } - // only for test function ctrlCb(obj) { var e = document.getElementById("result"); if(obj["success"]) @@ -169,44 +168,36 @@ const wrapper = document.getElementById('power'); wrapper.addEventListener('click', (event) => { - var power = event.target.value; var obj = new Object(); + obj.id = get_selected_iv(); + obj.cmd = "power"; - switch (power) - { + switch (event.target.value) { + default: case "Turn On": - obj.cmd = 0; + obj.val = 1; break; case "Turn Off": - obj.cmd = 1; + obj.val = 0; break; - default: - obj.cmd = 2; } - obj.inverter = get_selected_iv(); - obj.tx_request = 81; getAjax("/api/ctrl", ctrlCb, "POST", JSON.stringify(obj)); }); document.getElementById("sendpwrlim").addEventListener("click", function() { var val = parseInt(document.getElementsByName('pwrlimval')[0].value); - var ctrl = parseInt(document.getElementsByName('pwrlimcntrl')[0].value); - - if((ctrl == 1 || ctrl == 257) && val < 2) val = 2; + var cmd = document.getElementsByName('pwrlimctrl')[0].value; - if(isNaN(val) || isNaN(ctrl)) - { - var tmp = (isNaN(val)) ? "Value" : "Unit"; - document.getElementById("result").textContent = tmp + " is missing"; + if(isNaN(val)) { + document.getElementById("result").textContent = "value is missing"; return; } var obj = new Object(); - obj.inverter = get_selected_iv(); - obj.cmd = 11; - obj.tx_request = 81; - obj.payload = [val, ctrl]; + obj.id = get_selected_iv(); + obj.cmd = cmd; + obj.val = val; getAjax("/api/ctrl", ctrlCb, "POST", JSON.stringify(obj)); }); diff --git a/src/web/html/setup.html b/src/web/html/setup.html index c86b7b4b..0a586849 100644 --- a/src/web/html/setup.html +++ b/src/web/html/setup.html @@ -50,7 +50,7 @@
@@ -226,7 +226,7 @@ var date = new Date(); var obj = new Object(); obj.cmd = "set_time"; - obj.ts = parseInt(date.getTime() / 1000); + obj.val = parseInt(date.getTime() / 1000); getAjax("/api/setup", apiCbNtp, "POST", JSON.stringify(obj)); } @@ -234,7 +234,7 @@ var obj = new Object(); obj.cmd = "scan_wifi"; getAjax("/api/setup", apiCbWifi, "POST", JSON.stringify(obj)); - setTimeout(function() {getAjax('/api/setup/networks', listNetworks)}, 7000); + setTimeout(function() {getAjax('/api/setup/networks', listNetworks)}, 5000); } function syncTime() { diff --git a/src/web/web.cpp b/src/web/web.cpp index 3db382ef..d302eee3 100644 --- a/src/web/web.cpp +++ b/src/web/web.cpp @@ -648,9 +648,12 @@ void web::serialCb(String msg) { mSerialBufFill = 0; mEvts->send("webSerial, buffer overflow!", "serial", millis()); } - } +//----------------------------------------------------------------------------- +void web::apiCtrlRequest(JsonObject obj) { + mApi->ctrlRequest(obj); +} //----------------------------------------------------------------------------- #ifdef ENABLE_JSON_EP diff --git a/src/web/web.h b/src/web/web.h index 2c74faf5..849f1caa 100644 --- a/src/web/web.h +++ b/src/web/web.h @@ -40,6 +40,8 @@ class web { void serialCb(String msg); + void apiCtrlRequest(JsonObject obj); + private: void onConnect(AsyncEventSourceClient *client); diff --git a/src/web/webApi.cpp b/src/web/webApi.cpp index e71aa511..82a9efda 100644 --- a/src/web/webApi.cpp +++ b/src/web/webApi.cpp @@ -27,13 +27,26 @@ void webApi::setup(void) { mSrv->on("/api", HTTP_POST, std::bind(&webApi::onApiPost, this, std::placeholders::_1)).onBody( std::bind(&webApi::onApiPostBody, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5)); - mSrv->on("/get_setup", HTTP_GET, std::bind(&webApi::onDwnldSetup, this, std::placeholders::_1)); + mSrv->on("/get_setup", HTTP_GET, std::bind(&webApi::onDwnldSetup, this, std::placeholders::_1)); } //----------------------------------------------------------------------------- void webApi::loop(void) { } +//----------------------------------------------------------------------------- +void webApi::ctrlRequest(JsonObject obj) { + /*char out[128]; + serializeJson(obj, out, 128); + DPRINTLN(DBG_INFO, "webApi: " + String(out));*/ + DynamicJsonDocument json(128); + JsonObject dummy = json.to(); + if(obj[F("path")] == "ctrl") + setCtrl(obj, dummy); + else if(obj[F("path")] == "setup") + setSetup(obj, dummy); +} + //----------------------------------------------------------------------------- void webApi::onApi(AsyncWebServerRequest *request) { @@ -333,7 +346,7 @@ void webApi::getStaticIp(JsonObject obj) { void webApi::getMenu(JsonObject obj) { obj["name"][0] = "Live"; obj["link"][0] = "/live"; - obj["name"][1] = "Serial Console"; + obj["name"][1] = "Serial / Control"; obj["link"][1] = "/serial"; obj["name"][2] = "Settings"; obj["link"][2] = "/setup"; @@ -346,10 +359,14 @@ void webApi::getMenu(JsonObject obj) { obj["link"][6] = "/update"; obj["name"][7] = "System"; obj["link"][7] = "/system"; + obj["name"][8] = "-"; + obj["name"][9] = "Documentation"; + obj["link"][9] = "https://ahoydtu.de"; + obj["trgt"][9] = "_blank"; if(strlen(mConfig->sys.adminPwd) > 0) { - obj["name"][8] = "-"; - obj["name"][9] = "Logout"; - obj["link"][9] = "/logout"; + obj["name"][10] = "-"; + obj["name"][11] = "Logout"; + obj["link"][11] = "/logout"; } } @@ -494,72 +511,50 @@ void webApi::getRecord(JsonObject obj, record_t<> *rec) { //----------------------------------------------------------------------------- bool webApi::setCtrl(JsonObject jsonIn, JsonObject jsonOut) { - uint8_t cmd = jsonIn[F("cmd")]; - - // Todo: num is the inverter number 0-3. For better display in DPRINTLN - uint8_t num = jsonIn[F("inverter")]; - uint8_t tx_request = jsonIn[F("tx_request")]; - - if(TX_REQ_DEVCONTROL == tx_request) - { - DPRINTLN(DBG_INFO, F("devcontrol [") + String(num) + F("], cmd: 0x") + String(cmd, HEX)); - - Inverter<> *iv = getInverter(jsonIn, jsonOut); - JsonArray payload = jsonIn[F("payload")].as(); - - if(NULL != iv) - { - switch (cmd) - { - case TurnOn: - iv->devControlCmd = TurnOn; - iv->devControlRequest = true; - break; - case TurnOff: - iv->devControlCmd = TurnOff; - iv->devControlRequest = true; - break; - case CleanState_LockAndAlarm: - iv->devControlCmd = CleanState_LockAndAlarm; - iv->devControlRequest = true; - break; - case Restart: - iv->devControlCmd = Restart; - iv->devControlRequest = true; - break; - case ActivePowerContr: - iv->devControlCmd = ActivePowerContr; - iv->devControlRequest = true; - iv->powerLimit[0] = payload[0]; - iv->powerLimit[1] = payload[1]; - break; - default: - jsonOut["error"] = "unknown 'cmd' = " + String(cmd); - return false; - } - } else { - return false; - } + Inverter<> *iv = mApp->mSys->getInverterByPos(jsonIn[F("id")]); + if(NULL == iv) { + jsonOut[F("error")] = F("inverter index invalid: ") + jsonIn[F("id")].as(); + return false; + } + + if(F("power") == jsonIn[F("cmd")]) { + iv->devControlCmd = (jsonIn[F("val")] == 1) ? TurnOn : TurnOff; + iv->devControlRequest = true; + } else if(F("restart") == jsonIn[F("restart")]) { + iv->devControlCmd = Restart; + iv->devControlRequest = true; + } + else if(0 == strncmp("limit_", jsonIn[F("cmd")].as(), 6)) { + iv->powerLimit[0] = jsonIn["val"]; + if(F("limit_persistent_relative") == jsonIn[F("cmd")]) + iv->powerLimit[1] = RelativPersistent; + else if(F("limit_persistent_absolute") == jsonIn[F("cmd")]) + iv->powerLimit[1] = AbsolutPersistent; + else if(F("limit_nonpersistent_relative") == jsonIn[F("cmd")]) + iv->powerLimit[1] = RelativNonPersistent; + else if(F("limit_nonpersistent_absolute") == jsonIn[F("cmd")]) + iv->powerLimit[1] = AbsolutNonPersistent; + iv->devControlCmd = ActivePowerContr; + iv->devControlRequest = true; } else { - jsonOut[F("error")] = F("unknown 'tx_request'"); + jsonOut[F("error")] = F("unknown cmd: '") + jsonIn["cmd"].as() + "'"; return false; } return true; } - //----------------------------------------------------------------------------- bool webApi::setSetup(JsonObject jsonIn, JsonObject jsonOut) { if(F("scan_wifi") == jsonIn[F("cmd")]) mApp->scanAvailNetworks(); else if(F("set_time") == jsonIn[F("cmd")]) - mApp->setTimestamp(jsonIn[F("ts")]); + mApp->setTimestamp(jsonIn[F("val")]); else if(F("sync_ntp") == jsonIn[F("cmd")]) mApp->setTimestamp(0); // 0: update ntp flag else if(F("serial_utc_offset") == jsonIn[F("cmd")]) - mTimezoneOffset = jsonIn[F("ts")]; + mTimezoneOffset = jsonIn[F("val")]; else if(F("discovery_cfg") == jsonIn[F("cmd")]) mApp->mFlagSendDiscoveryConfig = true; // for homeassistant else { @@ -569,13 +564,3 @@ bool webApi::setSetup(JsonObject jsonIn, JsonObject jsonOut) { return true; } - - -//----------------------------------------------------------------------------- -Inverter<> *webApi::getInverter(JsonObject jsonIn, JsonObject jsonOut) { - uint8_t id = jsonIn[F("inverter")]; - Inverter<> *iv = mApp->mSys->getInverterByPos(id); - if(NULL == iv) - jsonOut[F("error")] = F("inverter index to high: ") + String(id); - return iv; -} diff --git a/src/web/webApi.h b/src/web/webApi.h index 873080d7..de37d9ba 100644 --- a/src/web/webApi.h +++ b/src/web/webApi.h @@ -25,6 +25,8 @@ class webApi { return mTimezoneOffset; } + void ctrlRequest(JsonObject obj); + private: void onApi(AsyncWebServerRequest *request); void onApiPost(AsyncWebServerRequest *request); @@ -57,8 +59,6 @@ class webApi { bool setCtrl(JsonObject jsonIn, JsonObject jsonOut); bool setSetup(JsonObject jsonIn, JsonObject jsonOut); - Inverter<> *getInverter(JsonObject jsonIn, JsonObject jsonOut); - double round3(double value) { return (int)(value * 1000 + 0.5) / 1000.0; }