diff --git a/src/app.cpp b/src/app.cpp index cd222136..71d60f42 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -9,8 +9,8 @@ #endif #include "app.h" - #include +#include "utils/sun.h" //----------------------------------------------------------------------------- app::app() { @@ -37,11 +37,10 @@ void app::setup(uint32_t timeout) { mWifi->setup(timeout, mWifiSettingsValid); + mSys->setup(mConfig.amplifierPower, mConfig.pinIrq, mConfig.pinCe, mConfig.pinCs); #ifndef AP_ONLY setupMqtt(); #endif - mSys->setup(mConfig.amplifierPower, mConfig.pinIrq, mConfig.pinCe, mConfig.pinCs); - setupLed(); mWebInst = new web(this, &mSysConfig, &mConfig, &mStat, mVersion); @@ -85,7 +84,7 @@ void app::loop(void) { if (mFlagSendDiscoveryConfig) { mFlagSendDiscoveryConfig = false; - mMqtt.sendMqttDiscoveryConfig(mSys, mConfig.mqtt.topic, mMqttInterval); + mMqtt.sendMqttDiscoveryConfig(mConfig.mqtt.topic, mMqttInterval); } mSys->Radio.loop(); @@ -164,13 +163,13 @@ void app::loop(void) { if (!mLatestSunTimestamp) { // first call: calculate time zone from longitude to refresh at local midnight mCalculatedTimezoneOffset = (int8_t)((mConfig.sunLon >= 0 ? mConfig.sunLon + 7.5 : mConfig.sunLon - 7.5) / 15) * 3600; } - calculateSunriseSunset(); + ah::calculateSunriseSunset(mUtcTimestamp, mCalculatedTimezoneOffset, mConfig.sunLat, mConfig.sunLon, &mSunrise, &mSunset); mLatestSunTimestamp = mUtcTimestamp; } if ((++mMqttTicker >= mMqttInterval) && (mMqttInterval != 0xffff) && mMqttActive) { mMqttTicker = 0; - mMqtt.sendIvData(mSys, mUtcTimestamp, mMqttSendList); + mMqtt.sendIvData(mUtcTimestamp, mMqttSendList); } if (mConfig.serialShowIv) { @@ -405,110 +404,6 @@ void app::processPayload(bool retransmit) { } } -//----------------------------------------------------------------------------- -void app::cbMqtt(char *topic, byte *payload, unsigned int length) { - // callback handling on subscribed devcontrol topic - DPRINTLN(DBG_INFO, F("app::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 = this->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")); -} - //----------------------------------------------------------------------------- bool app::getWifiApActive(void) { return mWifi->getApActive(); @@ -685,15 +580,12 @@ void app::setupMqtt(void) { if (mConfig.mqtt.broker[0] > 0) { mMqttActive = true; if (mMqttInterval < MIN_MQTT_INTERVAL) mMqttInterval = MIN_MQTT_INTERVAL; - } else { + } else mMqttInterval = 0xffff; - } mMqttTicker = 0; - if(mMqttActive) { - mMqtt.setup(&mConfig.mqtt, mSysConfig.deviceName); - mMqtt.setCallback(std::bind(&app::cbMqtt, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); - } + if(mMqttActive) + mMqtt.setup(&mConfig.mqtt, mSysConfig.deviceName, mSys); if (mMqttActive) { mMqtt.sendMsg("version", mVersion); @@ -747,31 +639,3 @@ void app::resetPayload(Inverter<> *iv) { mPayload[iv->id].requested = false; mPayload[iv->id].ts = mUtcTimestamp; } - -//----------------------------------------------------------------------------- -void app::calculateSunriseSunset() { - // Source: https://en.wikipedia.org/wiki/Sunrise_equation#Complete_calculation_on_Earth - - // Julian day since 1.1.2000 12:00 + correction 69.12s - double n_JulianDay = (mUtcTimestamp + mCalculatedTimezoneOffset) / 86400 - 10957.0 + 0.0008; - // Mean solar time - double J = n_JulianDay - mConfig.sunLon / 360; - // Solar mean anomaly - double M = fmod((357.5291 + 0.98560028 * J), 360); - // Equation of the center - double C = 1.9148 * SIN(M) + 0.02 * SIN(2 * M) + 0.0003 * SIN(3 * M); - // Ecliptic longitude - double lambda = fmod((M + C + 180 + 102.9372), 360); - // Solar transit - double Jtransit = 2451545.0 + J + 0.0053 * SIN(M) - 0.0069 * SIN(2 * lambda); - // Declination of the sun - double delta = ASIN(SIN(lambda) * SIN(23.44)); - // Hour angle - double omega = ACOS(SIN(-0.83) - SIN(mConfig.sunLat) * SIN(delta) / COS(mConfig.sunLat) * COS(delta)); - // Calculate sunrise and sunset - double Jrise = Jtransit - omega / 360; - double Jset = Jtransit + omega / 360; - // Julian sunrise/sunset to UTC unix timestamp (days incl. fraction to seconds + unix offset 1.1.2000 12:00) - mSunrise = (Jrise - 2451545.0) * 86400 + 946728000; // OPTIONAL: Add an offset of +-seconds to the end of the line - mSunset = (Jset - 2451545.0) * 86400 + 946728000; // OPTIONAL: Add an offset of +-seconds to the end of the line -} diff --git a/src/app.h b/src/app.h index 2dd48f62..1b1ae31e 100644 --- a/src/app.h +++ b/src/app.h @@ -247,8 +247,6 @@ class app { DPRINTLN(DBG_VERBOSE, F(" - frag: ") + String(frag)); } - void calculateSunriseSunset(void); - uint32_t mUptimeSecs; uint32_t mPrevMillis; uint8_t mHeapStatCnt; @@ -294,8 +292,7 @@ class app { // sun int32_t mCalculatedTimezoneOffset; - uint32_t mSunrise; - uint32_t mSunset; + uint32_t mSunrise, mSunset; uint32_t mLatestSunTimestamp; }; diff --git a/src/utils/sun.h b/src/utils/sun.h new file mode 100644 index 00000000..7c6d035b --- /dev/null +++ b/src/utils/sun.h @@ -0,0 +1,38 @@ +//----------------------------------------------------------------------------- +// 2022 Ahoy, https://www.mikrocontroller.net/topic/525778 +// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/ +//----------------------------------------------------------------------------- + +#ifndef __SUN_H__ +#define __SUN_H__ + +namespace ah { + void calculateSunriseSunset(uint32_t utcTs, uint32_t offset, float lat, float lon, uint32_t *sunrise, uint32_t *sunset) { + // Source: https://en.wikipedia.org/wiki/Sunrise_equation#Complete_calculation_on_Earth + + // Julian day since 1.1.2000 12:00 + correction 69.12s + double n_JulianDay = (utcTs + offset) / 86400 - 10957.0 + 0.0008; + // Mean solar time + double J = n_JulianDay - lon / 360; + // Solar mean anomaly + double M = fmod((357.5291 + 0.98560028 * J), 360); + // Equation of the center + double C = 1.9148 * SIN(M) + 0.02 * SIN(2 * M) + 0.0003 * SIN(3 * M); + // Ecliptic longitude + double lambda = fmod((M + C + 180 + 102.9372), 360); + // Solar transit + double Jtransit = 2451545.0 + J + 0.0053 * SIN(M) - 0.0069 * SIN(2 * lambda); + // Declination of the sun + double delta = ASIN(SIN(lambda) * SIN(23.44)); + // Hour angle + double omega = ACOS(SIN(-0.83) - SIN(lat) * SIN(delta) / COS(lat) * COS(delta)); + // Calculate sunrise and sunset + double Jrise = Jtransit - omega / 360; + double Jset = Jtransit + omega / 360; + // Julian sunrise/sunset to UTC unix timestamp (days incl. fraction to seconds + unix offset 1.1.2000 12:00) + *sunrise = (Jrise - 2451545.0) * 86400 + 946728000; // OPTIONAL: Add an offset of +-seconds to the end of the line + *sunset = (Jset - 2451545.0) * 86400 + 946728000; // OPTIONAL: Add an offset of +-seconds to the end of the line + } +} + +#endif /*__SUN_H__*/ diff --git a/src/web/mqtt.h b/src/web/mqtt.h index f778e2be..d5b46ae7 100644 --- a/src/web/mqtt.h +++ b/src/web/mqtt.h @@ -38,15 +38,18 @@ class mqtt { ~mqtt() { } - void setup(mqttConfig_t *cfg, const char *devname) { + void setup(mqttConfig_t *cfg, const char *devname, HMSYSTEM *sys) { DPRINTLN(DBG_VERBOSE, F("mqtt.h:setup")); mAddressSet = true; mCfg = cfg; snprintf(mDevName, DEVNAME_LEN, "%s", devname); + mSys = sys; mClient->setServer(mCfg->broker, mCfg->port); mClient->setBufferSize(MQTT_MAX_PACKET_SIZE); + + setCallback(std::bind(&mqtt::cbMqtt, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); } void setCallback(MQTT_CALLBACK_SIGNATURE) { @@ -94,12 +97,12 @@ class mqtt { return mTxCnt; } - void sendMqttDiscoveryConfig(HMSYSTEM *sys, const char *topic, uint32_t invertval) { - DPRINTLN(DBG_VERBOSE, F("app::sendMqttDiscoveryConfig")); + void sendMqttDiscoveryConfig(const char *topic, uint32_t invertval) { + DPRINTLN(DBG_VERBOSE, F("sendMqttDiscoveryConfig")); char stateTopic[64], discoveryTopic[64], buffer[512], name[32], uniq_id[32]; - for (uint8_t id = 0; id < sys->getNumInverters(); id++) { - Inverter<> *iv = sys->getInverterByPos(id); + for (uint8_t id = 0; id < mSys->getNumInverters(); id++) { + Inverter<> *iv = mSys->getInverterByPos(id); if (NULL != iv) { record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug); DynamicJsonDocument deviceDoc(128); @@ -145,7 +148,7 @@ class mqtt { } } - void sendIvData(HMSYSTEM *sys, uint32_t mUtcTs, std::queue list) { + void sendIvData(uint32_t mUtcTs, std::queue list) { isConnected(true); // really needed? See comment from HorstG-57 #176 char topic[32 + MAX_NAME_LENGTH], val[32]; float total[4]; @@ -160,8 +163,8 @@ class mqtt { while(!list.empty()) { memset(total, 0, sizeof(float) * 4); - for (uint8_t id = 0; id < sys->getNumInverters(); id++) { - Inverter<> *iv = sys->getInverterByPos(id); + for (uint8_t id = 0; id < mSys->getNumInverters(); id++) { + Inverter<> *iv = mSys->getInverterByPos(id); if (NULL == iv) continue; // skip to next inverter @@ -256,24 +259,6 @@ class mqtt { } } - const char *getFieldDeviceClass(uint8_t fieldId) { - uint8_t pos = 0; - for (; pos < DEVICE_CLS_ASSIGN_LIST_LEN; pos++) { - if (deviceFieldAssignment[pos].fieldId == fieldId) - break; - } - return (pos >= DEVICE_CLS_ASSIGN_LIST_LEN) ? NULL : deviceClasses[deviceFieldAssignment[pos].deviceClsId]; - } - - const char *getFieldStateClass(uint8_t fieldId) { - uint8_t pos = 0; - for (; pos < DEVICE_CLS_ASSIGN_LIST_LEN; pos++) { - if (deviceFieldAssignment[pos].fieldId == fieldId) - break; - } - return (pos >= DEVICE_CLS_ASSIGN_LIST_LEN) ? NULL : stateClasses[deviceFieldAssignment[pos].stateClsId]; - } - private: void reconnect(void) { DPRINTLN(DBG_DEBUG, F("mqtt.h:reconnect")); @@ -311,8 +296,130 @@ class mqtt { } } + const char *getFieldDeviceClass(uint8_t fieldId) { + uint8_t pos = 0; + for (; pos < DEVICE_CLS_ASSIGN_LIST_LEN; pos++) { + if (deviceFieldAssignment[pos].fieldId == fieldId) + break; + } + return (pos >= DEVICE_CLS_ASSIGN_LIST_LEN) ? NULL : deviceClasses[deviceFieldAssignment[pos].deviceClsId]; + } + + const char *getFieldStateClass(uint8_t fieldId) { + uint8_t pos = 0; + for (; pos < DEVICE_CLS_ASSIGN_LIST_LEN; pos++) { + if (deviceFieldAssignment[pos].fieldId == fieldId) + break; + } + return (pos >= DEVICE_CLS_ASSIGN_LIST_LEN) ? NULL : stateClasses[deviceFieldAssignment[pos].stateClsId]; + } + + 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")); + } + WiFiClient mEspClient; PubSubClient *mClient; + HMSYSTEM *mSys; bool mAddressSet; mqttConfig_t *mCfg;