Browse Source

Merge branch 'development03' into Ethernet+Wifi

main
lumapu 4 months ago
parent
commit
3228f2cfc6
  1. 33
      .github/workflows/compile_development.yml
  2. 29
      manual/factory_firmware.md
  3. BIN
      scripts/__pycache__/htmlPreprocessorDefines.cpython-311.pyc
  4. 38
      scripts/add_littlefs_binary.py
  5. 3
      scripts/getVersion.py
  6. 23
      src/CHANGES.md
  7. 4
      src/config/settings.h
  8. 2
      src/defines.h
  9. 13
      src/hm/hmDefines.h
  10. 4
      src/hm/hmInverter.h
  11. 11
      src/hm/hmSystem.h
  12. 61
      src/hms/hmsDefines.h
  13. 2
      src/network/AhoyWifiEsp32.h
  14. 2
      src/network/AhoyWifiEsp8266.h
  15. 6
      src/platformio.ini
  16. 5
      src/publisher/pubMqtt.h
  17. 69
      src/publisher/pubMqttIvData.h
  18. 43
      src/web/RestApi.h
  19. 8
      src/web/html/api.js
  20. 21
      src/web/html/history.html
  21. 20
      src/web/html/index.html
  22. 7
      src/web/html/serial.html
  23. 12
      src/web/html/setup.html
  24. 7
      src/web/html/system.html
  25. 1
      src/web/html/update.html
  26. 7
      src/web/html/visualization.html
  27. 6
      src/web/lang.h
  28. 5
      src/web/lang.json
  29. 1
      src/web/web.h
  30. 466
      tools/NodeRED/flows-mqtt-json-example.json

33
.github/workflows/compile_development.yml

