Browse Source

Merge branch 'development03' into zero-export

pull/1701/head
Patrick Amrhein 7 months ago
parent
commit
2cad55afbe
  1. 17
      .github/workflows/compile_zero-export.yml
  2. 13
      src/CHANGES.md
  3. 5
      src/config/config.h
  4. 3
      src/config/config_override_example.h
  5. 2
      src/defines.h
  6. 2
      src/network/AhoyWifiEsp32.h
  7. 2
      src/plugins/MaxPower.h
  8. 80
      src/publisher/pubMqtt.h
  9. 13
      src/publisher/pubMqttIvData.h
  10. 4
      src/web/RestApi.h
  11. 18
      src/web/html/index.html
  12. 33
      src/web/html/setup.html

17
.github/workflows/compile_zero-export.yml

@ -217,10 +217,25 @@ jobs:
rm -f \ rm -f \
${{ steps.version_name.outputs.name }}/*/*.elf.7z ${{ steps.version_name.outputs.name }}/*/*.elf.7z
# - name: Deploy
# uses: nogsantos/scp-deploy@master
# with:
# src: ${{ steps.version_name.outputs.name }}/
# host: ${{ secrets.FW_SSH_HOST }}
# remote: ${{ secrets.FW_SSH_DIR }}/dev
# port: ${{ secrets.FW_SSH_PORT }}
# user: ${{ secrets.FW_SSH_USER }}
# key: ${{ secrets.FW_SSH_KEY }}
- name: Clean elf files (7z compressed) for Artifact
run: |
rm -f \
${{ steps.version_name.outputs.name }}/*/*.elf.7z
- name: Create Artifact - name: Create Artifact
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: zero-${{ steps.version_name.outputs.name }} name: dev-${{ steps.version_name.outputs.name }}
path: | path: |
${{ steps.version_name.outputs.name }}/* ${{ steps.version_name.outputs.name }}/*
manual/User_Manual.md manual/User_Manual.md

13
src/CHANGES.md

@ -1,10 +1,19 @@
# Development Changes # Development Changes
## 0.8.125 - 2024-06-09
* fix ESP8266 compilation
* merge PR: active_PowerLimit #1663
## 0.8.124 - 2024-06-06
* improved MqTT `OnMessage` (threadsafe)
* support of HERF inverters, serial number is converted in Javascript #1425
* revert buffer size in `RestAPI` for ESP8266 #1650
## 0.8.123 - 2024-05-30 ## 0.8.123 - 2024-05-30
* fix ESP8266, ESP32 static IP #1643 #1608 * fix ESP8266, ESP32 static IP #1643 #1608
* update MqTT library which enhances stability #1646 * update MqTT library which enhances stability #1646
* merge PR: MQTT JSON Payload pro Kanal und total, auswählbar #1541 * merge PR: MqTT JSON Payload pro Kanal und total, auswählbar #1541
* add option to publish mqtt as json * add option to publish MqTT as json
* publish rssi not on ch0 any more, published on `topic/rssi` * publish rssi not on ch0 any more, published on `topic/rssi`
* add total power to index page (if multiple inverters are configured) * add total power to index page (if multiple inverters are configured)
* show device name in html title #1639 * show device name in html title #1639

5
src/config/config.h

@ -16,9 +16,8 @@
//------------------------------------- //-------------------------------------
// Fallback WiFi Info // Fallback WiFi Info
#define FB_WIFI_SSID "YOUR_WIFI_SSID" #define FB_WIFI_SSID ""
#define FB_WIFI_PWD "YOUR_WIFI_PWD" #define FB_WIFI_PWD ""
// Access Point Info // Access Point Info
// In case there is no WiFi Network or Ahoy can not connect to it, it will act as an Access Point // In case there is no WiFi Network or Ahoy can not connect to it, it will act as an Access Point

3
src/config/config_override_example.h

@ -6,9 +6,6 @@
#ifndef __CONFIG_OVERRIDE_H__ #ifndef __CONFIG_OVERRIDE_H__
#define __CONFIG_OVERRIDE_H__ #define __CONFIG_OVERRIDE_H__
// override fallback WiFi info
#define FB_WIFI_OVERRIDDEN
// each override must be preceded with an #undef statement // each override must be preceded with an #undef statement
#undef FB_WIFI_SSID #undef FB_WIFI_SSID
#define FB_WIFI_SSID "MY_SSID" #define FB_WIFI_SSID "MY_SSID"

2
src/defines.h

@ -13,7 +13,7 @@
//------------------------------------- //-------------------------------------
#define VERSION_MAJOR 0 #define VERSION_MAJOR 0
#define VERSION_MINOR 8 #define VERSION_MINOR 8
#define VERSION_PATCH 1230003 #define VERSION_PATCH 125
//------------------------------------- //-------------------------------------
typedef struct { typedef struct {
uint8_t ch; uint8_t ch;

2
src/network/AhoyWifiEsp32.h

@ -17,7 +17,7 @@ class AhoyWifi : public AhoyNetwork {
void begin() override { void begin() override {
mAp.enable(); mAp.enable();
if(String(FB_WIFI_SSID) == mConfig->sys.stationSsid) if(strlen(mConfig->sys.stationSsid) == 0)
return; // no station wifi defined return; // no station wifi defined
WiFi.disconnect(); // clean up WiFi.disconnect(); // clean up

2
src/plugins/MaxPower.h

@ -50,7 +50,7 @@ class MaxPower {
if((mValues[i].first + mMaxDiff) >= *mTs) if((mValues[i].first + mMaxDiff) >= *mTs)
val += mValues[i].second; val += mValues[i].second;
else if(mValues[i].first > 0) else if(mValues[i].first > 0)
return mLast; // old data break; // old data
} }
if(val > mLast) if(val > mLast)
mLast = val; mLast = val;

80
src/publisher/pubMqtt.h

@ -11,6 +11,8 @@
#if defined(ENABLE_MQTT) #if defined(ENABLE_MQTT)
#ifdef ESP8266 #ifdef ESP8266
#include <ESP8266WiFi.h> #include <ESP8266WiFi.h>
#define xSemaphoreTake(a, b) { while(a) { yield(); } a = true; }
#define xSemaphoreGive(a) { a = false; }
#elif defined(ESP32) #elif defined(ESP32)
#include <WiFi.h> #include <WiFi.h>
#endif #endif
@ -40,6 +42,13 @@ template<class HMSYSTEM>
class PubMqtt { class PubMqtt {
public: public:
PubMqtt() : SendIvData() { PubMqtt() : SendIvData() {
#if defined(ESP32)
mutex = xSemaphoreCreateBinaryStatic(&mutexBuffer);
xSemaphoreGive(mutex);
#else
mutex = false;
#endif
mLastIvState.fill(InverterStatus::OFF); mLastIvState.fill(InverterStatus::OFF);
mIvLastRTRpub.fill(0); mIvLastRTRpub.fill(0);
@ -51,7 +60,11 @@ class PubMqtt {
mSendAlarm.fill(false); mSendAlarm.fill(false);
} }
~PubMqtt() { } ~PubMqtt() {
#if defined(ESP32)
vSemaphoreDelete(mutex);
#endif
}
void setup(IApp *app, cfgMqtt_t *cfg_mqtt, const char *devName, const char *version, HMSYSTEM *sys, uint32_t *utcTs, uint32_t *uptime) { void setup(IApp *app, cfgMqtt_t *cfg_mqtt, const char *devName, const char *version, HMSYSTEM *sys, uint32_t *utcTs, uint32_t *uptime) {
mApp = app; mApp = app;
@ -97,6 +110,17 @@ class PubMqtt {
} }
void loop() { void loop() {
std::queue<message_s> queue;
xSemaphoreTake(mutex, portMAX_DELAY);
std::swap(queue, mReceiveQueue);
xSemaphoreGive(mutex);
while (!queue.empty()) {
message_s *entry = &queue.front();
handleMessage(entry->topic, entry->payload, entry->len, entry->index, entry->total);
queue.pop();
}
SendIvData.loop(); SendIvData.loop();
#if defined(ESP8266) #if defined(ESP8266)
@ -272,14 +296,14 @@ class PubMqtt {
tickerMinute(); tickerMinute();
publish(mLwtTopic.data(), mqttStr[MQTT_STR_LWT_CONN], true, false); publish(mLwtTopic.data(), mqttStr[MQTT_STR_LWT_CONN], true, false);
// for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i++) { for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i++) {
// snprintf(mVal.data(), mVal.size(), "ctrl/limit/%d", i); snprintf(mVal.data(), mVal.size(), "ctrl/limit/%d", i);
// subscribe(mVal.data(), QOS_2); subscribe(mVal.data(), QOS_2);
// snprintf(mVal.data(), mVal.size(), "ctrl/restart/%d", i); snprintf(mVal.data(), mVal.size(), "ctrl/restart/%d", i);
// subscribe(mVal.data()); subscribe(mVal.data());
// snprintf(mVal.data(), mVal.size(), "ctrl/power/%d", i); snprintf(mVal.data(), mVal.size(), "ctrl/power/%d", i);
// subscribe(mVal.data()); subscribe(mVal.data());
// } }
snprintf(mVal.data(), mVal.size(), "ctrl/#"); snprintf(mVal.data(), mVal.size(), "ctrl/#");
subscribe(mVal.data(), QOS_2); subscribe(mVal.data(), QOS_2);
subscribe(subscr[MQTT_SUBS_SET_TIME]); subscribe(subscr[MQTT_SUBS_SET_TIME]);
@ -318,6 +342,14 @@ class PubMqtt {
void onMessage(const espMqttClientTypes::MessageProperties& properties, const char* topic, const uint8_t* payload, size_t len, size_t index, size_t total) { void onMessage(const espMqttClientTypes::MessageProperties& properties, const char* topic, const uint8_t* payload, size_t len, size_t index, size_t total) {
if(len == 0) if(len == 0)
return; return;
xSemaphoreTake(mutex, portMAX_DELAY);
mReceiveQueue.push(message_s(topic, payload, len, index, total));
xSemaphoreGive(mutex);
}
inline void handleMessage(const char* topic, const uint8_t* payload, size_t len, size_t index, size_t total) {
DPRINT(DBG_INFO, mqttStr[MQTT_STR_GOT_TOPIC]); DPRINT(DBG_INFO, mqttStr[MQTT_STR_GOT_TOPIC]);
DBGPRINTLN(String(topic)); DBGPRINTLN(String(topic));
if(NULL == mSubscriptionCb) if(NULL == mSubscriptionCb)
@ -631,12 +663,40 @@ class PubMqtt {
private: private:
enum {MQTT_STATUS_OFFLINE = 0, MQTT_STATUS_PARTIAL, MQTT_STATUS_ONLINE}; enum {MQTT_STATUS_OFFLINE = 0, MQTT_STATUS_PARTIAL, MQTT_STATUS_ONLINE};
struct message_s {
char* topic;
uint8_t* payload;
size_t len;
size_t index;
size_t total;
message_s(const char* topic, const uint8_t* payload, size_t len, size_t index, size_t total) {
this->topic = new char[strlen(topic) + 1];
this->payload = new uint8_t[len];
memcpy(this->topic, topic, strlen(topic));
memcpy(this->payload, payload, len);
this->len = len;
this->index = index;
this->total = total;
}
~message_s() {
delete[] this->topic;
delete[] this->payload;
}
};
private: private:
espMqttClient mClient; espMqttClient mClient;
cfgMqtt_t *mCfgMqtt = nullptr; cfgMqtt_t *mCfgMqtt = nullptr;
IApp *mApp; IApp *mApp;
#if defined(ESP8266) #if defined(ESP8266)
WiFiEventHandler mHWifiCon, mHWifiDiscon; WiFiEventHandler mHWifiCon, mHWifiDiscon;
volatile bool mutex;
#else
SemaphoreHandle_t mutex;
StaticSemaphore_t mutexBuffer;
#endif #endif
HMSYSTEM *mSys = nullptr; HMSYSTEM *mSys = nullptr;
@ -653,6 +713,8 @@ class PubMqtt {
std::array<uint32_t, MAX_NUM_INVERTERS> mIvLastRTRpub; std::array<uint32_t, MAX_NUM_INVERTERS> mIvLastRTRpub;
uint16_t mIntervalTimeout = 0; uint16_t mIntervalTimeout = 0;
std::queue<message_s> mReceiveQueue;
// last will topic and payload must be available through lifetime of 'espMqttClient' // last will topic and payload must be available through lifetime of 'espMqttClient'
std::array<char, (MQTT_TOPIC_LEN + 5)> mLwtTopic; std::array<char, (MQTT_TOPIC_LEN + 5)> mLwtTopic;
const char *mDevName = nullptr, *mVersion = nullptr; const char *mDevName = nullptr, *mVersion = nullptr;

13
src/publisher/pubMqttIvData.h

@ -197,13 +197,20 @@ class PubMqttIvData {
if (!mCfg->json) { if (!mCfg->json) {
snprintf(mSubTopic.data(), mSubTopic.size(), "%s/ch%d/%s", mIv->config->name, rec->assign[mPos].ch, fields[rec->assign[mPos].fieldId]); snprintf(mSubTopic.data(), mSubTopic.size(), "%s/ch%d/%s", mIv->config->name, rec->assign[mPos].ch, fields[rec->assign[mPos].fieldId]);
snprintf(mVal.data(), mVal.size(), "%g", ah::round3(mIv->getValue(mPos, rec))); snprintf(mVal.data(), mVal.size(), "%g", ah::round3(mIv->getValue(mPos, rec)));
} else {
if (FLD_ACT_ACTIVE_PWR_LIMIT == rec->assign[mPos].fieldId) {
uint8_t qos = (FLD_ACT_ACTIVE_PWR_LIMIT == rec->assign[mPos].fieldId) ? QOS_2 : QOS_0;
snprintf(mSubTopic.data(), mSubTopic.size(), "%s/%s", mIv->config->name, fields[rec->assign[mPos].fieldId]);
snprintf(mVal.data(), mVal.size(), "%g", ah::round3(mIv->getValue(mPos, rec)));
mPublish(mSubTopic.data(), mVal.data(), retained, qos);
}
} }
} }
if ((InverterDevInform_All == mCmd) || (InverterDevInform_Simple == mCmd) || !mCfg->json) { if ((InverterDevInform_All == mCmd) || (InverterDevInform_Simple == mCmd) || !mCfg->json)
{
uint8_t qos = (FLD_ACT_ACTIVE_PWR_LIMIT == rec->assign[mPos].fieldId) ? QOS_2 : QOS_0; uint8_t qos = (FLD_ACT_ACTIVE_PWR_LIMIT == rec->assign[mPos].fieldId) ? QOS_2 : QOS_0;
if((FLD_EVT != rec->assign[mPos].fieldId) if((FLD_EVT != rec->assign[mPos].fieldId) && (FLD_LAST_ALARM_CODE != rec->assign[mPos].fieldId))
&& (FLD_LAST_ALARM_CODE != rec->assign[mPos].fieldId))
mPublish(mSubTopic.data(), mVal.data(), retained, qos); mPublish(mSubTopic.data(), mVal.data(), retained, qos);
} }
} }

4
src/web/RestApi.h

@ -84,7 +84,11 @@ class RestApi {
mHeapFrag = ESP.getHeapFragmentation(); mHeapFrag = ESP.getHeapFragmentation();
#endif #endif
#if defined(ESP32)
AsyncJsonResponse* response = new AsyncJsonResponse(false, 10000); AsyncJsonResponse* response = new AsyncJsonResponse(false, 10000);
#else
AsyncJsonResponse* response = new AsyncJsonResponse(false, 6000);
#endif
JsonObject root = response->getRoot(); JsonObject root = response->getRoot();
String path = request->url().substring(5); String path = request->url().substring(5);

18
src/web/html/index.html

@ -116,6 +116,8 @@
var p = div(["none"]); var p = div(["none"]);
var total = 0; var total = 0;
var count = 0; var count = 0;
var mobile = window.screen.width < 470;
for(var i of obj) { for(var i of obj) {
var icon = iconSuccess; var icon = iconSuccess;
var cl = "icon-success"; var cl = "icon-success";
@ -131,7 +133,8 @@
} else if(0 == i["ts_last_success"]) { } else if(0 == i["ts_last_success"]) {
avail = "{#AVAIL_NO_DATA}"; avail = "{#AVAIL_NO_DATA}";
} else { } else {
avail = "{#AVAIL} "; if (!mobile)
avail = "{#AVAIL} ";
if(false == i["is_producing"]) if(false == i["is_producing"])
avail += "{#NOT_PRODUCING}"; avail += "{#NOT_PRODUCING}";
else { else {
@ -142,11 +145,16 @@
} }
} }
var text;
if (mobile)
text = "#";
else
text = "{#INVERTER} #";
p.append( p.append(
svg(icon, 30, 30, "icon " + cl), svg(icon, 30, 30, "icon " + cl),
span("{#INVERTER} #" + i["id"] + ": " + i["name"] + " {#IS} " + avail), span(text + i["id"] + ": " + i["name"] + " {#IS} " + avail),
br() br()
); );
if(false == i["is_avail"]) { if(false == i["is_avail"]) {
if(i["ts_last_success"] > 0) { if(i["ts_last_success"] > 0) {

33
src/web/html/setup.html

@ -903,11 +903,16 @@
ser.dispatchEvent(new Event('change')); ser.dispatchEvent(new Event('change'));
function ivSave() { function ivSave() {
var o = new Object(); var o = {}
o.cmd = "save_iv" o.cmd = "save_iv"
o.token = "*" o.token = "*"
o.id = obj.id o.id = obj.id
o.ser = parseInt(document.getElementsByName("ser")[0].value, 16);
let sn = document.getElementsByName("ser")[0].value
if(sn[0] == 'A')
sn = convHerf(sn)
o.ser = parseInt(sn, 16)
o.name = document.getElementsByName("name")[0].value; o.name = document.getElementsByName("name")[0].value;
o.en = document.getElementsByName("enable")[0].checked; o.en = document.getElementsByName("enable")[0].checked;
o.ch = []; o.ch = [];
@ -927,6 +932,30 @@
getAjax("/api/setup", cb, "POST", JSON.stringify(o)); getAjax("/api/setup", cb, "POST", JSON.stringify(o));
} }
function convHerf(sn) {
let sn_int = 0n;
const CHARS = "0123456789ABCDEFGHJKLMNPRSTUVWXY";
for (let i = 0; i < 9; ++i) {
const pos = CHARS.indexOf(sn[i])
const shift = 42 - 5 * i - (i <= 2 ? 0 : 2)
sn_int |= BigInt(pos) << BigInt(shift)
}
let first4Hex = (sn_int >> 32n) & 0xFFFFn
if (first4Hex === 0x2841n)
first4Hex = 0x1121n
else if (first4Hex === 0x2821n)
first4Hex = 0x1141n
else if (first4Hex === 0x2801n)
first4Hex = 0x1161n
sn_int = (sn_int & ~(0xFFFFn << 32n)) | (first4Hex << 32n);
return sn_int.toString(16)
}
function cb(obj2) { function cb(obj2) {
var e = document.getElementById("res"); var e = document.getElementById("res");
if(!obj2.success) if(!obj2.success)

Loading…
Cancel
Save