mirror of https://github.com/lumapu/ahoy.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
354 lines
15 KiB
354 lines
15 KiB
//-----------------------------------------------------------------------------
|
|
// 2024 Ahoy, https://ahoydtu.de
|
|
// Creative Commons - https://creativecommons.org/licenses/by-nc-sa/4.0/deed
|
|
//-----------------------------------------------------------------------------
|
|
|
|
#ifndef __PUB_MQTT_IV_DATA_H__
|
|
#define __PUB_MQTT_IV_DATA_H__
|
|
|
|
#include <array>
|
|
#include "../utils/dbg.h"
|
|
#include "../hm/hmSystem.h"
|
|
#include "pubMqttDefs.h"
|
|
|
|
typedef std::function<void(const char *subTopic, const char *payload, bool retained, uint8_t qos)> pubMqttPublisherType;
|
|
|
|
struct sendListCmdIv {
|
|
uint8_t cmd;
|
|
Inverter<> *iv;
|
|
sendListCmdIv(uint8_t a, Inverter<> *i) : cmd(a), iv(i) {}
|
|
};
|
|
|
|
template<class HMSYSTEM>
|
|
class PubMqttIvData {
|
|
public:
|
|
PubMqttIvData() : mTotal{}, mSubTopic{}, mVal{} {}
|
|
|
|
void setup(IApp *app, HMSYSTEM *sys, cfgMqtt_t *cfg_mqtt, uint32_t *utcTs, std::queue<sendListCmdIv> *sendList) {
|
|
mApp = app;
|
|
mSys = sys;
|
|
mCfg = cfg_mqtt;
|
|
mUtcTimestamp = utcTs;
|
|
mSendList = sendList;
|
|
mState = IDLE;
|
|
mYldTotalStore = 0;
|
|
|
|
mRTRDataHasBeenSent = false;
|
|
|
|
mTable[IDLE] = &PubMqttIvData::stateIdle;
|
|
mTable[START] = &PubMqttIvData::stateStart;
|
|
mTable[FIND_NXT_IV] = &PubMqttIvData::stateFindNxtIv;
|
|
mTable[SEND_DATA] = &PubMqttIvData::stateSend;
|
|
mTable[SEND_TOTALS] = &PubMqttIvData::stateSendTotals;
|
|
}
|
|
|
|
void loop() {
|
|
(this->*mTable[mState])();
|
|
yield();
|
|
}
|
|
|
|
void resetYieldDay() {
|
|
mYldTotalStore = 0;
|
|
}
|
|
|
|
bool start() {
|
|
if(IDLE != mState)
|
|
return false;
|
|
|
|
mRTRDataHasBeenSent = false;
|
|
mState = START;
|
|
return true;
|
|
}
|
|
|
|
void setPublishFunc(pubMqttPublisherType cb) {
|
|
mPublish = cb;
|
|
}
|
|
|
|
private:
|
|
enum State {IDLE, START, FIND_NXT_IV, SEND_DATA, SEND_TOTALS, NUM_STATES};
|
|
typedef void (PubMqttIvData::*StateFunction)();
|
|
|
|
void stateIdle() {
|
|
; // nothing to do
|
|
}
|
|
|
|
void stateStart() {
|
|
mLastIvId = 0;
|
|
mTotalFound = false;
|
|
mSendTotalYd = true;
|
|
mAllTotalFound = true;
|
|
mAtLeastOneWasntSent = false;
|
|
if(!mSendList->empty()) {
|
|
mCmd = mSendList->front().cmd;
|
|
mIvSend = mSendList->front().iv;
|
|
|
|
if((RealTimeRunData_Debug != mCmd) || !mRTRDataHasBeenSent) { // send RealTimeRunData only once
|
|
mSendTotals = (RealTimeRunData_Debug == mCmd);
|
|
memset(mTotal, 0, sizeof(float) * 5);
|
|
mState = FIND_NXT_IV;
|
|
} else
|
|
mSendList->pop();
|
|
} else
|
|
mState = IDLE;
|
|
}
|
|
|
|
void stateFindNxtIv() {
|
|
bool found = false;
|
|
|
|
for (; mLastIvId < mSys->getNumInverters(); mLastIvId++) {
|
|
mIv = mSys->getInverterByPos(mLastIvId);
|
|
if (NULL != mIv) {
|
|
if (mIv->config->enabled) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
mLastIvId++;
|
|
|
|
mPos = 0;
|
|
if(found) {
|
|
record_t<> *rec = mIv->getRecordStruct(mCmd);
|
|
if(MqttSentStatus::NEW_DATA == rec->mqttSentStatus) {
|
|
snprintf(mSubTopic.data(), mSubTopic.size(), "%s/last_success", mIv->config->name);
|
|
snprintf(mVal.data(), mVal.size(), "%d", mIv->getLastTs(rec));
|
|
mPublish(mSubTopic.data(), mVal.data(), true, QOS_0);
|
|
|
|
if((mIv->ivGen == IV_HMS) || (mIv->ivGen == IV_HMT)) {
|
|
snprintf(mSubTopic.data(), mSubTopic.size(), "%s/rssi", mIv->config->name);
|
|
snprintf(mVal.data(), mVal.size(), "%d", mIv->rssi);
|
|
mPublish(mSubTopic.data(), mVal.data(), false, QOS_0);
|
|
}
|
|
rec->mqttSentStatus = MqttSentStatus::LAST_SUCCESS_SENT;
|
|
}
|
|
|
|
mIv->isProducing(); // recalculate status
|
|
mState = SEND_DATA;
|
|
} else if(mSendTotals && mTotalFound && mAtLeastOneWasntSent) {
|
|
if(mYldTotalStore > mTotal[2])
|
|
mSendTotalYd = false; // don't send yield total if last value was greater
|
|
else
|
|
mYldTotalStore = mTotal[2];
|
|
|
|
mState = SEND_TOTALS;
|
|
} else {
|
|
mSendList->pop();
|
|
mState = START;
|
|
}
|
|
}
|
|
|
|
void stateSend() {
|
|
record_t<> *rec = mIv->getRecordStruct(mCmd);
|
|
if(rec == NULL) {
|
|
if (mCmd != GetLossRate)
|
|
DPRINT(DBG_WARN, "unknown record to publish!");
|
|
return;
|
|
}
|
|
|
|
if(mPos < rec->length) {
|
|
bool retained = false;
|
|
if (RealTimeRunData_Debug == mCmd) {
|
|
if((FLD_YT == rec->assign[mPos].fieldId) || (FLD_YD == rec->assign[mPos].fieldId))
|
|
retained = true;
|
|
|
|
// calculate total values for RealTimeRunData_Debug
|
|
if (CH0 == rec->assign[mPos].ch) {
|
|
if(mIv->getStatus() != InverterStatus::OFF) {
|
|
mTotalFound = true;
|
|
switch (rec->assign[mPos].fieldId) {
|
|
case FLD_PAC:
|
|
mTotal[0] += mIv->getValue(mPos, rec);
|
|
break;
|
|
case FLD_YT:
|
|
mTotal[1] += mIv->getValue(mPos, rec);
|
|
break;
|
|
case FLD_YD: {
|
|
mTotal[2] += mIv->getValue(mPos, rec);
|
|
break;
|
|
}
|
|
case FLD_PDC:
|
|
mTotal[3] += mIv->getValue(mPos, rec);
|
|
break;
|
|
}
|
|
} else
|
|
mAllTotalFound = false;
|
|
}
|
|
}
|
|
|
|
if (MqttSentStatus::LAST_SUCCESS_SENT == rec->mqttSentStatus) {
|
|
mAtLeastOneWasntSent = true;
|
|
if(InverterDevInform_All == mCmd) {
|
|
snprintf(mSubTopic.data(), mSubTopic.size(), "%s/firmware", mIv->config->name);
|
|
snprintf(mVal.data(), mVal.size(), "{\"version\":%d,\"build_year\":\"%d\",\"build_month_day\":%d,\"build_hour_min\":%d,\"bootloader\":%d}",
|
|
static_cast<int>(mIv->getChannelFieldValue(CH0, FLD_FW_VERSION, rec)),
|
|
static_cast<int>(mIv->getChannelFieldValue(CH0, FLD_FW_BUILD_YEAR, rec)),
|
|
static_cast<int>(mIv->getChannelFieldValue(CH0, FLD_FW_BUILD_MONTH_DAY, rec)),
|
|
static_cast<int>(mIv->getChannelFieldValue(CH0, FLD_FW_BUILD_HOUR_MINUTE, rec)),
|
|
static_cast<int>(mIv->getChannelFieldValue(CH0, FLD_BOOTLOADER_VER, rec)));
|
|
} else if(InverterDevInform_Simple == mCmd) {
|
|
snprintf(mSubTopic.data(), mSubTopic.size(), "%s/hardware", mIv->config->name);
|
|
snprintf(mVal.data(), mVal.size(), "{\"part\":%d,\"version\":\"%d\",\"grid_profile_code\":%d,\"grid_profile_version\":%d}",
|
|
static_cast<int>(mIv->getChannelFieldValue(CH0, FLD_PART_NUM, rec)),
|
|
static_cast<int>(mIv->getChannelFieldValue(CH0, FLD_HW_VERSION, rec)),
|
|
static_cast<int>(mIv->getChannelFieldValue(CH0, FLD_GRID_PROFILE_CODE, rec)),
|
|
static_cast<int>(mIv->getChannelFieldValue(CH0, FLD_GRID_PROFILE_VERSION, rec)));
|
|
} else {
|
|
if (!mCfg->json) {
|
|
snprintf(mSubTopic.data(), mSubTopic.size(), "%s/ch%d/%s", mIv->config->name, rec->assign[mPos].ch, fields[rec->assign[mPos].fieldId]);
|
|
snprintf(mVal.data(), mVal.size(), "%g", ah::round3(mIv->getValue(mPos, rec)));
|
|
} else {
|
|
if (FLD_ACT_ACTIVE_PWR_LIMIT == rec->assign[mPos].fieldId) {
|
|
uint8_t qos = (FLD_ACT_ACTIVE_PWR_LIMIT == rec->assign[mPos].fieldId) ? QOS_2 : QOS_0;
|
|
snprintf(mSubTopic.data(), mSubTopic.size(), "%s/%s", mIv->config->name, fields[rec->assign[mPos].fieldId]);
|
|
snprintf(mVal.data(), mVal.size(), "%g", ah::round3(mIv->getValue(mPos, rec)));
|
|
mPublish(mSubTopic.data(), mVal.data(), retained, qos);
|
|
}
|
|
}
|
|
}
|
|
|
|
if ((InverterDevInform_All == mCmd) || (InverterDevInform_Simple == mCmd) || !mCfg->json)
|
|
{
|
|
uint8_t qos = (FLD_ACT_ACTIVE_PWR_LIMIT == rec->assign[mPos].fieldId) ? QOS_2 : QOS_0;
|
|
if((FLD_EVT != rec->assign[mPos].fieldId) && (FLD_LAST_ALARM_CODE != rec->assign[mPos].fieldId))
|
|
mPublish(mSubTopic.data(), mVal.data(), retained, qos);
|
|
}
|
|
}
|
|
mPos++;
|
|
} else {
|
|
if (MqttSentStatus::LAST_SUCCESS_SENT == rec->mqttSentStatus) {
|
|
if (mCfg->json && (RealTimeRunData_Debug == mCmd)) {
|
|
DynamicJsonDocument doc(300);
|
|
|
|
for (mPos = 0; mPos < rec->length; mPos++) {
|
|
doc[fields[rec->assign[mPos].fieldId]] = ah::round3(mIv->getValue(mPos, rec));
|
|
|
|
bool publish = false;
|
|
if (mPos != (rec->length - 1)) { // not last one
|
|
if (rec->assign[mPos].ch != rec->assign[mPos+1].ch)
|
|
publish = true;
|
|
} else
|
|
publish = true;
|
|
|
|
if (publish) {
|
|
doc[F("ts")] = rec->ts;
|
|
// if next channel or end->publish
|
|
serializeJson(doc, mVal.data(), mVal.size());
|
|
snprintf(mSubTopic.data(), mSubTopic.size(), "%s/ch%d", mIv->config->name, rec->assign[mPos].ch);
|
|
mPublish(mSubTopic.data(), mVal.data(), false, QOS_0);
|
|
doc.clear();
|
|
}
|
|
}
|
|
}
|
|
|
|
sendRadioStat(rec->length);
|
|
rec->mqttSentStatus = MqttSentStatus::DATA_SENT;
|
|
}
|
|
mState = FIND_NXT_IV;
|
|
}
|
|
}
|
|
|
|
inline void sendRadioStat(uint8_t start) {
|
|
snprintf(mSubTopic.data(), mSubTopic.size(), "%s/radio_stat", mIv->config->name);
|
|
snprintf(mVal.data(), mVal.size(), "{\"tx\":%d,\"success\":%d,\"fail\":%d,\"no_answer\":%d,\"retransmits\":%d,\"lossIvRx\":%d,\"lossIvTx\":%d,\"lossDtuRx\":%d,\"lossDtuTx\":%d}",
|
|
mIv->radioStatistics.txCnt,
|
|
mIv->radioStatistics.rxSuccess,
|
|
mIv->radioStatistics.rxFail,
|
|
mIv->radioStatistics.rxFailNoAnswer,
|
|
mIv->radioStatistics.retransmits,
|
|
mIv->radioStatistics.ivLoss,
|
|
mIv->radioStatistics.ivSent,
|
|
mIv->radioStatistics.dtuLoss,
|
|
mIv->radioStatistics.dtuSent);
|
|
mPublish(mSubTopic.data(), mVal.data(), false, QOS_0);
|
|
}
|
|
|
|
void stateSendTotals() {
|
|
mRTRDataHasBeenSent = true;
|
|
if(mPos < 5) {
|
|
uint8_t fieldId;
|
|
bool retained = true;
|
|
switch (mPos) {
|
|
default:
|
|
case 0:
|
|
fieldId = FLD_PAC;
|
|
retained = false;
|
|
break;
|
|
case 1:
|
|
if(!mAllTotalFound) {
|
|
mPos++;
|
|
return;
|
|
}
|
|
fieldId = FLD_YT;
|
|
break;
|
|
case 2:
|
|
if((!mAllTotalFound) || (!mSendTotalYd)) {
|
|
mPos++;
|
|
return;
|
|
}
|
|
fieldId = FLD_YD;
|
|
break;
|
|
case 3:
|
|
fieldId = FLD_PDC;
|
|
retained = false;
|
|
break;
|
|
case 4:
|
|
fieldId = FLD_MP;
|
|
retained = false;
|
|
mTotal[4] = mApp->getTotalMaxPower();
|
|
break;
|
|
}
|
|
if (!mCfg->json) {
|
|
snprintf(mSubTopic.data(), mSubTopic.size(), "total/%s", fields[fieldId]);
|
|
snprintf(mVal.data(), mVal.size(), "%g", ah::round3(mTotal[mPos]));
|
|
mPublish(mSubTopic.data(), mVal.data(), retained, QOS_0);
|
|
}
|
|
mPos++;
|
|
} else {
|
|
if (mCfg->json) {
|
|
int type[5] = {FLD_PAC, FLD_YT, FLD_YD, FLD_PDC, FLD_MP};
|
|
snprintf(mVal.data(), mVal.size(), "{");
|
|
|
|
for (mPos = 0; mPos < 5; mPos++) {
|
|
snprintf(mSubTopic.data(), mSubTopic.size(), "\"%s\":%g", fields[type[mPos]], ah::round3(mTotal[mPos]));
|
|
strcat(mVal.data(), mSubTopic.data());
|
|
if (mPos < 4)
|
|
strcat(mVal.data(), ",");
|
|
else
|
|
strcat(mVal.data(), "}");
|
|
}
|
|
mPublish("total", mVal.data(), true, QOS_0);
|
|
}
|
|
mSendList->pop();
|
|
mSendTotals = false;
|
|
mState = IDLE;
|
|
}
|
|
}
|
|
|
|
private:
|
|
IApp *mApp = nullptr;
|
|
cfgMqtt_t *mCfg = nullptr;
|
|
|
|
HMSYSTEM *mSys = nullptr;
|
|
uint32_t *mUtcTimestamp = nullptr;
|
|
pubMqttPublisherType mPublish;
|
|
State mState = IDLE;
|
|
StateFunction mTable[NUM_STATES];
|
|
|
|
uint8_t mCmd = 0;
|
|
uint8_t mLastIvId = 0;
|
|
bool mSendTotals = false, mTotalFound = false, mAllTotalFound = false;
|
|
bool mSendTotalYd = false, mAtLeastOneWasntSent = false;
|
|
float mTotal[5], mYldTotalStore = 0;
|
|
|
|
Inverter<> *mIv = nullptr, *mIvSend = nullptr;
|
|
uint8_t mPos = 0;
|
|
bool mRTRDataHasBeenSent = false;
|
|
|
|
std::array<char, (32 + MAX_NAME_LENGTH + 1)> mSubTopic;
|
|
std::array<char, 300> mVal;
|
|
|
|
std::queue<sendListCmdIv> *mSendList = nullptr;
|
|
};
|
|
|
|
#endif /*__PUB_MQTT_IV_DATA_H__*/
|
|
|