@ -70,6 +70,11 @@ jobs:
- name: Run PlatformIO
run: pio run -d src -e ${{ matrix.variant }}
- name: Compress .elf
uses: edgarrc/action-7z@v1
with:
args: 7z a -t7z -mx=9 src/.pio/build/${{ matrix.variant }}/firmware.elf.7z ./src/.pio/build/${{ matrix.variant }}/firmware.elf
- name: Rename Firmware
run: python scripts/getVersion.py ${{ matrix.variant }} >> $GITHUB_OUTPUT
@ -132,6 +137,11 @@ jobs:
- name: Run PlatformIO
run: pio run -d src -e ${{ matrix.variant }}
- name: Compress .elf
uses: edgarrc/action-7z@v1
with:
args: 7z a -t7z -mx=9 src/.pio/build/${{ matrix.variant }}/firmware.elf.7z ./src/.pio/build/${{ matrix.variant }}/firmware.elf
- name: Rename Firmware
run: python scripts/getVersion.py ${{ matrix.variant }} >> $GITHUB_OUTPUT
@ -188,15 +198,6 @@ jobs:
with:
name: dev-*
- name: Create Artifact
uses: actions/upload-artifact@v4
with:
name: dev-${{ steps.version_name.outputs.name }}
path: |
${{ steps.version_name.outputs.name }}/*
manual/User_Manual.md
manual/Getting_Started.md
- name: Deploy
uses: nogsantos/scp-deploy@master
with:
@ -206,3 +207,17 @@ jobs:
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
uses: actions/upload-artifact@v4
with:
name: dev-${{ steps.version_name.outputs.name }}
path: |
${{ steps.version_name.outputs.name }}/*
manual/User_Manual.md
manual/Getting_Started.md

29
manual/factory_firmware.md

@ -10,7 +10,7 @@ First install on the requested platform the standard firmware and configure ever
First create a directory `data` inside the following project path: `src/`.
As the export removes all your password you need to add them again to the `json` file. Open the `json` file with a text editor and search for all the `"pwd": ""`. Between the second bunch of quotation marks you have to place the password.
As the export removes all your passwords you need to add them again to the `json` file. Open the `json` file with a text editor and search for all the `"pwd":""` sections. Between the second bunch of quotation marks you have to place the password.
*Note: It's recommended to keep all information in one line to save space on the ESP littlefs partition*
@ -26,27 +26,13 @@ ahoy
...
```
## modify platform.ini to build factory binary
Open the file `src/platformio.ini` and uncomment the following line `#post:../scripts/add_littlefs_binary.py` (remove the `#`)
## build firmware
Choose your prefered environment and build firmware as usual. Once the process is finished you should find along with the standard `firmware.bin` an additional file called `firmware.factory.bin`. Both files are located here: `src/.pio/build/[ENVIRONMENT]/`
## Upload to device
Navigate to the firmware output directory `src/.pio/build/[ENVIRONMENT]/` and open a terminal.
### ESP32
Python:
`esptool.py -b 921600 write_flash --flash_mode dio --flash_size detect 0x1000 bootloader.bin 0x8000 partitions.bin 0x10000 firmware.factory.bin`
Windows:
`esptool.exe -b 921600 write_flash --flash_mode dio --flash_size detect 0x1000 bootloader.bin 0x8000 partitions.bin 0x10000 firmware.factory.bin`
### ESP32-S3 (OpenDTU Fusion Board)
Navigate to the firmware output directory `src/.pio/build/[ENVIRONMENT]/` and open a terminal or vice versa.
Python:
`esptool.py -b 921600 write_flash --flash_mode dio --flash_size detect 0x0 firmware.factory.bin`
@ -54,7 +40,7 @@ Python:
Windows:
`esptool.exe -b 921600 write_flash --flash_mode dio --flash_size detect 0x0 firmware.factory.bin`
For a 4MB flash size the upload should be finished within 22 seconds.
The upload should be finished within one minute.
## Testing
@ -64,8 +50,7 @@ Reboot your ESP an check if all your settings are present.
From time to time a new version of AhoyDTU will be published. To get the changes into your already prepared factory binary generation environment you have to do only a few steps:
1. revert the changes of `platformio.ini` by executing from repository root: `git checkout src/platformio.ini`
2. pull new changes from remote: `git pull`
3. modify the `platformio.ini` again as you can read above (remove comment)
4. build and upload
5. enjoy
1. pull new changes from remote: `git pull`
2. check if the `data` folder is still there and contains the `settings.json`
3. build and upload
4. enjoy

BIN
scripts/__pycache__/htmlPreprocessorDefines.cpython-311.pyc

Binary file not shown.

38
scripts/add_littlefs_binary.py

@ -1,10 +1,14 @@
import os
import subprocess
import shutil
from SCons.Script import DefaultEnvironment
Import("env")
def build_littlefs():
if os.path.isfile('data/settings.json') == False:
return # nothing to do
result = subprocess.run(["pio", "run", "--target", "buildfs", "--environment", env['PIOENV']])
if result.returncode != 0:
print("Error building LittleFS:")
@ -13,7 +17,17 @@ def build_littlefs():
print("LittleFS build successful")
def merge_bins():
flash_size = int(env.get("BOARD_FLASH_SIZE", "4MB").replace("MB", ""))
if os.path.isfile('data/settings.json') == False:
return # nothing to do
BOOTLOADER_OFFSET = 0x0000
PARTITIONS_OFFSET = 0x8000
FIRMWARE_OFFSET = 0x10000
if env['PIOENV'][:13] == "esp32-wroom32":
BOOTLOADER_OFFSET = 0x1000
flash_size = int(env.BoardConfig().get("upload.maximum_size", "1310720")) # 0x140000
app0_offset = 0x10000
if env['PIOENV'][:7] == "esp8266":
app0_offset = 0
@ -21,15 +35,21 @@ def merge_bins():
app0_offset = 0
littlefs_offset = 0x290000
if flash_size == 8:
if flash_size == 0x330000:
littlefs_offset = 0x670000
elif flash_size == 16:
elif flash_size == 0x640000:
littlefs_offset = 0xc90000
# save current wd
start = os.getcwd()
os.chdir('.pio/build/' + env['PIOENV'] + '/')
with open("bootloader.bin", "rb") as bootloader_file:
bootloader_data = bootloader_file.read()
with open("partitions.bin", "rb") as partitions_file:
partitions_data = partitions_file.read()
with open("firmware.bin", "rb") as firmware_file:
firmware_data = firmware_file.read()
@ -37,11 +57,16 @@ def merge_bins():
littlefs_data = littlefs_file.read()
with open("firmware.factory.bin", "wb") as merged_file:
# fill gap with 0xff
merged_file.write(b'\xFF' * BOOTLOADER_OFFSET)
merged_file.write(bootloader_data)
merged_file.write(b'\xFF' * (PARTITIONS_OFFSET - (BOOTLOADER_OFFSET + len(bootloader_data))))
merged_file.write(partitions_data)
merged_file.write(b'\xFF' * (FIRMWARE_OFFSET - (PARTITIONS_OFFSET + len(partitions_data))))
merged_file.write(firmware_data)
if len(firmware_data) < (littlefs_offset - app0_offset):
merged_file.write(b'\xFF' * ((littlefs_offset - app0_offset) - len(firmware_data)))
merged_file.write(b'\xFF' * (littlefs_offset - (FIRMWARE_OFFSET + len(firmware_data))))
merged_file.write(littlefs_data)
os.chdir(start)
@ -50,6 +75,5 @@ def main(target, source, env):
build_littlefs()
merge_bins()
# ensure that script is called once firmeware was compiled
env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", main)

3
scripts/getVersion.py

@ -76,8 +76,9 @@ def renameFw(path_define, env):
fname = version[:-1] + "_" + sha + "_" + env + ".bin"
os.rename("src/.pio/build/" + env + "/firmware.bin", dst + fname)
os.rename("src/.pio/build/" + env + "/firmware.elf.7z", dst + fname[:-3] + "elf.7z")
if env[:5] == "esp32":
if env[:5] == "esp32" or env[:4] == "open":
os.rename("src/.pio/build/" + env + "/bootloader.bin", dst + "bootloader.bin")
os.rename("src/.pio/build/" + env + "/partitions.bin", dst + "partitions.bin")
genOtaBin(dst)

23
src/CHANGES.md

@ -1,5 +1,28 @@
# Development Changes
## 0.8.123 - 2024-05-30
* fix ESP8266, ESP32 static IP #1643 #1608
* update MqTT library which enhances stability #1646
* merge PR: MQTT JSON Payload pro Kanal und total, auswählbar #1541
* add option to publish mqtt as json
* publish rssi not on ch0 any more, published on `topic/rssi`
* add total power to index page (if multiple inverters are configured)
* show device name in html title #1639
* update AsyncWebserver library to `3.2.2`
* add environment name to filename of coredump
## 0.8.122 - 2024-05-23
* add button for donwloading coredump (ESP32 variants only)
## 0.8.121 - 2024-05-20
* fix ESP32 factory image generation
* fix plot of history graph #1635
## 0.8.120 - 2024-05-18
* fix crash if invalid serial number was set -> inverter will be disabled automatically
* improved and fixed factory image generation
* fix HMT-1800-4T number of inputs #1628
## 0.8.119 - 2024-05-17
* fix reset values at midnight if WiFi isn't available #1620
* fix typo in English versions

4
src/config/settings.h

@ -163,6 +163,7 @@ typedef struct {
char user[MQTT_USER_LEN];
char pwd[MQTT_PWD_LEN];
char topic[MQTT_TOPIC_LEN];
bool json;
uint16_t interval;
bool enableRetain;
} cfgMqtt_t;
@ -484,6 +485,7 @@ class settings {
snprintf(mCfg.mqtt.pwd, MQTT_PWD_LEN, "%s", DEF_MQTT_PWD);
snprintf(mCfg.mqtt.topic, MQTT_TOPIC_LEN, "%s", DEF_MQTT_TOPIC);
mCfg.mqtt.interval = 0; // off
mCfg.mqtt.json = false; // off
mCfg.mqtt.enableRetain = true;
mCfg.inst.sendInterval = SEND_INTERVAL;
@ -741,12 +743,14 @@ class settings {
obj[F("user")] = mCfg.mqtt.user;
obj[F("pwd")] = mCfg.mqtt.pwd;
obj[F("topic")] = mCfg.mqtt.topic;
obj[F("json")] = mCfg.mqtt.json;
obj[F("intvl")] = mCfg.mqtt.interval;
obj[F("retain")] = mCfg.mqtt.enableRetain;
} else {
getVal<uint16_t>(obj, F("port"), &mCfg.mqtt.port);
getVal<uint16_t>(obj, F("intvl"), &mCfg.mqtt.interval);
getVal<bool>(obj, F("json"), &mCfg.mqtt.json);
getChar(obj, F("broker"), mCfg.mqtt.broker, MQTT_ADDR_LEN);
getChar(obj, F("user"), mCfg.mqtt.user, MQTT_USER_LEN);
getChar(obj, F("clientId"), mCfg.mqtt.clientId, MQTT_CLIENTID_LEN);

2
src/defines.h

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

13
src/hm/hmDefines.h

@ -62,8 +62,8 @@ enum {FLD_UDC = 0, FLD_IDC, FLD_PDC, FLD_YD, FLD_YW, FLD_YT,
FLD_GRID_PROFILE_VERSION, /*FLD_ACT_REACTIVE_PWR_LIMIT, FLD_ACT_PF,*/ FLD_LAST_ALARM_CODE, FLD_MP, FLD_MT};
const char* const fields[] = {"U_DC", "I_DC", "P_DC", "YieldDay", "YieldWeek", "YieldTotal",
"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",
"U_AC", "U_AC_1N", "U_AC_2N", "U_AC_3N", "U_AC_12", "U_AC_23", "U_AC_31", "I_AC",
"I_AC_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","BootloaderVersion",
"active_PowerLimit", "HWPartNumber", "HWVersion", "GridProfileCode",
"GridProfileVersion", /*"reactivePowerLimit","Powerfactor",*/ "LastAlarmCode", "MaxPower", "MaxTemp"};
@ -72,7 +72,7 @@ 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_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, UNIT_NONE, UNIT_NONE, UNIT_NONE, UNIT_NONE, UNIT_W};
UNIT_NONE, UNIT_NONE, UNIT_NONE, UNIT_NONE, UNIT_NONE, UNIT_NONE, UNIT_PCT, UNIT_NONE, UNIT_NONE, UNIT_NONE, UNIT_NONE, UNIT_NONE, UNIT_W, UNIT_C};
// mqtt discovery device classes
enum {DEVICE_CLS_NONE = 0, DEVICE_CLS_CURRENT, DEVICE_CLS_ENERGY, DEVICE_CLS_PWR, DEVICE_CLS_VOLTAGE, DEVICE_CLS_FREQ, DEVICE_CLS_TEMP};
@ -389,8 +389,11 @@ const devInfo_t devInfo[] = {
{ 0x102271, 2000 }, // v2 black backplane, 16A
// HMT
{ 0x103311, 1800 },
{ 0x103331, 2250 }
{ 0x103241, 1600 }, // -4T
{ 0x103251, 1800 }, // -4T
{ 0x103271, 2000 }, // -4T
{ 0x103311, 1800 }, // -6T
{ 0x103331, 2250 } // -6T
};
#define MI_REQ_CH1 0x09

4
src/hm/hmInverter.h

@ -538,6 +538,10 @@ class Inverter {
rec->length = (uint8_t)(HMS4CH_LIST_LEN);
rec->assign = reinterpret_cast<byteAssign_t*>(const_cast<byteAssign_t*>(hms4chAssignment));
rec->pyldLen = HMS4CH_PAYLOAD_LEN;
} else if(IV_HMT == ivGen){
rec->length = (uint8_t)(HMT4CH_LIST_LEN);
rec->assign = reinterpret_cast<byteAssign_t*>(const_cast<byteAssign_t*>(hmt4chAssignment));
rec->pyldLen = HMT4CH_PAYLOAD_LEN;
}
channels = 4;
}

11
src/hm/hmSystem.h

@ -69,11 +69,16 @@ class HmSystem {
iv->ivRadioType = INV_RADIO_TYPE_NRF;
}
} else if(iv->config->serial.b[5] == 0x13) {
iv->ivGen = IV_HMT;
iv->ivGen = IV_HMT;
if(iv->config->serial.b[4] == 0x61)
iv->type = INV_TYPE_4CH;
else
iv->type = INV_TYPE_6CH;
iv->ivRadioType = INV_RADIO_TYPE_CMT;
iv->ivRadioType = INV_RADIO_TYPE_CMT;
} else if(iv->config->serial.u64 != 0ULL) {
DPRINTLN(DBG_ERROR, F("inverter type can't be detected!"));
iv->config->enabled = false;
return;
} else
iv->ivGen = IV_UNKNOWN;
@ -116,6 +121,8 @@ class HmSystem {
DPRINTLN(DBG_VERBOSE, F("hmSystem.h:getInverterByPos"));
if(pos >= MAX_INVERTER)
return nullptr;
else if(nullptr == mInverter[pos].config)
return nullptr;
else if((mInverter[pos].config->serial.u64 != 0ULL) || (false == check))
return &mInverter[pos];
else

61
src/hms/hmsDefines.h

@ -131,6 +131,67 @@ const byteAssign_t hms4chAssignment[] = {
#define HMS4CH_LIST_LEN (sizeof(hms4chAssignment) / sizeof(byteAssign_t))
#define HMS4CH_PAYLOAD_LEN 66
//-------------------------------------
// HMT-1600, HMT-1800, HMT-2000
//-------------------------------------
const byteAssign_t hmt4chAssignment[] = {
{ 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_MP, UNIT_W, CH1, CALC_MPDC_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_MP, UNIT_W, CH2, CALC_MPDC_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_MP, UNIT_W, CH3, CALC_MPDC_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_MP, UNIT_W, CH4, CALC_MPDC_CH, CH4, 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 },
{ FLD_MP, UNIT_W, CH0, CALC_MPAC_CH0, 0, CMD_CALC },
{ FLD_MT, UNIT_C, CH0, CALC_MT_CH0, 0, CMD_CALC }
};
#define HMT4CH_LIST_LEN (sizeof(hmt4chAssignment) / sizeof(byteAssign_t))
#define HMT4CH_PAYLOAD_LEN 98
//-------------------------------------
// HMT-1800, HMT-2250
//-------------------------------------

2
src/network/AhoyWifiEsp32.h

@ -25,6 +25,7 @@ class AhoyWifi : public AhoyNetwork {
#if !defined(AP_ONLY)
WiFi.setScanMethod(WIFI_ALL_CHANNEL_SCAN);
WiFi.setSortMethod(WIFI_CONNECT_AP_BY_SIGNAL);
setStaticIp();
WiFi.begin(mConfig->sys.stationSsid, mConfig->sys.stationPwd, WIFI_ALL_CHANNEL_SCAN);
mWifiConnecting = true;
@ -41,7 +42,6 @@ class AhoyWifi : public AhoyNetwork {
mStatus = NetworkState::CONNECTED;
mWifiConnecting = false;
DPRINTLN(DBG_INFO, F("Network connected"));
setStaticIp();
}
break;

2
src/network/AhoyWifiEsp8266.h

@ -71,6 +71,7 @@ class AhoyWifi : public AhoyNetwork {
DBGPRINT(" " + String(bssid[j], HEX));
}
DBGPRINTLN("");
setStaticIp();
WiFi.begin(mConfig->sys.stationSsid, mConfig->sys.stationPwd, 0, &bssid[0]);
mWifiConnecting = true;
break;
@ -84,7 +85,6 @@ class AhoyWifi : public AhoyNetwork {
break;
case NetworkState::CONNECTED:
setStaticIp();
break;
case NetworkState::GOT_IP:

6
src/platformio.ini

@ -23,13 +23,13 @@ extra_scripts =
pre:../scripts/convertHtml.py
pre:../scripts/applyPatches.py
pre:../scripts/reduceGxEPD2.py
#post:../scripts/add_littlefs_binary.py
post:../scripts/add_littlefs_binary.py
lib_deps =
https://github.com/esphome/ESPAsyncWebServer @ ^3.2.0
https://github.com/esphome/ESPAsyncWebServer @ ^3.2.2
https://github.com/nRF24/RF24.git#v1.4.8
paulstoffregen/Time @ ^1.6.1
https://github.com/bertmelis/espMqttClient#v1.6.0
https://github.com/bertmelis/espMqttClient#v1.7.0
bblanchon/ArduinoJson @ ^6.21.3
https://github.com/JChristensen/Timezone @ ^1.2.4
olikraus/U8g2 @ ^2.35.9

5
src/publisher/pubMqtt.h

@ -62,7 +62,7 @@ class PubMqtt {
mUptime = uptime;
mIntervalTimeout = 1;
SendIvData.setup(app, sys, utcTs, &mSendList);
SendIvData.setup(app, sys, cfg_mqtt, utcTs, &mSendList);
SendIvData.setPublishFunc([this](const char *subTopic, const char *payload, bool retained, uint8_t qos) {
publish(subTopic, payload, retained, true, qos);
});
@ -564,6 +564,9 @@ class PubMqtt {
}
void sendData(Inverter<> *iv, uint8_t curInfoCmd) {
if (mCfgMqtt->json)
return;
record_t<> *rec = iv->getRecordStruct(curInfoCmd);
uint32_t lastTs = iv->getLastTs(rec);

69
src/publisher/pubMqttIvData.h

@ -24,9 +24,10 @@ class PubMqttIvData {
public:
PubMqttIvData() : mTotal{}, mSubTopic{}, mVal{} {}
void setup(IApp *app, HMSYSTEM *sys, uint32_t *utcTs, std::queue<sendListCmdIv> *sendList) {
void setup(IApp *app, HMSYSTEM *sys, cfgMqtt_t *cfg_mqtt, uint32_t *utcTs, std::queue<sendListCmdIv> *sendList) {
mApp = app;
mSys = sys;
mCfg = cfg_mqtt;
mUtcTimestamp = utcTs;
mSendList = sendList;
mState = IDLE;
@ -115,7 +116,7 @@ class PubMqttIvData {
mPublish(mSubTopic.data(), mVal.data(), true, QOS_0);
if((mIv->ivGen == IV_HMS) || (mIv->ivGen == IV_HMT)) {
snprintf(mSubTopic.data(), mSubTopic.size(), "%s/ch0/rssi", mIv->config->name);
snprintf(mSubTopic.data(), mSubTopic.size(), "%s/rssi", mIv->config->name);
snprintf(mVal.data(), mVal.size(), "%d", mIv->rssi);
mPublish(mSubTopic.data(), mVal.data(), false, QOS_0);
}
@ -193,18 +194,45 @@ class PubMqttIvData {
static_cast<int>(mIv->getChannelFieldValue(CH0, FLD_GRID_PROFILE_CODE, rec)),
static_cast<int>(mIv->getChannelFieldValue(CH0, FLD_GRID_PROFILE_VERSION, rec)));
} else {
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)));
if (!mCfg->json) {
snprintf(mSubTopic.data(), mSubTopic.size(), "%s/ch%d/%s", mIv->config->name, rec->assign[mPos].ch, fields[rec->assign[mPos].fieldId]);
snprintf(mVal.data(), mVal.size(), "%g", ah::round3(mIv->getValue(mPos, rec)));
}
}
uint8_t qos = (FLD_ACT_ACTIVE_PWR_LIMIT == rec->assign[mPos].fieldId) ? QOS_2 : QOS_0;
if((FLD_EVT != rec->assign[mPos].fieldId)
&& (FLD_LAST_ALARM_CODE != rec->assign[mPos].fieldId))
mPublish(mSubTopic.data(), mVal.data(), retained, qos);
if ((InverterDevInform_All == mCmd) || (InverterDevInform_Simple == mCmd) || !mCfg->json) {
uint8_t qos = (FLD_ACT_ACTIVE_PWR_LIMIT == rec->assign[mPos].fieldId) ? QOS_2 : QOS_0;
if((FLD_EVT != rec->assign[mPos].fieldId)
&& (FLD_LAST_ALARM_CODE != rec->assign[mPos].fieldId))
mPublish(mSubTopic.data(), mVal.data(), retained, qos);
}
}
mPos++;
} else {
if (MqttSentStatus::LAST_SUCCESS_SENT == rec->mqttSentStatus) {
if (mCfg->json && (RealTimeRunData_Debug == mCmd)) {
DynamicJsonDocument doc(300);
for (mPos = 0; mPos < rec->length; mPos++) {
doc[fields[rec->assign[mPos].fieldId]] = ah::round3(mIv->getValue(mPos, rec));
bool publish = false;
if (mPos != (rec->length - 1)) { // not last one
if (rec->assign[mPos].ch != rec->assign[mPos+1].ch)
publish = true;
} else
publish = true;
if (publish) {
// if next channel or end->publish
serializeJson(doc, mVal.data(), mVal.size());
snprintf(mSubTopic.data(), mSubTopic.size(), "%s/ch%d", mIv->config->name, rec->assign[mPos].ch);
mPublish(mSubTopic.data(), mVal.data(), false, QOS_0);
doc.clear();
}
}
}
sendRadioStat(rec->length);
rec->mqttSentStatus = MqttSentStatus::DATA_SENT;
}
@ -262,11 +290,27 @@ class PubMqttIvData {
mTotal[4] = mApp->getTotalMaxPower();
break;
}
snprintf(mSubTopic.data(), mSubTopic.size(), "total/%s", fields[fieldId]);
snprintf(mVal.data(), mVal.size(), "%g", ah::round3(mTotal[mPos]));
mPublish(mSubTopic.data(), mVal.data(), retained, QOS_0);
if (!mCfg->json) {
snprintf(mSubTopic.data(), mSubTopic.size(), "total/%s", fields[fieldId]);
snprintf(mVal.data(), mVal.size(), "%g", ah::round3(mTotal[mPos]));
mPublish(mSubTopic.data(), mVal.data(), retained, QOS_0);
}
mPos++;
} else {
if (mCfg->json) {
int type[5] = {FLD_PAC, FLD_YT, FLD_YD, FLD_PDC, FLD_MP};
snprintf(mVal.data(), mVal.size(), "{");
for (mPos = 0; mPos < 5; mPos++) {
snprintf(mSubTopic.data(), mSubTopic.size(), "\"%s\":%g", fields[type[mPos]], ah::round3(mTotal[mPos]));
strcat(mVal.data(), mSubTopic.data());
if (mPos < 4)
strcat(mVal.data(), ",");
else
strcat(mVal.data(), "}");
}
mPublish("total", mVal.data(), true, QOS_0);
}
mSendList->pop();
mSendTotals = false;
mState = IDLE;
@ -275,6 +319,7 @@ class PubMqttIvData {
private:
IApp *mApp = nullptr;
cfgMqtt_t *mCfg = nullptr;
HMSYSTEM *mSys = nullptr;
uint32_t *mUtcTimestamp = nullptr;
@ -293,7 +338,7 @@ class PubMqttIvData {
bool mRTRDataHasBeenSent = false;
std::array<char, (32 + MAX_NAME_LENGTH + 1)> mSubTopic;
std::array<char, 160> mVal;
std::array<char, 300> mVal;
std::queue<sendListCmdIv> *mSendList = nullptr;
};

43
src/web/RestApi.h

@ -55,6 +55,9 @@ class RestApi {
mSrv->on("/api", HTTP_GET, std::bind(&RestApi::onApi, this, std::placeholders::_1));
mSrv->on("/get_setup", HTTP_GET, std::bind(&RestApi::onDwnldSetup, this, std::placeholders::_1));
#if defined(ESP32)
mSrv->on("/coredump", HTTP_GET, std::bind(&RestApi::getCoreDump, this, std::placeholders::_1));
#endif
}
uint32_t getTimezoneOffset(void) {
@ -80,7 +83,7 @@ class RestApi {
mHeapFrag = ESP.getHeapFragmentation();
#endif
AsyncJsonResponse* response = new AsyncJsonResponse(false, 6000);
AsyncJsonResponse* response = new AsyncJsonResponse(false, 8000);
JsonObject root = response->getRoot();
String path = request->url().substring(5);
@ -348,6 +351,36 @@ class RestApi {
fp.close();
}
#if defined(ESP32)
void getCoreDump(AsyncWebServerRequest *request) {
const esp_partition_t *partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_COREDUMP, "coredump");
if (partition != NULL) {
size_t size = partition->size;
AsyncWebServerResponse *response = request->beginResponse("application/octet-stream", size, [size, partition](uint8_t *buffer, size_t maxLen, size_t index) -> size_t {
if((index + maxLen) > size)
maxLen = size - index;
if (ESP_OK != esp_partition_read(partition, index, buffer, maxLen))
DPRINTLN(DBG_ERROR, F("can't read partition"));
return maxLen;
});
String filename = ah::getDateTimeStrFile(gTimezone.toLocal(mApp->getTimestamp()));
filename += "_v" + String(mApp->getVersion());
filename += "_" + String(ENV_NAME);
response->addHeader("Content-Description", "File Transfer");
response->addHeader("Content-Disposition", "attachment; filename=" + filename + "_coredump.bin");
request->send(response);
} else {
AsyncWebServerResponse *response = request->beginResponse(200, F("application/json; charset=utf-8"), "{}");
request->send(response);
}
}
#endif
void getGeneric(AsyncWebServerRequest *request, JsonObject obj) {
mApp->resetLockTimeout();
#if !defined(ETHERNET)
@ -359,6 +392,7 @@ class RestApi {
obj[F("modules")] = String(mApp->getVersionModules());
obj[F("build")] = String(AUTO_GIT_HASH);
obj[F("env")] = String(ENV_NAME);
obj[F("host")] = mConfig->sys.deviceName;
obj[F("menu_prot")] = mApp->isProtected(request->client()->remoteIP().toString().c_str(), "", true);
obj[F("menu_mask")] = (uint16_t)(mConfig->sys.protectionMask );
obj[F("menu_protEn")] = (bool) (mConfig->sys.adminPwd[0] != '\0');
@ -386,7 +420,6 @@ class RestApi {
obj[F("dark_mode")] = (bool)mConfig->sys.darkMode;
obj[F("sched_reboot")] = (bool)mConfig->sys.schedReboot;
obj[F("hostname")] = mConfig->sys.deviceName;
obj[F("pwd_set")] = (strlen(mConfig->sys.adminPwd) > 0);
obj[F("prot_mask")] = mConfig->sys.protectionMask;
@ -435,8 +468,13 @@ class RestApi {
void getHtmlSystem(AsyncWebServerRequest *request, JsonObject obj) {
getSysInfo(request, obj.createNestedObject(F("system")));
getGeneric(request, obj.createNestedObject(F("generic")));
#if defined(ESP32)
char tmp[300];
snprintf(tmp, 300, "<a href=\"/factory\" class=\"btn\">%s</a><br/><br/><a href=\"/reboot\" class=\"btn\">%s</a><br/><br/><a href=\"/coredump\" class=\"btn\">%s</a>", FACTORY_RESET, BTN_REBOOT, BTN_COREDUMP);
#else
char tmp[200];
snprintf(tmp, 200, "<a href=\"/factory\" class=\"btn\">%s</a><br/><br/><a href=\"/reboot\" class=\"btn\">%s</a>", FACTORY_RESET, BTN_REBOOT);
#endif
obj[F("html")] = String(tmp);
}
@ -713,6 +751,7 @@ class RestApi {
obj[F("user")] = String(mConfig->mqtt.user);
obj[F("pwd")] = (strlen(mConfig->mqtt.pwd) > 0) ? F("{PWD}") : String("");
obj[F("topic")] = String(mConfig->mqtt.topic);
obj[F("json")] = (bool) mConfig->mqtt.json;
obj[F("interval")] = String(mConfig->mqtt.interval);
obj[F("retain")] = (bool)mConfig->mqtt.enableRetain;
}

8
src/web/html/api.js

@ -143,7 +143,7 @@ function parseVersion(obj) {
function parseESP(obj) {
document.getElementById("esp_type").replaceChildren(
document.createTextNode("Board: " + obj["esp_type"])
document.createTextNode("Board: " + obj.esp_type)
);
}
@ -153,7 +153,11 @@ function parseRssi(obj) {
icon = iconWifi1;
else if(obj["wifi_rssi"] <= -70)
icon = iconWifi2;
document.getElementById("wifiicon").replaceChildren(svg(icon, 32, 32, "icon-fg2", obj["wifi_rssi"]));
document.getElementById("wifiicon").replaceChildren(svg(icon, 32, 32, "icon-fg2", obj.wifi_rssi));
}
function parseTitle(obj) {
document.title = obj.host + " - " + document.title
}
function toIsoDateStr(d) {

21
src/web/html/history.html

@ -43,13 +43,10 @@
function calcScale(obj) {
let s = {}
s.x_mul = 60
s.ts_start = obj.lastValueTs - (obj.refresh * obj.value.length)
s.ts_dur = obj.lastValueTs - s.ts_start
s.ts_pad = (s.ts_dur < 1800) ? 0 : s.ts_start % 1800
s.ts_dur += s.ts_pad
s.ts_dur = obj.refresh * obj.value.length
s.ts_start = obj.lastValueTs - s.ts_dur
while(s.x_mul * 10 <= s.ts_dur)
s.x_mul += (s.x_mul == 60) ? 240 : ((s.x_mul < 1800) ? 300 : 1800)
s.x_step = Math.ceil(s.ts_dur / s.x_mul)
s.y_mul = 10
while(s.y_mul * 10 <= obj.max)
@ -91,8 +88,8 @@
}
div = x2 / scale.ts_dur
for(let i = 0; i < scale.ts_dur; i++) {
if(i % scale.x_mul == 0) {
let d = new Date((scale.ts_start - scale.ts_pad + i) * 1000)
if((i + scale.ts_start) % scale.x_mul == 0) {
let d = new Date((scale.ts_start + i) * 1000)
g.push(mlNs("text", {x: (i*div)+17, y: height+20}, ("0"+d.getHours()).slice(-2) + ":" + ("0"+d.getMinutes()).slice(-2)))
}
}
@ -107,7 +104,7 @@
}
div = x2 / scale.ts_dur
for(let i = 0; i <= scale.ts_dur; i++) {
if(i % scale.x_mul == 0) {
if((i + scale.ts_start) % scale.x_mul == 0) {
g.push(mlNs("line", {x1: (i*div), x2: (i*div), y1: 0, y2: height, "stroke-width": 1, "stroke-dasharray": "1,3", stroke: "#aaa"}))
}
}
@ -118,16 +115,15 @@
let pts = ""
let i = 0, first = -1, last = -1, lastVal = 0
let div = scale.y_max / height
let xOff = x2 / scale.ts_dur * scale.ts_pad
if(div == 0)
div = 1
for (val of obj.value) {
if(val > 0) {
lastVal = val
pts += " " + String(i + xOff) + "," + String(height - val / div)
pts += " " + String(i) + "," + String(height - val / div)
if(first < 0)
first = i + xOff
last = i + xOff
first = i
last = i
}
i += 2
}
@ -153,6 +149,7 @@
parseNav(obj.generic)
parseESP(obj.generic)
parseRssi(obj.generic)
parseTitle(obj.generic)
window.setInterval("getAjax('/api/powerHistory', parsePowerHistory)", obj.refresh * 1000)
setTimeout(() => {
window.setInterval("getAjax('/api/powerHistoryDay', parsePowerHistoryDay)", obj.refresh * 1000)

20
src/web/html/index.html

@ -14,6 +14,7 @@
</p>
<p>
<span class="des">System Infos:</span>
<div id="total"></div>
<div id="iv"></div>
<div class="hr"></div>
<div id="warn_info"></div>
@ -56,8 +57,10 @@
}
function parseGeneric(obj) {
if(exeOnce)
if(exeOnce) {
parseESP(obj)
parseTitle(obj)
}
parseRssi(obj)
}
@ -111,6 +114,8 @@
function parseIv(obj, ts) {
var p = div(["none"]);
var total = 0;
var count = 0;
for(var i of obj) {
var icon = iconSuccess;
var cl = "icon-success";
@ -131,7 +136,9 @@
avail += "{#NOT_PRODUCING}";
else {
icon = iconSuccessFull;
avail += "{#PRODUCING} " + i.cur_pwr + "W";
avail += "{#PRODUCING} " + i.cur_pwr + " W";
total += i.cur_pwr;
count += 1;
}
}
@ -149,6 +156,13 @@
}
}
document.getElementById("iv").replaceChildren(p);
if (count > 1) {
var t = div(["none"]);
t.append(svg(iconInfo, 30, 30, "icon icon-info"), span("Total: " + Math.round(total).toLocaleString() + " W"), br());
document.getElementById("total").replaceChildren(t);
document.getElementById("total").appendChild(div(["hr"]));
}
}
function parseWarn(warn) {
@ -165,7 +179,7 @@
p.append(svg(iconInfo, 30, 30, "icon icon-info"), span("{#UPDATE_AVAIL}: " + release), br());
else if(getVerInt("{#VERSION}") > getVerInt(release))
p.append(svg(iconInfo, 30, 30, "icon icon-info"), span("{#USING_DEV_VERSION} {#VERSION}. {#DEV_ISSUE_RELEASE_VERSION}: " + release), br());
else
else
p.append(svg(iconInfo, 30, 30, "icon icon-info"), span("{#RELEASE_INSTALLED}: " + release), br());
}

7
src/web/html/serial.html

@ -40,10 +40,11 @@
+ ("0"+min).substr(-2) + ":"
+ ("0"+sec).substr(-2);
parseRssi(obj);
parseRssi(obj)
if(true == exeOnce) {
parseNav(obj);
parseESP(obj);
parseNav(obj)
parseESP(obj)
parseTitle(obj)
window.setInterval("getAjax('/api/generic', parseGeneric)", 5000);
exeOnce = false;
setTimeOffset();

12
src/web/html/setup.html

@ -238,6 +238,10 @@
<div class="col-12 col-sm-3 my-2">Topic</div>
<div class="col-12 col-sm-9"><input type="text" name="mqttTopic" pattern="[\-\+A-Za-z0-9\.\/#\$%&=_]+" title="Invalid input" /></div>
</div>
<div class="row mb-3">
<div class="col-12 col-sm-3 my-2">{#MQTT_JSON}</div>
<div class="col-12 col-sm-9"><input type="checkbox" name="mqttJson" /></div>
</div>
<p class="des">{#MQTT_NOTE}</p>
<div class="row mb-3">
<div class="col-12 col-sm-3 my-2">{#INTERVAL}</div>
@ -713,9 +717,10 @@
}
function parseGeneric(obj) {
parseNav(obj);
parseESP(obj);
parseRssi(obj);
parseNav(obj)
parseESP(obj)
parseRssi(obj)
parseTitle(obj)
if(0 != obj.cst_lnk.length) {
document.getElementsByName("cstLnk")[0].value = obj.cst_lnk
@ -946,6 +951,7 @@
function parseMqtt(obj) {
for(var i of [["Addr", "broker"], ["Port", "port"], ["ClientId", "clientId"], ["User", "user"], ["Pwd", "pwd"], ["Topic", "topic"], ["Interval", "interval"]])
document.getElementsByName("mqtt"+i[0])[0].value = obj[i[1]];
document.getElementsByName("mqttJson")[0].checked = obj["json"];
document.getElementsByName("retain")[0].checked = obj.retain
}

7
src/web/html/system.html

@ -15,9 +15,10 @@
{#HTML_FOOTER}
<script type="text/javascript">
function parseGeneric(obj) {
parseNav(obj);
parseESP(obj);
parseRssi(obj);
parseNav(obj)
parseESP(obj)
parseRssi(obj)
parseTitle(obj)
}
function parseSysInfo(obj) {

1
src/web/html/update.html

@ -44,6 +44,7 @@
parseNav(obj)
parseESP(obj)
parseRssi(obj)
parseTitle(obj)
env = obj.env
document.getElementById("version").innerHTML = "{#VERSION_FULL}_" + obj.env + ".bin"
}

7
src/web/html/visualization.html

@ -34,10 +34,11 @@
function parseGeneric(obj) {
if(true == exeOnce){
parseNav(obj);
parseESP(obj);
parseNav(obj)
parseESP(obj)
parseTitle(obj)
}
parseRssi(obj);
parseRssi(obj)
}
function numBig(val, unit, des) {

6
src/web/lang.h

@ -90,4 +90,10 @@
#define BTN_NO "no"
#endif
#ifdef LANG_DE
#define BTN_COREDUMP "CoreDump herunterladen"
#else /*LANG_EN*/
#define BTN_COREDUMP "download CoreDump"
#endif
#endif /*__LANG_H__*/

5
src/web/lang.json

@ -413,6 +413,11 @@
"en": "Password (optional)",
"de": "Passwort (optional)"
},
{
"token": "MQTT_JSON",
"en": "Payload as JSON",
"de": "Ausgabe als JSON"
},
{
"token": "MQTT_NOTE",
"en": "Send Inverter data in a fixed interval, even if there is no change. A value of '0' disables the fixed interval. The data is published once it was successfully received from inverter. (default: 0)",

1
src/web/web.h

@ -582,6 +582,7 @@ class Web {
if (request->arg("mqttPwd") != "{PWD}")
request->arg("mqttPwd").toCharArray(mConfig->mqtt.pwd, MQTT_PWD_LEN);
request->arg("mqttTopic").toCharArray(mConfig->mqtt.topic, MQTT_TOPIC_LEN);
mConfig->mqtt.json = (request->arg("mqttJson") == "on");
mConfig->mqtt.port = request->arg("mqttPort").toInt();
mConfig->mqtt.interval = request->arg("mqttInterval").toInt();
mConfig->mqtt.enableRetain = (request->arg("retain") == "on");

466
tools/NodeRED/flows-mqtt-json-example.json

@ -0,0 +1,466 @@
[
{
"id": "67bced2c4e728783",
"type": "mqtt in",
"z": "5de5756d190f9086",
"name": "",
"topic": "hoymiles/+",
"qos": "0",
"datatype": "auto-detect",
"broker": "319864a4e0fd913f",
"nl": false,
"rap": true,
"rh": 0,
"inputs": 0,
"x": 80,
"y": 2100,
"wires": [
[
"a55632ad0dff0b69"
]
]
},
{
"id": "a7f0d307d7cf77e2",
"type": "mqtt in",
"z": "5de5756d190f9086",
"name": "",
"topic": "hoymiles/X/#",
"qos": "0",
"datatype": "auto-detect",
"broker": "319864a4e0fd913f",
"nl": false,
"rap": true,
"rh": 0,
"inputs": 0,
"x": 90,
"y": 2260,
"wires": [
[
"7e17e5a3f4df3011",
"1a8cca488d53394a"
]
]
},
{
"id": "7e17e5a3f4df3011",
"type": "debug",
"z": "5de5756d190f9086",
"name": "Inverter X",
"active": false,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "payload",
"targetType": "msg",
"statusVal": "",
"statusType": "auto",
"x": 340,
"y": 2260,
"wires": []
},
{
"id": "fb7357db50501627",
"type": "change",
"z": "5de5756d190f9086",
"name": "Tags setzen",
"rules": [
{
"t": "set",
"p": "payload",
"pt": "msg",
"to": "(\t $a := $split(topic, '/');\t [\t payload,\t {\t \"device\":$a[0],\t \"name\":$a[1],\t \"channel\":$a[2]\t }\t ]\t)\t",
"tot": "jsonata"
},
{
"t": "delete",
"p": "topic",
"pt": "msg"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 610,
"y": 2360,
"wires": [
[
"91a4607dfda84b67"
]
]
},
{
"id": "670eb9fbb5c31b2c",
"type": "debug",
"z": "5de5756d190f9086",
"name": "InfluxDB",
"active": false,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "payload",
"targetType": "msg",
"statusVal": "",
"statusType": "auto",
"x": 940,
"y": 2360,
"wires": []
},
{
"id": "1a8cca488d53394a",
"type": "switch",
"z": "5de5756d190f9086",
"name": "",
"property": "$split(topic, '/')[2]",
"propertyType": "jsonata",
"rules": [
{
"t": "eq",
"v": "available",
"vt": "str"
},
{
"t": "eq",
"v": "last_success",
"vt": "str"
},
{
"t": "regex",
"v": "(ch[0-6])\\b",
"vt": "str",
"case": false
},
{
"t": "eq",
"v": "radio_stat",
"vt": "str"
},
{
"t": "eq",
"v": "firmware",
"vt": "str"
},
{
"t": "eq",
"v": "hardware",
"vt": "str"
},
{
"t": "eq",
"v": "alarm",
"vt": "str"
}
],
"checkall": "true",
"repair": false,
"outputs": 7,
"x": 330,
"y": 2380,
"wires": [
[
"845aeb93e39092c5"
],
[
"241a8e70e9fde93c"
],
[
"fb7357db50501627"
],
[
"9d38f021308664c1"
],
[
"a508355f0cc87966"
],
[
"d2c9aa1a8978aca6"
],
[
"b27032beb597d5a7"
]
]
},
{
"id": "845aeb93e39092c5",
"type": "debug",
"z": "5de5756d190f9086",
"name": "available",
"active": true,
"tosidebar": false,
"console": false,
"tostatus": true,
"complete": "payload",
"targetType": "msg",
"statusVal": "payload",
"statusType": "auto",
"x": 600,
"y": 2240,
"wires": []
},
{
"id": "241a8e70e9fde93c",
"type": "debug",
"z": "5de5756d190f9086",
"name": "last_success",
"active": true,
"tosidebar": false,
"console": false,
"tostatus": true,
"complete": "payload",
"targetType": "msg",
"statusVal": "payload",
"statusType": "auto",
"x": 610,
"y": 2300,
"wires": []
},
{
"id": "9d38f021308664c1",
"type": "debug",
"z": "5de5756d190f9086",
"name": "radio_stat",
"active": false,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "payload",
"targetType": "msg",
"statusVal": "",
"statusType": "auto",
"x": 600,
"y": 2400,
"wires": []
},
{
"id": "a508355f0cc87966",
"type": "debug",
"z": "5de5756d190f9086",
"name": "firmware",
"active": false,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "payload",
"targetType": "msg",
"statusVal": "",
"statusType": "auto",
"x": 600,
"y": 2440,
"wires": []
},
{
"id": "d2c9aa1a8978aca6",
"type": "debug",
"z": "5de5756d190f9086",
"name": "hardware",
"active": false,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "payload",
"targetType": "msg",
"statusVal": "",
"statusType": "auto",
"x": 600,
"y": 2480,
"wires": []
},
{
"id": "b27032beb597d5a7",
"type": "debug",
"z": "5de5756d190f9086",
"name": "alarm",
"active": false,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "payload",
"targetType": "msg",
"statusVal": "",
"statusType": "auto",
"x": 590,
"y": 2520,
"wires": []
},
{
"id": "d814738cf55ad663",
"type": "debug",
"z": "5de5756d190f9086",
"name": "total",
"active": false,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "payload",
"targetType": "msg",
"statusVal": "",
"statusType": "auto",
"x": 590,
"y": 2160,
"wires": []
},
{
"id": "a55632ad0dff0b69",
"type": "switch",
"z": "5de5756d190f9086",
"name": "",
"property": "$split(topic, '/')[1]",
"propertyType": "jsonata",
"rules": [
{
"t": "eq",
"v": "uptime",
"vt": "str"
},
{
"t": "eq",
"v": "wifi_rssi",
"vt": "str"
},
{
"t": "eq",
"v": "status",
"vt": "str"
},
{
"t": "eq",
"v": "total",
"vt": "str"
}
],
"checkall": "true",
"repair": false,
"outputs": 4,
"x": 330,
"y": 2100,
"wires": [
[
"1fbb0674d2576ee7"
],
[
"e6be1c98ac55f511"
],
[
"f9c2d3b30e34fdda"
],
[
"d814738cf55ad663"
]
]
},
{
"id": "f9c2d3b30e34fdda",
"type": "debug",
"z": "5de5756d190f9086",
"name": "status",
"active": false,
"tosidebar": false,
"console": false,
"tostatus": true,
"complete": "payload",
"targetType": "msg",
"statusVal": "payload",
"statusType": "auto",
"x": 590,
"y": 2100,
"wires": []
},
{
"id": "e6be1c98ac55f511",
"type": "debug",
"z": "5de5756d190f9086",
"name": "wifi_rssi",
"active": false,
"tosidebar": false,
"console": false,
"tostatus": true,
"complete": "payload",
"targetType": "msg",
"statusVal": "payload",
"statusType": "auto",
"x": 600,
"y": 2040,
"wires": []
},
{
"id": "1fbb0674d2576ee7",
"type": "debug",
"z": "5de5756d190f9086",
"name": "uptime",
"active": false,
"tosidebar": false,
"console": false,
"tostatus": true,
"complete": "payload",
"targetType": "msg",
"statusVal": "payload",
"statusType": "auto",
"x": 590,
"y": 1980,
"wires": []
},
{
"id": "91a4607dfda84b67",
"type": "change",
"z": "5de5756d190f9086",
"name": "Lösche",
"rules": [
{
"t": "delete",
"p": "payload[0].YieldDay",
"pt": "msg"
},
{
"t": "delete",
"p": "payload[0].MaxPower",
"pt": "msg"
},
{
"t": "delete",
"p": "payload[0].ALARM_MES_ID",
"pt": "msg"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 780,
"y": 2360,
"wires": [
[
"670eb9fbb5c31b2c"
]
]
},
{
"id": "319864a4e0fd913f",
"type": "mqtt-broker",
"name": "broker",
"broker": "localhost",
"port": "1883",
"clientid": "",
"autoConnect": true,
"usetls": false,
"protocolVersion": "4",
"keepalive": "60",
"cleansession": true,
"birthTopic": "",
"birthQos": "0",
"birthPayload": "",
"birthMsg": {},
"closeTopic": "",
"closeQos": "0",
"closePayload": "",
"closeMsg": {},
"willTopic": "",
"willQos": "0",
"willPayload": "",
"willMsg": {},
"userProps": "",
"sessionExpiry": ""
}
]
Loading…
Cancel
Save