Browse Source

Merge branch 'dev/littlefs' into development03

pull/425/head^2
lumapu 2 years ago
parent
commit
f72a2eb1ec
  1. 3
      src/.vscode/settings.json
  2. 222
      src/app.cpp
  3. 92
      src/app.h
  4. 18
      src/config/config.h
  5. 161
      src/config/eep.h
  6. 417
      src/config/settings.h
  7. 150
      src/defines.h
  8. 7
      src/hm/hmDefines.h
  9. 24
      src/hm/hmInverter.h
  10. 42
      src/hm/hmSystem.h
  11. 6
      src/platformio.ini
  12. 2
      src/utils/ahoyTimer.h
  13. 2
      src/utils/sun.h
  14. 2
      src/web/html/serial.html
  15. 8
      src/web/html/setup.html
  16. 44
      src/web/mqtt.h
  17. 101
      src/web/web.cpp
  18. 5
      src/web/web.h
  19. 69
      src/web/webApi.cpp
  20. 5
      src/web/webApi.h
  21. 36
      src/wifi/ahoywifi.cpp
  22. 14
      src/wifi/ahoywifi.h

3
src/.vscode/settings.json

@ -20,4 +20,7 @@
// https://clang.llvm.org/docs/ClangFormatStyleOptions.html
"C_Cpp.clang_format_fallbackStyle": "{ BasedOnStyle: Google, IndentWidth: 4, ColumnLimit: 0}",
"files.associations": {
"typeinfo": "cpp"
},
}

222
src/app.cpp

@ -13,33 +13,27 @@
#include "utils/sun.h"
//-----------------------------------------------------------------------------
app::app() {
void app::setup(uint32_t timeout) {
Serial.begin(115200);
DPRINTLN(DBG_VERBOSE, F("app::app"));
mEep = new eep();
while (!Serial)
yield();
resetSystem();
loadDefaultConfig();
mSettings.setup();
mSettings.getPtr(mConfig);
mWifi = new ahoywifi(mConfig);
mWifi = new ahoywifi(this, &mSysConfig, &mConfig);
mSys = new HmSystemType();
mSys->enableDebug();
mShouldReboot = false;
}
//-----------------------------------------------------------------------------
void app::setup(uint32_t timeout) {
DPRINTLN(DBG_VERBOSE, F("app::setup"));
mWifiSettingsValid = checkEEpCrc(ADDR_START, ADDR_WIFI_CRC, ADDR_WIFI_CRC);
mSettingsValid = checkEEpCrc(ADDR_START_SETTINGS, ((ADDR_NEXT) - (ADDR_START_SETTINGS)), ADDR_SETTINGS_CRC);
loadEEpconfig();
mWifi->setup(timeout, mSettings.getValid());
mWifi->setup(timeout, mWifiSettingsValid);
mSys->setup(mConfig.amplifierPower, mConfig.pinIrq, mConfig.pinCe, mConfig.pinCs);
mSys->setup(mConfig->nrf.amplifierPower, mConfig->nrf.pinIrq, mConfig->nrf.pinCe, mConfig->nrf.pinCs);
mSys->addInverters(&mConfig->inst);
mPayload.setup(mSys);
mPayload.enableSerialDebug(mConfig.serialDebug);
mPayload.enableSerialDebug(mConfig->serial.debug);
#ifndef AP_ONLY
setupMqtt();
if(mMqttActive)
@ -47,11 +41,12 @@ void app::setup(uint32_t timeout) {
#endif
setupLed();
mWebInst = new web(this, &mSysConfig, &mConfig, &mStat, mVersion);
mWebInst = new web(this, mConfig, &mStat, mVersion);
mWebInst->setup();
mWebInst->setProtection(strlen(mConfig.password) != 0);
DPRINTLN(DBG_INFO, F("Settings valid: ") + String((mSettingsValid) ? F("true") : F("false")));
DPRINTLN(DBG_INFO, F("EEprom storage size: 0x") + String(ADDR_SETTINGS_CRC, HEX));
mWebInst->setProtection(strlen(mConfig->sys.adminPwd) != 0);
DPRINTLN(DBG_INFO, F("Settings valid: ") + String((mSettings.getValid()) ? F("true") : F("false")));
}
//-----------------------------------------------------------------------------
@ -88,7 +83,7 @@ void app::loop(void) {
if (mFlagSendDiscoveryConfig) {
mFlagSendDiscoveryConfig = false;
mMqtt.sendMqttDiscoveryConfig(mConfig.mqtt.topic);
mMqtt.sendMqttDiscoveryConfig(mConfig->mqtt.topic);
}
mSys->Radio.loop();
@ -104,7 +99,7 @@ void app::loop(void) {
if (mSys->Radio.checkPaketCrc(p->packet, &len, p->rxCh)) {
// process buffer only on first occurrence
if (mConfig.serialDebug) {
if (mConfig->serial.debug) {
DPRINT(DBG_INFO, "RX " + String(len) + "B Ch" + String(p->rxCh) + " | ");
mSys->Radio.dumpBuf(NULL, p->packet, len);
}
@ -119,23 +114,23 @@ void app::loop(void) {
yield();
if (rxRdy)
mPayload.process(true, mConfig.maxRetransPerPyld, &mStat);
mPayload.process(true, mConfig->nrf.maxRetransPerPyld, &mStat);
}
if (mMqttActive)
mMqtt.loop();
if (ah::checkTicker(&mTicker, 1000)) {
if (mUtcTimestamp > 946684800 && mConfig.sunLat && mConfig.sunLon && (mUtcTimestamp + mCalculatedTimezoneOffset) / 86400 != (mLatestSunTimestamp + mCalculatedTimezoneOffset) / 86400) { // update on reboot or midnight
if (mUtcTimestamp > 946684800 && mConfig->sun.lat && mConfig->sun.lon && (mUtcTimestamp + mCalculatedTimezoneOffset) / 86400 != (mLatestSunTimestamp + mCalculatedTimezoneOffset) / 86400) { // update on reboot or midnight
if (!mLatestSunTimestamp) { // first call: calculate time zone from longitude to refresh at local midnight
mCalculatedTimezoneOffset = (int8_t)((mConfig.sunLon >= 0 ? mConfig.sunLon + 7.5 : mConfig.sunLon - 7.5) / 15) * 3600;
mCalculatedTimezoneOffset = (int8_t)((mConfig->sun.lon >= 0 ? mConfig->sun.lon + 7.5 : mConfig->sun.lon - 7.5) / 15) * 3600;
}
ah::calculateSunriseSunset(mUtcTimestamp, mCalculatedTimezoneOffset, mConfig.sunLat, mConfig.sunLon, &mSunrise, &mSunset);
ah::calculateSunriseSunset(mUtcTimestamp, mCalculatedTimezoneOffset, mConfig->sun.lat, mConfig->sun.lon, &mSunrise, &mSunset);
mLatestSunTimestamp = mUtcTimestamp;
}
if (mConfig.serialShowIv) {
if (++mSerialTicker >= mConfig.serialInterval) {
if (mConfig->serial.showIv) {
if (++mSerialTicker >= mConfig->serial.interval) {
mSerialTicker = 0;
char topic[30], val[10];
for (uint8_t id = 0; id < mSys->getNumInverters(); id++) {
@ -146,7 +141,7 @@ void app::loop(void) {
DPRINTLN(DBG_INFO, "Inverter: " + String(id));
for (uint8_t i = 0; i < rec->length; i++) {
if (0.0f != iv->getValue(i, rec)) {
snprintf(topic, 30, "%s/ch%d/%s", iv->name, rec->assign[i].ch, iv->getFieldName(i, rec));
snprintf(topic, 30, "%s/ch%d/%s", iv->config->name, rec->assign[i].ch, iv->getFieldName(i, rec));
snprintf(val, 10, "%.3f %s", iv->getValue(i, rec), iv->getUnit(i, rec));
DPRINTLN(DBG_INFO, String(topic) + ": " + String(val));
}
@ -159,15 +154,15 @@ void app::loop(void) {
}
}
if (++mSendTicker >= mConfig.sendInterval) {
if (++mSendTicker >= mConfig->nrf.sendInterval) {
mSendTicker = 0;
if (mUtcTimestamp > 946684800 && (!mConfig.sunDisNightCom || !mLatestSunTimestamp || (mUtcTimestamp >= mSunrise && mUtcTimestamp <= mSunset))) { // Timestamp is set and (inverter communication only during the day if the option is activated and sunrise/sunset is set)
if (mConfig.serialDebug)
if (mUtcTimestamp > 946684800 && (!mConfig->sun.disNightCom || !mLatestSunTimestamp || (mUtcTimestamp >= mSunrise && mUtcTimestamp <= mSunset))) { // Timestamp is set and (inverter communication only during the day if the option is activated and sunrise/sunset is set)
if (mConfig->serial.debug)
DPRINTLN(DBG_DEBUG, F("Free heap: 0x") + String(ESP.getFreeHeap(), HEX));
if (!mSys->BufCtrl.empty()) {
if (mConfig.serialDebug)
if (mConfig->serial.debug)
DPRINTLN(DBG_DEBUG, F("recbuf not empty! #") + String(mSys->BufCtrl.getFill()));
}
@ -180,7 +175,7 @@ void app::loop(void) {
if (NULL != iv) {
if (!mPayload.isComplete(iv))
mPayload.process(false, mConfig.maxRetransPerPyld, &mStat);
mPayload.process(false, mConfig->nrf.maxRetransPerPyld, &mStat);
if (!mPayload.isComplete(iv)) {
if (0 == mPayload.getMaxPacketId(iv))
@ -189,9 +184,9 @@ void app::loop(void) {
mStat.rxFail++;
iv->setQueuedCmdFinished(); // command failed
if (mConfig.serialDebug)
if (mConfig->serial.debug)
DPRINTLN(DBG_INFO, F("enqueued cmd failed/timeout"));
if (mConfig.serialDebug) {
if (mConfig->serial.debug) {
DPRINT(DBG_INFO, F("(#") + String(iv->id) + ") ");
DPRINTLN(DBG_INFO, F("no Payload received! (retransmits: ") + String(mPayload.getRetransmits(iv)) + ")");
}
@ -201,13 +196,13 @@ void app::loop(void) {
mPayload.request(iv);
yield();
if (mConfig.serialDebug) {
if (mConfig->serial.debug) {
DPRINTLN(DBG_DEBUG, F("app:loop WiFi WiFi.status ") + String(WiFi.status()));
DPRINTLN(DBG_INFO, F("(#") + String(iv->id) + F(") Requesting Inv SN ") + String(iv->serial.u64, HEX));
DPRINTLN(DBG_INFO, F("(#") + String(iv->id) + F(") Requesting Inv SN ") + String(iv->config->serial.u64, HEX));
}
if (iv->devControlRequest) {
if (mConfig.serialDebug)
if (mConfig->serial.debug)
DPRINTLN(DBG_INFO, F("(#") + String(iv->id) + F(") Devcontrol request ") + String(iv->devControlCmd) + F(" power limit ") + String(iv->powerLimit[0]));
mSys->Radio.sendControlPacket(iv->radioId.u64, iv->devControlCmd, iv->powerLimit);
mPayload.setTxCmd(iv, iv->devControlCmd);
@ -221,7 +216,7 @@ void app::loop(void) {
mRxTicker = 0;
}
}
} else if (mConfig.serialDebug)
} else if (mConfig->serial.debug)
DPRINTLN(DBG_WARN, F("Time not set or it is night time, therefore no communication to the inverter!"));
yield();
@ -254,6 +249,8 @@ void app::getAvailNetworks(JsonObject obj) {
//-----------------------------------------------------------------------------
void app::resetSystem(void) {
snprintf(mVersion, 12, "%d.%d.%d", VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH);
mUptimeSecs = 0;
mPrevMillis = 0;
mUpdateNtp = false;
@ -284,130 +281,13 @@ void app::resetSystem(void) {
memset(&mStat, 0, sizeof(statistics_t));
}
//-----------------------------------------------------------------------------
void app::loadDefaultConfig(void) {
memset(&mSysConfig, 0, sizeof(sysConfig_t));
memset(&mConfig, 0, sizeof(config_t));
snprintf(mVersion, 12, "%d.%d.%d", VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH);
snprintf(mSysConfig.deviceName, DEVNAME_LEN, "%s", DEF_DEVICE_NAME);
// wifi
snprintf(mSysConfig.stationSsid, SSID_LEN, "%s", FB_WIFI_SSID);
snprintf(mSysConfig.stationPwd, PWD_LEN, "%s", FB_WIFI_PWD);
// password
snprintf(mConfig.password, PWD_LEN, "%s", GUI_DEF_PASSWORD);
// nrf24
mConfig.sendInterval = SEND_INTERVAL;
mConfig.maxRetransPerPyld = DEF_MAX_RETRANS_PER_PYLD;
mConfig.pinCs = DEF_CS_PIN;
mConfig.pinCe = DEF_CE_PIN;
mConfig.pinIrq = DEF_IRQ_PIN;
mConfig.amplifierPower = DEF_AMPLIFIERPOWER & 0x03;
// status LED
mConfig.led.led0 = DEF_LED0_PIN;
mConfig.led.led1 = DEF_LED1_PIN;
// ntp
snprintf(mConfig.ntpAddr, NTP_ADDR_LEN, "%s", DEF_NTP_SERVER_NAME);
mConfig.ntpPort = DEF_NTP_PORT;
// Latitude + Longitude
mConfig.sunLat = 0.0;
mConfig.sunLon = 0.0;
mConfig.sunDisNightCom = false;
// mqtt
snprintf(mConfig.mqtt.broker, MQTT_ADDR_LEN, "%s", DEF_MQTT_BROKER);
mConfig.mqtt.port = DEF_MQTT_PORT;
snprintf(mConfig.mqtt.user, MQTT_USER_LEN, "%s", DEF_MQTT_USER);
snprintf(mConfig.mqtt.pwd, MQTT_PWD_LEN, "%s", DEF_MQTT_PWD);
snprintf(mConfig.mqtt.topic, MQTT_TOPIC_LEN, "%s", DEF_MQTT_TOPIC);
// serial
mConfig.serialInterval = SERIAL_INTERVAL;
mConfig.serialShowIv = false;
mConfig.serialDebug = false;
// Disclaimer
mConfig.disclaimer = false;
}
//-----------------------------------------------------------------------------
void app::loadEEpconfig(void) {
DPRINTLN(DBG_INFO, F("loadEEpconfig"));
if (mWifiSettingsValid)
mEep->read(ADDR_CFG_SYS, (uint8_t *)&mSysConfig, CFG_SYS_LEN);
if (mSettingsValid) {
mEep->read(ADDR_CFG, (uint8_t *)&mConfig, CFG_LEN);
mSendTicker = mConfig.sendInterval;
mSerialTicker = 0;
// inverter
uint64_t invSerial;
char name[MAX_NAME_LENGTH + 1] = {0};
uint16_t modPwr[4];
Inverter<> *iv;
for (uint8_t i = 0; i < MAX_NUM_INVERTERS; i++) {
mEep->read(ADDR_INV_ADDR + (i * 8), &invSerial);
mEep->read(ADDR_INV_NAME + (i * MAX_NAME_LENGTH), name, MAX_NAME_LENGTH);
mEep->read(ADDR_INV_CH_PWR + (i * 2 * 4), modPwr, 4);
if (0ULL != invSerial) {
iv = mSys->addInverter(name, invSerial, modPwr);
if (NULL != iv) { // will run once on every dtu boot
for (uint8_t j = 0; j < 4; j++) {
mEep->read(ADDR_INV_CH_NAME + (i * 4 * MAX_NAME_LENGTH) + j * MAX_NAME_LENGTH, iv->chName[j], MAX_NAME_LENGTH);
}
}
}
}
for (uint8_t i = 0; i < MAX_NUM_INVERTERS; i++) {
iv = mSys->getInverterByPos(i, false);
if (NULL != iv)
mPayload.reset(iv, mUtcTimestamp);
}
}
}
//-----------------------------------------------------------------------------
void app::saveValues(void) {
DPRINTLN(DBG_VERBOSE, F("app::saveValues"));
mEep->write(ADDR_CFG_SYS, (uint8_t *)&mSysConfig, CFG_SYS_LEN);
mEep->write(ADDR_CFG, (uint8_t *)&mConfig, CFG_LEN);
Inverter<> *iv;
for (uint8_t i = 0; i < MAX_NUM_INVERTERS; i++) {
iv = mSys->getInverterByPos(i, false);
mEep->write(ADDR_INV_ADDR + (i * 8), iv->serial.u64);
mEep->write(ADDR_INV_NAME + (i * MAX_NAME_LENGTH), iv->name, MAX_NAME_LENGTH);
// max channel power / name
for (uint8_t j = 0; j < 4; j++) {
mEep->write(ADDR_INV_CH_PWR + (i * 2 * 4) + (j * 2), iv->chMaxPwr[j]);
mEep->write(ADDR_INV_CH_NAME + (i * 4 * MAX_NAME_LENGTH) + j * MAX_NAME_LENGTH, iv->chName[j], MAX_NAME_LENGTH);
}
}
updateCrc();
// update sun
mLatestSunTimestamp = 0;
}
//-----------------------------------------------------------------------------
void app::setupMqtt(void) {
if (mSettingsValid) {
if (mConfig.mqtt.broker[0] > 0)
mMqttActive = true;
if (mConfig->mqtt.broker[0] > 0)
mMqttActive = true;
if(mMqttActive)
mMqtt.setup(&mConfig.mqtt, mSysConfig.deviceName, mVersion, mSys, &mUtcTimestamp);
}
if(mMqttActive)
mMqtt.setup(&mConfig->mqtt, mConfig->sys.deviceName, mVersion, mSys, &mUtcTimestamp);
}
//-----------------------------------------------------------------------------
@ -417,26 +297,26 @@ void app::setupLed(void) {
* PIN ---- |<----- 3.3V
*
* */
if(mConfig.led.led0 != 0xff) {
pinMode(mConfig.led.led0, OUTPUT);
digitalWrite(mConfig.led.led0, HIGH); // LED off
if(mConfig->led.led0 != 0xff) {
pinMode(mConfig->led.led0, OUTPUT);
digitalWrite(mConfig->led.led0, HIGH); // LED off
}
if(mConfig.led.led1 != 0xff) {
pinMode(mConfig.led.led1, OUTPUT);
digitalWrite(mConfig.led.led1, HIGH); // LED off
if(mConfig->led.led1 != 0xff) {
pinMode(mConfig->led.led1, OUTPUT);
digitalWrite(mConfig->led.led1, HIGH); // LED off
}
}
//-----------------------------------------------------------------------------
void app::updateLed(void) {
if(mConfig.led.led0 != 0xff) {
if(mConfig->led.led0 != 0xff) {
Inverter<> *iv = mSys->getInverterByPos(0);
if (NULL != iv) {
record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug);
if(iv->isProducing(mUtcTimestamp, rec))
digitalWrite(mConfig.led.led0, LOW); // LED on
digitalWrite(mConfig->led.led0, LOW); // LED on
else
digitalWrite(mConfig.led.led0, HIGH); // LED off
digitalWrite(mConfig->led.led0, HIGH); // LED off
}
}
}

92
src/app.h

@ -7,15 +7,12 @@
#define __APP_H__
#include "utils/dbg.h"
#include "Arduino.h"
#include <queue>
#include <Arduino.h>
#include <RF24.h>
#include <RF24_config.h>
#include <ArduinoJson.h>
#include "config/eep.h"
#include "config/settings.h"
#include "defines.h"
#include "utils/crc.h"
#include "utils/ahoyTimer.h"
@ -42,7 +39,7 @@ class web;
class app {
public:
app();
app() {}
~app() {}
void setup(uint32_t timeout);
@ -55,9 +52,16 @@ class app {
void scanAvailNetworks(void);
void getAvailNetworks(JsonObject obj);
void saveSettings(void) {
mSettings.saveSettings();
}
bool eraseSettings(bool eraseWifi = false) {
return mSettings.eraseSettings(eraseWifi);
}
uint8_t getIrqPin(void) {
return mConfig.pinIrq;
return mConfig->nrf.pinIrq;
}
uint64_t Serial2u64(const char *val) {
@ -122,26 +126,8 @@ class app {
return mLatestSunTimestamp;
}
void eraseSettings(bool all = false) {
//DPRINTLN(DBG_VERBOSE, F("main.h:eraseSettings"));
uint8_t buf[64];
uint16_t addr = (all) ? ADDR_START : ADDR_START_SETTINGS;
uint16_t end;
memset(buf, 0xff, 64);
do {
end = addr + 64;
if(end > (ADDR_SETTINGS_CRC + 2))
end = (ADDR_SETTINGS_CRC + 2);
DPRINTLN(DBG_DEBUG, F("erase: 0x") + String(addr, HEX) + " - 0x" + String(end, HEX));
mEep->write(addr, buf, (end-addr));
addr = end;
} while(addr < (ADDR_SETTINGS_CRC + 2));
mEep->commit();
}
inline bool mqttIsConnected(void) { return mMqtt.isConnected(); }
inline bool getSettingsValid(void) { return mSettingsValid; }
inline bool getSettingsValid(void) { return mSettings.getValid(); }
inline bool getRebootRequestState(void) { return mShowRebootRequest; }
inline uint32_t getMqttTxCnt(void) { return mMqtt.getTxCnt(); }
@ -151,57 +137,12 @@ class app {
private:
void resetSystem(void);
void loadDefaultConfig(void);
void loadEEpconfig(void);
void setupMqtt(void);
void setupLed(void);
void updateLed(void);
void processPayload(bool retransmit);
inline uint16_t buildEEpCrc(uint32_t start, uint32_t length) {
DPRINTLN(DBG_VERBOSE, F("main.h:buildEEpCrc"));
uint8_t buf[32];
uint16_t crc = 0xffff;
uint8_t len;
while(length > 0) {
len = (length < 32) ? length : 32;
mEep->read(start, buf, len);
crc = ah::crc16(buf, len, crc);
start += len;
length -= len;
}
return crc;
}
void updateCrc(void) {
DPRINTLN(DBG_VERBOSE, F("app::updateCrc"));
uint16_t crc;
crc = buildEEpCrc(ADDR_START, ADDR_WIFI_CRC);
DPRINTLN(DBG_DEBUG, F("new Wifi CRC: ") + String(crc, HEX));
mEep->write(ADDR_WIFI_CRC, crc);
crc = buildEEpCrc(ADDR_START_SETTINGS, ((ADDR_NEXT) - (ADDR_START_SETTINGS)));
DPRINTLN(DBG_DEBUG, F("new Settings CRC: ") + String(crc, HEX));
mEep->write(ADDR_SETTINGS_CRC, crc);
mEep->commit();
}
bool checkEEpCrc(uint32_t start, uint32_t length, uint32_t crcPos) {
DPRINTLN(DBG_VERBOSE, F("main.h:checkEEpCrc"));
DPRINTLN(DBG_DEBUG, F("start: ") + String(start) + F(", length: ") + String(length));
uint16_t crcRd, crcCheck;
crcCheck = buildEEpCrc(start, length);
mEep->read(crcPos, &crcRd);
DPRINTLN(DBG_DEBUG, "CRC RD: " + String(crcRd, HEX) + " CRC CALC: " + String(crcCheck, HEX));
return (crcCheck == crcRd);
}
void stats(void) {
DPRINTLN(DBG_VERBOSE, F("main.h:stats"));
#ifdef ESP8266
@ -228,11 +169,6 @@ class app {
uint32_t mNtpRefreshTicker;
uint32_t mNtpRefreshInterval;
bool mWifiSettingsValid;
bool mSettingsValid;
eep *mEep;
uint32_t mUtcTimestamp;
bool mUpdateNtp;
@ -240,10 +176,10 @@ class app {
ahoywifi *mWifi;
web *mWebInst;
sysConfig_t mSysConfig;
config_t mConfig;
char mVersion[12];
PayloadType mPayload;
settings mSettings;
settings_t *mConfig;
uint16_t mSendTicker;
uint8_t mSendLastIvId;

18
src/config/config.h

@ -25,9 +25,6 @@
// If the next line is uncommented, Ahoy will stay in access point mode all the time
//#define AP_ONLY
// protection of the GUI by password
#define GUI_DEF_PASSWORD ""
// timeout for automatic logoff (20 minutes)
#define LOGOUT_TIMEOUT (20 * 60 * 60)
@ -47,13 +44,13 @@
// default pinout (GPIO Number)
#if defined(ESP32)
#define DEF_CS_PIN 15
#define DEF_CE_PIN 2
#define DEF_IRQ_PIN 0
#else
#define DEF_CS_PIN 5
#define DEF_CE_PIN 4
#define DEF_IRQ_PIN 16
#else
#define DEF_CS_PIN 15
#define DEF_CE_PIN 2
#define DEF_IRQ_PIN 0
#endif
#define DEF_LED0_PIN 255 // off
#define DEF_LED1_PIN 255 // off
@ -65,7 +62,7 @@
#define PACKET_BUFFER_SIZE 30
// number of configurable inverters
#define MAX_NUM_INVERTERS 4
#define MAX_NUM_INVERTERS 10
// default serial interval
#define SERIAL_INTERVAL 5
@ -121,6 +118,11 @@
// default MQTT topic
#define DEF_MQTT_TOPIC "inverter"
// discovery prefix
#define MQTT_DISCOVERY_PREFIX "homeassistant"
// reconnect delay
#define MQTT_RECONNECT_DELAY 5000
#if __has_include("config_override.h")
#include "config_override.h"

161
src/config/eep.h

@ -1,161 +0,0 @@
//-----------------------------------------------------------------------------
// 2022 Ahoy, https://www.mikrocontroller.net/topic/525778
// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/
//-----------------------------------------------------------------------------
#ifndef __EEP_H__
#define __EEP_H__
#include "Arduino.h"
#include <EEPROM.h>
#ifdef ESP32
#include <nvs_flash.h>
#endif
class eep {
public:
eep() {
#ifdef ESP32
if(!EEPROM.begin(4096)) {
nvs_flash_init();
EEPROM.begin(4096);
}
#else
EEPROM.begin(4096);
#endif
}
~eep() {
EEPROM.end();
}
void read(uint32_t addr, char *str, uint8_t length) {
for(uint8_t i = 0; i < length; i ++) {
*(str++) = (char)EEPROM.read(addr++);
}
}
void read(uint32_t addr, float *value) {
uint8_t *p = (uint8_t*)value;
for(uint8_t i = 0; i < 4; i ++) {
*(p++) = (uint8_t)EEPROM.read(addr++);
}
}
void read(uint32_t addr, bool *value) {
uint8_t intVal = 0x00;
intVal = EEPROM.read(addr++);
*value = (intVal == 0x01);
}
void read(uint32_t addr, uint8_t *value) {
*value = (EEPROM.read(addr++));
}
void read(uint32_t addr, uint8_t data[], uint16_t length) {
for(uint16_t i = 0; i < length; i ++) {
*(data++) = EEPROM.read(addr++);
}
}
void read(uint32_t addr, uint16_t *value) {
*value = (EEPROM.read(addr++) << 8);
*value |= (EEPROM.read(addr++));
}
void read(uint32_t addr, uint16_t data[], uint16_t length) {
for(uint16_t i = 0; i < length; i ++) {
*(data) = (EEPROM.read(addr++) << 8);
*(data++) |= (EEPROM.read(addr++));
}
}
void read(uint32_t addr, uint32_t *value) {
*value = (EEPROM.read(addr++) << 24);
*value |= (EEPROM.read(addr++) << 16);
*value |= (EEPROM.read(addr++) << 8);
*value |= (EEPROM.read(addr++));
}
void read(uint32_t addr, uint64_t *value) {
read(addr, (uint32_t *)value);
*value <<= 32;
uint32_t tmp;
read(addr+4, &tmp);
*value |= tmp;
/**value = (EEPROM.read(addr++) << 56);
*value |= (EEPROM.read(addr++) << 48);
*value |= (EEPROM.read(addr++) << 40);
*value |= (EEPROM.read(addr++) << 32);
*value |= (EEPROM.read(addr++) << 24);
*value |= (EEPROM.read(addr++) << 16);
*value |= (EEPROM.read(addr++) << 8);
*value |= (EEPROM.read(addr++));*/
}
void write(uint32_t addr, const char *str, uint8_t length) {
for(uint8_t i = 0; i < length; i ++) {
EEPROM.write(addr++, str[i]);
}
}
void write(uint32_t addr, uint8_t data[], uint16_t length) {
for(uint16_t i = 0; i < length; i ++) {
EEPROM.write(addr++, data[i]);
}
}
void write(uint32_t addr, float value) {
uint8_t *p = (uint8_t*)&value;
for(uint8_t i = 0; i < 4; i ++) {
EEPROM.write(addr++, p[i]);
}
}
void write(uint32_t addr, bool value) {
uint8_t intVal = (value) ? 0x01 : 0x00;
EEPROM.write(addr++, intVal);
}
void write(uint32_t addr, uint8_t value) {
EEPROM.write(addr++, value);
}
void write(uint32_t addr, uint16_t value) {
EEPROM.write(addr++, (value >> 8) & 0xff);
EEPROM.write(addr++, (value ) & 0xff);
}
void write(uint32_t addr, uint16_t data[], uint16_t length) {
for(uint16_t i = 0; i < length; i ++) {
EEPROM.write(addr++, (data[i] >> 8) & 0xff);
EEPROM.write(addr++, (data[i] ) & 0xff);
}
}
void write(uint32_t addr, uint32_t value) {
EEPROM.write(addr++, (value >> 24) & 0xff);
EEPROM.write(addr++, (value >> 16) & 0xff);
EEPROM.write(addr++, (value >> 8) & 0xff);
EEPROM.write(addr++, (value ) & 0xff);
}
void write(uint32_t addr, uint64_t value) {
EEPROM.write(addr++, (value >> 56) & 0xff);
EEPROM.write(addr++, (value >> 48) & 0xff);
EEPROM.write(addr++, (value >> 40) & 0xff);
EEPROM.write(addr++, (value >> 32) & 0xff);
EEPROM.write(addr++, (value >> 24) & 0xff);
EEPROM.write(addr++, (value >> 16) & 0xff);
EEPROM.write(addr++, (value >> 8) & 0xff);
EEPROM.write(addr++, (value ) & 0xff);
}
void commit(void) {
EEPROM.commit();
}
};
#endif /*__EEP_H__*/

417
src/config/settings.h

@ -0,0 +1,417 @@
//-----------------------------------------------------------------------------
// 2022 Ahoy, https://ahoydtu.de
// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/
//-----------------------------------------------------------------------------
#ifndef __SETTINGS_H__
#define __SETTINGS_H__
#include <Arduino.h>
#include <LittleFS.h>
#include <ArduinoJson.h>
#include "../utils/dbg.h"
#include "../defines.h"
/**
* More info:
* https://arduino-esp8266.readthedocs.io/en/latest/filesystem.html#flash-layout
* */
typedef struct {
uint8_t ip[4]; // ip address
uint8_t mask[4]; // sub mask
uint8_t dns1[4]; // dns 1
uint8_t dns2[4]; // dns 2
uint8_t gateway[4]; // standard gateway
} cfgIp_t;
typedef struct {
char deviceName[DEVNAME_LEN];
char adminPwd[PWD_LEN];
// wifi
char stationSsid[SSID_LEN];
char stationPwd[PWD_LEN];
cfgIp_t ip;
} cfgSys_t;
typedef struct {
uint16_t sendInterval;
uint8_t maxRetransPerPyld;
uint8_t pinCs;
uint8_t pinCe;
uint8_t pinIrq;
uint8_t amplifierPower;
} cfgNrf24_t;
typedef struct {
char addr[NTP_ADDR_LEN];
uint16_t port;
} cfgNtp_t;
typedef struct {
float lat;
float lon;
bool disNightCom; // disable night communication
} cfgSun_t;
typedef struct {
uint16_t interval;
bool showIv;
bool debug;
} cfgSerial_t;
typedef struct {
uint8_t led0; // first LED pin
uint8_t led1; // second LED pin
} cfgLed_t;
typedef struct {
char broker[MQTT_ADDR_LEN];
uint16_t port;
char user[MQTT_USER_LEN];
char pwd[MQTT_PWD_LEN];
char topic[MQTT_TOPIC_LEN];
} cfgMqtt_t;
typedef struct {
bool enabled;
char name[MAX_NAME_LENGTH];
serial_u serial;
uint16_t chMaxPwr[4];
char chName[4][MAX_NAME_LENGTH];
} cfgIv_t;
typedef struct {
bool enabled;
cfgIv_t iv[MAX_NUM_INVERTERS];
} cfgInst_t;
typedef struct {
cfgSys_t sys;
cfgNrf24_t nrf;
cfgNtp_t ntp;
cfgSun_t sun;
cfgSerial_t serial;
cfgMqtt_t mqtt;
cfgLed_t led;
cfgInst_t inst;
} settings_t;
class settings {
public:
settings() {}
void setup() {
DPRINTLN(DBG_INFO, F("Initializing FS .."));
mValid = false;
LittleFSConfig cfg;
cfg.setAutoFormat(false);
LittleFS.setConfig(cfg);
if(!LittleFS.begin()) {
DPRINTLN(DBG_INFO, F(".. format .."));
LittleFS.format();
if(LittleFS.begin())
DPRINTLN(DBG_INFO, F(".. success"));
else
DPRINTLN(DBG_INFO, F(".. failed"));
}
else
DPRINTLN(DBG_INFO, F(" .. done"));
readSettings();
}
// should be used before OTA
void stop() {
LittleFS.end();
DPRINTLN(DBG_INFO, F("FS stopped"));
}
void getPtr(settings_t *&cfg) {
cfg = &mCfg;
}
bool getValid(void) {
return mValid;
}
void getInfo(uint32_t *used, uint32_t *size) {
FSInfo info;
LittleFS.info(info);
*used = info.usedBytes;
*size = info.totalBytes;
DPRINTLN(DBG_INFO, F("-- FILESYSTEM INFO --"));
DPRINTLN(DBG_INFO, String(info.usedBytes) + F(" of ") + String(info.totalBytes) + F(" used"));
}
void readSettings(void) {
loadDefaults();
File fp = LittleFS.open("/settings.json", "r");
if(!fp)
DPRINTLN(DBG_WARN, F("failed to load json, using default config"));
else {
DPRINTLN(DBG_INFO, fp.readString());
fp.seek(0, SeekSet);
DynamicJsonDocument root(4096);
DeserializationError err = deserializeJson(root, fp);
if(!err) {
mValid = true;
jsonWifi(root["wifi"]);
jsonNrf(root["nrf"]);
jsonNtp(root["ntp"]);
jsonSun(root["sun"]);
jsonSerial(root["serial"]);
jsonMqtt(root["mqtt"]);
jsonLed(root["led"]);
jsonInst(root["inst"]);
}
else {
Serial.println(F("failed to parse json, using default config"));
}
fp.close();
}
}
bool saveSettings(void) {
DPRINTLN(DBG_INFO, F("save settings"));
File fp = LittleFS.open("/settings.json", "w");
if(!fp) {
DPRINTLN(DBG_ERROR, F("can't open settings file!"));
return false;
}
DynamicJsonDocument json(4096);
JsonObject root = json.to<JsonObject>();
jsonWifi(root.createNestedObject(F("wifi")), true);
jsonNrf(root.createNestedObject(F("nrf")), true);
jsonNtp(root.createNestedObject(F("ntp")), true);
jsonSun(root.createNestedObject(F("sun")), true);
jsonSerial(root.createNestedObject(F("serial")), true);
jsonMqtt(root.createNestedObject(F("mqtt")), true);
jsonLed(root.createNestedObject(F("led")), true);
jsonInst(root.createNestedObject(F("inst")), true);
if(0 == serializeJson(root, fp)) {
DPRINTLN(DBG_ERROR, F("can't write settings file!"));
return false;
}
fp.close();
return true;
}
bool eraseSettings(bool eraseWifi = false) {
loadDefaults(eraseWifi);
return saveSettings();
}
String ip2Str(uint8_t ip[]) {
return String(ip[0]) + F(".")
+ String(ip[1]) + F(".")
+ String(ip[2]) + F(".")
+ String(ip[3]);
}
void ip2Arr(uint8_t ip[], const char *ipStr) {
char *tmp = new char[strlen(ipStr)];
strncpy(tmp, ipStr, strlen(ipStr));
char *p = strtok(tmp, ".");
uint8_t i = 0;
while(NULL != p) {
ip[i++] = atoi(p);
p = strtok(NULL, ".");
}
delete[] tmp;
}
private:
void loadDefaults(bool wifi = true) {
DPRINTLN(DBG_INFO, F("loadDefaults"));
memset(&mCfg, 0, sizeof(settings_t));
if(wifi) {
snprintf(mCfg.sys.stationSsid, SSID_LEN, FB_WIFI_SSID);
snprintf(mCfg.sys.stationPwd, PWD_LEN, FB_WIFI_PWD);
snprintf(mCfg.sys.deviceName, DEVNAME_LEN, DEF_DEVICE_NAME);
}
mCfg.nrf.sendInterval = SEND_INTERVAL;
mCfg.nrf.maxRetransPerPyld = DEF_MAX_RETRANS_PER_PYLD;
mCfg.nrf.pinCs = DEF_CS_PIN;
mCfg.nrf.pinCe = DEF_CE_PIN;
mCfg.nrf.pinIrq = DEF_IRQ_PIN;
mCfg.nrf.amplifierPower = DEF_AMPLIFIERPOWER & 0x03;
snprintf(mCfg.ntp.addr, NTP_ADDR_LEN, "%s", DEF_NTP_SERVER_NAME);
mCfg.ntp.port = DEF_NTP_PORT;
mCfg.sun.lat = 0.0;
mCfg.sun.lon = 0.0;
mCfg.sun.disNightCom = false;
mCfg.serial.interval = SERIAL_INTERVAL;
mCfg.serial.showIv = false;
mCfg.serial.debug = false;
mCfg.mqtt.port = DEF_MQTT_PORT;
snprintf(mCfg.mqtt.broker, MQTT_ADDR_LEN, "%s", DEF_MQTT_BROKER);
snprintf(mCfg.mqtt.user, MQTT_USER_LEN, "%s", DEF_MQTT_USER);
snprintf(mCfg.mqtt.pwd, MQTT_PWD_LEN, "%s", DEF_MQTT_PWD);
snprintf(mCfg.mqtt.topic, MQTT_TOPIC_LEN, "%s", DEF_MQTT_TOPIC);
mCfg.led.led0 = DEF_LED0_PIN;
mCfg.led.led1 = DEF_LED1_PIN;
}
void jsonWifi(JsonObject obj, bool set = false) {
if(set) {
obj[F("ssid")] = mCfg.sys.stationSsid;
obj[F("pwd")] = mCfg.sys.stationPwd;
obj[F("dev")] = mCfg.sys.deviceName;
obj[F("adm")] = mCfg.sys.adminPwd;
obj[F("ip")] = ip2Str(mCfg.sys.ip.ip);
obj[F("mask")] = ip2Str(mCfg.sys.ip.mask);
obj[F("dns1")] = ip2Str(mCfg.sys.ip.dns1);
obj[F("dns2")] = ip2Str(mCfg.sys.ip.dns2);
obj[F("gtwy")] = ip2Str(mCfg.sys.ip.gateway);
} else {
snprintf(mCfg.sys.stationSsid, SSID_LEN, "%s", obj[F("ssid")].as<const char*>());
snprintf(mCfg.sys.stationPwd, PWD_LEN, "%s", obj[F("pwd")].as<const char*>());
snprintf(mCfg.sys.deviceName, DEVNAME_LEN, "%s", obj[F("dev")].as<const char*>());
snprintf(mCfg.sys.adminPwd, PWD_LEN, "%s", obj[F("adm")].as<const char*>());
ip2Arr(mCfg.sys.ip.ip, obj[F("ip")]);
ip2Arr(mCfg.sys.ip.mask, obj[F("mask")]);
ip2Arr(mCfg.sys.ip.dns1, obj[F("dns1")]);
ip2Arr(mCfg.sys.ip.dns2, obj[F("dns2")]);
ip2Arr(mCfg.sys.ip.gateway, obj[F("gtwy")]);
}
}
void jsonNrf(JsonObject obj, bool set = false) {
if(set) {
obj[F("intvl")] = mCfg.nrf.sendInterval;
obj[F("maxRetry")] = mCfg.nrf.maxRetransPerPyld;
obj[F("cs")] = mCfg.nrf.pinCs;
obj[F("ce")] = mCfg.nrf.pinCe;
obj[F("irq")] = mCfg.nrf.pinIrq;
obj[F("pwr")] = mCfg.nrf.amplifierPower;
} else {
mCfg.nrf.sendInterval = obj[F("intvl")];
mCfg.nrf.maxRetransPerPyld = obj[F("maxRetry")];
mCfg.nrf.pinCs = obj[F("cs")];
mCfg.nrf.pinCe = obj[F("ce")];
mCfg.nrf.pinIrq = obj[F("irq")];
mCfg.nrf.amplifierPower = obj[F("pwr")];
}
}
void jsonNtp(JsonObject obj, bool set = false) {
if(set) {
obj[F("addr")] = mCfg.ntp.addr;
obj[F("port")] = mCfg.ntp.port;
} else {
snprintf(mCfg.ntp.addr, NTP_ADDR_LEN, "%s", obj[F("addr")].as<const char*>());
mCfg.ntp.port = obj[F("port")];
}
}
void jsonSun(JsonObject obj, bool set = false) {
if(set) {
obj[F("lat")] = mCfg.sun.lat;
obj[F("lon")] = mCfg.sun.lon;
obj[F("dis")] = mCfg.sun.disNightCom;
} else {
mCfg.sun.lat = obj[F("lat")];
mCfg.sun.lon = obj[F("lon")];
mCfg.sun.disNightCom = obj[F("dis")];
}
}
void jsonSerial(JsonObject obj, bool set = false) {
if(set) {
obj[F("intvl")] = mCfg.serial.interval;
obj[F("show")] = mCfg.serial.showIv;
obj[F("debug")] = mCfg.serial.debug;
} else {
mCfg.serial.interval = obj[F("intvl")];
mCfg.serial.showIv = obj[F("show")];
mCfg.serial.debug = obj[F("debug")];
}
}
void jsonMqtt(JsonObject obj, bool set = false) {
if(set) {
obj[F("broker")] = mCfg.mqtt.broker;
obj[F("port")] = mCfg.mqtt.port;
obj[F("user")] = mCfg.mqtt.user;
obj[F("pwd")] = mCfg.mqtt.pwd;
obj[F("topic")] = mCfg.mqtt.topic;
} else {
mCfg.mqtt.port = obj[F("port")];
snprintf(mCfg.mqtt.broker, MQTT_ADDR_LEN, "%s", obj[F("broker")].as<const char*>());
snprintf(mCfg.mqtt.user, MQTT_USER_LEN, "%s", obj[F("user")].as<const char*>());
snprintf(mCfg.mqtt.pwd, MQTT_PWD_LEN, "%s", obj[F("pwd")].as<const char*>());
snprintf(mCfg.mqtt.topic, MQTT_TOPIC_LEN, "%s", obj[F("topic")].as<const char*>());
}
}
void jsonLed(JsonObject obj, bool set = false) {
if(set) {
obj[F("0")] = mCfg.led.led0;
obj[F("1")] = mCfg.led.led1;
} else {
mCfg.led.led0 = obj[F("0")];
mCfg.led.led1 = obj[F("1")];
}
}
void jsonInst(JsonObject obj, bool set = false) {
if(set)
obj[F("en")] = mCfg.inst.enabled;
else
mCfg.inst.enabled = obj[F("en")];
JsonArray ivArr;
if(set)
ivArr = obj.createNestedArray(F("iv"));
for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i++) {
if(set)
jsonIv(ivArr.createNestedObject(), &mCfg.inst.iv[i], true);
else
jsonIv(obj[F("iv")][i], &mCfg.inst.iv[i]);
}
}
void jsonIv(JsonObject obj, cfgIv_t *cfg, bool set = false) {
if(set) {
obj[F("en")] = cfg->enabled;
obj[F("name")] = cfg->name;
obj[F("sn")] = cfg->serial.u64;
for(uint8_t i = 0; i < 4; i++) {
obj[F("pwr")][i] = cfg->chMaxPwr[i];
obj[F("chName")][i] = cfg->chName[i];
}
} else {
cfg->enabled = obj[F("en")];
snprintf(cfg->name, MAX_NAME_LENGTH, "%s", obj[F("name")].as<const char*>());
cfg->serial.u64 = obj[F("sn")];
for(uint8_t i = 0; i < 4; i++) {
cfg->chMaxPwr[i] = obj[F("pwr")][i];
snprintf(cfg->chName[i], MAX_NAME_LENGTH, "%s", obj[F("chName")][i].as<const char*>());
}
}
}
settings_t mCfg;
bool mValid;
};
#endif /*__SETTINGS_H__*/

150
src/defines.h

@ -13,7 +13,7 @@
//-------------------------------------
#define VERSION_MAJOR 0
#define VERSION_MINOR 5
#define VERSION_PATCH 35
#define VERSION_PATCH 36
//-------------------------------------
typedef struct {
@ -63,6 +63,11 @@ typedef enum {
RelativPersistent = 257UL // 0x0101
} PowerLimitControlType;
union serial_u {
uint64_t u64;
uint8_t b[8];
};
#define MIN_SERIAL_INTERVAL 5
#define MIN_SEND_INTERVAL 15
#define MIN_MQTT_INTERVAL 60
@ -78,116 +83,14 @@ typedef enum {
#define SSID_LEN 32
#define PWD_LEN 64
#define DEVNAME_LEN 16
#define CRC_LEN 2 // uint16_t
#define DISCLAIMER_LEN 1
#define STATIC_IP_LEN 16 // 4x uint32_t
#define STATUS_LED_LEN 2 // 2x uint8_t
#define INV_ADDR_LEN MAX_NUM_INVERTERS * 8 // uint64_t
#define INV_NAME_LEN MAX_NUM_INVERTERS * MAX_NAME_LENGTH // char[]
#define INV_CH_CH_PWR_LEN MAX_NUM_INVERTERS * 2 * 4 // uint16_t (4 channels)
#define INV_CH_CH_NAME_LEN MAX_NUM_INVERTERS * MAX_NAME_LENGTH * 4 // (4 channels)
#define INV_INTERVAL_LEN 2 // uint16_t
#define INV_MAX_RTRY_LEN 1 // uint8_t
#define INV_ENABLED_LEN 1 // uint8_t
#define CFG_SUN_LEN 9 // 2x float(4+4) + bool(1)
#define NTP_ADDR_LEN 32 // DNS Name
#define MQTT_ADDR_LEN 32 // DNS Name
#define MQTT_USER_LEN 16
#define MQTT_PWD_LEN 32
#define MQTT_TOPIC_LEN 32
#define MQTT_DISCOVERY_PREFIX "homeassistant"
#define MQTT_MAX_PACKET_SIZE 384
#define MQTT_RECONNECT_DELAY 5000
#pragma pack(push) // push current alignment to stack
#pragma pack(1) // set alignment to 1 byte boundary
typedef struct {
char broker[MQTT_ADDR_LEN];
uint16_t port;
char user[MQTT_USER_LEN];
char pwd[MQTT_PWD_LEN];
char topic[MQTT_TOPIC_LEN];
} mqttConfig_t;
#pragma pack(pop) // restore original alignment from stack
#pragma pack(push)
#pragma pack(1)
typedef struct {
char deviceName[DEVNAME_LEN];
// wifi
char stationSsid[SSID_LEN];
char stationPwd[PWD_LEN];
} sysConfig_t;
#pragma pack(pop)
#pragma pack(push)
#pragma pack(1)
typedef struct {
uint8_t ip[4]; // ip address
uint8_t mask[4]; // sub mask
uint8_t dns[4]; // dns
uint8_t gateway[4]; // standard gateway
} staticIp_t;
#pragma pack(pop)
#pragma pack(push)
#pragma pack(1)
typedef struct {
uint8_t led0; // first led pin
uint8_t led1; // second led pin
} statusLed_t;
#pragma pack(pop)
#pragma pack(push)
#pragma pack(1)
typedef struct {
// protection
char password[PWD_LEN];
// nrf24
uint16_t sendInterval;
uint8_t maxRetransPerPyld;
uint8_t pinCs;
uint8_t pinCe;
uint8_t pinIrq;
uint8_t amplifierPower;
// Disclaimer
bool disclaimer;
// ntp
char ntpAddr[NTP_ADDR_LEN];
uint16_t ntpPort;
// mqtt
mqttConfig_t mqtt;
// sun
float sunLat;
float sunLon;
bool sunDisNightCom; // disable night communication
// serial
uint16_t serialInterval;
bool serialShowIv;
bool serialDebug;
// static ip
staticIp_t staticIp;
// status LED(s)
statusLed_t led;
} config_t;
#pragma pack(pop)
#define MQTT_TOPIC_LEN 64
#define MQTT_MAX_PACKET_SIZE 384
typedef struct {
uint32_t rxFail;
@ -196,41 +99,4 @@ typedef struct {
uint32_t frmCnt;
} statistics_t;
#define CFG_MQTT_LEN MQTT_ADDR_LEN + 2 + MQTT_USER_LEN + MQTT_PWD_LEN +MQTT_TOPIC_LEN
#define CFG_SYS_LEN DEVNAME_LEN + SSID_LEN + PWD_LEN
#define CFG_LEN PWD_LEN + 7 + DISCLAIMER_LEN + NTP_ADDR_LEN + 2 + CFG_MQTT_LEN + CFG_SUN_LEN + 4 + STATIC_IP_LEN + STATUS_LED_LEN
#define ADDR_START 0
#define ADDR_CFG_SYS ADDR_START
#define ADDR_WIFI_CRC ADDR_CFG_SYS + CFG_SYS_LEN
#define ADDR_START_SETTINGS ADDR_WIFI_CRC + CRC_LEN
#define ADDR_CFG ADDR_START_SETTINGS
#define ADDR_CFG_INVERTER ADDR_CFG + CFG_LEN
#define ADDR_INV_ADDR ADDR_CFG_INVERTER
#define ADDR_INV_NAME ADDR_INV_ADDR + INV_ADDR_LEN
#define ADDR_INV_CH_PWR ADDR_INV_NAME + INV_NAME_LEN
#define ADDR_INV_CH_NAME ADDR_INV_CH_PWR + INV_CH_CH_PWR_LEN
#define ADDR_INV_INTERVAL ADDR_INV_CH_NAME + INV_CH_CH_NAME_LEN
#define ADDR_INV_MAX_RTRY ADDR_INV_INTERVAL + INV_INTERVAL_LEN
#define ADDR_INV_ENABLED ADDR_INV_MAX_RTRY + INV_MAX_RTRY_LEN
#define ADDR_NEXT ADDR_INV_MAX_RTRY + INV_ENABLED_LEN
#define ADDR_SETTINGS_CRC ADDR_NEXT + 2
#if(ADDR_SETTINGS_CRC <= ADDR_NEXT)
#pragma error "address overlap! (ADDR_SETTINGS_CRC="+ ADDR_SETTINGS_CRC +", ADDR_NEXT="+ ADDR_NEXT +")"
#endif
#if(ADDR_SETTINGS_CRC >= 4096 - CRC_LEN)
#pragma error "EEPROM size exceeded! (ADDR_SETTINGS_CRC="+ ADDR_SETTINGS_CRC +", CRC_LEN="+ CRC_LEN +")"
#pragma error "Configure less inverters? (MAX_NUM_INVERTERS=" + MAX_NUM_INVERTERS +")"
#endif
#endif /*__DEFINES_H__*/

7
src/hm/hmDefines.h

@ -9,13 +9,6 @@
#include "../utils/dbg.h"
#include <cstdint>
union serial_u {
uint64_t u64;
uint8_t b[8];
};
// units
enum {UNIT_V = 0, UNIT_A, UNIT_W, UNIT_WH, UNIT_KWH, UNIT_HZ, UNIT_C, UNIT_PCT, UNIT_VAR, UNIT_NONE};
const char* const units[] = {"V", "A", "W", "Wh", "kWh", "Hz", "°C", "%", "var", ""};

24
src/hm/hmInverter.h

@ -14,6 +14,7 @@
#include "hmDefines.h"
#include <memory>
#include <queue>
#include "../config/settings.h"
/**
* For values which are of interest and not transmitted by the inverter can be
@ -104,8 +105,8 @@ const calcFunc_t<T> calcFunctions[] = {
template <class REC_TYP>
class Inverter {
public:
cfgIv_t *config; // stored settings
uint8_t id; // unique id
char name[MAX_NAME_LENGTH]; // human readable name, eg. "HM-600.1"
uint8_t type; // integer which refers to inverter type
uint16_t alarmMesIndex; // Last recorded Alarm Message Index
uint16_t fwVersion; // Firmware Version from Info Command Request
@ -113,15 +114,12 @@ class Inverter {
float actPowerLimit; // actual power limit
uint8_t devControlCmd; // carries the requested cmd
bool devControlRequest; // true if change needed
serial_u serial; // serial number as on barcode
serial_u radioId; // id converted to modbus
uint8_t channels; // number of PV channels (1-4)
record_t<REC_TYP> recordMeas; // structure for measured values
record_t<REC_TYP> recordInfo; // structure for info values
record_t<REC_TYP> recordConfig; // structure for system config values
record_t<REC_TYP> recordAlarm; // structure for alarm values
uint16_t chMaxPwr[4]; // maximum power of the modules (Wp)
char chName[4][MAX_NAME_LENGTH]; // human readable name for channels
String lastAlarmMsg;
bool initialized; // needed to check if the inverter was correctly added (ESP32 specific - union types are never null)
@ -150,14 +148,14 @@ class Inverter {
void setQueuedCmdFinished() {
if (!_commandQueue.empty()) {
// Will destroy CommandAbstract Class Object (?)
_commandQueue.pop();
_commandQueue.pop();
}
}
void clearCmdQueue() {
while (!_commandQueue.empty()) {
// Will destroy CommandAbstract Class Object (?)
_commandQueue.pop();
_commandQueue.pop();
}
}
@ -185,8 +183,6 @@ class Inverter {
initAssignment(&recordConfig, SystemConfigPara);
initAssignment(&recordAlarm, AlarmData);
toRadioId();
memset(name, 0, MAX_NAME_LENGTH);
memset(chName, 0, MAX_NAME_LENGTH * 4);
initialized = true;
}
@ -484,10 +480,10 @@ class Inverter {
void toRadioId(void) {
DPRINTLN(DBG_VERBOSE, F("hmInverter.h:toRadioId"));
radioId.u64 = 0ULL;
radioId.b[4] = serial.b[0];
radioId.b[3] = serial.b[1];
radioId.b[2] = serial.b[2];
radioId.b[1] = serial.b[3];
radioId.b[4] = config->serial.b[0];
radioId.b[3] = config->serial.b[1];
radioId.b[2] = config->serial.b[2];
radioId.b[1] = config->serial.b[3];
radioId.b[0] = 0x01;
}
};
@ -583,8 +579,8 @@ static T calcIrradiation(Inverter<> *iv, uint8_t arg0) {
if(NULL != iv) {
record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug);
uint8_t pos = iv->getPosByChFld(arg0, FLD_PDC, rec);
if(iv->chMaxPwr[arg0-1] > 0)
return iv->getValue(pos, rec) / iv->chMaxPwr[arg0-1] * 100.0f;
if(iv->config->chMaxPwr[arg0-1] > 0)
return iv->getValue(pos, rec) / iv->config->chMaxPwr[arg0-1] * 100.0f;
}
return 0.0;
}

42
src/hm/hmSystem.h

@ -20,7 +20,7 @@ class HmSystem {
RadioType Radio;
typedef BUFFER BufferType;
BufferType BufCtrl;
//DevControlCmdType DevControlCmd;
//DevControlCmdType DevControlCmd;
HmSystem() {
mNumInv = 0;
@ -37,7 +37,18 @@ class HmSystem {
Radio.setup(&BufCtrl, ampPwr, irqPin, cePin, csPin);
}
INVERTERTYPE *addInverter(const char *name, uint64_t serial, uint16_t chMaxPwr[]) {
void addInverters(cfgInst_t *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)
DPRINTLN(DBG_INFO, "added inverter " + String(iv->config->serial.u64, HEX));
}
}
}
INVERTERTYPE *addInverter(cfgIv_t *config) {
DPRINTLN(DBG_VERBOSE, F("hmSystem.h:addInverter"));
if(MAX_INVERTER <= mNumInv) {
DPRINT(DBG_WARN, F("max number of inverters reached!"));
@ -45,18 +56,17 @@ class HmSystem {
}
INVERTERTYPE *p = &mInverter[mNumInv];
p->id = mNumInv;
p->serial.u64 = serial;
memcpy(p->chMaxPwr, chMaxPwr, (4*2));
DPRINT(DBG_VERBOSE, "SERIAL: " + String(p->serial.b[5], HEX));
DPRINTLN(DBG_VERBOSE, " " + String(p->serial.b[4], HEX));
if(p->serial.b[5] == 0x11) {
switch(p->serial.b[4]) {
p->config = config;
DPRINT(DBG_VERBOSE, "SERIAL: " + String(p->config->serial.b[5], HEX));
DPRINTLN(DBG_VERBOSE, " " + String(p->config->serial.b[4], HEX));
if(p->config->serial.b[5] == 0x11) {
switch(p->config->serial.b[4]) {
case 0x21: p->type = INV_TYPE_1CH; break;
case 0x41: p->type = INV_TYPE_2CH; break;
case 0x61: p->type = INV_TYPE_4CH; break;
default:
default:
DPRINT(DBG_ERROR, F("unknown inverter type: 11"));
DPRINTLN(DBG_ERROR, String(p->serial.b[4], HEX));
DPRINTLN(DBG_ERROR, String(p->config->serial.b[4], HEX));
break;
}
}
@ -64,8 +74,6 @@ class HmSystem {
DPRINTLN(DBG_ERROR, F("inverter type can't be detected!"));
p->init();
uint8_t len = (uint8_t)strlen(name);
strncpy(p->name, name, (len > MAX_NAME_LENGTH) ? MAX_NAME_LENGTH : len);
mNumInv ++;
return p;
@ -76,10 +84,10 @@ class HmSystem {
INVERTERTYPE *p;
for(uint8_t i = 0; i < mNumInv; i++) {
p = &mInverter[i];
if((p->serial.b[3] == buf[0])
&& (p->serial.b[2] == buf[1])
&& (p->serial.b[1] == buf[2])
&& (p->serial.b[0] == buf[3]))
if((p->config->serial.b[3] == buf[0])
&& (p->config->serial.b[2] == buf[1])
&& (p->config->serial.b[1] == buf[2])
&& (p->config->serial.b[0] == buf[3]))
return p;
}
return NULL;
@ -89,7 +97,7 @@ class HmSystem {
DPRINTLN(DBG_VERBOSE, F("hmSystem.h:getInverterByPos"));
if(pos >= MAX_INVERTER)
return NULL;
else if((mInverter[pos].initialized && mInverter[pos].serial.u64 != 0ULL) || false == check)
else if((mInverter[pos].initialized && mInverter[pos].config->serial.u64 != 0ULL) || false == check)
return &mInverter[pos];
else
return NULL;

6
src/platformio.ini

@ -10,12 +10,13 @@
[platformio]
src_dir = .
include_dir = .
[env]
framework = arduino
board_build.filesystem = littlefs
build_flags =
-include "config.h"
;build_flags =
; ;;;;; Possible Debug options ;;;;;;
; https://docs.platformio.org/en/latest/platforms/espressif8266.html#debug-level
;-DDEBUG_ESP_PORT=Serial
@ -43,6 +44,7 @@ lib_deps =
;esp8266/SPI
;esp8266/Ticker
[env:esp8266-release]
platform = espressif8266
board = esp12e

2
src/utils/ahoyTimer.h

@ -1,5 +1,5 @@
//-----------------------------------------------------------------------------
// 2022 Ahoy, https://www.mikrocontroller.net/topic/525778
// 2022 Ahoy, https://ahoydtu.de
// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/
//-----------------------------------------------------------------------------

2
src/utils/sun.h

@ -1,5 +1,5 @@
//-----------------------------------------------------------------------------
// 2022 Ahoy, https://www.mikrocontroller.net/topic/525778
// 2022 Ahoy, https://ahoydtu.de
// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/
//-----------------------------------------------------------------------------

2
src/web/html/serial.html

@ -128,7 +128,7 @@
});
document.getElementById("scroll").addEventListener("click", function() {
mAutoScroll = !mAutoScroll;
this.value = (mAutoScroll) ? "autoscroll" : "manual scoll";
this.value = (mAutoScroll) ? "autoscroll" : "manual scroll";
});
if (!!window.EventSource) {

8
src/web/html/setup.html

@ -67,8 +67,10 @@
<input type="text" name="ipAddr" class="text" maxlength="15" />
<label for="ipMask">Submask</label>
<input type="text" name="ipMask" class="text" maxlength="15" />
<label for="ipDns">DNS</label>
<input type="text" name="ipDns" class="text" maxlength="15" />
<label for="ipDns1">DNS 1</label>
<input type="text" name="ipDns1" class="text" maxlength="15" />
<label for="ipDns2">DNS 2</label>
<input type="text" name="ipDns2" class="text" maxlength="15" />
<label for="ipGateway">Gateway</label>
<input type="text" name="ipGateway" class="text" maxlength="15" />
</fieldset>
@ -336,7 +338,7 @@
}
function parseStaticIp(obj) {
for(var i of [["ipAddr", "ip"], ["ipMask", "mask"], ["ipDns", "dns"], ["ipGateway", "gateway"]])
for(var i of [["ipAddr", "ip"], ["ipMask", "mask"], ["ipDns1", "dns1"], ["ipDns2", "dns2"], ["ipGateway", "gateway"]])
if(null != obj[i[1]])
document.getElementsByName(i[0])[0].value = obj[i[1]];
}

44
src/web/mqtt.h

@ -1,5 +1,5 @@
//-----------------------------------------------------------------------------
// 2022 Ahoy, https://www.mikrocontroller.net/topic/525778
// 2022 Ahoy, https://ahoydtu.de
// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/
//-----------------------------------------------------------------------------
@ -21,9 +21,9 @@
#include "../utils/ahoyTimer.h"
#include "../config/config.h"
#include <PubSubClient.h>
#include <ArduinoJson.h>
#include "../defines.h"
#include "../hm/hmSystem.h"
#include <ArduinoJson.h>
template<class HMSYSTEM>
class mqtt {
@ -34,18 +34,16 @@ class mqtt {
mLastReconnect = 0;
mTxCnt = 0;
memset(mDevName, 0, DEVNAME_LEN);
}
~mqtt() { }
void setup(mqttConfig_t *cfg, const char *devname, const char *version, HMSYSTEM *sys, uint32_t *utcTs) {
void setup(cfgMqtt_t *cfg, const char *devName, const char *version, HMSYSTEM *sys, uint32_t *utcTs) {
DPRINTLN(DBG_VERBOSE, F("mqtt.h:setup"));
mAddressSet = true;
snprintf(mDevName, DEVNAME_LEN, "%s", devname);
mCfg = cfg;
mDevName = devName;
mSys = sys;
mUtcTimestamp = utcTs;
@ -55,7 +53,7 @@ class mqtt {
setCallback(std::bind(&mqtt<HMSYSTEM>::cbMqtt, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
sendMsg("version", version);
sendMsg("device", devname);
sendMsg("device", devName);
sendMsg("uptime", "0");
}
@ -76,8 +74,8 @@ class mqtt {
void sendMsg(const char *topic, const char *msg) {
//DPRINTLN(DBG_VERBOSE, F("mqtt.h:sendMsg"));
if(mAddressSet) {
char top[64];
snprintf(top, 64, "%s/%s", mCfg->topic, topic);
char top[66];
snprintf(top, 66, "%s/%s", mCfg->topic, topic);
sendMsg2(top, msg, false);
}
}
@ -118,22 +116,22 @@ class mqtt {
if (NULL != iv) {
record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug);
DynamicJsonDocument deviceDoc(128);
deviceDoc["name"] = iv->name;
deviceDoc["ids"] = String(iv->serial.u64, HEX);
deviceDoc["name"] = iv->config->name;
deviceDoc["ids"] = String(iv->config->serial.u64, HEX);
deviceDoc["cu"] = F("http://") + String(WiFi.localIP().toString());
deviceDoc["mf"] = "Hoymiles";
deviceDoc["mdl"] = iv->name;
deviceDoc["mdl"] = iv->config->name;
JsonObject deviceObj = deviceDoc.as<JsonObject>();
DynamicJsonDocument doc(384);
for (uint8_t i = 0; i < rec->length; i++) {
if (rec->assign[i].ch == CH0) {
snprintf(name, 32, "%s %s", iv->name, iv->getFieldName(i, rec));
snprintf(name, 32, "%s %s", iv->config->name, iv->getFieldName(i, rec));
} else {
snprintf(name, 32, "%s CH%d %s", iv->name, rec->assign[i].ch, iv->getFieldName(i, rec));
snprintf(name, 32, "%s CH%d %s", iv->config->name, rec->assign[i].ch, iv->getFieldName(i, rec));
}
snprintf(stateTopic, 64, "%s/%s/ch%d/%s", topic, iv->name, rec->assign[i].ch, iv->getFieldName(i, rec));
snprintf(discoveryTopic, 64, "%s/sensor/%s/ch%d_%s/config", MQTT_DISCOVERY_PREFIX, iv->name, rec->assign[i].ch, iv->getFieldName(i, rec));
snprintf(stateTopic, 64, "%s/%s/ch%d/%s", topic, iv->config->name, rec->assign[i].ch, iv->getFieldName(i, rec));
snprintf(discoveryTopic, 64, "%s/sensor/%s/ch%d_%s/config", MQTT_DISCOVERY_PREFIX, iv->config->name, rec->assign[i].ch, iv->getFieldName(i, rec));
snprintf(uniq_id, 32, "ch%d_%s", rec->assign[i].ch, iv->getFieldName(i, rec));
const char *devCls = getFieldDeviceClass(rec->assign[i].fieldId);
const char *stateCls = getFieldStateClass(rec->assign[i].fieldId);
@ -141,7 +139,7 @@ class mqtt {
doc["name"] = name;
doc["stat_t"] = stateTopic;
doc["unit_of_meas"] = iv->getUnit(i, rec);
doc["uniq_id"] = String(iv->serial.u64, HEX) + "_" + uniq_id;
doc["uniq_id"] = String(iv->config->serial.u64, HEX) + "_" + uniq_id;
doc["dev"] = deviceObj;
doc["exp_aft"] = MQTT_INTERVAL + 5; // add 5 sec if connection is bad or ESP too slow @TODO: stimmt das wirklich als expire!?
if (devCls != NULL)
@ -248,7 +246,7 @@ class mqtt {
if (MQTT_STATUS_AVAIL_PROD == status)
status = MQTT_STATUS_AVAIL_NOT_PROD;
}
snprintf(topic, 32 + MAX_NAME_LENGTH, "%s/available_text", iv->name);
snprintf(topic, 32 + MAX_NAME_LENGTH, "%s/available_text", iv->config->name);
snprintf(val, 40, "%s%s%s%s",
(status == MQTT_STATUS_NOT_AVAIL_NOT_PROD) ? "not yet " : "",
"available and ",
@ -257,11 +255,11 @@ class mqtt {
);
sendMsg(topic, val);
snprintf(topic, 32 + MAX_NAME_LENGTH, "%s/available", iv->name);
snprintf(topic, 32 + MAX_NAME_LENGTH, "%s/available", iv->config->name);
snprintf(val, 40, "%d", status);
sendMsg(topic, val);
snprintf(topic, 32 + MAX_NAME_LENGTH, "%s/last_success", iv->name);
snprintf(topic, 32 + MAX_NAME_LENGTH, "%s/last_success", iv->config->name);
snprintf(val, 40, "%i", iv->getLastTs(rec) * 1000);
sendMsg(topic, val);
}
@ -269,7 +267,7 @@ class mqtt {
// data
if(iv->isAvailable(*mUtcTimestamp, rec)) {
for (uint8_t i = 0; i < rec->length; i++) {
snprintf(topic, 32 + MAX_NAME_LENGTH, "%s/ch%d/%s", iv->name, rec->assign[i].ch, fields[rec->assign[i].fieldId]);
snprintf(topic, 32 + MAX_NAME_LENGTH, "%s/ch%d/%s", iv->config->name, rec->assign[i].ch, fields[rec->assign[i].fieldId]);
snprintf(val, 40, "%.3f", iv->getValue(i, rec));
sendMsg(topic, val);
@ -435,8 +433,8 @@ class mqtt {
uint32_t *mUtcTimestamp;
bool mAddressSet;
mqttConfig_t *mCfg;
char mDevName[DEVNAME_LEN];
cfgMqtt_t *mCfg;
const char *mDevName;
uint32_t mLastReconnect;
uint32_t mTxCnt;
std::queue<uint8_t> mSendList;

101
src/web/web.cpp

@ -26,15 +26,14 @@
const char* const pinArgNames[] = {"pinCs", "pinCe", "pinIrq", "pinLed0", "pinLed1"};
//-----------------------------------------------------------------------------
web::web(app *main, sysConfig_t *sysCfg, config_t *config, statistics_t *stat, char version[]) {
web::web(app *main, settings_t *config, statistics_t *stat, char version[]) {
mMain = main;
mSysCfg = sysCfg;
mConfig = config;
mStat = stat;
mVersion = version;
mWeb = new AsyncWebServer(80);
mEvts = new AsyncEventSource("/events");
mApi = new webApi(mWeb, main, sysCfg, config, stat, version);
mApi = new webApi(mWeb, main, config, stat, version);
mProtected = true;
mLogoutTimeout = 0;
@ -151,7 +150,7 @@ void web::onLogin(AsyncWebServerRequest *request) {
DPRINTLN(DBG_VERBOSE, F("onLogin"));
if(request->args() > 0) {
if(String(request->arg("pwd")) == String(mConfig->password)) {
if(String(request->arg("pwd")) == String(mConfig->sys.adminPwd)) {
mProtected = false;
request->redirect("/");
}
@ -260,7 +259,7 @@ void web::showErase(AsyncWebServerRequest *request) {
}
DPRINTLN(DBG_VERBOSE, F("showErase"));
mMain->eraseSettings();
mMain->eraseSettings(false);
onReboot(request);
}
@ -277,9 +276,11 @@ void web::showFactoryRst(AsyncWebServerRequest *request) {
int refresh = 3;
if(request->args() > 0) {
if(request->arg("reset").toInt() == 1) {
mMain->eraseSettings(true);
content = F("factory reset: success\n\nrebooting ... ");
refresh = 10;
if(mMain->eraseSettings(true))
content = F("factory reset: success\n\nrebooting ... ");
else
content = F("factory reset: failed\n\nrebooting ... ");
}
else {
content = F("factory reset: aborted");
@ -328,36 +329,40 @@ void web::showSave(AsyncWebServerRequest *request) {
// general
if(request->arg("ssid") != "")
request->arg("ssid").toCharArray(mSysCfg->stationSsid, SSID_LEN);
request->arg("ssid").toCharArray(mConfig->sys.stationSsid, SSID_LEN);
if(request->arg("pwd") != "{PWD}")
request->arg("pwd").toCharArray(mSysCfg->stationPwd, PWD_LEN);
request->arg("pwd").toCharArray(mConfig->sys.stationPwd, PWD_LEN);
if(request->arg("device") != "")
request->arg("device").toCharArray(mSysCfg->deviceName, DEVNAME_LEN);
request->arg("device").toCharArray(mConfig->sys.deviceName, DEVNAME_LEN);
if(request->arg("adminpwd") != "{PWD}") {
request->arg("adminpwd").toCharArray(mConfig->password, PWD_LEN);
mProtected = (strlen(mConfig->password) > 0);
request->arg("adminpwd").toCharArray(mConfig->sys.adminPwd, PWD_LEN);
mProtected = (strlen(mConfig->sys.adminPwd) > 0);
}
// static ip
if(request->arg("ipAddr") != "") {
request->arg("ipAddr").toCharArray(buf, SSID_LEN);
ip2Arr(mConfig->staticIp.ip, buf);
ip2Arr(mConfig->sys.ip.ip, buf);
if(request->arg("ipMask") != "") {
request->arg("ipMask").toCharArray(buf, SSID_LEN);
ip2Arr(mConfig->staticIp.mask, buf);
ip2Arr(mConfig->sys.ip.mask, buf);
}
if(request->arg("ipDns") != "") {
request->arg("ipDns").toCharArray(buf, SSID_LEN);
ip2Arr(mConfig->staticIp.dns, buf);
if(request->arg("ipDns1") != "") {
request->arg("ipDns1").toCharArray(buf, SSID_LEN);
ip2Arr(mConfig->sys.ip.dns1, buf);
}
if(request->arg("ipDns2") != "") {
request->arg("ipDns2").toCharArray(buf, SSID_LEN);
ip2Arr(mConfig->sys.ip.dns2, buf);
}
if(request->arg("ipGateway") != "") {
request->arg("ipGateway").toCharArray(buf, SSID_LEN);
ip2Arr(mConfig->staticIp.gateway, buf);
ip2Arr(mConfig->sys.ip.gateway, buf);
}
}
else
memset(&mConfig->staticIp, 0, sizeof(staticIp_t));
memset(&mConfig->sys.ip.ip, 0, 4);
// inverter
@ -368,8 +373,8 @@ void web::showSave(AsyncWebServerRequest *request) {
request->arg("inv" + String(i) + "Addr").toCharArray(buf, 20);
if(strlen(buf) == 0)
memset(buf, 0, 20);
iv->serial.u64 = mMain->Serial2u64(buf);
switch(iv->serial.b[4]) {
iv->config->serial.u64 = mMain->Serial2u64(buf);
switch(iv->config->serial.b[4]) {
case 0x21: iv->type = INV_TYPE_1CH; iv->channels = 1; break;
case 0x41: iv->type = INV_TYPE_2CH; iv->channels = 2; break;
case 0x61: iv->type = INV_TYPE_4CH; iv->channels = 4; break;
@ -377,59 +382,54 @@ void web::showSave(AsyncWebServerRequest *request) {
}
// name
request->arg("inv" + String(i) + "Name").toCharArray(iv->name, MAX_NAME_LENGTH);
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->chMaxPwr[j] = request->arg("inv" + String(i) + "ModPwr" + String(j)).toInt() & 0xffff;
request->arg("inv" + String(i) + "ModName" + String(j)).toCharArray(iv->chName[j], MAX_NAME_LENGTH);
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);
}
iv->initialized = true;
}
if(request->arg("invInterval") != "")
mConfig->sendInterval = request->arg("invInterval").toInt();
mConfig->nrf.sendInterval = request->arg("invInterval").toInt();
if(request->arg("invRetry") != "")
mConfig->maxRetransPerPyld = request->arg("invRetry").toInt();
// Disclaimer
if(request->arg("disclaimer") != "")
mConfig->disclaimer = strcmp("true", request->arg("disclaimer").c_str()) == 0 ? true : false;
DPRINTLN(DBG_INFO, request->arg("disclaimer").c_str());
mConfig->nrf.maxRetransPerPyld = request->arg("invRetry").toInt();
// pinout
uint8_t pin;
for(uint8_t i = 0; i < 5; i ++) {
pin = request->arg(String(pinArgNames[i])).toInt();
switch(i) {
default: mConfig->pinCs = ((pin != 0xff) ? pin : DEF_CS_PIN); break;
case 1: mConfig->pinCe = ((pin != 0xff) ? pin : DEF_CE_PIN); break;
case 2: mConfig->pinIrq = ((pin != 0xff) ? pin : DEF_IRQ_PIN); break;
default: 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->led.led0 = pin; break;
case 4: mConfig->led.led1 = pin; break;
}
}
// nrf24 amplifier power
mConfig->amplifierPower = request->arg("rf24Power").toInt() & 0x03;
mConfig->nrf.amplifierPower = request->arg("rf24Power").toInt() & 0x03;
// ntp
if(request->arg("ntpAddr") != "") {
request->arg("ntpAddr").toCharArray(mConfig->ntpAddr, NTP_ADDR_LEN);
mConfig->ntpPort = request->arg("ntpPort").toInt() & 0xffff;
request->arg("ntpAddr").toCharArray(mConfig->ntp.addr, NTP_ADDR_LEN);
mConfig->ntp.port = request->arg("ntpPort").toInt() & 0xffff;
}
// sun
if(request->arg("sunLat") == "" || (request->arg("sunLon") == "")) {
mConfig->sunLat = 0.0;
mConfig->sunLon = 0.0;
mConfig->sunDisNightCom = false;
mConfig->sun.lat = 0.0;
mConfig->sun.lon = 0.0;
mConfig->sun.disNightCom = false;
} else {
mConfig->sunLat = request->arg("sunLat").toFloat();
mConfig->sunLon = request->arg("sunLon").toFloat();
mConfig->sunDisNightCom = (request->arg("sunDisNightCom") == "on");
mConfig->sun.lat = request->arg("sunLat").toFloat();
mConfig->sun.lon = request->arg("sunLon").toFloat();
mConfig->sun.disNightCom = (request->arg("sunDisNightCom") == "on");
}
// mqtt
if(request->arg("mqttAddr") != "") {
String addr = request->arg("mqttAddr");
@ -444,15 +444,14 @@ void web::showSave(AsyncWebServerRequest *request) {
// serial console
if(request->arg("serIntvl") != "") {
mConfig->serialInterval = request->arg("serIntvl").toInt() & 0xffff;
mConfig->serial.interval = request->arg("serIntvl").toInt() & 0xffff;
mConfig->serialDebug = (request->arg("serDbg") == "on");
mConfig->serialShowIv = (request->arg("serEn") == "on");
mConfig->serial.debug = (request->arg("serDbg") == "on");
mConfig->serial.showIv = (request->arg("serEn") == "on");
// Needed to log TX buffers to serial console
mMain->mSys->Radio.mSerialDebug = mConfig->serialDebug;
mMain->mSys->Radio.mSerialDebug = mConfig->serial.debug;
}
mMain->saveValues();
mMain->saveSettings();
if(request->arg("reboot") == "on")
onReboot(request);
@ -701,7 +700,7 @@ void web::showMetrics(void) {
String metrics;
char headline[80];
snprintf(headline, 80, "ahoy_solar_info{version=\"%s\",image=\"\",devicename=\"%s\"} 1", mVersion, mSysCfg->deviceName);
snprintf(headline, 80, "ahoy_solar_info{version=\"%s\",image=\"\",devicename=\"%s\"} 1", mVersion, mconfig->sys.deviceName);
metrics += "# TYPE ahoy_solar_info gauge\n" + String(headline) + "\n";
for(uint8_t id = 0; id < mMain->mSys->getNumInverters(); id++) {

5
src/web/web.h

@ -24,7 +24,7 @@ class webApi;
class web {
public:
web(app *main, sysConfig_t *sysCfg, config_t *config, statistics_t *stat, char version[]);
web(app *main, settings_t *config, statistics_t *stat, char version[]);
~web() {}
void setup(void);
@ -85,8 +85,7 @@ class web {
bool mProtected;
uint32_t mLogoutTimeout;
config_t *mConfig;
sysConfig_t *mSysCfg;
settings_t *mConfig;
statistics_t *mStat;
char *mVersion;
app *mMain;

69
src/web/webApi.cpp

@ -11,10 +11,9 @@
#include "webApi.h"
//-----------------------------------------------------------------------------
webApi::webApi(AsyncWebServer *srv, app *app, sysConfig_t *sysCfg, config_t *config, statistics_t *stat, char version[]) {
webApi::webApi(AsyncWebServer *srv, app *app, settings_t *config, statistics_t *stat, char version[]) {
mSrv = srv;
mApp = app;
mSysCfg = sysCfg;
mConfig = config;
mStat = stat;
mVersion = version;
@ -143,8 +142,8 @@ void webApi::onDwnldSetup(AsyncWebServerRequest *request) {
//-----------------------------------------------------------------------------
void webApi::getSysInfo(JsonObject obj) {
obj[F("ssid")] = mSysCfg->stationSsid;
obj[F("device_name")] = mSysCfg->deviceName;
obj[F("ssid")] = mConfig->sys.stationSsid;
obj[F("device_name")] = mConfig->sys.deviceName;
obj[F("version")] = String(mVersion);
obj[F("build")] = String(AUTO_GIT_HASH);
obj[F("ts_uptime")] = mApp->getUptime();
@ -153,8 +152,7 @@ void webApi::getSysInfo(JsonObject obj) {
obj[F("ts_sunset")] = mApp->getSunset();
obj[F("ts_sun_upd")] = mApp->getLatestSunTimestamp();
obj[F("wifi_rssi")] = WiFi.RSSI();
obj[F("disclaimer")] = mConfig->disclaimer;
obj[F("pwd_set")] = (strlen(mConfig->password) > 0);
obj[F("pwd_set")] = (strlen(mConfig->sys.adminPwd) > 0);
#if defined(ESP32)
obj[F("esp_type")] = F("ESP32");
#else
@ -221,19 +219,19 @@ void webApi::getInverterList(JsonObject obj) {
if(NULL != iv) {
JsonObject obj2 = invArr.createNestedObject();
obj2[F("id")] = i;
obj2[F("name")] = String(iv->name);
obj2[F("serial")] = String(iv->serial.u64, HEX);
obj2[F("name")] = String(iv->config->name);
obj2[F("serial")] = String(iv->config->serial.u64, HEX);
obj2[F("channels")] = iv->channels;
obj2[F("version")] = String(iv->fwVersion);
for(uint8_t j = 0; j < iv->channels; j ++) {
obj2[F("ch_max_power")][j] = iv->chMaxPwr[j];
obj2[F("ch_name")][j] = iv->chName[j];
obj2[F("ch_max_power")][j] = iv->config->chMaxPwr[j];
obj2[F("ch_name")][j] = iv->config->chName[j];
}
}
}
obj[F("interval")] = String(mConfig->sendInterval);
obj[F("retries")] = String(mConfig->maxRetransPerPyld);
obj[F("interval")] = String(mConfig->nrf.sendInterval);
obj[F("retries")] = String(mConfig->nrf.maxRetransPerPyld);
obj[F("max_num_inverters")] = MAX_NUM_INVERTERS;
}
@ -250,23 +248,23 @@ void webApi::getMqtt(JsonObject obj) {
//-----------------------------------------------------------------------------
void webApi::getNtp(JsonObject obj) {
obj[F("addr")] = String(mConfig->ntpAddr);
obj[F("port")] = String(mConfig->ntpPort);
obj[F("addr")] = String(mConfig->ntp.addr);
obj[F("port")] = String(mConfig->ntp.port);
}
//-----------------------------------------------------------------------------
void webApi::getSun(JsonObject obj) {
obj[F("lat")] = mConfig->sunLat ? String(mConfig->sunLat, 5) : "";
obj[F("lon")] = mConfig->sunLat ? String(mConfig->sunLon, 5) : "";
obj[F("disnightcom")] = mConfig->sunDisNightCom;
obj[F("lat")] = mConfig->sun.lat ? String(mConfig->sun.lat, 5) : "";
obj[F("lon")] = mConfig->sun.lat ? String(mConfig->sun.lon, 5) : "";
obj[F("disnightcom")] = mConfig->sun.disNightCom;
}
//-----------------------------------------------------------------------------
void webApi::getPinout(JsonObject obj) {
obj[F("cs")] = mConfig->pinCs;
obj[F("ce")] = mConfig->pinCe;
obj[F("irq")] = mConfig->pinIrq;
obj[F("cs")] = mConfig->nrf.pinCs;
obj[F("ce")] = mConfig->nrf.pinCe;
obj[F("irq")] = mConfig->nrf.pinIrq;
obj[F("led0")] = mConfig->led.led0;
obj[F("led1")] = mConfig->led.led1;
}
@ -274,25 +272,26 @@ void webApi::getPinout(JsonObject obj) {
//-----------------------------------------------------------------------------
void webApi::getRadio(JsonObject obj) {
obj[F("power_level")] = mConfig->amplifierPower;
obj[F("power_level")] = mConfig->nrf.amplifierPower;
}
//-----------------------------------------------------------------------------
void webApi::getSerial(JsonObject obj) {
obj[F("interval")] = (uint16_t)mConfig->serialInterval;
obj[F("show_live_data")] = mConfig->serialShowIv;
obj[F("debug")] = mConfig->serialDebug;
obj[F("interval")] = (uint16_t)mConfig->serial.interval;
obj[F("show_live_data")] = mConfig->serial.showIv;
obj[F("debug")] = mConfig->serial.debug;
}
//-----------------------------------------------------------------------------
void webApi::getStaticIp(JsonObject obj) {
if(mConfig->staticIp.ip[0] != 0) {
obj[F("ip")] = ip2String(mConfig->staticIp.ip);
obj[F("mask")] = ip2String(mConfig->staticIp.mask);
obj[F("dns")] = ip2String(mConfig->staticIp.dns);
obj[F("gateway")] = ip2String(mConfig->staticIp.gateway);
if(mConfig->sys.ip.ip[0] != 0) {
obj[F("ip")] = ip2String(mConfig->sys.ip.ip);
obj[F("mask")] = ip2String(mConfig->sys.ip.mask);
obj[F("dns1")] = ip2String(mConfig->sys.ip.dns1);
obj[F("dns2")] = ip2String(mConfig->sys.ip.dns2);
obj[F("gateway")] = ip2String(mConfig->sys.ip.gateway);
}
}
@ -314,7 +313,7 @@ void webApi::getMenu(JsonObject obj) {
obj["link"][6] = "/update";
obj["name"][7] = "System";
obj["link"][7] = "/system";
if(strlen(mConfig->password) > 0) {
if(strlen(mConfig->sys.adminPwd) > 0) {
obj["name"][8] = "-";
obj["name"][9] = "Logout";
obj["link"][9] = "/logout";
@ -327,7 +326,7 @@ void webApi::getIndex(JsonObject obj) {
getMenu(obj.createNestedObject(F("menu")));
getSysInfo(obj.createNestedObject(F("system")));
getStatistics(obj.createNestedObject(F("statistics")));
obj["refresh_interval"] = mConfig->sendInterval;
obj["refresh_interval"] = mConfig->nrf.sendInterval;
JsonArray inv = obj.createNestedArray(F("inverter"));
Inverter<> *iv;
@ -337,7 +336,7 @@ void webApi::getIndex(JsonObject obj) {
record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug);
JsonObject invObj = inv.createNestedObject();
invObj[F("id")] = i;
invObj[F("name")] = String(iv->name);
invObj[F("name")] = String(iv->config->name);
invObj[F("version")] = String(iv->fwVersion);
invObj[F("is_avail")] = iv->isAvailable(mApp->getTimestamp(), rec);
invObj[F("is_producing")] = iv->isProducing(mApp->getTimestamp(), rec);
@ -387,7 +386,7 @@ void webApi::getLive(JsonObject obj) {
getMenu(obj.createNestedObject(F("menu")));
getSysInfo(obj.createNestedObject(F("system")));
JsonArray invArr = obj.createNestedArray(F("inverter"));
obj["refresh_interval"] = mConfig->sendInterval;
obj["refresh_interval"] = mConfig->nrf.sendInterval;
uint8_t list[] = {FLD_UAC, FLD_IAC, FLD_PAC, FLD_F, FLD_PF, FLD_T, FLD_YT, FLD_YD, FLD_PDC, FLD_EFF, FLD_Q};
@ -398,7 +397,7 @@ void webApi::getLive(JsonObject obj) {
if(NULL != iv) {
record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug);
JsonObject obj2 = invArr.createNestedObject();
obj2[F("name")] = String(iv->name);
obj2[F("name")] = String(iv->config->name);
obj2[F("channels")] = iv->channels;
obj2[F("power_limit_read")] = round3(iv->actPowerLimit);
obj2[F("last_alarm")] = String(iv->lastAlarmMsg);
@ -415,7 +414,7 @@ void webApi::getLive(JsonObject obj) {
}
for(uint8_t j = 1; j <= iv->channels; j ++) {
obj2[F("ch_names")][j] = String(iv->chName[j-1]);
obj2[F("ch_names")][j] = String(iv->config->chName[j-1]);
JsonArray cur = ch.createNestedArray();
for (uint8_t k = 0; k < 6; k++) {
switch(k) {

5
src/web/webApi.h

@ -16,7 +16,7 @@ class app;
class webApi {
public:
webApi(AsyncWebServer *srv, app *app, sysConfig_t *sysCfg, config_t *config, statistics_t *stat, char version[]);
webApi(AsyncWebServer *srv, app *app, settings_t *config, statistics_t *stat, char version[]);
void setup(void);
void loop(void);
@ -72,8 +72,7 @@ class webApi {
AsyncWebServer *mSrv;
app *mApp;
config_t *mConfig;
sysConfig_t *mSysCfg;
settings_t *mConfig;
statistics_t *mStat;
char *mVersion;

36
src/wifi/ahoywifi.cpp

@ -16,9 +16,7 @@
//-----------------------------------------------------------------------------
ahoywifi::ahoywifi(app *main, sysConfig_t *sysCfg, config_t *config) {
mMain = main;
mSysCfg = sysCfg;
ahoywifi::ahoywifi(settings_t *config) {
mConfig = config;
mDns = new DNSServer();
@ -34,18 +32,17 @@ ahoywifi::ahoywifi(app *main, sysConfig_t *sysCfg, config_t *config) {
//-----------------------------------------------------------------------------
void ahoywifi::setup(uint32_t timeout, bool settingValid) {
#ifdef FB_WIFI_OVERRIDDEN
mStationWifiIsDef = false;
#else
mStationWifiIsDef = (strncmp(mSysCfg->stationSsid, FB_WIFI_SSID, 14) == 0);
mStationWifiIsDef = (strncmp(mConfig->sys.stationSsid, FB_WIFI_SSID, 14) == 0);
#endif
mWifiStationTimeout = timeout;
#ifndef AP_ONLY
if(false == mApActive)
mApActive = (mStationWifiIsDef) ? true : setupStation(mWifiStationTimeout);
#endif
if(!settingValid) {
DPRINTLN(DBG_WARN, F("your settings are not valid! check [IP]/setup"));
mApActive = true;
@ -58,7 +55,7 @@ void ahoywifi::setup(uint32_t timeout, bool settingValid) {
DPRINTLN(DBG_INFO, F("Welcome to AHOY!"));
DPRINT(DBG_INFO, F("\npoint your browser to http://"));
if(mApActive)
DBGPRINTLN(F("192.168.1.1"));
DBGPRINTLN(F("192.168.4.1"));
else
DBGPRINTLN(WiFi.localIP().toString());
DPRINTLN(DBG_INFO, F("to configure your device"));
@ -142,20 +139,21 @@ bool ahoywifi::setupStation(uint32_t timeout) {
}
WiFi.mode(WIFI_STA);
if(mConfig->staticIp.ip[0] != 0) {
IPAddress ip(mConfig->staticIp.ip);
IPAddress mask(mConfig->staticIp.mask);
IPAddress dns(mConfig->staticIp.dns);
IPAddress gateway(mConfig->staticIp.gateway);
if(!WiFi.config(ip, gateway, mask, dns))
if(mConfig->sys.ip.ip[0] != 0) {
IPAddress ip(mConfig->sys.ip.ip);
IPAddress mask(mConfig->sys.ip.mask);
IPAddress dns1(mConfig->sys.ip.dns1);
IPAddress dns2(mConfig->sys.ip.dns2);
IPAddress gateway(mConfig->sys.ip.gateway);
if(!WiFi.config(ip, gateway, mask, dns1, dns2))
DPRINTLN(DBG_ERROR, F("failed to set static IP!"));
}
WiFi.begin(mSysCfg->stationSsid, mSysCfg->stationPwd);
if(String(mSysCfg->deviceName) != "")
WiFi.hostname(mSysCfg->deviceName);
WiFi.begin(mConfig->sys.stationSsid, mConfig->sys.stationPwd);
if(String(mConfig->sys.deviceName) != "")
WiFi.hostname(mConfig->sys.deviceName);
delay(2000);
DPRINTLN(DBG_INFO, F("connect to network '") + String(mSysCfg->stationSsid) + F("' ..."));
DPRINTLN(DBG_INFO, F("connect to network '") + String(mConfig->sys.stationSsid) + F("' ..."));
while (WiFi.status() != WL_CONNECTED) {
delay(100);
if(cnt % 40 == 0)
@ -197,8 +195,8 @@ time_t ahoywifi::getNtpTime(void) {
uint8_t buf[NTP_PACKET_SIZE];
uint8_t retry = 0;
WiFi.hostByName(mConfig->ntpAddr, timeServer);
mUdp->begin(mConfig->ntpPort);
WiFi.hostByName(mConfig->ntp.addr, timeServer);
mUdp->begin(mConfig->ntp.port);
sendNTPpacket(timeServer);

14
src/wifi/ahoywifi.h

@ -7,21 +7,19 @@
#define __AHOYWIFI_H__
#include "../utils/dbg.h"
// NTP
#include <Arduino.h>
#include <WiFiUdp.h>
#include <TimeLib.h>
#include <DNSServer.h>
#include "ESPAsyncWebServer.h"
#include "../defines.h"
#include "../app.h"
#include "../config/settings.h"
class app;
class ahoywifi {
public:
ahoywifi(app *main, sysConfig_t *sysCfg, config_t *config);
ahoywifi(settings_t *config);
~ahoywifi() {}
void setup(uint32_t timeout, bool settingValid);
@ -36,9 +34,7 @@ class ahoywifi {
private:
void sendNTPpacket(IPAddress& address);
config_t *mConfig;
sysConfig_t *mSysCfg;
app *mMain;
settings_t *mConfig;
DNSServer *mDns;
WiFiUDP *mUdp; // for time server

Loading…
Cancel
Save