mirror of
https://github.com/lumapu/ahoy.git
synced 2025-04-29 10:16:21 +02:00
Merge branch 'development03' into eth
* untested
This commit is contained in:
commit
e4e7e64861
53 changed files with 3017 additions and 605 deletions
|
@ -14,6 +14,8 @@ Hoymiles Inverters
|
|||
| ----- | ----- | ------ | ------- |
|
||||
| ✔️ | MI | 300, 600, 1000/1200/⚠️ 1500 | 4-Channel is not tested yet |
|
||||
| ✔️ | HM | 300, 350, 400, 600, 700, 800, 1000?, 1200, 1500 | |
|
||||
| ✔️ | HMS | 350, 500, 800, 1000, 1600, 1800, 2000 | |
|
||||
| ✔️ | HMT | 1600, 1800, 2250 | |
|
||||
| ⚠️ | TSUN | [TSOL-M350](https://www.tsun-ess.com/Micro-Inverter/M350-M400), [TSOL-M400](https://www.tsun-ess.com/Micro-Inverter/M350-M400), [TSOL-M800/TSOL-M800(DE)](https://www.tsun-ess.com/Micro-Inverter/M800) | others may work as well (need to be verified). |
|
||||
|
||||
## Table of Contents
|
||||
|
|
|
@ -22,7 +22,7 @@ Table of approaches:
|
|||
|
||||
| Board | MI | HM | HMS/HMT | comment | HowTo start |
|
||||
| ------ | -- | -- | ------- | ------- | ---------- |
|
||||
| [ESP8266/ESP32, C++](Getting_Started.md) | ✔️ | ✔️ | coming soon✨ | 👈 the most effort is spent here | [create your own DTU](https://ahoydtu.de/getting_started/) |
|
||||
| [ESP8266/ESP32, C++](Getting_Started.md) | ✔️ | ✔️ | ✔️ ✨ | 👈 the most effort is spent here | [create your own DTU](https://ahoydtu.de/getting_started/) |
|
||||
| [Arduino Nano, C++](tools/nano/NRF24_SendRcv/) | ❌ | ✔️ | ❌ | |
|
||||
| [Raspberry Pi, Python](tools/rpi/) | ❌ | ✔️ | ❌ | |
|
||||
| [Others, C/C++](tools/nano/NRF24_SendRcv/) | ❌ | ✔️ | ❌ | |
|
||||
|
|
|
@ -48,9 +48,11 @@ The AhoyDTU will publish on the following topics
|
|||
|
||||
| status code | Remarks |
|
||||
|---|---|
|
||||
| 0 | not available and not producing |
|
||||
| 0 | off: not available and not producing |
|
||||
| 1 | available but not producing |
|
||||
| 2 | available and producing |
|
||||
| 3 | available and was producing |
|
||||
| 4 | was available |
|
||||
|
||||
|
||||
### `<TOPIC>/<INVERTER_NAME_FROM_SETUP>/ch0/#`
|
||||
|
|
5
src/.vscode/settings.json
vendored
5
src/.vscode/settings.json
vendored
|
@ -8,7 +8,7 @@
|
|||
"files.eol": "\n",
|
||||
"files.trimTrailingWhitespace": true,
|
||||
"diffEditor.ignoreTrimWhitespace": true,
|
||||
"files.autoSave": "afterDelay",
|
||||
"files.autoSave": "off",
|
||||
"editor.tabSize": 4,
|
||||
"editor.insertSpaces": true,
|
||||
// `editor.tabSize` and `editor.insertSpaces` will be detected based on the file contents.
|
||||
|
@ -79,7 +79,8 @@
|
|||
"mutex": "cpp",
|
||||
"ranges": "cpp",
|
||||
"stop_token": "cpp",
|
||||
"thread": "cpp"
|
||||
"thread": "cpp",
|
||||
"variant": "cpp"
|
||||
},
|
||||
"cmake.configureOnOpen": false,
|
||||
"editor.formatOnSave": false,
|
||||
|
|
101
src/CHANGES.md
101
src/CHANGES.md
|
@ -1,5 +1,106 @@
|
|||
# Development Changes
|
||||
|
||||
## 0.7.21 - 2023-07-30
|
||||
* fix MqTT YieldDay Total goes to 0 serveral times #1016
|
||||
|
||||
## 0.7.20 - 2023-07-28
|
||||
* merge PR #1048 version and hash in API, fixes #1045
|
||||
* fix: no yield day update if yield day reads `0` after inverter reboot (mostly on evening) #848
|
||||
* try to fix Wifi override #1047
|
||||
* added information after NTP sync to WebUI #1040
|
||||
|
||||
## 0.7.19 - 2023-07-27
|
||||
* next attempt to fix yield day for multiple inverters #1016
|
||||
* reduced threshold for inverter state machine from 60min to 15min to go from state `WAS_ON` to `OFF`
|
||||
|
||||
## 0.7.18 - 2023-07-26
|
||||
* next attempt to fix yield day for multiple inverters #1016
|
||||
|
||||
## 0.7.17 - 2023-07-25
|
||||
* next attempt to fix yield day for multiple inverters #1016
|
||||
* added two more states for the inverter status (also docu)
|
||||
|
||||
## 0.7.16 - 2023-07-24
|
||||
* next attempt to fix yield day for multiple inverters #1016
|
||||
* fix export settings date #1040
|
||||
* fix time on WebUI (timezone was not observed) #913 #1016
|
||||
|
||||
## 0.7.15 - 2023-07-23
|
||||
* add NTP sync interval #1019
|
||||
* adjusted range of contrast / luminance setting #1041
|
||||
* use only ISO time format in Web-UI #913
|
||||
|
||||
## 0.7.14 - 2023-07-23
|
||||
* fix Contrast for Nokia Display #1041
|
||||
* attempt to fix #1016 by improving inverter status
|
||||
* added option to adjust effiency for yield (day/total) #1028
|
||||
|
||||
## 0.7.13 - 2023-07-19
|
||||
* merged display PR #1027
|
||||
* add date, time and version to export json #1024
|
||||
|
||||
## 0.7.12 - 2023-07-09
|
||||
* added inverter status - state-machine #1016
|
||||
|
||||
## 0.7.11 - 2023-07-09
|
||||
* fix MqTT endless loop #1013
|
||||
|
||||
## 0.7.10 - 2023-07-08
|
||||
* fix MqTT endless loop #1013
|
||||
|
||||
## 0.7.9 - 2023-07-08
|
||||
* added 'improve' functions to set wifi password directly with ESP web tools #1014
|
||||
* fixed MqTT publish while appling power limit #1013
|
||||
* slightly improved HMT live view (Voltage & Current)
|
||||
|
||||
## 0.7.8 - 2023-07-05
|
||||
* fix `YieldDay`, `YieldTotal` and `P_AC` in `TotalValues` #929
|
||||
* fix some serial debug prints
|
||||
* merge PR #1005 which fixes issue #889
|
||||
* merge homeassistant PR #963
|
||||
* merge PR #890 which gives option for scheduled reboot at midnight (default off)
|
||||
|
||||
## 0.7.7 - 2023-07-03
|
||||
* attempt to fix MqTT `YieldDay` in `TotalValues` #927
|
||||
* attempt to fix MqTT `YieldDay` and `YieldTotal` even if inverters are not completly available #929
|
||||
* fix wrong message 'NRF not connected' if it is disabled #1007
|
||||
|
||||
## 0.7.6 - 2023-06-17
|
||||
* fix display of hidden SSID checkbox
|
||||
* changed yield correction data type to `double`, now decimal places are supported
|
||||
* corrected name of 0.91" display in settings
|
||||
* attempt to fix MqTT zero values only if setting is there #980, #957
|
||||
* made AP password configurable #951
|
||||
* added option to start without time-sync, eg. for AP-only-mode #951
|
||||
|
||||
## 0.7.5 - 2023-06-16
|
||||
* fix yield day reset on midnight #957
|
||||
* improved tickers in `app.cpp`
|
||||
|
||||
## 0.7.4 - 2023-06-15
|
||||
* fix MqTT `P_AC` send if inverters are available #987
|
||||
* fix assignments for HMS 1CH and 2CH devices
|
||||
* fixed uptime overflow #990
|
||||
|
||||
## 0.7.3 - 2023-06-09
|
||||
* fix hidden SSID scan #983
|
||||
* improved NRF24 missing message on home screen #981
|
||||
* fix MqTT publishing only updated values #982
|
||||
|
||||
## 0.7.2 - 2023-06-08
|
||||
* fix HMS-800 and HMS-1000 assignments #981
|
||||
* make nrf enabled all the time for ESP8266
|
||||
* fix menu item `active` highlight for 'API' and 'Doku'
|
||||
* fix MqTT totals issue #927, #980
|
||||
* reduce maximum number of inverters to 4 for ESP8266, increase to 16 for ESP32
|
||||
|
||||
## 0.7.1 - 2023-06-05
|
||||
* enabled power limit control for HMS / HMT devices
|
||||
* changed NRF24 lib version back to 1.4.5 because of compile problems for EPS8266
|
||||
|
||||
## 0.7.0 - 2023-06-04
|
||||
* HMS / HMT support for ESP32 devices
|
||||
|
||||
## 0.6.15 - 2023-05-25
|
||||
* improved Prometheus Endpoint PR #958
|
||||
* fix turn off ePaper only if setting was set #956
|
||||
|
|
303
src/app.cpp
303
src/app.cpp
|
@ -23,12 +23,12 @@ void app::setup() {
|
|||
while (!Serial)
|
||||
yield();
|
||||
|
||||
ah::Scheduler::setup();
|
||||
|
||||
resetSystem();
|
||||
|
||||
mSettings.setup();
|
||||
mSettings.getPtr(mConfig);
|
||||
ah::Scheduler::setup(mConfig->inst.startWithoutTime);
|
||||
DPRINT(DBG_INFO, F("Settings valid: "));
|
||||
DSERIAL.flush();
|
||||
if (mSettings.getValid())
|
||||
|
@ -36,8 +36,16 @@ void app::setup() {
|
|||
else
|
||||
DBGPRINTLN(F("false"));
|
||||
|
||||
mSys.enableDebug();
|
||||
mSys.setup(mConfig->nrf.amplifierPower, mConfig->nrf.pinIrq, mConfig->nrf.pinCe, mConfig->nrf.pinCs, mConfig->nrf.pinSclk, mConfig->nrf.pinMosi, mConfig->nrf.pinMiso);
|
||||
if(mConfig->nrf.enabled) {
|
||||
mNrfRadio.setup(mConfig->nrf.amplifierPower, mConfig->nrf.pinIrq, mConfig->nrf.pinCe, mConfig->nrf.pinCs, mConfig->nrf.pinSclk, mConfig->nrf.pinMosi, mConfig->nrf.pinMiso);
|
||||
mNrfRadio.enableDebug();
|
||||
}
|
||||
#if defined(ESP32)
|
||||
if(mConfig->cmt.enabled) {
|
||||
mCmtRadio.setup(mConfig->cmt.pinCsb, mConfig->cmt.pinFcsb, false);
|
||||
mCmtRadio.enableDebug();
|
||||
}
|
||||
#endif
|
||||
#ifdef ETHERNET
|
||||
delay(1000);
|
||||
DPRINT(DBG_INFO, F("mEth setup..."));
|
||||
|
@ -62,29 +70,34 @@ void app::setup() {
|
|||
#endif
|
||||
#endif /* defined(ETHERNET) */
|
||||
|
||||
mSys.setup(&mTimestamp);
|
||||
mSys.addInverters(&mConfig->inst);
|
||||
|
||||
mPayload.setup(this, &mSys, &mStat, mConfig->nrf.maxRetransPerPyld, &mTimestamp);
|
||||
if(mConfig->nrf.enabled) {
|
||||
mPayload.setup(this, &mSys, &mNrfRadio, &mStat, mConfig->nrf.maxRetransPerPyld, &mTimestamp);
|
||||
mPayload.enableSerialDebug(mConfig->serial.debug);
|
||||
mPayload.addPayloadListener(std::bind(&app::payloadEventListener, this, std::placeholders::_1));
|
||||
mPayload.addPayloadListener(std::bind(&app::payloadEventListener, this, std::placeholders::_1, std::placeholders::_2));
|
||||
|
||||
mMiPayload.setup(this, &mSys, &mStat, mConfig->nrf.maxRetransPerPyld, &mTimestamp);
|
||||
mMiPayload.setup(this, &mSys, &mNrfRadio, &mStat, mConfig->nrf.maxRetransPerPyld, &mTimestamp);
|
||||
mMiPayload.enableSerialDebug(mConfig->serial.debug);
|
||||
mMiPayload.addPayloadListener(std::bind(&app::payloadEventListener, this, std::placeholders::_1));
|
||||
mMiPayload.addPayloadListener(std::bind(&app::payloadEventListener, this, std::placeholders::_1, std::placeholders::_2));
|
||||
}
|
||||
|
||||
// DBGPRINTLN("--- after payload");
|
||||
// DBGPRINTLN(String(ESP.getFreeHeap()));
|
||||
// DBGPRINTLN(String(ESP.getHeapFragmentation()));
|
||||
// DBGPRINTLN(String(ESP.getMaxFreeBlockSize()));
|
||||
#if defined(ESP32)
|
||||
mHmsPayload.setup(this, &mSys, &mCmtRadio, &mStat, 5, &mTimestamp);
|
||||
mHmsPayload.enableSerialDebug(mConfig->serial.debug);
|
||||
mHmsPayload.addPayloadListener(std::bind(&app::payloadEventListener, this, std::placeholders::_1, std::placeholders::_2));
|
||||
#endif
|
||||
|
||||
if (!mSys.Radio.isChipConnected())
|
||||
if(mConfig->nrf.enabled) {
|
||||
if (!mNrfRadio.isChipConnected())
|
||||
DPRINTLN(DBG_WARN, F("WARNING! your NRF24 module can't be reached, check the wiring"));
|
||||
}
|
||||
|
||||
// when WiFi is in client mode, then enable mqtt broker
|
||||
#if !defined(AP_ONLY)
|
||||
mMqttEnabled = (mConfig->mqtt.broker[0] > 0);
|
||||
if (mMqttEnabled) {
|
||||
mMqtt.setup(&mConfig->mqtt, mConfig->sys.deviceName, mVersion, &mSys, &mTimestamp);
|
||||
mMqtt.setup(&mConfig->mqtt, mConfig->sys.deviceName, mVersion, &mSys, &mTimestamp, &mUptime);
|
||||
mMqtt.setSubscriptionCb(std::bind(&app::mqttSubRxCb, this, std::placeholders::_1));
|
||||
mPayload.addAlarmListener(std::bind(&PubMqttType::alarmEventListener, &mMqtt, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
|
||||
mMiPayload.addAlarmListener(std::bind(&PubMqttType::alarmEventListener, &mMqtt, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
|
||||
|
@ -95,7 +108,7 @@ void app::setup() {
|
|||
mWeb.setup(this, &mSys, mConfig);
|
||||
mWeb.setProtection(strlen(mConfig->sys.adminPwd) != 0);
|
||||
|
||||
mApi.setup(this, &mSys, mWeb.getWebSrvPtr(), mConfig);
|
||||
mApi.setup(this, &mSys, &mNrfRadio, mWeb.getWebSrvPtr(), mConfig);
|
||||
|
||||
// Plugins
|
||||
if (mConfig->plugin.display.type != 0)
|
||||
|
@ -103,28 +116,29 @@ void app::setup() {
|
|||
|
||||
mPubSerial.setup(mConfig, &mSys, &mTimestamp);
|
||||
|
||||
#if !defined(ETHERNET)
|
||||
mImprov.setup(this, mConfig->sys.deviceName, mVersion);
|
||||
#endif
|
||||
|
||||
regularTickers();
|
||||
|
||||
|
||||
// DBGPRINTLN("--- end setup");
|
||||
// DBGPRINTLN(String(ESP.getFreeHeap()));
|
||||
// DBGPRINTLN(String(ESP.getHeapFragmentation()));
|
||||
// DBGPRINTLN(String(ESP.getMaxFreeBlockSize()));
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void app::loop(void) {
|
||||
if (mInnerLoopCb)
|
||||
mInnerLoopCb();
|
||||
#if !defined(ETHERNET)
|
||||
mImprov.tickSerial();
|
||||
#endif
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void app::loopStandard(void) {
|
||||
ah::Scheduler::loop();
|
||||
|
||||
if (mSys.Radio.loop()) {
|
||||
while (!mSys.Radio.mBufCtrl.empty()) {
|
||||
packet_t *p = &mSys.Radio.mBufCtrl.front();
|
||||
if (mNrfRadio.loop() && mConfig->nrf.enabled) {
|
||||
while (!mNrfRadio.mBufCtrl.empty()) {
|
||||
packet_t *p = &mNrfRadio.mBufCtrl.front();
|
||||
|
||||
if (mConfig->serial.debug) {
|
||||
DPRINT(DBG_INFO, F("RX "));
|
||||
|
@ -132,7 +146,7 @@ void app::loopStandard(void) {
|
|||
DBGPRINT(F("B Ch"));
|
||||
DBGPRINT(String(p->ch));
|
||||
DBGPRINT(F(" | "));
|
||||
mSys.Radio.dumpBuf(p->packet, p->len);
|
||||
ah::dumpBuf(p->packet, p->len);
|
||||
}
|
||||
mStat.frmCnt++;
|
||||
|
||||
|
@ -143,14 +157,42 @@ void app::loopStandard(void) {
|
|||
else
|
||||
mMiPayload.add(iv, p);
|
||||
}
|
||||
mSys.Radio.mBufCtrl.pop();
|
||||
mNrfRadio.mBufCtrl.pop();
|
||||
yield();
|
||||
}
|
||||
mPayload.process(true);
|
||||
mMiPayload.process(true);
|
||||
}
|
||||
#if defined(ESP32)
|
||||
if (mCmtRadio.loop() && mConfig->cmt.enabled) {
|
||||
while (!mCmtRadio.mBufCtrl.empty()) {
|
||||
hmsPacket_t *p = &mCmtRadio.mBufCtrl.front();
|
||||
if (mConfig->serial.debug) {
|
||||
DPRINT(DBG_INFO, F("RX "));
|
||||
DBGPRINT(String(p->data[0]));
|
||||
DBGPRINT(F(" RSSI "));
|
||||
DBGPRINT(String(p->rssi));
|
||||
DBGPRINT(F("dBm | "));
|
||||
ah::dumpBuf(&p->data[1], p->data[0]);
|
||||
}
|
||||
mStat.frmCnt++;
|
||||
|
||||
Inverter<> *iv = mSys.findInverter(&p->data[2]);
|
||||
if(NULL != iv) {
|
||||
if((iv->ivGen == IV_HMS) || (iv->ivGen == IV_HMT))
|
||||
mHmsPayload.add(iv, p);
|
||||
}
|
||||
mCmtRadio.mBufCtrl.pop();
|
||||
yield();
|
||||
}
|
||||
mHmsPayload.process(false); //true
|
||||
}
|
||||
#endif
|
||||
mPayload.loop();
|
||||
mMiPayload.loop();
|
||||
#if defined(ESP32)
|
||||
mHmsPayload.loop();
|
||||
#endif
|
||||
|
||||
if (mMqttEnabled)
|
||||
mMqtt.loop();
|
||||
|
@ -171,6 +213,10 @@ void app::onNetwork(bool gotIp) {
|
|||
regularTickers(); // reinstall regular tickers
|
||||
if (gotIp) {
|
||||
every(std::bind(&app::tickSend, this), mConfig->nrf.sendInterval, "tSend");
|
||||
#if defined(ESP32)
|
||||
if(mConfig->cmt.enabled)
|
||||
everySec(std::bind(&CmtRadioType::tickSecond, &mCmtRadio), "tsCmt");
|
||||
#endif
|
||||
mMqttReconnect = true;
|
||||
mSunrise = 0; // needs to be set to 0, to reinstall sunrise and ivComm tickers!
|
||||
once(std::bind(&app::tickNtpUpdate, this), 2, "ntp2");
|
||||
|
@ -199,35 +245,10 @@ void app::regularTickers(void) {
|
|||
if (mConfig->plugin.display.type != 0)
|
||||
everySec(std::bind(&DisplayType::tickerSecond, &mDisplay), "disp");
|
||||
every(std::bind(&PubSerialType::tick, &mPubSerial), mConfig->serial.interval, "uart");
|
||||
|
||||
//everySec(std::bind(&Improv::tickSerial, &mImprov), "impro");
|
||||
// every([this]() {mPayload.simulation();}, 15, "simul");
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void app::tickNtpUpdate(void) {
|
||||
uint32_t nxtTrig = 5; // default: check again in 5 sec
|
||||
bool isOK = false;
|
||||
#if defined(ETHERNET)
|
||||
if (!(isOK = mEth.updateNtpTime()))
|
||||
once(std::bind(&app::tickNtpUpdate, this), nxtTrig, "ntp");
|
||||
#else /* defined(ETHERNET) */
|
||||
isOK = mWifi.getNtpTime();
|
||||
if (isOK || mTimestamp != 0) {
|
||||
this->updateNtp();
|
||||
nxtTrig = isOK ? 43200 : 60; // depending on NTP update success check again in 12 h or in 1 min
|
||||
}
|
||||
once(std::bind(&app::tickNtpUpdate, this), nxtTrig, "ntp");
|
||||
#endif /* defined(ETHERNET) */
|
||||
|
||||
// immediately start communicating
|
||||
// @TODO: leads to reboot loops? not sure #674
|
||||
if (isOK && mSendFirst) {
|
||||
mSendFirst = false;
|
||||
once(std::bind(&app::tickSend, this), 2, "senOn");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#if defined(ETHERNET)
|
||||
void app::onNtpUpdate(bool gotTime)
|
||||
{
|
||||
|
@ -242,30 +263,54 @@ void app::onNtpUpdate(bool gotTime)
|
|||
|
||||
//-----------------------------------------------------------------------------
|
||||
void app::updateNtp(void) {
|
||||
if (mMqttReconnect && mMqttEnabled) {
|
||||
mMqtt.tickerSecond();
|
||||
everySec(std::bind(&PubMqttType::tickerSecond, &mMqtt), "mqttS");
|
||||
everyMin(std::bind(&PubMqttType::tickerMinute, &mMqtt), "mqttM");
|
||||
}
|
||||
if (mMqttReconnect && mMqttEnabled) {
|
||||
mMqtt.tickerSecond();
|
||||
everySec(std::bind(&PubMqttType::tickerSecond, &mMqtt), "mqttS");
|
||||
everyMin(std::bind(&PubMqttType::tickerMinute, &mMqtt), "mqttM");
|
||||
}
|
||||
|
||||
// only install schedulers once even if NTP wasn't successful in first loop
|
||||
if (mMqttReconnect) { // @TODO: mMqttReconnect is variable which scope has changed
|
||||
if (mConfig->inst.rstValsNotAvail)
|
||||
everyMin(std::bind(&app::tickMinute, this), "tMin");
|
||||
if (mConfig->inst.rstYieldMidNight) {
|
||||
uint32_t localTime = gTimezone.toLocal(mTimestamp);
|
||||
uint32_t midTrig = gTimezone.toUTC(localTime - (localTime % 86400) + 86400); // next midnight local time
|
||||
onceAt(std::bind(&app::tickMidnight, this), midTrig, "midNi");
|
||||
}
|
||||
// only install schedulers once even if NTP wasn't successful in first loop
|
||||
if (mMqttReconnect) { // @TODO: mMqttReconnect is variable which scope has changed
|
||||
if (mConfig->inst.rstValsNotAvail)
|
||||
everyMin(std::bind(&app::tickMinute, this), "tMin");
|
||||
if (mConfig->inst.rstYieldMidNight) {
|
||||
uint32_t localTime = gTimezone.toLocal(mTimestamp);
|
||||
uint32_t midTrig = gTimezone.toUTC(localTime - (localTime % 86400) + 86400); // next midnight local time
|
||||
onceAt(std::bind(&app::tickMidnight, this), midTrig, "midNi");
|
||||
}
|
||||
}
|
||||
|
||||
if ((mSunrise == 0) && (mConfig->sun.lat) && (mConfig->sun.lon)) {
|
||||
mCalculatedTimezoneOffset = (int8_t)((mConfig->sun.lon >= 0 ? mConfig->sun.lon + 7.5 : mConfig->sun.lon - 7.5) / 15) * 3600;
|
||||
tickCalcSunrise();
|
||||
if ((mSunrise == 0) && (mConfig->sun.lat) && (mConfig->sun.lon)) {
|
||||
mCalculatedTimezoneOffset = (int8_t)((mConfig->sun.lon >= 0 ? mConfig->sun.lon + 7.5 : mConfig->sun.lon - 7.5) / 15) * 3600;
|
||||
tickCalcSunrise();
|
||||
}
|
||||
|
||||
mMqttReconnect = false;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void app::tickNtpUpdate(void) {
|
||||
uint32_t nxtTrig = 5; // default: check again in 5 sec
|
||||
#if defined(ETHERNET)
|
||||
bool isOK = mEth.updateNtpTime();
|
||||
#else
|
||||
bool isOK = mWifi.getNtpTime();
|
||||
#endif
|
||||
if (isOK || mTimestamp != 0) {
|
||||
this->updateNtp();
|
||||
|
||||
nxtTrig = isOK ? (mConfig->ntp.interval * 60) : 60; // depending on NTP update success check again in 12h (depends on setting) or in 1 min
|
||||
|
||||
// immediately start communicating
|
||||
if (isOK && mSendFirst) {
|
||||
mSendFirst = false;
|
||||
once(std::bind(&app::tickSend, this), 2, "senOn");
|
||||
}
|
||||
|
||||
mMqttReconnect = false;
|
||||
}
|
||||
once(std::bind(&app::tickNtpUpdate, this), nxtTrig, "ntp");
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void app::tickCalcSunrise(void) {
|
||||
|
@ -324,43 +369,15 @@ void app::tickComm(void) {
|
|||
|
||||
//-----------------------------------------------------------------------------
|
||||
void app::tickZeroValues(void) {
|
||||
Inverter<> *iv;
|
||||
bool changed = false;
|
||||
// set values to zero, except yields
|
||||
for (uint8_t id = 0; id < mSys.getNumInverters(); id++) {
|
||||
iv = mSys.getInverterByPos(id);
|
||||
if (NULL == iv)
|
||||
continue; // skip to next inverter
|
||||
|
||||
mPayload.zeroInverterValues(iv);
|
||||
changed = true;
|
||||
zeroIvValues(!CHECK_AVAIL, SKIP_YIELD_DAY);
|
||||
}
|
||||
|
||||
if(changed)
|
||||
payloadEventListener(RealTimeRunData_Debug);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void app::tickMinute(void) {
|
||||
// only triggered if 'reset values on no avail is enabled'
|
||||
|
||||
Inverter<> *iv;
|
||||
bool changed = false;
|
||||
// set values to zero, except yields
|
||||
for (uint8_t id = 0; id < mSys.getNumInverters(); id++) {
|
||||
iv = mSys.getInverterByPos(id);
|
||||
if (NULL == iv)
|
||||
continue; // skip to next inverter
|
||||
|
||||
if (!iv->isAvailable(mTimestamp) && !iv->isProducing(mTimestamp) && iv->config->enabled) {
|
||||
mPayload.zeroInverterValues(iv);
|
||||
changed = true;
|
||||
zeroIvValues(CHECK_AVAIL, SKIP_YIELD_DAY);
|
||||
}
|
||||
}
|
||||
|
||||
if(changed)
|
||||
payloadEventListener(RealTimeRunData_Debug);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void app::tickMidnight(void) {
|
||||
|
@ -369,20 +386,7 @@ void app::tickMidnight(void) {
|
|||
uint32_t nxtTrig = gTimezone.toUTC(localTime - (localTime % 86400) + 86400); // next midnight local time
|
||||
onceAt(std::bind(&app::tickMidnight, this), nxtTrig, "mid2");
|
||||
|
||||
Inverter<> *iv;
|
||||
bool changed = false;
|
||||
// set values to zero, except yield total
|
||||
for (uint8_t id = 0; id < mSys.getNumInverters(); id++) {
|
||||
iv = mSys.getInverterByPos(id);
|
||||
if (NULL == iv)
|
||||
continue; // skip to next inverter
|
||||
|
||||
mPayload.zeroInverterValues(iv, false);
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if(changed)
|
||||
payloadEventListener(RealTimeRunData_Debug);
|
||||
zeroIvValues(!CHECK_AVAIL, !SKIP_YIELD_DAY);
|
||||
|
||||
if (mMqttEnabled)
|
||||
mMqtt.tickerMidnight();
|
||||
|
@ -390,17 +394,27 @@ void app::tickMidnight(void) {
|
|||
|
||||
//-----------------------------------------------------------------------------
|
||||
void app::tickSend(void) {
|
||||
if (!mSys.Radio.isChipConnected()) {
|
||||
if(mConfig->nrf.enabled) {
|
||||
if(!mNrfRadio.isChipConnected()) {
|
||||
DPRINTLN(DBG_WARN, F("NRF24 not connected!"));
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (mIVCommunicationOn) {
|
||||
if (!mSys.Radio.mBufCtrl.empty()) {
|
||||
if (!mNrfRadio.mBufCtrl.empty()) {
|
||||
if (mConfig->serial.debug) {
|
||||
DPRINT(DBG_DEBUG, F("recbuf not empty! #"));
|
||||
DBGPRINTLN(String(mSys.Radio.mBufCtrl.size()));
|
||||
DBGPRINTLN(String(mNrfRadio.mBufCtrl.size()));
|
||||
}
|
||||
}
|
||||
#if defined(ESP32)
|
||||
if (!mCmtRadio.mBufCtrl.empty()) {
|
||||
if (mConfig->serial.debug) {
|
||||
DPRINT(DBG_INFO, F("recbuf not empty! #"));
|
||||
DBGPRINTLN(String(mCmtRadio.mBufCtrl.size()));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
int8_t maxLoop = MAX_NUM_INVERTERS;
|
||||
Inverter<> *iv = mSys.getInverterByPos(mSendLastIvId);
|
||||
|
@ -411,11 +425,19 @@ void app::tickSend(void) {
|
|||
|
||||
if (NULL != iv) {
|
||||
if (iv->config->enabled) {
|
||||
if(mConfig->nrf.enabled) {
|
||||
if (iv->ivGen == IV_HM)
|
||||
mPayload.ivSend(iv);
|
||||
else
|
||||
else if(iv->ivGen == IV_MI)
|
||||
mMiPayload.ivSend(iv);
|
||||
}
|
||||
#if defined(ESP32)
|
||||
if(mConfig->cmt.enabled) {
|
||||
if((iv->ivGen == IV_HMS) || (iv->ivGen == IV_HMT))
|
||||
mHmsPayload.ivSend(iv);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (mConfig->serial.debug)
|
||||
|
@ -426,6 +448,51 @@ void app::tickSend(void) {
|
|||
updateLed();
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void app:: zeroIvValues(bool checkAvail, bool skipYieldDay) {
|
||||
Inverter<> *iv;
|
||||
bool changed = false;
|
||||
// set values to zero, except yields
|
||||
for (uint8_t id = 0; id < mSys.getNumInverters(); id++) {
|
||||
iv = mSys.getInverterByPos(id);
|
||||
if (NULL == iv)
|
||||
continue; // skip to next inverter
|
||||
if (!iv->config->enabled)
|
||||
continue; // skip to next inverter
|
||||
|
||||
if (checkAvail) {
|
||||
if (!iv->isAvailable())
|
||||
continue;
|
||||
}
|
||||
|
||||
record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug);
|
||||
for(uint8_t ch = 0; ch <= iv->channels; ch++) {
|
||||
uint8_t pos = 0;
|
||||
for(uint8_t fld = 0; fld < FLD_EVT; fld++) {
|
||||
switch(fld) {
|
||||
case FLD_YD:
|
||||
if(skipYieldDay)
|
||||
continue;
|
||||
else
|
||||
break;
|
||||
case FLD_YT:
|
||||
continue;
|
||||
}
|
||||
pos = iv->getPosByChFld(ch, fld, rec);
|
||||
iv->setValue(pos, rec, 0.0f);
|
||||
}
|
||||
iv->doCalculations();
|
||||
}
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if(changed) {
|
||||
if(mMqttEnabled && !skipYieldDay)
|
||||
mMqtt.setZeroValuesEnable();
|
||||
payloadEventListener(RealTimeRunData_Debug, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void app::resetSystem(void) {
|
||||
snprintf(mVersion, 12, "%d.%d.%d", VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH);
|
||||
|
@ -477,7 +544,7 @@ void app::updateLed(void) {
|
|||
if (mConfig->led.led0 != 0xff) {
|
||||
Inverter<> *iv = mSys.getInverterByPos(0);
|
||||
if (NULL != iv) {
|
||||
if (iv->isProducing(mTimestamp))
|
||||
if (iv->isProducing())
|
||||
digitalWrite(mConfig->led.led0, led_on);
|
||||
else
|
||||
digitalWrite(mConfig->led.led0, led_off);
|
||||
|
|
85
src/app.h
85
src/app.h
|
@ -9,19 +9,23 @@
|
|||
#include <Arduino.h>
|
||||
#include <ArduinoJson.h>
|
||||
#include <RF24.h>
|
||||
#include <RF24_config.h>
|
||||
|
||||
#include "appInterface.h"
|
||||
#include "config/settings.h"
|
||||
#include "defines.h"
|
||||
#include "hm/hmPayload.h"
|
||||
#include "hm/hmSystem.h"
|
||||
#include "hm/hmRadio.h"
|
||||
#include "hms/hmsRadio.h"
|
||||
#include "hms/hmsPayload.h"
|
||||
#include "hm/hmPayload.h"
|
||||
#include "hm/miPayload.h"
|
||||
#include "publisher/pubMqtt.h"
|
||||
#include "publisher/pubSerial.h"
|
||||
#include "utils/crc.h"
|
||||
#include "utils/dbg.h"
|
||||
#include "utils/scheduler.h"
|
||||
#include "utils/improv.h"
|
||||
#include "web/RestApi.h"
|
||||
#include "web/web.h"
|
||||
#if defined(ETHERNET)
|
||||
|
@ -37,10 +41,14 @@
|
|||
#define ACOS(x) (degrees(acos(x)))
|
||||
|
||||
typedef HmSystem<MAX_NUM_INVERTERS> HmSystemType;
|
||||
typedef HmPayload<HmSystemType> PayloadType;
|
||||
typedef MiPayload<HmSystemType> MiPayloadType;
|
||||
typedef HmPayload<HmSystemType, HmRadio<>> PayloadType;
|
||||
typedef MiPayload<HmSystemType, HmRadio<>> MiPayloadType;
|
||||
#ifdef ESP32
|
||||
typedef CmtRadio<esp32_3wSpi<>> CmtRadioType;
|
||||
typedef HmsPayload<HmSystemType, CmtRadioType> HmsPayloadType;
|
||||
#endif
|
||||
typedef Web<HmSystemType> WebType;
|
||||
typedef RestApi<HmSystemType> RestApiType;
|
||||
typedef RestApi<HmSystemType, HmRadio<>> RestApiType;
|
||||
typedef PubMqtt<HmSystemType> PubMqttType;
|
||||
typedef PubSerial<HmSystemType> PubSerialType;
|
||||
|
||||
|
@ -63,9 +71,15 @@ class app : public IApp, public ah::Scheduler {
|
|||
void regularTickers(void);
|
||||
|
||||
void handleIntr(void) {
|
||||
mSys.Radio.handleIntr();
|
||||
mNrfRadio.handleIntr();
|
||||
}
|
||||
|
||||
#ifdef ESP32
|
||||
void handleHmsIntr(void) {
|
||||
mCmtRadio.handleIntr();
|
||||
}
|
||||
#endif
|
||||
|
||||
uint32_t getUptime() {
|
||||
return Scheduler::getUptime();
|
||||
}
|
||||
|
@ -78,6 +92,10 @@ class app : public IApp, public ah::Scheduler {
|
|||
mShowRebootRequest = true; // only message on index, no reboot
|
||||
mSavePending = true;
|
||||
mSaveReboot = reboot;
|
||||
if(reboot) {
|
||||
onNetwork(false);
|
||||
ah::Scheduler::resetTicker();
|
||||
}
|
||||
once(std::bind(&app::tickSave, this), 3, "save");
|
||||
return true;
|
||||
}
|
||||
|
@ -111,8 +129,8 @@ class app : public IApp, public ah::Scheduler {
|
|||
mWifi.scanAvailNetworks();
|
||||
}
|
||||
|
||||
void getAvailNetworks(JsonObject obj) {
|
||||
mWifi.getAvailNetworks(obj);
|
||||
bool getAvailNetworks(JsonObject obj) {
|
||||
return mWifi.getAvailNetworks(obj);
|
||||
}
|
||||
|
||||
void setOnUpdate() {
|
||||
|
@ -177,10 +195,27 @@ class app : public IApp, public ah::Scheduler {
|
|||
return mWeb.isProtected(request);
|
||||
}
|
||||
|
||||
uint8_t getIrqPin(void) {
|
||||
void getNrfRadioCounters(uint32_t *sendCnt, uint32_t *retransmits) {
|
||||
*sendCnt = mNrfRadio.mSendCnt;
|
||||
*retransmits = mNrfRadio.mRetransmits;
|
||||
}
|
||||
|
||||
bool getNrfEnabled(void) {
|
||||
return mConfig->nrf.enabled;
|
||||
}
|
||||
|
||||
bool getCmtEnabled(void) {
|
||||
return mConfig->cmt.enabled;
|
||||
}
|
||||
|
||||
uint8_t getNrfIrqPin(void) {
|
||||
return mConfig->nrf.pinIrq;
|
||||
}
|
||||
|
||||
uint8_t getCmtIrqPin(void) {
|
||||
return mConfig->cmt.pinIrq;
|
||||
}
|
||||
|
||||
String getTimeStr(uint32_t offset = 0) {
|
||||
char str[10];
|
||||
if(0 == mTimestamp)
|
||||
|
@ -217,17 +252,19 @@ class app : public IApp, public ah::Scheduler {
|
|||
Scheduler::setTimestamp(newTime);
|
||||
}
|
||||
|
||||
HmSystemType mSys;
|
||||
|
||||
private:
|
||||
#define CHECK_AVAIL true
|
||||
#define SKIP_YIELD_DAY true
|
||||
|
||||
typedef std::function<void()> innerLoopCb;
|
||||
|
||||
void resetSystem(void);
|
||||
void zeroIvValues(bool checkAvail = false, bool skipYieldDay = true);
|
||||
|
||||
void payloadEventListener(uint8_t cmd) {
|
||||
void payloadEventListener(uint8_t cmd, Inverter<> *iv) {
|
||||
#if !defined(AP_ONLY)
|
||||
if (mMqttEnabled)
|
||||
mMqtt.payloadEventListener(cmd);
|
||||
mMqtt.payloadEventListener(cmd, iv);
|
||||
#endif
|
||||
if(mConfig->plugin.display.type != 0)
|
||||
mDisplay.payloadEventListener(cmd);
|
||||
|
@ -270,23 +307,12 @@ class app : public IApp, public ah::Scheduler {
|
|||
void tickMinute(void);
|
||||
void tickZeroValues(void);
|
||||
void tickMidnight(void);
|
||||
/*void tickSerial(void) {
|
||||
if(Serial.available() == 0)
|
||||
return;
|
||||
|
||||
uint8_t buf[80];
|
||||
uint8_t len = Serial.readBytes(buf, 80);
|
||||
DPRINTLN(DBG_INFO, "got serial data, len: " + String(len));
|
||||
for(uint8_t i = 0; i < len; i++) {
|
||||
if((0 != i) && (i % 8 == 0))
|
||||
DBGPRINTLN("");
|
||||
DBGPRINT(String(buf[i], HEX) + " ");
|
||||
}
|
||||
DBGPRINTLN("");
|
||||
}*/
|
||||
|
||||
innerLoopCb mInnerLoopCb;
|
||||
|
||||
HmSystemType mSys;
|
||||
HmRadio<> mNrfRadio;
|
||||
|
||||
bool mShowRebootRequest;
|
||||
bool mIVCommunicationOn;
|
||||
|
||||
|
@ -300,6 +326,13 @@ class app : public IApp, public ah::Scheduler {
|
|||
PayloadType mPayload;
|
||||
MiPayloadType mMiPayload;
|
||||
PubSerialType mPubSerial;
|
||||
#if !defined(ETHERNET)
|
||||
Improv mImprov;
|
||||
#endif
|
||||
#ifdef ESP32
|
||||
CmtRadioType mCmtRadio;
|
||||
HmsPayloadType mHmsPayload;
|
||||
#endif
|
||||
|
||||
char mVersion[12];
|
||||
settings mSettings;
|
||||
|
|
|
@ -34,7 +34,7 @@ class IApp {
|
|||
|
||||
#if !defined(ETHERNET)
|
||||
virtual void scanAvailNetworks() = 0;
|
||||
virtual void getAvailNetworks(JsonObject obj) = 0;
|
||||
virtual bool getAvailNetworks(JsonObject obj) = 0;
|
||||
#endif /* defined(ETHERNET) */
|
||||
|
||||
virtual uint32_t getUptime() = 0;
|
||||
|
@ -59,6 +59,9 @@ class IApp {
|
|||
virtual uint32_t getMqttTxCnt() = 0;
|
||||
|
||||
virtual bool getProtection(AsyncWebServerRequest *request) = 0;
|
||||
|
||||
virtual void getNrfRadioCounters(uint32_t *sendCnt, uint32_t *retransmits) = 0;
|
||||
//virtual void getCmtRadioCounters(uint32_t *sendCnt, uint32_t *retransmits) = 0;
|
||||
};
|
||||
|
||||
#endif /*__IAPP_H__*/
|
||||
|
|
|
@ -84,7 +84,11 @@
|
|||
#define PACKET_BUFFER_SIZE 30
|
||||
|
||||
// number of configurable inverters
|
||||
#define MAX_NUM_INVERTERS 10
|
||||
#if defined(ESP32)
|
||||
#define MAX_NUM_INVERTERS 16
|
||||
#else
|
||||
#define MAX_NUM_INVERTERS 4
|
||||
#endif
|
||||
|
||||
// default serial interval
|
||||
#define SERIAL_INTERVAL 5
|
||||
|
@ -105,7 +109,10 @@
|
|||
#define DEF_MAX_RETRANS_PER_PYLD 5
|
||||
|
||||
// number of seconds since last successful response, before inverter is marked inactive
|
||||
#define INACT_THRES_SEC 300
|
||||
#define INVERTER_INACT_THRES_SEC 5*60
|
||||
|
||||
// number of seconds since last successful response, before inverter is marked offline
|
||||
#define INVERTER_OFF_THRES_SEC 15*60
|
||||
|
||||
// threshold of minimum power on which the inverter is marked as inactive
|
||||
#define INACT_PWR_THRESH 3
|
||||
|
|
|
@ -64,6 +64,7 @@ typedef struct {
|
|||
char adminPwd[PWD_LEN];
|
||||
uint16_t protectionMask;
|
||||
bool darkMode;
|
||||
bool schedReboot;
|
||||
|
||||
#if defined(ETHERNET)
|
||||
// ethernet
|
||||
|
@ -72,12 +73,15 @@ typedef struct {
|
|||
// wifi
|
||||
char stationSsid[SSID_LEN];
|
||||
char stationPwd[PWD_LEN];
|
||||
char apPwd[PWD_LEN];
|
||||
bool isHidden;
|
||||
#endif /* defined(ETHERNET) */
|
||||
|
||||
cfgIp_t ip;
|
||||
} cfgSys_t;
|
||||
|
||||
typedef struct {
|
||||
bool enabled;
|
||||
uint16_t sendInterval;
|
||||
uint8_t maxRetransPerPyld;
|
||||
uint8_t pinCs;
|
||||
|
@ -89,9 +93,17 @@ typedef struct {
|
|||
uint8_t amplifierPower;
|
||||
} cfgNrf24_t;
|
||||
|
||||
typedef struct {
|
||||
bool enabled;
|
||||
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 {
|
||||
|
@ -126,9 +138,9 @@ typedef struct {
|
|||
bool enabled;
|
||||
char name[MAX_NAME_LENGTH];
|
||||
serial_u serial;
|
||||
uint16_t chMaxPwr[4];
|
||||
int32_t yieldCor[4]; // signed YieldTotal correction value
|
||||
char chName[4][MAX_NAME_LENGTH];
|
||||
uint16_t chMaxPwr[6];
|
||||
double yieldCor[6]; // YieldTotal correction value
|
||||
char chName[6][MAX_NAME_LENGTH];
|
||||
} cfgIv_t;
|
||||
|
||||
typedef struct {
|
||||
|
@ -138,6 +150,8 @@ typedef struct {
|
|||
bool rstYieldMidNight;
|
||||
bool rstValsNotAvail;
|
||||
bool rstValsCommStop;
|
||||
bool startWithoutTime;
|
||||
float yieldEffiency;
|
||||
} cfgInst_t;
|
||||
|
||||
typedef struct {
|
||||
|
@ -163,6 +177,7 @@ typedef struct {
|
|||
typedef struct {
|
||||
cfgSys_t sys;
|
||||
cfgNrf24_t nrf;
|
||||
cfgCmt_t cmt;
|
||||
cfgNtp_t ntp;
|
||||
cfgSun_t sun;
|
||||
cfgSerial_t serial;
|
||||
|
@ -257,6 +272,9 @@ class settings {
|
|||
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")]);
|
||||
|
@ -281,6 +299,9 @@ class settings {
|
|||
JsonObject root = json.to<JsonObject>();
|
||||
jsonNetwork(root.createNestedObject(F("wifi")), true);
|
||||
jsonNrf(root.createNestedObject(F("nrf")), true);
|
||||
#if defined(ESP32)
|
||||
jsonCmt(root.createNestedObject(F("cmt")), true);
|
||||
#endif
|
||||
jsonNtp(root.createNestedObject(F("ntp")), true);
|
||||
jsonSun(root.createNestedObject(F("sun")), true);
|
||||
jsonSerial(root.createNestedObject(F("serial")), true);
|
||||
|
@ -343,7 +364,7 @@ class settings {
|
|||
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;
|
||||
mCfg.sys.darkMode = false;
|
||||
|
||||
mCfg.sys.schedReboot = false;
|
||||
// restore temp settings
|
||||
#if defined(ETHERNET)
|
||||
memcpy(&mCfg.sys, &tmp, sizeof(cfgSys_t));
|
||||
|
@ -353,6 +374,8 @@ class settings {
|
|||
else {
|
||||
snprintf(mCfg.sys.stationSsid, SSID_LEN, FB_WIFI_SSID);
|
||||
snprintf(mCfg.sys.stationPwd, PWD_LEN, FB_WIFI_PWD);
|
||||
snprintf(mCfg.sys.apPwd, PWD_LEN, WIFI_AP_PWD);
|
||||
mCfg.sys.isHidden = false;
|
||||
}
|
||||
#endif /* defined(ETHERNET) */
|
||||
|
||||
|
@ -368,9 +391,16 @@ class settings {
|
|||
mCfg.nrf.pinSclk = DEF_SCLK_PIN;
|
||||
|
||||
mCfg.nrf.amplifierPower = DEF_AMPLIFIERPOWER & 0x03;
|
||||
mCfg.nrf.enabled = true;
|
||||
|
||||
mCfg.cmt.pinCsb = DEF_PIN_OFF;
|
||||
mCfg.cmt.pinFcsb = DEF_PIN_OFF;
|
||||
mCfg.cmt.pinIrq = DEF_PIN_OFF;
|
||||
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 = 0.0;
|
||||
mCfg.sun.lon = 0.0;
|
||||
|
@ -391,6 +421,8 @@ class settings {
|
|||
mCfg.inst.rstYieldMidNight = false;
|
||||
mCfg.inst.rstValsNotAvail = false;
|
||||
mCfg.inst.rstValsCommStop = false;
|
||||
mCfg.inst.startWithoutTime = false;
|
||||
mCfg.inst.yieldEffiency = 0.955f;
|
||||
|
||||
mCfg.led.led0 = DEF_PIN_OFF;
|
||||
mCfg.led.led1 = DEF_PIN_OFF;
|
||||
|
@ -416,11 +448,14 @@ class settings {
|
|||
#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;
|
||||
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);
|
||||
|
@ -430,11 +465,14 @@ class settings {
|
|||
#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<bool>(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<uint16_t>(obj, F("prot_mask"), &mCfg.sys.protectionMask);
|
||||
getVal<bool>(obj, F("dark"), &mCfg.sys.darkMode);
|
||||
getVal<bool>(obj, F("reb"), &mCfg.sys.schedReboot);
|
||||
if(obj.containsKey(F("ip"))) ah::ip2Arr(mCfg.sys.ip.ip, obj[F("ip")].as<const char*>());
|
||||
if(obj.containsKey(F("mask"))) ah::ip2Arr(mCfg.sys.ip.mask, obj[F("mask")].as<const char*>());
|
||||
if(obj.containsKey(F("dns1"))) ah::ip2Arr(mCfg.sys.ip.dns1, obj[F("dns1")].as<const char*>());
|
||||
|
@ -458,6 +496,7 @@ class settings {
|
|||
obj[F("mosi")] = mCfg.nrf.pinMosi;
|
||||
obj[F("miso")] = mCfg.nrf.pinMiso;
|
||||
obj[F("pwr")] = mCfg.nrf.amplifierPower;
|
||||
obj[F("en")] = (bool) mCfg.nrf.enabled;
|
||||
} else {
|
||||
getVal<uint16_t>(obj, F("intvl"), &mCfg.nrf.sendInterval);
|
||||
getVal<uint8_t>(obj, F("maxRetry"), &mCfg.nrf.maxRetransPerPyld);
|
||||
|
@ -468,6 +507,11 @@ class settings {
|
|||
getVal<uint8_t>(obj, F("mosi"), &mCfg.nrf.pinMosi);
|
||||
getVal<uint8_t>(obj, F("miso"), &mCfg.nrf.pinMiso);
|
||||
getVal<uint8_t>(obj, F("pwr"), &mCfg.nrf.amplifierPower);
|
||||
#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_CS_PIN;
|
||||
mCfg.nrf.pinCe = DEF_CE_PIN;
|
||||
|
@ -479,13 +523,32 @@ class settings {
|
|||
}
|
||||
}
|
||||
|
||||
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("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.enabled = (bool) obj[F("en")];
|
||||
}
|
||||
}
|
||||
|
||||
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<uint16_t>(obj, F("port"), &mCfg.ntp.port);
|
||||
getVal<uint16_t>(obj, F("intvl"), &mCfg.ntp.interval);
|
||||
|
||||
if(mCfg.ntp.interval < 5) // minimum 5 minutes
|
||||
mCfg.ntp.interval = 720; // default -> 12 hours
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -586,12 +649,21 @@ class settings {
|
|||
obj[F("rstMidNight")] = (bool)mCfg.inst.rstYieldMidNight;
|
||||
obj[F("rstNotAvail")] = (bool)mCfg.inst.rstValsNotAvail;
|
||||
obj[F("rstComStop")] = (bool)mCfg.inst.rstValsCommStop;
|
||||
obj[F("strtWthtTime")] = (bool)mCfg.inst.startWithoutTime;
|
||||
obj[F("yldEff")] = mCfg.inst.yieldEffiency;
|
||||
}
|
||||
else {
|
||||
getVal<bool>(obj, F("en"), &mCfg.inst.enabled);
|
||||
getVal<bool>(obj, F("rstMidNight"), &mCfg.inst.rstYieldMidNight);
|
||||
getVal<bool>(obj, F("rstNotAvail"), &mCfg.inst.rstValsNotAvail);
|
||||
getVal<bool>(obj, F("rstComStop"), &mCfg.inst.rstValsCommStop);
|
||||
getVal<bool>(obj, F("strtWthtTime"), &mCfg.inst.startWithoutTime);
|
||||
getVal<float>(obj, F("yldEff"), &mCfg.inst.yieldEffiency);
|
||||
|
||||
if(mCfg.inst.yieldEffiency < 0.5)
|
||||
mCfg.inst.yieldEffiency = 1.0f;
|
||||
else if(mCfg.inst.yieldEffiency > 1.0f)
|
||||
mCfg.inst.yieldEffiency = 1.0f;
|
||||
}
|
||||
|
||||
JsonArray ivArr;
|
||||
|
@ -611,7 +683,7 @@ class settings {
|
|||
obj[F("en")] = (bool)cfg->enabled;
|
||||
obj[F("name")] = cfg->name;
|
||||
obj[F("sn")] = cfg->serial.u64;
|
||||
for(uint8_t i = 0; i < 4; i++) {
|
||||
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];
|
||||
|
@ -620,7 +692,10 @@ class settings {
|
|||
getVal<bool>(obj, F("en"), &cfg->enabled);
|
||||
getChar(obj, F("name"), cfg->name, MAX_NAME_LENGTH);
|
||||
getVal<uint64_t>(obj, F("sn"), &cfg->serial.u64);
|
||||
for(uint8_t i = 0; i < 4; i++) {
|
||||
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<const char*>());
|
||||
|
|
|
@ -12,8 +12,8 @@
|
|||
// VERSION
|
||||
//-------------------------------------
|
||||
#define VERSION_MAJOR 0
|
||||
#define VERSION_MINOR 6
|
||||
#define VERSION_PATCH 15
|
||||
#define VERSION_MINOR 7
|
||||
#define VERSION_PATCH 21
|
||||
|
||||
//-------------------------------------
|
||||
typedef struct {
|
||||
|
@ -74,10 +74,6 @@ union serial_u {
|
|||
#define MIN_MQTT_INTERVAL 60
|
||||
|
||||
|
||||
#define MQTT_STATUS_NOT_AVAIL_NOT_PROD 0
|
||||
#define MQTT_STATUS_AVAIL_NOT_PROD 1
|
||||
#define MQTT_STATUS_AVAIL_PROD 2
|
||||
|
||||
enum {MQTT_STATUS_OFFLINE = 0, MQTT_STATUS_PARTIAL, MQTT_STATUS_ONLINE};
|
||||
|
||||
//-------------------------------------
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 2022 Ahoy, https://github.com/lumpapu/ahoy
|
||||
// 2023 Ahoy, https://github.com/lumpapu/ahoy
|
||||
// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
|
@ -10,7 +10,7 @@
|
|||
#include <cstdint>
|
||||
|
||||
// inverter generations
|
||||
enum {IV_HM = 0, IV_MI};
|
||||
enum {IV_HM = 0, IV_MI, IV_HMS, IV_HMT};
|
||||
|
||||
// units
|
||||
enum {UNIT_V = 0, UNIT_A, UNIT_W, UNIT_WH, UNIT_KWH, UNIT_HZ, UNIT_C, UNIT_PCT, UNIT_VAR, UNIT_NONE};
|
||||
|
@ -18,19 +18,22 @@ const char* const units[] = {"V", "A", "W", "Wh", "kWh", "Hz", "°C", "%", "var"
|
|||
|
||||
// field types
|
||||
enum {FLD_UDC = 0, FLD_IDC, FLD_PDC, FLD_YD, FLD_YW, FLD_YT,
|
||||
FLD_UAC, FLD_IAC, FLD_PAC, FLD_F, FLD_T, FLD_PF, FLD_EFF,
|
||||
FLD_UAC, FLD_UAC_1N, FLD_UAC_2N, FLD_UAC_3N, FLD_UAC_12, FLD_UAC_23, FLD_UAC_31, FLD_IAC,
|
||||
FLD_IAC_1, FLD_IAC_2, FLD_IAC_3, FLD_PAC, FLD_F, FLD_T, FLD_PF, FLD_EFF,
|
||||
FLD_IRR, FLD_Q, FLD_EVT, FLD_FW_VERSION, FLD_FW_BUILD_YEAR,
|
||||
FLD_FW_BUILD_MONTH_DAY, FLD_FW_BUILD_HOUR_MINUTE, FLD_HW_ID,
|
||||
FLD_ACT_ACTIVE_PWR_LIMIT, /*FLD_ACT_REACTIVE_PWR_LIMIT, FLD_ACT_PF,*/ FLD_LAST_ALARM_CODE};
|
||||
|
||||
const char* const fields[] = {"U_DC", "I_DC", "P_DC", "YieldDay", "YieldWeek", "YieldTotal",
|
||||
"U_AC", "I_AC", "P_AC", "F_AC", "Temp", "PF_AC", "Efficiency", "Irradiation","Q_AC",
|
||||
"U_AC", "U_AC_1N", "U_AC_2N", "U_AC_3N", "UAC_12", "UAC_23", "UAC_31", "I_AC",
|
||||
"IAC_1", "I_AC_2", "I_AC_3", "P_AC", "F_AC", "Temp", "PF_AC", "Efficiency", "Irradiation","Q_AC",
|
||||
"ALARM_MES_ID","FWVersion","FWBuildYear","FWBuildMonthDay","FWBuildHourMinute","HWPartId",
|
||||
"active_PowerLimit", /*"reactivePowerLimit","Powerfactor",*/ "LastAlarmCode"};
|
||||
const char* const notAvail = "n/a";
|
||||
|
||||
const uint8_t fieldUnits[] = {UNIT_V, UNIT_A, UNIT_W, UNIT_WH, UNIT_KWH, UNIT_KWH,
|
||||
UNIT_V, UNIT_A, UNIT_W, UNIT_HZ, UNIT_C, UNIT_NONE, UNIT_PCT, UNIT_PCT, UNIT_VAR,
|
||||
UNIT_V, UNIT_V, UNIT_V, UNIT_V, UNIT_V, UNIT_V, UNIT_V, UNIT_A, UNIT_A, UNIT_A, UNIT_A,
|
||||
UNIT_W, UNIT_HZ, UNIT_C, UNIT_NONE, UNIT_PCT, UNIT_PCT, UNIT_VAR,
|
||||
UNIT_NONE, UNIT_NONE, UNIT_NONE, UNIT_NONE, UNIT_NONE, UNIT_NONE, UNIT_PCT, UNIT_NONE};
|
||||
|
||||
// mqtt discovery device classes
|
||||
|
@ -53,7 +56,7 @@ const byteAssign_fieldDeviceClass deviceFieldAssignment[] = {
|
|||
{FLD_UAC, DEVICE_CLS_VOLTAGE, STATE_CLS_MEASUREMENT},
|
||||
{FLD_IAC, DEVICE_CLS_CURRENT, STATE_CLS_MEASUREMENT},
|
||||
{FLD_PAC, DEVICE_CLS_PWR, STATE_CLS_MEASUREMENT},
|
||||
{FLD_F, DEVICE_CLS_FREQ, STATE_CLS_NONE},
|
||||
{FLD_F, DEVICE_CLS_FREQ, STATE_CLS_MEASUREMENT},
|
||||
{FLD_T, DEVICE_CLS_TEMP, STATE_CLS_MEASUREMENT},
|
||||
{FLD_PF, DEVICE_CLS_NONE, STATE_CLS_NONE},
|
||||
{FLD_EFF, DEVICE_CLS_NONE, STATE_CLS_NONE},
|
||||
|
@ -67,9 +70,9 @@ enum {CMD_CALC = 0xffff};
|
|||
|
||||
|
||||
// CH0 is default channel (freq, ac, temp)
|
||||
enum {CH0 = 0, CH1, CH2, CH3, CH4};
|
||||
enum {CH0 = 0, CH1, CH2, CH3, CH4, CH5, CH6};
|
||||
|
||||
enum {INV_TYPE_1CH = 0, INV_TYPE_2CH, INV_TYPE_4CH};
|
||||
enum {INV_TYPE_1CH = 0, INV_TYPE_2CH, INV_TYPE_4CH, INV_TYPE_6CH};
|
||||
|
||||
|
||||
typedef struct {
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
#endif
|
||||
|
||||
#include "hmDefines.h"
|
||||
#include "../hms/hmsDefines.h"
|
||||
#include <memory>
|
||||
#include <queue>
|
||||
#include "../config/settings.h"
|
||||
|
@ -101,6 +102,13 @@ const calcFunc_t<T> calcFunctions[] = {
|
|||
{ CALC_IRR_CH, &calcIrradiation }
|
||||
};
|
||||
|
||||
enum class InverterStatus : uint8_t {
|
||||
OFF,
|
||||
STARTING,
|
||||
PRODUCING,
|
||||
WAS_PRODUCING,
|
||||
WAS_ON
|
||||
};
|
||||
|
||||
template <class REC_TYP>
|
||||
class Inverter {
|
||||
|
@ -122,6 +130,10 @@ class Inverter {
|
|||
//String lastAlarmMsg;
|
||||
bool initialized; // needed to check if the inverter was correctly added (ESP32 specific - union types are never null)
|
||||
bool isConnected; // shows if inverter was successfully identified (fw version and hardware info)
|
||||
InverterStatus status; // indicates the current inverter status
|
||||
|
||||
static uint32_t *timestamp; // system timestamp
|
||||
static cfgInst_t *generalConfig; // general inverter configuration from setup
|
||||
|
||||
Inverter() {
|
||||
ivGen = IV_HM;
|
||||
|
@ -134,6 +146,7 @@ class Inverter {
|
|||
//lastAlarmMsg = "nothing";
|
||||
alarmMesIndex = 0;
|
||||
isConnected = false;
|
||||
status = InverterStatus::OFF;
|
||||
}
|
||||
|
||||
~Inverter() {
|
||||
|
@ -265,11 +278,13 @@ class Inverter {
|
|||
val <<= 8;
|
||||
val |= buf[ptr];
|
||||
} while(++ptr != end);
|
||||
if (FLD_T == rec->assign[pos].fieldId) {
|
||||
// temperature is a signed value!
|
||||
rec->record[pos] = (REC_TYP)((int16_t)val) / (REC_TYP)(div);
|
||||
if ((FLD_T == rec->assign[pos].fieldId) || (FLD_Q == rec->assign[pos].fieldId) || (FLD_PF == rec->assign[pos].fieldId)) {
|
||||
// temperature, Qvar, and power factor are a signed values
|
||||
rec->record[pos] = ((REC_TYP)((int16_t)val)) / (REC_TYP)(div);
|
||||
} else if (FLD_YT == rec->assign[pos].fieldId) {
|
||||
rec->record[pos] = ((REC_TYP)(val) / (REC_TYP)(div)) + ((REC_TYP)config->yieldCor[rec->assign[pos].ch-1]);
|
||||
rec->record[pos] = ((REC_TYP)(val) / (REC_TYP)(div) * generalConfig->yieldEffiency) + ((REC_TYP)config->yieldCor[rec->assign[pos].ch-1]);
|
||||
} else if (FLD_YD == rec->assign[pos].fieldId) {
|
||||
rec->record[pos] = (REC_TYP)(val) / (REC_TYP)(div) * generalConfig->yieldEffiency;
|
||||
} else {
|
||||
if ((REC_TYP)(div) > 1)
|
||||
rec->record[pos] = (REC_TYP)(val) / (REC_TYP)(div);
|
||||
|
@ -318,6 +333,9 @@ class Inverter {
|
|||
}
|
||||
else
|
||||
DPRINTLN(DBG_ERROR, F("addValue: assignment not found with cmd 0x"));
|
||||
|
||||
// update status state-machine
|
||||
isProducing();
|
||||
}
|
||||
|
||||
/*inline REC_TYP getPowerLimit(void) {
|
||||
|
@ -371,25 +389,42 @@ class Inverter {
|
|||
}
|
||||
}
|
||||
|
||||
bool isAvailable(uint32_t timestamp) {
|
||||
if((timestamp - recordMeas.ts) < INACT_THRES_SEC)
|
||||
return true;
|
||||
if((timestamp - recordInfo.ts) < INACT_THRES_SEC)
|
||||
return true;
|
||||
if((timestamp - recordConfig.ts) < INACT_THRES_SEC)
|
||||
return true;
|
||||
if((timestamp - recordAlarm.ts) < INACT_THRES_SEC)
|
||||
return true;
|
||||
return false;
|
||||
bool isAvailable() {
|
||||
bool avail = false;
|
||||
if((*timestamp - recordMeas.ts) < INVERTER_INACT_THRES_SEC)
|
||||
avail = true;
|
||||
if((*timestamp - recordInfo.ts) < INVERTER_INACT_THRES_SEC)
|
||||
avail = true;
|
||||
if((*timestamp - recordConfig.ts) < INVERTER_INACT_THRES_SEC)
|
||||
avail = true;
|
||||
if((*timestamp - recordAlarm.ts) < INVERTER_INACT_THRES_SEC)
|
||||
avail = true;
|
||||
|
||||
if(avail) {
|
||||
if(status < InverterStatus::PRODUCING)
|
||||
status = InverterStatus::STARTING;
|
||||
} else {
|
||||
if((*timestamp - recordMeas.ts) > INVERTER_OFF_THRES_SEC)
|
||||
status = InverterStatus::OFF;
|
||||
else
|
||||
status = InverterStatus::WAS_ON;
|
||||
}
|
||||
|
||||
return avail;
|
||||
}
|
||||
|
||||
bool isProducing(uint32_t timestamp) {
|
||||
bool isProducing() {
|
||||
bool producing = false;
|
||||
DPRINTLN(DBG_VERBOSE, F("hmInverter.h:isProducing"));
|
||||
if(isAvailable(timestamp)) {
|
||||
uint8_t pos = getPosByChFld(CH0, FLD_PAC, &recordMeas);
|
||||
return (getValue(pos, &recordMeas) > INACT_PWR_THRESH);
|
||||
if(isAvailable()) {
|
||||
producing = (getChannelFieldValue(CH0, FLD_PAC, &recordMeas) > INACT_PWR_THRESH);
|
||||
|
||||
if(producing)
|
||||
status = InverterStatus::PRODUCING;
|
||||
else if(InverterStatus::PRODUCING == status)
|
||||
status = InverterStatus::WAS_PRODUCING;
|
||||
}
|
||||
return false;
|
||||
return producing;
|
||||
}
|
||||
|
||||
uint16_t getFwVersion() {
|
||||
|
@ -421,22 +456,46 @@ class Inverter {
|
|||
switch (cmd) {
|
||||
case RealTimeRunData_Debug:
|
||||
if (INV_TYPE_1CH == type) {
|
||||
rec->length = (uint8_t)(HM1CH_LIST_LEN);
|
||||
rec->assign = (byteAssign_t *)hm1chAssignment;
|
||||
rec->pyldLen = HM1CH_PAYLOAD_LEN;
|
||||
channels = 1;
|
||||
if(IV_HM == ivGen) {
|
||||
rec->length = (uint8_t)(HM1CH_LIST_LEN);
|
||||
rec->assign = (byteAssign_t *)hm1chAssignment;
|
||||
rec->pyldLen = HM1CH_PAYLOAD_LEN;
|
||||
} else if(IV_HMS == ivGen) {
|
||||
rec->length = (uint8_t)(HMS1CH_LIST_LEN);
|
||||
rec->assign = (byteAssign_t *)hms1chAssignment;
|
||||
rec->pyldLen = HMS1CH_PAYLOAD_LEN;
|
||||
}
|
||||
channels = 1;
|
||||
}
|
||||
else if (INV_TYPE_2CH == type) {
|
||||
rec->length = (uint8_t)(HM2CH_LIST_LEN);
|
||||
rec->assign = (byteAssign_t *)hm2chAssignment;
|
||||
rec->pyldLen = HM2CH_PAYLOAD_LEN;
|
||||
channels = 2;
|
||||
if(IV_HM == ivGen) {
|
||||
rec->length = (uint8_t)(HM2CH_LIST_LEN);
|
||||
rec->assign = (byteAssign_t *)hm2chAssignment;
|
||||
rec->pyldLen = HM2CH_PAYLOAD_LEN;
|
||||
} else if(IV_HMS == ivGen) {
|
||||
rec->length = (uint8_t)(HMS2CH_LIST_LEN);
|
||||
rec->assign = (byteAssign_t *)hms2chAssignment;
|
||||
rec->pyldLen = HMS2CH_PAYLOAD_LEN;
|
||||
}
|
||||
channels = 2;
|
||||
}
|
||||
else if (INV_TYPE_4CH == type) {
|
||||
rec->length = (uint8_t)(HM4CH_LIST_LEN);
|
||||
rec->assign = (byteAssign_t *)hm4chAssignment;
|
||||
rec->pyldLen = HM4CH_PAYLOAD_LEN;
|
||||
channels = 4;
|
||||
if(IV_HM == ivGen) {
|
||||
rec->length = (uint8_t)(HM4CH_LIST_LEN);
|
||||
rec->assign = (byteAssign_t *)hm4chAssignment;
|
||||
rec->pyldLen = HM4CH_PAYLOAD_LEN;
|
||||
} else if(IV_HMS == ivGen) {
|
||||
rec->length = (uint8_t)(HMS4CH_LIST_LEN);
|
||||
rec->assign = (byteAssign_t *)hms4chAssignment;
|
||||
rec->pyldLen = HMS4CH_PAYLOAD_LEN;
|
||||
}
|
||||
channels = 4;
|
||||
}
|
||||
else if (INV_TYPE_6CH == type) {
|
||||
rec->length = (uint8_t)(HMT6CH_LIST_LEN);
|
||||
rec->assign = (byteAssign_t *)hmt6chAssignment;
|
||||
rec->pyldLen = HMT6CH_PAYLOAD_LEN;
|
||||
channels = 6;
|
||||
}
|
||||
else {
|
||||
rec->length = 0;
|
||||
|
@ -580,6 +639,11 @@ class Inverter {
|
|||
bool mDevControlRequest; // true if change needed
|
||||
};
|
||||
|
||||
template <class REC_TYP>
|
||||
uint32_t *Inverter<REC_TYP>::timestamp {0};
|
||||
template <class REC_TYP>
|
||||
cfgInst_t *Inverter<REC_TYP>::generalConfig {0};
|
||||
|
||||
|
||||
/**
|
||||
* To calculate values which are not transmitted by the unit there is a generic
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#include "../utils/dbg.h"
|
||||
#include "../utils/crc.h"
|
||||
#include "../config/config.h"
|
||||
#include "hmRadio.h"
|
||||
#include <Arduino.h>
|
||||
|
||||
typedef struct {
|
||||
|
@ -27,18 +28,19 @@ typedef struct {
|
|||
} invPayload_t;
|
||||
|
||||
|
||||
typedef std::function<void(uint8_t)> payloadListenerType;
|
||||
typedef std::function<void(uint8_t, Inverter<> *)> payloadListenerType;
|
||||
typedef std::function<void(uint16_t alarmCode, uint32_t start, uint32_t end)> alarmListenerType;
|
||||
|
||||
|
||||
template<class HMSYSTEM>
|
||||
template<class HMSYSTEM, class HMRADIO>
|
||||
class HmPayload {
|
||||
public:
|
||||
HmPayload() {}
|
||||
|
||||
void setup(IApp *app, HMSYSTEM *sys, statistics_t *stat, uint8_t maxRetransmits, uint32_t *timestamp) {
|
||||
void setup(IApp *app, HMSYSTEM *sys, HMRADIO *radio, statistics_t *stat, uint8_t maxRetransmits, uint32_t *timestamp) {
|
||||
mApp = app;
|
||||
mSys = sys;
|
||||
mRadio = radio;
|
||||
mStat = stat;
|
||||
mMaxRetrans = maxRetransmits;
|
||||
mTimestamp = timestamp;
|
||||
|
@ -93,28 +95,6 @@ class HmPayload {
|
|||
notify(0x0b);
|
||||
}*/
|
||||
|
||||
void zeroInverterValues(Inverter<> *iv, bool skipYieldDay = true) {
|
||||
DPRINTLN(DBG_DEBUG, F("zeroInverterValues"));
|
||||
record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug);
|
||||
for(uint8_t ch = 0; ch <= iv->channels; ch++) {
|
||||
uint8_t pos = 0;
|
||||
for(uint8_t fld = 0; fld < FLD_EVT; fld++) {
|
||||
switch(fld) {
|
||||
case FLD_YD:
|
||||
if(skipYieldDay)
|
||||
continue;
|
||||
else
|
||||
break;
|
||||
case FLD_YT:
|
||||
continue;
|
||||
}
|
||||
pos = iv->getPosByChFld(ch, fld, rec);
|
||||
iv->setValue(pos, rec, 0.0f);
|
||||
}
|
||||
iv->doCalculations();
|
||||
}
|
||||
}
|
||||
|
||||
void ivSendHighPrio(Inverter<> *iv) {
|
||||
mHighPrioIv = iv;
|
||||
}
|
||||
|
@ -163,7 +143,7 @@ class HmPayload {
|
|||
DBGPRINT(F(" power limit "));
|
||||
DBGPRINTLN(String(iv->powerLimit[0]));
|
||||
}
|
||||
mSys->Radio.sendControlPacket(iv->radioId.u64, iv->devControlCmd, iv->powerLimit, false);
|
||||
mRadio->sendControlPacket(iv->radioId.u64, iv->devControlCmd, iv->powerLimit, false);
|
||||
mPayload[iv->id].txCmd = iv->devControlCmd;
|
||||
//iv->clearCmdQueue();
|
||||
//iv->enqueCommand<InfoCommand>(SystemConfigPara); // read back power limit
|
||||
|
@ -172,7 +152,7 @@ class HmPayload {
|
|||
DPRINT_IVID(DBG_INFO, iv->id);
|
||||
DBGPRINT(F("prepareDevInformCmd 0x"));
|
||||
DBGHEXLN(cmd);
|
||||
mSys->Radio.prepareDevInformCmd(iv->radioId.u64, cmd, mPayload[iv->id].ts, iv->alarmMesIndex, false);
|
||||
mRadio->prepareDevInformCmd(iv->radioId.u64, cmd, mPayload[iv->id].ts, iv->alarmMesIndex, false);
|
||||
mPayload[iv->id].txCmd = cmd;
|
||||
}
|
||||
}
|
||||
|
@ -216,7 +196,7 @@ class HmPayload {
|
|||
ok = false;
|
||||
|
||||
DPRINT_IVID(DBG_INFO, iv->id);
|
||||
DBGPRINT(F("has "));
|
||||
DBGPRINT(F(" has "));
|
||||
if(!ok) DBGPRINT(F("not "));
|
||||
DBGPRINT(F("accepted power limit set point "));
|
||||
DBGPRINT(String(iv->powerLimit[0]));
|
||||
|
@ -238,7 +218,7 @@ class HmPayload {
|
|||
if (NULL == iv)
|
||||
continue; // skip to next inverter
|
||||
|
||||
if (IV_MI == iv->ivGen) // only process HM inverters
|
||||
if (IV_HM != iv->ivGen) // only process HM inverters
|
||||
continue; // skip to next inverter
|
||||
|
||||
if ((mPayload[iv->id].txId != (TX_REQ_INFO + ALL_FRAMES)) && (0 != mPayload[iv->id].txId)) {
|
||||
|
@ -261,14 +241,14 @@ class HmPayload {
|
|||
} else if(iv->devControlCmd == ActivePowerContr) {
|
||||
DPRINT_IVID(DBG_INFO, iv->id);
|
||||
DPRINTLN(DBG_INFO, F("retransmit power limit"));
|
||||
mSys->Radio.sendControlPacket(iv->radioId.u64, iv->devControlCmd, iv->powerLimit, true);
|
||||
mRadio->sendControlPacket(iv->radioId.u64, iv->devControlCmd, iv->powerLimit, true);
|
||||
} else {
|
||||
if(false == mPayload[iv->id].gotFragment) {
|
||||
/*
|
||||
DPRINTLN(DBG_WARN, F("nothing received: Request Complete Retransmit"));
|
||||
mPayload[iv->id].txCmd = iv->getQueuedCmd();
|
||||
DPRINTLN(DBG_INFO, F("(#") + String(iv->id) + F(") prepareDevInformCmd 0x") + String(mPayload[iv->id].txCmd, HEX));
|
||||
mSys->Radio.prepareDevInformCmd(iv->radioId.u64, mPayload[iv->id].txCmd, mPayload[iv->id].ts, iv->alarmMesIndex, true);
|
||||
mRadio->prepareDevInformCmd(iv->radioId.u64, mPayload[iv->id].txCmd, mPayload[iv->id].ts, iv->alarmMesIndex, true);
|
||||
*/
|
||||
DPRINT_IVID(DBG_INFO, iv->id);
|
||||
DBGPRINTLN(F("nothing received"));
|
||||
|
@ -280,7 +260,7 @@ class HmPayload {
|
|||
DBGPRINT(F("Frame "));
|
||||
DBGPRINT(String(i + 1));
|
||||
DBGPRINTLN(F(" missing: Request Retransmit"));
|
||||
mSys->Radio.sendCmdPacket(iv->radioId.u64, TX_REQ_INFO, (SINGLE_FRAME + i), true);
|
||||
mRadio->sendCmdPacket(iv->radioId.u64, TX_REQ_INFO, (SINGLE_FRAME + i), true);
|
||||
break; // only request retransmit one frame per loop
|
||||
}
|
||||
yield();
|
||||
|
@ -297,7 +277,7 @@ class HmPayload {
|
|||
DPRINT_IVID(DBG_INFO, iv->id);
|
||||
DBGPRINT(F("prepareDevInformCmd 0x"));
|
||||
DBGHEXLN(mPayload[iv->id].txCmd);
|
||||
mSys->Radio.prepareDevInformCmd(iv->radioId.u64, mPayload[iv->id].txCmd, mPayload[iv->id].ts, iv->alarmMesIndex, true);
|
||||
mRadio->prepareDevInformCmd(iv->radioId.u64, mPayload[iv->id].txCmd, mPayload[iv->id].ts, iv->alarmMesIndex, true);
|
||||
}
|
||||
} else { // payload complete
|
||||
DPRINT(DBG_INFO, F("procPyld: cmd: 0x"));
|
||||
|
@ -325,7 +305,7 @@ class HmPayload {
|
|||
DPRINT(DBG_INFO, F("Payload ("));
|
||||
DBGPRINT(String(payloadLen));
|
||||
DBGPRINT(F("): "));
|
||||
mSys->Radio.dumpBuf(payload, payloadLen);
|
||||
ah::dumpBuf(payload, payloadLen);
|
||||
}
|
||||
|
||||
if (NULL == rec) {
|
||||
|
@ -340,7 +320,7 @@ class HmPayload {
|
|||
yield();
|
||||
}
|
||||
iv->doCalculations();
|
||||
notify(mPayload[iv->id].txCmd);
|
||||
notify(mPayload[iv->id].txCmd, iv);
|
||||
|
||||
if(AlarmData == mPayload[iv->id].txCmd) {
|
||||
uint8_t i = 0;
|
||||
|
@ -370,9 +350,9 @@ class HmPayload {
|
|||
}
|
||||
|
||||
private:
|
||||
void notify(uint8_t val) {
|
||||
void notify(uint8_t val, Inverter<> *iv) {
|
||||
if(NULL != mCbPayload)
|
||||
(mCbPayload)(val);
|
||||
(mCbPayload)(val, iv);
|
||||
}
|
||||
|
||||
void notify(uint16_t code, uint32_t start, uint32_t endTime) {
|
||||
|
@ -425,6 +405,7 @@ class HmPayload {
|
|||
|
||||
IApp *mApp;
|
||||
HMSYSTEM *mSys;
|
||||
HMRADIO *mRadio;
|
||||
statistics_t *mStat;
|
||||
uint8_t mMaxRetrans;
|
||||
uint32_t *mTimestamp;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 2023 Ahoy, https://github.com/lumpapu/ahoy
|
||||
// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/
|
||||
// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/4.0/deed
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#ifndef __RADIO_H__
|
||||
|
@ -24,27 +24,6 @@
|
|||
const char* const rf24AmpPowerNames[] = {"MIN", "LOW", "HIGH", "MAX"};
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// MACROS
|
||||
//-----------------------------------------------------------------------------
|
||||
#define CP_U32_LittleEndian(buf, v) ({ \
|
||||
uint8_t *b = buf; \
|
||||
b[0] = ((v >> 24) & 0xff); \
|
||||
b[1] = ((v >> 16) & 0xff); \
|
||||
b[2] = ((v >> 8) & 0xff); \
|
||||
b[3] = ((v ) & 0xff); \
|
||||
})
|
||||
|
||||
#define CP_U32_BigEndian(buf, v) ({ \
|
||||
uint8_t *b = buf; \
|
||||
b[3] = ((v >> 24) & 0xff); \
|
||||
b[2] = ((v >> 16) & 0xff); \
|
||||
b[1] = ((v >> 8) & 0xff); \
|
||||
b[0] = ((v ) & 0xff); \
|
||||
})
|
||||
|
||||
#define BIT_CNT(x) ((x)<<3)
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// HM Radio class
|
||||
//-----------------------------------------------------------------------------
|
||||
|
@ -53,7 +32,7 @@ class HmRadio {
|
|||
public:
|
||||
HmRadio() : mNrf24(CE_PIN, CS_PIN, SPI_SPEED) {
|
||||
if(mSerialDebug) {
|
||||
DPRINT(DBG_VERBOSE, F("hmRadio.h : HmRadio():mNrf24(CE_PIN: "));
|
||||
DPRINT(DBG_VERBOSE, F("hmRadio.h : HmRadio():mNrf24(CE_PIN: "));
|
||||
DBGPRINT(String(CE_PIN));
|
||||
DBGPRINT(F(", CS_PIN: "));
|
||||
DBGPRINT(String(CS_PIN));
|
||||
|
@ -105,8 +84,8 @@ class HmRadio {
|
|||
DTU_RADIO_ID = ((uint64_t)(((dtuSn >> 24) & 0xFF) | ((dtuSn >> 8) & 0xFF00) | ((dtuSn << 8) & 0xFF0000) | ((dtuSn << 24) & 0xFF000000)) << 8) | 0x01;
|
||||
|
||||
#ifdef ESP32
|
||||
#if CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S3
|
||||
mSpi = new SPIClass(FSPI);
|
||||
#if CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3
|
||||
mSpi = new SPIClass(HSPI);
|
||||
#else
|
||||
mSpi = new SPIClass(VSPI);
|
||||
#endif
|
||||
|
@ -242,7 +221,7 @@ class HmRadio {
|
|||
mTxBuf[18] = (alarmMesId >> 8) & 0xff;
|
||||
mTxBuf[19] = (alarmMesId ) & 0xff;
|
||||
}
|
||||
sendPacket(invId, 24, isRetransmit, true);
|
||||
sendPacket(invId, 24, isRetransmit);
|
||||
}
|
||||
|
||||
void sendCmdPacket(uint64_t invId, uint8_t mid, uint8_t pid, bool isRetransmit, bool appendCrc16=true) {
|
||||
|
@ -250,15 +229,6 @@ class HmRadio {
|
|||
sendPacket(invId, 10, isRetransmit, appendCrc16);
|
||||
}
|
||||
|
||||
void dumpBuf(uint8_t buf[], uint8_t len) {
|
||||
//DPRINTLN(DBG_VERBOSE, F("hmRadio.h:dumpBuf"));
|
||||
for(uint8_t i = 0; i < len; i++) {
|
||||
DHEX(buf[i]);
|
||||
DBGPRINT(" ");
|
||||
}
|
||||
DBGPRINTLN("");
|
||||
}
|
||||
|
||||
uint8_t getDataRate(void) {
|
||||
if(!mNrf24.isChipConnected())
|
||||
return 3; // unkown
|
||||
|
@ -291,17 +261,17 @@ class HmRadio {
|
|||
p.len = len;
|
||||
mNrf24.read(p.packet, len);
|
||||
if (p.packet[0] != 0x00) {
|
||||
mBufCtrl.push(p);
|
||||
if (p.packet[0] == (TX_REQ_INFO + ALL_FRAMES)) // response from get information command
|
||||
mBufCtrl.push(p);
|
||||
if (p.packet[0] == (TX_REQ_INFO + ALL_FRAMES)) // response from get information command
|
||||
isLastPackage = (p.packet[9] > ALL_FRAMES); // > ALL_FRAMES indicates last packet received
|
||||
else if (p.packet[0] == ( 0x0f + ALL_FRAMES) ) // response from MI get information command
|
||||
else if (p.packet[0] == ( 0x0f + ALL_FRAMES) ) // response from MI get information command
|
||||
isLastPackage = (p.packet[9] > 0x10); // > 0x10 indicates last packet received
|
||||
else if ((p.packet[0] != 0x88) && (p.packet[0] != 0x92)) // ignore fragment number zero and MI status messages //#0 was p.packet[0] != 0x00 &&
|
||||
isLastPackage = true; // response from dev control command
|
||||
isLastPackage = true; // response from dev control command
|
||||
}
|
||||
}
|
||||
yield();
|
||||
}
|
||||
yield();
|
||||
}
|
||||
return isLastPackage;
|
||||
}
|
||||
|
||||
|
@ -311,7 +281,7 @@ class HmRadio {
|
|||
DHEX(mid);
|
||||
DBGPRINT(F(" pid: "));
|
||||
DBGHEXLN(pid);
|
||||
}
|
||||
}
|
||||
memset(mTxBuf, 0, MAX_RF_PAYLOAD_SIZE);
|
||||
mTxBuf[0] = mid; // message id
|
||||
CP_U32_BigEndian(&mTxBuf[1], (invId >> 8));
|
||||
|
@ -344,7 +314,7 @@ class HmRadio {
|
|||
DBGPRINT("B Ch");
|
||||
DBGPRINT(String(mRfChLst[mTxChIdx]));
|
||||
DBGPRINT(F(" | "));
|
||||
dumpBuf(mTxBuf, len);
|
||||
ah::dumpBuf(mTxBuf, len);
|
||||
}
|
||||
|
||||
mNrf24.stopListening();
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 2022 Ahoy, https://github.com/lumpapu/ahoy
|
||||
// 2023 Ahoy, https://github.com/lumpapu/ahoy
|
||||
// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
|
@ -7,37 +7,34 @@
|
|||
#define __HM_SYSTEM_H__
|
||||
|
||||
#include "hmInverter.h"
|
||||
#include "hmRadio.h"
|
||||
|
||||
template <uint8_t MAX_INVERTER=3, class INVERTERTYPE=Inverter<float>>
|
||||
class HmSystem {
|
||||
public:
|
||||
HmRadio<> Radio;
|
||||
|
||||
HmSystem() {}
|
||||
|
||||
void setup() {
|
||||
void setup(uint32_t *timestamp) {
|
||||
mInverter[0].timestamp = timestamp;
|
||||
mNumInv = 0;
|
||||
Radio.setup();
|
||||
}
|
||||
|
||||
void setup(uint8_t ampPwr, uint8_t irqPin, uint8_t cePin, uint8_t csPin, uint8_t sclkPin, uint8_t mosiPin, uint8_t misoPin) {
|
||||
mNumInv = 0;
|
||||
Radio.setup(ampPwr, irqPin, cePin, csPin, sclkPin, mosiPin, misoPin);
|
||||
}
|
||||
|
||||
void addInverters(cfgInst_t *config) {
|
||||
mInverter[0].generalConfig = config;
|
||||
Inverter<> *iv;
|
||||
for (uint8_t i = 0; i < MAX_NUM_INVERTERS; i++) {
|
||||
iv = addInverter(&config->iv[i]);
|
||||
if (0ULL != config->iv[i].serial.u64) {
|
||||
if (NULL != iv) {
|
||||
DPRINT(DBG_INFO, "added inverter ");
|
||||
if(iv->config->serial.b[5] == 0x11)
|
||||
DBGPRINT("HM");
|
||||
else {
|
||||
if(iv->config->serial.b[5] == 0x11) {
|
||||
if((iv->config->serial.b[4] & 0x0f) == 0x04)
|
||||
DBGPRINT("HMS");
|
||||
else
|
||||
DBGPRINT("HM");
|
||||
} else if(iv->config->serial.b[5] == 0x13)
|
||||
DBGPRINT("HMT");
|
||||
else
|
||||
DBGPRINT(((iv->config->serial.b[4] & 0x03) == 0x01) ? " (2nd Gen) " : " (3rd Gen) ");
|
||||
}
|
||||
|
||||
DBGPRINTLN(String(iv->config->serial.u64, HEX));
|
||||
|
||||
|
@ -61,25 +58,40 @@ class HmSystem {
|
|||
DPRINTLN(DBG_VERBOSE, " " + String(p->config->serial.b[4], HEX));
|
||||
if((p->config->serial.b[5] == 0x11) || (p->config->serial.b[5] == 0x10)) {
|
||||
switch(p->config->serial.b[4]) {
|
||||
case 0x24: // HMS-500
|
||||
case 0x22:
|
||||
case 0x21: p->type = INV_TYPE_1CH; break;
|
||||
case 0x21: p->type = INV_TYPE_1CH;
|
||||
break;
|
||||
|
||||
case 0x44: // HMS-1000
|
||||
case 0x42:
|
||||
case 0x41: p->type = INV_TYPE_2CH; break;
|
||||
case 0x41: p->type = INV_TYPE_2CH;
|
||||
break;
|
||||
|
||||
case 0x64: // HMS-2000
|
||||
case 0x62:
|
||||
case 0x61: p->type = INV_TYPE_4CH; break;
|
||||
case 0x61: p->type = INV_TYPE_4CH;
|
||||
break;
|
||||
|
||||
default:
|
||||
DPRINTLN(DBG_ERROR, F("unknown inverter type"));
|
||||
break;
|
||||
}
|
||||
|
||||
if(p->config->serial.b[5] == 0x11)
|
||||
p->ivGen = IV_HM;
|
||||
if(p->config->serial.b[5] == 0x11) {
|
||||
if((p->config->serial.b[4] & 0x0f) == 0x04)
|
||||
p->ivGen = IV_HMS;
|
||||
else
|
||||
p->ivGen = IV_HM;
|
||||
}
|
||||
else if((p->config->serial.b[4] & 0x03) == 0x02) // MI 3rd Gen -> same as HM
|
||||
p->ivGen = IV_HM;
|
||||
else // MI 2nd Gen
|
||||
p->ivGen = IV_MI;
|
||||
}
|
||||
else if(p->config->serial.u64 != 0ULL)
|
||||
} else if(p->config->serial.b[5] == 0x13) {
|
||||
p->ivGen = IV_HMT;
|
||||
p->type = INV_TYPE_6CH;
|
||||
} else if(p->config->serial.u64 != 0ULL)
|
||||
DPRINTLN(DBG_ERROR, F("inverter type can't be detected!"));
|
||||
|
||||
p->init();
|
||||
|
@ -124,10 +136,6 @@ class HmSystem {
|
|||
return MAX_NUM_INVERTERS;
|
||||
}
|
||||
|
||||
void enableDebug() {
|
||||
Radio.enableDebug();
|
||||
}
|
||||
|
||||
private:
|
||||
INVERTERTYPE mInverter[MAX_INVERTER];
|
||||
uint8_t mNumInv;
|
||||
|
|
|
@ -33,15 +33,15 @@ typedef struct {
|
|||
} miPayload_t;
|
||||
|
||||
|
||||
typedef std::function<void(uint8_t)> miPayloadListenerType;
|
||||
typedef std::function<void(uint8_t, Inverter<> *)> miPayloadListenerType;
|
||||
|
||||
|
||||
template<class HMSYSTEM>
|
||||
template<class HMSYSTEM, class HMRADIO>
|
||||
class MiPayload {
|
||||
public:
|
||||
MiPayload() {}
|
||||
|
||||
void setup(IApp *app, HMSYSTEM *sys, statistics_t *stat, uint8_t maxRetransmits, uint32_t *timestamp) {
|
||||
void setup(IApp *app, HMSYSTEM *sys, HMRADIO *radio, statistics_t *stat, uint8_t maxRetransmits, uint32_t *timestamp) {
|
||||
mApp = app;
|
||||
mSys = sys;
|
||||
mStat = stat;
|
||||
|
@ -90,21 +90,21 @@ class MiPayload {
|
|||
DPRINT_IVID(DBG_INFO, iv->id);
|
||||
if (!mPayload[iv->id].gotFragment) {
|
||||
mStat->rxFailNoAnser++; // got nothing
|
||||
if (mSerialDebug)
|
||||
DBGPRINTLN(F("enqueued cmd failed/timeout"));
|
||||
if (mSerialDebug)
|
||||
DBGPRINTLN(F("enqueued cmd failed/timeout"));
|
||||
} else {
|
||||
mStat->rxFail++; // got "fragments" (part of the required messages)
|
||||
// but no complete set of responses
|
||||
if (mSerialDebug) {
|
||||
if (mSerialDebug) {
|
||||
DBGPRINT(F("no complete Payload received! (retransmits: "));
|
||||
DBGPRINT(String(mPayload[iv->id].retransmits));
|
||||
DBGPRINTLN(F(")"));
|
||||
}
|
||||
DBGPRINT(String(mPayload[iv->id].retransmits));
|
||||
DBGPRINTLN(F(")"));
|
||||
}
|
||||
iv->setQueuedCmdFinished(); // command failed
|
||||
}
|
||||
iv->setQueuedCmdFinished(); // command failed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
reset(iv->id);
|
||||
mPayload[iv->id].requested = true;
|
||||
|
@ -124,7 +124,7 @@ class MiPayload {
|
|||
DBGPRINT(F(" power limit "));
|
||||
DBGPRINTLN(String(iv->powerLimit[0]));
|
||||
}
|
||||
mSys->Radio.sendControlPacket(iv->radioId.u64, iv->devControlCmd, iv->powerLimit, false, false);
|
||||
mRadio->sendControlPacket(iv->radioId.u64, iv->devControlCmd, iv->powerLimit, false, false);
|
||||
mPayload[iv->id].txCmd = iv->devControlCmd;
|
||||
mPayload[iv->id].limitrequested = true;
|
||||
|
||||
|
@ -148,10 +148,10 @@ class MiPayload {
|
|||
if (cmd == 0x01 || cmd == SystemConfigPara ) { //0x1 and 0x05 for HM-types
|
||||
cmd = 0x0f; // for MI, these seem to make part of the Polling the device software and hardware version number command
|
||||
cmd2 = cmd == SystemConfigPara ? 0x01 : 0x00; //perhaps we can only try to get second frame?
|
||||
mSys->Radio.sendCmdPacket(iv->radioId.u64, cmd, cmd2, false, false);
|
||||
mRadio->sendCmdPacket(iv->radioId.u64, cmd, cmd2, false, false);
|
||||
} else {
|
||||
//mSys->Radio.prepareDevInformCmd(iv->radioId.u64, cmd2, mPayload[iv->id].ts, iv->alarmMesIndex, false, cmd);
|
||||
mSys->Radio.sendCmdPacket(iv->radioId.u64, cmd, cmd2, false, false);
|
||||
mRadio->sendCmdPacket(iv->radioId.u64, cmd, cmd2, false, false);
|
||||
};
|
||||
|
||||
mPayload[iv->id].txCmd = cmd;
|
||||
|
@ -236,27 +236,27 @@ const byteAssign_t InfoAssignment[] = {
|
|||
mPayload[iv->id].gotFragment = true;
|
||||
if(mSerialDebug) {
|
||||
DPRINT_IVID(DBG_INFO, iv->id);
|
||||
DPRINT(DBG_INFO,F("HW_VER is "));
|
||||
DBGPRINTLN(String((p->packet[24] << 8) + p->packet[25]));
|
||||
DPRINT(DBG_INFO,F("HW_VER is "));
|
||||
DBGPRINTLN(String((p->packet[24] << 8) + p->packet[25]));
|
||||
}
|
||||
} else if ( p->packet[9] == 0x01 || p->packet[9] == 0x10 ) {//second frame for MI, 3rd gen. answers in 0x10
|
||||
DPRINT_IVID(DBG_INFO, iv->id);
|
||||
if ( p->packet[9] == 0x01 ) {
|
||||
DBGPRINTLN(F("got 2nd frame (hw info)"));
|
||||
DPRINT(DBG_INFO,F("HW_PartNo "));
|
||||
DBGPRINTLN(String((uint32_t) (((p->packet[10] << 8) | p->packet[11]) << 8 | p->packet[12]) << 8 | p->packet[13]));
|
||||
DPRINT(DBG_INFO,F("HW_PartNo "));
|
||||
DBGPRINTLN(String((uint32_t) (((p->packet[10] << 8) | p->packet[11]) << 8 | p->packet[12]) << 8 | p->packet[13]));
|
||||
mPayload[iv->id].gotFragment = true;
|
||||
iv->setValue(iv->getPosByChFld(0, FLD_YT, rec), rec, (float) ((p->packet[20] << 8) + p->packet[21])/1);
|
||||
if(mSerialDebug) {
|
||||
DPRINT(DBG_INFO,F("HW_FB_TLmValue "));
|
||||
DBGPRINTLN(String((p->packet[14] << 8) + p->packet[15]));
|
||||
DPRINT(DBG_INFO,F("HW_FB_ReSPRT "));
|
||||
DBGPRINTLN(String((p->packet[16] << 8) + p->packet[17]));
|
||||
DPRINT(DBG_INFO,F("HW_GridSamp_ResValule "));
|
||||
DBGPRINTLN(String((p->packet[18] << 8) + p->packet[19]));
|
||||
DPRINT(DBG_INFO,F("HW_FB_TLmValue "));
|
||||
DBGPRINTLN(String((p->packet[14] << 8) + p->packet[15]));
|
||||
DPRINT(DBG_INFO,F("HW_FB_ReSPRT "));
|
||||
DBGPRINTLN(String((p->packet[16] << 8) + p->packet[17]));
|
||||
DPRINT(DBG_INFO,F("HW_GridSamp_ResValule "));
|
||||
DBGPRINTLN(String((p->packet[18] << 8) + p->packet[19]));
|
||||
DPRINT(DBG_INFO,F("HW_ECapValue "));
|
||||
DBGPRINTLN(String((p->packet[20] << 8) + p->packet[21]));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
DBGPRINTLN(F("3rd gen. inverter!")); // see table in OpenDTU code, DevInfoParser.cpp devInfo[]
|
||||
}
|
||||
|
@ -348,7 +348,7 @@ const byteAssign_t InfoAssignment[] = {
|
|||
DPRINT(DBG_INFO, F("Payload ("));
|
||||
DBGPRINT(String(payloadLen));
|
||||
DBGPRINT("): ");
|
||||
mSys->Radio.dumpBuf(payload, payloadLen);
|
||||
ah::dumpBuf(payload, payloadLen);
|
||||
}
|
||||
|
||||
if (NULL == rec) {
|
||||
|
@ -363,7 +363,7 @@ const byteAssign_t InfoAssignment[] = {
|
|||
yield();
|
||||
}
|
||||
iv->doCalculations();
|
||||
notify(mPayload[iv->id].txCmd);
|
||||
notify(mPayload[iv->id].txCmd, iv);
|
||||
|
||||
if(AlarmData == mPayload[iv->id].txCmd) {
|
||||
uint8_t i = 0;
|
||||
|
@ -431,7 +431,7 @@ const byteAssign_t InfoAssignment[] = {
|
|||
} else if(iv->devControlCmd == ActivePowerContr) {
|
||||
DPRINT_IVID(DBG_INFO, iv->id);
|
||||
DBGPRINTLN(F("retransmit power limit"));
|
||||
mSys->Radio.sendControlPacket(iv->radioId.u64, iv->devControlCmd, iv->powerLimit, true, false);
|
||||
mRadio->sendControlPacket(iv->radioId.u64, iv->devControlCmd, iv->powerLimit, true, false);
|
||||
} else {
|
||||
uint8_t cmd = mPayload[iv->id].txCmd;
|
||||
if (mPayload[iv->id].retransmits < mMaxRetrans) {
|
||||
|
@ -442,7 +442,7 @@ const byteAssign_t InfoAssignment[] = {
|
|||
mPayload[iv->id].retransmits = mMaxRetrans;
|
||||
} else if ( cmd == 0x0f ) {
|
||||
//hard/firmware request
|
||||
mSys->Radio.sendCmdPacket(iv->radioId.u64, 0x0f, 0x00, true, false);
|
||||
mRadio->sendCmdPacket(iv->radioId.u64, 0x0f, 0x00, true, false);
|
||||
//iv->setQueuedCmdFinished();
|
||||
//cmd = iv->getQueuedCmd();
|
||||
} else {
|
||||
|
@ -479,7 +479,7 @@ const byteAssign_t InfoAssignment[] = {
|
|||
}
|
||||
DBGPRINT(F(" 0x"));
|
||||
DBGHEXLN(cmd);
|
||||
mSys->Radio.sendCmdPacket(iv->radioId.u64, cmd, cmd, true, false);
|
||||
mRadio->sendCmdPacket(iv->radioId.u64, cmd, cmd, true, false);
|
||||
//mSys->Radio.prepareDevInformCmd(iv->radioId.u64, cmd, mPayload[iv->id].ts, iv->alarmMesIndex, true, cmd);
|
||||
yield();
|
||||
}
|
||||
|
@ -497,7 +497,7 @@ const byteAssign_t InfoAssignment[] = {
|
|||
DBGPRINT(F("prepareDevInformCmd 0x"));
|
||||
DBGHEXLN(mPayload[iv->id].txCmd);
|
||||
//mSys->Radio.prepareDevInformCmd(iv->radioId.u64, mPayload[iv->id].txCmd, mPayload[iv->id].ts, iv->alarmMesIndex, true);
|
||||
mSys->Radio.sendCmdPacket(iv->radioId.u64, mPayload[iv->id].txCmd, mPayload[iv->id].txCmd, false, false);
|
||||
mRadio->sendCmdPacket(iv->radioId.u64, mPayload[iv->id].txCmd, mPayload[iv->id].txCmd, false, false);
|
||||
}
|
||||
}
|
||||
/*else { // payload complete
|
||||
|
@ -556,9 +556,9 @@ const byteAssign_t InfoAssignment[] = {
|
|||
}
|
||||
|
||||
private:
|
||||
void notify(uint8_t val) {
|
||||
void notify(uint8_t val, Inverter<> *iv) {
|
||||
if(NULL != mCbMiPayload)
|
||||
(mCbMiPayload)(val);
|
||||
(mCbMiPayload)(val, iv);
|
||||
}
|
||||
|
||||
void miStsDecode(Inverter<> *iv, packet_t *p, uint8_t stschan = CH1) {
|
||||
|
@ -704,20 +704,20 @@ const byteAssign_t InfoAssignment[] = {
|
|||
|
||||
}
|
||||
|
||||
/*
|
||||
if(AlarmData == mPayload[iv->id].txCmd) {
|
||||
uint8_t i = 0;
|
||||
uint16_t code;
|
||||
uint32_t start, end;
|
||||
while(1) {
|
||||
code = iv->parseAlarmLog(i++, payload, payloadLen, &start, &end);
|
||||
if(0 == code)
|
||||
break;
|
||||
if (NULL != mCbMiAlarm)
|
||||
(mCbAlarm)(code, start, end);
|
||||
yield();
|
||||
}
|
||||
}*/
|
||||
/*
|
||||
if(AlarmData == mPayload[iv->id].txCmd) {
|
||||
uint8_t i = 0;
|
||||
uint16_t code;
|
||||
uint32_t start, end;
|
||||
while(1) {
|
||||
code = iv->parseAlarmLog(i++, payload, payloadLen, &start, &end);
|
||||
if(0 == code)
|
||||
break;
|
||||
if (NULL != mCbMiAlarm)
|
||||
(mCbAlarm)(code, start, end);
|
||||
yield();
|
||||
}
|
||||
}*/
|
||||
|
||||
//if ( mPayload[iv->id].complete || //4ch device
|
||||
if ( p->packet[0] == (0x39 + ALL_FRAMES) || //4ch device - last message
|
||||
|
@ -725,12 +725,12 @@ const byteAssign_t InfoAssignment[] = {
|
|||
&& mPayload[iv->id].dataAB[CH0]
|
||||
&& mPayload[iv->id].stsAB[CH0])) {
|
||||
miComplete(iv);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void miComplete(Inverter<> *iv) {
|
||||
if ( mPayload[iv->id].complete ) // && iv->type != INV_TYPE_4CH)
|
||||
return; // if we got second message as well in repreated attempt
|
||||
return; //if we got second message as well in repreated attempt
|
||||
mPayload[iv->id].complete = true;
|
||||
DPRINT_IVID(DBG_INFO, iv->id);
|
||||
DBGPRINTLN(F("got all msgs"));
|
||||
|
@ -752,7 +752,7 @@ const byteAssign_t InfoAssignment[] = {
|
|||
iv->setQueuedCmdFinished();
|
||||
mStat->rxSuccess++;
|
||||
yield();
|
||||
notify(RealTimeRunData_Debug); //iv->type == INV_TYPE_4CH ? 0x36 : 0x09 );
|
||||
notify(RealTimeRunData_Debug, iv); //iv->type == INV_TYPE_4CH ? 0x36 : 0x09 );
|
||||
}
|
||||
|
||||
bool build(uint8_t id, bool *complete) {
|
||||
|
@ -826,6 +826,7 @@ const byteAssign_t InfoAssignment[] = {
|
|||
|
||||
IApp *mApp;
|
||||
HMSYSTEM *mSys;
|
||||
HMRADIO *mRadio;
|
||||
statistics_t *mStat;
|
||||
uint8_t mMaxRetrans;
|
||||
uint32_t *mTimestamp;
|
||||
|
|
437
src/hms/cmt2300a.h
Normal file
437
src/hms/cmt2300a.h
Normal file
|
@ -0,0 +1,437 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 2023 Ahoy, https://github.com/lumpapu/ahoy
|
||||
// Creative Commons - https://creativecommons.org/licenses/by-nc-sa/4.0/deed
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#ifndef __CMT2300A_H__
|
||||
#define __CMT2300A_H__
|
||||
|
||||
#include "esp32_3wSpi.h"
|
||||
|
||||
#define WORK_FREQ_KHZ 865000 // disired work frequency between DTU and
|
||||
// inverter in kHz
|
||||
#define HOY_BASE_FREQ_KHZ 860000 // in kHz
|
||||
#define HOY_MAX_FREQ_KHZ 923500 // 0xFE * 250kHz + Base_freq
|
||||
#define HOY_BOOT_FREQ_KHZ 868000 // Hoymiles boot/init frequency after power up inverter
|
||||
#define FREQ_STEP_KHZ 250 // channel step size in kHz
|
||||
#define FREQ_WARN_MIN_KHZ 863000 // for EU 863 - 870 MHz is allowed
|
||||
#define FREQ_WARN_MAX_KHZ 870000 // for EU 863 - 870 MHz is allowed
|
||||
|
||||
// detailed register infos from AN142_CMT2300AW_Quick_Start_Guide-Rev0.8.pdf
|
||||
|
||||
#define CMT2300A_MASK_CFG_RETAIN 0x10
|
||||
#define CMT2300A_MASK_RSTN_IN_EN 0x20
|
||||
#define CMT2300A_MASK_LOCKING_EN 0x20
|
||||
#define CMT2300A_MASK_CHIP_MODE_STA 0x0F
|
||||
|
||||
#define CMT2300A_CUS_CMT10 0x09
|
||||
|
||||
#define CMT2300A_CUS_MODE_CTL 0x60 // [7] go_switch
|
||||
// [6] go_tx
|
||||
// [5] go_tfs
|
||||
// [4] go_sleep
|
||||
// [3] go_rx
|
||||
// [2] go_rfs
|
||||
// [1] go_stby
|
||||
// [0] n/a
|
||||
|
||||
#define CMT2300A_CUS_MODE_STA 0x61 // [3:0] 0x00 IDLE
|
||||
// 0x01 SLEEP
|
||||
// 0x02 STBY
|
||||
// 0x03 RFS
|
||||
// 0x04 TFS
|
||||
// 0x05 RX
|
||||
// 0x06 TX
|
||||
// 0x08 UNLOCKED/LOW_VDD
|
||||
// 0x09 CAL
|
||||
#define CMT2300A_CUS_EN_CTL 0x62
|
||||
#define CMT2300A_CUS_FREQ_CHNL 0x63
|
||||
|
||||
#define CMT2300A_CUS_IO_SEL 0x65 // [5:4] GPIO3
|
||||
// 0x00 CLKO
|
||||
// 0x01 DOUT / DIN
|
||||
// 0x02 INT2
|
||||
// 0x03 DCLK
|
||||
// [3:2] GPIO2
|
||||
// 0x00 INT1
|
||||
// 0x01 INT2
|
||||
// 0x02 DOUT / DIN
|
||||
// 0x03 DCLK
|
||||
// [1:0] GPIO1
|
||||
// 0x00 DOUT / DIN
|
||||
// 0x01 INT1
|
||||
// 0x02 INT2
|
||||
// 0x03 DCLK
|
||||
|
||||
#define CMT2300A_CUS_INT1_CTL 0x66 // [4:0] INT1_SEL
|
||||
// 0x00 RX active
|
||||
// 0x01 TX active
|
||||
// 0x02 RSSI VLD
|
||||
// 0x03 Pream OK
|
||||
// 0x04 SYNC OK
|
||||
// 0x05 NODE OK
|
||||
// 0x06 CRC OK
|
||||
// 0x07 PKT OK
|
||||
// 0x08 SL TMO
|
||||
// 0x09 RX TMO
|
||||
// 0x0A TX DONE
|
||||
// 0x0B RX FIFO NMTY
|
||||
// 0x0C RX FIFO TH
|
||||
// 0x0D RX FIFO FULL
|
||||
// 0x0E RX FIFO WBYTE
|
||||
// 0x0F RX FIFO OVF
|
||||
// 0x10 TX FIFO NMTY
|
||||
// 0x11 TX FIFO TH
|
||||
// 0x12 TX FIFO FULL
|
||||
// 0x13 STATE IS STBY
|
||||
// 0x14 STATE IS FS
|
||||
// 0x15 STATE IS RX
|
||||
// 0x16 STATE IS TX
|
||||
// 0x17 LED
|
||||
// 0x18 TRX ACTIVE
|
||||
// 0x19 PKT DONE
|
||||
|
||||
#define CMT2300A_CUS_INT2_CTL 0x67 // [4:0] INT2_SEL
|
||||
|
||||
#define CMT2300A_CUS_INT_EN 0x68 // [7] SL TMO EN
|
||||
// [6] RX TMO EN
|
||||
// [5] TX DONE EN
|
||||
// [4] PREAM OK EN
|
||||
// [3] SYNC_OK EN
|
||||
// [2] NODE OK EN
|
||||
// [1] CRC OK EN
|
||||
// [0] PKT DONE EN
|
||||
|
||||
#define CMT2300A_CUS_FIFO_CTL 0x69 // [7] TX DIN EN
|
||||
// [6:5] TX DIN SEL
|
||||
// 0x00 SEL GPIO1
|
||||
// 0x01 SEL GPIO2
|
||||
// 0x02 SEL GPIO3
|
||||
// [4] FIFO AUTO CLR DIS
|
||||
// [3] FIFO TX RD EN
|
||||
// [2] FIFO RX TX SEL
|
||||
// [1] FIFO MERGE EN
|
||||
// [0] SPI FIFO RD WR SEL
|
||||
|
||||
#define CMT2300A_CUS_INT_CLR1 0x6A // clear interrupts Bank1
|
||||
#define CMT2300A_CUS_INT_CLR2 0x6B // clear interrupts Bank2
|
||||
#define CMT2300A_CUS_FIFO_CLR 0x6C
|
||||
|
||||
#define CMT2300A_CUS_INT_FLAG 0x6D // [7] LBD FLG
|
||||
// [6] COL ERR FLG
|
||||
// [5] PKT ERR FLG
|
||||
// [4] PREAM OK FLG
|
||||
// [3] SYNC OK FLG
|
||||
// [2] NODE OK FLG
|
||||
// [1] CRC OK FLG
|
||||
// [0] PKT OK FLG
|
||||
|
||||
#define CMT2300A_CUS_RSSI_DBM 0x70
|
||||
|
||||
#define CMT2300A_GO_SWITCH 0x80
|
||||
#define CMT2300A_GO_TX 0x40
|
||||
#define CMT2300A_GO_TFS 0x20
|
||||
#define CMT2300A_GO_SLEEP 0x10
|
||||
#define CMT2300A_GO_RX 0x08
|
||||
#define CMT2300A_GO_RFS 0x04
|
||||
#define CMT2300A_GO_STBY 0x02
|
||||
#define CMT2300A_GO_EEPROM 0x01
|
||||
|
||||
#define CMT2300A_STA_IDLE 0x00
|
||||
#define CMT2300A_STA_SLEEP 0x01
|
||||
#define CMT2300A_STA_STBY 0x02
|
||||
#define CMT2300A_STA_RFS 0x03
|
||||
#define CMT2300A_STA_TFS 0x04
|
||||
#define CMT2300A_STA_RX 0x05
|
||||
#define CMT2300A_STA_TX 0x06
|
||||
#define CMT2300A_STA_EEPROM 0x07
|
||||
#define CMT2300A_STA_ERROR 0x08
|
||||
#define CMT2300A_STA_CAL 0x09
|
||||
|
||||
#define CMT2300A_INT_SEL_TX_DONE 0x0A
|
||||
|
||||
#define CMT2300A_MASK_TX_DONE_FLG 0x08
|
||||
#define CMT2300A_MASK_PKT_OK_FLG 0x01
|
||||
|
||||
// default CMT paramters
|
||||
static uint8_t cmtConfig[0x60] PROGMEM {
|
||||
// 0x00 - 0x0f -- RSSI offset +- 0 and 13dBm
|
||||
0x00, 0x66, 0xEC, 0x1C, 0x70, 0x80, 0x14, 0x08,
|
||||
0x11, 0x02, 0x02, 0x00, 0xAE, 0xE0, 0x35, 0x00,
|
||||
// 0x10 - 0x1f
|
||||
0x00, 0xF4, 0x10, 0xE2, 0x42, 0x20, 0x0C, 0x81,
|
||||
0x42, 0x32, 0xCF, 0x82, 0x42, 0x27, 0x76, 0x12, // 860MHz as default
|
||||
// 0x20 - 0x2f
|
||||
0xA6, 0xC9, 0x20, 0x20, 0xD2, 0x35, 0x0C, 0x0A,
|
||||
0x9F, 0x4B, 0x29, 0x29, 0xC0, 0x14, 0x05, 0x53,
|
||||
// 0x30 - 0x3f
|
||||
0x10, 0x00, 0xB4, 0x00, 0x00, 0x01, 0x00, 0x00,
|
||||
0x12, 0x1E, 0x00, 0xAA, 0x06, 0x00, 0x00, 0x00,
|
||||
// 0x40 - 0x4f
|
||||
0x00, 0x48, 0x5A, 0x48, 0x4D, 0x01, 0x1D, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0xC3, 0x00, 0x00, 0x60,
|
||||
// 0x50 - 0x5f
|
||||
0xFF, 0x00, 0x00, 0x1F, 0x10, 0x70, 0x4D, 0x06,
|
||||
0x00, 0x07, 0x50, 0x00, 0x42, 0x0C, 0x3F, 0x7F // - TX 13dBm
|
||||
};
|
||||
|
||||
|
||||
enum {CMT_SUCCESS = 0, CMT_ERR_SWITCH_STATE, CMT_ERR_TX_PENDING, CMT_FIFO_EMPTY, CMT_ERR_RX_IN_FIFO};
|
||||
|
||||
template<class SPI>
|
||||
class Cmt2300a {
|
||||
typedef SPI SpiType;
|
||||
public:
|
||||
Cmt2300a() {}
|
||||
|
||||
void setup(uint8_t pinCsb, uint8_t pinFcsb) {
|
||||
mSpi.setup(pinCsb, pinFcsb);
|
||||
init();
|
||||
}
|
||||
|
||||
void setup() {
|
||||
mSpi.setup();
|
||||
init();
|
||||
}
|
||||
|
||||
// call as often as possible
|
||||
void loop() {
|
||||
if(mTxPending) {
|
||||
if(CMT2300A_MASK_TX_DONE_FLG == mSpi.readReg(CMT2300A_CUS_INT_CLR1)) {
|
||||
if(cmtSwitchStatus(CMT2300A_GO_STBY, CMT2300A_STA_STBY)) {
|
||||
mTxPending = false;
|
||||
goRx();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t goRx(void) {
|
||||
if(mTxPending)
|
||||
return CMT_ERR_TX_PENDING;
|
||||
|
||||
if(mInRxMode)
|
||||
return CMT_SUCCESS;
|
||||
|
||||
mSpi.readReg(CMT2300A_CUS_INT1_CTL);
|
||||
mSpi.writeReg(CMT2300A_CUS_INT1_CTL, CMT2300A_INT_SEL_TX_DONE);
|
||||
|
||||
uint8_t tmp = mSpi.readReg(CMT2300A_CUS_INT_CLR1);
|
||||
if(0x08 == tmp) // first time after TX a value of 0x08 is read
|
||||
mSpi.writeReg(CMT2300A_CUS_INT_CLR1, 0x04);
|
||||
else
|
||||
mSpi.writeReg(CMT2300A_CUS_INT_CLR1, 0x00);
|
||||
|
||||
if(0x10 == tmp)
|
||||
mSpi.writeReg(CMT2300A_CUS_INT_CLR2, 0x10);
|
||||
else
|
||||
mSpi.writeReg(CMT2300A_CUS_INT_CLR2, 0x00);
|
||||
|
||||
mSpi.writeReg(CMT2300A_CUS_FIFO_CTL, 0x02);
|
||||
mSpi.writeReg(CMT2300A_CUS_FIFO_CLR, 0x02);
|
||||
mSpi.writeReg(0x16, 0x0C); // [4:3]: RSSI_DET_SEL, [2:0]: RSSI_AVG_MODE
|
||||
|
||||
if(!cmtSwitchStatus(CMT2300A_GO_RX, CMT2300A_STA_RX))
|
||||
return CMT_ERR_SWITCH_STATE;
|
||||
|
||||
mInRxMode = true;
|
||||
|
||||
return CMT_SUCCESS;
|
||||
}
|
||||
|
||||
uint8_t getRx(uint8_t buf[], uint8_t len, int8_t *rssi) {
|
||||
if(mTxPending)
|
||||
return CMT_ERR_TX_PENDING;
|
||||
|
||||
if(0x1b != (mSpi.readReg(CMT2300A_CUS_INT_FLAG) & 0x1b))
|
||||
return CMT_FIFO_EMPTY;
|
||||
|
||||
// receive ok (pream, sync, node, crc)
|
||||
if(!cmtSwitchStatus(CMT2300A_GO_STBY, CMT2300A_STA_STBY))
|
||||
return CMT_ERR_SWITCH_STATE;
|
||||
|
||||
mSpi.readFifo(buf, len);
|
||||
*rssi = mSpi.readReg(CMT2300A_CUS_RSSI_DBM) - 128;
|
||||
|
||||
if(!cmtSwitchStatus(CMT2300A_GO_SLEEP, CMT2300A_STA_SLEEP))
|
||||
return CMT_ERR_SWITCH_STATE;
|
||||
|
||||
if(!cmtSwitchStatus(CMT2300A_GO_STBY, CMT2300A_STA_STBY))
|
||||
return CMT_ERR_SWITCH_STATE;
|
||||
|
||||
mInRxMode = false;
|
||||
mCusIntFlag = mSpi.readReg(CMT2300A_CUS_INT_FLAG);
|
||||
|
||||
return CMT_SUCCESS;
|
||||
}
|
||||
|
||||
uint8_t tx(uint8_t buf[], uint8_t len) {
|
||||
if(mTxPending)
|
||||
return CMT_ERR_TX_PENDING;
|
||||
|
||||
if(mInRxMode) {
|
||||
mInRxMode = false;
|
||||
if(!cmtSwitchStatus(CMT2300A_GO_STBY, CMT2300A_STA_STBY))
|
||||
return CMT_ERR_SWITCH_STATE;
|
||||
}
|
||||
|
||||
mSpi.writeReg(CMT2300A_CUS_INT1_CTL, CMT2300A_INT_SEL_TX_DONE);
|
||||
|
||||
// no data received
|
||||
mSpi.readReg(CMT2300A_CUS_INT_CLR1);
|
||||
mSpi.writeReg(CMT2300A_CUS_INT_CLR1, 0x00);
|
||||
mSpi.writeReg(CMT2300A_CUS_INT_CLR2, 0x00);
|
||||
|
||||
mSpi.writeReg(CMT2300A_CUS_FIFO_CTL, 0x07);
|
||||
mSpi.writeReg(CMT2300A_CUS_FIFO_CLR, 0x01);
|
||||
|
||||
mSpi.writeReg(0x45, 0x01);
|
||||
mSpi.writeReg(0x46, len); // payload length
|
||||
|
||||
mSpi.writeFifo(buf, len);
|
||||
|
||||
if(0xff != mRqstCh) {
|
||||
mCurCh = mRqstCh;
|
||||
mRqstCh = 0xff;
|
||||
mSpi.writeReg(CMT2300A_CUS_FREQ_CHNL, mCurCh);
|
||||
}
|
||||
|
||||
if(!cmtSwitchStatus(CMT2300A_GO_TX, CMT2300A_STA_TX))
|
||||
return CMT_ERR_SWITCH_STATE;
|
||||
|
||||
// wait for tx done
|
||||
mTxPending = true;
|
||||
|
||||
return CMT_SUCCESS;
|
||||
}
|
||||
|
||||
// initialize CMT2300A, returns true on success
|
||||
bool reset(void) {
|
||||
mSpi.writeReg(0x7f, 0xff); // soft reset
|
||||
delay(30);
|
||||
|
||||
if(!cmtSwitchStatus(CMT2300A_GO_STBY, CMT2300A_STA_STBY))
|
||||
return false;
|
||||
|
||||
mSpi.writeReg(CMT2300A_CUS_MODE_STA, 0x52);
|
||||
mSpi.writeReg(0x62, 0x20);
|
||||
|
||||
for(uint8_t i = 0; i < 0x60; i++) {
|
||||
mSpi.writeReg(i, cmtConfig[i]);
|
||||
}
|
||||
|
||||
|
||||
mSpi.writeReg(CMT2300A_CUS_IO_SEL, 0x20); // -> GPIO3_SEL[1:0] = 0x02
|
||||
|
||||
// interrupt 1 control selection to TX DONE
|
||||
if(CMT2300A_INT_SEL_TX_DONE != mSpi.readReg(CMT2300A_CUS_INT1_CTL))
|
||||
mSpi.writeReg(CMT2300A_CUS_INT1_CTL, CMT2300A_INT_SEL_TX_DONE);
|
||||
|
||||
// select interrupt 2
|
||||
if(0x07 != mSpi.readReg(CMT2300A_CUS_INT2_CTL))
|
||||
mSpi.writeReg(CMT2300A_CUS_INT2_CTL, 0x07);
|
||||
|
||||
// interrupt enable (TX_DONE, PREAM_OK, SYNC_OK, CRC_OK, PKT_DONE)
|
||||
mSpi.writeReg(CMT2300A_CUS_INT_EN, 0x3B);
|
||||
|
||||
mSpi.writeReg(0x64, 0x64);
|
||||
|
||||
if(0x00 == mSpi.readReg(CMT2300A_CUS_FIFO_CTL))
|
||||
mSpi.writeReg(CMT2300A_CUS_FIFO_CTL, 0x02); // FIFO_MERGE_EN
|
||||
|
||||
if(!cmtSwitchStatus(CMT2300A_GO_SLEEP, CMT2300A_STA_SLEEP))
|
||||
return false;
|
||||
|
||||
delayMicroseconds(95);
|
||||
|
||||
if(!cmtSwitchStatus(CMT2300A_GO_STBY, CMT2300A_STA_STBY))
|
||||
return false;
|
||||
|
||||
if(!cmtSwitchStatus(CMT2300A_GO_SLEEP, CMT2300A_STA_SLEEP))
|
||||
return false;
|
||||
|
||||
if(!cmtSwitchStatus(CMT2300A_GO_STBY, CMT2300A_STA_STBY))
|
||||
return false;
|
||||
|
||||
//switchDtuFreq(WORK_FREQ_KHZ);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
inline uint8_t freq2Chan(const uint32_t freqKhz) {
|
||||
if((freqKhz % FREQ_STEP_KHZ) != 0) {
|
||||
DPRINT(DBG_WARN, F("swtich frequency to "));
|
||||
DBGPRINT(String(freqKhz));
|
||||
DBGPRINT(F("kHz not possible!"));
|
||||
return 0xff; // error
|
||||
// apply the nearest frequency
|
||||
//freqKhz = (freqKhz + FREQ_STEP_KHZ/2) / FREQ_STEP_KHZ;
|
||||
//freqKhz *= FREQ_STEP_KHZ;
|
||||
}
|
||||
|
||||
if((freqKhz < HOY_BASE_FREQ_KHZ) || (freqKhz > HOY_MAX_FREQ_KHZ))
|
||||
return 0xff; // error
|
||||
|
||||
if((freqKhz < FREQ_WARN_MIN_KHZ) || (freqKhz > FREQ_WARN_MAX_KHZ))
|
||||
DPRINTLN(DBG_WARN, F("Disired frequency is out of EU legal range! (863 - 870MHz)"));
|
||||
|
||||
return (freqKhz - HOY_BASE_FREQ_KHZ) / FREQ_STEP_KHZ;
|
||||
}
|
||||
|
||||
inline void switchChannel(uint8_t ch) {
|
||||
mRqstCh = ch;
|
||||
}
|
||||
|
||||
inline uint32_t getFreqKhz(void) {
|
||||
if(0xff != mRqstCh)
|
||||
return HOY_BASE_FREQ_KHZ + (mRqstCh * FREQ_STEP_KHZ);
|
||||
else
|
||||
return HOY_BASE_FREQ_KHZ + (mCurCh * FREQ_STEP_KHZ);
|
||||
}
|
||||
|
||||
private:
|
||||
void init() {
|
||||
mTxPending = false;
|
||||
mInRxMode = false;
|
||||
mCusIntFlag = 0x00;
|
||||
mCnt = 0;
|
||||
mRqstCh = 0xff;
|
||||
mCurCh = 0x20;
|
||||
}
|
||||
|
||||
// CMT state machine, wait for next state, true on success
|
||||
bool cmtSwitchStatus(uint8_t cmd, uint8_t waitFor, uint16_t cycles = 40) {
|
||||
mSpi.writeReg(CMT2300A_CUS_MODE_CTL, cmd);
|
||||
while(cycles--) {
|
||||
yield();
|
||||
delayMicroseconds(10);
|
||||
if(waitFor == (getChipStatus() & waitFor))
|
||||
return true;
|
||||
}
|
||||
//Serial.println("status wait for: " + String(waitFor, HEX) + " read: " + String(getChipStatus(), HEX));
|
||||
return false;
|
||||
}
|
||||
|
||||
inline bool switchDtuFreq(const uint32_t freqKhz) {
|
||||
uint8_t toCh = freq2Chan(freqKhz);
|
||||
if(0xff == toCh)
|
||||
return false;
|
||||
|
||||
switchChannel(toCh);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
inline uint8_t getChipStatus(void) {
|
||||
return mSpi.readReg(CMT2300A_CUS_MODE_STA) & CMT2300A_MASK_CHIP_MODE_STA;
|
||||
}
|
||||
|
||||
SpiType mSpi;
|
||||
uint8_t mCnt;
|
||||
bool mTxPending;
|
||||
bool mInRxMode;
|
||||
uint8_t mCusIntFlag;
|
||||
uint8_t mRqstCh, mCurCh;
|
||||
};
|
||||
|
||||
#endif /*__CMT2300A_H__*/
|
189
src/hms/esp32_3wSpi.h
Normal file
189
src/hms/esp32_3wSpi.h
Normal file
|
@ -0,0 +1,189 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 2023 Ahoy, https://github.com/lumpapu/ahoy
|
||||
// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#ifndef __ESP32_3WSPI_H__
|
||||
#define __ESP32_3WSPI_H__
|
||||
|
||||
#include "Arduino.h"
|
||||
#if defined(ESP32)
|
||||
#include "driver/spi_master.h"
|
||||
#include "esp_rom_gpio.h" // for esp_rom_gpio_connect_out_signal
|
||||
|
||||
#if CONFIG_IDF_TARGET_ESP32S3
|
||||
#define CLK_PIN 6
|
||||
#define MOSI_PIN 5
|
||||
#else
|
||||
#define CLK_PIN 18
|
||||
#define MOSI_PIN 23
|
||||
#endif
|
||||
|
||||
#define SPI_CLK 1 * 1000 * 1000 // 1MHz
|
||||
|
||||
#define SPI_PARAM_LOCK() \
|
||||
do { \
|
||||
} while (xSemaphoreTake(paramLock, portMAX_DELAY) != pdPASS)
|
||||
#define SPI_PARAM_UNLOCK() xSemaphoreGive(paramLock)
|
||||
|
||||
// for ESP32 this is the so-called HSPI
|
||||
// for ESP32-S2/S3/C3 this nomenclature does not really exist anymore,
|
||||
// it is simply the first externally usable hardware SPI master controller
|
||||
#define SPI_CMT SPI2_HOST
|
||||
|
||||
template<uint8_t CSB_PIN=5, uint8_t FCSB_PIN=4> //, uint8_t GPIO3_PIN=15>
|
||||
class esp32_3wSpi {
|
||||
public:
|
||||
esp32_3wSpi() {
|
||||
mInitialized = false;
|
||||
}
|
||||
|
||||
void setup(uint8_t pinCsb = CSB_PIN, uint8_t pinFcsb = FCSB_PIN) { //, uint8_t pinGpio3 = GPIO3_PIN) {
|
||||
paramLock = xSemaphoreCreateMutex();
|
||||
spi_bus_config_t buscfg = {
|
||||
.mosi_io_num = MOSI_PIN,
|
||||
.miso_io_num = -1, // single wire MOSI/MISO
|
||||
.sclk_io_num = CLK_PIN,
|
||||
.quadwp_io_num = -1,
|
||||
.quadhd_io_num = -1,
|
||||
.max_transfer_sz = 32,
|
||||
};
|
||||
spi_device_interface_config_t devcfg = {
|
||||
.command_bits = 1,
|
||||
.address_bits = 7,
|
||||
.dummy_bits = 0,
|
||||
.mode = 0,
|
||||
.cs_ena_pretrans = 1,
|
||||
.cs_ena_posttrans = 1,
|
||||
.clock_speed_hz = SPI_CLK,
|
||||
.spics_io_num = pinCsb,
|
||||
.flags = SPI_DEVICE_HALFDUPLEX | SPI_DEVICE_3WIRE,
|
||||
.queue_size = 1,
|
||||
.pre_cb = NULL,
|
||||
.post_cb = NULL,
|
||||
};
|
||||
|
||||
ESP_ERROR_CHECK(spi_bus_initialize(SPI_CMT, &buscfg, SPI_DMA_DISABLED));
|
||||
ESP_ERROR_CHECK(spi_bus_add_device(SPI_CMT, &devcfg, &spi_reg));
|
||||
|
||||
// FiFo
|
||||
spi_device_interface_config_t devcfg2 = {
|
||||
.command_bits = 0,
|
||||
.address_bits = 0,
|
||||
.dummy_bits = 0,
|
||||
.mode = 0,
|
||||
.cs_ena_pretrans = 2,
|
||||
.cs_ena_posttrans = (uint8_t)(1 / (SPI_CLK * 10e6 * 2) + 2), // >2 us
|
||||
.clock_speed_hz = SPI_CLK,
|
||||
.spics_io_num = pinFcsb,
|
||||
.flags = SPI_DEVICE_HALFDUPLEX | SPI_DEVICE_3WIRE,
|
||||
.queue_size = 1,
|
||||
.pre_cb = NULL,
|
||||
.post_cb = NULL,
|
||||
};
|
||||
ESP_ERROR_CHECK(spi_bus_add_device(SPI_CMT, &devcfg2, &spi_fifo));
|
||||
|
||||
esp_rom_gpio_connect_out_signal(MOSI_PIN, spi_periph_signal[SPI_CMT].spid_out, true, false);
|
||||
delay(100);
|
||||
|
||||
//pinMode(pinGpio3, INPUT);
|
||||
mInitialized = true;
|
||||
}
|
||||
|
||||
void writeReg(uint8_t addr, uint8_t reg) {
|
||||
if(!mInitialized)
|
||||
return;
|
||||
|
||||
uint8_t tx_data;
|
||||
tx_data = ~reg;
|
||||
spi_transaction_t t = {
|
||||
.cmd = 1,
|
||||
.addr = (uint64_t)(~addr),
|
||||
.length = 8,
|
||||
.tx_buffer = &tx_data,
|
||||
.rx_buffer = NULL
|
||||
};
|
||||
SPI_PARAM_LOCK();
|
||||
ESP_ERROR_CHECK(spi_device_polling_transmit(spi_reg, &t));
|
||||
SPI_PARAM_UNLOCK();
|
||||
delayMicroseconds(100);
|
||||
}
|
||||
|
||||
uint8_t readReg(uint8_t addr) {
|
||||
if(!mInitialized)
|
||||
return 0;
|
||||
|
||||
uint8_t rx_data;
|
||||
spi_transaction_t t = {
|
||||
.cmd = 0,
|
||||
.addr = (uint64_t)(~addr),
|
||||
.length = 8,
|
||||
.rxlength = 8,
|
||||
.tx_buffer = NULL,
|
||||
.rx_buffer = &rx_data
|
||||
};
|
||||
|
||||
SPI_PARAM_LOCK();
|
||||
ESP_ERROR_CHECK(spi_device_polling_transmit(spi_reg, &t));
|
||||
SPI_PARAM_UNLOCK();
|
||||
delayMicroseconds(100);
|
||||
return rx_data;
|
||||
}
|
||||
|
||||
void writeFifo(uint8_t buf[], uint8_t len) {
|
||||
if(!mInitialized)
|
||||
return;
|
||||
uint8_t tx_data;
|
||||
|
||||
spi_transaction_t t = {
|
||||
.length = 8,
|
||||
.tx_buffer = &tx_data, // reference to write data
|
||||
.rx_buffer = NULL
|
||||
};
|
||||
|
||||
SPI_PARAM_LOCK();
|
||||
for(uint8_t i = 0; i < len; i++) {
|
||||
tx_data = ~buf[i]; // negate buffer contents
|
||||
ESP_ERROR_CHECK(spi_device_polling_transmit(spi_fifo, &t));
|
||||
delayMicroseconds(4); // > 4 us
|
||||
}
|
||||
SPI_PARAM_UNLOCK();
|
||||
}
|
||||
|
||||
void readFifo(uint8_t buf[], uint8_t len) {
|
||||
if(!mInitialized)
|
||||
return;
|
||||
uint8_t rx_data;
|
||||
|
||||
spi_transaction_t t = {
|
||||
.length = 8,
|
||||
.rxlength = 8,
|
||||
.tx_buffer = NULL,
|
||||
.rx_buffer = &rx_data
|
||||
};
|
||||
|
||||
SPI_PARAM_LOCK();
|
||||
for(uint8_t i = 0; i < len; i++) {
|
||||
ESP_ERROR_CHECK(spi_device_polling_transmit(spi_fifo, &t));
|
||||
delayMicroseconds(4); // > 4 us
|
||||
buf[i] = rx_data;
|
||||
}
|
||||
SPI_PARAM_UNLOCK();
|
||||
}
|
||||
|
||||
private:
|
||||
spi_device_handle_t spi_reg, spi_fifo;
|
||||
bool mInitialized;
|
||||
SemaphoreHandle_t paramLock = NULL;
|
||||
};
|
||||
#else
|
||||
template<uint8_t CSB_PIN=5, uint8_t FCSB_PIN=4>
|
||||
class esp32_3wSpi {
|
||||
public:
|
||||
esp32_3wSpi() {}
|
||||
void setup() {}
|
||||
void loop() {}
|
||||
};
|
||||
#endif
|
||||
|
||||
#endif /*__ESP32_3WSPI_H__*/
|
190
src/hms/hmsDefines.h
Normal file
190
src/hms/hmsDefines.h
Normal file
|
@ -0,0 +1,190 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 2023 Ahoy, https://github.com/lumpapu/ahoy
|
||||
// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#ifndef __HMS_DEFINES_H__
|
||||
#define __HMS_DEFINES_H__
|
||||
|
||||
#include "../hm/hmDefines.h"
|
||||
|
||||
//-------------------------------------
|
||||
// HMS-350, HMS-500
|
||||
//-------------------------------------
|
||||
const byteAssign_t hms1chAssignment[] = {
|
||||
{ FLD_UDC, UNIT_V, CH1, 2, 2, 10 },
|
||||
{ FLD_IDC, UNIT_A, CH1, 4, 2, 100 },
|
||||
{ FLD_PDC, UNIT_W, CH1, 6, 2, 10 },
|
||||
{ FLD_YT, UNIT_KWH, CH1, 8, 4, 1000 },
|
||||
{ FLD_YD, UNIT_WH, CH1, 12, 2, 1 },
|
||||
{ FLD_IRR, UNIT_PCT, CH1, CALC_IRR_CH, CH1, CMD_CALC },
|
||||
|
||||
{ FLD_UAC, UNIT_V, CH0, 14, 2, 10 },
|
||||
{ FLD_F, UNIT_HZ, CH0, 16, 2, 100 },
|
||||
{ FLD_PAC, UNIT_W, CH0, 18, 2, 10 },
|
||||
{ FLD_Q, UNIT_VAR, CH0, 20, 2, 10 }, // signed!
|
||||
{ FLD_IAC, UNIT_A, CH0, 22, 2, 100 },
|
||||
{ FLD_PF, UNIT_NONE, CH0, 24, 2, 1000 }, // signed!
|
||||
{ FLD_T, UNIT_C, CH0, 26, 2, 10 }, // signed!
|
||||
{ FLD_EVT, UNIT_NONE, CH0, 28, 2, 1 },
|
||||
|
||||
{ FLD_YD, UNIT_WH, CH0, CALC_YD_CH0, 0, CMD_CALC },
|
||||
{ FLD_YT, UNIT_KWH, CH0, CALC_YT_CH0, 0, CMD_CALC },
|
||||
{ FLD_PDC, UNIT_W, CH0, CALC_PDC_CH0, 0, CMD_CALC },
|
||||
{ FLD_EFF, UNIT_PCT, CH0, CALC_EFF_CH0, 0, CMD_CALC }
|
||||
};
|
||||
#define HMS1CH_LIST_LEN (sizeof(hms1chAssignment) / sizeof(byteAssign_t))
|
||||
#define HMS1CH_PAYLOAD_LEN 30
|
||||
|
||||
//-------------------------------------
|
||||
// HMS-800, HMS-1000
|
||||
//-------------------------------------
|
||||
const byteAssign_t hms2chAssignment[] = {
|
||||
{ FLD_UDC, UNIT_V, CH1, 2, 2, 10 },
|
||||
{ FLD_IDC, UNIT_A, CH1, 6, 2, 100 },
|
||||
{ FLD_PDC, UNIT_W, CH1, 10, 2, 10 },
|
||||
{ FLD_YT, UNIT_KWH, CH1, 14, 4, 1000 },
|
||||
{ FLD_YD, UNIT_WH, CH1, 22, 2, 1 },
|
||||
{ FLD_IRR, UNIT_PCT, CH1, CALC_IRR_CH, CH1, CMD_CALC },
|
||||
|
||||
{ FLD_UDC, UNIT_V, CH2, 4, 2, 10 },
|
||||
{ FLD_IDC, UNIT_A, CH2, 8, 2, 100 },
|
||||
{ FLD_PDC, UNIT_W, CH2, 12, 2, 10 },
|
||||
{ FLD_YT, UNIT_KWH, CH2, 18, 4, 1000 },
|
||||
{ FLD_YD, UNIT_WH, CH2, 24, 2, 1 },
|
||||
{ FLD_IRR, UNIT_PCT, CH2, CALC_IRR_CH, CH2, CMD_CALC },
|
||||
|
||||
{ FLD_UAC, UNIT_V, CH0, 26, 2, 10 },
|
||||
{ FLD_F, UNIT_HZ, CH0, 28, 2, 100 },
|
||||
{ FLD_PAC, UNIT_W, CH0, 30, 2, 10 },
|
||||
{ FLD_Q, UNIT_VAR, CH0, 32, 2, 10 }, // signed!
|
||||
{ FLD_IAC, UNIT_A, CH0, 34, 2, 100 },
|
||||
{ FLD_PF, UNIT_NONE, CH0, 36, 2, 1000 }, // signed!
|
||||
{ FLD_T, UNIT_C, CH0, 38, 2, 10 }, // signed!
|
||||
{ FLD_EVT, UNIT_NONE, CH0, 40, 2, 1 },
|
||||
{ FLD_YD, UNIT_WH, CH0, CALC_YD_CH0, 0, CMD_CALC },
|
||||
{ FLD_YT, UNIT_KWH, CH0, CALC_YT_CH0, 0, CMD_CALC },
|
||||
{ FLD_PDC, UNIT_W, CH0, CALC_PDC_CH0, 0, CMD_CALC },
|
||||
{ FLD_EFF, UNIT_PCT, CH0, CALC_EFF_CH0, 0, CMD_CALC }
|
||||
};
|
||||
#define HMS2CH_LIST_LEN (sizeof(hms2chAssignment) / sizeof(byteAssign_t))
|
||||
#define HMS2CH_PAYLOAD_LEN 42
|
||||
|
||||
//-------------------------------------
|
||||
// HMS-1800, HMS-2000
|
||||
//-------------------------------------
|
||||
const byteAssign_t hms4chAssignment[] = {
|
||||
{ FLD_UDC, UNIT_V, CH1, 2, 2, 10 },
|
||||
{ FLD_IDC, UNIT_A, CH1, 6, 2, 100 },
|
||||
{ FLD_PDC, UNIT_W, CH1, 10, 2, 10 },
|
||||
{ FLD_YT, UNIT_KWH, CH1, 14, 4, 1000 },
|
||||
{ FLD_YD, UNIT_WH, CH1, 22, 2, 1 },
|
||||
{ FLD_IRR, UNIT_PCT, CH1, CALC_IRR_CH, CH1, CMD_CALC },
|
||||
|
||||
{ FLD_UDC, UNIT_V, CH2, 4, 2, 10 },
|
||||
{ FLD_IDC, UNIT_A, CH2, 8, 2, 100 },
|
||||
{ FLD_PDC, UNIT_W, CH2, 12, 2, 10 },
|
||||
{ FLD_YT, UNIT_KWH, CH2, 18, 4, 1000 },
|
||||
{ FLD_YD, UNIT_WH, CH2, 24, 2, 1 },
|
||||
{ FLD_IRR, UNIT_PCT, CH2, CALC_IRR_CH, CH2, CMD_CALC },
|
||||
|
||||
{ FLD_UDC, UNIT_V, CH3, 26, 2, 10 },
|
||||
{ FLD_IDC, UNIT_A, CH3, 30, 2, 100 },
|
||||
{ FLD_PDC, UNIT_W, CH3, 34, 2, 10 },
|
||||
{ FLD_YT, UNIT_KWH, CH3, 38, 4, 1000 },
|
||||
{ FLD_YD, UNIT_WH, CH3, 46, 2, 1 },
|
||||
{ FLD_IRR, UNIT_PCT, CH3, CALC_IRR_CH, CH3, CMD_CALC },
|
||||
|
||||
{ FLD_UDC, UNIT_V, CH4, 28, 2, 10 },
|
||||
{ FLD_IDC, UNIT_A, CH4, 32, 2, 100 },
|
||||
{ FLD_PDC, UNIT_W, CH4, 36, 2, 10 },
|
||||
{ FLD_YT, UNIT_KWH, CH4, 42, 4, 1000 },
|
||||
{ FLD_YD, UNIT_WH, CH4, 48, 2, 1 },
|
||||
{ FLD_IRR, UNIT_PCT, CH4, CALC_IRR_CH, CH4, CMD_CALC },
|
||||
|
||||
{ FLD_UAC, UNIT_V, CH0, 50, 2, 10 },
|
||||
{ FLD_F, UNIT_HZ, CH0, 52, 2, 100 },
|
||||
{ FLD_PAC, UNIT_W, CH0, 54, 2, 10 },
|
||||
{ FLD_Q, UNIT_VAR, CH0, 56, 2, 10 }, // signed!
|
||||
{ FLD_IAC, UNIT_A, CH0, 58, 2, 100 },
|
||||
{ FLD_PF, UNIT_NONE, CH0, 60, 2, 1000 }, // signed!
|
||||
{ FLD_T, UNIT_C, CH0, 62, 2, 10 }, // signed!
|
||||
{ FLD_EVT, UNIT_NONE, CH0, 64, 2, 1 },
|
||||
{ FLD_YD, UNIT_WH, CH0, CALC_YD_CH0, 0, CMD_CALC },
|
||||
{ FLD_YT, UNIT_KWH, CH0, CALC_YT_CH0, 0, CMD_CALC },
|
||||
{ FLD_PDC, UNIT_W, CH0, CALC_PDC_CH0, 0, CMD_CALC },
|
||||
{ FLD_EFF, UNIT_PCT, CH0, CALC_EFF_CH0, 0, CMD_CALC }
|
||||
};
|
||||
#define HMS4CH_LIST_LEN (sizeof(hms4chAssignment) / sizeof(byteAssign_t))
|
||||
#define HMS4CH_PAYLOAD_LEN 66
|
||||
|
||||
//-------------------------------------
|
||||
// HMT-1800, HMT-2250
|
||||
//-------------------------------------
|
||||
const byteAssign_t hmt6chAssignment[] = {
|
||||
{ FLD_UDC, UNIT_V, CH1, 2, 2, 10 },
|
||||
{ FLD_IDC, UNIT_A, CH1, 4, 2, 100 },
|
||||
{ FLD_PDC, UNIT_W, CH1, 8, 2, 10 },
|
||||
{ FLD_YT, UNIT_KWH, CH1, 12, 4, 1000 },
|
||||
{ FLD_YD, UNIT_WH, CH1, 20, 2, 1 },
|
||||
{ FLD_IRR, UNIT_PCT, CH1, CALC_IRR_CH, CH1, CMD_CALC },
|
||||
|
||||
{ FLD_UDC, UNIT_V, CH2, CALC_UDC_CH, CH1, CMD_CALC },
|
||||
{ FLD_IDC, UNIT_A, CH2, 6, 2, 100 },
|
||||
{ FLD_PDC, UNIT_W, CH2, 10, 2, 10 },
|
||||
{ FLD_YT, UNIT_KWH, CH2, 16, 4, 1000 },
|
||||
{ FLD_YD, UNIT_WH, CH2, 22, 2, 1 },
|
||||
{ FLD_IRR, UNIT_PCT, CH2, CALC_IRR_CH, CH2, CMD_CALC },
|
||||
|
||||
{ FLD_UDC, UNIT_V, CH3, 24, 2, 10 },
|
||||
{ FLD_IDC, UNIT_A, CH3, 26, 2, 100 },
|
||||
{ FLD_PDC, UNIT_W, CH3, 30, 2, 10 },
|
||||
{ FLD_YT, UNIT_KWH, CH3, 34, 4, 1000 },
|
||||
{ FLD_YD, UNIT_WH, CH3, 42, 2, 1 },
|
||||
{ FLD_IRR, UNIT_PCT, CH3, CALC_IRR_CH, CH3, CMD_CALC },
|
||||
|
||||
{ FLD_UDC, UNIT_V, CH4, CALC_UDC_CH, CH3, CMD_CALC },
|
||||
{ FLD_IDC, UNIT_A, CH4, 28, 2, 100 },
|
||||
{ FLD_PDC, UNIT_W, CH4, 32, 2, 10 },
|
||||
{ FLD_YT, UNIT_KWH, CH4, 38, 4, 1000 },
|
||||
{ FLD_YD, UNIT_WH, CH4, 44, 2, 1 },
|
||||
{ FLD_IRR, UNIT_PCT, CH4, CALC_IRR_CH, CH4, CMD_CALC },
|
||||
|
||||
{ FLD_UDC, UNIT_V, CH5, 46, 2, 10 },
|
||||
{ FLD_IDC, UNIT_A, CH5, 48, 2, 100 },
|
||||
{ FLD_PDC, UNIT_W, CH5, 52, 2, 10 },
|
||||
{ FLD_YT, UNIT_KWH, CH5, 56, 4, 1000 },
|
||||
{ FLD_YD, UNIT_WH, CH5, 64, 2, 1 },
|
||||
{ FLD_IRR, UNIT_PCT, CH5, CALC_IRR_CH, CH5, CMD_CALC },
|
||||
|
||||
{ FLD_UDC, UNIT_V, CH6, CALC_UDC_CH, CH5, CMD_CALC },
|
||||
{ FLD_IDC, UNIT_A, CH6, 50, 2, 100 },
|
||||
{ FLD_PDC, UNIT_W, CH6, 54, 2, 10 },
|
||||
{ FLD_YT, UNIT_KWH, CH6, 60, 4, 1000 },
|
||||
{ FLD_YD, UNIT_WH, CH6, 66, 2, 1 },
|
||||
{ FLD_IRR, UNIT_PCT, CH6, CALC_IRR_CH, CH6, CMD_CALC },
|
||||
|
||||
{ FLD_UAC_1N, UNIT_V, CH0, 68, 2, 10 },
|
||||
{ FLD_UAC_2N, UNIT_V, CH0, 70, 2, 10 },
|
||||
{ FLD_UAC_3N, UNIT_V, CH0, 72, 2, 10 },
|
||||
{ FLD_UAC_12, UNIT_V, CH0, 74, 2, 10 },
|
||||
{ FLD_UAC_23, UNIT_V, CH0, 76, 2, 10 },
|
||||
{ FLD_UAC_31, UNIT_V, CH0, 78, 2, 10 },
|
||||
{ FLD_F, UNIT_HZ, CH0, 80, 2, 100 },
|
||||
{ FLD_PAC, UNIT_W, CH0, 82, 2, 10 },
|
||||
{ FLD_Q, UNIT_VAR, CH0, 84, 2, 10 },
|
||||
{ FLD_IAC_1, UNIT_A, CH0, 86, 2, 100 },
|
||||
{ FLD_IAC_2, UNIT_A, CH0, 88, 2, 100 },
|
||||
{ FLD_IAC_3, UNIT_A, CH0, 90, 2, 100 },
|
||||
{ FLD_PF, UNIT_NONE, CH0, 92, 2, 1000 },
|
||||
{ FLD_T, UNIT_C, CH0, 94, 2, 10 },
|
||||
{ FLD_EVT, UNIT_NONE, CH0, 96, 2, 1 },
|
||||
{ FLD_YD, UNIT_WH, CH0, CALC_YD_CH0, 0, CMD_CALC },
|
||||
{ FLD_YT, UNIT_KWH, CH0, CALC_YT_CH0, 0, CMD_CALC },
|
||||
{ FLD_PDC, UNIT_W, CH0, CALC_PDC_CH0, 0, CMD_CALC },
|
||||
{ FLD_EFF, UNIT_PCT, CH0, CALC_EFF_CH0, 0, CMD_CALC }
|
||||
};
|
||||
#define HMT6CH_LIST_LEN (sizeof(hmt6chAssignment) / sizeof(byteAssign_t))
|
||||
#define HMT6CH_PAYLOAD_LEN 98
|
||||
|
||||
#endif /*__HMS_DEFINES_H__*/
|
405
src/hms/hmsPayload.h
Normal file
405
src/hms/hmsPayload.h
Normal file
|
@ -0,0 +1,405 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 2023 Ahoy, https://ahoydtu.de
|
||||
// Creative Commons - https://creativecommons.org/licenses/by-nc-sa/4.0/deed
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#ifndef __HMS_PAYLOAD_H__
|
||||
#define __HMS_PAYLOAD_H__
|
||||
|
||||
#include "../utils/dbg.h"
|
||||
#include "../utils/crc.h"
|
||||
#include "../config/config.h"
|
||||
#include <Arduino.h>
|
||||
|
||||
#define HMS_TIMEOUT_SEC 30 // 30s * 1000
|
||||
|
||||
typedef struct {
|
||||
uint8_t txCmd;
|
||||
uint8_t txId;
|
||||
//uint8_t invId;
|
||||
uint32_t ts;
|
||||
uint8_t data[MAX_PAYLOAD_ENTRIES][MAX_RF_PAYLOAD_SIZE];
|
||||
uint8_t len[MAX_PAYLOAD_ENTRIES];
|
||||
bool complete;
|
||||
uint8_t maxPackId;
|
||||
bool lastFound;
|
||||
uint8_t retransmits;
|
||||
bool requested;
|
||||
bool gotFragment;
|
||||
} hmsPayload_t;
|
||||
|
||||
|
||||
typedef std::function<void(uint8_t, Inverter<> *)> payloadListenerType;
|
||||
typedef std::function<void(uint16_t alarmCode, uint32_t start, uint32_t end)> alarmListenerType;
|
||||
|
||||
|
||||
template<class HMSYSTEM, class RADIO>
|
||||
class HmsPayload {
|
||||
public:
|
||||
HmsPayload() {}
|
||||
|
||||
void setup(IApp *app, HMSYSTEM *sys, RADIO *radio, statistics_t *stat, uint8_t maxRetransmits, uint32_t *timestamp) {
|
||||
mApp = app;
|
||||
mSys = sys;
|
||||
mRadio = radio;
|
||||
mStat = stat;
|
||||
mMaxRetrans = maxRetransmits;
|
||||
mTimestamp = timestamp;
|
||||
for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i++) {
|
||||
reset(i);
|
||||
mIvCmd56Cnt[i] = 0;
|
||||
}
|
||||
mSerialDebug = false;
|
||||
//mHighPrioIv = NULL;
|
||||
mCbAlarm = NULL;
|
||||
mCbPayload = NULL;
|
||||
//mLastRx = 0;
|
||||
}
|
||||
|
||||
void enableSerialDebug(bool enable) {
|
||||
mSerialDebug = enable;
|
||||
}
|
||||
|
||||
void addPayloadListener(payloadListenerType cb) {
|
||||
mCbPayload = cb;
|
||||
}
|
||||
|
||||
void addAlarmListener(alarmListenerType cb) {
|
||||
mCbAlarm = cb;
|
||||
}
|
||||
|
||||
void loop() {
|
||||
/*if(NULL != mHighPrioIv) {
|
||||
ivSend(mHighPrioIv, true);
|
||||
mHighPrioIv = NULL;
|
||||
}*/
|
||||
}
|
||||
|
||||
void ivSendHighPrio(Inverter<> *iv) {
|
||||
//mHighPrioIv = iv;
|
||||
}
|
||||
|
||||
void ivSend(Inverter<> *iv, bool highPrio = false) {
|
||||
if ((IV_HMS != iv->ivGen) && (IV_HMT != iv->ivGen)) // only process HMS inverters
|
||||
return;
|
||||
|
||||
if(!highPrio) {
|
||||
if (mPayload[iv->id].requested) {
|
||||
if (!mPayload[iv->id].complete)
|
||||
process(false); // no retransmit
|
||||
|
||||
if (!mPayload[iv->id].complete) {
|
||||
if (MAX_PAYLOAD_ENTRIES == mPayload[iv->id].maxPackId)
|
||||
mStat->rxFailNoAnser++; // got nothing
|
||||
else
|
||||
mStat->rxFail++; // got fragments but not complete response
|
||||
|
||||
iv->setQueuedCmdFinished(); // command failed
|
||||
if (mSerialDebug)
|
||||
DPRINTLN(DBG_INFO, F("enqueued cmd failed/timeout"));
|
||||
/*if (mSerialDebug) {
|
||||
DPRINT(DBG_INFO, F("(#"));
|
||||
DBGPRINT(String(iv->id));
|
||||
DBGPRINT(F(") no Payload received! (retransmits: "));
|
||||
DBGPRINT(String(mPayload[iv->id].retransmits));
|
||||
DBGPRINTLN(F(")"));
|
||||
}*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
reset(iv->id);
|
||||
mPayload[iv->id].requested = true;
|
||||
|
||||
yield();
|
||||
if (mSerialDebug) {
|
||||
DPRINT_IVID(DBG_INFO, iv->id);
|
||||
DBGPRINT(F("Requesting Inv SN "));
|
||||
DBGPRINTLN(String(iv->config->serial.u64, HEX));
|
||||
}
|
||||
|
||||
record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug);
|
||||
if (iv->getDevControlRequest()) {
|
||||
if (mSerialDebug) {
|
||||
DPRINT_IVID(DBG_INFO, iv->id);
|
||||
DBGPRINT(F("Devcontrol request 0x"));
|
||||
DBGPRINT(String(iv->devControlCmd, HEX));
|
||||
DBGPRINT(F(" power limit "));
|
||||
DBGPRINTLN(String(iv->powerLimit[0]));
|
||||
}
|
||||
mRadio->sendControlPacket(&iv->radioId.u64, iv->devControlCmd, iv->powerLimit, false);
|
||||
mPayload[iv->id].txCmd = iv->devControlCmd;
|
||||
//iv->clearCmdQueue();
|
||||
//iv->enqueCommand<InfoCommand>(SystemConfigPara); // read back power limit
|
||||
} else if(((rec->ts + HMS_TIMEOUT_SEC) < *mTimestamp) && (mIvCmd56Cnt[iv->id] < 3)) {
|
||||
mRadio->switchFrequency(&iv->radioId.u64, HOY_BOOT_FREQ_KHZ, WORK_FREQ_KHZ);
|
||||
mIvCmd56Cnt[iv->id]++;
|
||||
} else {
|
||||
if(++mIvCmd56Cnt[iv->id] == 10)
|
||||
mIvCmd56Cnt[iv->id] = 0;
|
||||
uint8_t cmd = iv->getQueuedCmd();
|
||||
DPRINT_IVID(DBG_INFO, iv->id);
|
||||
DBGPRINT(F("prepareDevInformCmd 0x"));
|
||||
DBGHEXLN(cmd);
|
||||
mRadio->prepareDevInformCmd(&iv->radioId.u64, cmd, mPayload[iv->id].ts, iv->alarmMesIndex, false);
|
||||
mPayload[iv->id].txCmd = cmd;
|
||||
}
|
||||
}
|
||||
|
||||
void add(Inverter<> *iv, hmsPacket_t *p) {
|
||||
if (p->data[1] == (TX_REQ_INFO + ALL_FRAMES)) { // response from get information command
|
||||
mPayload[iv->id].txId = p->data[1];
|
||||
DPRINTLN(DBG_DEBUG, F("Response from info request received"));
|
||||
uint8_t *pid = &p->data[10];
|
||||
if (*pid == 0x00) {
|
||||
DPRINT(DBG_DEBUG, F("fragment number zero received and ignored"));
|
||||
} else {
|
||||
DPRINTLN(DBG_DEBUG, "PID: 0x" + String(*pid, HEX));
|
||||
if ((*pid & 0x7F) < MAX_PAYLOAD_ENTRIES) {
|
||||
memcpy(mPayload[iv->id].data[(*pid & 0x7F) - 1], &p->data[11], p->data[0] - 11);
|
||||
mPayload[iv->id].len[(*pid & 0x7F) - 1] = p->data[0] -11;
|
||||
mPayload[iv->id].gotFragment = true;
|
||||
}
|
||||
|
||||
if ((*pid & ALL_FRAMES) == ALL_FRAMES) {
|
||||
// Last packet
|
||||
if (((*pid & 0x7f) > mPayload[iv->id].maxPackId) || (MAX_PAYLOAD_ENTRIES == mPayload[iv->id].maxPackId)) {
|
||||
mPayload[iv->id].maxPackId = (*pid & 0x7f);
|
||||
if (*pid > 0x81)
|
||||
mPayload[iv->id].lastFound = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (p->data[1] == (TX_REQ_DEVCONTROL + ALL_FRAMES)) { // response from dev control command
|
||||
DPRINTLN(DBG_DEBUG, F("Response from devcontrol request received"));
|
||||
|
||||
mPayload[iv->id].txId = p->data[1];
|
||||
iv->clearDevControlRequest();
|
||||
|
||||
if ((p->data[13] == ActivePowerContr) && (p->data[14] == 0x00)) {
|
||||
bool ok = true;
|
||||
if((p->data[11] == 0x00) && (p->data[12] == 0x00))
|
||||
mApp->setMqttPowerLimitAck(iv);
|
||||
else
|
||||
ok = false;
|
||||
DPRINT_IVID(DBG_INFO, iv->id);
|
||||
DBGPRINT(F(" has "));
|
||||
if(!ok) DBGPRINT(F("not "));
|
||||
DBGPRINT(F("accepted power limit set point "));
|
||||
DBGPRINT(String(iv->powerLimit[0]));
|
||||
DBGPRINT(F(" with PowerLimitControl "));
|
||||
DBGPRINTLN(String(iv->powerLimit[1]));
|
||||
|
||||
iv->clearCmdQueue();
|
||||
iv->enqueCommand<InfoCommand>(SystemConfigPara); // read back power limit
|
||||
}
|
||||
iv->devControlCmd = Init;
|
||||
}
|
||||
}
|
||||
|
||||
void process(bool retransmit) {
|
||||
for (uint8_t id = 0; id < mSys->getNumInverters(); id++) {
|
||||
Inverter<> *iv = mSys->getInverterByPos(id);
|
||||
if (NULL == iv)
|
||||
continue; // skip to next inverter
|
||||
|
||||
if ((IV_HMS != iv->ivGen) && (IV_HMT != iv->ivGen)) // only process HMS inverters
|
||||
continue; // skip to next inverter
|
||||
|
||||
if ((mPayload[iv->id].txId != (TX_REQ_INFO + ALL_FRAMES)) && (0 != mPayload[iv->id].txId)) {
|
||||
// no processing needed if txId is not 0x95
|
||||
mPayload[iv->id].complete = true;
|
||||
continue; // skip to next inverter
|
||||
}
|
||||
|
||||
if (!mPayload[iv->id].complete) {
|
||||
bool crcPass, pyldComplete;
|
||||
crcPass = build(iv->id, &pyldComplete);
|
||||
if (!crcPass && !pyldComplete) { // payload not complete
|
||||
if ((mPayload[iv->id].requested) && (retransmit)) {
|
||||
if (mPayload[iv->id].retransmits < mMaxRetrans) {
|
||||
mPayload[iv->id].retransmits++;
|
||||
if (iv->devControlCmd == Restart || iv->devControlCmd == CleanState_LockAndAlarm) {
|
||||
// This is required to prevent retransmissions without answer.
|
||||
DPRINTLN(DBG_INFO, F("Prevent retransmit on Restart / CleanState_LockAndAlarm..."));
|
||||
mPayload[iv->id].retransmits = mMaxRetrans;
|
||||
} else if(iv->devControlCmd == ActivePowerContr) {
|
||||
DPRINTLN(DBG_INFO, F("retransmit power limit"));
|
||||
mRadio->sendControlPacket(&iv->radioId.u64, iv->devControlCmd, iv->powerLimit, true);
|
||||
} else {
|
||||
if(false == mPayload[iv->id].gotFragment) {
|
||||
|
||||
//DPRINTLN(DBG_WARN, F("nothing received: Request Complete Retransmit"));
|
||||
//mPayload[iv->id].txCmd = iv->getQueuedCmd();
|
||||
//DPRINTLN(DBG_INFO, F("(#") + String(iv->id) + F(") prepareDevInformCmd 0x") + String(mPayload[iv->id].txCmd, HEX));
|
||||
//mRadio->prepareDevInformCmd(iv->radioId.u64, mPayload[iv->id].txCmd, mPayload[iv->id].ts, iv->alarmMesIndex, true);
|
||||
|
||||
DPRINT_IVID(DBG_INFO, iv->id);
|
||||
DBGPRINTLN(F("nothing received"));
|
||||
mPayload[iv->id].retransmits = mMaxRetrans;
|
||||
} else {
|
||||
for (uint8_t i = 0; i < (mPayload[iv->id].maxPackId - 1); i++) {
|
||||
if (mPayload[iv->id].len[i] == 0) {
|
||||
DPRINT(DBG_WARN, F("Frame "));
|
||||
DBGPRINT(String(i + 1));
|
||||
DBGPRINTLN(F(" missing: Request Retransmit"));
|
||||
//mRadio->sendCmdPacket(iv->radioId.u64, TX_REQ_INFO, (SINGLE_FRAME + i), true);
|
||||
break; // only request retransmit one frame per loop
|
||||
}
|
||||
yield();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} /*else if(!crcPass && pyldComplete) { // crc error on complete Payload
|
||||
if (mPayload[iv->id].retransmits < mMaxRetrans) {
|
||||
mPayload[iv->id].retransmits++;
|
||||
DPRINTLN(DBG_WARN, F("CRC Error: Request Complete Retransmit"));
|
||||
mPayload[iv->id].txCmd = iv->getQueuedCmd();
|
||||
DPRINT(DBG_INFO, F("(#"));
|
||||
DBGPRINT(String(iv->id));
|
||||
DBGPRINT(F(") prepareDevInformCmd 0x"));
|
||||
DBGPRINTLN(String(mPayload[iv->id].txCmd, HEX));
|
||||
mRadio->prepareDevInformCmd(iv->radioId.u64, mPayload[iv->id].txCmd, mPayload[iv->id].ts, iv->alarmMesIndex, true);
|
||||
}
|
||||
}*/ else { // payload complete
|
||||
DPRINT(DBG_INFO, F("procPyld: cmd: 0x"));
|
||||
DBGPRINTLN(String(mPayload[iv->id].txCmd, HEX));
|
||||
DPRINT(DBG_INFO, F("procPyld: txid: 0x"));
|
||||
DBGPRINTLN(String(mPayload[iv->id].txId, HEX));
|
||||
DPRINTLN(DBG_DEBUG, F("procPyld: max: ") + String(mPayload[iv->id].maxPackId));
|
||||
record_t<> *rec = iv->getRecordStruct(mPayload[iv->id].txCmd); // choose the parser
|
||||
mPayload[iv->id].complete = true;
|
||||
|
||||
uint8_t payload[100];
|
||||
uint8_t payloadLen = 0;
|
||||
|
||||
memset(payload, 0, 100);
|
||||
|
||||
for (uint8_t i = 0; i < (mPayload[iv->id].maxPackId); i++) {
|
||||
memcpy(&payload[payloadLen], mPayload[iv->id].data[i], (mPayload[iv->id].len[i]));
|
||||
payloadLen += (mPayload[iv->id].len[i]);
|
||||
yield();
|
||||
}
|
||||
payloadLen -= 2;
|
||||
|
||||
if (mSerialDebug) {
|
||||
DPRINT(DBG_INFO, F("Payload ("));
|
||||
DBGPRINT(String(payloadLen));
|
||||
DBGPRINT(F("): "));
|
||||
ah::dumpBuf(payload, payloadLen);
|
||||
}
|
||||
|
||||
if (NULL == rec) {
|
||||
DPRINTLN(DBG_ERROR, F("record is NULL!"));
|
||||
} else if ((rec->pyldLen == payloadLen) || (0 == rec->pyldLen)) {
|
||||
if (mPayload[iv->id].txId == (TX_REQ_INFO + ALL_FRAMES))
|
||||
mStat->rxSuccess++;
|
||||
|
||||
rec->ts = mPayload[iv->id].ts;
|
||||
for (uint8_t i = 0; i < rec->length; i++) {
|
||||
iv->addValue(i, payload, rec);
|
||||
yield();
|
||||
}
|
||||
iv->doCalculations();
|
||||
notify(mPayload[iv->id].txCmd, iv);
|
||||
|
||||
/*if(AlarmData == mPayload[iv->id].txCmd) {
|
||||
uint8_t i = 0;
|
||||
uint16_t code;
|
||||
uint32_t start, end;
|
||||
while(1) {
|
||||
code = iv->parseAlarmLog(i++, payload, payloadLen, &start, &end);
|
||||
if(0 == code)
|
||||
break;
|
||||
if (NULL != mCbAlarm)
|
||||
(mCbAlarm)(code, start, end);
|
||||
yield();
|
||||
}
|
||||
}*/
|
||||
} else {
|
||||
DPRINT(DBG_ERROR, F("plausibility check failed, expected "));
|
||||
DBGPRINT(String(rec->pyldLen));
|
||||
DBGPRINTLN(F(" bytes"));
|
||||
mStat->rxFail++;
|
||||
}
|
||||
|
||||
iv->setQueuedCmdFinished();
|
||||
}
|
||||
}
|
||||
yield();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void notify(uint8_t val, Inverter<> *iv) {
|
||||
if(NULL != mCbPayload)
|
||||
(mCbPayload)(val, iv);
|
||||
}
|
||||
|
||||
void notify(uint16_t code, uint32_t start, uint32_t endTime) {
|
||||
if (NULL != mCbAlarm)
|
||||
(mCbAlarm)(code, start, endTime);
|
||||
}
|
||||
|
||||
bool build(uint8_t id, bool *complete) {
|
||||
DPRINTLN(DBG_VERBOSE, F("build"));
|
||||
uint16_t crc = 0xffff, crcRcv = 0x0000;
|
||||
if (mPayload[id].maxPackId > MAX_PAYLOAD_ENTRIES)
|
||||
mPayload[id].maxPackId = MAX_PAYLOAD_ENTRIES;
|
||||
|
||||
// check if all fragments are there
|
||||
*complete = true;
|
||||
for (uint8_t i = 0; i < mPayload[id].maxPackId; i++) {
|
||||
if(mPayload[id].len[i] == 0)
|
||||
*complete = false;
|
||||
}
|
||||
if(!*complete)
|
||||
return false;
|
||||
|
||||
for (uint8_t i = 0; i < mPayload[id].maxPackId; i++) {
|
||||
if (mPayload[id].len[i] > 0) {
|
||||
if (i == (mPayload[id].maxPackId - 1)) {
|
||||
crc = ah::crc16(mPayload[id].data[i], mPayload[id].len[i] - 1, crc);
|
||||
crcRcv = (mPayload[id].data[i][mPayload[id].len[i] - 2] << 8) | (mPayload[id].data[i][mPayload[id].len[i] - 1]);
|
||||
} else
|
||||
crc = ah::crc16(mPayload[id].data[i], mPayload[id].len[i], crc);
|
||||
}
|
||||
yield();
|
||||
}
|
||||
|
||||
return (crc == crcRcv) ? true : false;
|
||||
}
|
||||
|
||||
void reset(uint8_t id) {
|
||||
DPRINT(DBG_INFO, "resetPayload: id: ");
|
||||
DBGPRINTLN(String(id));
|
||||
memset(&mPayload[id], 0, sizeof(hmsPayload_t));
|
||||
//mPayload[id].txCmd = 0;
|
||||
mPayload[id].gotFragment = false;
|
||||
//mPayload[id].retransmits = 0;
|
||||
mPayload[id].maxPackId = MAX_PAYLOAD_ENTRIES;
|
||||
mPayload[id].lastFound = false;
|
||||
mPayload[id].complete = false;
|
||||
mPayload[id].requested = false;
|
||||
mPayload[id].ts = *mTimestamp;
|
||||
}
|
||||
|
||||
IApp *mApp;
|
||||
HMSYSTEM *mSys;
|
||||
RADIO *mRadio;
|
||||
statistics_t *mStat;
|
||||
uint8_t mMaxRetrans;
|
||||
uint32_t *mTimestamp;
|
||||
//uint32_t mLastRx;
|
||||
hmsPayload_t mPayload[MAX_NUM_INVERTERS];
|
||||
uint8_t mIvCmd56Cnt[MAX_NUM_INVERTERS];
|
||||
bool mSerialDebug;
|
||||
Inverter<> *mHighPrioIv;
|
||||
|
||||
alarmListenerType mCbAlarm;
|
||||
payloadListenerType mCbPayload;
|
||||
};
|
||||
|
||||
#endif /*__HMS_PAYLOAD_H__*/
|
213
src/hms/hmsRadio.h
Normal file
213
src/hms/hmsRadio.h
Normal file
|
@ -0,0 +1,213 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 2023 Ahoy, https://github.com/lumpapu/ahoy
|
||||
// Creative Commons - https://creativecommons.org/licenses/by-nc-sa/4.0/deed
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#ifndef __HMS_RADIO_H__
|
||||
#define __HMS_RADIO_H__
|
||||
|
||||
#include "../utils/dbg.h"
|
||||
#include "cmt2300a.h"
|
||||
|
||||
typedef struct {
|
||||
int8_t rssi;
|
||||
uint8_t data[28];
|
||||
} hmsPacket_t;
|
||||
|
||||
#define U32_B3(val) ((uint8_t)((val >> 24) & 0xff))
|
||||
#define U32_B2(val) ((uint8_t)((val >> 16) & 0xff))
|
||||
#define U32_B1(val) ((uint8_t)((val >> 8) & 0xff))
|
||||
#define U32_B0(val) ((uint8_t)((val ) & 0xff))
|
||||
|
||||
template<class SPI, uint32_t DTU_SN = 0x81001765>
|
||||
class CmtRadio {
|
||||
typedef SPI SpiType;
|
||||
typedef Cmt2300a<SpiType> CmtType;
|
||||
public:
|
||||
CmtRadio() {
|
||||
mDtuSn = DTU_SN;
|
||||
}
|
||||
|
||||
void setup(uint8_t pinCsb, uint8_t pinFcsb, bool genDtuSn = true) {
|
||||
mCmt.setup(pinCsb, pinFcsb);
|
||||
reset(genDtuSn);
|
||||
}
|
||||
|
||||
void setup(bool genDtuSn = true) {
|
||||
mCmt.setup();
|
||||
reset(genDtuSn);
|
||||
}
|
||||
|
||||
bool loop() {
|
||||
mCmt.loop();
|
||||
|
||||
if((!mIrqRcvd) && (!mRqstGetRx))
|
||||
return false;
|
||||
getRx();
|
||||
if(CMT_SUCCESS == mCmt.goRx()) {
|
||||
mIrqRcvd = false;
|
||||
mRqstGetRx = false;
|
||||
return true;
|
||||
} else
|
||||
return false;
|
||||
}
|
||||
|
||||
void tickSecond() {
|
||||
}
|
||||
|
||||
void handleIntr(void) {
|
||||
mIrqRcvd = true;
|
||||
}
|
||||
|
||||
void enableDebug() {
|
||||
mSerialDebug = true;
|
||||
}
|
||||
|
||||
void sendControlPacket(const uint64_t *ivId, uint8_t cmd, uint16_t *data, bool isRetransmit) {
|
||||
DPRINT(DBG_INFO, F("sendControlPacket cmd: 0x"));
|
||||
DBGHEXLN(cmd);
|
||||
initPacket(ivId, TX_REQ_DEVCONTROL, SINGLE_FRAME);
|
||||
uint8_t cnt = 10;
|
||||
|
||||
mTxBuf[cnt++] = cmd; // cmd -> 0 on, 1 off, 2 restart, 11 active power, 12 reactive power, 13 power factor
|
||||
mTxBuf[cnt++] = 0x00;
|
||||
if(cmd >= ActivePowerContr && cmd <= PFSet) { // ActivePowerContr, ReactivePowerContr, PFSet
|
||||
mTxBuf[cnt++] = ((data[0] * 10) >> 8) & 0xff; // power limit
|
||||
mTxBuf[cnt++] = ((data[0] * 10) ) & 0xff; // power limit
|
||||
mTxBuf[cnt++] = ((data[1] ) >> 8) & 0xff; // setting for persistens handlings
|
||||
mTxBuf[cnt++] = ((data[1] ) ) & 0xff; // setting for persistens handling
|
||||
}
|
||||
|
||||
sendPacket(cnt, isRetransmit);
|
||||
}
|
||||
|
||||
bool switchFrequency(const uint64_t *ivId, uint32_t fromkHz, uint32_t tokHz) {
|
||||
uint8_t fromCh = mCmt.freq2Chan(fromkHz);
|
||||
uint8_t toCh = mCmt.freq2Chan(tokHz);
|
||||
|
||||
if((0xff == fromCh) || (0xff == toCh))
|
||||
return false;
|
||||
|
||||
mCmt.switchChannel(fromCh);
|
||||
sendSwitchChCmd(ivId, toCh);
|
||||
mCmt.switchChannel(toCh);
|
||||
return true;
|
||||
}
|
||||
|
||||
void prepareDevInformCmd(const uint64_t *ivId, uint8_t cmd, uint32_t ts, uint16_t alarmMesId, bool isRetransmit, uint8_t reqfld=TX_REQ_INFO) { // might not be necessary to add additional arg.
|
||||
initPacket(ivId, reqfld, ALL_FRAMES);
|
||||
mTxBuf[10] = cmd;
|
||||
CP_U32_LittleEndian(&mTxBuf[12], ts);
|
||||
/*if (cmd == RealTimeRunData_Debug || cmd == AlarmData ) {
|
||||
mTxBuf[18] = (alarmMesId >> 8) & 0xff;
|
||||
mTxBuf[19] = (alarmMesId ) & 0xff;
|
||||
}*/
|
||||
sendPacket(24, isRetransmit);
|
||||
}
|
||||
|
||||
void sendPacket(uint8_t len, bool isRetransmit) {
|
||||
if (len > 14) {
|
||||
uint16_t crc = ah::crc16(&mTxBuf[10], len - 10);
|
||||
mTxBuf[len++] = (crc >> 8) & 0xff;
|
||||
mTxBuf[len++] = (crc ) & 0xff;
|
||||
}
|
||||
mTxBuf[len] = ah::crc8(mTxBuf, len);
|
||||
len++;
|
||||
|
||||
if(mSerialDebug) {
|
||||
DPRINT(DBG_INFO, F("TX "));
|
||||
DBGPRINT(String(mCmt.getFreqKhz()/1000.0f));
|
||||
DBGPRINT(F("Mhz | "));
|
||||
ah::dumpBuf(mTxBuf, len);
|
||||
}
|
||||
|
||||
uint8_t status = mCmt.tx(mTxBuf, len);
|
||||
if(CMT_SUCCESS != status) {
|
||||
DPRINT(DBG_WARN, F("CMT TX failed, code: "));
|
||||
DBGPRINTLN(String(status));
|
||||
if(CMT_ERR_RX_IN_FIFO == status)
|
||||
mIrqRcvd = true;
|
||||
}
|
||||
|
||||
if(isRetransmit)
|
||||
mRetransmits++;
|
||||
else
|
||||
mSendCnt++;
|
||||
}
|
||||
|
||||
uint32_t mSendCnt;
|
||||
uint32_t mRetransmits;
|
||||
std::queue<hmsPacket_t> mBufCtrl;
|
||||
|
||||
private:
|
||||
inline void reset(bool genDtuSn) {
|
||||
if(genDtuSn)
|
||||
generateDtuSn();
|
||||
if(!mCmt.reset())
|
||||
DPRINTLN(DBG_WARN, F("Initializing CMT2300A failed!"));
|
||||
else
|
||||
mCmt.goRx();
|
||||
|
||||
mSendCnt = 0;
|
||||
mRetransmits = 0;
|
||||
mSerialDebug = false;
|
||||
mIrqRcvd = false;
|
||||
mRqstGetRx = false;
|
||||
}
|
||||
|
||||
inline void sendSwitchChCmd(const uint64_t *ivId, uint8_t ch) {
|
||||
/** ch:
|
||||
* 0x00: 860.00 MHz
|
||||
* 0x01: 860.25 MHz
|
||||
* 0x02: 860.50 MHz
|
||||
* ...
|
||||
* 0x14: 865.00 MHz
|
||||
* ...
|
||||
* 0x28: 870.00 MHz
|
||||
* */
|
||||
initPacket(ivId, 0x56, 0x02);
|
||||
mTxBuf[10] = 0x15;
|
||||
mTxBuf[11] = 0x21;
|
||||
mTxBuf[12] = ch;
|
||||
mTxBuf[13] = 0x14;
|
||||
sendPacket(14, false);
|
||||
mRqstGetRx = true;
|
||||
}
|
||||
|
||||
void initPacket(const uint64_t *ivId, uint8_t mid, uint8_t pid) {
|
||||
mTxBuf[0] = mid;
|
||||
CP_U32_BigEndian(&mTxBuf[1], (*ivId) >> 8);
|
||||
CP_U32_LittleEndian(&mTxBuf[5], mDtuSn);
|
||||
mTxBuf[9] = pid;
|
||||
memset(&mTxBuf[10], 0x00, 17);
|
||||
}
|
||||
|
||||
inline void generateDtuSn(void) {
|
||||
uint32_t chipID = 0;
|
||||
#ifdef ESP32
|
||||
uint64_t MAC = ESP.getEfuseMac();
|
||||
chipID = ((MAC >> 8) & 0xFF0000) | ((MAC >> 24) & 0xFF00) | ((MAC >> 40) & 0xFF);
|
||||
#endif
|
||||
mDtuSn = 0x80000000; // the first digit is an 8 for DTU production year 2022, the rest is filled with the ESP chipID in decimal
|
||||
for(int i = 0; i < 7; i++) {
|
||||
mDtuSn |= (chipID % 10) << (i * 4);
|
||||
chipID /= 10;
|
||||
}
|
||||
}
|
||||
|
||||
inline void getRx(void) {
|
||||
hmsPacket_t p;
|
||||
uint8_t status = mCmt.getRx(p.data, 28, &p.rssi);
|
||||
if(CMT_SUCCESS == status)
|
||||
mBufCtrl.push(p);
|
||||
}
|
||||
|
||||
CmtType mCmt;
|
||||
uint32_t mDtuSn;
|
||||
uint8_t mTxBuf[27];
|
||||
bool mSerialDebug;
|
||||
bool mIrqRcvd;
|
||||
bool mRqstGetRx;
|
||||
};
|
||||
|
||||
#endif /*__HMS_RADIO_H__*/
|
20
src/main.cpp
20
src/main.cpp
|
@ -5,8 +5,6 @@
|
|||
|
||||
#include "utils/dbg.h"
|
||||
#include "app.h"
|
||||
#include "config/config.h"
|
||||
|
||||
|
||||
app myApp;
|
||||
|
||||
|
@ -15,13 +13,27 @@ IRAM_ATTR void handleIntr(void) {
|
|||
myApp.handleIntr();
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
#ifdef ESP32
|
||||
IRAM_ATTR void handleHmsIntr(void) {
|
||||
myApp.handleHmsIntr();
|
||||
}
|
||||
#endif
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void setup() {
|
||||
myApp.setup();
|
||||
|
||||
// TODO: move to HmRadio
|
||||
attachInterrupt(digitalPinToInterrupt(myApp.getIrqPin()), handleIntr, FALLING);
|
||||
if(myApp.getNrfEnabled()) {
|
||||
if(DEF_PIN_OFF != myApp.getNrfIrqPin())
|
||||
attachInterrupt(digitalPinToInterrupt(myApp.getNrfIrqPin()), handleIntr, FALLING);
|
||||
}
|
||||
#ifdef ESP32
|
||||
if(myApp.getCmtEnabled()) {
|
||||
if(DEF_PIN_OFF != myApp.getCmtIrqPin())
|
||||
attachInterrupt(digitalPinToInterrupt(myApp.getCmtIrqPin()), handleHmsIntr, RISING);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ extra_scripts =
|
|||
|
||||
lib_deps =
|
||||
https://github.com/yubox-node-org/ESPAsyncWebServer
|
||||
nrf24/RF24 @ ^1.4.5
|
||||
nrf24/RF24 @ 1.4.5
|
||||
paulstoffregen/Time @ ^1.6.1
|
||||
https://github.com/bertmelis/espMqttClient#v1.4.2
|
||||
bblanchon/ArduinoJson @ ^6.21.2
|
||||
|
@ -37,7 +37,7 @@ lib_deps =
|
|||
platform = espressif8266
|
||||
board = esp12e
|
||||
board_build.f_cpu = 80000000L
|
||||
build_flags = -D RELEASE
|
||||
build_flags = -D RELEASE -std=gnu++17
|
||||
;-Wl,-Map,output.map
|
||||
monitor_filters =
|
||||
;default ; Remove typical terminal control codes from input
|
||||
|
@ -50,7 +50,7 @@ monitor_filters =
|
|||
platform = espressif8266
|
||||
board = esp12e
|
||||
board_build.f_cpu = 80000000L
|
||||
build_flags = -D RELEASE -DENABLE_PROMETHEUS_EP
|
||||
build_flags = -D RELEASE -std=gnu++17 -DENABLE_PROMETHEUS_EP
|
||||
monitor_filters =
|
||||
;default ; Remove typical terminal control codes from input
|
||||
;time ; Add timestamp with milliseconds for each new line
|
||||
|
@ -61,7 +61,7 @@ monitor_filters =
|
|||
platform = espressif8266
|
||||
board = esp12e
|
||||
board_build.f_cpu = 80000000L
|
||||
build_flags = -DDEBUG_LEVEL=DBG_DEBUG -DDEBUG_ESP_CORE -DDEBUG_ESP_WIFI -DDEBUG_ESP_HTTP_CLIENT -DDEBUG_ESP_HTTP_SERVER -DDEBUG_ESP_OOM -DDEBUG_ESP_PORT=Serial -DPIO_FRAMEWORK_ARDUINO_MMU_CACHE16_IRAM48
|
||||
build_flags = -DDEBUG_LEVEL=DBG_DEBUG -std=gnu++17 -DDEBUG_ESP_CORE -DDEBUG_ESP_WIFI -DDEBUG_ESP_HTTP_CLIENT -DDEBUG_ESP_HTTP_SERVER -DDEBUG_ESP_OOM -DDEBUG_ESP_PORT=Serial -DPIO_FRAMEWORK_ARDUINO_MMU_CACHE16_IRAM48
|
||||
build_type = debug
|
||||
monitor_filters =
|
||||
;default ; Remove typical terminal control codes from input
|
||||
|
@ -73,7 +73,7 @@ platform = espressif8266
|
|||
board = esp8285
|
||||
board_build.ldscript = eagle.flash.1m64.ld
|
||||
board_build.f_cpu = 80000000L
|
||||
build_flags = -D RELEASE
|
||||
build_flags = -D RELEASE -std=gnu++17
|
||||
monitor_filters =
|
||||
;default ; Remove typical terminal control codes from input
|
||||
time ; Add timestamp with milliseconds for each new line
|
||||
|
|
|
@ -10,12 +10,15 @@
|
|||
#include "Display_Mono_128X32.h"
|
||||
#include "Display_Mono_128X64.h"
|
||||
#include "Display_Mono_84X48.h"
|
||||
#include "Display_Mono_64X48.h"
|
||||
#include "Display_ePaper.h"
|
||||
|
||||
template <class HMSYSTEM>
|
||||
class Display {
|
||||
public:
|
||||
Display() {}
|
||||
Display() {
|
||||
mMono = NULL;
|
||||
}
|
||||
|
||||
void setup(display_t *cfg, HMSYSTEM *sys, uint32_t *utcTs, const char *version) {
|
||||
mCfg = cfg;
|
||||
|
@ -25,31 +28,28 @@ class Display {
|
|||
mLoopCnt = 0;
|
||||
mVersion = version;
|
||||
|
||||
if (mCfg->type == 0)
|
||||
return;
|
||||
switch (mCfg->type) {
|
||||
case 0: mMono = NULL; break;
|
||||
case 1: // fall-through
|
||||
case 2: mMono = new DisplayMono128X64(); break;
|
||||
case 3: mMono = new DisplayMono84X48(); break;
|
||||
case 4: mMono = new DisplayMono128X32(); break;
|
||||
case 5: mMono = new DisplayMono64X48(); break;
|
||||
|
||||
if ((0 < mCfg->type) && (mCfg->type < 10)) {
|
||||
switch (mCfg->type) {
|
||||
case 2:
|
||||
case 1:
|
||||
default:
|
||||
mMono = new DisplayMono128X64();
|
||||
break;
|
||||
case 3:
|
||||
mMono = new DisplayMono84X48();
|
||||
break;
|
||||
case 4:
|
||||
mMono = new DisplayMono128X32();
|
||||
break;
|
||||
}
|
||||
#if defined(ESP32)
|
||||
case 10:
|
||||
mMono = NULL; // ePaper does not use this
|
||||
mRefreshCycle = 0;
|
||||
mEpaper.config(mCfg->rot, mCfg->pwrSaveAtIvOffline);
|
||||
mEpaper.init(mCfg->type, mCfg->disp_cs, mCfg->disp_dc, mCfg->disp_reset, mCfg->disp_busy, mCfg->disp_clk, mCfg->disp_data, mUtcTs, mVersion);
|
||||
break;
|
||||
#endif
|
||||
|
||||
default: mMono = NULL; break;
|
||||
}
|
||||
if(mMono) {
|
||||
mMono->config(mCfg->pwrSaveAtIvOffline, mCfg->pxShift, mCfg->contrast);
|
||||
mMono->init(mCfg->type, mCfg->rot, mCfg->disp_cs, mCfg->disp_dc, 0xff, mCfg->disp_clk, mCfg->disp_data, mUtcTs, mVersion);
|
||||
} else if (mCfg->type >= 10) {
|
||||
#if defined(ESP32)
|
||||
mRefreshCycle = 0;
|
||||
mEpaper.config(mCfg->rot, mCfg->pwrSaveAtIvOffline);
|
||||
mEpaper.init(mCfg->type, mCfg->disp_cs, mCfg->disp_dc, mCfg->disp_reset, mCfg->disp_busy, mCfg->disp_clk, mCfg->disp_data, mUtcTs, mVersion);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -89,7 +89,7 @@ class Display {
|
|||
if (iv == NULL)
|
||||
continue;
|
||||
|
||||
if (iv->isProducing(*mUtcTs))
|
||||
if (iv->isProducing())
|
||||
isprod++;
|
||||
|
||||
totalPower += iv->getChannelFieldValue(CH0, FLD_PAC, rec);
|
||||
|
@ -97,14 +97,16 @@ class Display {
|
|||
totalYieldTotal += iv->getChannelFieldValue(CH0, FLD_YT, rec);
|
||||
}
|
||||
|
||||
if ((0 < mCfg->type) && (mCfg->type < 10) && (mMono != NULL)) {
|
||||
if (mMono ) {
|
||||
mMono->disp(totalPower, totalYieldDay, totalYieldTotal, isprod);
|
||||
} else if (mCfg->type >= 10) {
|
||||
}
|
||||
#if defined(ESP32)
|
||||
else if (mCfg->type == 10) {
|
||||
|
||||
mEpaper.loop(totalPower, totalYieldDay, totalYieldTotal, isprod);
|
||||
mRefreshCycle++;
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(ESP32)
|
||||
if (mRefreshCycle > 480) {
|
||||
|
|
|
@ -35,8 +35,8 @@ class DisplayMono {
|
|||
|
||||
uint8_t mLoopCnt;
|
||||
uint32_t* mUtcTs;
|
||||
uint8_t mLineXOffsets[5];
|
||||
uint8_t mLineYOffsets[5];
|
||||
uint8_t mLineXOffsets[5] = {};
|
||||
uint8_t mLineYOffsets[5] = {};
|
||||
|
||||
uint16_t mDispY;
|
||||
|
||||
|
|
|
@ -21,8 +21,6 @@ class DisplayMono128X32 : public DisplayMono {
|
|||
|
||||
|
||||
void init(uint8_t type, uint8_t rotation, uint8_t cs, uint8_t dc, uint8_t reset, uint8_t clock, uint8_t data, uint32_t *utcTs, const char *version) {
|
||||
if((0 == type) || (type > 4))
|
||||
return;
|
||||
|
||||
u8g2_cb_t *rot = (u8g2_cb_t *)((rotation != 0x00) ? U8G2_R2 : U8G2_R0);
|
||||
mType = type;
|
||||
|
|
|
@ -19,8 +19,6 @@ class DisplayMono128X64 : public DisplayMono {
|
|||
}
|
||||
|
||||
void init(uint8_t type, uint8_t rotation, uint8_t cs, uint8_t dc, uint8_t reset, uint8_t clock, uint8_t data, uint32_t *utcTs, const char *version) {
|
||||
if((0 == type) || (type > 4))
|
||||
return;
|
||||
|
||||
u8g2_cb_t *rot = (u8g2_cb_t *)((rotation != 0x00) ? U8G2_R2 : U8G2_R0);
|
||||
mType = type;
|
||||
|
@ -65,8 +63,7 @@ class DisplayMono128X64 : public DisplayMono {
|
|||
mDisplay->clearBuffer();
|
||||
|
||||
// set Contrast of the Display to raise the lifetime
|
||||
if (3 != mType)
|
||||
mDisplay->setContrast(mLuminance);
|
||||
mDisplay->setContrast(mLuminance);
|
||||
|
||||
if ((totalPower > 0) && (isprod > 0)) {
|
||||
mTimeout = DISP_DEFAULT_TIMEOUT;
|
||||
|
|
134
src/plugins/Display/Display_Mono_64X48.h
Normal file
134
src/plugins/Display/Display_Mono_64X48.h
Normal file
|
@ -0,0 +1,134 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 2023 Ahoy, https://ahoydtu.de
|
||||
// Creative Commons - https://creativecommons.org/licenses/by-nc-sa/4.0/deed
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#pragma once
|
||||
#include "Display_Mono.h"
|
||||
|
||||
class DisplayMono64X48 : public DisplayMono {
|
||||
public:
|
||||
DisplayMono64X48() : DisplayMono() {
|
||||
mEnPowerSafe = true;
|
||||
mEnScreenSaver = false;
|
||||
mLuminance = 20;
|
||||
mExtra = 0;
|
||||
mDispY = 0;
|
||||
mTimeout = DISP_DEFAULT_TIMEOUT; // interval at which to power save (milliseconds)
|
||||
mUtcTs = NULL;
|
||||
mType = 0;
|
||||
}
|
||||
|
||||
void init(uint8_t type, uint8_t rotation, uint8_t cs, uint8_t dc, uint8_t reset, uint8_t clock, uint8_t data, uint32_t *utcTs, const char *version) {
|
||||
|
||||
u8g2_cb_t *rot = (u8g2_cb_t *)((rotation != 0x00) ? U8G2_R2 : U8G2_R0);
|
||||
mType = type;
|
||||
|
||||
// Wemos OLed Shield is not defined in u8 lib -> use nearest compatible
|
||||
mDisplay = new U8G2_SSD1306_64X48_ER_F_HW_I2C(rot, reset, clock, data);
|
||||
|
||||
mUtcTs = utcTs;
|
||||
|
||||
mDisplay->begin();
|
||||
calcLinePositions();
|
||||
|
||||
mDisplay->clearBuffer();
|
||||
mDisplay->setContrast(mLuminance);
|
||||
|
||||
printText("AHOY!", 0);
|
||||
printText("ahoydtu.de", 1);
|
||||
printText(version, 2);
|
||||
mDisplay->sendBuffer();
|
||||
}
|
||||
|
||||
void config(bool enPowerSafe, bool enScreenSaver, uint8_t lum) {
|
||||
mEnPowerSafe = enPowerSafe;
|
||||
mEnScreenSaver = enScreenSaver;
|
||||
mLuminance = lum;
|
||||
}
|
||||
|
||||
void loop(void) {
|
||||
if (mEnPowerSafe) {
|
||||
if (mTimeout != 0)
|
||||
mTimeout--;
|
||||
}
|
||||
}
|
||||
|
||||
void disp(float totalPower, float totalYieldDay, float totalYieldTotal, uint8_t isprod) {
|
||||
mDisplay->clearBuffer();
|
||||
|
||||
// set Contrast of the Display to raise the lifetime
|
||||
mDisplay->setContrast(mLuminance);
|
||||
|
||||
if ((totalPower > 0) && (isprod > 0)) {
|
||||
mTimeout = DISP_DEFAULT_TIMEOUT;
|
||||
mDisplay->setPowerSave(false);
|
||||
|
||||
if (totalPower > 999)
|
||||
snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%2.2f kW", (totalPower / 1000));
|
||||
else
|
||||
snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%3.0f W", totalPower);
|
||||
|
||||
printText(mFmtText, 0);
|
||||
} else {
|
||||
printText("offline", 0);
|
||||
// check if it's time to enter power saving mode
|
||||
if (mTimeout == 0)
|
||||
mDisplay->setPowerSave(mEnPowerSafe);
|
||||
}
|
||||
|
||||
snprintf(mFmtText, DISP_FMT_TEXT_LEN, "D: %4.0f Wh", totalYieldDay);
|
||||
printText(mFmtText, 1);
|
||||
|
||||
snprintf(mFmtText, DISP_FMT_TEXT_LEN, "T: %4.0f kWh", totalYieldTotal);
|
||||
printText(mFmtText, 2);
|
||||
|
||||
IPAddress ip = WiFi.localIP();
|
||||
if (!(mExtra % 10) && (ip))
|
||||
printText(ip.toString().c_str(), 3);
|
||||
else if (!(mExtra % 5)) {
|
||||
snprintf(mFmtText, DISP_FMT_TEXT_LEN, "active Inv: %d", isprod);
|
||||
printText(mFmtText, 3);
|
||||
} else if (NULL != mUtcTs)
|
||||
printText(ah::getTimeStr(gTimezone.toLocal(*mUtcTs)).c_str(), 3);
|
||||
|
||||
mDisplay->sendBuffer();
|
||||
|
||||
mExtra++;
|
||||
}
|
||||
|
||||
private:
|
||||
void calcLinePositions() {
|
||||
uint8_t yOff = 0;
|
||||
for (uint8_t i = 0; i < 4; i++) {
|
||||
setFont(i);
|
||||
yOff += (mDisplay->getMaxCharHeight());
|
||||
mLineYOffsets[i] = yOff;
|
||||
}
|
||||
}
|
||||
|
||||
inline void setFont(uint8_t line) {
|
||||
switch (line) {
|
||||
case 0:
|
||||
mDisplay->setFont(u8g2_font_fur11_tf);
|
||||
break;
|
||||
case 1:
|
||||
case 2:
|
||||
mDisplay->setFont(u8g2_font_6x10_tf);
|
||||
break;
|
||||
case 3:
|
||||
mDisplay->setFont(u8g2_font_4x6_tr);
|
||||
break;
|
||||
case 4:
|
||||
mDisplay->setFont(u8g2_font_4x6_tr);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void printText(const char *text, uint8_t line) {
|
||||
uint8_t dispX = 0; //small display, use all we have
|
||||
dispX += (mEnScreenSaver) ? (mExtra % 4) : 0;
|
||||
setFont(line);
|
||||
mDisplay->drawStr(dispX, mLineYOffsets[line], text);
|
||||
}
|
||||
};
|
|
@ -20,8 +20,6 @@ class DisplayMono84X48 : public DisplayMono {
|
|||
}
|
||||
|
||||
void init(uint8_t type, uint8_t rotation, uint8_t cs, uint8_t dc, uint8_t reset, uint8_t clock, uint8_t data, uint32_t *utcTs, const char *version) {
|
||||
if((0 == type) || (type > 4))
|
||||
return;
|
||||
|
||||
u8g2_cb_t *rot = (u8g2_cb_t *)((rotation != 0x00) ? U8G2_R2 : U8G2_R0);
|
||||
mType = type;
|
||||
|
@ -33,8 +31,8 @@ class DisplayMono84X48 : public DisplayMono {
|
|||
calcLinePositions();
|
||||
|
||||
mDisplay->clearBuffer();
|
||||
if (3 != mType)
|
||||
mDisplay->setContrast(mLuminance);
|
||||
mDisplay->setContrast(mLuminance);
|
||||
|
||||
printText("AHOY!", 0);
|
||||
printText("ahoydtu.de", 2);
|
||||
printText(version, 3);
|
||||
|
@ -58,8 +56,7 @@ class DisplayMono84X48 : public DisplayMono {
|
|||
mDisplay->clearBuffer();
|
||||
|
||||
// set Contrast of the Display to raise the lifetime
|
||||
if (3 != mType)
|
||||
mDisplay->setContrast(mLuminance);
|
||||
mDisplay->setContrast(mLuminance);
|
||||
|
||||
if ((totalPower > 0) && (isprod > 0)) {
|
||||
mTimeout = DISP_DEFAULT_TIMEOUT;
|
||||
|
@ -95,7 +92,7 @@ class DisplayMono84X48 : public DisplayMono {
|
|||
|
||||
mDisplay->sendBuffer();
|
||||
|
||||
mExtra = 1;
|
||||
mExtra++;
|
||||
}
|
||||
|
||||
private:
|
||||
|
|
|
@ -26,7 +26,7 @@ DisplayEPaper::DisplayEPaper() {
|
|||
void DisplayEPaper::init(uint8_t type, uint8_t _CS, uint8_t _DC, uint8_t _RST, uint8_t _BUSY, uint8_t _SCK, uint8_t _MOSI, uint32_t *utcTs, const char *version) {
|
||||
mUtcTs = utcTs;
|
||||
|
||||
if (type > 9) {
|
||||
if (type == 10) {
|
||||
Serial.begin(115200);
|
||||
_display = new GxEPD2_BW<GxEPD2_150_BN, GxEPD2_150_BN::HEIGHT>(GxEPD2_150_BN(_CS, _DC, _RST, _BUSY));
|
||||
hspi.begin(_SCK, _BUSY, _MOSI, _CS);
|
||||
|
|
|
@ -49,19 +49,21 @@ class PubMqtt {
|
|||
mRxCnt = 0;
|
||||
mTxCnt = 0;
|
||||
mSubscriptionCb = NULL;
|
||||
memset(mLastIvState, MQTT_STATUS_NOT_AVAIL_NOT_PROD, MAX_NUM_INVERTERS);
|
||||
memset(mLastIvState, (uint8_t)InverterStatus::OFF, MAX_NUM_INVERTERS);
|
||||
memset(mIvLastRTRpub, 0, MAX_NUM_INVERTERS * 4);
|
||||
mLastAnyAvail = false;
|
||||
mZeroValues = false;
|
||||
}
|
||||
|
||||
~PubMqtt() { }
|
||||
|
||||
void setup(cfgMqtt_t *cfg_mqtt, const char *devName, const char *version, HMSYSTEM *sys, uint32_t *utcTs) {
|
||||
void setup(cfgMqtt_t *cfg_mqtt, const char *devName, const char *version, HMSYSTEM *sys, uint32_t *utcTs, uint32_t *uptime) {
|
||||
mCfgMqtt = cfg_mqtt;
|
||||
mDevName = devName;
|
||||
mVersion = version;
|
||||
mSys = sys;
|
||||
mUtcTimestamp = utcTs;
|
||||
mUptime = uptime;
|
||||
mIntervalTimeout = 1;
|
||||
|
||||
mSendIvData.setup(sys, utcTs, &mSendList);
|
||||
|
@ -119,14 +121,14 @@ class PubMqtt {
|
|||
else { // send mqtt data in a fixed interval
|
||||
if(mIntervalTimeout == 0) {
|
||||
mIntervalTimeout = mCfgMqtt->interval;
|
||||
mSendList.push(RealTimeRunData_Debug);
|
||||
mSendList.push(sendListCmdIv(RealTimeRunData_Debug, NULL));
|
||||
sendIvData();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void tickerMinute() {
|
||||
snprintf(mVal, 40, "%ld", millis() / 1000);
|
||||
snprintf(mVal, 40, "%d", (*mUptime));
|
||||
publish(subtopics[MQTT_UPTIME], mVal);
|
||||
publish(subtopics[MQTT_RSSI], String(WiFi.RSSI()).c_str());
|
||||
publish(subtopics[MQTT_FREE_HEAP], String(ESP.getFreeHeap()).c_str());
|
||||
|
@ -165,10 +167,10 @@ class PubMqtt {
|
|||
publish(mSubTopic, mVal, true);
|
||||
}
|
||||
|
||||
void payloadEventListener(uint8_t cmd) {
|
||||
void payloadEventListener(uint8_t cmd, Inverter<> *iv) {
|
||||
if(mClient.connected()) { // prevent overflow if MQTT broker is not reachable but set
|
||||
if((0 == mCfgMqtt->interval) || (RealTimeRunData_Debug != cmd)) // no interval or no live data
|
||||
mSendList.push(cmd);
|
||||
mSendList.push(sendListCmdIv(cmd, iv));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -239,6 +241,10 @@ class PubMqtt {
|
|||
}
|
||||
}
|
||||
|
||||
void setZeroValuesEnable(void) {
|
||||
mZeroValues = true;
|
||||
}
|
||||
|
||||
private:
|
||||
void onConnect(bool sessionPreset) {
|
||||
DPRINTLN(DBG_INFO, F("MQTT connected"));
|
||||
|
@ -308,7 +314,7 @@ class PubMqtt {
|
|||
delete[] pyld;
|
||||
}
|
||||
|
||||
const char *p = topic;
|
||||
const char *p = topic + strlen(mCfgMqtt->topic);
|
||||
uint8_t pos = 0;
|
||||
uint8_t elm = 0;
|
||||
char tmp[30];
|
||||
|
@ -482,24 +488,22 @@ class PubMqtt {
|
|||
rec = iv->getRecordStruct(RealTimeRunData_Debug);
|
||||
|
||||
// inverter status
|
||||
uint8_t status = MQTT_STATUS_NOT_AVAIL_NOT_PROD;
|
||||
if (iv->isAvailable(*mUtcTimestamp)) {
|
||||
iv->isProducing(); // recalculate status
|
||||
if (iv->isAvailable())
|
||||
anyAvail = true;
|
||||
status = (iv->isProducing(*mUtcTimestamp)) ? MQTT_STATUS_AVAIL_PROD : MQTT_STATUS_AVAIL_NOT_PROD;
|
||||
}
|
||||
else // inverter is enabled but not available
|
||||
allAvail = false;
|
||||
|
||||
if(mLastIvState[id] != status) {
|
||||
if(mLastIvState[id] != iv->status) {
|
||||
// if status changed from producing to not producing send last data immediately
|
||||
if (MQTT_STATUS_AVAIL_PROD == mLastIvState[id])
|
||||
if (InverterStatus::WAS_PRODUCING == mLastIvState[id])
|
||||
sendData(iv, RealTimeRunData_Debug);
|
||||
|
||||
mLastIvState[id] = status;
|
||||
mLastIvState[id] = iv->status;
|
||||
changed = true;
|
||||
|
||||
snprintf(mSubTopic, 32 + MAX_NAME_LENGTH, "%s/available", iv->config->name);
|
||||
snprintf(mVal, 40, "%d", status);
|
||||
snprintf(mVal, 40, "%d", (uint8_t)iv->status);
|
||||
publish(mSubTopic, mVal, true);
|
||||
|
||||
snprintf(mSubTopic, 32 + MAX_NAME_LENGTH, "%s/last_success", iv->config->name);
|
||||
|
@ -545,7 +549,7 @@ class PubMqtt {
|
|||
switch (rec->assign[i].fieldId) {
|
||||
case FLD_YT:
|
||||
case FLD_YD:
|
||||
if ((rec->assign[i].ch == CH0) && (!iv->isProducing(*mUtcTimestamp))) // avoids returns to 0 on restart
|
||||
if ((rec->assign[i].ch == CH0) && (!iv->isProducing())) // avoids returns to 0 on restart
|
||||
continue;
|
||||
retained = true;
|
||||
break;
|
||||
|
@ -564,12 +568,13 @@ class PubMqtt {
|
|||
void sendIvData() {
|
||||
bool anyAvail = processIvStatus();
|
||||
if (mLastAnyAvail != anyAvail)
|
||||
mSendList.push(RealTimeRunData_Debug); // makes sure that total values are calculated
|
||||
mSendList.push(sendListCmdIv(RealTimeRunData_Debug, NULL)); // makes sure that total values are calculated
|
||||
|
||||
if(mSendList.empty())
|
||||
return;
|
||||
|
||||
mSendIvData.start();
|
||||
mSendIvData.start(mZeroValues);
|
||||
mZeroValues = false;
|
||||
mLastAnyAvail = anyAvail;
|
||||
}
|
||||
|
||||
|
@ -582,13 +587,14 @@ class PubMqtt {
|
|||
HMSYSTEM *mSys;
|
||||
PubMqttIvData<HMSYSTEM> mSendIvData;
|
||||
|
||||
uint32_t *mUtcTimestamp;
|
||||
uint32_t *mUtcTimestamp, *mUptime;
|
||||
uint32_t mRxCnt, mTxCnt;
|
||||
std::queue<uint8_t> mSendList;
|
||||
std::queue<sendListCmdIv> mSendList;
|
||||
std::queue<alarm_t> mAlarmList;
|
||||
subscriptionCb mSubscriptionCb;
|
||||
bool mLastAnyAvail;
|
||||
uint8_t mLastIvState[MAX_NUM_INVERTERS];
|
||||
bool mZeroValues;
|
||||
InverterStatus mLastIvState[MAX_NUM_INVERTERS];
|
||||
uint32_t mIvLastRTRpub[MAX_NUM_INVERTERS];
|
||||
uint16_t mIntervalTimeout;
|
||||
|
||||
|
|
|
@ -12,14 +12,21 @@
|
|||
|
||||
typedef std::function<void(const char *subTopic, const char *payload, bool retained)> pubMqttPublisherType;
|
||||
|
||||
struct sendListCmdIv {
|
||||
uint8_t cmd;
|
||||
Inverter<> *iv;
|
||||
sendListCmdIv(uint8_t a, Inverter<> *i) : cmd(a), iv(i) {}
|
||||
};
|
||||
|
||||
template<class HMSYSTEM>
|
||||
class PubMqttIvData {
|
||||
public:
|
||||
void setup(HMSYSTEM *sys, uint32_t *utcTs, std::queue<uint8_t> *sendList) {
|
||||
void setup(HMSYSTEM *sys, uint32_t *utcTs, std::queue<sendListCmdIv> *sendList) {
|
||||
mSys = sys;
|
||||
mUtcTimestamp = utcTs;
|
||||
mSendList = sendList;
|
||||
mState = IDLE;
|
||||
mZeroValues = false;
|
||||
|
||||
memset(mIvLastRTRpub, 0, MAX_NUM_INVERTERS * 4);
|
||||
mRTRDataHasBeenSent = false;
|
||||
|
@ -36,10 +43,11 @@ class PubMqttIvData {
|
|||
yield();
|
||||
}
|
||||
|
||||
bool start(void) {
|
||||
bool start(bool zeroValues = false) {
|
||||
if(IDLE != mState)
|
||||
return false;
|
||||
|
||||
mZeroValues = zeroValues;
|
||||
mRTRDataHasBeenSent = false;
|
||||
mState = START;
|
||||
return true;
|
||||
|
@ -59,10 +67,14 @@ class PubMqttIvData {
|
|||
|
||||
void stateStart() {
|
||||
mLastIvId = 0;
|
||||
mTotalFound = false;
|
||||
mSendTotalYd = true;
|
||||
mAllTotalFound = true;
|
||||
if(!mSendList->empty()) {
|
||||
mCmd = mSendList->front();
|
||||
mCmd = mSendList->front().cmd;
|
||||
mIvSend = mSendList->front().iv;
|
||||
|
||||
if((RealTimeRunData_Debug != mCmd) || !mRTRDataHasBeenSent) {
|
||||
if((RealTimeRunData_Debug != mCmd) || !mRTRDataHasBeenSent) { // send RealTimeRunData only once
|
||||
mSendTotals = (RealTimeRunData_Debug == mCmd);
|
||||
memset(mTotal, 0, sizeof(float) * 4);
|
||||
mState = FIND_NXT_IV;
|
||||
|
@ -88,12 +100,14 @@ class PubMqttIvData {
|
|||
mLastIvId++;
|
||||
|
||||
mPos = 0;
|
||||
if(found)
|
||||
if(found) {
|
||||
mIv->isProducing(); // recalculate status
|
||||
mState = SEND_DATA;
|
||||
else if(mSendTotals)
|
||||
} else if(mSendTotals && mTotalFound)
|
||||
mState = SEND_TOTALS;
|
||||
else {
|
||||
mSendList->pop();
|
||||
mZeroValues = false;
|
||||
mState = START;
|
||||
}
|
||||
}
|
||||
|
@ -109,40 +123,43 @@ class PubMqttIvData {
|
|||
if(mPos < rec->length) {
|
||||
bool retained = false;
|
||||
if (mCmd == RealTimeRunData_Debug) {
|
||||
switch (rec->assign[mPos].fieldId) {
|
||||
case FLD_YT:
|
||||
case FLD_YD:
|
||||
if ((rec->assign[mPos].ch == CH0) && (!mIv->isProducing(*mUtcTimestamp))) { // avoids returns to 0 on restart
|
||||
mPos++;
|
||||
return;
|
||||
}
|
||||
retained = true;
|
||||
break;
|
||||
}
|
||||
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) {
|
||||
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;
|
||||
}
|
||||
if(mIv->status > InverterStatus::STARTING) {
|
||||
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: {
|
||||
float val = mIv->getValue(mPos, rec);
|
||||
if(0 == val) // inverter restarted during day
|
||||
mSendTotalYd = false;
|
||||
else
|
||||
mTotal[2] += val;
|
||||
break;
|
||||
}
|
||||
case FLD_PDC:
|
||||
mTotal[3] += mIv->getValue(mPos, rec);
|
||||
break;
|
||||
}
|
||||
} else
|
||||
mAllTotalFound = false;
|
||||
}
|
||||
} else
|
||||
mIvLastRTRpub[mIv->id] = lastTs;
|
||||
|
||||
snprintf(mSubTopic, 32 + MAX_NAME_LENGTH, "%s/ch%d/%s", mIv->config->name, rec->assign[mPos].ch, fields[rec->assign[mPos].fieldId]);
|
||||
snprintf(mVal, 40, "%g", ah::round3(mIv->getValue(mPos, rec)));
|
||||
mPublish(mSubTopic, mVal, retained);
|
||||
if((mIvSend == mIv) || (NULL == mIvSend)) { // send only updated values, or all if the inverter is NULL
|
||||
snprintf(mSubTopic, 32 + MAX_NAME_LENGTH, "%s/ch%d/%s", mIv->config->name, rec->assign[mPos].ch, fields[rec->assign[mPos].fieldId]);
|
||||
snprintf(mVal, 40, "%g", ah::round3(mIv->getValue(mPos, rec)));
|
||||
mPublish(mSubTopic, mVal, retained);
|
||||
}
|
||||
mPos++;
|
||||
} else
|
||||
mState = FIND_NXT_IV;
|
||||
|
@ -152,6 +169,7 @@ class PubMqttIvData {
|
|||
|
||||
void stateSendTotals() {
|
||||
uint8_t fieldId;
|
||||
mRTRDataHasBeenSent = true;
|
||||
if(mPos < 4) {
|
||||
bool retained = true;
|
||||
switch (mPos) {
|
||||
|
@ -161,9 +179,17 @@ class PubMqttIvData {
|
|||
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:
|
||||
|
@ -177,10 +203,9 @@ class PubMqttIvData {
|
|||
mPos++;
|
||||
} else {
|
||||
mSendList->pop();
|
||||
mZeroValues = false;
|
||||
mState = START;
|
||||
}
|
||||
|
||||
mRTRDataHasBeenSent = true;
|
||||
}
|
||||
|
||||
HMSYSTEM *mSys;
|
||||
|
@ -191,18 +216,19 @@ class PubMqttIvData {
|
|||
|
||||
uint8_t mCmd;
|
||||
uint8_t mLastIvId;
|
||||
bool mSendTotals;
|
||||
bool mSendTotals, mTotalFound, mAllTotalFound, mSendTotalYd;
|
||||
float mTotal[4];
|
||||
|
||||
Inverter<> *mIv;
|
||||
Inverter<> *mIv, *mIvSend;
|
||||
uint8_t mPos;
|
||||
uint32_t mIvLastRTRpub[MAX_NUM_INVERTERS];
|
||||
bool mRTRDataHasBeenSent;
|
||||
|
||||
char mSubTopic[32 + MAX_NAME_LENGTH + 1];
|
||||
char mVal[40];
|
||||
bool mZeroValues; // makes sure that yield day is sent even if no inverter is online
|
||||
|
||||
std::queue<uint8_t> *mSendList;
|
||||
std::queue<sendListCmdIv> *mSendList;
|
||||
};
|
||||
|
||||
#endif /*__PUB_MQTT_IV_DATA_H__*/
|
||||
|
|
|
@ -28,7 +28,7 @@ class PubSerial {
|
|||
Inverter<> *iv = mSys->getInverterByPos(id);
|
||||
if (NULL != iv) {
|
||||
record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug);
|
||||
if (iv->isAvailable(*mUtcTimestamp)) {
|
||||
if (iv->isAvailable()) {
|
||||
DPRINTLN(DBG_INFO, "Iv: " + String(id));
|
||||
for (uint8_t i = 0; i < rec->length; i++) {
|
||||
if (0.0f != iv->getValue(i, rec)) {
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
#include "dbg.h"
|
||||
|
||||
DBG_CB mCb = NULL;
|
||||
bool mDebugEn = true;
|
||||
|
|
|
@ -39,6 +39,7 @@
|
|||
#ifdef ARDUINO
|
||||
#define DBG_CB std::function<void(String)>
|
||||
extern DBG_CB mCb;
|
||||
extern bool mDebugEn;
|
||||
|
||||
inline void registerDebugCb(DBG_CB cb) {
|
||||
mCb = cb;
|
||||
|
@ -48,22 +49,28 @@
|
|||
#define DSERIAL Serial
|
||||
#endif
|
||||
|
||||
inline void setDebugEn(bool en) {
|
||||
mDebugEn = en;
|
||||
}
|
||||
|
||||
//template <class T>
|
||||
inline void DBGPRINT(String str) { DSERIAL.print(str); if(NULL != mCb) mCb(str); }
|
||||
inline void DBGPRINT(String str, bool ser = true) { if(ser && mDebugEn) DSERIAL.print(str); if(NULL != mCb) mCb(str); }
|
||||
//template <class T>
|
||||
inline void DBGPRINTLN(String str) { DBGPRINT(str); DBGPRINT(F("\r\n")); }
|
||||
inline void DHEX(uint8_t b) {
|
||||
if( b<0x10 ) DSERIAL.print(F("0"));
|
||||
DSERIAL.print(b,HEX);
|
||||
inline void DBGPRINTLN(String str, bool ser = true) { DBGPRINT(str); DBGPRINT(F("\r\n")); }
|
||||
inline void DHEX(uint8_t b, bool ser = true) {
|
||||
if(ser && mDebugEn) {
|
||||
if( b<0x10 ) DSERIAL.print(F("0"));
|
||||
DSERIAL.print(b,HEX);
|
||||
}
|
||||
if(NULL != mCb) {
|
||||
if( b<0x10 ) mCb(F("0"));
|
||||
mCb(String(b, HEX));
|
||||
}
|
||||
}
|
||||
|
||||
inline void DBGHEXLN(uint8_t b) {
|
||||
DHEX(b);
|
||||
DBGPRINT(F("\r\n"));
|
||||
inline void DBGHEXLN(uint8_t b, bool ser = true) {
|
||||
DHEX(b, ser);
|
||||
DBGPRINT(F("\r\n"), ser);
|
||||
}
|
||||
/*inline void DHEX(uint16_t b) {
|
||||
if( b<0x10 ) DSERIAL.print(F("000"));
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
|
||||
#include "helper.h"
|
||||
#include "dbg.h"
|
||||
|
||||
namespace ah {
|
||||
void ip2Arr(uint8_t ip[], const char *ipStr) {
|
||||
|
@ -40,6 +41,15 @@ namespace ah {
|
|||
return String(str);
|
||||
}
|
||||
|
||||
String getDateTimeStrFile(time_t t) {
|
||||
char str[20];
|
||||
if(0 == t)
|
||||
sprintf(str, "na");
|
||||
else
|
||||
sprintf(str, "%04d-%02d-%02d_%02d-%02d-%02d", year(t), month(t), day(t), hour(t), minute(t), second(t));
|
||||
return String(str);
|
||||
}
|
||||
|
||||
String getTimeStr(time_t t) {
|
||||
char str[9];
|
||||
if(0 == t)
|
||||
|
@ -64,4 +74,12 @@ namespace ah {
|
|||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void dumpBuf(uint8_t buf[], uint8_t len) {
|
||||
for(uint8_t i = 0; i < len; i++) {
|
||||
DHEX(buf[i]);
|
||||
DBGPRINT(" ");
|
||||
}
|
||||
DBGPRINTLN("");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 2022 Ahoy, https://ahoydtu.de
|
||||
// 2023 Ahoy, https://ahoydtu.de
|
||||
// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
|
@ -20,13 +20,31 @@ static Timezone gTimezone(CEST, CET);
|
|||
|
||||
#define CHECK_MASK(a,b) ((a & b) == b)
|
||||
|
||||
#define CP_U32_LittleEndian(buf, v) ({ \
|
||||
uint8_t *b = buf; \
|
||||
b[0] = ((v >> 24) & 0xff); \
|
||||
b[1] = ((v >> 16) & 0xff); \
|
||||
b[2] = ((v >> 8) & 0xff); \
|
||||
b[3] = ((v ) & 0xff); \
|
||||
})
|
||||
|
||||
#define CP_U32_BigEndian(buf, v) ({ \
|
||||
uint8_t *b = buf; \
|
||||
b[3] = ((v >> 24) & 0xff); \
|
||||
b[2] = ((v >> 16) & 0xff); \
|
||||
b[1] = ((v >> 8) & 0xff); \
|
||||
b[0] = ((v ) & 0xff); \
|
||||
})
|
||||
|
||||
namespace ah {
|
||||
void ip2Arr(uint8_t ip[], const char *ipStr);
|
||||
void ip2Char(uint8_t ip[], char *str);
|
||||
double round3(double value);
|
||||
String getDateTimeStr(time_t t);
|
||||
String getDateTimeStrFile(time_t t);
|
||||
String getTimeStr(time_t t);
|
||||
uint64_t Serial2u64(const char *val);
|
||||
void dumpBuf(uint8_t buf[], uint8_t len);
|
||||
}
|
||||
|
||||
#endif /*__HELPER_H__*/
|
||||
|
|
221
src/utils/improv.h
Normal file
221
src/utils/improv.h
Normal file
|
@ -0,0 +1,221 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 2023 Ahoy, https://github.com/lumpapu/ahoy
|
||||
// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/4.0/deed
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#ifndef __IMPROV_H__
|
||||
#define __IMPROV_H__
|
||||
|
||||
#include <cstring>
|
||||
#include <functional>
|
||||
#include "dbg.h"
|
||||
#include "AsyncJson.h"
|
||||
|
||||
// https://www.improv-wifi.com/serial/
|
||||
// https://github.com/jnthas/improv-wifi-demo/blob/main/src/esp32-wifiimprov/esp32-wifiimprov.ino
|
||||
|
||||
// configure ESP through Serial interface
|
||||
#if !defined(ETHERNET)
|
||||
class Improv {
|
||||
public:
|
||||
void setup(IApp *app, const char *devName, const char *version) {
|
||||
mApp = app;
|
||||
mDevName = devName;
|
||||
mVersion = version;
|
||||
|
||||
mScanRunning = false;
|
||||
}
|
||||
|
||||
void tickSerial(void) {
|
||||
if(mScanRunning)
|
||||
getNetworks();
|
||||
|
||||
if(Serial.available() == 0)
|
||||
return;
|
||||
|
||||
uint8_t buf[40];
|
||||
uint8_t len = Serial.readBytes(buf, 40);
|
||||
|
||||
if(!checkPaket(&buf[0], len, [this](uint8_t type, uint8_t buf[], uint8_t len) {
|
||||
parsePayload(type, buf, len);
|
||||
})) {
|
||||
DBGPRINTLN(F("check paket failed"));
|
||||
}
|
||||
dumpBuf(buf, len);
|
||||
}
|
||||
|
||||
private:
|
||||
enum State : uint8_t {
|
||||
STATE_STOPPED = 0x00,
|
||||
STATE_AWAITING_AUTHORIZATION = 0x01,
|
||||
STATE_AUTHORIZED = 0x02,
|
||||
STATE_PROVISIONING = 0x03,
|
||||
STATE_PROVISIONED = 0x04,
|
||||
};
|
||||
|
||||
enum Command : uint8_t {
|
||||
UNKNOWN = 0x00,
|
||||
WIFI_SETTINGS = 0x01,
|
||||
IDENTIFY = 0x02,
|
||||
GET_CURRENT_STATE = 0x02,
|
||||
GET_DEVICE_INFO = 0x03,
|
||||
GET_WIFI_NETWORKS = 0x04,
|
||||
BAD_CHECKSUM = 0xFF,
|
||||
};
|
||||
|
||||
enum ImprovSerialType : uint8_t {
|
||||
TYPE_CURRENT_STATE = 0x01,
|
||||
TYPE_ERROR_STATE = 0x02,
|
||||
TYPE_RPC = 0x03,
|
||||
TYPE_RPC_RESPONSE = 0x04
|
||||
};
|
||||
|
||||
void dumpBuf(uint8_t buf[], uint8_t len) {
|
||||
for(uint8_t i = 0; i < len; i++) {
|
||||
DHEX(buf[i], false);
|
||||
DBGPRINT(" ", false);
|
||||
}
|
||||
DBGPRINTLN("", false);
|
||||
}
|
||||
|
||||
inline uint8_t buildChecksum(uint8_t buf[], uint8_t len) {
|
||||
uint8_t calc = 0;
|
||||
for(uint8_t i = 0; i < len; i++) {
|
||||
calc += buf[i];
|
||||
}
|
||||
return calc;
|
||||
}
|
||||
|
||||
inline bool checkChecksum(uint8_t buf[], uint8_t len) {
|
||||
/*DHEX(buf[len], false);
|
||||
DBGPRINT(F(" == "), false);
|
||||
DBGHEXLN(buildChecksum(buf, len), false);*/
|
||||
return ((buildChecksum(buf, len)) == buf[len]);
|
||||
}
|
||||
|
||||
bool checkPaket(uint8_t buf[], uint8_t len, std::function<void(uint8_t type, uint8_t b[], uint8_t l)> cb) {
|
||||
if(len < 11)
|
||||
return false;
|
||||
|
||||
if(0 != strncmp((char*)buf, "IMPROV", 6))
|
||||
return false;
|
||||
|
||||
// verison check (only version 1 is supported!)
|
||||
if(0x01 != buf[6])
|
||||
return false;
|
||||
|
||||
if(!checkChecksum(buf, (9 + buf[8])))
|
||||
return false;
|
||||
|
||||
cb(buf[7], &buf[9], buf[8]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
uint8_t char2Improv(const char *str, uint8_t buf[]) {
|
||||
uint8_t len = strlen(str);
|
||||
buf[0] = len;
|
||||
for(uint8_t i = 1; i <= len; i++) {
|
||||
buf[i] = (uint8_t)str[i-1];
|
||||
}
|
||||
return len + 1;
|
||||
}
|
||||
|
||||
void sendDevInfo(void) {
|
||||
uint8_t buf[50];
|
||||
buf[7] = TYPE_RPC_RESPONSE;
|
||||
buf[9] = GET_DEVICE_INFO; // repsonse to cmd
|
||||
uint8_t p = 11;
|
||||
// firmware name
|
||||
p += char2Improv("AhoyDTU", &buf[p]);
|
||||
// firmware version
|
||||
p += char2Improv(mVersion, &buf[p]);
|
||||
// chip variant
|
||||
#if defined(ESP32)
|
||||
p += char2Improv("ESP32", &buf[p]);
|
||||
#else
|
||||
p += char2Improv("ESP8266", &buf[p]);
|
||||
#endif
|
||||
// device name
|
||||
p += char2Improv(mDevName, &buf[p]);
|
||||
|
||||
buf[10] = p - 11; // sub length
|
||||
buf[8] = p - 9; // paket length
|
||||
|
||||
sendPaket(buf, p);
|
||||
}
|
||||
|
||||
void getNetworks(void) {
|
||||
if(!mScanRunning)
|
||||
mApp->scanAvailNetworks();
|
||||
|
||||
JsonObject obj;
|
||||
if(!mApp->getAvailNetworks(obj))
|
||||
return;
|
||||
|
||||
mScanRunning = false;
|
||||
|
||||
uint8_t buf[50];
|
||||
buf[7] = TYPE_RPC_RESPONSE;
|
||||
buf[9] = GET_WIFI_NETWORKS; // repsonse to cmd
|
||||
uint8_t p = 11;
|
||||
|
||||
JsonArray arr = obj[F("networks")];
|
||||
for(uint8_t i = 0; i < arr.size(); i++) {
|
||||
buf[p++] = strlen(arr[i][F("ssid")]);
|
||||
// ssid
|
||||
p += char2Improv(arr[i][F("ssid")], &buf[p]);
|
||||
buf[p++] = String(arr[i][F("rssi")]).length();
|
||||
// rssi
|
||||
p += char2Improv(String(arr[i][F("rssi")]).c_str(), &buf[p]);
|
||||
|
||||
buf[10] = p - 11; // sub length
|
||||
buf[8] = p - 9; // paket length
|
||||
|
||||
sendPaket(buf, p);
|
||||
}
|
||||
}
|
||||
|
||||
void setState(uint8_t state) {
|
||||
uint8_t buf[20];
|
||||
buf[7] = TYPE_CURRENT_STATE;
|
||||
buf[8] = 0x01;
|
||||
buf[9] = state;
|
||||
sendPaket(buf, 10);
|
||||
}
|
||||
|
||||
void sendPaket(uint8_t buf[], uint8_t len) {
|
||||
buf[0] = 'I';
|
||||
buf[1] = 'M';
|
||||
buf[2] = 'P';
|
||||
buf[3] = 'R';
|
||||
buf[4] = 'O';
|
||||
buf[5] = 'V';
|
||||
buf[6] = 1; // protocol version
|
||||
|
||||
buf[len] = buildChecksum(buf, len);
|
||||
len++;
|
||||
Serial.write(buf, len);
|
||||
dumpBuf(buf, len);
|
||||
}
|
||||
|
||||
void parsePayload(uint8_t type, uint8_t buf[], uint8_t len) {
|
||||
if(TYPE_RPC == type) {
|
||||
if(GET_CURRENT_STATE == buf[0]) {
|
||||
setDebugEn(false);
|
||||
setState(STATE_AUTHORIZED);
|
||||
}
|
||||
else if(GET_DEVICE_INFO == buf[0])
|
||||
sendDevInfo();
|
||||
else if(GET_WIFI_NETWORKS == buf[0])
|
||||
getNetworks();
|
||||
}
|
||||
}
|
||||
|
||||
IApp *mApp;
|
||||
const char *mDevName, *mVersion;
|
||||
bool mScanRunning;
|
||||
};
|
||||
#endif
|
||||
|
||||
#endif /*__IMPROV_H__*/
|
|
@ -31,9 +31,9 @@ namespace ah {
|
|||
public:
|
||||
Scheduler() {}
|
||||
|
||||
void setup() {
|
||||
void setup(bool directStart) {
|
||||
mUptime = 0;
|
||||
mTimestamp = 0;
|
||||
mTimestamp = (directStart) ? 1 : 0;
|
||||
mMax = 0;
|
||||
mPrevMillis = millis();
|
||||
resetTicker();
|
||||
|
@ -117,6 +117,7 @@ namespace ah {
|
|||
|
||||
protected:
|
||||
uint32_t mTimestamp;
|
||||
uint32_t mUptime;
|
||||
|
||||
private:
|
||||
inline uint8_t addTicker(scdCb c, uint32_t timeout, uint32_t reload, bool isTimestamp, const char *name) {
|
||||
|
@ -162,7 +163,6 @@ namespace ah {
|
|||
sP mTicker[MAX_NUM_TICKER];
|
||||
bool mTickerInUse[MAX_NUM_TICKER];
|
||||
uint32_t mMillis, mPrevMillis, mDiff;
|
||||
uint32_t mUptime;
|
||||
uint8_t mDiffSeconds;
|
||||
uint8_t mMax;
|
||||
};
|
||||
|
|
|
@ -28,9 +28,10 @@
|
|||
#endif
|
||||
|
||||
const uint8_t acList[] = {FLD_UAC, FLD_IAC, FLD_PAC, FLD_F, FLD_PF, FLD_T, FLD_YT, FLD_YD, FLD_PDC, FLD_EFF, FLD_Q};
|
||||
const uint8_t acListHmt[] = {FLD_UAC_1N, FLD_IAC_1, FLD_PAC, FLD_F, FLD_PF, FLD_T, FLD_YT, FLD_YD, FLD_PDC, FLD_EFF, FLD_Q};
|
||||
const uint8_t dcList[] = {FLD_UDC, FLD_IDC, FLD_PDC, FLD_YD, FLD_YT, FLD_IRR};
|
||||
|
||||
template <class HMSYSTEM>
|
||||
template<class HMSYSTEM, class HMRADIO>
|
||||
class RestApi {
|
||||
public:
|
||||
RestApi() {
|
||||
|
@ -41,10 +42,11 @@ class RestApi {
|
|||
nr = 0;
|
||||
}
|
||||
|
||||
void setup(IApp *app, HMSYSTEM *sys, AsyncWebServer *srv, settings_t *config) {
|
||||
void setup(IApp *app, HMSYSTEM *sys, HMRADIO *radio, AsyncWebServer *srv, settings_t *config) {
|
||||
mApp = app;
|
||||
mSrv = srv;
|
||||
mSys = sys;
|
||||
mRadio = radio;
|
||||
mConfig = config;
|
||||
mSrv->on("/api", HTTP_GET, std::bind(&RestApi::onApi, this, std::placeholders::_1));
|
||||
mSrv->on("/api", HTTP_POST, std::bind(&RestApi::onApiPost, this, std::placeholders::_1)).onBody(
|
||||
|
@ -188,9 +190,12 @@ class RestApi {
|
|||
response = request->beginResponse(200, F("application/json; charset=utf-8"), tmp);
|
||||
}
|
||||
|
||||
String filename = ah::getDateTimeStrFile(gTimezone.toLocal(mApp->getTimestamp()));
|
||||
filename += "_v" + String(mApp->getVersion());
|
||||
|
||||
response->addHeader("Content-Type", "application/octet-stream");
|
||||
response->addHeader("Content-Description", "File Transfer");
|
||||
response->addHeader("Content-Disposition", "attachment; filename=ahoy_setup.json");
|
||||
response->addHeader("Content-Disposition", "attachment; filename=" + filename + "_ahoy_setup.json");
|
||||
request->send(response);
|
||||
fp.close();
|
||||
}
|
||||
|
@ -198,6 +203,9 @@ class RestApi {
|
|||
void getGeneric(AsyncWebServerRequest *request, JsonObject obj) {
|
||||
obj[F("wifi_rssi")] = (WiFi.status() != WL_CONNECTED) ? 0 : WiFi.RSSI();
|
||||
obj[F("ts_uptime")] = mApp->getUptime();
|
||||
obj[F("ts_now")] = mApp->getTimestamp();
|
||||
obj[F("version")] = String(mApp->getVersion());
|
||||
obj[F("build")] = String(AUTO_GIT_HASH);
|
||||
obj[F("menu_prot")] = mApp->getProtection(request);
|
||||
obj[F("menu_mask")] = (uint16_t)(mConfig->sys.protectionMask );
|
||||
obj[F("menu_protEn")] = (bool) (strlen(mConfig->sys.adminPwd) > 0);
|
||||
|
@ -212,9 +220,12 @@ class RestApi {
|
|||
void getSysInfo(AsyncWebServerRequest *request, JsonObject obj) {
|
||||
#if !defined(ETHERNET)
|
||||
obj[F("ssid")] = mConfig->sys.stationSsid;
|
||||
obj[F("ap_pwd")] = mConfig->sys.apPwd;
|
||||
obj[F("hidd")] = mConfig->sys.isHidden;
|
||||
#endif /* !defined(ETHERNET) */
|
||||
obj[F("device_name")] = mConfig->sys.deviceName;
|
||||
obj[F("dark_mode")] = (bool)mConfig->sys.darkMode;
|
||||
obj[F("sched_reboot")] = (bool)mConfig->sys.schedReboot;
|
||||
|
||||
obj[F("mac")] = WiFi.macAddress();
|
||||
obj[F("hostname")] = mConfig->sys.deviceName;
|
||||
|
@ -228,7 +239,7 @@ class RestApi {
|
|||
obj[F("sketch_used")] = ESP.getSketchSize() / 1024; // in kb
|
||||
getGeneric(request, obj);
|
||||
|
||||
getRadio(obj.createNestedObject(F("radio")));
|
||||
getRadioNrf(obj.createNestedObject(F("radio")));
|
||||
getStatistics(obj.createNestedObject(F("statistics")));
|
||||
|
||||
#if defined(ESP32)
|
||||
|
@ -300,8 +311,8 @@ class RestApi {
|
|||
obj[F("rx_fail")] = stat->rxFail;
|
||||
obj[F("rx_fail_answer")] = stat->rxFailNoAnser;
|
||||
obj[F("frame_cnt")] = stat->frmCnt;
|
||||
obj[F("tx_cnt")] = mSys->Radio.mSendCnt;
|
||||
obj[F("retransmits")] = mSys->Radio.mRetransmits;
|
||||
obj[F("tx_cnt")] = mRadio->mSendCnt;
|
||||
obj[F("retransmits")] = mRadio->mRetransmits;
|
||||
}
|
||||
|
||||
void getInverterList(JsonObject obj) {
|
||||
|
@ -320,7 +331,7 @@ class RestApi {
|
|||
obj2[F("version")] = String(iv->getFwVersion());
|
||||
|
||||
for(uint8_t j = 0; j < iv->channels; j ++) {
|
||||
obj2[F("ch_yield_cor")][j] = iv->config->yieldCor[j];
|
||||
obj2[F("ch_yield_cor")][j] = (double)iv->config->yieldCor[j];
|
||||
obj2[F("ch_name")][j] = iv->config->chName[j];
|
||||
obj2[F("ch_max_pwr")][j] = iv->config->chMaxPwr[j];
|
||||
}
|
||||
|
@ -332,6 +343,8 @@ class RestApi {
|
|||
obj[F("rstMid")] = (bool)mConfig->inst.rstYieldMidNight;
|
||||
obj[F("rstNAvail")] = (bool)mConfig->inst.rstValsNotAvail;
|
||||
obj[F("rstComStop")] = (bool)mConfig->inst.rstValsCommStop;
|
||||
obj[F("strtWthtTm")] = (bool)mConfig->inst.startWithoutTime;
|
||||
obj[F("yldEff")] = mConfig->inst.yieldEffiency;
|
||||
}
|
||||
|
||||
void getInverter(JsonObject obj, uint8_t id) {
|
||||
|
@ -345,6 +358,7 @@ class RestApi {
|
|||
obj[F("version")] = String(iv->getFwVersion());
|
||||
obj[F("power_limit_read")] = ah::round3(iv->actPowerLimit);
|
||||
obj[F("ts_last_success")] = rec->ts;
|
||||
obj[F("generation")] = iv->ivGen;
|
||||
|
||||
JsonArray ch = obj.createNestedArray("ch");
|
||||
|
||||
|
@ -352,10 +366,17 @@ class RestApi {
|
|||
uint8_t pos;
|
||||
obj[F("ch_name")][0] = "AC";
|
||||
JsonArray ch0 = ch.createNestedArray();
|
||||
if(IV_HMT == iv->ivGen) {
|
||||
for (uint8_t fld = 0; fld < sizeof(acListHmt); fld++) {
|
||||
pos = (iv->getPosByChFld(CH0, acListHmt[fld], rec));
|
||||
ch0[fld] = (0xff != pos) ? ah::round3(iv->getValue(pos, rec)) : 0.0;
|
||||
}
|
||||
} else {
|
||||
for (uint8_t fld = 0; fld < sizeof(acList); fld++) {
|
||||
pos = (iv->getPosByChFld(CH0, acList[fld], rec));
|
||||
ch0[fld] = (0xff != pos) ? ah::round3(iv->getValue(pos, rec)) : 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
// DC
|
||||
for(uint8_t j = 0; j < iv->channels; j ++) {
|
||||
|
@ -382,6 +403,7 @@ class RestApi {
|
|||
void getNtp(JsonObject obj) {
|
||||
obj[F("addr")] = String(mConfig->ntp.addr);
|
||||
obj[F("port")] = String(mConfig->ntp.port);
|
||||
obj[F("interval")] = String(mConfig->ntp.interval);
|
||||
}
|
||||
|
||||
void getSun(JsonObject obj) {
|
||||
|
@ -403,11 +425,19 @@ class RestApi {
|
|||
obj[F("led_high_active")] = mConfig->led.led_high_active;
|
||||
}
|
||||
|
||||
void getRadio(JsonObject obj) {
|
||||
void getRadioCmt(JsonObject obj) {
|
||||
obj[F("csb")] = mConfig->cmt.pinCsb;
|
||||
obj[F("fcsb")] = mConfig->cmt.pinFcsb;
|
||||
obj[F("irq")] = mConfig->cmt.pinIrq;
|
||||
obj[F("en")] = (bool) mConfig->cmt.enabled;
|
||||
}
|
||||
|
||||
void getRadioNrf(JsonObject obj) {
|
||||
obj[F("power_level")] = mConfig->nrf.amplifierPower;
|
||||
obj[F("isconnected")] = mSys->Radio.isChipConnected();
|
||||
obj[F("DataRate")] = mSys->Radio.getDataRate();
|
||||
obj[F("isPVariant")] = mSys->Radio.isPVariant();
|
||||
obj[F("isconnected")] = mRadio->isChipConnected();
|
||||
obj[F("DataRate")] = mRadio->getDataRate();
|
||||
obj[F("isPVariant")] = mRadio->isPVariant();
|
||||
obj[F("en")] = (bool) mConfig->nrf.enabled;
|
||||
}
|
||||
|
||||
void getSerial(JsonObject obj) {
|
||||
|
@ -458,16 +488,16 @@ class RestApi {
|
|||
invObj[F("id")] = i;
|
||||
invObj[F("name")] = String(iv->config->name);
|
||||
invObj[F("version")] = String(iv->getFwVersion());
|
||||
invObj[F("is_avail")] = iv->isAvailable(mApp->getTimestamp());
|
||||
invObj[F("is_producing")] = iv->isProducing(mApp->getTimestamp());
|
||||
invObj[F("is_avail")] = iv->isAvailable();
|
||||
invObj[F("is_producing")] = iv->isProducing();
|
||||
invObj[F("ts_last_success")] = iv->getLastTs(rec);
|
||||
}
|
||||
}
|
||||
|
||||
JsonArray warn = obj.createNestedArray(F("warnings"));
|
||||
if(!mSys->Radio.isChipConnected())
|
||||
warn.add(F("your NRF24 module can't be reached, check the wiring and pinout"));
|
||||
else if(!mSys->Radio.isPVariant())
|
||||
if(!mRadio->isChipConnected() && mConfig->nrf.enabled)
|
||||
warn.add(F("your NRF24 module can't be reached, check the wiring, pinout and enable"));
|
||||
else if(!mRadio->isPVariant() && mConfig->nrf.enabled)
|
||||
warn.add(F("your NRF24 module isn't a plus version(+), maybe incompatible"));
|
||||
if(!mApp->getSettingsValid())
|
||||
warn.add(F("your settings are invalid"));
|
||||
|
@ -496,7 +526,8 @@ class RestApi {
|
|||
getNtp(obj.createNestedObject(F("ntp")));
|
||||
getSun(obj.createNestedObject(F("sun")));
|
||||
getPinout(obj.createNestedObject(F("pinout")));
|
||||
getRadio(obj.createNestedObject(F("radio")));
|
||||
getRadioCmt(obj.createNestedObject(F("radioCmt")));
|
||||
getRadioNrf(obj.createNestedObject(F("radioNrf")));
|
||||
getSerial(obj.createNestedObject(F("serial")));
|
||||
getStaticIp(obj.createNestedObject(F("static_ip")));
|
||||
getDisplay(obj.createNestedObject(F("display")));
|
||||
|
@ -620,6 +651,7 @@ class RestApi {
|
|||
|
||||
IApp *mApp;
|
||||
HMSYSTEM *mSys;
|
||||
HMRADIO *mRadio;
|
||||
AsyncWebServer *mSrv;
|
||||
settings_t *mConfig;
|
||||
|
||||
|
|
|
@ -78,8 +78,10 @@ function parseNav(obj) {
|
|||
if(i == 2)
|
||||
continue;
|
||||
var l = document.getElementById("nav"+i);
|
||||
if(window.location.pathname == "/" + l.href.substring(0, l.href.indexOf("?")).split('/').pop())
|
||||
l.classList.add("active");
|
||||
if(window.location.pathname == "/" + l.href.substring(0, l.href.indexOf("?")).split('/').pop()) {
|
||||
if((i != 8 )&& (i != 9))
|
||||
l.classList.add("active");
|
||||
}
|
||||
|
||||
if(obj["menu_protEn"]) {
|
||||
if(obj["menu_prot"]) {
|
||||
|
@ -117,6 +119,10 @@ function parseRssi(obj) {
|
|||
document.getElementById("wifiicon").replaceChildren(svg(icon, 32, 32, "wifi", obj["wifi_rssi"]));
|
||||
}
|
||||
|
||||
function toIsoDateStr(d) {
|
||||
return new Date(d.getTime() + (d.getTimezoneOffset() * -60000)).toISOString().substring(0, 19).replace('T', ', ');
|
||||
}
|
||||
|
||||
function setHide(id, hide) {
|
||||
var elm = document.getElementById(id);
|
||||
if(hide) {
|
||||
|
|
|
@ -62,10 +62,6 @@
|
|||
getAjax("/api/setup", apiCb, "POST", JSON.stringify(obj));
|
||||
}
|
||||
|
||||
function ts2Span(ts) {
|
||||
return span(new Date(ts * 1000).toLocaleString('de-DE'));
|
||||
}
|
||||
|
||||
function parseGeneric(obj) {
|
||||
if(exeOnce)
|
||||
parseESP(obj);
|
||||
|
@ -88,8 +84,12 @@
|
|||
+ ("0"+min).substr(-2) + ":"
|
||||
+ ("0"+sec).substr(-2);
|
||||
var dSpan = document.getElementById("date");
|
||||
if(0 != obj["ts_now"])
|
||||
dSpan.innerHTML = date.toLocaleString('de-DE');
|
||||
if(0 != obj["ts_now"]) {
|
||||
if(obj["ts_now"] < 1680000000)
|
||||
setTime();
|
||||
else
|
||||
dSpan.innerHTML = toIsoDateStr(date);
|
||||
}
|
||||
else {
|
||||
dSpan.innerHTML = "";
|
||||
var e = inp("set", "sync from browser", 0, ["btn"], "set", "button");
|
||||
|
@ -153,7 +153,7 @@
|
|||
if(false == i["is_avail"]) {
|
||||
if(i["ts_last_success"] > 0) {
|
||||
var date = new Date(i["ts_last_success"] * 1000);
|
||||
p.append(span("-> last successful transmission: " + date.toLocaleString('de-DE')), br());
|
||||
p.append(span("-> last successful transmission: " + toIsoDateStr(date)), br());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -186,7 +186,7 @@
|
|||
|
||||
function tick() {
|
||||
if(0 != ts)
|
||||
document.getElementById("date").innerHTML = (new Date((++ts) * 1000)).toLocaleString('de-DE');
|
||||
document.getElementById("date").innerHTML = toIsoDateStr((new Date((++ts) * 1000)));
|
||||
if(++tickCnt >= 10) {
|
||||
tickCnt = 0;
|
||||
getAjax('/api/index', parse);
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
<div id="wrapper">
|
||||
<div id="content">
|
||||
<form method="post" action="/save" id="settings">
|
||||
<fieldset>
|
||||
<button type="button" class="s_collapsible mt-4">System Config</button>
|
||||
<div class="s_content">
|
||||
<fieldset class="mb-2">
|
||||
|
@ -17,10 +18,14 @@
|
|||
<div class="col-12 col-sm-3">Device Name</div>
|
||||
<div class="col-12 col-sm-9"><input type="text" name="device"/></div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-8 col-sm-3">Reboot Ahoy at midnight</div>
|
||||
<div class="col-4 col-sm-9"><input type="checkbox" name="schedReboot"/></div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-8 col-sm-3">Dark Mode</div>
|
||||
<div class="col-4 col-sm-9"><input type="checkbox" name="darkMode"/></div>
|
||||
<div class="col-8 col-sm-3">(empty browser cache or use CTRL + F5 after reboot to apply this setting)</div>
|
||||
<div class="col-12">(empty browser cache or use CTRL + F5 after reboot to apply this setting)</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset class="mb-4">
|
||||
|
@ -31,6 +36,9 @@
|
|||
<p class="des">Radio (NRF24L01+)</p>
|
||||
<div id="rf24"></div>
|
||||
|
||||
<p class="des">Radio (CMT2300A)</p>
|
||||
<div id="cmt"><div class="col-12">(ESP32 only)</div></div>
|
||||
|
||||
<p class="des">Serial Console</p>
|
||||
<div class="row mb-3">
|
||||
<div class="col-8 col-sm-3">print inverter data</div>
|
||||
|
@ -51,6 +59,12 @@
|
|||
<div class="s_content">
|
||||
<fieldset class="mb-2">
|
||||
<legend class="des">WiFi</legend>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-12 col-sm-3 my-2">AP Password (min. length: 8)</div>
|
||||
<div class="col-12 col-sm-9"><input type="text" name="ap_pwd" minlength="8" /></div>
|
||||
</div>
|
||||
|
||||
<p>Enter the credentials to your prefered WiFi station. After rebooting the device tries to connect with this information.</p>
|
||||
|
||||
<div class="row mb-3">
|
||||
|
@ -70,6 +84,10 @@
|
|||
<div class="col-12 col-sm-3 my-2">SSID</div>
|
||||
<div class="col-12 col-sm-9"><input type="text" name="ssid"/></div>
|
||||
</div>
|
||||
<div class="row mb-2 mb-sm-3">
|
||||
<div class="col-12 col-sm-3">SSID is hidden</div>
|
||||
<div class="col-12 col-sm-9"><input type="checkbox" name="hidd"/></div>
|
||||
</div>
|
||||
<div class="row mb-2 mb-sm-3">
|
||||
<div class="col-12 col-sm-3 my-2">Password</div>
|
||||
<div class="col-12 col-sm-9"><input type="password" name="pwd" value="{PWD}"/></div>
|
||||
|
@ -154,6 +172,14 @@
|
|||
<div class="col-8 col-sm-3">Reset values when inverter status is 'not available'</div>
|
||||
<div class="col-4 col-sm-9"><input type="checkbox" name="invRstNotAvail"/></div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-8 col-sm-3">Start without time sync (useful in AP-Only-Mode)</div>
|
||||
<div class="col-4 col-sm-9"><input type="checkbox" name="strtWthtTm"/></div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-8 col-sm-3">Yield Effiency (should be between 0.95 and 0.96)</div>
|
||||
<div class="col-4 col-sm-9"><input type="number" name="yldEff" step="any"/></div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
|
@ -170,13 +196,21 @@
|
|||
<div class="col-12 col-sm-9"><input type="number" name="ntpPort"/></div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-12 col-sm-3 my-2">set system time</div>
|
||||
<div class="col-12 col-sm-3 my-2">NTP Intervall (in Minutes, min. 5 Minutes)</div>
|
||||
<div class="col-12 col-sm-9"><input type="number" name="ntpIntvl"/></div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-12 col-sm-3 my-2">set System time</div>
|
||||
<div class="col-12 col-sm-9">
|
||||
<input type="button" name="ntpBtn" id="ntpBtn" class="btn" value="from browser" onclick="setTime()"/>
|
||||
<input type="button" name="ntpSync" id="ntpSync" class="btn" value="sync NTP" onclick="syncTime()"/>
|
||||
<input type="button" name="ntpSync" id="ntpSync" class="btn" value="sync NTP" onclick="syncTime()"/><br/>
|
||||
<span id="apiResultNtp"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-12 col-sm-3 my-2">System Time</div>
|
||||
<div class="col-12 col-sm-9 my-2"><span id="date"></span></div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
|
@ -258,7 +292,7 @@
|
|||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-12 col-sm-3 my-2">Luminance</div>
|
||||
<div class="col-12 col-sm-9"><input type="number" name="disp_cont" min="0" max="100"></select></div>
|
||||
<div class="col-12 col-sm-9"><input type="number" name="disp_cont" min="0" max="255"></select></div>
|
||||
</div>
|
||||
<p class="des">Pinout</p>
|
||||
<div id="dispPins"></div>
|
||||
|
@ -303,6 +337,7 @@
|
|||
<script type="text/javascript">
|
||||
var highestId = 0;
|
||||
var maxInv = 0;
|
||||
var ts = 0;
|
||||
|
||||
var esp8266pins = [
|
||||
[255, "off / default"],
|
||||
|
@ -406,7 +441,7 @@
|
|||
[1, "high active"],
|
||||
];
|
||||
|
||||
const re = /11[2,4,6]1.*/;
|
||||
const re = /1[1,3][2,4,6,8][1,2,4].*/;
|
||||
|
||||
window.onload = function() {
|
||||
for(it of document.getElementsByClassName("s_collapsible")) {
|
||||
|
@ -428,7 +463,7 @@
|
|||
|
||||
document.getElementById("btnAdd").addEventListener("click", function() {
|
||||
if(highestId <= (maxInv-1)) {
|
||||
ivHtml(JSON.parse('{"enabled":true,"name":"","serial":"","channels":4,"ch_max_pwr":[0,0,0,0],"ch_name":["","","",""],"ch_yield_cor":[0,0,0,0]}'), highestId);
|
||||
ivHtml(JSON.parse('{"enabled":true,"name":"","serial":"","channels":6,"ch_max_pwr":[0,0,0,0,0,0],"ch_name":["","","","","",""],"ch_yield_cor":[0,0,0,0,0,0]}'), highestId);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -444,11 +479,17 @@
|
|||
function apiCbNtp(obj) {
|
||||
var e = document.getElementById("apiResultNtp");
|
||||
if(obj["success"])
|
||||
e.innerHTML = "command excuted";
|
||||
e.innerHTML = "command excuted, set new time ...";
|
||||
else
|
||||
e.innerHTML = "Error: " + obj["error"];
|
||||
}
|
||||
|
||||
function apiCbNtp2(obj) {
|
||||
var e = document.getElementById("apiResultNtp");
|
||||
var date = new Date(obj["ts_now"] * 1000);
|
||||
e.innerHTML = "synced at: " + toIsoDateStr(date) + ", difference: " + (ts - obj["ts_now"]) + "ms";
|
||||
}
|
||||
|
||||
function apiCbMqtt(obj) {
|
||||
var e = document.getElementById("apiResultMqtt");
|
||||
if(obj["success"])
|
||||
|
@ -463,6 +504,7 @@
|
|||
obj.cmd = "set_time";
|
||||
obj.val = parseInt(date.getTime() / 1000);
|
||||
getAjax("/api/setup", apiCbNtp, "POST", JSON.stringify(obj));
|
||||
setTimeout(function() {getAjax('/api/index', apiCbNtp2)}, 2000);
|
||||
}
|
||||
|
||||
function scan() {
|
||||
|
@ -476,6 +518,7 @@
|
|||
var obj = new Object();
|
||||
obj.cmd = "sync_ntp";
|
||||
getAjax("/api/setup", apiCbNtp, "POST", JSON.stringify(obj));
|
||||
setTimeout(function() {getAjax('/api/index', apiCbNtp2)}, 2000);
|
||||
}
|
||||
|
||||
function sendDiscoveryConfig() {
|
||||
|
@ -535,7 +578,7 @@
|
|||
addr.addEventListener(evt, (e) => {
|
||||
var serial = addr.value.substring(0,4);
|
||||
var max = 0;
|
||||
for(var i=0;i<4;i++) {
|
||||
for(var i=0;i<6;i++) {
|
||||
setHide(id+"ModPwr"+i, true);
|
||||
setHide(id+"ModName"+i, true);
|
||||
setHide(id+"YieldCor"+i, true);
|
||||
|
@ -545,12 +588,13 @@
|
|||
setHide("row"+id+"YieldCor", true);
|
||||
|
||||
if(serial.charAt(0) == 1) {
|
||||
if((serial.charAt(1) == 0) || (serial.charAt(1) == 1)) {
|
||||
if((serial.charAt(3) == 1) || (serial.charAt(3) == 2)) {
|
||||
if((serial.charAt(1) == 0) || (serial.charAt(1) == 1) || (serial.charAt(1) == 3)) {
|
||||
if((serial.charAt(3) == 1) || (serial.charAt(3) == 2) || (serial.charAt(3) == 4)) {
|
||||
switch(serial.charAt(2)) {
|
||||
case "2": max = 1; break;
|
||||
case "4": max = 2; break;
|
||||
case "6": max = 4; break;
|
||||
case "8": max = 6; break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -574,7 +618,7 @@
|
|||
for(var j of [
|
||||
["ModPwr", "ch_max_pwr", "Max Module Power (Wp)", 4, "[0-9]+"],
|
||||
["ModName", "ch_name", "Module Name", 15, null],
|
||||
["YieldCor", "ch_yield_cor", "Yield Total Correction [kWh]", 8, "[0-9-]+"]]) {
|
||||
["YieldCor", "ch_yield_cor", "Yield Total Correction [kWh]", 8, "[0-9-\.]+"]]) {
|
||||
|
||||
var cl = (re.test(obj["serial"])) ? "" : " hide";
|
||||
|
||||
|
@ -603,16 +647,19 @@
|
|||
}
|
||||
|
||||
function ivGlob(obj) {
|
||||
for(var i of [["invInterval", "interval"], ["invRetry", "retries"]])
|
||||
for(var i of [["invInterval", "interval"], ["invRetry", "retries"], ["yldEff", "yldEff"]])
|
||||
document.getElementsByName(i[0])[0].value = obj[i[1]];
|
||||
for(var i of [["Mid", "rstMid"], ["ComStop", "rstComStop"], ["NotAvail", "rstNAvail"]])
|
||||
document.getElementsByName("invRst"+i[0])[0].checked = obj[i[1]];
|
||||
document.getElementsByName("strtWthtTm")[0].checked = obj["strtWthtTm"];
|
||||
}
|
||||
|
||||
function parseSys(obj) {
|
||||
for(var i of [["device", "device_name"], ["ssid", "ssid"]])
|
||||
for(var i of [["device", "device_name"], ["ssid", "ssid"], ["ap_pwd", "ap_pwd"]])
|
||||
document.getElementsByName(i[0])[0].value = obj[i[1]];
|
||||
document.getElementsByName("hidd")[0].checked = obj["hidd"];
|
||||
document.getElementsByName("darkMode")[0].checked = obj["dark_mode"];
|
||||
document.getElementsByName("schedReboot")[0].checked = obj["sched_reboot"];
|
||||
e = document.getElementsByName("adminpwd")[0];
|
||||
if(!obj["pwd_set"])
|
||||
e.value = "";
|
||||
|
@ -630,6 +677,9 @@
|
|||
parseNav(obj);
|
||||
parseESP(obj);
|
||||
parseRssi(obj);
|
||||
|
||||
ts = obj["ts_now"];
|
||||
window.setInterval("tick()", 1000);
|
||||
}
|
||||
|
||||
function parseStaticIp(obj) {
|
||||
|
@ -651,7 +701,7 @@
|
|||
}
|
||||
|
||||
function parseNtp(obj) {
|
||||
for(var i of [["ntpAddr", "addr"], ["ntpPort", "port"]])
|
||||
for(var i of [["ntpAddr", "addr"], ["ntpPort", "port"], ["ntpIntvl", "interval"]])
|
||||
document.getElementsByName(i[0])[0].value = obj[i[1]];
|
||||
}
|
||||
|
||||
|
@ -667,11 +717,7 @@
|
|||
|
||||
function parsePinout(obj, type, system) {
|
||||
var e = document.getElementById("pinout");
|
||||
if ("ESP8266" == type) {
|
||||
pins = [['cs', 'pinCs'], ['ce', 'pinCe'], ['irq', 'pinIrq'], ['led0', 'pinLed0'], ['led1', 'pinLed1']];
|
||||
} else {
|
||||
pins = [['cs', 'pinCs'], ['ce', 'pinCe'], ['irq', 'pinIrq'], ['sclk', 'pinSclk'], ['mosi', 'pinMosi'], ['miso', 'pinMiso'], ['led0', 'pinLed0'], ['led1', 'pinLed1']];
|
||||
}
|
||||
pins = [['led0', 'pinLed0'], ['led1', 'pinLed1']];
|
||||
for(p of pins) {
|
||||
e.append(
|
||||
ml("div", {class: "row mb-3"}, [
|
||||
|
@ -689,11 +735,37 @@
|
|||
sel('pinLedHighActive', led_high_active, obj['led_high_active'])
|
||||
)
|
||||
])
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
function parseRadio(obj) {
|
||||
var e = document.getElementById("rf24").append(
|
||||
function parseNrfRadio(obj, objPin, type, system) {
|
||||
var e = document.getElementById("rf24");
|
||||
var en = inp("nrfEnable", null, null, ["cb"], "nrfEnable", "checkbox");
|
||||
en.checked = obj["en"];
|
||||
|
||||
e.replaceChildren (
|
||||
ml("div", {class: "row mb-3"}, [
|
||||
ml("div", {class: "col-8 col-sm-3 my-2"}, "NRF24 Enable"),
|
||||
ml("div", {class: "col-4 col-sm-9"}, en)
|
||||
])
|
||||
);
|
||||
|
||||
if ("ESP8266" == type) {
|
||||
pins = [['cs', 'pinCs'], ['ce', 'pinCe'], ['irq', 'pinIrq']];
|
||||
} else {
|
||||
pins = [['cs', 'pinCs'], ['ce', 'pinCe'], ['irq', 'pinIrq'], ['sclk', 'pinSclk'], ['mosi', 'pinMosi'], ['miso', 'pinMiso']];
|
||||
}
|
||||
for(p of pins) {
|
||||
e.append(
|
||||
ml("div", {class: "row mb-3"}, [
|
||||
ml("div", {class: "col-12 col-sm-3 my-2"}, p[0].toUpperCase()),
|
||||
ml("div", {class: "col-12 col-sm-9"},
|
||||
sel(p[1], ("ESP8266" == type) ? esp8266pins : ("ESP32-S3" == system["chip_model"]) ? esp32s3pins : esp32pins, objPin[p[0]])
|
||||
)
|
||||
])
|
||||
);
|
||||
}
|
||||
e.append(
|
||||
ml("div", {class: "row mb-3"}, [
|
||||
ml("div", {class: "col-12 col-sm-3 my-2"}, "Power Level"),
|
||||
ml("div", {class: "col-12 col-sm-9"},
|
||||
|
@ -708,6 +780,30 @@
|
|||
);
|
||||
}
|
||||
|
||||
function parseCmtRadio(obj, type, system) {
|
||||
var e = document.getElementById("cmt");
|
||||
var en = inp("cmtEnable", null, null, ["cb"], "cmtEnable", "checkbox");
|
||||
en.checked = obj["en"];
|
||||
|
||||
e.replaceChildren (
|
||||
ml("div", {class: "row mb-3"}, [
|
||||
ml("div", {class: "col-8 col-sm-3 my-2"}, "CMT2300A Enable"),
|
||||
ml("div", {class: "col-4 col-sm-9"}, en)
|
||||
])
|
||||
);
|
||||
pins = [['csb', 'pinCsb'], ['fcsb', 'pinFcsb'], ['irq', 'pinGpio3']];
|
||||
for(p of pins) {
|
||||
e.append(
|
||||
ml("div", {class: "row mb-3"}, [
|
||||
ml("div", {class: "col-12 col-sm-3 my-2"}, p[0].toUpperCase()),
|
||||
ml("div", {class: "col-12 col-sm-9"},
|
||||
sel(p[1], ("ESP8266" == type) ? esp8266pins : ("ESP32-S3" == system["chip_model"]) ? esp32s3pins : esp32pins, obj[p[0]])
|
||||
)
|
||||
])
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function parseSerial(obj) {
|
||||
for(var i of [["serEn", "show_live_data"], ["serDbg", "debug"]])
|
||||
document.getElementsByName(i[0])[0].checked = obj[i[1]];
|
||||
|
@ -719,6 +815,7 @@
|
|||
document.getElementsByName(i)[0].checked = obj[i];
|
||||
|
||||
var e = document.getElementById("dispPins");
|
||||
//KEEP this order !!!
|
||||
var pins = [['clock', 'disp_clk'], ['data', 'disp_data'], ['cs', 'disp_cs'], ['dc', 'disp_dc'], ['reset', 'disp_rst']];
|
||||
if("ESP32" == type)
|
||||
pins.push(['busy', 'disp_bsy']);
|
||||
|
@ -733,7 +830,8 @@
|
|||
);
|
||||
}
|
||||
|
||||
var opts = [[0, "None"], [1, "SSD1306 0.96\" 128X64"], [2, "SH1106 1.3\""], [3, "Nokia5110"], [4, "SSD1306 0.96\" 128X32"]];
|
||||
// keep display types grouped
|
||||
var opts = [[0, "None"], [2, "SH1106 1.3\" 128X64"], [5, "SSD1306 0.66\" 64X48 (Wemos OLED Shield)"], [4, "SSD1306 0.91\" 128X32"], [1, "SSD1306 0.96\" 128X64"], [3, "Nokia5110"]];
|
||||
if("ESP32" == type)
|
||||
opts.push([10, "ePaper"]);
|
||||
var dispType = sel("disp_typ", opts, obj["disp_typ"]);
|
||||
|
@ -744,7 +842,7 @@
|
|||
])
|
||||
);
|
||||
dispType.addEventListener('change', (e) => {
|
||||
hideDispPins(pins, e.target.value)
|
||||
hideDispPins(pins, parseInt(e.target.value))
|
||||
});
|
||||
|
||||
opts = [[0, "0°"], [2, "180°"]];
|
||||
|
@ -764,26 +862,33 @@
|
|||
}
|
||||
|
||||
function hideDispPins(pins, dispType) {
|
||||
// create pin map for each display type.
|
||||
// It depends on fix pin array (see var pins)
|
||||
// var pins = [['clock', 'disp_clk'], ['data', 'disp_data'], ['cs', 'disp_cs'], ['dc', 'disp_dc'], ['reset', 'disp_rst']];
|
||||
const pinMap = new Map([
|
||||
[0, [0,0,0,0,0]], //none
|
||||
[1, [1,1,0,0,0]], //SSD1306_128X64
|
||||
[2, [1,1,0,0,0]], //SH1106_128X64
|
||||
[3, [1,1,1,1,0]], //PCD8544_84X48 /nokia5110
|
||||
[4, [1,1,0,0,0]], //SSD1306_128X32
|
||||
[5, [1,1,0,0,0]], //SSD1306_64X48
|
||||
[10, [1,1,1,1,1]] //ePaper
|
||||
])
|
||||
for(var i = 0; i < pins.length; i++) {
|
||||
var cl = document.getElementById("row_" + pins[i][1]).classList;
|
||||
|
||||
if(0 == dispType)
|
||||
cl.add("hide");
|
||||
else if(dispType <= 2 || dispType == 4) { // OLED
|
||||
if(i < 2)
|
||||
cl.remove("hide");
|
||||
else
|
||||
cl.add("hide");
|
||||
} else if(dispType == 3) { // Nokia
|
||||
if(i < 4)
|
||||
cl.remove("hide");
|
||||
else
|
||||
cl.add("hide");
|
||||
} else // ePaper
|
||||
if(pinMap.get(dispType)[i]) {
|
||||
cl.remove("hide");
|
||||
}
|
||||
else {
|
||||
cl.add("hide");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function tick() {
|
||||
document.getElementById("date").innerHTML = toIsoDateStr((new Date((++ts) * 1000)));
|
||||
}
|
||||
|
||||
function parse(root) {
|
||||
if(null != root) {
|
||||
parseSys(root["system"]);
|
||||
|
@ -793,7 +898,9 @@
|
|||
parseNtp(root["ntp"]);
|
||||
parseSun(root["sun"]);
|
||||
parsePinout(root["pinout"], root["system"]["esp_type"], root["system"]);
|
||||
parseRadio(root["radio"]);
|
||||
parseNrfRadio(root["radioNrf"], root["pinout"], root["system"]["esp_type"], root["system"]);
|
||||
if(root["generic"]["esp_type"] == "ESP32")
|
||||
parseCmtRadio(root["radioCmt"], root["system"]["esp_type"], root["system"]);
|
||||
parseSerial(root["serial"]);
|
||||
parseDisplay(root["display"], root["system"]["esp_type"], root["system"]);
|
||||
getAjax("/api/inverter/list", parseIv);
|
||||
|
@ -808,8 +915,7 @@
|
|||
for(i = 0; i < root["networks"].length; i++) {
|
||||
s.appendChild(opt(root["networks"][i]["ssid"], root["networks"][i]["ssid"] + " (" + root["networks"][i]["rssi"] + " dBm)"));
|
||||
}
|
||||
}
|
||||
else
|
||||
} else
|
||||
s.appendChild(opt("-1", "no network found"));
|
||||
}
|
||||
|
||||
|
|
|
@ -475,6 +475,11 @@ input[type=text], input[type=password], select, input[type=number] {
|
|||
color: var(--fg);
|
||||
}
|
||||
|
||||
input:invalid {
|
||||
border: 2px solid #f00 !important;
|
||||
background-color: #400 !important;
|
||||
}
|
||||
|
||||
input.sh {
|
||||
max-width: 150px !important;
|
||||
margin-right: 10px;
|
||||
|
|
|
@ -19,7 +19,6 @@
|
|||
var units, ivEn;
|
||||
var mIvHtml = [];
|
||||
var mNum = 0;
|
||||
var names = ["Voltage", "Current", "Power", "Yield Day", "Yield Total", "Irradiation"];
|
||||
var total = Array(5).fill(0);
|
||||
|
||||
function parseGeneric(obj) {
|
||||
|
@ -159,7 +158,7 @@
|
|||
var ageInfo = "Last received data requested at: ";
|
||||
if(ts > 0) {
|
||||
var date = new Date(ts * 1000);
|
||||
ageInfo += date.toLocaleString('de-DE');
|
||||
ageInfo += toIsoDateStr(date);
|
||||
}
|
||||
else
|
||||
ageInfo += "nothing received";
|
||||
|
|
|
@ -38,7 +38,7 @@
|
|||
|
||||
#define WEB_SERIAL_BUF_SIZE 2048
|
||||
|
||||
const char *const pinArgNames[] = {"pinCs", "pinCe", "pinIrq", "pinSclk", "pinMosi", "pinMiso", "pinLed0", "pinLed1", "pinLedHighActive"};
|
||||
const char* const pinArgNames[] = {"pinCs", "pinCe", "pinIrq", "pinSclk", "pinMosi", "pinMiso", "pinLed0", "pinLed1", "pinLedHighActive", "pinCsb", "pinFcsb", "pinGpio3"};
|
||||
|
||||
template <class HMSYSTEM>
|
||||
class Web {
|
||||
|
@ -459,10 +459,14 @@ class Web {
|
|||
request->arg("ssid").toCharArray(mConfig->sys.stationSsid, SSID_LEN);
|
||||
if (request->arg("pwd") != "{PWD}")
|
||||
request->arg("pwd").toCharArray(mConfig->sys.stationPwd, PWD_LEN);
|
||||
if (request->arg("ap_pwd") != "")
|
||||
request->arg("ap_pwd").toCharArray(mConfig->sys.apPwd, PWD_LEN);
|
||||
mConfig->sys.isHidden = (request->arg("hidd") == "on");
|
||||
#endif /* !defined(ETHERNET) */
|
||||
if (request->arg("device") != "")
|
||||
request->arg("device").toCharArray(mConfig->sys.deviceName, DEVNAME_LEN);
|
||||
mConfig->sys.darkMode = (request->arg("darkMode") == "on");
|
||||
mConfig->sys.schedReboot = (request->arg("schedReboot") == "on");
|
||||
|
||||
// protection
|
||||
if (request->arg("adminpwd") != "{PWD}") {
|
||||
|
@ -499,8 +503,16 @@ class Web {
|
|||
memset(buf, 0, 20);
|
||||
iv->config->serial.u64 = ah::Serial2u64(buf);
|
||||
switch(iv->config->serial.b[4]) {
|
||||
case 0x24:
|
||||
case 0x22:
|
||||
case 0x21: iv->type = INV_TYPE_1CH; iv->channels = 1; break;
|
||||
|
||||
case 0x44:
|
||||
case 0x42:
|
||||
case 0x41: iv->type = INV_TYPE_2CH; iv->channels = 2; break;
|
||||
|
||||
case 0x64:
|
||||
case 0x62:
|
||||
case 0x61: iv->type = INV_TYPE_4CH; iv->channels = 4; break;
|
||||
default: break;
|
||||
}
|
||||
|
@ -509,8 +521,8 @@ class Web {
|
|||
request->arg("inv" + String(i) + "Name").toCharArray(iv->config->name, MAX_NAME_LENGTH);
|
||||
|
||||
// max channel power / name
|
||||
for (uint8_t j = 0; j < 4; j++) {
|
||||
iv->config->yieldCor[j] = request->arg("inv" + String(i) + "YieldCor" + String(j)).toInt();
|
||||
for (uint8_t j = 0; j < 6; j++) {
|
||||
iv->config->yieldCor[j] = request->arg("inv" + String(i) + "YieldCor" + String(j)).toDouble();
|
||||
iv->config->chMaxPwr[j] = request->arg("inv" + String(i) + "ModPwr" + String(j)).toInt() & 0xffff;
|
||||
request->arg("inv" + String(i) + "ModName" + String(j)).toCharArray(iv->config->chName[j], MAX_NAME_LENGTH);
|
||||
}
|
||||
|
@ -524,13 +536,16 @@ class Web {
|
|||
mConfig->inst.rstYieldMidNight = (request->arg("invRstMid") == "on");
|
||||
mConfig->inst.rstValsCommStop = (request->arg("invRstComStop") == "on");
|
||||
mConfig->inst.rstValsNotAvail = (request->arg("invRstNotAvail") == "on");
|
||||
mConfig->inst.startWithoutTime = (request->arg("strtWthtTm") == "on");
|
||||
mConfig->inst.yieldEffiency = (request->arg("yldEff")).toFloat();
|
||||
|
||||
|
||||
// pinout
|
||||
uint8_t pin;
|
||||
for (uint8_t i = 0; i < 9; i++) {
|
||||
for (uint8_t i = 0; i < 12; i++) {
|
||||
pin = request->arg(String(pinArgNames[i])).toInt();
|
||||
switch(i) {
|
||||
default: mConfig->nrf.pinCs = ((pin != 0xff) ? pin : DEF_CS_PIN); break;
|
||||
case 0: mConfig->nrf.pinCs = ((pin != 0xff) ? pin : DEF_CS_PIN); break;
|
||||
case 1: mConfig->nrf.pinCe = ((pin != 0xff) ? pin : DEF_CE_PIN); break;
|
||||
case 2: mConfig->nrf.pinIrq = ((pin != 0xff) ? pin : DEF_IRQ_PIN); break;
|
||||
case 3: mConfig->nrf.pinSclk = ((pin != 0xff) ? pin : DEF_SCLK_PIN); break;
|
||||
|
@ -539,16 +554,24 @@ class Web {
|
|||
case 6: mConfig->led.led0 = pin; break;
|
||||
case 7: mConfig->led.led1 = pin; break;
|
||||
case 8: mConfig->led.led_high_active = pin; break; // this is not really a pin but a polarity, but handling it close to here makes sense
|
||||
case 9: mConfig->cmt.pinCsb = pin; break;
|
||||
case 10: mConfig->cmt.pinFcsb = pin; break;
|
||||
case 11: mConfig->cmt.pinIrq = pin; break;
|
||||
}
|
||||
}
|
||||
|
||||
// nrf24 amplifier power
|
||||
mConfig->nrf.amplifierPower = request->arg("rf24Power").toInt() & 0x03;
|
||||
mConfig->nrf.enabled = (request->arg("nrfEnable") == "on");
|
||||
|
||||
// cmt
|
||||
mConfig->cmt.enabled = (request->arg("cmtEnable") == "on");
|
||||
|
||||
// ntp
|
||||
if (request->arg("ntpAddr") != "") {
|
||||
request->arg("ntpAddr").toCharArray(mConfig->ntp.addr, NTP_ADDR_LEN);
|
||||
mConfig->ntp.port = request->arg("ntpPort").toInt() & 0xffff;
|
||||
mConfig->ntp.interval = request->arg("ntpIntvl").toInt() & 0xffff;
|
||||
}
|
||||
|
||||
// sun
|
||||
|
@ -585,7 +608,7 @@ class Web {
|
|||
mConfig->serial.debug = (request->arg("serDbg") == "on");
|
||||
mConfig->serial.showIv = (request->arg("serEn") == "on");
|
||||
// Needed to log TX buffers to serial console
|
||||
mSys->Radio.mSerialDebug = mConfig->serial.debug;
|
||||
// mSys->Radio.mSerialDebug = mConfig->serial.debug;
|
||||
}
|
||||
|
||||
// display
|
||||
|
@ -693,11 +716,15 @@ class Web {
|
|||
|
||||
// NRF Statistics
|
||||
stat = mApp->getStatistics();
|
||||
uint32_t nrfSendCnt;
|
||||
uint32_t nrfRetransmits;
|
||||
mApp->getNrfRadioCounters(&nrfSendCnt, &nrfRetransmits);
|
||||
metrics += radioStatistic(F("rx_success"), stat->rxSuccess);
|
||||
metrics += radioStatistic(F("rx_fail"), stat->rxFail);
|
||||
metrics += radioStatistic(F("rx_fail_answer"), stat->rxFailNoAnser);
|
||||
metrics += radioStatistic(F("frame_cnt"), stat->frmCnt);
|
||||
metrics += radioStatistic(F("tx_cnt"), mSys->Radio.mSendCnt);
|
||||
metrics += radioStatistic(F("tx_cnt"), nrfSendCnt);
|
||||
metrics += radioStatistic(F("retrans_cnt"), nrfRetransmits);
|
||||
|
||||
len = snprintf((char *)buffer,maxLen,"%s",metrics.c_str());
|
||||
// Next is Inverter information
|
||||
|
@ -725,7 +752,7 @@ class Web {
|
|||
case metricsStateInverter3: // Information about all inverters configured : fit to one packet
|
||||
metrics += "# TYPE ahoy_solar_inverter_is_available gauge\n";
|
||||
metrics += inverterMetric(topic, sizeof(topic),"ahoy_solar_inverter_is_available {inverter=\"%s\"} %d\n",
|
||||
[](Inverter<> *iv,IApp *mApp)-> uint64_t {return iv->isAvailable(mApp->getTimestamp());});
|
||||
[](Inverter<> *iv,IApp *mApp)-> uint64_t {return iv->isAvailable();});
|
||||
len = snprintf((char *)buffer,maxLen,"%s",metrics.c_str());
|
||||
metricsStep = metricsStateInverter4;
|
||||
break;
|
||||
|
@ -733,7 +760,7 @@ class Web {
|
|||
case metricsStateInverter4: // Information about all inverters configured : fit to one packet
|
||||
metrics += "# TYPE ahoy_solar_inverter_is_producing gauge\n";
|
||||
metrics += inverterMetric(topic, sizeof(topic),"ahoy_solar_inverter_is_producing {inverter=\"%s\"} %d\n",
|
||||
[](Inverter<> *iv,IApp *mApp)-> uint64_t {return iv->isProducing(mApp->getTimestamp());});
|
||||
[](Inverter<> *iv,IApp *mApp)-> uint64_t {return iv->isProducing();});
|
||||
len = snprintf((char *)buffer,maxLen,"%s",metrics.c_str());
|
||||
// Start Realtime Field loop
|
||||
metricsFieldId = FLD_UDC;
|
||||
|
@ -767,7 +794,6 @@ class Web {
|
|||
|
||||
// Try inverter channel (channel 0) or any channel with maxPwr > 0
|
||||
if (0 == channel || 0 != iv->config->chMaxPwr[channel-1]) {
|
||||
|
||||
if (metricsFieldId == iv->getByteAssign(metricsChannelId, rec)->fieldId) {
|
||||
// This is the correct field to report
|
||||
std::tie(promUnit, promType) = convertToPromUnits(iv->getUnit(metricsChannelId, rec));
|
||||
|
|
|
@ -17,6 +17,12 @@
|
|||
ahoywifi::ahoywifi() : mApIp(192, 168, 4, 1) {}
|
||||
|
||||
|
||||
/**
|
||||
* TODO: ESP32 has native strongest AP support!
|
||||
* WiFi.setScanMethod(WIFI_ALL_CHANNEL_SCAN);
|
||||
WiFi.setSortMethod(WIFI_CONNECT_AP_BY_SIGNAL);
|
||||
*/
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void ahoywifi::setup(settings_t *config, uint32_t *utcTimestamp, appWifiCb cb) {
|
||||
mConfig = config;
|
||||
|
@ -49,14 +55,16 @@ void ahoywifi::setupWifi(bool startAP = false) {
|
|||
}
|
||||
#endif
|
||||
#if !defined(AP_ONLY)
|
||||
if(mConfig->valid) {
|
||||
#if !defined(FB_WIFI_OVERRIDDEN)
|
||||
#if defined(FB_WIFI_OVERRIDDEN)
|
||||
snprintf(mConfig->sys.stationSsid, SSID_LEN, "%s", FB_WIFI_SSID);
|
||||
snprintf(mConfig->sys.stationPwd, PWD_LEN, "%s", FB_WIFI_PWD);
|
||||
setupStation();
|
||||
#else
|
||||
if(mConfig->valid) {
|
||||
if(strncmp(mConfig->sys.stationSsid, FB_WIFI_SSID, 14) != 0)
|
||||
setupStation();
|
||||
#else
|
||||
setupStation();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -100,9 +108,17 @@ void ahoywifi::tickWifiLoop() {
|
|||
mScanCnt = 0;
|
||||
mScanActive = true;
|
||||
#if defined(ESP8266)
|
||||
WiFi.scanNetworks(true, false, 0U, (uint8_t *)mConfig->sys.stationSsid);
|
||||
WiFi.scanNetworks(true, true, 0U, ([this] () {
|
||||
if(mConfig->sys.isHidden)
|
||||
return (uint8_t *)NULL;
|
||||
return (uint8_t *)(mConfig->sys.stationSsid);
|
||||
})());
|
||||
#else
|
||||
WiFi.scanNetworks(true, false, false, 300U, 0U, mConfig->sys.stationSsid);
|
||||
WiFi.scanNetworks(true, true, false, 300U, 0U, ([this] () {
|
||||
if(mConfig->sys.isHidden)
|
||||
return (char*)NULL;
|
||||
return (mConfig->sys.stationSsid);
|
||||
})());
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
@ -157,7 +173,7 @@ void ahoywifi::setupAp(void) {
|
|||
DBGPRINT(F("\n---------\nAP MODE\nSSID: "));
|
||||
DBGPRINTLN(WIFI_AP_SSID);
|
||||
DBGPRINT(F("PWD: "));
|
||||
DBGPRINTLN(WIFI_AP_PWD);
|
||||
DBGPRINTLN(mConfig->sys.apPwd);
|
||||
DBGPRINT(F("IP Address: http://"));
|
||||
DBGPRINTLN(mApIp.toString());
|
||||
DBGPRINTLN(F("---------\n"));
|
||||
|
@ -167,7 +183,7 @@ void ahoywifi::setupAp(void) {
|
|||
|
||||
WiFi.mode(WIFI_AP_STA);
|
||||
WiFi.softAPConfig(mApIp, mApIp, IPAddress(255, 255, 255, 0));
|
||||
WiFi.softAP(WIFI_AP_SSID, WIFI_AP_PWD);
|
||||
WiFi.softAP(WIFI_AP_SSID, mConfig->sys.apPwd);
|
||||
}
|
||||
|
||||
|
||||
|
@ -275,12 +291,12 @@ void ahoywifi::scanAvailNetworks(void) {
|
|||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void ahoywifi::getAvailNetworks(JsonObject obj) {
|
||||
bool ahoywifi::getAvailNetworks(JsonObject obj) {
|
||||
JsonArray nets = obj.createNestedArray("networks");
|
||||
|
||||
int n = WiFi.scanComplete();
|
||||
if (n < 0)
|
||||
return;
|
||||
return false;
|
||||
if(n > 0) {
|
||||
int sort[n];
|
||||
sortRSSI(&sort[0], n);
|
||||
|
@ -293,6 +309,8 @@ void ahoywifi::getAvailNetworks(JsonObject obj) {
|
|||
WiFi.scanDelete();
|
||||
if(mStaConn == IN_AP_MODE)
|
||||
WiFi.mode(WIFI_AP);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
|
|
@ -27,7 +27,7 @@ class ahoywifi {
|
|||
void tickWifiLoop(void);
|
||||
bool getNtpTime(void);
|
||||
void scanAvailNetworks(void);
|
||||
void getAvailNetworks(JsonObject obj);
|
||||
bool getAvailNetworks(JsonObject obj);
|
||||
|
||||
private:
|
||||
typedef enum WiFiStatus {
|
||||
|
|
|
@ -2,11 +2,14 @@
|
|||
# build executable binary
|
||||
############################
|
||||
|
||||
FROM python:slim-bullseye
|
||||
FROM python:slim-bookworm
|
||||
|
||||
COPY . /hoymiles
|
||||
WORKDIR /hoymiles
|
||||
|
||||
# RUN apt-get update \
|
||||
# && groupadd spi
|
||||
|
||||
RUN python3 -m pip install pyrf24 influxdb_client && \
|
||||
python3 -m pip list #watch for RF24 module - if its there its installed
|
||||
|
||||
|
|
|
@ -179,18 +179,45 @@ class ResponseDecoder(ResponseDecoderFactory):
|
|||
command = self.request_command
|
||||
|
||||
if HOYMILES_DEBUG_LOGGING:
|
||||
if command.upper() == '01':
|
||||
if command.upper() == '00':
|
||||
model_desc = "Inverter Dev Inform Simple"
|
||||
elif command.upper() == '01':
|
||||
model_desc = "Firmware version / date"
|
||||
elif command.upper() == '02':
|
||||
model_desc = "Inverter generic events log"
|
||||
elif command.upper() == '0B':
|
||||
elif command.upper() == '03': ## HardWareConfig
|
||||
model_desc = "Hardware configuration"
|
||||
elif command.upper() == '04': ## SimpleCalibrationPara
|
||||
model_desc = "Simple Calibration Parameter"
|
||||
elif command.upper() == '05': ## SystemConfigPara
|
||||
model_desc = "Inverter generic SystemConfigPara"
|
||||
elif command.upper() == '0B': ## 11 - RealTimeRunData_Debug
|
||||
model_desc = "mirco-inverters status data"
|
||||
elif command.upper() == '0C':
|
||||
elif command.upper() == '0C': ## 12 - RealTimeRunData_Reality
|
||||
model_desc = "mirco-inverters status data"
|
||||
elif command.upper() == '11':
|
||||
elif command.upper() == '0D': ## 13 - RealTimeRunData_A_Phase
|
||||
model_desc = "Real-Time Run Data A Phase "
|
||||
elif command.upper() == '0E': ## 14 - RealTimeRunData_B_Phase
|
||||
model_desc = "Real-Time Run Data B Phase "
|
||||
elif command.upper() == '0F': ## 15 - RealTimeRunData_C_Phase
|
||||
model_desc = "Real-Time Run Data C Phase "
|
||||
elif command.upper() == '11': ## 17 - AlarmData
|
||||
model_desc = "Inverter generic events log"
|
||||
elif command.upper() == '12':
|
||||
elif command.upper() == '12': ## 18 - AlarmUpdate
|
||||
model_desc = "Inverter major events log"
|
||||
elif command.upper() == '13': ## 19 - RecordData
|
||||
model_desc = "Record Data"
|
||||
elif command.upper() == '14': ## 20 - InternalData
|
||||
model_desc = "Internal Data"
|
||||
elif command.upper() == '15': ## 21 - GetLossRate
|
||||
model_desc = "Get Loss Rate"
|
||||
elif command.upper() == '1E': ## 30 - GetSelfCheckState
|
||||
model_desc = "Get Self Check State"
|
||||
elif command.upper() == 'FF': ## 255 - InitDataState
|
||||
model_desc = "Initi Data State"
|
||||
|
||||
else:
|
||||
model_desc = "event not configured - check ahoy script"
|
||||
logging.info(f'model_decoder: {model}Decode{command.upper()} - {model_desc}')
|
||||
|
||||
model_decoders = __import__('hoymiles.decoders')
|
||||
|
@ -290,10 +317,9 @@ class InverterPacketFragment:
|
|||
:return: log line received frame
|
||||
:rtype: str
|
||||
"""
|
||||
c_datetime = self.time_rx.strftime("%Y-%m-%d %H:%M:%S.%f")
|
||||
size = len(self.frame)
|
||||
channel = f' channel {self.ch_rx}' if self.ch_rx else ''
|
||||
return f"{c_datetime} Received {size} bytes{channel}: {hexify_payload(self.frame)}"
|
||||
return f"Received {size} bytes{channel}: {hexify_payload(self.frame)}"
|
||||
|
||||
class HoymilesNRF:
|
||||
"""Hoymiles NRF24 Interface"""
|
||||
|
@ -743,9 +769,8 @@ class InverterTransaction:
|
|||
:return: log line of payload for transmission
|
||||
:rtype: str
|
||||
"""
|
||||
c_datetime = self.request_time.strftime("%Y-%m-%d %H:%M:%S.%f")
|
||||
size = len(self.request)
|
||||
return f'{c_datetime} Transmit | {hexify_payload(self.request)}'
|
||||
return f'Transmit | {hexify_payload(self.request)}'
|
||||
|
||||
def hexify_payload(byte_var):
|
||||
"""
|
||||
|
|
|
@ -101,7 +101,7 @@ class SunsetHandler:
|
|||
logging.info (f'Woke up...')
|
||||
|
||||
def sun_status2mqtt(self, dtu_ser, dtu_name):
|
||||
if not mqtt_client:
|
||||
if not mqtt_client or not self.suntimes:
|
||||
return
|
||||
local_sunrise = self.suntimes.riselocal(datetime.now()).strftime("%d.%m.%YT%H:%M")
|
||||
local_sunset = self.suntimes.setlocal(datetime.now()).strftime("%d.%m.%YT%H:%M")
|
||||
|
@ -129,6 +129,11 @@ def main_loop(ahoy_config):
|
|||
sunset.sun_status2mqtt(dtu_ser, dtu_name)
|
||||
loop_interval = ahoy_config.get('interval', 1)
|
||||
transmit_retries = ahoy_config.get('transmit_retries', 5)
|
||||
if (transmit_retries <= 0):
|
||||
logging.critical('Parameter "transmit_retries" must be >0 - please check ahoy.yml.')
|
||||
# print message to console too
|
||||
print('Parameter "transmit_retries" must be >0 - please check ahoy.yml - STOP(0)x')
|
||||
sys.exit(0)
|
||||
|
||||
try:
|
||||
do_init = True
|
||||
|
@ -175,15 +180,16 @@ def poll_inverter(inverter, dtu_ser, do_init, retries):
|
|||
inv_str = str(inverter_ser)
|
||||
if do_init:
|
||||
command_queue[inv_str].append(hoymiles.compose_send_time_payload(InfoCommands.InverterDevInform_All))
|
||||
# command_queue[inv_str].append(hoymiles.compose_send_time_payload(InfoCommands.SystemConfigPara))
|
||||
#command_queue[inv_str].append(hoymiles.compose_send_time_payload(InfoCommands.SystemConfigPara))
|
||||
command_queue[inv_str].append(hoymiles.compose_send_time_payload(InfoCommands.RealTimeRunData_Debug))
|
||||
|
||||
# Put all queued commands for current inverter on air
|
||||
while len(command_queue[inv_str]) > 0:
|
||||
payload = command_queue[inv_str].pop(0)
|
||||
payload = command_queue[inv_str].pop(0) ## Sub.Cmd
|
||||
|
||||
# Send payload {ttl}-times until we get at least one reponse
|
||||
payload_ttl = retries
|
||||
response = None
|
||||
while payload_ttl > 0:
|
||||
payload_ttl = payload_ttl - 1
|
||||
com = hoymiles.InverterTransaction(
|
||||
|
@ -197,7 +203,6 @@ def poll_inverter(inverter, dtu_ser, do_init, retries):
|
|||
src=dtu_ser,
|
||||
dst=inverter_ser
|
||||
)))
|
||||
response = None
|
||||
while com.rxtx():
|
||||
try:
|
||||
response = com.get_payload()
|
||||
|
@ -210,8 +215,7 @@ def poll_inverter(inverter, dtu_ser, do_init, retries):
|
|||
# Handle the response data if any
|
||||
if response:
|
||||
if hoymiles.HOYMILES_TRANSACTION_LOGGING:
|
||||
c_datetime = datetime.now()
|
||||
logging.debug(f'{c_datetime} Payload: ' + hoymiles.hexify_payload(response))
|
||||
logging.debug(f'Payload: ' + hoymiles.hexify_payload(response))
|
||||
|
||||
# prepare decoder object
|
||||
decoder = hoymiles.ResponseDecoder(response,
|
||||
|
@ -319,9 +323,11 @@ def init_logging(ahoy_config):
|
|||
max_log_files = log_config.get('max_log_files', max_log_files)
|
||||
if hoymiles.HOYMILES_TRANSACTION_LOGGING:
|
||||
lvl = logging.DEBUG
|
||||
logging.basicConfig(handlers=[RotatingFileHandler(fn, maxBytes=max_log_filesize, backupCount=max_log_files)], format='%(asctime)s %(levelname)s: %(message)s', datefmt='%Y-%m-%d %H:%M:%S', level=lvl)
|
||||
logging.basicConfig(handlers=[RotatingFileHandler(fn, maxBytes=max_log_filesize, backupCount=max_log_files)],
|
||||
format='%(asctime)s %(levelname)s: %(message)s',
|
||||
datefmt='%Y-%m-%d %H:%M:%S.%s', level=lvl)
|
||||
dtu_name = ahoy_config.get('dtu',{}).get('name','hoymiles-dtu')
|
||||
logging.info(f'start logging for {dtu_name} with level: {logging.root.level}')
|
||||
logging.info(f'start logging for {dtu_name} with level: {logging.getLevelName(logging.root.level)}')
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser(description='Ahoy - Hoymiles solar inverter gateway', prog="hoymiles")
|
||||
|
|
|
@ -151,18 +151,19 @@ class StatusResponse(Response):
|
|||
"""
|
||||
strings = []
|
||||
s_exists = True
|
||||
while s_exists:
|
||||
while s_exists and len(strings) < len(self.inv_strings):
|
||||
s_exists = False
|
||||
string_id = len(strings)
|
||||
string = {}
|
||||
string['name'] = self.inv_strings[string_id]['s_name']
|
||||
for key in self.string_keys:
|
||||
prop = f'dc_{key}_{string_id}'
|
||||
if hasattr(self, prop):
|
||||
s_exists = True
|
||||
string[key] = getattr(self, prop)
|
||||
if s_exists:
|
||||
strings.append(string)
|
||||
if string_id < len(self.inv_strings):
|
||||
string = {}
|
||||
string['name'] = self.inv_strings[string_id]['s_name']
|
||||
for key in self.string_keys:
|
||||
prop = f'dc_{key}_{string_id}'
|
||||
if hasattr(self, prop):
|
||||
s_exists = True
|
||||
string[key] = getattr(self, prop)
|
||||
if s_exists:
|
||||
strings.append(string)
|
||||
|
||||
return strings
|
||||
|
||||
|
@ -430,15 +431,15 @@ class DebugDecodeAny(UnknownResponse):
|
|||
l_payload = len(self.response)
|
||||
logging.debug(f' payload has {l_payload} bytes')
|
||||
|
||||
logging.debug()
|
||||
logging.debug('')
|
||||
logging.debug('Field view: int')
|
||||
print_table_unpack('>B', self.response)
|
||||
|
||||
logging.debug()
|
||||
logging.debug('')
|
||||
logging.debug('Field view: shorts')
|
||||
print_table_unpack('>H', self.response)
|
||||
|
||||
logging.debug()
|
||||
logging.debug('')
|
||||
logging.debug('Field view: longs')
|
||||
print_table_unpack('>L', self.response)
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue