mirror of
https://github.com/lumapu/ahoy.git
synced 2025-07-23 11:17:11 +02:00
Merge branch 'development03' into ethW5500
This commit is contained in:
commit
b2e6223efb
67 changed files with 2104 additions and 1509 deletions
59
.github/workflows/compile_development.yml
vendored
59
.github/workflows/compile_development.yml
vendored
|
@ -1,4 +1,4 @@
|
|||
name: Ahoy Dev-Build for ESP8266/ESP32
|
||||
name: Ahoy Development
|
||||
|
||||
on:
|
||||
push:
|
||||
|
@ -8,13 +8,15 @@ on:
|
|||
|
||||
jobs:
|
||||
check:
|
||||
name: Check Repository
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'lumapu/ahoy' && github.ref_name == 'development03'
|
||||
continue-on-error: true
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
build-en:
|
||||
name: Build (EN)
|
||||
needs: check
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: true
|
||||
|
@ -32,14 +34,14 @@ jobs:
|
|||
- opendtufusion
|
||||
- opendtufusion-ethernet
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: benjlevesque/short-sha@v2.1
|
||||
- uses: actions/checkout@v4
|
||||
- uses: benjlevesque/short-sha@v3.0
|
||||
id: short-sha
|
||||
with:
|
||||
length: 7
|
||||
|
||||
- name: Cache Pip
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
|
||||
|
@ -47,13 +49,13 @@ jobs:
|
|||
${{ runner.os }}-pip-
|
||||
|
||||
- name: Cache PlatformIO
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.platformio
|
||||
key: ${{ runner.os }}-${{ hashFiles('**/lockfiles') }}
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v4.3.0
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.x"
|
||||
|
||||
|
@ -75,6 +77,7 @@ jobs:
|
|||
path: firmware/*
|
||||
|
||||
build-de:
|
||||
name: Build (DE)
|
||||
needs: check
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: true
|
||||
|
@ -92,14 +95,14 @@ jobs:
|
|||
- opendtufusion-de
|
||||
- opendtufusion-ethernet-de
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: benjlevesque/short-sha@v2.1
|
||||
- uses: actions/checkout@v4
|
||||
- uses: benjlevesque/short-sha@v3.0
|
||||
id: short-sha
|
||||
with:
|
||||
length: 7
|
||||
|
||||
- name: Cache Pip
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
|
||||
|
@ -107,13 +110,13 @@ jobs:
|
|||
${{ runner.os }}-pip-
|
||||
|
||||
- name: Cache PlatformIO
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.platformio
|
||||
key: ${{ runner.os }}-${{ hashFiles('**/lockfiles') }}
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v4.3.0
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.x"
|
||||
|
||||
|
@ -135,10 +138,12 @@ jobs:
|
|||
path: firmware/*
|
||||
|
||||
deploy:
|
||||
name: Update Artifacts / Deploy
|
||||
needs: [build-en, build-de]
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: false
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
#- name: Copy boot_app0.bin
|
||||
# run: cp ~/.platformio/packages/framework-arduinoespressif32/tools/partitions/boot_app0.bin src/.pio/build/opendtufusion/ota.bin
|
||||
|
||||
|
@ -155,13 +160,39 @@ jobs:
|
|||
- name: Set Version
|
||||
uses: cschleiden/replace-tokens@v1
|
||||
with:
|
||||
files: tools/esp8266/User_Manual.md
|
||||
files: manual/User_Manual.md
|
||||
env:
|
||||
VERSION: ${{ steps.version_name.outputs.name }}
|
||||
|
||||
|
||||
- name: Create ESP Web Tools Manifest
|
||||
working-directory: src
|
||||
run: python ../scripts/buildManifest.py
|
||||
|
||||
- name: Copy install html
|
||||
run: mv scripts/gh-action-dev-build-flash.html firmware/install.html
|
||||
|
||||
- name: Copy Changes.md
|
||||
run: mv src/CHANGES.md firmware/CHANGES.md
|
||||
|
||||
|
||||
- name: Rename firmware directory
|
||||
run: mv firmware ${{ steps.version_name.outputs.name }}
|
||||
|
||||
- name: delete environment Artifacts
|
||||
uses: geekyeggo/delete-artifact@v4
|
||||
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:
|
||||
|
|
174
.github/workflows/compile_release.yml
vendored
174
.github/workflows/compile_release.yml
vendored
|
@ -1,29 +1,49 @@
|
|||
name: Ahoy Release for ESP8266/ESP32
|
||||
name: Ahoy Release
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: main
|
||||
paths:
|
||||
- 'src/**' # build only when changes occur here
|
||||
- '.github/workflows/compile_release.yml'
|
||||
- '!README.md'
|
||||
- '!CHANGES.md'
|
||||
- '!User_Manual.md'
|
||||
paths-ignore:
|
||||
- '**.md' # Do no build on *.md changes
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build Environments
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
if: github.repository == 'lumapu/ahoy' && github.ref_name == 'main'
|
||||
continue-on-error: false
|
||||
strategy:
|
||||
matrix:
|
||||
variant:
|
||||
- esp8266
|
||||
- esp8266-prometheus
|
||||
- esp8285
|
||||
- esp32-wroom32
|
||||
- esp32-wroom32-prometheus
|
||||
- esp32-wroom32-ethernet
|
||||
- esp32-s2-mini
|
||||
- esp32-c3-mini
|
||||
- opendtufusion
|
||||
- opendtufusion-ethernet
|
||||
- esp8266-de
|
||||
- esp8266-prometheus-de
|
||||
- esp8285-de
|
||||
- esp32-wroom32-de
|
||||
- esp32-wroom32-prometheus-de
|
||||
- esp32-wroom32-ethernet-de
|
||||
- esp32-s2-mini-de
|
||||
- esp32-c3-mini-de
|
||||
- opendtufusion-de
|
||||
- opendtufusion-ethernet-de
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
ref: main
|
||||
- uses: benjlevesque/short-sha@v2.1
|
||||
- uses: actions/checkout@v4
|
||||
- uses: benjlevesque/short-sha@v3.0
|
||||
id: short-sha
|
||||
with:
|
||||
length: 7
|
||||
|
||||
- name: Cache Pip
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
|
||||
|
@ -31,13 +51,13 @@ jobs:
|
|||
${{ runner.os }}-pip-
|
||||
|
||||
- name: Cache PlatformIO
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.platformio
|
||||
key: ${{ runner.os }}-${{ hashFiles('**/lockfiles') }}
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v4.3.0
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.x"
|
||||
|
||||
|
@ -47,56 +67,108 @@ jobs:
|
|||
pip install --upgrade platformio
|
||||
|
||||
- name: Run PlatformIO
|
||||
run: pio run -d src --environment esp8266 --environment esp8266-prometheus --environment esp8285 --environment esp32-wroom32 --environment esp32-wroom32-prometheus --environment esp32-wroom32-ethernet --environment esp32-s2-mini --environment esp32-c3-mini --environment opendtufusion --environment opendtufusion-ethernet
|
||||
run: pio run -d src -e ${{ matrix.variant }}
|
||||
|
||||
- name: Copy boot_app0.bin
|
||||
run: cp ~/.platformio/packages/framework-arduinoespressif32/tools/partitions/boot_app0.bin src/.pio/build/opendtufusion/ota.bin
|
||||
- name: Rename Firmware
|
||||
run: python scripts/getVersion.py ${{ matrix.variant }} >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Rename Binary files
|
||||
id: rename-binary-files
|
||||
working-directory: src
|
||||
run: python ../scripts/getVersion.py >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Create Release
|
||||
id: create-release
|
||||
uses: actions/create-release@v1
|
||||
- name: Create Artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
draft: false
|
||||
prerelease: false
|
||||
release_name: ${{ steps.rename-binary-files.outputs.name }}
|
||||
tag_name: ${{ steps.rename-binary-files.outputs.name }}
|
||||
body_path: src/CHANGES.md
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
name: ${{ matrix.variant }}
|
||||
path: firmware/*
|
||||
|
||||
|
||||
release:
|
||||
name: Create Release
|
||||
runs-on: ubuntu-latest
|
||||
needs: [build]
|
||||
continue-on-error: false
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Get Artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
merge-multiple: true
|
||||
path: firmware
|
||||
|
||||
- name: Get Version from code
|
||||
id: version_name
|
||||
run: python scripts/getVersion.py ${{ matrix.variant }} >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Create tag
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
github.rest.git.createRef({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
ref: 'refs/tags/${{ steps.version_name.outputs.name }}',
|
||||
sha: context.sha
|
||||
})
|
||||
|
||||
- name: Set Version
|
||||
uses: cschleiden/replace-tokens@v1
|
||||
with:
|
||||
files: User_Manual.md
|
||||
files: manual/User_Manual.md
|
||||
env:
|
||||
VERSION: ${{ steps.rename-binary-files.outputs.name }}
|
||||
|
||||
- name: Create Artifact
|
||||
run: zip --junk-paths ${{ steps.rename-binary-files.outputs.name }}.zip src/firmware/* User_Manual.md
|
||||
|
||||
- name: Upload Release
|
||||
id: upload-release
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create-release.outputs.upload_url }}
|
||||
asset_path: ./${{ steps.rename-binary-files.outputs.name }}.zip
|
||||
asset_name: ${{ steps.rename-binary-files.outputs.name }}.zip
|
||||
asset_content_type: application/zip
|
||||
VERSION: ${{ steps.version_name.outputs.name }}
|
||||
|
||||
- name: Rename firmware directory
|
||||
run: mv src/firmware src/${{ steps.rename-binary-files.outputs.name }}
|
||||
run: mv firmware ${{ steps.version_name.outputs.name }}
|
||||
|
||||
- name: Rename firmware directory
|
||||
uses: vimtor/action-zip@v1.2
|
||||
with:
|
||||
files: ${{ steps.version_name.outputs.name }} manual/User_Manual.md manual/Getting_Started.md
|
||||
dest: '${{ steps.version_name.outputs.name }}.zip'
|
||||
|
||||
- name: Publish Release
|
||||
uses: ncipollo/release-action@v1
|
||||
with:
|
||||
artifactErrorsFailBuild: true
|
||||
skipIfReleaseExists: true
|
||||
bodyFile: src/CHANGES.md
|
||||
artifacts: '${{ steps.version_name.outputs.name }}.zip'
|
||||
tag: ${{ steps.version_name.outputs.name }}
|
||||
name: ${{ steps.version_name.outputs.name }}
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
|
||||
deploy:
|
||||
name: Deploy Environments to fw.ahoydtu.de
|
||||
needs: [build, release]
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: false
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Get Artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
merge-multiple: true
|
||||
path: firmware
|
||||
|
||||
- name: Get Version from code
|
||||
id: version_name
|
||||
run: python scripts/getVersion.py ${{ matrix.variant }} >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Set Version
|
||||
uses: cschleiden/replace-tokens@v1
|
||||
with:
|
||||
files: manual/User_Manual.md
|
||||
env:
|
||||
VERSION: ${{ steps.version_name.outputs.name }}
|
||||
|
||||
- name: Rename firmware directory
|
||||
run: mv firmware ${{ steps.version_name.outputs.name }}
|
||||
|
||||
- name: Deploy
|
||||
uses: nogsantos/scp-deploy@master
|
||||
with:
|
||||
src: src/${{ steps.rename-binary-files.outputs.name }}/
|
||||
src: ${{ steps.version_name.outputs.name }}/
|
||||
host: ${{ secrets.FW_SSH_HOST }}
|
||||
remote: ${{ secrets.FW_SSH_DIR }}/release
|
||||
port: ${{ secrets.FW_SSH_PORT }}
|
||||
|
|
|
@ -31,7 +31,7 @@ Table of approaches:
|
|||
|
||||
| Board | MI | HM | HMS/HMT | comment | HowTo start |
|
||||
| ------ | -- | -- | ------- | ------- | ---------- |
|
||||
| [ESP8266/ESP32, C++](Getting_Started.md) | ✔️ | ✔️ | ✔️ | 👈 the most effort is spent here | [create your own DTU](https://ahoydtu.de/getting_started/) |
|
||||
| [ESP8266/ESP32, C++](manual/Getting_Started.md) | ✔️ | ✔️ | ✔️ | 👈 the most effort is spent here | [create your own DTU](https://ahoydtu.de/getting_started/) |
|
||||
| [Arduino Nano, C++](tools/nano/NRF24_SendRcv/) | ❌ | ✔️ | ❌ | |
|
||||
| [Raspberry Pi, Python](tools/rpi/) | ❌ | ✔️ | ❌ | |
|
||||
| [Others, C/C++](tools/nano/NRF24_SendRcv/) | ❌ | ✔️ | ❌ | |
|
||||
|
@ -39,11 +39,11 @@ Table of approaches:
|
|||
⚠️ **Warning: HMS-XXXXW-2T WiFi inverters are not supported. They have a 'W' in their name and a DTU serial number on its sticker**
|
||||
|
||||
## Getting Started
|
||||
1. [Guide how to start with a ESP module](Getting_Started.md)
|
||||
1. [Guide how to start with a ESP module](manual/Getting_Started.md)
|
||||
|
||||
2. [ESP Webinstaller (Edge / Chrome Browser only)](https://ahoydtu.de/web_install)
|
||||
|
||||
3. [Ahoy Configuration ](ahoy_config.md)
|
||||
3. [Ahoy Configuration ](manual/ahoy_config.md)
|
||||
|
||||
## Our Website
|
||||
[https://ahoydtu.de](https://ahoydtu.de)
|
||||
|
|
|
@ -166,6 +166,8 @@ inverter/ctrl/limit/0 600W
|
|||
### Power Limit persistent
|
||||
This feature was removed. The persisten limit should not be modified cyclic by a script because of potential wearout of the flash inside the inverter.
|
||||
|
||||
|
||||
|
||||
## Control via REST API
|
||||
|
||||
### Generic Information
|
||||
|
@ -174,6 +176,46 @@ The rest API works with *JSON* POST requests. All the following instructions mus
|
|||
|
||||
👆 `<INVERTER_ID>` is the number of the specific inverter in the setup page.
|
||||
|
||||
### Authentication (new for versions > `0.8.79`)
|
||||
|
||||
The authentication is only needed if a password was set.
|
||||
To authenticate from API you have to add the following `JSON` to your request:
|
||||
|
||||
```json
|
||||
{
|
||||
"auth": <PASSOWRD>
|
||||
}
|
||||
```
|
||||
`<PASSWORD>` is your DTU password in plain text.
|
||||
|
||||
As Response you get the following `JSON` if successful:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"token": "<TOKEN>"
|
||||
}
|
||||
```
|
||||
Where `<TOKEN>` is a random token with a length of 16 characters.
|
||||
|
||||
For all following commands you have only to include the token into your `JSON`:
|
||||
```json
|
||||
{
|
||||
"token": "<TOKEN>"
|
||||
}
|
||||
```
|
||||
|
||||
ℹ️ Do not pass the plain text password with each command. Authenticate once and then use the token for all following commands. The token expires once the token wasn't sent for 20 minutes.
|
||||
|
||||
If the authentication fails or the token is expired you will receive the following `JSON`:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"error": "ERR_PROTECTED"
|
||||
}
|
||||
```
|
||||
|
||||
### Inverter Power (On / Off)
|
||||
|
||||
```json
|
||||
|
@ -245,19 +287,6 @@ The `VALUE` represents a percent number in a range of `[2.0 .. 100.0]`
|
|||
The `VALUE` represents watts in a range of `[1.0 .. 6553.5]`
|
||||
|
||||
|
||||
|
||||
### Developer Information REST API (obsolete)
|
||||
In the same approach as for MQTT any other SubCmd and also MainCmd can be applied and the response payload can be observed in the serial logs. Eg. request the Alarm-Data from the Alarm-Index 5 from inverter 0 will look like this:
|
||||
```json
|
||||
{
|
||||
"inverter":0,
|
||||
"tx_request": 21,
|
||||
"cmd": 17,
|
||||
"payload": 5,
|
||||
"payload2": 0
|
||||
}
|
||||
```
|
||||
|
||||
## Zero Export Control (needs rework)
|
||||
* You can use the mqtt topic `<TOPIC>/devcontrol/<INVERTER_ID>/11` with a number as payload (eg. 300 -> 300 Watt) to set the power limit to the published number in Watt. (In regular cases the inverter will use the new set point within one intervall period; to verify this see next bullet)
|
||||
* You can check the inverter set point for the power limit control on the topic `<TOPIC>/<INVERTER_NAME_FROM_SETUP>/ch0/PowerLimit` 👆 This value is ALWAYS in percent of the maximum power limit of the inverter. In regular cases this value will be updated within approx. 15 seconds. (depends on request intervall)
|
||||
|
|
|
@ -1,35 +0,0 @@
|
|||
diff --git a/RF24.cpp b/RF24.cpp
|
||||
index 9e5b4a8..a4de63c 100644
|
||||
--- a/RF24.cpp
|
||||
+++ b/RF24.cpp
|
||||
@@ -1871,6 +1871,11 @@ uint8_t RF24::getARC(void)
|
||||
return read_register(OBSERVE_TX) & 0x0F;
|
||||
}
|
||||
|
||||
+uint8_t RF24::getPLOS(void)
|
||||
+{
|
||||
+ return read_register(OBSERVE_TX) & 0x0F;
|
||||
+}
|
||||
+
|
||||
/****************************************************************************/
|
||||
|
||||
bool RF24::setDataRate(rf24_datarate_e speed)
|
||||
diff --git a/RF24.h b/RF24.h
|
||||
index dbd32ae..a3d6b52 100644
|
||||
--- a/RF24.h
|
||||
+++ b/RF24.h
|
||||
@@ -1644,6 +1644,7 @@ public:
|
||||
* @return Returns values from 0 to 15.
|
||||
*/
|
||||
uint8_t getARC(void);
|
||||
+ uint8_t getPLOS(void);
|
||||
|
||||
/**
|
||||
* Set the transmission @ref Datarate
|
||||
@@ -2415,4 +2416,4 @@ private:
|
||||
* Use `ctrl+c` to quit at any time.
|
||||
*/
|
||||
|
||||
-#endif // __RF24_H__
|
||||
\ No newline at end of file
|
||||
+#endif // __RF24_H__
|
|
@ -12,11 +12,11 @@ def applyPatch(libName, patchFile):
|
|||
|
||||
os.chdir('.pio/libdeps/' + env['PIOENV'] + '/' + libName)
|
||||
|
||||
process = subprocess.run(['git', 'apply', '--reverse', '--check', '../../../../' + patchFile], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||
process = subprocess.run(['git', 'apply', '--ignore-whitespace', '--reverse', '--check', '../../../../' + patchFile], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||
if (process.returncode == 0):
|
||||
print('\'' + patchFile + '\' already applied')
|
||||
else:
|
||||
process = subprocess.run(['git', 'apply', '../../../../' + patchFile])
|
||||
process = subprocess.run(['git', 'apply', '--ignore-whitespace', '../../../../' + patchFile])
|
||||
if (process.returncode == 0):
|
||||
print('\'' + patchFile + '\' applied')
|
||||
else:
|
||||
|
@ -32,5 +32,3 @@ if env['PIOENV'][:22] != "opendtufusion-ethernet":
|
|||
if env['PIOENV'][:13] == "opendtufusion":
|
||||
applyPatch("GxEPD2", "../patches/GxEPD2_SW_SPI.patch")
|
||||
applyPatch("RF24", "../patches/RF24_Hal.patch")
|
||||
else:
|
||||
applyPatch("RF24", "../patches/RF24.patch")
|
||||
|
|
|
@ -36,9 +36,27 @@ def buildManifest(path, infile, outfile):
|
|||
esp32["parts"].append({"path": "ESP32/bootloader.bin", "offset": 4096})
|
||||
esp32["parts"].append({"path": "ESP32/partitions.bin", "offset": 32768})
|
||||
esp32["parts"].append({"path": "ESP32/ota.bin", "offset": 57344})
|
||||
esp32["parts"].append({"path": "ESP32/" + version[1] + "_" + sha + "_esp32.bin", "offset": 65536})
|
||||
esp32["parts"].append({"path": "ESP32/" + version[1] + "_" + sha + "_esp32-wroom32.bin", "offset": 65536})
|
||||
data["builds"].append(esp32)
|
||||
|
||||
esp32s2 = {}
|
||||
esp32s2["chipFamily"] = "ESP32-S2"
|
||||
esp32s2["parts"] = []
|
||||
esp32s2["parts"].append({"path": "ESP32-S2/bootloader.bin", "offset": 4096})
|
||||
esp32s2["parts"].append({"path": "ESP32-S2/partitions.bin", "offset": 32768})
|
||||
esp32s2["parts"].append({"path": "ESP32-S2/ota.bin", "offset": 57344})
|
||||
esp32s2["parts"].append({"path": "ESP32-S2/" + version[1] + "_" + sha + "_esp32-s2-mini.bin", "offset": 65536})
|
||||
data["builds"].append(esp32s2)
|
||||
|
||||
esp32s3 = {}
|
||||
esp32s3["chipFamily"] = "ESP32-S3"
|
||||
esp32s3["parts"] = []
|
||||
esp32s3["parts"].append({"path": "ESP32/bootloader.bin", "offset": 4096})
|
||||
esp32s3["parts"].append({"path": "ESP32/partitions.bin", "offset": 32768})
|
||||
esp32s3["parts"].append({"path": "ESP32/ota.bin", "offset": 57344})
|
||||
esp32s3["parts"].append({"path": "ESP32-S3/" + version[1] + "_" + sha + "_opendtufusion.bin", "offset": 65536})
|
||||
data["builds"].append(esp32s3)
|
||||
|
||||
esp8266 = {}
|
||||
esp8266["chipFamily"] = "ESP8266"
|
||||
esp8266["parts"] = []
|
||||
|
@ -47,7 +65,7 @@ def buildManifest(path, infile, outfile):
|
|||
|
||||
jsonString = json.dumps(data, indent=2)
|
||||
|
||||
fp = open(path + "firmware/" + outfile, "w")
|
||||
fp = open(path + "../firmware/" + outfile, "w")
|
||||
fp.write(jsonString)
|
||||
fp.close()
|
||||
|
||||
|
|
126
src/CHANGES.md
126
src/CHANGES.md
|
@ -1,5 +1,127 @@
|
|||
# Development Changes
|
||||
|
||||
## 0.8.89 - 2024-03-02
|
||||
* merge PR: Collection of small fixes #1465
|
||||
* fix: show esp type on `/history` #1463
|
||||
* improved HMS-400-1T support (serial number 1125...) #1460
|
||||
|
||||
## 0.8.88 - 2024-02-28
|
||||
* fix MqTT statistic data overflow #1458
|
||||
* add HMS-400-1T support (serial number 1125...) #1460
|
||||
* removed `yield efficiency` because the inverter already calculates correct #1243
|
||||
* merge PR: Remove hint to INV_RESET_MIDNIGHT resp. INV_PAUSE_DURING_NIGHT #1431
|
||||
|
||||
## 0.8.87 - 2024-02-25
|
||||
* fix translations #1455 #1442
|
||||
|
||||
## 0.8.86 - 2024-02-23
|
||||
* RestAPI check for parent element to be JsonObject #1449
|
||||
* fix translation #1448 #1442
|
||||
* fix reset values when inverter status is 'not available' #1035 #1437
|
||||
|
||||
## 0.8.85 - 2024-02-22
|
||||
* possible fix of MqTT fix "total values are sent to often" #1421
|
||||
* fix translation #1442
|
||||
* availability check only related to live data #1035 #1437
|
||||
|
||||
## 0.8.84 - 2024-02-19
|
||||
* fix homeassistant autodiscovery #1432
|
||||
* merge PR: more gracefull handling of complete retransmits #1433
|
||||
|
||||
# RELEASE 0.8.83 - 2024-02-16
|
||||
|
||||
## 0.8.82 - 2024-02-15
|
||||
* fixed crash once firmware version was read and sent via MqTT #1428
|
||||
* possible fix: reset yield offset on midnight #1429
|
||||
|
||||
## 0.8.81 - 2024-02-13
|
||||
* fixed authentication with empty token #1415
|
||||
* added new setting for future function to send log via MqTT
|
||||
* combined firmware and hardware version to JSON topics (MqTT) #1212
|
||||
|
||||
## 0.8.80 - 2024-02-12
|
||||
* optimize API authentication, Error-Codes #1415
|
||||
* breaking change: authentication API command changed #1415
|
||||
* breaking change: limit has to be send als `float`, `0.0 .. 100.0` #1415
|
||||
* updated documentation #1415
|
||||
* fix don't send control command twice #1426
|
||||
|
||||
## 0.8.79 - 2024-02-11
|
||||
* fix `opendtufusion` build (started only once USB-console was connected)
|
||||
* code quality improvments
|
||||
|
||||
## 0.8.78 - 2024-02-10
|
||||
* finalized API token access #1415
|
||||
* possible fix of MqTT fix "total values are sent to often" #1421
|
||||
* removed `switchCycle` from `hmsRadio.h` #1412
|
||||
* merge PR: Add hint to INV_RESET_MIDNIGHT resp. INV_PAUSE_DURING_NIGHT #1418
|
||||
* merge PR: simplify rxOffset logic #1417
|
||||
* code quality improvments
|
||||
|
||||
## 0.8.77 - 2024-02-08
|
||||
* merge PR: BugFix: ACK #1414
|
||||
* fix suspicious if condition #1416
|
||||
* prepared API token for access, not functional #1415
|
||||
|
||||
## 0.8.76 - 2024-02-07
|
||||
* revert changes from yesterday regarding snprintf and its size #1410, #1411
|
||||
* reduced cppcheck linter warnings significantly
|
||||
* try to improve ePaper (ghosting) #1107
|
||||
|
||||
## 0.8.75 - 2024-02-06
|
||||
* fix active power control value #1406, #1409
|
||||
* update Mqtt lib to version `1.6.0`
|
||||
* take care of null terminator of chars #1410, #1411
|
||||
|
||||
## 0.8.74 - 2024-02-05
|
||||
* reduced cppcheck linter warnings significantly
|
||||
|
||||
## 0.8.73 - 2024-02-03
|
||||
* fix nullpointer during communication #1401
|
||||
* added `max_power` to MqTT total values #1375
|
||||
|
||||
## 0.8.72 - 2024-02-03
|
||||
* fixed translation #1403
|
||||
* fixed sending commands to inverters which are soft turned off #1397
|
||||
* reduce switchChannel command for HMS (only each 5th cycle it will be send now)
|
||||
|
||||
## 0.8.71 - 2024-02-03
|
||||
* fix heuristics reset
|
||||
* fix CMT missing frames problem
|
||||
* removed inverter gap setting
|
||||
* removed add to total (MqTT) inverter setting
|
||||
* fixed sending commands to inverters which are soft turned off
|
||||
* save settings before they are exported #1395
|
||||
* fix autologin bug if no password is set
|
||||
* translated `/serial`
|
||||
* removed "yield day" history
|
||||
|
||||
## 0.8.70 - 2024-02-01
|
||||
* prevent sending commands to inverter which isn't active #1387
|
||||
* protect commands from popup in `/live` if password is set #1199
|
||||
|
||||
## 0.8.69 - 2024-01-31
|
||||
* merge PR: Dynamic retries, pendular first rx chan #1394
|
||||
|
||||
## 0.8.68 - 2024-01-29
|
||||
* fix HMS / HMT startup
|
||||
* added `flush_rx` to NRF on TX
|
||||
* start with heuristics set to `0`
|
||||
* added warning for WiFi channel 12-14 (ESP8266 only) #1381
|
||||
|
||||
## 0.8.67 - 2024-01-29
|
||||
* fix HMS frequency
|
||||
* fix display of inverter id in serial log (was displayed twice)
|
||||
|
||||
## 0.8.66 - 2024-01-28
|
||||
* added support for other regions - untested #1271
|
||||
* fix generation of DTU-ID; was computed twice without reset if two radios are enabled
|
||||
|
||||
## 0.8.65 - 2024-01-24
|
||||
* removed patch for NRF `PLOS`
|
||||
* fix lang issues #1388
|
||||
* fix build on Windows of `opendtufusion` environments (git: trailing whitespaces)
|
||||
|
||||
## 0.8.64 - 2024-01-22
|
||||
* add `ARC` to log (NRF24 Debug)
|
||||
* merge PR: ETH NTP update bugfix #1385
|
||||
|
@ -148,7 +270,7 @@
|
|||
|
||||
## 0.8.39 - 2024-01-01
|
||||
* fix MqTT dis_night_comm in the morning #1309 #1286
|
||||
* seperated offset for sunrise and sunset #1308
|
||||
* separated offset for sunrise and sunset #1308
|
||||
* powerlimit (active power control) now has one decimal place (MqTT / API) #1199
|
||||
* merge Prometheus metrics fix #1310
|
||||
* merge MI grid profile request #1306
|
||||
|
@ -361,7 +483,7 @@
|
|||
## 0.7.61 - 2023-10-01
|
||||
* merged `hmPayload` and `hmsPayload` into single class
|
||||
* merged generic radio functions into new parent class `radio.h`
|
||||
* moved radio statistics into the inverter - each inverter has now seperate statistics which can be accessed by click on the footer in `/live`
|
||||
* moved radio statistics into the inverter - each inverter has now separate statistics which can be accessed by click on the footer in `/live`
|
||||
* fix compiler warnings #1191
|
||||
* fix ePaper logo during night time #1151
|
||||
|
||||
|
|
39
src/app.cpp
39
src/app.cpp
|
@ -13,7 +13,10 @@
|
|||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
app::app() : ah::Scheduler {} {}
|
||||
app::app() : ah::Scheduler {} {
|
||||
memset(mVersion, 0, sizeof(char) * 12);
|
||||
memset(mVersionModules, 0, sizeof(char) * 12);
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
@ -47,7 +50,7 @@ void app::setup() {
|
|||
}
|
||||
#if defined(ESP32)
|
||||
if(mConfig->cmt.enabled) {
|
||||
mCmtRadio.setup(&mConfig->serial.debug, &mConfig->serial.privacyLog, &mConfig->serial.printWholeTrace, mConfig->cmt.pinSclk, mConfig->cmt.pinSdio, mConfig->cmt.pinCsb, mConfig->cmt.pinFcsb);
|
||||
mCmtRadio.setup(&mConfig->serial.debug, &mConfig->serial.privacyLog, &mConfig->serial.printWholeTrace, mConfig->cmt.pinSclk, mConfig->cmt.pinSdio, mConfig->cmt.pinCsb, mConfig->cmt.pinFcsb, mConfig->sys.region);
|
||||
}
|
||||
#endif
|
||||
#ifdef ETHERNET
|
||||
|
@ -64,7 +67,7 @@ void app::setup() {
|
|||
|
||||
esp_task_wdt_reset();
|
||||
|
||||
mCommunication.setup(&mTimestamp, &mConfig->serial.debug, &mConfig->serial.privacyLog, &mConfig->serial.printWholeTrace, &mConfig->inst.gapMs);
|
||||
mCommunication.setup(&mTimestamp, &mConfig->serial.debug, &mConfig->serial.privacyLog, &mConfig->serial.printWholeTrace);
|
||||
mCommunication.addPayloadListener(std::bind(&app::payloadEventListener, this, std::placeholders::_1, std::placeholders::_2));
|
||||
#if defined(ENABLE_MQTT)
|
||||
mCommunication.addPowerLimitAckListener([this] (Inverter<> *iv) { mMqtt.setPowerLimitAck(iv); });
|
||||
|
@ -97,9 +100,8 @@ void app::setup() {
|
|||
esp_task_wdt_reset();
|
||||
|
||||
mWeb.setup(this, &mSys, mConfig);
|
||||
mWeb.setProtection(strlen(mConfig->sys.adminPwd) != 0);
|
||||
|
||||
mApi.setup(this, &mSys, mWeb.getWebSrvPtr(), mConfig);
|
||||
mProtection = Protection::getInstance(mConfig->sys.adminPwd);
|
||||
|
||||
#ifdef ENABLE_SYSLOG
|
||||
mDbgSyslog.setup(mConfig); // be sure to init after mWeb.setup (webSerial uses also debug callback)
|
||||
|
@ -182,6 +184,8 @@ void app::onNetwork(bool gotIp) {
|
|||
void app::regularTickers(void) {
|
||||
DPRINTLN(DBG_DEBUG, F("regularTickers"));
|
||||
everySec(std::bind(&WebType::tickSecond, &mWeb), "webSc");
|
||||
everySec([this]() { mProtection->tickSecond(); }, "prot");
|
||||
|
||||
// Plugins
|
||||
#if defined(PLUGIN_DISPLAY)
|
||||
if (DISP_TYPE_T0_NONE != mConfig->plugin.display.type)
|
||||
|
@ -227,7 +231,6 @@ void app::updateNtp(void) {
|
|||
onceAt(std::bind(&app::tickMidnight, this), midTrig, "midNi");
|
||||
|
||||
if (mConfig->sys.schedReboot) {
|
||||
uint32_t localTime = gTimezone.toLocal(mTimestamp);
|
||||
uint32_t rebootTrig = gTimezone.toUTC(localTime - (localTime % 86400) + 86410); // reboot 10 secs after midnght
|
||||
if (rebootTrig <= mTimestamp) { //necessary for times other than midnight to prevent reboot loop
|
||||
rebootTrig += 86400;
|
||||
|
@ -236,7 +239,7 @@ void app::updateNtp(void) {
|
|||
}
|
||||
}
|
||||
|
||||
if ((mSunrise == 0) && (mConfig->sun.lat) && (mConfig->sun.lon)) {
|
||||
if ((0 == mSunrise) && (0.0 != mConfig->sun.lat) && (0.0 != mConfig->sun.lon)) {
|
||||
mCalculatedTimezoneOffset = (int8_t)((mConfig->sun.lon >= 0 ? mConfig->sun.lon + 7.5 : mConfig->sun.lon - 7.5) / 15) * 3600;
|
||||
tickCalcSunrise();
|
||||
}
|
||||
|
@ -261,11 +264,10 @@ void app::tickNtpUpdate(void) {
|
|||
#endif
|
||||
if (isOK) {
|
||||
this->updateNtp();
|
||||
|
||||
nxtTrig = isOK ? (mConfig->ntp.interval * 60) : 60; // depending on NTP update success check again in 12h (depends on setting) or in 1 min
|
||||
nxtTrig = mConfig->ntp.interval * 60; // check again in 12h
|
||||
|
||||
// immediately start communicating
|
||||
if (isOK && mSendFirst) {
|
||||
if (mSendFirst) {
|
||||
mSendFirst = false;
|
||||
once(std::bind(&app::tickSend, this), 1, "senOn");
|
||||
}
|
||||
|
@ -300,9 +302,8 @@ void app::tickIVCommunication(void) {
|
|||
bool zeroValues = false;
|
||||
uint32_t nxtTrig = 0;
|
||||
|
||||
Inverter<> *iv;
|
||||
for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i ++) {
|
||||
iv = mSys.getInverterByPos(i);
|
||||
Inverter<> *iv = mSys.getInverterByPos(i);
|
||||
if(NULL == iv)
|
||||
continue;
|
||||
|
||||
|
@ -389,10 +390,9 @@ void app::tickMidnight(void) {
|
|||
|
||||
// clear max values
|
||||
if(mConfig->inst.rstMaxValsMidNight) {
|
||||
uint8_t pos;
|
||||
record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug);
|
||||
for(uint8_t i = 0; i <= iv->channels; i++) {
|
||||
pos = iv->getPosByChFld(i, FLD_MP, rec);
|
||||
uint8_t pos = iv->getPosByChFld(i, FLD_MP, rec);
|
||||
iv->setValue(pos, rec, 0.0f);
|
||||
}
|
||||
}
|
||||
|
@ -466,14 +466,13 @@ void app:: zeroIvValues(bool checkAvail, bool skipYieldDay) {
|
|||
continue; // skip to next inverter
|
||||
if (!iv->config->enabled)
|
||||
continue; // skip to next inverter
|
||||
if (iv->commEnabled)
|
||||
continue; // skip to next inverter
|
||||
|
||||
if (checkAvail) {
|
||||
if (!iv->isAvailable())
|
||||
if (iv->isAvailable())
|
||||
continue;
|
||||
}
|
||||
|
||||
changed = true;
|
||||
record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug);
|
||||
for(uint8_t ch = 0; ch <= iv->channels; ch++) {
|
||||
uint8_t pos = 0;
|
||||
|
@ -495,10 +494,9 @@ void app:: zeroIvValues(bool checkAvail, bool skipYieldDay) {
|
|||
pos = iv->getPosByChFld(ch, FLD_MP, rec);
|
||||
iv->setValue(pos, rec, 0.0f);
|
||||
}
|
||||
|
||||
iv->resetAlarms();
|
||||
iv->doCalculations();
|
||||
}
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if(changed)
|
||||
|
@ -591,9 +589,8 @@ void app::updateLed(void) {
|
|||
uint8_t led_on = (mConfig->led.high_active) ? (mConfig->led.luminance) : (255-mConfig->led.luminance);
|
||||
|
||||
if (mConfig->led.led[0] != DEF_PIN_OFF) {
|
||||
Inverter<> *iv;
|
||||
for (uint8_t id = 0; id < mSys.getNumInverters(); id++) {
|
||||
iv = mSys.getInverterByPos(id);
|
||||
Inverter<> *iv = mSys.getInverterByPos(id);
|
||||
if (NULL != iv) {
|
||||
if (iv->isProducing()) {
|
||||
// turn on when at least one inverter is producing
|
||||
|
|
118
src/app.h
118
src/app.h
|
@ -30,6 +30,7 @@
|
|||
#include "utils/scheduler.h"
|
||||
#include "utils/syslog.h"
|
||||
#include "web/RestApi.h"
|
||||
#include "web/Protection.h"
|
||||
#if defined(ENABLE_HISTORY)
|
||||
#include "plugins/history.h"
|
||||
#endif /*ENABLE_HISTORY*/
|
||||
|
@ -89,7 +90,7 @@ class app : public IApp, public ah::Scheduler {
|
|||
void handleIntr(void) {
|
||||
mNrfRadio.handleIntr();
|
||||
}
|
||||
void* getRadioObj(bool nrf) {
|
||||
void* getRadioObj(bool nrf) override {
|
||||
if(nrf)
|
||||
return (void*)&mNrfRadio;
|
||||
else {
|
||||
|
@ -107,19 +108,19 @@ class app : public IApp, public ah::Scheduler {
|
|||
}
|
||||
#endif
|
||||
|
||||
uint32_t getUptime() {
|
||||
uint32_t getUptime() override {
|
||||
return Scheduler::getUptime();
|
||||
}
|
||||
|
||||
uint32_t getTimestamp() {
|
||||
uint32_t getTimestamp() override {
|
||||
return Scheduler::mTimestamp;
|
||||
}
|
||||
|
||||
uint64_t getTimestampMs() {
|
||||
uint64_t getTimestampMs() override {
|
||||
return ((uint64_t)Scheduler::mTimestamp * 1000) + ((uint64_t)millis() - (uint64_t)Scheduler::mTsMillis) % 1000;
|
||||
}
|
||||
|
||||
bool saveSettings(bool reboot) {
|
||||
bool saveSettings(bool reboot) override {
|
||||
mShowRebootRequest = true; // only message on index, no reboot
|
||||
mSavePending = true;
|
||||
mSaveReboot = reboot;
|
||||
|
@ -130,7 +131,7 @@ class app : public IApp, public ah::Scheduler {
|
|||
return true;
|
||||
}
|
||||
|
||||
void initInverter(uint8_t id) {
|
||||
void initInverter(uint8_t id) override {
|
||||
mSys.addInverter(id, [this](Inverter<> *iv) {
|
||||
if((IV_MI == iv->ivGen) || (IV_HM == iv->ivGen))
|
||||
iv->radio = &mNrfRadio;
|
||||
|
@ -141,7 +142,7 @@ class app : public IApp, public ah::Scheduler {
|
|||
});
|
||||
}
|
||||
|
||||
bool readSettings(const char *path) {
|
||||
bool readSettings(const char *path) override {
|
||||
return mSettings.readSettings(path);
|
||||
}
|
||||
|
||||
|
@ -149,76 +150,80 @@ class app : public IApp, public ah::Scheduler {
|
|||
return mSettings.eraseSettings(eraseWifi);
|
||||
}
|
||||
|
||||
bool getSavePending() {
|
||||
bool getSavePending() override {
|
||||
return mSavePending;
|
||||
}
|
||||
|
||||
bool getLastSaveSucceed() {
|
||||
bool getLastSaveSucceed() override {
|
||||
return mSettings.getLastSaveSucceed();
|
||||
}
|
||||
|
||||
bool getShouldReboot() {
|
||||
bool getShouldReboot() override {
|
||||
return mSaveReboot;
|
||||
}
|
||||
|
||||
#if !defined(ETHERNET)
|
||||
void scanAvailNetworks() {
|
||||
void scanAvailNetworks() override {
|
||||
mWifi.scanAvailNetworks();
|
||||
}
|
||||
|
||||
bool getAvailNetworks(JsonObject obj) {
|
||||
bool getAvailNetworks(JsonObject obj) override {
|
||||
return mWifi.getAvailNetworks(obj);
|
||||
}
|
||||
|
||||
void setupStation(void) {
|
||||
void setupStation(void) override {
|
||||
mWifi.setupStation();
|
||||
}
|
||||
|
||||
void setStopApAllowedMode(bool allowed) {
|
||||
void setStopApAllowedMode(bool allowed) override {
|
||||
mWifi.setStopApAllowedMode(allowed);
|
||||
}
|
||||
|
||||
String getStationIp(void) {
|
||||
String getStationIp(void) override {
|
||||
return mWifi.getStationIp();
|
||||
}
|
||||
|
||||
bool getWasInCh12to14(void) const override {
|
||||
return mWifi.getWasInCh12to14();
|
||||
}
|
||||
|
||||
#endif /* !defined(ETHERNET) */
|
||||
|
||||
void setRebootFlag() {
|
||||
void setRebootFlag() override {
|
||||
once(std::bind(&app::tickReboot, this), 3, "rboot");
|
||||
}
|
||||
|
||||
const char *getVersion() {
|
||||
const char *getVersion() override {
|
||||
return mVersion;
|
||||
}
|
||||
|
||||
const char *getVersionModules() {
|
||||
const char *getVersionModules() override {
|
||||
return mVersionModules;
|
||||
}
|
||||
|
||||
uint32_t getSunrise() {
|
||||
uint32_t getSunrise() override {
|
||||
return mSunrise;
|
||||
}
|
||||
|
||||
uint32_t getSunset() {
|
||||
uint32_t getSunset() override {
|
||||
return mSunset;
|
||||
}
|
||||
|
||||
bool getSettingsValid() {
|
||||
bool getSettingsValid() override {
|
||||
return mSettings.getValid();
|
||||
}
|
||||
|
||||
bool getRebootRequestState() {
|
||||
bool getRebootRequestState() override {
|
||||
return mShowRebootRequest;
|
||||
}
|
||||
|
||||
void setMqttDiscoveryFlag() {
|
||||
void setMqttDiscoveryFlag() override {
|
||||
#if defined(ENABLE_MQTT)
|
||||
once(std::bind(&PubMqttType::sendDiscoveryConfig, &mMqtt), 1, "disCf");
|
||||
#endif
|
||||
}
|
||||
|
||||
bool getMqttIsConnected() {
|
||||
bool getMqttIsConnected() override {
|
||||
#if defined(ENABLE_MQTT)
|
||||
return mMqtt.isConnected();
|
||||
#else
|
||||
|
@ -226,7 +231,7 @@ class app : public IApp, public ah::Scheduler {
|
|||
#endif
|
||||
}
|
||||
|
||||
uint32_t getMqttTxCnt() {
|
||||
uint32_t getMqttTxCnt() override {
|
||||
#if defined(ENABLE_MQTT)
|
||||
return mMqtt.getTxCnt();
|
||||
#else
|
||||
|
@ -234,7 +239,7 @@ class app : public IApp, public ah::Scheduler {
|
|||
#endif
|
||||
}
|
||||
|
||||
uint32_t getMqttRxCnt() {
|
||||
uint32_t getMqttRxCnt() override {
|
||||
#if defined(ENABLE_MQTT)
|
||||
return mMqtt.getRxCnt();
|
||||
#else
|
||||
|
@ -242,15 +247,27 @@ class app : public IApp, public ah::Scheduler {
|
|||
#endif
|
||||
}
|
||||
|
||||
bool getProtection(AsyncWebServerRequest *request) {
|
||||
return mWeb.isProtected(request);
|
||||
void lock(bool fromWeb) override {
|
||||
mProtection->lock(fromWeb);
|
||||
}
|
||||
|
||||
bool getNrfEnabled(void) {
|
||||
char *unlock(const char *clientIp, bool loginFromWeb) override {
|
||||
return mProtection->unlock(clientIp, loginFromWeb);
|
||||
}
|
||||
|
||||
void resetLockTimeout(void) override {
|
||||
mProtection->resetLockTimeout();
|
||||
}
|
||||
|
||||
bool isProtected(const char *clientIp, const char *token, bool askedFromWeb) const override {
|
||||
return mProtection->isProtected(clientIp, token, askedFromWeb);
|
||||
}
|
||||
|
||||
bool getNrfEnabled(void) override {
|
||||
return mConfig->nrf.enabled;
|
||||
}
|
||||
|
||||
bool getCmtEnabled(void) {
|
||||
bool getCmtEnabled(void) override {
|
||||
return mConfig->cmt.enabled;
|
||||
}
|
||||
|
||||
|
@ -262,19 +279,19 @@ class app : public IApp, public ah::Scheduler {
|
|||
return mConfig->cmt.pinIrq;
|
||||
}
|
||||
|
||||
uint32_t getTimezoneOffset() {
|
||||
uint32_t getTimezoneOffset() override {
|
||||
return mApi.getTimezoneOffset();
|
||||
}
|
||||
|
||||
void getSchedulerInfo(uint8_t *max) {
|
||||
void getSchedulerInfo(uint8_t *max) override {
|
||||
getStat(max);
|
||||
}
|
||||
|
||||
void getSchedulerNames(void) {
|
||||
void getSchedulerNames(void) override {
|
||||
printSchedulers();
|
||||
}
|
||||
|
||||
void setTimestamp(uint32_t newTime) {
|
||||
void setTimestamp(uint32_t newTime) override {
|
||||
DPRINT(DBG_DEBUG, F("setTimestamp: "));
|
||||
DBGPRINTLN(String(newTime));
|
||||
if(0 == newTime)
|
||||
|
@ -289,7 +306,7 @@ class app : public IApp, public ah::Scheduler {
|
|||
Scheduler::setTimestamp(newTime);
|
||||
}
|
||||
|
||||
uint16_t getHistoryValue(uint8_t type, uint16_t i) {
|
||||
uint16_t getHistoryValue(uint8_t type, uint16_t i) override {
|
||||
#if defined(ENABLE_HISTORY)
|
||||
return mHistory.valueAt((HistoryStorageType)type, i);
|
||||
#else
|
||||
|
@ -297,7 +314,7 @@ class app : public IApp, public ah::Scheduler {
|
|||
#endif
|
||||
}
|
||||
|
||||
uint16_t getHistoryMaxDay() {
|
||||
uint16_t getHistoryMaxDay() override {
|
||||
#if defined(ENABLE_HISTORY)
|
||||
return mHistory.getMaximumDay();
|
||||
#else
|
||||
|
@ -351,11 +368,11 @@ class app : public IApp, public ah::Scheduler {
|
|||
void tickNtpUpdate(void);
|
||||
#if defined(ETHERNET)
|
||||
void onNtpUpdate(bool gotTime);
|
||||
bool mNtpReceived;
|
||||
bool mNtpReceived = false;
|
||||
#endif /* defined(ETHERNET) */
|
||||
void updateNtp(void);
|
||||
|
||||
void triggerTickSend() {
|
||||
void triggerTickSend() override {
|
||||
once(std::bind(&app::tickSend, this), 0, "tSend");
|
||||
}
|
||||
|
||||
|
@ -374,7 +391,7 @@ class app : public IApp, public ah::Scheduler {
|
|||
HmRadio<> mNrfRadio;
|
||||
Communication mCommunication;
|
||||
|
||||
bool mShowRebootRequest;
|
||||
bool mShowRebootRequest = false;
|
||||
|
||||
#if defined(ETHERNET)
|
||||
ahoyeth mEth;
|
||||
|
@ -383,6 +400,7 @@ class app : public IApp, public ah::Scheduler {
|
|||
#endif /* defined(ETHERNET) */
|
||||
WebType mWeb;
|
||||
RestApiType mApi;
|
||||
Protection *mProtection = nullptr;
|
||||
#ifdef ENABLE_SYSLOG
|
||||
DbgSyslog mDbgSyslog;
|
||||
#endif
|
||||
|
@ -399,26 +417,26 @@ class app : public IApp, public ah::Scheduler {
|
|||
char mVersion[12];
|
||||
char mVersionModules[12];
|
||||
settings mSettings;
|
||||
settings_t *mConfig;
|
||||
bool mSavePending;
|
||||
bool mSaveReboot;
|
||||
settings_t *mConfig = nullptr;
|
||||
bool mSavePending = false;
|
||||
bool mSaveReboot = false;
|
||||
|
||||
uint8_t mSendLastIvId;
|
||||
bool mSendFirst;
|
||||
bool mAllIvNotAvail;
|
||||
uint8_t mSendLastIvId = 0;
|
||||
bool mSendFirst = false;
|
||||
bool mAllIvNotAvail = false;
|
||||
|
||||
bool mNetworkConnected;
|
||||
bool mNetworkConnected = false;
|
||||
|
||||
// mqtt
|
||||
#if defined(ENABLE_MQTT)
|
||||
PubMqttType mMqtt;
|
||||
#endif /*ENABLE_MQTT*/
|
||||
bool mMqttReconnect;
|
||||
bool mMqttEnabled;
|
||||
bool mMqttReconnect = false;
|
||||
bool mMqttEnabled = false;
|
||||
|
||||
// sun
|
||||
int32_t mCalculatedTimezoneOffset;
|
||||
uint32_t mSunrise, mSunset;
|
||||
int32_t mCalculatedTimezoneOffset = 0;
|
||||
uint32_t mSunrise = 0, mSunset = 0;
|
||||
|
||||
// plugins
|
||||
#if defined(PLUGIN_DISPLAY)
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
class IApp {
|
||||
public:
|
||||
virtual ~IApp() {}
|
||||
virtual bool saveSettings(bool stopFs) = 0;
|
||||
virtual bool saveSettings(bool reboot) = 0;
|
||||
virtual void initInverter(uint8_t id) = 0;
|
||||
virtual bool readSettings(const char *path) = 0;
|
||||
virtual bool eraseSettings(bool eraseWifi) = 0;
|
||||
|
@ -31,6 +31,7 @@ class IApp {
|
|||
virtual void setupStation(void) = 0;
|
||||
virtual void setStopApAllowedMode(bool allowed) = 0;
|
||||
virtual String getStationIp(void) = 0;
|
||||
virtual bool getWasInCh12to14(void) const = 0;
|
||||
#endif /* defined(ETHERNET) */
|
||||
|
||||
virtual uint32_t getUptime() = 0;
|
||||
|
@ -56,7 +57,10 @@ class IApp {
|
|||
virtual uint32_t getMqttRxCnt() = 0;
|
||||
virtual uint32_t getMqttTxCnt() = 0;
|
||||
|
||||
virtual bool getProtection(AsyncWebServerRequest *request) = 0;
|
||||
virtual void lock(bool fromWeb) = 0;
|
||||
virtual char *unlock(const char *clientIp, bool loginFromWeb) = 0;
|
||||
virtual void resetLockTimeout(void) = 0;
|
||||
virtual bool isProtected(const char *clientIp, const char *token, bool askedFromWeb) const = 0;
|
||||
|
||||
virtual uint16_t getHistoryValue(uint8_t type, uint16_t i) = 0;
|
||||
virtual uint16_t getHistoryMaxDay() = 0;
|
||||
|
|
|
@ -215,7 +215,7 @@
|
|||
#define INVERTER_OFF_THRES_SEC 15*60
|
||||
|
||||
// threshold of minimum power on which the inverter is marked as inactive
|
||||
#define INACT_PWR_THRESH 1
|
||||
#define INACT_PWR_THRESH 0
|
||||
|
||||
// Timezone
|
||||
#define TIMEZONE 1
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
|
||||
#include <Arduino.h>
|
||||
#include <ArduinoJson.h>
|
||||
#include <algorithm>
|
||||
#include <LittleFS.h>
|
||||
|
||||
#include "../defines.h"
|
||||
|
@ -30,7 +31,7 @@
|
|||
* https://arduino-esp8266.readthedocs.io/en/latest/filesystem.html#flash-layout
|
||||
* */
|
||||
|
||||
#define CONFIG_VERSION 9
|
||||
#define CONFIG_VERSION 11
|
||||
|
||||
|
||||
#define PROT_MASK_INDEX 0x0001
|
||||
|
@ -68,6 +69,8 @@ typedef struct {
|
|||
uint16_t protectionMask;
|
||||
bool darkMode;
|
||||
bool schedReboot;
|
||||
uint8_t region;
|
||||
int8_t timezone;
|
||||
|
||||
#if !defined(ETHERNET)
|
||||
// wifi
|
||||
|
@ -117,6 +120,7 @@ typedef struct {
|
|||
bool debug;
|
||||
bool privacyLog;
|
||||
bool printWholeTrace;
|
||||
bool log2mqtt;
|
||||
} cfgSerial_t;
|
||||
|
||||
typedef struct {
|
||||
|
@ -145,11 +149,10 @@ typedef struct {
|
|||
uint8_t frequency;
|
||||
uint8_t powerLevel;
|
||||
bool disNightCom; // disable night communication
|
||||
bool add2Total; // add values to total values - useful if one inverter is on battery to turn off
|
||||
} cfgIv_t;
|
||||
|
||||
typedef struct {
|
||||
bool enabled;
|
||||
// bool enabled;
|
||||
cfgIv_t iv[MAX_NUM_INVERTERS];
|
||||
|
||||
uint16_t sendInterval;
|
||||
|
@ -158,8 +161,6 @@ typedef struct {
|
|||
bool rstValsCommStop;
|
||||
bool rstMaxValsMidNight;
|
||||
bool startWithoutTime;
|
||||
float yieldEffiency;
|
||||
uint16_t gapMs;
|
||||
bool readGrid;
|
||||
} cfgInst_t;
|
||||
|
||||
|
@ -206,7 +207,7 @@ typedef struct {
|
|||
class settings {
|
||||
public:
|
||||
settings() {
|
||||
mLastSaveSucceed = false;
|
||||
std::fill(reinterpret_cast<char*>(&mCfg), reinterpret_cast<char*>(&mCfg) + sizeof(mCfg), 0);
|
||||
}
|
||||
|
||||
void setup() {
|
||||
|
@ -377,7 +378,7 @@ class settings {
|
|||
memcpy(&tmp, &mCfg.sys, sizeof(cfgSys_t));
|
||||
}
|
||||
// erase all settings and reset to default
|
||||
memset(&mCfg, 0, sizeof(settings_t));
|
||||
std::fill(reinterpret_cast<char*>(&mCfg), reinterpret_cast<char*>(&mCfg) + sizeof(mCfg), 0);
|
||||
mCfg.sys.protectionMask = DEF_PROT_INDEX | DEF_PROT_LIVE | DEF_PROT_SERIAL | DEF_PROT_SETUP
|
||||
| DEF_PROT_UPDATE | DEF_PROT_SYSTEM | DEF_PROT_API | DEF_PROT_MQTT | DEF_PROT_HISTORY;
|
||||
mCfg.sys.darkMode = false;
|
||||
|
@ -395,6 +396,8 @@ class settings {
|
|||
#endif /* !defined(ETHERNET) */
|
||||
|
||||
snprintf(mCfg.sys.deviceName, DEVNAME_LEN, DEF_DEVICE_NAME);
|
||||
mCfg.sys.region = 0; // Europe
|
||||
mCfg.sys.timezone = 1;
|
||||
|
||||
mCfg.nrf.pinCs = DEF_NRF_CS_PIN;
|
||||
mCfg.nrf.pinCe = DEF_NRF_CE_PIN;
|
||||
|
@ -433,6 +436,7 @@ class settings {
|
|||
mCfg.serial.debug = false;
|
||||
mCfg.serial.privacyLog = true;
|
||||
mCfg.serial.printWholeTrace = false;
|
||||
mCfg.serial.log2mqtt = false;
|
||||
|
||||
mCfg.mqtt.port = DEF_MQTT_PORT;
|
||||
snprintf(mCfg.mqtt.broker, MQTT_ADDR_LEN, "%s", DEF_MQTT_BROKER);
|
||||
|
@ -447,15 +451,12 @@ class settings {
|
|||
mCfg.inst.rstValsCommStop = false;
|
||||
mCfg.inst.startWithoutTime = false;
|
||||
mCfg.inst.rstMaxValsMidNight = false;
|
||||
mCfg.inst.yieldEffiency = 1.0f;
|
||||
mCfg.inst.gapMs = 1;
|
||||
mCfg.inst.readGrid = true;
|
||||
|
||||
for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i++) {
|
||||
mCfg.inst.iv[i].powerLevel = 0xff; // impossible high value
|
||||
mCfg.inst.iv[i].frequency = 0x12; // 863MHz (minimum allowed frequency)
|
||||
mCfg.inst.iv[i].disNightCom = false;
|
||||
mCfg.inst.iv[i].add2Total = true;
|
||||
}
|
||||
|
||||
mCfg.led.led[0] = DEF_LED0;
|
||||
|
@ -487,20 +488,15 @@ class settings {
|
|||
}
|
||||
if(mCfg.configVersion < 2) {
|
||||
mCfg.inst.iv[i].disNightCom = false;
|
||||
mCfg.inst.iv[i].add2Total = true;
|
||||
}
|
||||
if(mCfg.configVersion < 3) {
|
||||
mCfg.serial.printWholeTrace = false;
|
||||
}
|
||||
if(mCfg.configVersion < 4) {
|
||||
mCfg.inst.gapMs = 500;
|
||||
}
|
||||
if(mCfg.configVersion < 5) {
|
||||
mCfg.inst.sendInterval = SEND_INTERVAL;
|
||||
mCfg.serial.printWholeTrace = false;
|
||||
}
|
||||
if(mCfg.configVersion < 6) {
|
||||
mCfg.inst.gapMs = 500;
|
||||
mCfg.inst.readGrid = true;
|
||||
}
|
||||
if(mCfg.configVersion < 7) {
|
||||
|
@ -509,8 +505,12 @@ class settings {
|
|||
if(mCfg.configVersion < 8) {
|
||||
mCfg.sun.offsetSecEvening = mCfg.sun.offsetSecMorning;
|
||||
}
|
||||
if(mCfg.configVersion < 9) {
|
||||
mCfg.inst.gapMs = 1;
|
||||
if(mCfg.configVersion < 10) {
|
||||
mCfg.sys.region = 0; // Europe
|
||||
mCfg.sys.timezone = 1;
|
||||
}
|
||||
if(mCfg.configVersion < 11) {
|
||||
mCfg.serial.log2mqtt = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -537,6 +537,8 @@ class settings {
|
|||
obj[F("prot_mask")] = mCfg.sys.protectionMask;
|
||||
obj[F("dark")] = mCfg.sys.darkMode;
|
||||
obj[F("reb")] = (bool) mCfg.sys.schedReboot;
|
||||
obj[F("region")] = mCfg.sys.region;
|
||||
obj[F("timezone")] = mCfg.sys.timezone;
|
||||
ah::ip2Char(mCfg.sys.ip.ip, buf); obj[F("ip")] = String(buf);
|
||||
ah::ip2Char(mCfg.sys.ip.mask, buf); obj[F("mask")] = String(buf);
|
||||
ah::ip2Char(mCfg.sys.ip.dns1, buf); obj[F("dns1")] = String(buf);
|
||||
|
@ -554,6 +556,8 @@ class settings {
|
|||
getVal<uint16_t>(obj, F("prot_mask"), &mCfg.sys.protectionMask);
|
||||
getVal<bool>(obj, F("dark"), &mCfg.sys.darkMode);
|
||||
getVal<bool>(obj, F("reb"), &mCfg.sys.schedReboot);
|
||||
getVal<uint8_t>(obj, F("region"), &mCfg.sys.region);
|
||||
getVal<int8_t>(obj, F("timezone"), &mCfg.sys.timezone);
|
||||
if(obj.containsKey(F("ip"))) ah::ip2Arr(mCfg.sys.ip.ip, obj[F("ip")].as<const char*>());
|
||||
if(obj.containsKey(F("mask"))) ah::ip2Arr(mCfg.sys.ip.mask, obj[F("mask")].as<const char*>());
|
||||
if(obj.containsKey(F("dns1"))) ah::ip2Arr(mCfg.sys.ip.dns1, obj[F("dns1")].as<const char*>());
|
||||
|
@ -657,11 +661,13 @@ class settings {
|
|||
obj[F("debug")] = mCfg.serial.debug;
|
||||
obj[F("prv")] = (bool) mCfg.serial.privacyLog;
|
||||
obj[F("trc")] = (bool) mCfg.serial.printWholeTrace;
|
||||
obj[F("mqtt")] = (bool) mCfg.serial.log2mqtt;
|
||||
} else {
|
||||
getVal<bool>(obj, F("show"), &mCfg.serial.showIv);
|
||||
getVal<bool>(obj, F("debug"), &mCfg.serial.debug);
|
||||
getVal<bool>(obj, F("prv"), &mCfg.serial.privacyLog);
|
||||
getVal<bool>(obj, F("trc"), &mCfg.serial.printWholeTrace);
|
||||
getVal<bool>(obj, F("mqtt"), &mCfg.serial.log2mqtt);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -749,32 +755,23 @@ class settings {
|
|||
void jsonInst(JsonObject obj, bool set = false) {
|
||||
if(set) {
|
||||
obj[F("intvl")] = mCfg.inst.sendInterval;
|
||||
obj[F("en")] = (bool)mCfg.inst.enabled;
|
||||
// obj[F("en")] = (bool)mCfg.inst.enabled;
|
||||
obj[F("rstMidNight")] = (bool)mCfg.inst.rstYieldMidNight;
|
||||
obj[F("rstNotAvail")] = (bool)mCfg.inst.rstValsNotAvail;
|
||||
obj[F("rstComStop")] = (bool)mCfg.inst.rstValsCommStop;
|
||||
obj[F("strtWthtTime")] = (bool)mCfg.inst.startWithoutTime;
|
||||
obj[F("rstMaxMidNight")] = (bool)mCfg.inst.rstMaxValsMidNight;
|
||||
obj[F("yldEff")] = mCfg.inst.yieldEffiency;
|
||||
obj[F("gap")] = mCfg.inst.gapMs;
|
||||
obj[F("rdGrid")] = (bool)mCfg.inst.readGrid;
|
||||
}
|
||||
else {
|
||||
getVal<uint16_t>(obj, F("intvl"), &mCfg.inst.sendInterval);
|
||||
getVal<bool>(obj, F("en"), &mCfg.inst.enabled);
|
||||
// getVal<bool>(obj, F("en"), &mCfg.inst.enabled);
|
||||
getVal<bool>(obj, F("rstMidNight"), &mCfg.inst.rstYieldMidNight);
|
||||
getVal<bool>(obj, F("rstNotAvail"), &mCfg.inst.rstValsNotAvail);
|
||||
getVal<bool>(obj, F("rstComStop"), &mCfg.inst.rstValsCommStop);
|
||||
getVal<bool>(obj, F("strtWthtTime"), &mCfg.inst.startWithoutTime);
|
||||
getVal<bool>(obj, F("rstMaxMidNight"), &mCfg.inst.rstMaxValsMidNight);
|
||||
getVal<float>(obj, F("yldEff"), &mCfg.inst.yieldEffiency);
|
||||
getVal<uint16_t>(obj, F("gap"), &mCfg.inst.gapMs);
|
||||
getVal<bool>(obj, F("rdGrid"), &mCfg.inst.readGrid);
|
||||
|
||||
if(mCfg.inst.yieldEffiency < 0.5)
|
||||
mCfg.inst.yieldEffiency = 1.0f;
|
||||
else if(mCfg.inst.yieldEffiency > 1.0f)
|
||||
mCfg.inst.yieldEffiency = 1.0f;
|
||||
}
|
||||
|
||||
JsonArray ivArr;
|
||||
|
@ -797,7 +794,6 @@ class settings {
|
|||
obj[F("freq")] = cfg->frequency;
|
||||
obj[F("pa")] = cfg->powerLevel;
|
||||
obj[F("dis")] = cfg->disNightCom;
|
||||
obj[F("add")] = cfg->add2Total;
|
||||
for(uint8_t i = 0; i < 6; i++) {
|
||||
obj[F("yield")][i] = cfg->yieldCor[i];
|
||||
obj[F("pwr")][i] = cfg->chMaxPwr[i];
|
||||
|
@ -810,7 +806,6 @@ class settings {
|
|||
getVal<uint8_t>(obj, F("freq"), &cfg->frequency);
|
||||
getVal<uint8_t>(obj, F("pa"), &cfg->powerLevel);
|
||||
getVal<bool>(obj, F("dis"), &cfg->disNightCom);
|
||||
getVal<bool>(obj, F("add"), &cfg->add2Total);
|
||||
uint8_t size = 4;
|
||||
if(obj.containsKey(F("pwr")))
|
||||
size = obj[F("pwr")].size();
|
||||
|
@ -851,7 +846,7 @@ class settings {
|
|||
#endif
|
||||
|
||||
settings_t mCfg;
|
||||
bool mLastSaveSucceed;
|
||||
bool mLastSaveSucceed = 0;
|
||||
};
|
||||
|
||||
#endif /*__SETTINGS_H__*/
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
//-------------------------------------
|
||||
#define VERSION_MAJOR 0
|
||||
#define VERSION_MINOR 8
|
||||
#define VERSION_PATCH 64
|
||||
#define VERSION_PATCH 89
|
||||
|
||||
//-------------------------------------
|
||||
typedef struct {
|
||||
|
@ -22,8 +22,6 @@ typedef struct {
|
|||
int8_t rssi;
|
||||
uint8_t packet[MAX_RF_PAYLOAD_SIZE];
|
||||
uint16_t millis;
|
||||
uint8_t arc;
|
||||
uint8_t plos;
|
||||
} packet_t;
|
||||
|
||||
typedef enum {
|
||||
|
@ -111,7 +109,7 @@ enum {
|
|||
|
||||
typedef struct {
|
||||
uint32_t rxFail;
|
||||
uint32_t rxFailNoAnser;
|
||||
uint32_t rxFailNoAnswer;
|
||||
uint32_t rxSuccess;
|
||||
uint32_t frmCnt;
|
||||
uint32_t txCnt;
|
||||
|
|
|
@ -49,7 +49,7 @@ class ahoyeth {
|
|||
#if defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
EthSpi mEthSpi;
|
||||
#endif
|
||||
settings_t *mConfig = NULL;
|
||||
settings_t *mConfig = nullptr;
|
||||
|
||||
uint32_t *mUtcTimestamp;
|
||||
AsyncUDP mUdp; // for time server
|
||||
|
|
|
@ -12,14 +12,12 @@
|
|||
#include "../utils/dbg.h"
|
||||
|
||||
#define DEFAULT_ATTEMPS 5
|
||||
#define MORE_ATTEMPS_ALARMDATA 8
|
||||
#define MORE_ATTEMPS_GRIDONPROFILEPARA 5
|
||||
#define MORE_ATTEMPS_ALARMDATA 3 // 8
|
||||
#define MORE_ATTEMPS_GRIDONPROFILEPARA 0 // 5
|
||||
|
||||
template <uint8_t N=100>
|
||||
class CommQueue {
|
||||
public:
|
||||
CommQueue() {}
|
||||
|
||||
void addImportant(Inverter<> *iv, uint8_t cmd) {
|
||||
dec(&mRdPtr);
|
||||
mQueue[mRdPtr] = queue_s(iv, cmd, true);
|
||||
|
@ -34,12 +32,12 @@ class CommQueue {
|
|||
mQueue[mWrPtr] = queue_s(iv, cmd, false);
|
||||
}
|
||||
|
||||
uint8_t getFillState(void) {
|
||||
uint8_t getFillState(void) const {
|
||||
//DPRINTLN(DBG_INFO, "wr: " + String(mWrPtr) + ", rd: " + String(mRdPtr));
|
||||
return abs(mRdPtr - mWrPtr);
|
||||
}
|
||||
|
||||
uint8_t getMaxFill(void) {
|
||||
uint8_t getMaxFill(void) const {
|
||||
return N;
|
||||
}
|
||||
|
||||
|
@ -93,7 +91,7 @@ class CommQueue {
|
|||
inc(&mRdPtr);
|
||||
}
|
||||
|
||||
void setTs(uint32_t *ts) {
|
||||
void setTs(const uint32_t *ts) {
|
||||
mQueue[mRdPtr].ts = *ts;
|
||||
}
|
||||
|
||||
|
|
|
@ -6,13 +6,14 @@
|
|||
#ifndef __COMMUNICATION_H__
|
||||
#define __COMMUNICATION_H__
|
||||
|
||||
#include <array>
|
||||
#include "CommQueue.h"
|
||||
#include <Arduino.h>
|
||||
#include "../utils/crc.h"
|
||||
#include "../utils/timemonitor.h"
|
||||
#include "Heuristic.h"
|
||||
|
||||
#define MAX_BUFFER 250
|
||||
#define MAX_BUFFER 200
|
||||
|
||||
typedef std::function<void(uint8_t, Inverter<> *)> payloadListenerType;
|
||||
typedef std::function<void(Inverter<> *)> powerLimitAckListenerType;
|
||||
|
@ -20,12 +21,11 @@ typedef std::function<void(Inverter<> *)> alarmListenerType;
|
|||
|
||||
class Communication : public CommQueue<> {
|
||||
public:
|
||||
void setup(uint32_t *timestamp, bool *serialDebug, bool *privacyMode, bool *printWholeTrace, uint16_t *inverterGap) {
|
||||
void setup(uint32_t *timestamp, bool *serialDebug, bool *privacyMode, bool *printWholeTrace) {
|
||||
mTimestamp = timestamp;
|
||||
mPrivacyMode = privacyMode;
|
||||
mSerialDebug = serialDebug;
|
||||
mPrintWholeTrace = printWholeTrace;
|
||||
mInverterGap = inverterGap;
|
||||
}
|
||||
|
||||
void addImportant(Inverter<> *iv, uint8_t cmd) {
|
||||
|
@ -83,14 +83,17 @@ class Communication : public CommQueue<> {
|
|||
q->iv->mGotFragment = false;
|
||||
q->iv->mGotLastMsg = false;
|
||||
q->iv->curFrmCnt = 0;
|
||||
q->iv->radioStatistics.txCnt++;
|
||||
mIsRetransmit = false;
|
||||
if(NULL == q->iv->radio)
|
||||
cmdDone(false); // can't communicate while radio is not defined!
|
||||
mFirstTry = q->iv->isAvailable();
|
||||
mFirstTry = (INV_RADIO_TYPE_NRF == q->iv->ivRadioType) && (q->iv->isAvailable());
|
||||
q->iv->mCmd = q->cmd;
|
||||
q->iv->mIsSingleframeReq = false;
|
||||
mFramesExpected = getFramesExpected(q); // function to get expected frame count.
|
||||
mTimeout = DURATION_TXFRAME + mFramesExpected*DURATION_ONEFRAME + duration_reserve[q->iv->ivRadioType];
|
||||
if((q->iv->ivGen == IV_MI) && ((q->cmd == MI_REQ_CH1) || (q->cmd == MI_REQ_4CH)))
|
||||
incrAttempt(q->iv->channels); // 2 more attempts for 2ch, 4 more for 4ch
|
||||
|
||||
mState = States::START;
|
||||
break;
|
||||
|
@ -112,13 +115,13 @@ class Communication : public CommQueue<> {
|
|||
} else
|
||||
q->iv->radio->prepareDevInformCmd(q->iv, q->cmd, q->ts, q->iv->alarmLastId, false);
|
||||
|
||||
q->iv->radioStatistics.txCnt++;
|
||||
//q->iv->radioStatistics.txCnt++;
|
||||
q->iv->radio->mRadioWaitTime.startTimeMonitor(mTimeout);
|
||||
if((!mIsRetransmit && (q->cmd == AlarmData)) || (q->cmd == GridOnProFilePara))
|
||||
incrAttempt((q->cmd == AlarmData)? MORE_ATTEMPS_ALARMDATA : MORE_ATTEMPS_GRIDONPROFILEPARA);
|
||||
|
||||
mIsRetransmit = false;
|
||||
setAttempt();
|
||||
if((q->cmd == AlarmData) || (q->cmd == GridOnProFilePara))
|
||||
incrAttempt(q->cmd == AlarmData? MORE_ATTEMPS_ALARMDATA : MORE_ATTEMPS_GRIDONPROFILEPARA);
|
||||
mState = States::WAIT;
|
||||
break;
|
||||
|
||||
|
@ -129,37 +132,38 @@ class Communication : public CommQueue<> {
|
|||
break;
|
||||
|
||||
case States::CHECK_FRAMES: {
|
||||
if((q->iv->radio->mBufCtrl.empty() && !mIsRetransmit) || (0 == q->attempts)) { // radio buffer empty or no more answers
|
||||
if((q->iv->radio->mBufCtrl.empty() && !mIsRetransmit) ) { // || (0 == q->attempts)) { // radio buffer empty. No more answers will be checked later
|
||||
if(*mSerialDebug) {
|
||||
DPRINT_IVID(DBG_INFO, q->iv->id);
|
||||
DBGPRINT(F("request timeout: "));
|
||||
DBGPRINT(String(q->iv->radio->mRadioWaitTime.getRunTime()));
|
||||
DBGPRINT(F("ms"));
|
||||
if(INV_RADIO_TYPE_NRF == q->iv->ivRadioType) {
|
||||
DBGPRINT(F(", ARC "));
|
||||
DBGPRINT(String(q->iv->radio->getARC()));
|
||||
DBGPRINT(F(", PLOS "));
|
||||
DBGPRINTLN(String(q->iv->radio->getPLOS()));
|
||||
} else
|
||||
DBGPRINTLN("");
|
||||
DBGPRINTLN(F("ms"));
|
||||
}
|
||||
|
||||
if(!q->iv->mGotFragment) {
|
||||
if(INV_RADIO_TYPE_CMT == q->iv->ivRadioType) {
|
||||
q->iv->radio->switchFrequency(q->iv, HOY_BOOT_FREQ_KHZ, (q->iv->config->frequency*FREQ_STEP_KHZ + HOY_BASE_FREQ_KHZ));
|
||||
#if defined(ESP32)
|
||||
if(!q->iv->radio->switchFrequency(q->iv, q->iv->radio->getBootFreqMhz() * 1000, (q->iv->config->frequency*FREQ_STEP_KHZ + q->iv->radio->getBaseFreqMhz() * 1000))) {
|
||||
DPRINT_IVID(DBG_INFO, q->iv->id);
|
||||
DBGPRINTLN(F("switch frequency failed!"));
|
||||
}
|
||||
mWaitTime.startTimeMonitor(1000);
|
||||
#endif
|
||||
} else {
|
||||
mHeu.setIvRetriesBad(q->iv);
|
||||
if(IV_MI == q->iv->ivGen)
|
||||
q->iv->mIvTxCnt++;
|
||||
|
||||
if(mFirstTry) {
|
||||
mFirstTry = false;
|
||||
setAttempt();
|
||||
if(q->attempts < 3 || !q->iv->isProducing())
|
||||
mFirstTry = false;
|
||||
mHeu.evalTxChQuality(q->iv, false, 0, 0);
|
||||
q->iv->radioStatistics.rxFailNoAnser++;
|
||||
mHeu.getTxCh(q->iv);
|
||||
//q->iv->radioStatistics.rxFailNoAnser++; // should only be one of fail or retransmit.
|
||||
//q->iv->radioStatistics.txCnt--;
|
||||
q->iv->radioStatistics.retransmits++;
|
||||
q->iv->radio->mRadioWaitTime.stopTimeMonitor();
|
||||
mState = States::START;
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -180,8 +184,11 @@ class Communication : public CommQueue<> {
|
|||
q->iv->mDtuRxCnt++;
|
||||
|
||||
if (p->packet[0] == (TX_REQ_INFO + ALL_FRAMES)) { // response from get information command
|
||||
if(parseFrame(p))
|
||||
if(parseFrame(p)) {
|
||||
q->iv->curFrmCnt++;
|
||||
if(!mIsRetransmit && ((p->packet[9] == 0x02) || (p->packet[9] == 0x82)) && (p->millis < LIMIT_FAST_IV))
|
||||
mHeu.setIvRetriesGood(q->iv,p->millis < LIMIT_VERYFAST_IV);
|
||||
}
|
||||
} else if (p->packet[0] == (TX_REQ_DEVCONTROL + ALL_FRAMES)) { // response from dev control command
|
||||
if(parseDevCtrl(p, q))
|
||||
closeRequest(q, true);
|
||||
|
@ -190,8 +197,8 @@ class Communication : public CommQueue<> {
|
|||
q->iv->radio->mBufCtrl.pop();
|
||||
return; // don't wait for empty buffer
|
||||
} else if(IV_MI == q->iv->ivGen) {
|
||||
if(parseMiFrame(p, q))
|
||||
q->iv->curFrmCnt++;
|
||||
parseMiFrame(p, q);
|
||||
q->iv->curFrmCnt++;
|
||||
}
|
||||
} //else -> serial does not match
|
||||
|
||||
|
@ -202,10 +209,9 @@ class Communication : public CommQueue<> {
|
|||
if(q->iv->ivGen != IV_MI) {
|
||||
mState = States::CHECK_PACKAGE;
|
||||
} else {
|
||||
bool fastNext = true;
|
||||
if(q->iv->miMultiParts < 6) {
|
||||
mState = States::WAIT;
|
||||
if((q->iv->radio->mRadioWaitTime.isTimeout() && mIsRetransmit) || !mIsRetransmit) {
|
||||
if(q->iv->radio->mRadioWaitTime.isTimeout() && q->attempts) {
|
||||
miRepeatRequest(q);
|
||||
return;
|
||||
}
|
||||
|
@ -215,12 +221,12 @@ class Communication : public CommQueue<> {
|
|||
|| ((q->cmd == MI_REQ_CH2) && (q->iv->type == INV_TYPE_2CH))
|
||||
|| ((q->cmd == MI_REQ_CH1) && (q->iv->type == INV_TYPE_1CH))) {
|
||||
miComplete(q->iv);
|
||||
fastNext = false;
|
||||
}
|
||||
if(fastNext)
|
||||
miNextRequest(q->iv->type == INV_TYPE_4CH ? MI_REQ_4CH : MI_REQ_CH1, q);
|
||||
else
|
||||
closeRequest(q, true);
|
||||
if(*mSerialDebug) {
|
||||
DPRINT_IVID(DBG_INFO, q->iv->id);
|
||||
DBGPRINTLN(F("Payload (MI got all)"));
|
||||
}
|
||||
closeRequest(q, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -252,10 +258,33 @@ class Communication : public CommQueue<> {
|
|||
if(framnr) {
|
||||
if(0 == q->attempts) {
|
||||
DPRINT_IVID(DBG_INFO, q->iv->id);
|
||||
DBGPRINT(F("no attempts left"));
|
||||
DBGPRINTLN(F("timeout, no attempts left"));
|
||||
closeRequest(q, false);
|
||||
return;
|
||||
}
|
||||
//count missing frames
|
||||
if(!q->iv->mIsSingleframeReq && (q->iv->ivRadioType == INV_RADIO_TYPE_NRF)) { // already checked?
|
||||
uint8_t missedFrames = 0;
|
||||
for(uint8_t i = 0; i < q->iv->radio->mFramesExpected; i++) {
|
||||
if(mLocalBuf[i].len == 0)
|
||||
missedFrames++;
|
||||
}
|
||||
if(missedFrames > 3 || (q->cmd == RealTimeRunData_Debug && missedFrames > 1) || ((missedFrames > 1) && ((missedFrames + 2) > q->attempts))) {
|
||||
if(*mSerialDebug) {
|
||||
DPRINT_IVID(DBG_INFO, q->iv->id);
|
||||
DBGPRINT(String(missedFrames));
|
||||
DBGPRINT(F(" frames missing "));
|
||||
DBGPRINTLN(F("-> complete retransmit"));
|
||||
}
|
||||
mHeu.evalTxChQuality(q->iv, false, (q->attemptsMax - 1 - q->attempts), q->iv->curFrmCnt, true);
|
||||
q->iv->radioStatistics.txCnt--;
|
||||
q->iv->radioStatistics.retransmits++;
|
||||
mCompleteRetry = true;
|
||||
mState = States::RESET;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
setAttempt();
|
||||
|
||||
if(*mSerialDebug) {
|
||||
|
@ -273,12 +302,14 @@ class Communication : public CommQueue<> {
|
|||
return;
|
||||
}
|
||||
|
||||
compilePayload(q);
|
||||
if(compilePayload(q)) {
|
||||
if((NULL != mCbPayload) && (GridOnProFilePara != q->cmd) && (GetLossRate != q->cmd))
|
||||
(mCbPayload)(q->cmd, q->iv);
|
||||
|
||||
if((NULL != mCbPayload) && (GridOnProFilePara != q->cmd) && (GetLossRate != q->cmd))
|
||||
(mCbPayload)(q->cmd, q->iv);
|
||||
closeRequest(q, true);
|
||||
} else
|
||||
closeRequest(q, false);
|
||||
|
||||
closeRequest(q, true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -291,11 +322,6 @@ class Communication : public CommQueue<> {
|
|||
DBGPRINT(String(p->millis));
|
||||
DBGPRINT(F("ms | "));
|
||||
DBGPRINT(String(p->len));
|
||||
DBGPRINT(F(", ARC "));
|
||||
DBGPRINT(String(p->arc));
|
||||
DBGPRINT(F(", PLOS "));
|
||||
DBGPRINT(String(p->plos));
|
||||
DBGPRINT(F(" |"));
|
||||
if(INV_RADIO_TYPE_NRF == q->iv->ivRadioType) {
|
||||
DBGPRINT(F(" CH"));
|
||||
if(3 == p->ch)
|
||||
|
@ -366,7 +392,7 @@ class Communication : public CommQueue<> {
|
|||
}
|
||||
}
|
||||
|
||||
inline bool validateIvSerial(uint8_t buf[], Inverter<> *iv) {
|
||||
inline bool validateIvSerial(const uint8_t buf[], Inverter<> *iv) {
|
||||
uint8_t tmp[4];
|
||||
CP_U32_BigEndian(tmp, iv->radioId.u64 >> 8);
|
||||
for(uint8_t i = 0; i < 4; i++) {
|
||||
|
@ -416,14 +442,15 @@ class Communication : public CommQueue<> {
|
|||
return true;
|
||||
}
|
||||
|
||||
inline bool parseMiFrame(packet_t *p, const queue_s *q) {
|
||||
inline void parseMiFrame(packet_t *p, const queue_s *q) {
|
||||
if((!mIsRetransmit && p->packet[9] == 0x00) && (p->millis < LIMIT_FAST_IV_MI)) //first frame is fast?
|
||||
mHeu.setIvRetriesGood(q->iv,p->millis < LIMIT_VERYFAST_IV_MI);
|
||||
if ((p->packet[0] == MI_REQ_CH1 + ALL_FRAMES)
|
||||
|| (p->packet[0] == MI_REQ_CH2 + ALL_FRAMES)
|
||||
|| ((p->packet[0] >= (MI_REQ_4CH + ALL_FRAMES))
|
||||
&& (p->packet[0] < (0x39 + SINGLE_FRAME))
|
||||
)) { //&& (p->packet[0] != (0x0f + ALL_FRAMES)))) {
|
||||
)) {
|
||||
// small MI or MI 1500 data responses to 0x09, 0x11, 0x36, 0x37, 0x38 and 0x39
|
||||
//mPayload[iv->id].txId = p->packet[0];
|
||||
miDataDecode(p, q);
|
||||
} else if (p->packet[0] == (0x0f + ALL_FRAMES)) {
|
||||
miHwDecode(p, q);
|
||||
|
@ -436,13 +463,10 @@ class Communication : public CommQueue<> {
|
|||
record_t<> *rec = q->iv->getRecordStruct(RealTimeRunData_Debug); // choose the record structure
|
||||
rec->ts = q->ts;
|
||||
miStsConsolidate(q, ((p->packet[0] == 0x88) ? 1 : 2), rec, p->packet[10], p->packet[12], p->packet[9], p->packet[11]);
|
||||
//mHeu.setGotFragment(q->iv); only do this when we are through the cycle?
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool parseDevCtrl(packet_t *p, const queue_s *q) {
|
||||
inline bool parseDevCtrl(const packet_t *p, const queue_s *q) {
|
||||
switch(p->packet[12]) {
|
||||
case ActivePowerContr:
|
||||
if(p->packet[13] != 0x00)
|
||||
|
@ -480,7 +504,7 @@ class Communication : public CommQueue<> {
|
|||
return accepted;
|
||||
}
|
||||
|
||||
inline void compilePayload(const queue_s *q) {
|
||||
inline bool compilePayload(const queue_s *q) {
|
||||
uint16_t crc = 0xffff, crcRcv = 0x0000;
|
||||
for(uint8_t i = 0; i < mMaxFrameId; i++) {
|
||||
if(i == (mMaxFrameId - 1)) {
|
||||
|
@ -496,27 +520,22 @@ class Communication : public CommQueue<> {
|
|||
DBGPRINT(F("CRC Error "));
|
||||
if(q->attempts == 0) {
|
||||
DBGPRINTLN(F("-> Fail"));
|
||||
closeRequest(q, false);
|
||||
|
||||
} else
|
||||
DBGPRINTLN(F("-> complete retransmit"));
|
||||
mCompleteRetry = true;
|
||||
mState = States::RESET;
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
/*DPRINT_IVID(DBG_INFO, q->iv->id);
|
||||
DBGPRINT(F("procPyld: cmd: 0x"));
|
||||
DBGHEXLN(q->cmd);*/
|
||||
|
||||
memset(mPayload, 0, MAX_BUFFER);
|
||||
mPayload.fill(0);
|
||||
int8_t rssi = -127;
|
||||
uint8_t len = 0;
|
||||
|
||||
DPRINT_IVID(DBG_INFO, q->iv->id);
|
||||
for(uint8_t i = 0; i < mMaxFrameId; i++) {
|
||||
if(mLocalBuf[i].len + len > MAX_BUFFER) {
|
||||
DPRINTLN(DBG_ERROR, F("payload buffer to small!"));
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
memcpy(&mPayload[len], mLocalBuf[i].buf, mLocalBuf[i].len);
|
||||
len += mLocalBuf[i].len;
|
||||
|
@ -527,30 +546,31 @@ class Communication : public CommQueue<> {
|
|||
|
||||
len -= 2;
|
||||
|
||||
DPRINT_IVID(DBG_INFO, q->iv->id);
|
||||
DBGPRINT(F("Payload ("));
|
||||
DBGPRINT(String(len));
|
||||
if(*mPrintWholeTrace) {
|
||||
DBGPRINT(F("): "));
|
||||
ah::dumpBuf(mPayload, len);
|
||||
} else
|
||||
DBGPRINTLN(F(")"));
|
||||
if(*mSerialDebug) {
|
||||
DPRINT_IVID(DBG_INFO, q->iv->id);
|
||||
DBGPRINT(F("Payload ("));
|
||||
DBGPRINT(String(len));
|
||||
if(*mPrintWholeTrace) {
|
||||
DBGPRINT(F("): "));
|
||||
ah::dumpBuf(mPayload.data(), len);
|
||||
} else
|
||||
DBGPRINTLN(F(")"));
|
||||
}
|
||||
|
||||
if(GridOnProFilePara == q->cmd) {
|
||||
q->iv->addGridProfile(mPayload, len);
|
||||
return;
|
||||
q->iv->addGridProfile(mPayload.data(), len);
|
||||
return true;
|
||||
}
|
||||
|
||||
record_t<> *rec = q->iv->getRecordStruct(q->cmd);
|
||||
if(NULL == rec) {
|
||||
if(GetLossRate == q->cmd) {
|
||||
q->iv->parseGetLossRate(mPayload, len);
|
||||
return;
|
||||
} else {
|
||||
q->iv->parseGetLossRate(mPayload.data(), len);
|
||||
return true;
|
||||
} else
|
||||
DPRINTLN(DBG_ERROR, F("record is NULL!"));
|
||||
closeRequest(q, false);
|
||||
}
|
||||
return;
|
||||
|
||||
return false;
|
||||
}
|
||||
if((rec->pyldLen != len) && (0 != rec->pyldLen)) {
|
||||
if(*mSerialDebug) {
|
||||
|
@ -558,15 +578,13 @@ class Communication : public CommQueue<> {
|
|||
DBGPRINT(String(rec->pyldLen));
|
||||
DBGPRINTLN(F(" bytes"));
|
||||
}
|
||||
/*q->iv->radioStatistics.rxFail++;*/
|
||||
closeRequest(q, false);
|
||||
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
rec->ts = q->ts;
|
||||
for (uint8_t i = 0; i < rec->length; i++) {
|
||||
q->iv->addValue(i, mPayload, rec);
|
||||
q->iv->addValue(i, mPayload.data(), rec);
|
||||
}
|
||||
rec->mqttSentStatus = MqttSentStatus::NEW_DATA;
|
||||
|
||||
|
@ -576,13 +594,14 @@ class Communication : public CommQueue<> {
|
|||
if(AlarmData == q->cmd) {
|
||||
uint8_t i = 0;
|
||||
while(1) {
|
||||
if(0 == q->iv->parseAlarmLog(i++, mPayload, len))
|
||||
if(0 == q->iv->parseAlarmLog(i++, mPayload.data(), len))
|
||||
break;
|
||||
if (NULL != mCbAlarm)
|
||||
(mCbAlarm)(q->iv);
|
||||
yield();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void sendRetransmit(const queue_s *q, uint8_t i) {
|
||||
|
@ -600,11 +619,11 @@ class Communication : public CommQueue<> {
|
|||
mHeu.evalTxChQuality(q->iv, crcPass, (q->attemptsMax - 1 - q->attempts), q->iv->curFrmCnt);
|
||||
if(crcPass)
|
||||
q->iv->radioStatistics.rxSuccess++;
|
||||
else if(q->iv->mGotFragment)
|
||||
else if(q->iv->mGotFragment || mCompleteRetry)
|
||||
q->iv->radioStatistics.rxFail++; // got no complete payload
|
||||
else
|
||||
q->iv->radioStatistics.rxFailNoAnser++; // got nothing
|
||||
mWaitTime.startTimeMonitor(*mInverterGap);
|
||||
q->iv->radioStatistics.rxFailNoAnswer++; // got nothing
|
||||
mWaitTime.startTimeMonitor(1); // maybe remove, side effects unknown
|
||||
|
||||
bool keep = false;
|
||||
if(q->isDevControl)
|
||||
|
@ -615,6 +634,7 @@ class Communication : public CommQueue<> {
|
|||
q->iv->mGotLastMsg = false;
|
||||
q->iv->miMultiParts = 0;
|
||||
mIsRetransmit = false;
|
||||
mCompleteRetry = false;
|
||||
mState = States::RESET;
|
||||
DBGPRINTLN(F("-----"));
|
||||
}
|
||||
|
@ -656,18 +676,17 @@ class Communication : public CommQueue<> {
|
|||
};
|
||||
*/
|
||||
|
||||
if ( p->packet[9] == 0x00 ) {//first frame
|
||||
if ( p->packet[9] == 0x00 ) { //first frame
|
||||
//FLD_FW_VERSION
|
||||
for (uint8_t i = 0; i < 5; i++) {
|
||||
q->iv->setValue(i, rec, (float) ((p->packet[(12+2*i)] << 8) + p->packet[(13+2*i)])/1);
|
||||
}
|
||||
q->iv->isConnected = true;
|
||||
if(*mSerialDebug) {
|
||||
DPRINT_IVID(DBG_INFO, q->iv->id);
|
||||
DBGPRINT(F("HW_VER is "));
|
||||
DBGPRINTLN(String((p->packet[24] << 8) + p->packet[25]));
|
||||
}
|
||||
record_t<> *rec = q->iv->getRecordStruct(InverterDevInform_Simple); // choose the record structure
|
||||
rec = q->iv->getRecordStruct(InverterDevInform_Simple); // choose the record structure
|
||||
rec->ts = q->ts;
|
||||
q->iv->setValue(1, rec, (uint32_t) ((p->packet[24] << 8) + p->packet[25])/1);
|
||||
q->iv->miMultiParts +=4;
|
||||
|
@ -686,7 +705,7 @@ class Communication : public CommQueue<> {
|
|||
byte[23] to byte[26] Matching_APPFW_PN*/
|
||||
DPRINT(DBG_INFO,F("HW_PartNo "));
|
||||
DBGPRINTLN(String((uint32_t) (((p->packet[10] << 8) | p->packet[11]) << 8 | p->packet[12]) << 8 | p->packet[13]));
|
||||
record_t<> *rec = q->iv->getRecordStruct(InverterDevInform_Simple); // choose the record structure
|
||||
rec = q->iv->getRecordStruct(InverterDevInform_Simple); // choose the record structure
|
||||
rec->ts = q->ts;
|
||||
q->iv->setValue(0, rec, (uint32_t) ((((p->packet[10] << 8) | p->packet[11]) << 8 | p->packet[12]) << 8 | p->packet[13])/1);
|
||||
rec->mqttSentStatus = MqttSentStatus::NEW_DATA;
|
||||
|
@ -801,35 +820,22 @@ class Communication : public CommQueue<> {
|
|||
miStsConsolidate(q, datachan, rec, p->packet[23], p->packet[24]);
|
||||
|
||||
if (p->packet[0] < (0x39 + ALL_FRAMES) ) {
|
||||
mHeu.evalTxChQuality(q->iv, true, (q->attemptsMax - 1 - q->attempts), 1);
|
||||
miNextRequest((p->packet[0] - ALL_FRAMES + 1), q);
|
||||
} else {
|
||||
q->iv->miMultiParts = 7; // indicate we are ready
|
||||
}
|
||||
} else if((p->packet[0] == (MI_REQ_CH1 + ALL_FRAMES)) && (q->iv->type == INV_TYPE_2CH)) {
|
||||
//addImportant(q->iv, MI_REQ_CH2);
|
||||
miNextRequest(MI_REQ_CH2, q);
|
||||
mHeu.evalTxChQuality(q->iv, true, (q->attemptsMax - 1 - q->attempts), q->iv->curFrmCnt);
|
||||
q->iv->mIvRxCnt++; // statistics workaround...
|
||||
q->iv->mIvRxCnt++; // statistics workaround...
|
||||
|
||||
} else { // first data msg for 1ch, 2nd for 2ch
|
||||
} else // first data msg for 1ch, 2nd for 2ch
|
||||
q->iv->miMultiParts += 6; // indicate we are ready
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void miNextRequest(uint8_t cmd, const queue_s *q) {
|
||||
incrAttempt(); // if function is called, we got something, and we necessarily need more transmissions for MI types...
|
||||
if(*mSerialDebug) {
|
||||
DPRINT_IVID(DBG_WARN, q->iv->id);
|
||||
DBGPRINT(F("next request ("));
|
||||
DBGPRINT(String(q->attempts));
|
||||
DBGPRINT(F(" attempts left): 0x"));
|
||||
DBGHEXLN(cmd);
|
||||
}
|
||||
|
||||
if(q->iv->miMultiParts == 7)
|
||||
q->iv->radioStatistics.rxSuccess++;
|
||||
mHeu.evalTxChQuality(q->iv, true, (q->attemptsMax - 1 - q->attempts), q->iv->curFrmCnt);
|
||||
mHeu.getTxCh(q->iv);
|
||||
q->iv->radioStatistics.ivSent++;
|
||||
|
||||
mFramesExpected = getFramesExpected(q);
|
||||
q->iv->radio->setExpectedFrames(mFramesExpected);
|
||||
|
@ -838,6 +844,13 @@ class Communication : public CommQueue<> {
|
|||
q->iv->radio->mRadioWaitTime.startTimeMonitor(DURATION_TXFRAME + DURATION_ONEFRAME + duration_reserve[q->iv->ivRadioType]);
|
||||
q->iv->miMultiParts = 0;
|
||||
q->iv->mGotFragment = 0;
|
||||
if(*mSerialDebug) {
|
||||
DPRINT_IVID(DBG_INFO, q->iv->id);
|
||||
DBGPRINT(F("next: ("));
|
||||
DBGPRINT(String(q->attempts));
|
||||
DBGPRINT(F(" attempts left): 0x"));
|
||||
DBGHEXLN(cmd);
|
||||
}
|
||||
mIsRetransmit = true;
|
||||
chgCmd(cmd);
|
||||
//mState = States::WAIT;
|
||||
|
@ -845,18 +858,17 @@ class Communication : public CommQueue<> {
|
|||
|
||||
void miRepeatRequest(const queue_s *q) {
|
||||
setAttempt(); // if function is called, we got something, and we necessarily need more transmissions for MI types...
|
||||
q->iv->radio->sendCmdPacket(q->iv, q->cmd, 0x00, true);
|
||||
q->iv->radioStatistics.retransmits++;
|
||||
q->iv->radio->mRadioWaitTime.startTimeMonitor(DURATION_TXFRAME + DURATION_ONEFRAME + duration_reserve[q->iv->ivRadioType]);
|
||||
if(*mSerialDebug) {
|
||||
DPRINT_IVID(DBG_WARN, q->iv->id);
|
||||
DPRINT_IVID(DBG_INFO, q->iv->id);
|
||||
DBGPRINT(F("resend request ("));
|
||||
DBGPRINT(String(q->attempts));
|
||||
DBGPRINT(F(" attempts left): 0x"));
|
||||
DBGHEXLN(q->cmd);
|
||||
}
|
||||
|
||||
q->iv->radio->sendCmdPacket(q->iv, q->cmd, 0x00, true);
|
||||
|
||||
q->iv->radio->mRadioWaitTime.startTimeMonitor(DURATION_TXFRAME + DURATION_ONEFRAME + duration_reserve[q->iv->ivRadioType]);
|
||||
mIsRetransmit = false;
|
||||
//mIsRetransmit = false;
|
||||
}
|
||||
|
||||
void miStsConsolidate(const queue_s *q, uint8_t stschan, record_t<> *rec, uint8_t uState, uint8_t uEnum, uint8_t lState = 0, uint8_t lEnum = 0) {
|
||||
|
@ -879,50 +891,62 @@ class Communication : public CommQueue<> {
|
|||
statusMi = 8310; //trick?
|
||||
}
|
||||
|
||||
uint16_t prntsts = statusMi == 3 ? 1 : statusMi;
|
||||
uint16_t prntsts = (statusMi == 3) ? 1 : statusMi;
|
||||
bool stsok = true;
|
||||
if ( prntsts != rec->record[q->iv->getPosByChFld(0, FLD_EVT, rec)] ) { //sth.'s changed?
|
||||
q->iv->alarmCnt = 1; // minimum...
|
||||
bool changedStatus = false; //if true, raise alarms and send via mqtt (might affect single channel only)
|
||||
uint8_t oldState = rec->record[q->iv->getPosByChFld(0, FLD_EVT, rec)];
|
||||
if ( prntsts != oldState ) { // sth.'s changed?
|
||||
stsok = false;
|
||||
//sth is or was wrong?
|
||||
if ( (q->iv->type != INV_TYPE_1CH) && ( (statusMi != 3)
|
||||
|| ((q->iv->lastAlarm[stschan].code) && (statusMi == 3) && (q->iv->lastAlarm[stschan].code != 1)))
|
||||
) {
|
||||
q->iv->lastAlarm[stschan+q->iv->type==INV_TYPE_2CH ? 2: 4] = alarm_t(q->iv->lastAlarm[stschan].code, q->iv->lastAlarm[stschan].start,q->ts);
|
||||
q->iv->lastAlarm[stschan] = alarm_t(prntsts, q->ts,0);
|
||||
q->iv->alarmCnt = q->iv->type == INV_TYPE_2CH ? 3 : 5;
|
||||
} else if ( (q->iv->type == INV_TYPE_1CH) && ( (statusMi != 3)
|
||||
|| ((q->iv->lastAlarm[stschan].code) && (statusMi == 3) && (q->iv->lastAlarm[stschan].code != 1)))
|
||||
) {
|
||||
q->iv->lastAlarm[stschan] = alarm_t(q->iv->lastAlarm[0].code, q->iv->lastAlarm[0].start,q->ts);
|
||||
} else if (q->iv->type == INV_TYPE_1CH)
|
||||
stsok = true;
|
||||
|
||||
q->iv->alarmLastId = prntsts; //iv->alarmMesIndex;
|
||||
|
||||
if (q->iv->alarmCnt > 1) { //more than one channel
|
||||
for (uint8_t ch = 0; ch < (q->iv->alarmCnt); ++ch) { //start with 1
|
||||
if (q->iv->lastAlarm[ch].code == 1) {
|
||||
stsok = true;
|
||||
break;
|
||||
if(!oldState) { // initial zero value? => just write this channel to main state and raise changed flags
|
||||
changedStatus = true;
|
||||
q->iv->alarmCnt = 1; // minimum...
|
||||
} else {
|
||||
//sth is or was wrong?
|
||||
if (q->iv->type == INV_TYPE_1CH) {
|
||||
changedStatus = true;
|
||||
if(q->iv->alarmCnt == 2) // we had sth. other than "producing" in the past
|
||||
q->iv->lastAlarm[1].end = q->ts;
|
||||
else { // copy old state and mark as ended
|
||||
q->iv->lastAlarm[1] = alarm_t(q->iv->lastAlarm[0].code, q->iv->lastAlarm[0].start,q->ts);
|
||||
q->iv->alarmCnt = 2;
|
||||
}
|
||||
} else if((prntsts != 1) || (q->iv->alarmCnt > 1) ) { // we had sth. other than "producing" in the past in at least one channel (2 and 4 ch types)
|
||||
if (q->iv->alarmCnt == 1)
|
||||
q->iv->alarmCnt = (q->iv->type == INV_TYPE_2CH) ? 5 : 9;
|
||||
if(q->iv->lastAlarm[stschan].code != prntsts) { // changed?
|
||||
changedStatus = true;
|
||||
if(q->iv->lastAlarm[stschan].code) // copy old data and mark as ended (if any)
|
||||
q->iv->lastAlarm[(stschan + (q->iv->type==INV_TYPE_2CH ? 2 : 4))] = alarm_t(q->iv->lastAlarm[stschan].code, q->iv->lastAlarm[stschan].start,q->ts);
|
||||
q->iv->lastAlarm[stschan] = alarm_t(prntsts, q->ts,0);
|
||||
}
|
||||
if(changedStatus) {
|
||||
for (uint8_t i = 1; i <= q->iv->channels; i++) { //start with 1
|
||||
if (q->iv->lastAlarm[i].code == 1) {
|
||||
stsok = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!stsok) {
|
||||
q->iv->setValue(q->iv->getPosByChFld(0, FLD_EVT, rec), rec, prntsts);
|
||||
q->iv->lastAlarm[0] = alarm_t(prntsts, q->ts, 0);
|
||||
}
|
||||
if (changedStatus || !stsok) {
|
||||
rec->ts = q->ts;
|
||||
rec->mqttSentStatus = MqttSentStatus::NEW_DATA;
|
||||
q->iv->alarmLastId = prntsts; //iv->alarmMesIndex;
|
||||
if (NULL != mCbAlarm)
|
||||
(mCbAlarm)(q->iv);
|
||||
if(*mSerialDebug) {
|
||||
DPRINT(DBG_WARN, F("New state on CH"));
|
||||
DBGPRINT(String(stschan)); DBGPRINT(F(" ("));
|
||||
DBGPRINT(String(prntsts)); DBGPRINT(F("): "));
|
||||
DBGPRINTLN(q->iv->getAlarmStr(prntsts));
|
||||
}
|
||||
if(!q->iv->miMultiParts)
|
||||
q->iv->miMultiParts = 1; // indicate we got status info (1+2 ch types)
|
||||
}
|
||||
|
||||
if (!stsok) {
|
||||
q->iv->setValue(q->iv->getPosByChFld(0, FLD_EVT, rec), rec, prntsts);
|
||||
q->iv->lastAlarm[0] = alarm_t(prntsts, q->ts, 0);
|
||||
rec->ts = q->ts;
|
||||
rec->mqttSentStatus = MqttSentStatus::NEW_DATA;
|
||||
}
|
||||
|
||||
if (q->iv->alarmMesIndex < rec->record[q->iv->getPosByChFld(0, FLD_EVT, rec)]) {
|
||||
|
@ -933,27 +957,30 @@ class Communication : public CommQueue<> {
|
|||
DBGPRINTLN(String(q->iv->alarmMesIndex));
|
||||
}
|
||||
}
|
||||
if(!q->iv->miMultiParts)
|
||||
q->iv->miMultiParts = 1; // indicate we got status info (1+2 ch types)
|
||||
}
|
||||
|
||||
|
||||
void miComplete(Inverter<> *iv) {
|
||||
if (*mSerialDebug) {
|
||||
DPRINT_IVID(DBG_INFO, iv->id);
|
||||
DBGPRINTLN(F("got all data msgs"));
|
||||
}
|
||||
|
||||
if (iv->mGetLossInterval >= AHOY_GET_LOSS_INTERVAL) { // initially mIvRxCnt = mIvTxCnt = 0
|
||||
iv->mGetLossInterval = 1;
|
||||
iv->radioStatistics.ivSent = iv->mIvRxCnt + iv->mDtuTxCnt; // iv->mIvRxCnt is the nr. of additional answer frames, default we expect one frame per request
|
||||
iv->radioStatistics.ivLoss = iv->radioStatistics.ivSent - iv->mDtuRxCnt; // this is what we didn't receive
|
||||
iv->radioStatistics.dtuLoss = iv->mIvTxCnt; // this is somehow the requests w/o answers in that periode
|
||||
iv->radioStatistics.dtuSent = iv->mDtuTxCnt;
|
||||
if (mSerialDebug) {
|
||||
if (*mSerialDebug) {
|
||||
DPRINT_IVID(DBG_INFO, iv->id);
|
||||
DBGPRINTLN("DTU loss: " +
|
||||
String (iv->radioStatistics.ivLoss) + "/" +
|
||||
String (iv->radioStatistics.ivSent) + " frames for " +
|
||||
String (iv->radioStatistics.dtuSent) + " requests");
|
||||
DBGPRINT(F("DTU loss: ") +
|
||||
String (iv->radioStatistics.ivLoss) + F("/") +
|
||||
String (iv->radioStatistics.ivSent) + F(" frames for ") +
|
||||
String (iv->radioStatistics.dtuSent) + F(" requests"));
|
||||
if(iv->mAckCount) {
|
||||
DBGPRINT(F(". ACKs: "));
|
||||
DBGPRINTLN(String(iv->mAckCount));
|
||||
iv->mAckCount = 0;
|
||||
} else
|
||||
DBGPRINTLN(F(""));
|
||||
}
|
||||
iv->mIvRxCnt = 0; // start new interval, iVRxCnt is abused to collect additional possible frames
|
||||
iv->mIvTxCnt = 0; // start new interval, iVTxCnt is abused to collect nr. of unanswered requests
|
||||
|
@ -971,10 +998,8 @@ class Communication : public CommQueue<> {
|
|||
ac_pow += iv->getValue(iv->getPosByChFld(1, FLD_PDC, rec), rec);
|
||||
} else {
|
||||
for(uint8_t i = 1; i <= iv->channels; i++) {
|
||||
if ((!iv->lastAlarm[i].code) || (iv->lastAlarm[i].code == 1)) {
|
||||
uint8_t pos = iv->getPosByChFld(i, FLD_PDC, rec);
|
||||
ac_pow += iv->getValue(pos, rec);
|
||||
}
|
||||
if ((!iv->lastAlarm[i].code) || (iv->lastAlarm[i].code == 1))
|
||||
ac_pow += iv->getValue(iv->getPosByChFld(i, FLD_PDC, rec), rec);
|
||||
}
|
||||
}
|
||||
ac_pow = (int) (ac_pow*9.5);
|
||||
|
@ -1002,17 +1027,17 @@ class Communication : public CommQueue<> {
|
|||
|
||||
private:
|
||||
States mState = States::RESET;
|
||||
uint32_t *mTimestamp;
|
||||
bool *mPrivacyMode, *mSerialDebug, *mPrintWholeTrace;
|
||||
uint16_t *mInverterGap;
|
||||
uint32_t *mTimestamp = nullptr;
|
||||
bool *mPrivacyMode = nullptr, *mSerialDebug = nullptr, *mPrintWholeTrace = nullptr;
|
||||
TimeMonitor mWaitTime = TimeMonitor(0, true); // start as expired (due to code in RESET state)
|
||||
std::array<frame_t, MAX_PAYLOAD_ENTRIES> mLocalBuf;
|
||||
bool mFirstTry = false; // see, if we should do a second try
|
||||
bool mIsRetransmit = false; // we already had waited one complete cycle
|
||||
uint8_t mMaxFrameId;
|
||||
bool mFirstTry = false; // see, if we should do a second try
|
||||
bool mCompleteRetry = false; // remember if we did request a complete retransmission
|
||||
bool mIsRetransmit = false; // we already had waited one complete cycle
|
||||
uint8_t mMaxFrameId = 0;
|
||||
uint8_t mFramesExpected = 12; // 0x8c was highest last frame for alarm data
|
||||
uint16_t mTimeout = 0; // calculating that once should be ok
|
||||
uint8_t mPayload[MAX_BUFFER];
|
||||
std::array<uint8_t, MAX_BUFFER> mPayload;
|
||||
payloadListenerType mCbPayload = NULL;
|
||||
powerLimitAckListenerType mCbPwrAck = NULL;
|
||||
alarmListenerType mCbAlarm = NULL;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 2023 Ahoy, https://github.com/lumpapu/ahoy
|
||||
// 2024 Ahoy, https://github.com/lumpapu/ahoy
|
||||
// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/4.0/deed
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
|
@ -23,8 +23,8 @@
|
|||
class Heuristic {
|
||||
public:
|
||||
uint8_t getTxCh(Inverter<> *iv) {
|
||||
if((IV_HMS == iv->ivGen) || (IV_HMT == iv->ivGen))
|
||||
return 0; // not used for these inverter types
|
||||
if(iv->ivRadioType != INV_RADIO_TYPE_NRF)
|
||||
return 0; // not used for other than nRF inverter types
|
||||
|
||||
HeuristicInv *ih = &iv->heuristics;
|
||||
|
||||
|
@ -38,6 +38,8 @@ class Heuristic {
|
|||
ih->txRfChId = curId;
|
||||
curId = (curId + 1) % RF_MAX_CHANNEL_ID;
|
||||
}
|
||||
if(ih->txRfQuality[ih->txRfChId] == RF_MIN_QUALTIY) // all channels are bad, reset...
|
||||
ih->clear();
|
||||
|
||||
if(ih->testPeriodSendCnt < 0xff)
|
||||
ih->testPeriodSendCnt++;
|
||||
|
@ -66,10 +68,12 @@ class Heuristic {
|
|||
ih->testPeriodFailCnt = 0;
|
||||
}
|
||||
|
||||
iv->radio->mTxRetriesNext = getIvRetries(iv);
|
||||
|
||||
return id2Ch(ih->txRfChId);
|
||||
}
|
||||
|
||||
void evalTxChQuality(Inverter<> *iv, bool crcPass, uint8_t retransmits, uint8_t rxFragments) {
|
||||
void evalTxChQuality(Inverter<> *iv, bool crcPass, uint8_t retransmits, uint8_t rxFragments, bool quotaMissed = false) {
|
||||
HeuristicInv *ih = &iv->heuristics;
|
||||
|
||||
#if (DBG_DEBUG == DEBUG_LEVEL)
|
||||
|
@ -82,8 +86,10 @@ class Heuristic {
|
|||
DBGPRINT(", ");
|
||||
DBGPRINTLN(String(ih->lastRxFragments));
|
||||
#endif
|
||||
if(quotaMissed) // we got not enough frames on this attempt, but iv was answering
|
||||
updateQuality(ih, (rxFragments > 3 ? RF_TX_CHAN_QUALITY_GOOD : (rxFragments > 1 ? RF_TX_CHAN_QUALITY_OK : RF_TX_CHAN_QUALITY_LOW)));
|
||||
|
||||
if(ih->lastRxFragments == rxFragments) {
|
||||
else if(ih->lastRxFragments == rxFragments) {
|
||||
if(crcPass)
|
||||
updateQuality(ih, RF_TX_CHAN_QUALITY_GOOD);
|
||||
else if(!retransmits || isNewTxCh(ih)) { // nothing received: send probably lost
|
||||
|
@ -130,7 +136,7 @@ class Heuristic {
|
|||
ih->lastRxFragments = rxFragments;
|
||||
}
|
||||
|
||||
void printStatus(Inverter<> *iv) {
|
||||
void printStatus(const Inverter<> *iv) {
|
||||
DPRINT_IVID(DBG_INFO, iv->id);
|
||||
DBGPRINT(F("Radio infos:"));
|
||||
if((IV_HMS != iv->ivGen) && (IV_HMT != iv->ivGen)) {
|
||||
|
@ -147,7 +153,7 @@ class Heuristic {
|
|||
DBGPRINT(F(", f: "));
|
||||
DBGPRINT(String(iv->radioStatistics.rxFail));
|
||||
DBGPRINT(F(", n: "));
|
||||
DBGPRINT(String(iv->radioStatistics.rxFailNoAnser));
|
||||
DBGPRINT(String(iv->radioStatistics.rxFailNoAnswer));
|
||||
DBGPRINT(F(" | p: ")); // better debugging for helpers...
|
||||
if((IV_HMS == iv->ivGen) || (IV_HMT == iv->ivGen))
|
||||
DBGPRINTLN(String(iv->config->powerLevel-10));
|
||||
|
@ -155,8 +161,50 @@ class Heuristic {
|
|||
DBGPRINTLN(String(iv->config->powerLevel));
|
||||
}
|
||||
|
||||
uint8_t getIvRetries(const Inverter<> *iv) const {
|
||||
if(iv->heuristics.rxSpeeds[0])
|
||||
return RETRIES_VERYFAST_IV;
|
||||
if(iv->heuristics.rxSpeeds[1])
|
||||
return RETRIES_FAST_IV;
|
||||
return 15;
|
||||
}
|
||||
|
||||
void setIvRetriesGood(Inverter<> *iv, bool veryGood) {
|
||||
if(iv->ivRadioType != INV_RADIO_TYPE_NRF)
|
||||
return; // not used for other than nRF inverter types
|
||||
|
||||
if(iv->heuristics.rxSpeedCnt[veryGood] > 9)
|
||||
return;
|
||||
iv->heuristics.rxSpeedCnt[veryGood]++;
|
||||
iv->heuristics.rxSpeeds[veryGood] = true;
|
||||
}
|
||||
|
||||
void setIvRetriesBad(Inverter<> *iv) {
|
||||
if(iv->ivRadioType != INV_RADIO_TYPE_NRF)
|
||||
return; // not used for other than nRF inverter types
|
||||
|
||||
if(iv->heuristics.rxSpeedCnt[0]) {
|
||||
iv->heuristics.rxSpeedCnt[0]--;
|
||||
return;
|
||||
}
|
||||
if(iv->heuristics.rxSpeeds[0]) {
|
||||
iv->heuristics.rxSpeeds[0] = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if(iv->heuristics.rxSpeedCnt[1]) {
|
||||
iv->heuristics.rxSpeedCnt[1]--;
|
||||
return;
|
||||
}
|
||||
if(iv->heuristics.rxSpeeds[1]) {
|
||||
iv->heuristics.rxSpeeds[1] = false;
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
private:
|
||||
bool isNewTxCh(HeuristicInv *ih) {
|
||||
bool isNewTxCh(const HeuristicInv *ih) const {
|
||||
return ih->txRfChId != ih->lastBestTxChId;
|
||||
}
|
||||
|
||||
|
@ -169,18 +217,12 @@ class Heuristic {
|
|||
}
|
||||
|
||||
inline uint8_t id2Ch(uint8_t id) {
|
||||
switch(id) {
|
||||
case 0: return 3;
|
||||
case 1: return 23;
|
||||
case 2: return 40;
|
||||
case 3: return 61;
|
||||
case 4: return 75;
|
||||
}
|
||||
return 3; // standard
|
||||
if (id < RF_MAX_CHANNEL_ID)
|
||||
return mChList[id];
|
||||
else
|
||||
return 3; // standard
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t mChList[5] = {03, 23, 40, 61, 75};
|
||||
uint8_t mChList[RF_MAX_CHANNEL_ID] = {03, 23, 40, 61, 75};
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 2023 Ahoy, https://github.com/lumpapu/ahoy
|
||||
// 2024 Ahoy, https://github.com/lumpapu/ahoy
|
||||
// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/4.0/deed
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
|
@ -14,7 +14,27 @@
|
|||
class HeuristicInv {
|
||||
public:
|
||||
HeuristicInv() {
|
||||
memset(txRfQuality, -6, RF_MAX_CHANNEL_ID);
|
||||
clear();
|
||||
}
|
||||
|
||||
void clear() {
|
||||
memset(txRfQuality, 0, RF_MAX_CHANNEL_ID);
|
||||
txRfChId = 0;
|
||||
lastBestTxChId = 0;
|
||||
testPeriodSendCnt = 0;
|
||||
testPeriodFailCnt = 0;
|
||||
testChId = 0;
|
||||
saveOldTestQuality = -6;
|
||||
lastRxFragments = 0;
|
||||
|
||||
rxSpeeds[0] = false;
|
||||
rxSpeeds[1] = false;
|
||||
rxSpeedCnt[0] = 0;
|
||||
rxSpeedCnt[1] = 0;
|
||||
}
|
||||
|
||||
bool isTxAtMax(void) const {
|
||||
return (RF_MAX_QUALITY == txRfQuality[txRfChId]);
|
||||
}
|
||||
|
||||
public:
|
||||
|
@ -27,6 +47,8 @@ class HeuristicInv {
|
|||
uint8_t testChId = 0;
|
||||
int8_t saveOldTestQuality = -6;
|
||||
uint8_t lastRxFragments = 0;
|
||||
bool rxSpeeds[2] = {false, false}; // is inverter responding very fast respective fast?
|
||||
uint8_t rxSpeedCnt[2] = {0, 0}; // count how many messages had been received very fast respective fast (10 max)
|
||||
};
|
||||
|
||||
#endif /*__HEURISTIC_INV_H__*/
|
||||
|
|
|
@ -76,23 +76,23 @@ enum {CMD_CALC = 0xffff};
|
|||
enum {CH0 = 0, CH1, CH2, CH3, CH4, CH5, CH6};
|
||||
|
||||
enum {INV_TYPE_1CH = 0, INV_TYPE_2CH, INV_TYPE_4CH, INV_TYPE_6CH};
|
||||
enum {INV_RADIO_TYPE_NRF = 0, INV_RADIO_TYPE_CMT};
|
||||
enum {INV_RADIO_TYPE_UNKNOWN = 0, INV_RADIO_TYPE_NRF, INV_RADIO_TYPE_CMT};
|
||||
|
||||
#define WORK_FREQ_KHZ 865000 // desired work frequency between DTU and
|
||||
// inverter in kHz
|
||||
#define HOY_BASE_FREQ_KHZ 860000 // in kHz
|
||||
#define HOY_MAX_FREQ_KHZ 923500 // 0xFE * 250kHz + Base_freq
|
||||
#define HOY_BOOT_FREQ_KHZ 868000 // Hoymiles boot/init frequency after power up inverter
|
||||
#define FREQ_STEP_KHZ 250 // channel step size in kHz
|
||||
#define FREQ_WARN_MIN_KHZ 863000 // for EU 863 - 870 MHz is allowed
|
||||
#define FREQ_WARN_MAX_KHZ 870000 // for EU 863 - 870 MHz is allowed
|
||||
|
||||
#define DURATION_ONEFRAME 50 // timeout parameter for each expected frame (ms)
|
||||
//#define DURATION_RESERVE {90,120} // timeout parameter to still wait after last expected frame (ms)
|
||||
#define DURATION_TXFRAME 85 // timeout parameter for first transmission and first expected frame (time to first channel switch from tx start!) (ms)
|
||||
#define DURATION_LISTEN_MIN 5 // time to stay at least on a listening channel (ms)
|
||||
#define DURATION_PAUSE_LASTFR 45 // how long to pause after last frame (ms)
|
||||
const uint8_t duration_reserve[2] = {115,115};
|
||||
const uint8_t duration_reserve[2] = {65, 115};
|
||||
|
||||
#define LIMIT_FAST_IV 85 // time limit to qualify an inverter as very fast answering inverter
|
||||
#define LIMIT_VERYFAST_IV 70 // time limit to qualify an inverter as very fast answering inverter
|
||||
#define LIMIT_FAST_IV_MI 35 // time limit to qualify a MI type inverter as fast answering inverter
|
||||
#define LIMIT_VERYFAST_IV_MI 25 // time limit to qualify a MI type inverter as very fast answering inverter
|
||||
#define RETRIES_FAST_IV 12 // how often shall a message be automatically retransmitted by the nRF (fast answering inverter)
|
||||
#define RETRIES_VERYFAST_IV 9 // how often shall a message be automatically retransmitted by the nRF (very fast answering inverter)
|
||||
|
||||
|
||||
typedef struct {
|
||||
uint8_t fieldId; // field id
|
||||
|
|
|
@ -81,12 +81,12 @@ enum class InverterStatus : uint8_t {
|
|||
|
||||
template<class T=float>
|
||||
struct record_t {
|
||||
byteAssign_t* assign; // assignment of bytes in payload
|
||||
uint8_t length; // length of the assignment list
|
||||
T *record; // data pointer
|
||||
uint32_t ts; // timestamp of last received payload
|
||||
uint8_t pyldLen; // expected payload length for plausibility check
|
||||
MqttSentStatus mqttSentStatus; // indicates the current MqTT sent status
|
||||
byteAssign_t* assign = nullptr; // assignment of bytes in payload
|
||||
uint8_t length = 0; // length of the assignment list
|
||||
T *record = nullptr; // data pointer
|
||||
uint32_t ts = 0; // timestamp of last received payload
|
||||
uint8_t pyldLen = 0; // expected payload length for plausibility check
|
||||
MqttSentStatus mqttSentStatus = MqttSentStatus:: NEW_DATA; // indicates the current MqTT sent status
|
||||
};
|
||||
|
||||
struct alarm_t {
|
||||
|
@ -113,124 +113,108 @@ const calcFunc_t<T> calcFunctions[] = {
|
|||
template <class REC_TYP>
|
||||
class Inverter {
|
||||
public:
|
||||
uint8_t ivGen; // generation of inverter (HM / MI)
|
||||
uint8_t ivRadioType; // refers to used radio (nRF24 / CMT)
|
||||
cfgIv_t *config; // stored settings
|
||||
uint8_t id; // unique id
|
||||
uint8_t type; // integer which refers to inverter type
|
||||
uint16_t alarmMesIndex; // Last recorded Alarm Message Index
|
||||
uint16_t powerLimit[2]; // limit power output (multiplied by 10)
|
||||
float actPowerLimit; // actual power limit
|
||||
bool powerLimitAck; // acknowledged power limit (default: false)
|
||||
uint8_t devControlCmd; // carries the requested cmd
|
||||
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> recordHwInfo; // structure for simple (hardware) info values
|
||||
record_t<REC_TYP> recordConfig; // structure for system config values
|
||||
record_t<REC_TYP> recordAlarm; // structure for alarm values
|
||||
bool isConnected; // shows if inverter was successfully identified (fw version and hardware info)
|
||||
InverterStatus status; // indicates the current inverter status
|
||||
std::array<alarm_t, 10> lastAlarm; // holds last 10 alarms
|
||||
int8_t rssi; // RSSI
|
||||
uint16_t alarmCnt; // counts the total number of occurred alarms
|
||||
uint16_t alarmLastId; // lastId which was received
|
||||
uint8_t mCmd; // holds the command to send
|
||||
bool mGotFragment; // shows if inverter has sent at least one fragment
|
||||
uint8_t miMultiParts; // helper info for MI multiframe msgs
|
||||
uint8_t outstandingFrames; // helper info to count difference between expected and received frames
|
||||
uint8_t curFrmCnt; // count received frames in current loop
|
||||
bool mGotLastMsg; // shows if inverter has already finished transmission cycle
|
||||
bool mIsSingleframeReq; // indicates this is a missing single frame request
|
||||
Radio *radio; // pointer to associated radio class
|
||||
statistics_t radioStatistics; // information about transmitted, failed, ... packets
|
||||
HeuristicInv heuristics; // heuristic information / logic
|
||||
uint8_t curCmtFreq; // current used CMT frequency, used to check if freq. was changed during runtime
|
||||
bool commEnabled; // 'pause night communication' sets this field to false
|
||||
uint32_t tsMaxAcPower; // holds the timestamp when the MaxAC power was seen
|
||||
|
||||
static uint32_t *timestamp; // system timestamp
|
||||
static cfgInst_t *generalConfig; // general inverter configuration from setup
|
||||
//static IApp *app; // pointer to app interface
|
||||
uint8_t ivGen = IV_UNKNOWN; // generation of inverter (HM / MI)
|
||||
uint8_t ivRadioType = INV_RADIO_TYPE_UNKNOWN; // refers to used radio (nRF24 / CMT)
|
||||
cfgIv_t *config = nullptr; // stored settings
|
||||
uint8_t id = 0; // unique id
|
||||
uint8_t type = INV_TYPE_1CH; // integer which refers to inverter type
|
||||
uint16_t alarmMesIndex = 0; // Last recorded Alarm Message Index
|
||||
uint16_t powerLimit[2] = {0xffff, AbsolutNonPersistent}; // limit power output (multiplied by 10)
|
||||
uint16_t actPowerLimit = 0xffff; // actual power limit
|
||||
bool powerLimitAck = false; // acknowledged power limit
|
||||
uint8_t devControlCmd = InitDataState; // carries the requested cmd
|
||||
serial_u radioId; // id converted to modbus
|
||||
uint8_t channels = 1; // 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> recordHwInfo; // structure for simple (hardware) info values
|
||||
record_t<REC_TYP> recordConfig; // structure for system config values
|
||||
record_t<REC_TYP> recordAlarm; // structure for alarm values
|
||||
InverterStatus status = InverterStatus::OFF; // indicates the current inverter status
|
||||
std::array<alarm_t, 10> lastAlarm; // holds last 10 alarms
|
||||
int8_t rssi = 0; // RSSI
|
||||
uint16_t alarmCnt = 0; // counts the total number of occurred alarms
|
||||
uint16_t alarmLastId = 0; // lastId which was received
|
||||
uint8_t mCmd = InitDataState; // holds the command to send
|
||||
bool mGotFragment = false; // shows if inverter has sent at least one fragment
|
||||
uint8_t miMultiParts = 0; // helper info for MI multiframe msgs
|
||||
uint8_t outstandingFrames = 0; // helper info to count difference between expected and received frames
|
||||
uint8_t curFrmCnt = 0; // count received frames in current loop
|
||||
bool mGotLastMsg = false; // shows if inverter has already finished transmission cycle
|
||||
bool mIsSingleframeReq = false; // indicates this is a missing single frame request
|
||||
Radio *radio = nullptr; // pointer to associated radio class
|
||||
statistics_t radioStatistics; // information about transmitted, failed, ... packets
|
||||
HeuristicInv heuristics; // heuristic information / logic
|
||||
uint8_t curCmtFreq = 0; // current used CMT frequency, used to check if freq. was changed during runtime
|
||||
uint32_t tsMaxAcPower = 0; // holds the timestamp when the MaxAC power was seen
|
||||
bool commEnabled = true; // 'pause night communication' sets this field to false
|
||||
|
||||
public:
|
||||
|
||||
Inverter() {
|
||||
ivGen = IV_HM;
|
||||
powerLimit[0] = 0xffff; // 6553.5 W Limit -> unlimited
|
||||
powerLimit[1] = AbsolutNonPersistent; // default power limit setting
|
||||
powerLimitAck = false;
|
||||
actPowerLimit = 0xffff; // init feedback from inverter to -1
|
||||
mDevControlRequest = false;
|
||||
devControlCmd = InitDataState;
|
||||
alarmMesIndex = 0;
|
||||
isConnected = false;
|
||||
status = InverterStatus::OFF;
|
||||
alarmCnt = 0;
|
||||
alarmLastId = 0;
|
||||
rssi = -127;
|
||||
miMultiParts = 0;
|
||||
mGotLastMsg = false;
|
||||
mCmd = InitDataState;
|
||||
mIsSingleframeReq = false;
|
||||
radio = NULL;
|
||||
commEnabled = true;
|
||||
tsMaxAcPower = 0;
|
||||
|
||||
memset(&radioStatistics, 0, sizeof(statistics_t));
|
||||
memset(heuristics.txRfQuality, -6, 5);
|
||||
|
||||
memset(mOffYD, 0, sizeof(float) * 6);
|
||||
memset(mLastYD, 0, sizeof(float) * 6);
|
||||
mGridProfile.fill(0);
|
||||
}
|
||||
|
||||
void tickSend(std::function<void(uint8_t cmd, bool isDevControl)> cb) {
|
||||
if(mDevControlRequest) {
|
||||
cb(devControlCmd, true);
|
||||
if(InverterStatus::OFF != status) {
|
||||
cb(devControlCmd, true);
|
||||
devControlCmd = InitDataState;
|
||||
} else
|
||||
DPRINTLN(DBG_WARN, F("Inverter is not avail"));
|
||||
mDevControlRequest = false;
|
||||
} else if (IV_MI != ivGen) { // HM / HMS / HMT
|
||||
mGetLossInterval++;
|
||||
if(mNextLive)
|
||||
cb(RealTimeRunData_Debug, false); // get live data
|
||||
else {
|
||||
if(actPowerLimit == 0xffff)
|
||||
cb(SystemConfigPara, false); // power limit info
|
||||
else if(InitDataState != devControlCmd) {
|
||||
cb(devControlCmd, false); // custom command which was received by API
|
||||
devControlCmd = InitDataState;
|
||||
mGetLossInterval = 1;
|
||||
} else if(0 == getFwVersion())
|
||||
cb(InverterDevInform_All, false); // get firmware version
|
||||
else if(0 == getHwVersion())
|
||||
cb(InverterDevInform_Simple, false); // get hardware version
|
||||
else if((alarmLastId != alarmMesIndex) && (alarmMesIndex != 0))
|
||||
cb(AlarmData, false); // get last alarms
|
||||
else if((0 == mGridLen) && generalConfig->readGrid) { // read grid profile
|
||||
cb(GridOnProFilePara, false);
|
||||
} else if (mGetLossInterval > AHOY_GET_LOSS_INTERVAL) { // get loss rate
|
||||
mGetLossInterval = 1;
|
||||
cb(RealTimeRunData_Debug, false); // get live data
|
||||
cb(GetLossRate, false);
|
||||
} else
|
||||
cb(RealTimeRunData_Debug, false); // get live data
|
||||
if(INV_RADIO_TYPE_NRF == ivRadioType) {
|
||||
// get live data until quality reaches maximum
|
||||
if(!heuristics.isTxAtMax()) {
|
||||
cb(RealTimeRunData_Debug, false); // get live data
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if(actPowerLimit == 0xffff) {
|
||||
cb(SystemConfigPara, false); // power limit info
|
||||
} else if(InitDataState != devControlCmd) {
|
||||
cb(devControlCmd, false); // custom command which was received by API
|
||||
devControlCmd = InitDataState;
|
||||
mGetLossInterval = 1;
|
||||
return;
|
||||
} else if(0 == getFwVersion()) {
|
||||
cb(InverterDevInform_All, false); // get firmware version
|
||||
} else if(0 == getHwVersion()) {
|
||||
cb(InverterDevInform_Simple, false); // get hardware version
|
||||
} else if((alarmLastId != alarmMesIndex) && (alarmMesIndex != 0)) {
|
||||
cb(AlarmData, false); // get last alarms
|
||||
} else if((0 == mGridLen) && generalConfig->readGrid) { // read grid profile
|
||||
cb(GridOnProFilePara, false);
|
||||
} else if (mGetLossInterval > AHOY_GET_LOSS_INTERVAL) { // get loss rate
|
||||
mGetLossInterval = 1;
|
||||
cb(RealTimeRunData_Debug, false); // get live data
|
||||
cb(GetLossRate, false);
|
||||
return;
|
||||
}
|
||||
|
||||
cb(RealTimeRunData_Debug, false); // get live data
|
||||
} else { // MI
|
||||
if(0 == getFwVersion()) {
|
||||
mIvRxCnt +=2;
|
||||
cb(0x0f, false); // get firmware version; for MI, this makes part of polling the device software and hardware version number
|
||||
} else {
|
||||
record_t<> *rec = getRecordStruct(InverterDevInform_Simple);
|
||||
if (getChannelFieldValue(CH0, FLD_PART_NUM, rec) == 0) {
|
||||
cb(0x0f, false); // hard- and firmware version for missing HW part nr, delivered by frame 1
|
||||
cb(((type == INV_TYPE_4CH) ? MI_REQ_4CH : MI_REQ_CH1), false);
|
||||
mGetLossInterval++;
|
||||
if (type != INV_TYPE_4CH)
|
||||
mIvRxCnt++; // statistics workaround...
|
||||
if(isAvailable()) {
|
||||
if(0 == getFwVersion()) {
|
||||
mIvRxCnt +=2;
|
||||
} else if((getChannelFieldValue(CH0, FLD_GRID_PROFILE_CODE, rec) == 0) && generalConfig->readGrid) // read grid profile
|
||||
cb(0x10, false); // legacy GPF command
|
||||
else {
|
||||
cb(((type == INV_TYPE_4CH) ? MI_REQ_4CH : MI_REQ_CH1), false);
|
||||
mGetLossInterval++;
|
||||
if (type != INV_TYPE_4CH)
|
||||
mIvRxCnt++; // statistics workaround...
|
||||
cb(0x0f, false); // get firmware version; for MI, this makes part of polling the device software and hardware version number
|
||||
} else {
|
||||
record_t<> *rec = getRecordStruct(InverterDevInform_Simple);
|
||||
if (getChannelFieldValue(CH0, FLD_PART_NUM, rec) == 0) {
|
||||
cb(0x0f, false); // hard- and firmware version for missing HW part nr, delivered by frame 1
|
||||
mIvRxCnt +=2;
|
||||
} else if((getChannelFieldValue(CH0, FLD_GRID_PROFILE_CODE, rec) == 0) && generalConfig->readGrid) // read grid profile
|
||||
cb(0x10, false); // legacy GPF command
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -249,15 +233,14 @@ class Inverter {
|
|||
|
||||
uint8_t getPosByChFld(uint8_t channel, uint8_t fieldId, record_t<> *rec) {
|
||||
DPRINTLN(DBG_VERBOSE, F("hmInverter.h:getPosByChFld"));
|
||||
uint8_t pos = 0;
|
||||
if(NULL != rec) {
|
||||
uint8_t pos = 0;
|
||||
for(; pos < rec->length; pos++) {
|
||||
if((rec->assign[pos].ch == channel) && (rec->assign[pos].fieldId == fieldId))
|
||||
break;
|
||||
}
|
||||
return (pos >= rec->length) ? 0xff : pos;
|
||||
}
|
||||
else
|
||||
} else
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
|
@ -266,78 +249,73 @@ class Inverter {
|
|||
}
|
||||
|
||||
const char *getFieldName(uint8_t pos, record_t<> *rec) {
|
||||
DPRINTLN(DBG_VERBOSE, F("hmInverter.h:getFieldName"));
|
||||
if(NULL != rec)
|
||||
return fields[rec->assign[pos].fieldId];
|
||||
return notAvail;
|
||||
}
|
||||
|
||||
const char *getUnit(uint8_t pos, record_t<> *rec) {
|
||||
DPRINTLN(DBG_VERBOSE, F("hmInverter.h:getUnit"));
|
||||
if(NULL != rec)
|
||||
return units[rec->assign[pos].unitId];
|
||||
return notAvail;
|
||||
}
|
||||
|
||||
uint8_t getChannel(uint8_t pos, record_t<> *rec) {
|
||||
DPRINTLN(DBG_VERBOSE, F("hmInverter.h:getChannel"));
|
||||
if(NULL != rec)
|
||||
return rec->assign[pos].ch;
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool setDevControlRequest(uint8_t cmd) {
|
||||
if(isConnected) {
|
||||
if(InverterStatus::OFF != status) {
|
||||
mDevControlRequest = true;
|
||||
devControlCmd = cmd;
|
||||
//app->triggerTickSend(); // done in RestApi.h, because of "chicken-and-egg problem ;-)"
|
||||
}
|
||||
return isConnected;
|
||||
return (InverterStatus::OFF != status);
|
||||
}
|
||||
|
||||
bool setDevCommand(uint8_t cmd) {
|
||||
if(isConnected)
|
||||
if(InverterStatus::OFF != status)
|
||||
devControlCmd = cmd;
|
||||
return isConnected;
|
||||
return (InverterStatus::OFF != status);
|
||||
}
|
||||
|
||||
void addValue(uint8_t pos, uint8_t buf[], record_t<> *rec) {
|
||||
void addValue(uint8_t pos, const uint8_t buf[], record_t<> *rec) {
|
||||
DPRINTLN(DBG_VERBOSE, F("hmInverter.h:addValue"));
|
||||
if(NULL != rec) {
|
||||
uint8_t ptr = rec->assign[pos].start;
|
||||
uint8_t end = ptr + rec->assign[pos].num;
|
||||
uint16_t div = rec->assign[pos].div;
|
||||
|
||||
if(NULL != rec) {
|
||||
if(CMD_CALC != div) {
|
||||
uint32_t val = 0;
|
||||
do {
|
||||
val <<= 8;
|
||||
val |= buf[ptr];
|
||||
} while(++ptr != end);
|
||||
if ((FLD_T == rec->assign[pos].fieldId) || (FLD_Q == rec->assign[pos].fieldId) || (FLD_PF == rec->assign[pos].fieldId)) {
|
||||
// temperature, Qvar, and power factor are a signed values
|
||||
rec->record[pos] = ((REC_TYP)((int16_t)val)) / (REC_TYP)(div);
|
||||
} else if (FLD_YT == rec->assign[pos].fieldId) {
|
||||
rec->record[pos] = ((REC_TYP)(val) / (REC_TYP)(div) * generalConfig->yieldEffiency) + ((REC_TYP)config->yieldCor[rec->assign[pos].ch-1]);
|
||||
} else if (FLD_YD == rec->assign[pos].fieldId) {
|
||||
float actYD = (REC_TYP)(val) / (REC_TYP)(div) * generalConfig->yieldEffiency;
|
||||
uint8_t idx = rec->assign[pos].ch - 1;
|
||||
if (mLastYD[idx] > actYD)
|
||||
mOffYD[idx] += mLastYD[idx];
|
||||
mLastYD[idx] = actYD;
|
||||
rec->record[pos] = mOffYD[idx] + actYD;
|
||||
} else {
|
||||
if ((REC_TYP)(div) > 1)
|
||||
rec->record[pos] = (REC_TYP)(val) / (REC_TYP)(div);
|
||||
else
|
||||
rec->record[pos] = (REC_TYP)(val);
|
||||
}
|
||||
if(CMD_CALC != div) {
|
||||
uint32_t val = 0;
|
||||
do {
|
||||
val <<= 8;
|
||||
val |= buf[ptr];
|
||||
} while(++ptr != end);
|
||||
|
||||
if ((FLD_T == rec->assign[pos].fieldId) || (FLD_Q == rec->assign[pos].fieldId) || (FLD_PF == rec->assign[pos].fieldId)) {
|
||||
// temperature, Qvar, and power factor are a signed values
|
||||
rec->record[pos] = ((REC_TYP)((int16_t)val)) / (REC_TYP)(div);
|
||||
} else if (FLD_YT == rec->assign[pos].fieldId) {
|
||||
rec->record[pos] = ((REC_TYP)(val) / (REC_TYP)(div)) + ((REC_TYP)config->yieldCor[rec->assign[pos].ch-1]);
|
||||
} else if (FLD_YD == rec->assign[pos].fieldId) {
|
||||
float actYD = (REC_TYP)(val) / (REC_TYP)(div);
|
||||
uint8_t idx = rec->assign[pos].ch - 1;
|
||||
if (mLastYD[idx] > actYD)
|
||||
mOffYD[idx] += mLastYD[idx];
|
||||
mLastYD[idx] = actYD;
|
||||
rec->record[pos] = mOffYD[idx] + actYD;
|
||||
} else {
|
||||
if ((REC_TYP)(div) > 1)
|
||||
rec->record[pos] = (REC_TYP)(val) / (REC_TYP)(div);
|
||||
else
|
||||
rec->record[pos] = (REC_TYP)(val);
|
||||
}
|
||||
}
|
||||
|
||||
if(rec == &recordMeas) {
|
||||
mNextLive = false; // live data received
|
||||
DPRINTLN(DBG_VERBOSE, "add real time");
|
||||
// get last alarm message index and save it in the inverter object
|
||||
if (getPosByChFld(0, FLD_EVT, rec) == pos) {
|
||||
|
@ -348,13 +326,10 @@ class Inverter {
|
|||
DBGPRINTLN(String(alarmMesIndex));
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
mNextLive = true;
|
||||
} else {
|
||||
if (rec->assign == InfoAssignment) {
|
||||
DPRINTLN(DBG_DEBUG, "add info");
|
||||
// eg. fw version ...
|
||||
isConnected = true;
|
||||
} else if (rec->assign == SimpleInfoAssignment) {
|
||||
DPRINTLN(DBG_DEBUG, "add simple info");
|
||||
// eg. hw version ...
|
||||
|
@ -370,8 +345,7 @@ class Inverter {
|
|||
} else
|
||||
DPRINTLN(DBG_WARN, F("add with unknown assignment"));
|
||||
}
|
||||
}
|
||||
else
|
||||
} else
|
||||
DPRINTLN(DBG_ERROR, F("addValue: assignment not found with cmd 0x"));
|
||||
|
||||
// update status state-machine
|
||||
|
@ -389,18 +363,18 @@ class Inverter {
|
|||
}
|
||||
|
||||
REC_TYP getChannelFieldValue(uint8_t channel, uint8_t fieldId, record_t<> *rec) {
|
||||
uint8_t pos = 0;
|
||||
if(NULL != rec) {
|
||||
uint8_t pos = 0;
|
||||
for(; pos < rec->length; pos++) {
|
||||
if((rec->assign[pos].ch == channel) && (rec->assign[pos].fieldId == fieldId))
|
||||
break;
|
||||
}
|
||||
|
||||
if(pos >= rec->length)
|
||||
return 0;
|
||||
|
||||
return rec->record[pos];
|
||||
}
|
||||
else
|
||||
} else
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -431,28 +405,25 @@ class Inverter {
|
|||
bool isAvailable() {
|
||||
bool avail = false;
|
||||
|
||||
if((recordMeas.ts == 0) && (recordInfo.ts == 0) && (recordConfig.ts == 0) && (recordAlarm.ts == 0))
|
||||
if(recordMeas.ts == 0)
|
||||
return false;
|
||||
|
||||
if((*timestamp - recordMeas.ts) < INVERTER_INACT_THRES_SEC)
|
||||
avail = true;
|
||||
if((*timestamp - recordInfo.ts) < INVERTER_INACT_THRES_SEC)
|
||||
avail = true;
|
||||
if((*timestamp - recordConfig.ts) < INVERTER_INACT_THRES_SEC)
|
||||
avail = true;
|
||||
if((*timestamp - recordAlarm.ts) < INVERTER_INACT_THRES_SEC)
|
||||
if(((*timestamp) - recordMeas.ts) < INVERTER_INACT_THRES_SEC)
|
||||
avail = true;
|
||||
|
||||
if(avail) {
|
||||
if(status < InverterStatus::PRODUCING)
|
||||
status = InverterStatus::STARTING;
|
||||
} else {
|
||||
if((*timestamp - recordMeas.ts) > INVERTER_OFF_THRES_SEC) {
|
||||
status = InverterStatus::OFF;
|
||||
actPowerLimit = 0xffff; // power limit will be read once inverter becomes available
|
||||
alarmMesIndex = 0;
|
||||
}
|
||||
else
|
||||
if(((*timestamp) - recordMeas.ts) > INVERTER_OFF_THRES_SEC) {
|
||||
if(status != InverterStatus::OFF) {
|
||||
status = InverterStatus::OFF;
|
||||
actPowerLimit = 0xffff; // power limit will be read once inverter becomes available
|
||||
alarmMesIndex = 0;
|
||||
if(INV_RADIO_TYPE_NRF == ivRadioType)
|
||||
heuristics.clear();
|
||||
}
|
||||
} else
|
||||
status = InverterStatus::WAS_ON;
|
||||
}
|
||||
|
||||
|
@ -470,6 +441,7 @@ class Inverter {
|
|||
else if(InverterStatus::PRODUCING == status)
|
||||
status = InverterStatus::WAS_PRODUCING;
|
||||
}
|
||||
|
||||
return producing;
|
||||
}
|
||||
|
||||
|
@ -527,11 +499,11 @@ class Inverter {
|
|||
if (INV_TYPE_1CH == type) {
|
||||
if((IV_HM == ivGen) || (IV_MI == ivGen)) {
|
||||
rec->length = (uint8_t)(HM1CH_LIST_LEN);
|
||||
rec->assign = (byteAssign_t *)hm1chAssignment;
|
||||
rec->assign = reinterpret_cast<byteAssign_t*>(const_cast<byteAssign_t*>(hm1chAssignment));
|
||||
rec->pyldLen = HM1CH_PAYLOAD_LEN;
|
||||
} else if(IV_HMS == ivGen) {
|
||||
rec->length = (uint8_t)(HMS1CH_LIST_LEN);
|
||||
rec->assign = (byteAssign_t *)hms1chAssignment;
|
||||
rec->assign = reinterpret_cast<byteAssign_t*>(const_cast<byteAssign_t*>(hms1chAssignment));
|
||||
rec->pyldLen = HMS1CH_PAYLOAD_LEN;
|
||||
}
|
||||
channels = 1;
|
||||
|
@ -539,11 +511,11 @@ class Inverter {
|
|||
else if (INV_TYPE_2CH == type) {
|
||||
if((IV_HM == ivGen) || (IV_MI == ivGen)) {
|
||||
rec->length = (uint8_t)(HM2CH_LIST_LEN);
|
||||
rec->assign = (byteAssign_t *)hm2chAssignment;
|
||||
rec->assign = reinterpret_cast<byteAssign_t*>(const_cast<byteAssign_t*>(hm2chAssignment));
|
||||
rec->pyldLen = HM2CH_PAYLOAD_LEN;
|
||||
} else if(IV_HMS == ivGen) {
|
||||
rec->length = (uint8_t)(HMS2CH_LIST_LEN);
|
||||
rec->assign = (byteAssign_t *)hms2chAssignment;
|
||||
rec->assign = reinterpret_cast<byteAssign_t*>(const_cast<byteAssign_t*>(hms2chAssignment));
|
||||
rec->pyldLen = HMS2CH_PAYLOAD_LEN;
|
||||
}
|
||||
channels = 2;
|
||||
|
@ -551,18 +523,18 @@ class Inverter {
|
|||
else if (INV_TYPE_4CH == type) {
|
||||
if((IV_HM == ivGen) || (IV_MI == ivGen)) {
|
||||
rec->length = (uint8_t)(HM4CH_LIST_LEN);
|
||||
rec->assign = (byteAssign_t *)hm4chAssignment;
|
||||
rec->assign = reinterpret_cast<byteAssign_t*>(const_cast<byteAssign_t*>(hm4chAssignment));
|
||||
rec->pyldLen = HM4CH_PAYLOAD_LEN;
|
||||
} else if(IV_HMS == ivGen) {
|
||||
rec->length = (uint8_t)(HMS4CH_LIST_LEN);
|
||||
rec->assign = (byteAssign_t *)hms4chAssignment;
|
||||
rec->assign = reinterpret_cast<byteAssign_t*>(const_cast<byteAssign_t*>(hms4chAssignment));
|
||||
rec->pyldLen = HMS4CH_PAYLOAD_LEN;
|
||||
}
|
||||
channels = 4;
|
||||
}
|
||||
else if (INV_TYPE_6CH == type) {
|
||||
rec->length = (uint8_t)(HMT6CH_LIST_LEN);
|
||||
rec->assign = (byteAssign_t *)hmt6chAssignment;
|
||||
rec->assign = reinterpret_cast<byteAssign_t*>(const_cast<byteAssign_t*>(hmt6chAssignment));
|
||||
rec->pyldLen = HMT6CH_PAYLOAD_LEN;
|
||||
channels = 6;
|
||||
}
|
||||
|
@ -575,22 +547,22 @@ class Inverter {
|
|||
break;
|
||||
case InverterDevInform_All:
|
||||
rec->length = (uint8_t)(HMINFO_LIST_LEN);
|
||||
rec->assign = (byteAssign_t *)InfoAssignment;
|
||||
rec->assign = reinterpret_cast<byteAssign_t*>(const_cast<byteAssign_t*>(InfoAssignment));
|
||||
rec->pyldLen = HMINFO_PAYLOAD_LEN;
|
||||
break;
|
||||
case InverterDevInform_Simple:
|
||||
rec->length = (uint8_t)(HMSIMPLE_INFO_LIST_LEN);
|
||||
rec->assign = (byteAssign_t *)SimpleInfoAssignment;
|
||||
rec->assign = reinterpret_cast<byteAssign_t*>(const_cast<byteAssign_t*>(SimpleInfoAssignment));
|
||||
rec->pyldLen = HMSIMPLE_INFO_PAYLOAD_LEN;
|
||||
break;
|
||||
case SystemConfigPara:
|
||||
rec->length = (uint8_t)(HMSYSTEM_LIST_LEN);
|
||||
rec->assign = (byteAssign_t *)SystemConfigParaAssignment;
|
||||
rec->assign = reinterpret_cast<byteAssign_t*>(const_cast<byteAssign_t*>(SystemConfigParaAssignment));
|
||||
rec->pyldLen = HMSYSTEM_PAYLOAD_LEN;
|
||||
break;
|
||||
case AlarmData:
|
||||
rec->length = (uint8_t)(HMALARMDATA_LIST_LEN);
|
||||
rec->assign = (byteAssign_t *)AlarmDataAssignment;
|
||||
rec->assign = reinterpret_cast<byteAssign_t*>(const_cast<byteAssign_t*>(AlarmDataAssignment));
|
||||
rec->pyldLen = HMALARMDATA_PAYLOAD_LEN;
|
||||
break;
|
||||
default:
|
||||
|
@ -614,7 +586,7 @@ class Inverter {
|
|||
memset(mLastYD, 0, sizeof(float) * 6);
|
||||
}
|
||||
|
||||
bool parseGetLossRate(uint8_t pyld[], uint8_t len) {
|
||||
bool parseGetLossRate(const uint8_t pyld[], uint8_t len) {
|
||||
if (len == HMGETLOSSRATE_PAYLOAD_LEN) {
|
||||
uint16_t rxCnt = (pyld[0] << 8) + pyld[1];
|
||||
uint16_t txCnt = (pyld[2] << 8) + pyld[3];
|
||||
|
@ -813,7 +785,7 @@ class Inverter {
|
|||
|
||||
void addGridProfile(uint8_t buf[], uint8_t length) {
|
||||
mGridLen = (length > MAX_GRID_LENGTH) ? MAX_GRID_LENGTH : length;
|
||||
std::copy(buf, &buf[mGridLen], mGridProfile);
|
||||
std::copy(buf, &buf[mGridLen], mGridProfile.data());
|
||||
}
|
||||
|
||||
String getGridProfile(void) {
|
||||
|
@ -843,21 +815,23 @@ class Inverter {
|
|||
radioId.b[0] = 0x01;
|
||||
}
|
||||
|
||||
private:
|
||||
float mOffYD[6], mLastYD[6];
|
||||
bool mDevControlRequest; // true if change needed
|
||||
uint8_t mGridLen = 0;
|
||||
uint8_t mGridProfile[MAX_GRID_LENGTH];
|
||||
uint8_t mAlarmNxtWrPos = 0; // indicates the position in array (rolling buffer)
|
||||
bool mNextLive = true; // first read live data after booting up then version etc.
|
||||
|
||||
public:
|
||||
static uint32_t *timestamp; // system timestamp
|
||||
static cfgInst_t *generalConfig; // general inverter configuration from setup
|
||||
|
||||
uint16_t mDtuRxCnt = 0;
|
||||
uint16_t mDtuTxCnt = 0;
|
||||
uint8_t mGetLossInterval = 0; // request iv every AHOY_GET_LOSS_INTERVAL RealTimeRunData_Debug
|
||||
uint16_t mIvRxCnt = 0;
|
||||
uint16_t mIvTxCnt = 0;
|
||||
uint16_t mAckCount = 0;
|
||||
|
||||
private:
|
||||
float mOffYD[6], mLastYD[6];
|
||||
bool mDevControlRequest = false; // true if change needed
|
||||
uint8_t mGridLen = 0;
|
||||
std::array<uint8_t, MAX_GRID_LENGTH> mGridProfile;
|
||||
uint8_t mAlarmNxtWrPos = 0; // indicates the position in array (rolling buffer)
|
||||
};
|
||||
|
||||
template <class REC_TYP>
|
||||
|
|
114
src/hm/hmRadio.h
114
src/hm/hmRadio.h
|
@ -15,7 +15,6 @@
|
|||
#endif
|
||||
|
||||
#define SPI_SPEED 1000000
|
||||
|
||||
#define RF_CHANNELS 5
|
||||
|
||||
const char* const rf24AmpPowerNames[] = {"MIN", "LOW", "HIGH", "MAX"};
|
||||
|
@ -53,7 +52,7 @@ class HmRadio : public Radio {
|
|||
mPrintWholeTrace = printWholeTrace;
|
||||
|
||||
generateDtuSn();
|
||||
DTU_RADIO_ID = ((uint64_t)(((mDtuSn >> 24) & 0xFF) | ((mDtuSn >> 8) & 0xFF00) | ((mDtuSn << 8) & 0xFF0000) | ((mDtuSn << 24) & 0xFF000000)) << 8) | 0x01;
|
||||
mDtuRadioId = ((uint64_t)(((mDtuSn >> 24) & 0xFF) | ((mDtuSn >> 8) & 0xFF00) | ((mDtuSn << 8) & 0xFF0000) | ((mDtuSn << 24) & 0xFF000000)) << 8) | 0x01;
|
||||
|
||||
#ifdef ESP32
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S3) && defined(SPI_HAL)
|
||||
|
@ -86,7 +85,7 @@ class HmRadio : public Radio {
|
|||
mNrf24->enableDynamicPayloads();
|
||||
mNrf24->setCRCLength(RF24_CRC_16);
|
||||
mNrf24->setAddressWidth(5);
|
||||
mNrf24->openReadingPipe(1, reinterpret_cast<uint8_t*>(&DTU_RADIO_ID));
|
||||
mNrf24->openReadingPipe(1, reinterpret_cast<uint8_t*>(&mDtuRadioId));
|
||||
mNrf24->maskIRQ(false, false, false); // enable all receiving interrupts
|
||||
mNrf24->setPALevel(1); // low is default
|
||||
|
||||
|
@ -100,7 +99,7 @@ class HmRadio : public Radio {
|
|||
}
|
||||
|
||||
// returns true if communication is active
|
||||
bool loop(void) {
|
||||
bool loop(void) override {
|
||||
if (!mIrqRcvd && !mNRFisInRX)
|
||||
return false; // first quick check => nothing to do at all here
|
||||
|
||||
|
@ -113,22 +112,22 @@ class HmRadio : public Radio {
|
|||
|
||||
if (mRadioWaitTime.isTimeout()) { // timeout reached!
|
||||
mNRFisInRX = false;
|
||||
rx_ready = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
// otherwise switch to next RX channel
|
||||
mTimeslotStart = millis();
|
||||
if(!mNRFloopChannels && ((mTimeslotStart - mLastIrqTime) > (DURATION_TXFRAME+DURATION_ONEFRAME)))
|
||||
if(!mNRFloopChannels && ((mTimeslotStart - mLastIrqTime) > (DURATION_TXFRAME))) //(DURATION_TXFRAME+DURATION_ONEFRAME)))
|
||||
mNRFloopChannels = true;
|
||||
|
||||
rxPendular = !rxPendular;
|
||||
//innerLoopTimeout = (rxPendular ? 1 : 2)*DURATION_LISTEN_MIN;
|
||||
mRxPendular = !mRxPendular;
|
||||
innerLoopTimeout = DURATION_LISTEN_MIN;
|
||||
|
||||
if(mNRFloopChannels)
|
||||
tempRxChIdx = (tempRxChIdx + 4) % RF_CHANNELS;
|
||||
else
|
||||
tempRxChIdx = (mRxChIdx + rxPendular*4) % RF_CHANNELS;
|
||||
tempRxChIdx = (mRxChIdx + mRxPendular*4) % RF_CHANNELS;
|
||||
|
||||
mNrf24->setChannel(mRfChLst[tempRxChIdx]);
|
||||
isRxInit = false;
|
||||
|
@ -142,7 +141,7 @@ class HmRadio : public Radio {
|
|||
|
||||
if(tx_ok || tx_fail) { // tx related interrupt, basically we should start listening
|
||||
mNrf24->flush_tx(); // empty TX FIFO
|
||||
mTxSetupTime = millis() - mMillis;
|
||||
//mTxSetupTime = millis() - mMillis;
|
||||
|
||||
if(mNRFisInRX) {
|
||||
DPRINTLN(DBG_WARN, F("unexpected tx irq!"));
|
||||
|
@ -153,19 +152,19 @@ class HmRadio : public Radio {
|
|||
if(tx_ok)
|
||||
mLastIv->mAckCount++;
|
||||
|
||||
mRxChIdx = (mTxChIdx + 2) % RF_CHANNELS;
|
||||
rxOffset = mLastIv->ivGen == IV_HM ? 3 : 2; // holds the default channel offset between tx and rx channel (nRF only)
|
||||
mRxChIdx = (mTxChIdx + rxOffset) % RF_CHANNELS;
|
||||
mNrf24->setChannel(mRfChLst[mRxChIdx]);
|
||||
mNrf24->startListening();
|
||||
mTimeslotStart = millis();
|
||||
tempRxChIdx = mRxChIdx;
|
||||
rxPendular = false;
|
||||
mNRFloopChannels = (mLastIv->ivGen == IV_MI);
|
||||
|
||||
innerLoopTimeout = DURATION_TXFRAME;
|
||||
tempRxChIdx = mRxChIdx; // might be better to start off with one channel less?
|
||||
mRxPendular = false;
|
||||
mNRFloopChannels = (mLastIv->mCmd == MI_REQ_CH1 || mLastIv->mCmd == MI_REQ_CH2);
|
||||
innerLoopTimeout = DURATION_LISTEN_MIN;
|
||||
}
|
||||
|
||||
if(rx_ready) {
|
||||
if (getReceived()) { // check what we got, returns true for last package
|
||||
if (getReceived()) { // check what we got, returns true for last package or success for single frame request
|
||||
mNRFisInRX = false;
|
||||
mRadioWaitTime.startTimeMonitor(DURATION_PAUSE_LASTFR); // let the inverter first end his transmissions
|
||||
mNrf24->stopListening();
|
||||
|
@ -173,7 +172,6 @@ class HmRadio : public Radio {
|
|||
innerLoopTimeout = DURATION_LISTEN_MIN;
|
||||
mTimeslotStart = millis();
|
||||
if (!mNRFloopChannels) {
|
||||
//rxPendular = true; // stay longer on the next rx channel
|
||||
if (isRxInit) {
|
||||
isRxInit = false;
|
||||
tempRxChIdx = (mRxChIdx + 4) % RF_CHANNELS;
|
||||
|
@ -182,22 +180,19 @@ class HmRadio : public Radio {
|
|||
mRxChIdx = tempRxChIdx;
|
||||
}
|
||||
}
|
||||
rx_ready = false; // reset
|
||||
return mNRFisInRX;
|
||||
} /*else if(tx_fail) {
|
||||
mNRFisInRX = false;
|
||||
return false;
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool isChipConnected(void) {
|
||||
//DPRINTLN(DBG_VERBOSE, F("hmRadio.h:isChipConnected"));
|
||||
bool isChipConnected(void) const override {
|
||||
return mNrf24->isChipConnected();
|
||||
}
|
||||
|
||||
void sendControlPacket(Inverter<> *iv, uint8_t cmd, uint16_t *data, bool isRetransmit) {
|
||||
void sendControlPacket(Inverter<> *iv, uint8_t cmd, uint16_t *data, bool isRetransmit) override {
|
||||
DPRINT_IVID(DBG_INFO, iv->id);
|
||||
DBGPRINT(F("sendControlPacket cmd: "));
|
||||
DBGHEXLN(cmd);
|
||||
|
@ -283,27 +278,20 @@ class HmRadio : public Radio {
|
|||
sendPacket(iv, cnt, isRetransmit, (IV_MI != iv->ivGen));
|
||||
}
|
||||
|
||||
uint8_t getDataRate(void) {
|
||||
uint8_t getDataRate(void) const {
|
||||
if(!mNrf24->isChipConnected())
|
||||
return 3; // unknown
|
||||
return mNrf24->getDataRate();
|
||||
}
|
||||
|
||||
bool isPVariant(void) {
|
||||
bool isPVariant(void) const {
|
||||
return mNrf24->isPVariant();
|
||||
}
|
||||
|
||||
uint8_t getARC(void) {
|
||||
return mNrf24->getARC();
|
||||
}
|
||||
|
||||
uint8_t getPLOS(void) {
|
||||
return mNrf24->getPLOS();
|
||||
}
|
||||
|
||||
private:
|
||||
inline bool getReceived(void) {
|
||||
bool isLastPackage = false;
|
||||
bool isRetransmitAnswer = false;
|
||||
rx_ready = false; // reset for ACK case
|
||||
|
||||
while(mNrf24->available()) {
|
||||
|
@ -315,21 +303,27 @@ class HmRadio : public Radio {
|
|||
p.len = (len > MAX_RF_PAYLOAD_SIZE) ? MAX_RF_PAYLOAD_SIZE : len;
|
||||
p.rssi = mNrf24->testRPD() ? -64 : -75;
|
||||
p.millis = millis() - mMillis;
|
||||
p.arc = mNrf24->getARC();
|
||||
p.plos = mNrf24->getPLOS();
|
||||
mNrf24->read(p.packet, p.len);
|
||||
|
||||
if (p.packet[0] != 0x00) {
|
||||
if(!checkIvSerial(p.packet, mLastIv)) {
|
||||
DPRINT(DBG_WARN, "RX other inverter ");
|
||||
DPRINT(DBG_WARN, F("RX other inverter "));
|
||||
if(!*mPrivacyMode)
|
||||
ah::dumpBuf(p.packet, p.len);
|
||||
else
|
||||
DBGPRINTLN(F(""));
|
||||
} else {
|
||||
mLastIv->mGotFragment = true;
|
||||
mBufCtrl.push(p);
|
||||
|
||||
if (p.packet[0] == (TX_REQ_INFO + ALL_FRAMES)) // response from get information command
|
||||
if (p.packet[0] == (TX_REQ_INFO + ALL_FRAMES)) { // response from get information command
|
||||
isLastPackage = (p.packet[9] > ALL_FRAMES); // > ALL_FRAMES indicates last packet received
|
||||
if(mLastIv->mIsSingleframeReq) // we only expect one frame here...
|
||||
isRetransmitAnswer = true;
|
||||
|
||||
if(isLastPackage)
|
||||
setExpectedFrames(p.packet[9] - ALL_FRAMES);
|
||||
}
|
||||
|
||||
if(IV_MI == mLastIv->ivGen) {
|
||||
if (p.packet[0] == (0x0f + ALL_FRAMES)) // response from MI get information command
|
||||
|
@ -345,7 +339,7 @@ class HmRadio : public Radio {
|
|||
}
|
||||
if(isLastPackage)
|
||||
mLastIv->mGotLastMsg = true;
|
||||
return isLastPackage;
|
||||
return isLastPackage || isRetransmitAnswer;
|
||||
}
|
||||
|
||||
void sendPacket(Inverter<> *iv, uint8_t len, bool isRetransmit, bool appendCrc16=true) {
|
||||
|
@ -356,23 +350,27 @@ class HmRadio : public Radio {
|
|||
mTxChIdx = iv->heuristics.txRfChId;
|
||||
|
||||
if(*mSerialDebug) {
|
||||
if(!isRetransmit) {
|
||||
/*if(!isRetransmit) {
|
||||
DPRINT(DBG_INFO, "last tx setup: ");
|
||||
DBGPRINT(String(mTxSetupTime));
|
||||
DBGPRINTLN("ms");
|
||||
}
|
||||
}*/
|
||||
|
||||
DPRINT_IVID(DBG_INFO, iv->id);
|
||||
DBGPRINT(F("TX "));
|
||||
DBGPRINT(String(len));
|
||||
DBGPRINT(" CH");
|
||||
if(mTxChIdx == 0)
|
||||
DBGPRINT("0");
|
||||
DBGPRINT(String(mRfChLst[mTxChIdx]));
|
||||
DBGPRINT(F(" | "));
|
||||
DBGPRINT(F(", "));
|
||||
DBGPRINT(String(mTxRetriesNext));
|
||||
DBGPRINT(F(" ret. | "));
|
||||
if(*mPrintWholeTrace) {
|
||||
if(*mPrivacyMode)
|
||||
ah::dumpBuf(mTxBuf, len, 1, 4);
|
||||
ah::dumpBuf(mTxBuf.data(), len, 1, 4);
|
||||
else
|
||||
ah::dumpBuf(mTxBuf, len);
|
||||
ah::dumpBuf(mTxBuf.data(), len);
|
||||
} else {
|
||||
DHEX(mTxBuf[0]);
|
||||
DBGPRINT(F(" "));
|
||||
|
@ -383,9 +381,14 @@ class HmRadio : public Radio {
|
|||
}
|
||||
|
||||
mNrf24->stopListening();
|
||||
mNrf24->flush_rx();
|
||||
if(!isRetransmit && (mTxRetries != mTxRetriesNext)) {
|
||||
mNrf24->setRetries(3, mTxRetriesNext);
|
||||
mTxRetries = mTxRetriesNext;
|
||||
}
|
||||
mNrf24->setChannel(mRfChLst[mTxChIdx]);
|
||||
mNrf24->openWritingPipe(reinterpret_cast<uint8_t*>(&iv->radioId.u64));
|
||||
mNrf24->startWrite(mTxBuf, len, false); // false = request ACK response
|
||||
mNrf24->startFastWrite(mTxBuf.data(), len, false, true); // false (3) = request ACK response; true (4) reset CE to high after transmission
|
||||
mMillis = millis();
|
||||
|
||||
mLastIv = iv;
|
||||
|
@ -393,15 +396,15 @@ class HmRadio : public Radio {
|
|||
mNRFisInRX = false;
|
||||
}
|
||||
|
||||
uint64_t getIvId(Inverter<> *iv) {
|
||||
uint64_t getIvId(Inverter<> *iv) const override {
|
||||
return iv->radioId.u64;
|
||||
}
|
||||
|
||||
uint8_t getIvGen(Inverter<> *iv) {
|
||||
uint8_t getIvGen(Inverter<> *iv) const override {
|
||||
return iv->ivGen;
|
||||
}
|
||||
|
||||
inline bool checkIvSerial(uint8_t buf[], Inverter<> *iv) {
|
||||
inline bool checkIvSerial(const uint8_t buf[], Inverter<> *iv) {
|
||||
for(uint8_t i = 1; i < 5; i++) {
|
||||
if(buf[i] != iv->radioId.b[i])
|
||||
return false;
|
||||
|
@ -409,22 +412,23 @@ class HmRadio : public Radio {
|
|||
return true;
|
||||
}
|
||||
|
||||
uint64_t DTU_RADIO_ID;
|
||||
uint8_t mRfChLst[RF_CHANNELS] = {03, 23, 40, 61, 75}; // channel List:2403, 2423, 2440, 2461, 2475MHz
|
||||
uint64_t mDtuRadioId = 0ULL;
|
||||
const uint8_t mRfChLst[RF_CHANNELS] = {03, 23, 40, 61, 75}; // channel List:2403, 2423, 2440, 2461, 2475MHz
|
||||
uint8_t mTxChIdx = 0;
|
||||
uint8_t mRxChIdx = 0;
|
||||
uint8_t tempRxChIdx = mRxChIdx;
|
||||
uint8_t tempRxChIdx = 0;
|
||||
bool mGotLastMsg = false;
|
||||
uint32_t mMillis;
|
||||
bool tx_ok, tx_fail, rx_ready = false;
|
||||
uint32_t mMillis = 0;
|
||||
bool tx_ok = false, tx_fail = false, rx_ready = false;
|
||||
unsigned long mTimeslotStart = 0;
|
||||
unsigned long mLastIrqTime = 0;
|
||||
bool mNRFloopChannels = false;
|
||||
bool mNRFisInRX = false;
|
||||
bool isRxInit = true;
|
||||
bool rxPendular = false;
|
||||
bool mRxPendular = false;
|
||||
uint32_t innerLoopTimeout = DURATION_LISTEN_MIN;
|
||||
uint8_t mTxSetupTime = 0;
|
||||
uint8_t mTxRetries = 15; // memorize last setting for mNrf24->setRetries(3, 15);
|
||||
uint8_t rxOffset = 3; // holds the channel offset between tx and rx channel used for actual inverter
|
||||
|
||||
std::unique_ptr<SPIClass> mSpi;
|
||||
std::unique_ptr<RF24> mNrf24;
|
||||
|
|
|
@ -16,8 +16,8 @@ class HmSystem {
|
|||
HmSystem() {}
|
||||
|
||||
void setup(uint32_t *timestamp, cfgInst_t *config, IApp *app) {
|
||||
mInverter[0].timestamp = timestamp;
|
||||
mInverter[0].generalConfig = config;
|
||||
INVERTERTYPE::timestamp = timestamp;
|
||||
INVERTERTYPE::generalConfig = config;
|
||||
//mInverter[0].app = app;
|
||||
}
|
||||
|
||||
|
@ -35,6 +35,8 @@ class HmSystem {
|
|||
case 0x21: iv->type = INV_TYPE_1CH;
|
||||
break;
|
||||
|
||||
case 0x25: // HMS-400 - 1 channel but payload like 2ch
|
||||
|
||||
case 0x44: // HMS-1000
|
||||
case 0x42:
|
||||
case 0x41: iv->type = INV_TYPE_2CH;
|
||||
|
@ -51,15 +53,14 @@ class HmSystem {
|
|||
}
|
||||
|
||||
if(iv->config->serial.b[5] == 0x11) {
|
||||
if((iv->config->serial.b[4] & 0x0f) == 0x04) {
|
||||
if(((iv->config->serial.b[4] & 0x0f) == 0x04) || ((iv->config->serial.b[4] & 0x0f) == 0x05)) {
|
||||
iv->ivGen = IV_HMS;
|
||||
iv->ivRadioType = INV_RADIO_TYPE_CMT;
|
||||
} else {
|
||||
iv->ivGen = IV_HM;
|
||||
iv->ivRadioType = INV_RADIO_TYPE_NRF;
|
||||
}
|
||||
}
|
||||
else if((iv->config->serial.b[4] & 0x03) == 0x02) { // MI 3rd Gen -> same as HM
|
||||
} else if((iv->config->serial.b[4] & 0x03) == 0x02) { // MI 3rd Gen -> same as HM
|
||||
iv->ivGen = IV_HM;
|
||||
iv->ivRadioType = INV_RADIO_TYPE_NRF;
|
||||
} else { // MI 2nd Gen
|
||||
|
@ -69,6 +70,7 @@ class HmSystem {
|
|||
} else if(iv->config->serial.b[5] == 0x13) {
|
||||
iv->ivGen = IV_HMT;
|
||||
iv->type = INV_TYPE_6CH;
|
||||
iv->ivRadioType = INV_RADIO_TYPE_CMT;
|
||||
} else if(iv->config->serial.u64 != 0ULL) {
|
||||
DPRINTLN(DBG_ERROR, F("inverter type can't be detected!"));
|
||||
return;
|
||||
|
@ -81,7 +83,7 @@ class HmSystem {
|
|||
|
||||
DPRINT(DBG_INFO, "added inverter ");
|
||||
if(iv->config->serial.b[5] == 0x11) {
|
||||
if((iv->config->serial.b[4] & 0x0f) == 0x04)
|
||||
if(((iv->config->serial.b[4] & 0x0f) == 0x04) || ((iv->config->serial.b[4] & 0x0f) == 0x05))
|
||||
DBGPRINT("HMS");
|
||||
else
|
||||
DBGPRINT("HM");
|
||||
|
@ -92,34 +94,31 @@ class HmSystem {
|
|||
|
||||
DBGPRINTLN(String(iv->config->serial.u64, HEX));
|
||||
|
||||
if((iv->config->serial.b[5] == 0x10) && ((iv->config->serial.b[4] & 0x03) == 0x01))
|
||||
if(IV_MI == iv->ivGen)
|
||||
DPRINTLN(DBG_WARN, F("MI Inverter, has some restrictions!"));
|
||||
|
||||
cb(iv);
|
||||
}
|
||||
|
||||
INVERTERTYPE *findInverter(uint8_t buf[]) {
|
||||
DPRINTLN(DBG_VERBOSE, F("hmSystem.h:findInverter"));
|
||||
INVERTERTYPE *p;
|
||||
INVERTERTYPE *findInverter(const uint8_t buf[]) {
|
||||
for(uint8_t i = 0; i < MAX_INVERTER; i++) {
|
||||
p = &mInverter[i];
|
||||
INVERTERTYPE *p = &mInverter[i];
|
||||
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;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
INVERTERTYPE *getInverterByPos(uint8_t pos, bool check = true) {
|
||||
DPRINTLN(DBG_VERBOSE, F("hmSystem.h:getInverterByPos"));
|
||||
if(pos >= MAX_INVERTER)
|
||||
return NULL;
|
||||
return nullptr;
|
||||
else if((mInverter[pos].config->serial.u64 != 0ULL) || (false == check))
|
||||
return &mInverter[pos];
|
||||
else
|
||||
return NULL;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
uint8_t getNumInverters(void) {
|
||||
|
|
|
@ -142,7 +142,7 @@ class nrfHal: public RF24_hal, public SpiPatcherHandle {
|
|||
}
|
||||
|
||||
uint8_t read(uint8_t cmd, uint8_t* buf, uint8_t len) override {
|
||||
uint8_t data[NRF_MAX_TRANSFER_SZ];
|
||||
uint8_t data[NRF_MAX_TRANSFER_SZ + 1];
|
||||
data[0] = cmd;
|
||||
if(len > NRF_MAX_TRANSFER_SZ)
|
||||
len = NRF_MAX_TRANSFER_SZ;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 2023 Ahoy, https://github.com/lumpapu/ahoy
|
||||
// 2024 Ahoy, https://github.com/lumpapu/ahoy
|
||||
// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/4.0/deed
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
|
@ -11,6 +11,8 @@
|
|||
#define ALL_FRAMES 0x80
|
||||
#define SINGLE_FRAME 0x81
|
||||
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include "../utils/dbg.h"
|
||||
#include "../utils/crc.h"
|
||||
#include "../utils/timemonitor.h"
|
||||
|
@ -27,10 +29,13 @@ class Radio {
|
|||
virtual void sendControlPacket(Inverter<> *iv, uint8_t cmd, uint16_t *data, bool isRetransmit) = 0;
|
||||
virtual bool switchFrequency(Inverter<> *iv, uint32_t fromkHz, uint32_t tokHz) { return true; }
|
||||
virtual bool switchFrequencyCh(Inverter<> *iv, uint8_t fromCh, uint8_t toCh) { return true; }
|
||||
virtual bool isChipConnected(void) { return false; }
|
||||
virtual bool isChipConnected(void) const { return false; }
|
||||
virtual uint16_t getBaseFreqMhz() { return 0; }
|
||||
virtual uint16_t getBootFreqMhz() { return 0; }
|
||||
virtual std::pair<uint16_t,uint16_t> getFreqRangeMhz(void) { return std::make_pair(0, 0); }
|
||||
virtual bool loop(void) = 0;
|
||||
virtual uint8_t getARC(void) { return 0xff; }
|
||||
virtual uint8_t getPLOS(void) { return 0xff; }
|
||||
|
||||
Radio() : mTxBuf{} {}
|
||||
|
||||
void handleIntr(void) {
|
||||
mIrqRcvd = true;
|
||||
|
@ -66,7 +71,7 @@ class Radio {
|
|||
sendPacket(iv, 24, isRetransmit);
|
||||
}
|
||||
|
||||
uint32_t getDTUSn(void) {
|
||||
uint32_t getDTUSn(void) const {
|
||||
return mDtuSn;
|
||||
}
|
||||
|
||||
|
@ -78,11 +83,13 @@ class Radio {
|
|||
std::queue<packet_t> mBufCtrl;
|
||||
uint8_t mIrqOk = IRQ_UNKNOWN;
|
||||
TimeMonitor mRadioWaitTime = TimeMonitor(0, true); // start as expired (due to code in RESET state)
|
||||
uint8_t mTxRetriesNext = 15; // let heuristics tell us the next reties count (for nRF type radios only)
|
||||
uint8_t mFramesExpected = 0x0c;
|
||||
|
||||
protected:
|
||||
virtual void sendPacket(Inverter<> *iv, uint8_t len, bool isRetransmit, bool appendCrc16=true) = 0;
|
||||
virtual uint64_t getIvId(Inverter<> *iv) = 0;
|
||||
virtual uint8_t getIvGen(Inverter<> *iv) = 0;
|
||||
virtual uint64_t getIvId(Inverter<> *iv) const = 0;
|
||||
virtual uint8_t getIvGen(Inverter<> *iv) const = 0;
|
||||
|
||||
void initPacket(uint64_t ivId, uint8_t mid, uint8_t pid) {
|
||||
mTxBuf[0] = mid;
|
||||
|
@ -103,7 +110,7 @@ class Radio {
|
|||
mTxBuf[(*len)++] = (crc ) & 0xff;
|
||||
}
|
||||
// crc over all
|
||||
mTxBuf[*len] = ah::crc8(mTxBuf, *len);
|
||||
mTxBuf[*len] = ah::crc8(mTxBuf.data(), *len);
|
||||
(*len)++;
|
||||
}
|
||||
|
||||
|
@ -115,21 +122,21 @@ class Radio {
|
|||
chipID = ESP.getChipId();
|
||||
#endif
|
||||
|
||||
uint8_t t;
|
||||
mDtuSn = 0;
|
||||
for(int i = 0; i < (7 << 2); i += 4) {
|
||||
t = (chipID >> i) & 0x0f;
|
||||
uint8_t t = (chipID >> i) & 0x0f;
|
||||
if(t > 0x09)
|
||||
t -= 6;
|
||||
mDtuSn |= (t << i);
|
||||
}
|
||||
mDtuSn |= 0x80000000; // the first digit is an 8 for DTU production year 2022, the rest is filled with the ESP chipID in decimal
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t mDtuSn;
|
||||
volatile bool mIrqRcvd;
|
||||
bool *mSerialDebug, *mPrivacyMode, *mPrintWholeTrace;
|
||||
uint8_t mTxBuf[MAX_RF_PAYLOAD_SIZE];
|
||||
uint8_t mFramesExpected = 0x0c;
|
||||
protected:
|
||||
uint32_t mDtuSn = 0;
|
||||
std::atomic<bool> mIrqRcvd = false;
|
||||
bool *mSerialDebug = nullptr, *mPrivacyMode = nullptr, *mPrintWholeTrace = nullptr;
|
||||
std::array<uint8_t, MAX_RF_PAYLOAD_SIZE> mTxBuf;
|
||||
};
|
||||
|
||||
#endif /*__RADIO_H__*/
|
||||
|
|
|
@ -118,9 +118,9 @@ class Simulator {
|
|||
}
|
||||
|
||||
private:
|
||||
HMSYSTEM *mSys;
|
||||
uint8_t mIvId;
|
||||
uint32_t *mTimestamp;
|
||||
HMSYSTEM *mSys = nullptr;
|
||||
uint8_t mIvId = 0;
|
||||
uint32_t *mTimestamp = nullptr;
|
||||
payloadListenerType mCbPayload = nullptr;
|
||||
uint8_t payloadCtrl = 0;
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 2023 Ahoy, https://github.com/lumpapu/ahoy
|
||||
// 2024 Ahoy, https://github.com/lumpapu/ahoy
|
||||
// Creative Commons - https://creativecommons.org/licenses/by-nc-sa/4.0/deed
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
|
@ -12,8 +12,23 @@
|
|||
#include "esp32_3wSpi.h"
|
||||
#endif
|
||||
|
||||
// detailed register infos from AN142_CMT2300AW_Quick_Start_Guide-Rev0.8.pdf
|
||||
#include <utility>
|
||||
|
||||
enum class RegionCfg : uint8_t {
|
||||
EUROPE, USA, BRAZIL, NUM
|
||||
};
|
||||
|
||||
enum class CmtStatus : uint8_t {
|
||||
SUCCESS = 0,
|
||||
ERR_SWITCH_STATE,
|
||||
ERR_TX_PENDING,
|
||||
FIFO_EMPTY,
|
||||
ERR_RX_IN_FIFO
|
||||
};
|
||||
|
||||
#define FREQ_STEP_KHZ 250 // channel step size in kHz
|
||||
|
||||
// detailed register infos from AN142_CMT2300AW_Quick_Start_Guide-Rev0.8.pdf
|
||||
#define CMT2300A_MASK_CFG_RETAIN 0x10
|
||||
#define CMT2300A_MASK_RSTN_IN_EN 0x20
|
||||
#define CMT2300A_MASK_LOCKING_EN 0x20
|
||||
|
@ -152,67 +167,6 @@
|
|||
#define CMT2300A_MASK_TX_DONE_FLG 0x08
|
||||
#define CMT2300A_MASK_PKT_OK_FLG 0x01
|
||||
|
||||
// this list and the TX5, TX10 registers were compiled from the output of
|
||||
// HopeRF RFPDK Tool v1.54
|
||||
static uint8_t paLevelList[31][2] PROGMEM = {
|
||||
{0x17, 0x01}, // -10dBm
|
||||
{0x1a, 0x01}, // -09dBm
|
||||
{0x1d, 0x01}, // -08dBm
|
||||
{0x21, 0x01}, // -07dBm
|
||||
{0x25, 0x01}, // -06dBm
|
||||
{0x29, 0x01}, // -05dBm
|
||||
{0x2d, 0x01}, // -04dBm
|
||||
{0x33, 0x01}, // -03dBm
|
||||
{0x39, 0x02}, // -02dBm
|
||||
{0x41, 0x02}, // -01dBm
|
||||
{0x4b, 0x02}, // 00dBm
|
||||
{0x56, 0x03}, // 01dBm
|
||||
{0x63, 0x03}, // 02dBm
|
||||
{0x71, 0x04}, // 03dBm
|
||||
{0x80, 0x04}, // 04dBm
|
||||
{0x22, 0x01}, // 05dBm
|
||||
{0x27, 0x04}, // 06dBm
|
||||
{0x2c, 0x05}, // 07dBm
|
||||
{0x31, 0x06}, // 08dBm
|
||||
{0x38, 0x06}, // 09dBm
|
||||
{0x3f, 0x07}, // 10dBm
|
||||
{0x48, 0x08}, // 11dBm
|
||||
{0x52, 0x09}, // 12dBm
|
||||
{0x5d, 0x0b}, // 13dBm
|
||||
{0x6a, 0x0c}, // 14dBm
|
||||
{0x79, 0x0d}, // 15dBm
|
||||
{0x46, 0x10}, // 16dBm
|
||||
{0x51, 0x10}, // 17dBm
|
||||
{0x60, 0x12}, // 18dBm
|
||||
{0x71, 0x14}, // 19dBm
|
||||
{0x8c, 0x1c} // 20dBm
|
||||
};
|
||||
|
||||
// default CMT parameters
|
||||
static uint8_t cmtConfig[0x60] PROGMEM {
|
||||
// 0x00 - 0x0f -- RSSI offset +- 0 and 13dBm
|
||||
0x00, 0x66, 0xEC, 0x1C, 0x70, 0x80, 0x14, 0x08,
|
||||
0x11, 0x02, 0x02, 0x00, 0xAE, 0xE0, 0x35, 0x00,
|
||||
// 0x10 - 0x1f
|
||||
0x00, 0xF4, 0x10, 0xE2, 0x42, 0x20, 0x0C, 0x81,
|
||||
0x42, 0x32, 0xCF, 0x82, 0x42, 0x27, 0x76, 0x12, // 860MHz as default
|
||||
// 0x20 - 0x2f
|
||||
0xA6, 0xC9, 0x20, 0x20, 0xD2, 0x35, 0x0C, 0x0A,
|
||||
0x9F, 0x4B, 0x29, 0x29, 0xC0, 0x14, 0x05, 0x53,
|
||||
// 0x30 - 0x3f
|
||||
0x10, 0x00, 0xB4, 0x00, 0x00, 0x01, 0x00, 0x00,
|
||||
0x12, 0x1E, 0x00, 0xAA, 0x06, 0x00, 0x00, 0x00,
|
||||
// 0x40 - 0x4f
|
||||
0x00, 0x48, 0x5A, 0x48, 0x4D, 0x01, 0x1F, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0xC3, 0x00, 0x00, 0x60,
|
||||
// 0x50 - 0x5f
|
||||
0xFF, 0x00, 0x00, 0x1F, 0x10, 0x70, 0x4D, 0x06,
|
||||
0x00, 0x07, 0x50, 0x00, 0x5D, 0x0B, 0x3F, 0x7F // - TX 13dBm
|
||||
};
|
||||
|
||||
|
||||
enum {CMT_SUCCESS = 0, CMT_ERR_SWITCH_STATE, CMT_ERR_TX_PENDING, CMT_FIFO_EMPTY, CMT_ERR_RX_IN_FIFO};
|
||||
|
||||
class Cmt2300a {
|
||||
public:
|
||||
Cmt2300a() {}
|
||||
|
@ -234,12 +188,12 @@ class Cmt2300a {
|
|||
}
|
||||
}
|
||||
|
||||
uint8_t goRx(void) {
|
||||
CmtStatus goRx(void) {
|
||||
if(mTxPending)
|
||||
return CMT_ERR_TX_PENDING;
|
||||
return CmtStatus::ERR_TX_PENDING;
|
||||
|
||||
if(mInRxMode)
|
||||
return CMT_SUCCESS;
|
||||
return CmtStatus::SUCCESS;
|
||||
|
||||
mSpi.readReg(CMT2300A_CUS_INT1_CTL);
|
||||
mSpi.writeReg(CMT2300A_CUS_INT1_CTL, CMT2300A_INT_SEL_TX_DONE);
|
||||
|
@ -260,47 +214,47 @@ class Cmt2300a {
|
|||
mSpi.writeReg(0x16, 0x0C); // [4:3]: RSSI_DET_SEL, [2:0]: RSSI_AVG_MODE
|
||||
|
||||
if(!cmtSwitchStatus(CMT2300A_GO_RX, CMT2300A_STA_RX))
|
||||
return CMT_ERR_SWITCH_STATE;
|
||||
return CmtStatus::ERR_SWITCH_STATE;
|
||||
|
||||
mInRxMode = true;
|
||||
|
||||
return CMT_SUCCESS;
|
||||
return CmtStatus::SUCCESS;
|
||||
}
|
||||
|
||||
uint8_t getRx(uint8_t buf[], uint8_t *rxLen, uint8_t maxlen, int8_t *rssi) {
|
||||
CmtStatus getRx(uint8_t buf[], uint8_t *rxLen, uint8_t maxlen, int8_t *rssi) {
|
||||
if(mTxPending)
|
||||
return CMT_ERR_TX_PENDING;
|
||||
return CmtStatus::ERR_TX_PENDING;
|
||||
|
||||
if(0x1b != (mSpi.readReg(CMT2300A_CUS_INT_FLAG) & 0x1b))
|
||||
return CMT_FIFO_EMPTY;
|
||||
return CmtStatus::FIFO_EMPTY;
|
||||
|
||||
// receive ok (pream, sync, node, crc)
|
||||
if(!cmtSwitchStatus(CMT2300A_GO_STBY, CMT2300A_STA_STBY))
|
||||
return CMT_ERR_SWITCH_STATE;
|
||||
return CmtStatus::ERR_SWITCH_STATE;
|
||||
|
||||
mSpi.readFifo(buf, rxLen, maxlen);
|
||||
*rssi = mSpi.readReg(CMT2300A_CUS_RSSI_DBM) - 128;
|
||||
|
||||
if(!cmtSwitchStatus(CMT2300A_GO_SLEEP, CMT2300A_STA_SLEEP))
|
||||
return CMT_ERR_SWITCH_STATE;
|
||||
return CmtStatus::ERR_SWITCH_STATE;
|
||||
|
||||
if(!cmtSwitchStatus(CMT2300A_GO_STBY, CMT2300A_STA_STBY))
|
||||
return CMT_ERR_SWITCH_STATE;
|
||||
return CmtStatus::ERR_SWITCH_STATE;
|
||||
|
||||
mInRxMode = false;
|
||||
mCusIntFlag = mSpi.readReg(CMT2300A_CUS_INT_FLAG);
|
||||
|
||||
return CMT_SUCCESS;
|
||||
return CmtStatus::SUCCESS;
|
||||
}
|
||||
|
||||
uint8_t tx(uint8_t buf[], uint8_t len) {
|
||||
CmtStatus tx(uint8_t buf[], uint8_t len) {
|
||||
if(mTxPending)
|
||||
return CMT_ERR_TX_PENDING;
|
||||
return CmtStatus::ERR_TX_PENDING;
|
||||
|
||||
if(mInRxMode) {
|
||||
mInRxMode = false;
|
||||
if(!cmtSwitchStatus(CMT2300A_GO_STBY, CMT2300A_STA_STBY))
|
||||
return CMT_ERR_SWITCH_STATE;
|
||||
return CmtStatus::ERR_SWITCH_STATE;
|
||||
}
|
||||
|
||||
mSpi.writeReg(CMT2300A_CUS_INT1_CTL, CMT2300A_INT_SEL_TX_DONE);
|
||||
|
@ -325,16 +279,17 @@ class Cmt2300a {
|
|||
}
|
||||
|
||||
if(!cmtSwitchStatus(CMT2300A_GO_TX, CMT2300A_STA_TX))
|
||||
return CMT_ERR_SWITCH_STATE;
|
||||
return CmtStatus::ERR_SWITCH_STATE;
|
||||
|
||||
// wait for tx done
|
||||
mTxPending = true;
|
||||
|
||||
return CMT_SUCCESS;
|
||||
return CmtStatus::SUCCESS;
|
||||
}
|
||||
|
||||
// initialize CMT2300A, returns true on success
|
||||
bool reset(void) {
|
||||
bool reset(RegionCfg region) {
|
||||
mRegionCfg = region;
|
||||
mSpi.writeReg(0x7f, 0xff); // soft reset
|
||||
delay(30);
|
||||
|
||||
|
@ -346,9 +301,18 @@ class Cmt2300a {
|
|||
if(mSpi.readReg(0x62) != 0x20)
|
||||
return false; // not connected!
|
||||
|
||||
for(uint8_t i = 0; i < 0x60; i++) {
|
||||
for(uint8_t i = 0; i < 0x18; i++) {
|
||||
mSpi.writeReg(i, cmtConfig[i]);
|
||||
}
|
||||
for(uint8_t i = 0; i < 8; i++) {
|
||||
mSpi.writeReg(0x18 + i, mBaseFreqCfg[static_cast<uint8_t>(region)][i]);
|
||||
}
|
||||
for(uint8_t i = 0x20; i < 0x60; i++) {
|
||||
mSpi.writeReg(i, cmtConfig[i]);
|
||||
}
|
||||
|
||||
if(RegionCfg::EUROPE != region)
|
||||
mSpi.writeReg(0x27, 0x0B);
|
||||
|
||||
|
||||
mSpi.writeReg(CMT2300A_CUS_IO_SEL, 0x20); // -> GPIO3_SEL[1:0] = 0x02
|
||||
|
@ -389,23 +353,14 @@ class Cmt2300a {
|
|||
}
|
||||
|
||||
inline uint8_t freq2Chan(const uint32_t freqKhz) {
|
||||
if((freqKhz % FREQ_STEP_KHZ) != 0) {
|
||||
DPRINT(DBG_WARN, F("switch frequency to "));
|
||||
DBGPRINT(String(freqKhz));
|
||||
DBGPRINT(F("kHz not possible!"));
|
||||
return 0xff; // error
|
||||
// apply the nearest frequency
|
||||
//freqKhz = (freqKhz + FREQ_STEP_KHZ/2) / FREQ_STEP_KHZ;
|
||||
//freqKhz *= FREQ_STEP_KHZ;
|
||||
}
|
||||
|
||||
if((freqKhz < HOY_BASE_FREQ_KHZ) || (freqKhz > HOY_MAX_FREQ_KHZ))
|
||||
if((freqKhz % FREQ_STEP_KHZ) != 0)
|
||||
return 0xff; // error
|
||||
|
||||
if((freqKhz < FREQ_WARN_MIN_KHZ) || (freqKhz > FREQ_WARN_MAX_KHZ))
|
||||
DPRINTLN(DBG_WARN, F("Desired frequency is out of EU legal range! (863 - 870MHz)"));
|
||||
std::pair<uint16_t, uint16_t> range = getFreqRangeMhz();
|
||||
if((freqKhz < (range.first * 1000)) || (freqKhz > (range.second * 1000)))
|
||||
return 0xff; // error
|
||||
|
||||
return (freqKhz - HOY_BASE_FREQ_KHZ) / FREQ_STEP_KHZ;
|
||||
return (freqKhz - (getBaseFreqMhz() * 1000)) / FREQ_STEP_KHZ;
|
||||
}
|
||||
|
||||
inline void switchChannel(uint8_t ch) {
|
||||
|
@ -414,9 +369,9 @@ class Cmt2300a {
|
|||
|
||||
inline uint32_t getFreqKhz(void) {
|
||||
if(0xff != mRqstCh)
|
||||
return HOY_BASE_FREQ_KHZ + (mRqstCh * FREQ_STEP_KHZ);
|
||||
return getBaseFreqMhz() * 1000 + (mRqstCh * FREQ_STEP_KHZ);
|
||||
else
|
||||
return HOY_BASE_FREQ_KHZ + (mCurCh * FREQ_STEP_KHZ);
|
||||
return getBaseFreqMhz() * 1000 + (mCurCh * FREQ_STEP_KHZ);
|
||||
}
|
||||
|
||||
uint8_t getCurrentChannel(void) {
|
||||
|
@ -443,6 +398,114 @@ class Cmt2300a {
|
|||
mSpi.writeReg(CMT2300A_CUS_TX9, paLevelList[level][1]);
|
||||
}
|
||||
|
||||
public:
|
||||
uint16_t getBaseFreqMhz(void) {
|
||||
switch(mRegionCfg) {
|
||||
default:
|
||||
[[fallthrough]];
|
||||
case RegionCfg::EUROPE:
|
||||
break;
|
||||
case RegionCfg::USA:
|
||||
return 905;
|
||||
case RegionCfg::BRAZIL:
|
||||
return 915;
|
||||
}
|
||||
return 860;
|
||||
}
|
||||
|
||||
uint16_t getBootFreqMhz(void) {
|
||||
switch(mRegionCfg) {
|
||||
default:
|
||||
[[fallthrough]];
|
||||
case RegionCfg::EUROPE:
|
||||
break;
|
||||
case RegionCfg::USA:
|
||||
return 915;
|
||||
case RegionCfg::BRAZIL:
|
||||
return 915;
|
||||
}
|
||||
return 868;
|
||||
}
|
||||
|
||||
std::pair<uint16_t,uint16_t> getFreqRangeMhz(void) {
|
||||
switch(mRegionCfg) {
|
||||
default:
|
||||
[[fallthrough]];
|
||||
case RegionCfg::EUROPE:
|
||||
break;
|
||||
case RegionCfg::USA:
|
||||
return std::make_pair(905, 925);
|
||||
case RegionCfg::BRAZIL:
|
||||
return std::make_pair(915, 928);
|
||||
}
|
||||
return std::make_pair(860, 870); // Europe
|
||||
}
|
||||
|
||||
private:
|
||||
// this list and the TX5, TX10 registers were compiled from the output of
|
||||
// HopeRF RFPDK Tool v1.54
|
||||
constexpr static uint8_t paLevelList[31][2] PROGMEM = {
|
||||
{0x17, 0x01}, // -10dBm
|
||||
{0x1a, 0x01}, // -09dBm
|
||||
{0x1d, 0x01}, // -08dBm
|
||||
{0x21, 0x01}, // -07dBm
|
||||
{0x25, 0x01}, // -06dBm
|
||||
{0x29, 0x01}, // -05dBm
|
||||
{0x2d, 0x01}, // -04dBm
|
||||
{0x33, 0x01}, // -03dBm
|
||||
{0x39, 0x02}, // -02dBm
|
||||
{0x41, 0x02}, // -01dBm
|
||||
{0x4b, 0x02}, // 00dBm
|
||||
{0x56, 0x03}, // 01dBm
|
||||
{0x63, 0x03}, // 02dBm
|
||||
{0x71, 0x04}, // 03dBm
|
||||
{0x80, 0x04}, // 04dBm
|
||||
{0x22, 0x01}, // 05dBm
|
||||
{0x27, 0x04}, // 06dBm
|
||||
{0x2c, 0x05}, // 07dBm
|
||||
{0x31, 0x06}, // 08dBm
|
||||
{0x38, 0x06}, // 09dBm
|
||||
{0x3f, 0x07}, // 10dBm
|
||||
{0x48, 0x08}, // 11dBm
|
||||
{0x52, 0x09}, // 12dBm
|
||||
{0x5d, 0x0b}, // 13dBm
|
||||
{0x6a, 0x0c}, // 14dBm
|
||||
{0x79, 0x0d}, // 15dBm
|
||||
{0x46, 0x10}, // 16dBm
|
||||
{0x51, 0x10}, // 17dBm
|
||||
{0x60, 0x12}, // 18dBm
|
||||
{0x71, 0x14}, // 19dBm
|
||||
{0x8c, 0x1c} // 20dBm
|
||||
};
|
||||
|
||||
// default CMT parameters
|
||||
constexpr static uint8_t cmtConfig[0x60] PROGMEM {
|
||||
// 0x00 - 0x0f -- RSSI offset +- 0 and 13dBm
|
||||
0x00, 0x66, 0xEC, 0x1C, 0x70, 0x80, 0x14, 0x08,
|
||||
0x11, 0x02, 0x02, 0x00, 0xAE, 0xE0, 0x35, 0x00,
|
||||
// 0x10 - 0x1f
|
||||
0x00, 0xF4, 0x10, 0xE2, 0x42, 0x20, 0x0C, 0x81,
|
||||
0x42, 0x32, 0xCF, 0x82, 0x42, 0x27, 0x76, 0x12, // 860MHz as default
|
||||
// 0x20 - 0x2f
|
||||
0xA6, 0xC9, 0x20, 0x20, 0xD2, 0x35, 0x0C, 0x0A,
|
||||
0x9F, 0x4B, 0x29, 0x29, 0xC0, 0x14, 0x05, 0x53,
|
||||
// 0x30 - 0x3f
|
||||
0x10, 0x00, 0xB4, 0x00, 0x00, 0x01, 0x00, 0x00,
|
||||
0x12, 0x1E, 0x00, 0xAA, 0x06, 0x00, 0x00, 0x00,
|
||||
// 0x40 - 0x4f
|
||||
0x00, 0x48, 0x5A, 0x48, 0x4D, 0x01, 0x1F, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0xC3, 0x00, 0x00, 0x60,
|
||||
// 0x50 - 0x5f
|
||||
0xFF, 0x00, 0x00, 0x1F, 0x10, 0x70, 0x4D, 0x06,
|
||||
0x00, 0x07, 0x50, 0x00, 0x5D, 0x0B, 0x3F, 0x7F // TX 13dBm
|
||||
};
|
||||
|
||||
constexpr static uint8_t mBaseFreqCfg[static_cast<uint8_t>(RegionCfg::NUM)][8] {
|
||||
{0x42, 0x32, 0xCF, 0x82, 0x42, 0x27, 0x76, 0x12}, // 860MHz
|
||||
{0x45, 0xA8, 0x31, 0x8A, 0x45, 0x9D, 0xD8, 0x19}, // 905MHz (USA, Indonesia)
|
||||
{0x46, 0x6D, 0x80, 0x86, 0x46, 0x62, 0x27, 0x16} // 915MHz (Brazil)
|
||||
};
|
||||
|
||||
private:
|
||||
void init() {
|
||||
mTxPending = false;
|
||||
|
@ -466,7 +529,8 @@ class Cmt2300a {
|
|||
return false;
|
||||
}
|
||||
|
||||
inline bool switchDtuFreq(const uint32_t freqKhz) {
|
||||
// maybe used in future
|
||||
/*inline bool switchDtuFreq(const uint32_t freqKhz) {
|
||||
uint8_t toCh = freq2Chan(freqKhz);
|
||||
if(0xff == toCh)
|
||||
return false;
|
||||
|
@ -474,22 +538,24 @@ class Cmt2300a {
|
|||
switchChannel(toCh);
|
||||
|
||||
return true;
|
||||
}
|
||||
}*/
|
||||
|
||||
inline uint8_t getChipStatus(void) {
|
||||
return mSpi.readReg(CMT2300A_CUS_MODE_STA) & CMT2300A_MASK_CHIP_MODE_STA;
|
||||
}
|
||||
|
||||
private:
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S3) && defined(SPI_HAL)
|
||||
cmtHal mSpi;
|
||||
#else
|
||||
esp32_3wSpi mSpi;
|
||||
#endif
|
||||
uint8_t mCnt;
|
||||
bool mTxPending;
|
||||
bool mInRxMode;
|
||||
uint8_t mCusIntFlag;
|
||||
uint8_t mRqstCh, mCurCh;
|
||||
uint8_t mCnt = 0;
|
||||
bool mTxPending = false;
|
||||
bool mInRxMode = false;
|
||||
uint8_t mCusIntFlag = 0;
|
||||
uint8_t mRqstCh = 0, mCurCh = 0;
|
||||
RegionCfg mRegionCfg = RegionCfg::EUROPE;
|
||||
};
|
||||
|
||||
#endif /*__CMT2300A_H__*/
|
||||
|
|
|
@ -89,7 +89,7 @@ class cmtHal : public SpiPatcherHandle {
|
|||
}
|
||||
|
||||
uint8_t readReg(uint8_t addr) {
|
||||
uint8_t data;
|
||||
uint8_t data = 0;
|
||||
|
||||
request_spi();
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#if defined(ESP32)
|
||||
#include "driver/spi_master.h"
|
||||
#include "esp_rom_gpio.h" // for esp_rom_gpio_connect_out_signal
|
||||
#include "../config/config.h"
|
||||
|
||||
#define SPI_CLK 1 * 1000 * 1000 // 1MHz
|
||||
|
||||
|
@ -104,7 +105,7 @@ class esp32_3wSpi {
|
|||
if(!mInitialized)
|
||||
return 0;
|
||||
|
||||
uint8_t rx_data;
|
||||
uint8_t rx_data = 0;
|
||||
spi_transaction_t t = {
|
||||
.cmd = 0,
|
||||
.addr = (uint64_t)(~addr),
|
||||
|
@ -121,7 +122,7 @@ class esp32_3wSpi {
|
|||
return rx_data;
|
||||
}
|
||||
|
||||
void writeFifo(uint8_t buf[], uint8_t len) {
|
||||
void writeFifo(const uint8_t buf[], uint8_t len) {
|
||||
if(!mInitialized)
|
||||
return;
|
||||
uint8_t tx_data;
|
||||
|
@ -144,7 +145,7 @@ class esp32_3wSpi {
|
|||
void readFifo(uint8_t buf[], uint8_t *len, uint8_t maxlen) {
|
||||
if(!mInitialized)
|
||||
return;
|
||||
uint8_t rx_data;
|
||||
uint8_t rx_data = 0;
|
||||
|
||||
spi_transaction_t t = {
|
||||
.length = 8,
|
||||
|
|
|
@ -9,40 +9,38 @@
|
|||
#include "cmt2300a.h"
|
||||
#include "../hm/radio.h"
|
||||
|
||||
//#define CMT_SWITCH_CHANNEL_CYCLE 5
|
||||
|
||||
template<uint32_t DTU_SN = 0x81001765>
|
||||
class CmtRadio : public Radio {
|
||||
typedef Cmt2300a CmtType;
|
||||
public:
|
||||
CmtRadio() {
|
||||
mDtuSn = DTU_SN;
|
||||
mCmtAvail = false;
|
||||
}
|
||||
|
||||
void setup(bool *serialDebug, bool *privacyMode, bool *printWholeTrace, uint8_t pinSclk, uint8_t pinSdio, uint8_t pinCsb, uint8_t pinFcsb, bool genDtuSn = true) {
|
||||
void setup(bool *serialDebug, bool *privacyMode, bool *printWholeTrace, uint8_t pinSclk, uint8_t pinSdio, uint8_t pinCsb, uint8_t pinFcsb, uint8_t region = 0, bool genDtuSn = true) {
|
||||
mCmt.setup(pinSclk, pinSdio, pinCsb, pinFcsb);
|
||||
reset(genDtuSn);
|
||||
reset(genDtuSn, static_cast<RegionCfg>(region));
|
||||
mPrivacyMode = privacyMode;
|
||||
mSerialDebug = serialDebug;
|
||||
mPrintWholeTrace = printWholeTrace;
|
||||
mTxBuf.fill(0);
|
||||
}
|
||||
|
||||
bool loop() {
|
||||
bool loop() override {
|
||||
mCmt.loop();
|
||||
if((!mIrqRcvd) && (!mRqstGetRx))
|
||||
return false;
|
||||
getRx();
|
||||
if(CMT_SUCCESS == mCmt.goRx()) {
|
||||
if(CmtStatus::SUCCESS == mCmt.goRx()) {
|
||||
mIrqRcvd = false;
|
||||
mRqstGetRx = false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool isChipConnected(void) {
|
||||
bool isChipConnected(void) const override {
|
||||
return mCmtAvail;
|
||||
}
|
||||
|
||||
void sendControlPacket(Inverter<> *iv, uint8_t cmd, uint16_t *data, bool isRetransmit) {
|
||||
void sendControlPacket(Inverter<> *iv, uint8_t cmd, uint16_t *data, bool isRetransmit) override {
|
||||
DPRINT(DBG_INFO, F("sendControlPacket cmd: "));
|
||||
DBGHEXLN(cmd);
|
||||
initPacket(iv->radioId.u64, TX_REQ_DEVCONTROL, SINGLE_FRAME);
|
||||
|
@ -60,14 +58,14 @@ class CmtRadio : public Radio {
|
|||
sendPacket(iv, cnt, isRetransmit);
|
||||
}
|
||||
|
||||
bool switchFrequency(Inverter<> *iv, uint32_t fromkHz, uint32_t tokHz) {
|
||||
bool switchFrequency(Inverter<> *iv, uint32_t fromkHz, uint32_t tokHz) override {
|
||||
uint8_t fromCh = mCmt.freq2Chan(fromkHz);
|
||||
uint8_t toCh = mCmt.freq2Chan(tokHz);
|
||||
|
||||
return switchFrequencyCh(iv, fromCh, toCh);
|
||||
}
|
||||
|
||||
bool switchFrequencyCh(Inverter<> *iv, uint8_t fromCh, uint8_t toCh) {
|
||||
bool switchFrequencyCh(Inverter<> *iv, uint8_t fromCh, uint8_t toCh) override {
|
||||
if((0xff == fromCh) || (0xff == toCh))
|
||||
return false;
|
||||
|
||||
|
@ -77,9 +75,21 @@ class CmtRadio : public Radio {
|
|||
return true;
|
||||
}
|
||||
|
||||
uint16_t getBaseFreqMhz(void) override {
|
||||
return mCmt.getBaseFreqMhz();
|
||||
}
|
||||
|
||||
uint16_t getBootFreqMhz(void) override {
|
||||
return mCmt.getBootFreqMhz();
|
||||
}
|
||||
|
||||
std::pair<uint16_t,uint16_t> getFreqRangeMhz(void) override {
|
||||
return mCmt.getFreqRangeMhz();
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
void sendPacket(Inverter<> *iv, uint8_t len, bool isRetransmit, bool appendCrc16=true) {
|
||||
void sendPacket(Inverter<> *iv, uint8_t len, bool isRetransmit, bool appendCrc16=true) override {
|
||||
// inverters have maybe different settings regarding frequency
|
||||
if(mCmt.getCurrentChannel() != iv->config->frequency)
|
||||
mCmt.switchChannel(iv->config->frequency);
|
||||
|
@ -93,9 +103,9 @@ class CmtRadio : public Radio {
|
|||
DBGPRINT(F("Mhz | "));
|
||||
if(*mPrintWholeTrace) {
|
||||
if(*mPrivacyMode)
|
||||
ah::dumpBuf(mTxBuf, len, 1, 4);
|
||||
ah::dumpBuf(mTxBuf.data(), len, 1, 4);
|
||||
else
|
||||
ah::dumpBuf(mTxBuf, len);
|
||||
ah::dumpBuf(mTxBuf.data(), len);
|
||||
} else {
|
||||
DHEX(mTxBuf[0]);
|
||||
DBGPRINT(F(" "));
|
||||
|
@ -105,29 +115,29 @@ class CmtRadio : public Radio {
|
|||
}
|
||||
}
|
||||
|
||||
uint8_t status = mCmt.tx(mTxBuf, len);
|
||||
CmtStatus status = mCmt.tx(mTxBuf.data(), len);
|
||||
mMillis = millis();
|
||||
if(CMT_SUCCESS != status) {
|
||||
if(CmtStatus::SUCCESS != status) {
|
||||
DPRINT(DBG_WARN, F("CMT TX failed, code: "));
|
||||
DBGPRINTLN(String(status));
|
||||
if(CMT_ERR_RX_IN_FIFO == status)
|
||||
DBGPRINTLN(String(static_cast<uint8_t>(status)));
|
||||
if(CmtStatus::ERR_RX_IN_FIFO == status)
|
||||
mIrqRcvd = true;
|
||||
}
|
||||
iv->mDtuTxCnt++;
|
||||
}
|
||||
|
||||
uint64_t getIvId(Inverter<> *iv) {
|
||||
uint64_t getIvId(Inverter<> *iv) const override {
|
||||
return iv->radioId.u64;
|
||||
}
|
||||
|
||||
uint8_t getIvGen(Inverter<> *iv) {
|
||||
uint8_t getIvGen(Inverter<> *iv) const override {
|
||||
return iv->ivGen;
|
||||
}
|
||||
|
||||
inline void reset(bool genDtuSn) {
|
||||
inline void reset(bool genDtuSn, RegionCfg region) {
|
||||
if(genDtuSn)
|
||||
generateDtuSn();
|
||||
if(!mCmt.reset()) {
|
||||
if(!mCmt.reset(region)) {
|
||||
mCmtAvail = false;
|
||||
DPRINTLN(DBG_WARN, F("Initializing CMT2300A failed!"));
|
||||
} else {
|
||||
|
@ -140,6 +150,10 @@ class CmtRadio : public Radio {
|
|||
}
|
||||
|
||||
inline void sendSwitchChCmd(Inverter<> *iv, uint8_t ch) {
|
||||
//if(CMT_SWITCH_CHANNEL_CYCLE > ++mSwitchCycle)
|
||||
// return;
|
||||
//mSwitchCycle = 0;
|
||||
|
||||
/** ch:
|
||||
* 0x00: 860.00 MHz
|
||||
* 0x01: 860.25 MHz
|
||||
|
@ -161,20 +175,23 @@ class CmtRadio : public Radio {
|
|||
inline void getRx(void) {
|
||||
packet_t p;
|
||||
p.millis = millis() - mMillis;
|
||||
uint8_t status = mCmt.getRx(p.packet, &p.len, 28, &p.rssi);
|
||||
if(CMT_SUCCESS == status)
|
||||
if(CmtStatus::SUCCESS == mCmt.getRx(p.packet, &p.len, 28, &p.rssi)) {
|
||||
//mSwitchCycle = 0;
|
||||
p.ch = 0; // not used for CMT inverters
|
||||
mBufCtrl.push(p);
|
||||
}
|
||||
|
||||
// this code completly stops communication!
|
||||
//if(p.packet[9] > ALL_FRAMES) // indicates last frame
|
||||
// mRadioWaitTime.stopTimeMonitor(); // we got everything we expected and can exit rx mode...
|
||||
//optionally instead: mRadioWaitTime.startTimeMonitor(DURATION_PAUSE_LASTFR); // let the inverter first get back to rx mode?
|
||||
if(p.packet[9] > ALL_FRAMES) { // indicates last frame
|
||||
setExpectedFrames(p.packet[9] - ALL_FRAMES);
|
||||
mRadioWaitTime.startTimeMonitor(DURATION_PAUSE_LASTFR); // let the inverter first get back to rx mode?
|
||||
}
|
||||
}
|
||||
|
||||
CmtType mCmt;
|
||||
bool mCmtAvail;
|
||||
bool mCmtAvail = false;
|
||||
bool mRqstGetRx = false;
|
||||
uint32_t mMillis;
|
||||
uint32_t mMillis = 0;
|
||||
//uint8_t mSwitchCycle = 0;
|
||||
};
|
||||
|
||||
#endif /*__HMS_RADIO_H__*/
|
||||
|
|
|
@ -28,7 +28,7 @@ lib_deps =
|
|||
https://github.com/yubox-node-org/ESPAsyncWebServer
|
||||
https://github.com/nRF24/RF24 @ 1.4.8
|
||||
paulstoffregen/Time @ ^1.6.1
|
||||
https://github.com/bertmelis/espMqttClient#v1.5.0
|
||||
https://github.com/bertmelis/espMqttClient#v1.6.0
|
||||
bblanchon/ArduinoJson @ ^6.21.3
|
||||
https://github.com/JChristensen/Timezone @ ^1.2.4
|
||||
olikraus/U8g2 @ ^2.35.9
|
||||
|
@ -394,7 +394,7 @@ lib_deps =
|
|||
khoih-prog/AsyncUDP_ESP32_W5500
|
||||
https://github.com/nrf24/RF24 @ ^1.4.8
|
||||
paulstoffregen/Time @ ^1.6.1
|
||||
https://github.com/bertmelis/espMqttClient#v1.5.0
|
||||
https://github.com/bertmelis/espMqttClient#v1.6.0
|
||||
bblanchon/ArduinoJson @ ^6.21.3
|
||||
https://github.com/JChristensen/Timezone @ ^1.2.4
|
||||
olikraus/U8g2 @ ^2.35.9
|
||||
|
@ -439,7 +439,7 @@ lib_deps =
|
|||
khoih-prog/AsyncUDP_ESP32_W5500
|
||||
https://github.com/nrf24/RF24 @ ^1.4.8
|
||||
paulstoffregen/Time @ ^1.6.1
|
||||
https://github.com/bertmelis/espMqttClient#v1.5.0
|
||||
https://github.com/bertmelis/espMqttClient#v1.6.0
|
||||
bblanchon/ArduinoJson @ ^6.21.3
|
||||
https://github.com/JChristensen/Timezone @ ^1.2.4
|
||||
olikraus/U8g2 @ ^2.35.9
|
||||
|
|
|
@ -192,15 +192,14 @@ class Display {
|
|||
if ((mCfg->screenSaver == 2) && (mCfg->pirPin != DEF_PIN_OFF)) {
|
||||
#if defined(ESP8266)
|
||||
if (mCfg->pirPin == A0)
|
||||
return((analogRead(A0) >= 512));
|
||||
return (analogRead(A0) >= 512);
|
||||
else
|
||||
return(digitalRead(mCfg->pirPin));
|
||||
return digitalRead(mCfg->pirPin);
|
||||
#elif defined(ESP32)
|
||||
return(digitalRead(mCfg->pirPin));
|
||||
return digitalRead(mCfg->pirPin);
|
||||
#endif
|
||||
}
|
||||
else
|
||||
return(false);
|
||||
} else
|
||||
return false;
|
||||
}
|
||||
|
||||
// approximate RSSI in dB by invQuality levels from heuristic function (very unscientific but better than nothing :-) )
|
||||
|
@ -224,21 +223,21 @@ class Display {
|
|||
}
|
||||
|
||||
// private member variables
|
||||
IApp *mApp;
|
||||
IApp *mApp = nullptr;
|
||||
DisplayData mDisplayData;
|
||||
bool mNewPayload;
|
||||
uint8_t mLoopCnt;
|
||||
uint32_t *mUtcTs;
|
||||
display_t *mCfg;
|
||||
HMSYSTEM *mSys;
|
||||
RADIO *mHmRadio;
|
||||
RADIO *mHmsRadio;
|
||||
uint16_t mRefreshCycle;
|
||||
bool mNewPayload = false;
|
||||
uint8_t mLoopCnt = 0;
|
||||
uint32_t *mUtcTs = nullptr;
|
||||
display_t *mCfg = nullptr;
|
||||
HMSYSTEM *mSys = nullptr;
|
||||
RADIO *mHmRadio = nullptr;
|
||||
RADIO *mHmsRadio = nullptr;
|
||||
uint16_t mRefreshCycle = 0;
|
||||
|
||||
#if defined(ESP32) && !defined(ETHERNET)
|
||||
DisplayEPaper mEpaper;
|
||||
#endif
|
||||
DisplayMono *mMono;
|
||||
DisplayMono *mMono = nullptr;
|
||||
};
|
||||
|
||||
#endif /*PLUGIN_DISPLAY*/
|
||||
|
|
|
@ -24,8 +24,6 @@
|
|||
|
||||
class DisplayMono {
|
||||
public:
|
||||
DisplayMono() {};
|
||||
|
||||
virtual void init(DisplayData *displayData) = 0;
|
||||
virtual void config(display_t *cfg) = 0;
|
||||
virtual void disp(void) = 0;
|
||||
|
@ -289,11 +287,11 @@ class DisplayMono {
|
|||
DispSwitchState mDispSwitchState = DispSwitchState::TEXT;
|
||||
|
||||
uint16_t mDispWidth;
|
||||
uint8_t mExtra;
|
||||
uint8_t mExtra = 0;
|
||||
int8_t mPixelshift=0;
|
||||
char mFmtText[DISP_FMT_TEXT_LEN];
|
||||
uint8_t mLineXOffsets[5] = {};
|
||||
uint8_t mLineYOffsets[5] = {};
|
||||
uint8_t mLineXOffsets[5] = {0, 0, 0, 0, 0};
|
||||
uint8_t mLineYOffsets[5] = {0, 0, 0, 0, 0};
|
||||
|
||||
uint8_t mPgWidth = 0;
|
||||
|
||||
|
@ -308,8 +306,8 @@ class DisplayMono {
|
|||
uint32_t mPgLastTime = 0;
|
||||
PowerGraphState mPgState = PowerGraphState::NO_TIME_SYNC;
|
||||
|
||||
uint16_t mDispHeight;
|
||||
uint8_t mLuminance;
|
||||
uint16_t mDispHeight = 0;
|
||||
uint8_t mLuminance = 0;
|
||||
|
||||
TimeMonitor mDisplayTime = TimeMonitor(1000 * DISP_DEFAULT_TIMEOUT, true);
|
||||
TimeMonitor mDispSwitchTime = TimeMonitor();
|
||||
|
|
|
@ -12,11 +12,11 @@ class DisplayMono128X32 : public DisplayMono {
|
|||
mExtra = 0;
|
||||
}
|
||||
|
||||
void config(display_t *cfg) {
|
||||
void config(display_t *cfg) override {
|
||||
mCfg = cfg;
|
||||
}
|
||||
|
||||
void init(DisplayData *displayData) {
|
||||
void init(DisplayData *displayData) override {
|
||||
u8g2_cb_t *rot = (u8g2_cb_t *)((mCfg->rot != 0x00) ? U8G2_R2 : U8G2_R0);
|
||||
monoInit(new U8G2_SSD1306_128X32_UNIVISION_F_HW_I2C(rot, 0xff, mCfg->disp_clk, mCfg->disp_data), displayData);
|
||||
calcLinePositions();
|
||||
|
@ -26,7 +26,7 @@ class DisplayMono128X32 : public DisplayMono {
|
|||
mDisplay->sendBuffer();
|
||||
}
|
||||
|
||||
void disp(void) {
|
||||
void disp(void) override {
|
||||
mDisplay->clearBuffer();
|
||||
|
||||
// calculate current pixelshift for pixelshift screensaver
|
||||
|
|
|
@ -13,11 +13,11 @@ class DisplayMono128X64 : public DisplayMono {
|
|||
mExtra = 0;
|
||||
}
|
||||
|
||||
void config(display_t *cfg) {
|
||||
void config(display_t *cfg) override {
|
||||
mCfg = cfg;
|
||||
}
|
||||
|
||||
void init(DisplayData *displayData) {
|
||||
void init(DisplayData *displayData) override {
|
||||
u8g2_cb_t *rot = (u8g2_cb_t *)(( mCfg->rot != 0x00) ? U8G2_R2 : U8G2_R0);
|
||||
switch (mCfg->type) {
|
||||
case DISP_TYPE_T1_SSD1306_128X64:
|
||||
|
@ -68,9 +68,7 @@ class DisplayMono128X64 : public DisplayMono {
|
|||
mDisplay->sendBuffer();
|
||||
}
|
||||
|
||||
void disp(void) {
|
||||
uint8_t pos, sun_pos, moon_pos;
|
||||
|
||||
void disp(void) override {
|
||||
mDisplay->clearBuffer();
|
||||
|
||||
// Layout-Test
|
||||
|
@ -106,8 +104,8 @@ class DisplayMono128X64 : public DisplayMono {
|
|||
}
|
||||
// print status of inverters
|
||||
else {
|
||||
sun_pos = -1;
|
||||
moon_pos = -1;
|
||||
int8_t sun_pos = -1;
|
||||
int8_t moon_pos = -1;
|
||||
setLineFont(l_Status);
|
||||
if (0 == mDisplayData->nrSleeping + mDisplayData->nrProducing)
|
||||
snprintf(mFmtText, DISP_FMT_TEXT_LEN, "no inverter");
|
||||
|
@ -128,11 +126,11 @@ class DisplayMono128X64 : public DisplayMono {
|
|||
}
|
||||
printText(mFmtText, l_Status, 0xff);
|
||||
|
||||
pos = (mDispWidth - mDisplay->getStrWidth(mFmtText)) / 2;
|
||||
uint8_t pos = (mDispWidth - mDisplay->getStrWidth(mFmtText)) / 2;
|
||||
mDisplay->setFont(u8g2_font_ncenB08_symbols8_ahoy);
|
||||
if (sun_pos!=-1)
|
||||
if (sun_pos != -1)
|
||||
mDisplay->drawStr(pos + sun_pos + mPixelshift, mLineYOffsets[l_Status], "G"); // sun symbol
|
||||
if (moon_pos!=-1)
|
||||
if (moon_pos != -1)
|
||||
mDisplay->drawStr(pos + moon_pos + mPixelshift, mLineYOffsets[l_Status], "H"); // moon symbol
|
||||
}
|
||||
}
|
||||
|
@ -181,12 +179,11 @@ class DisplayMono128X64 : public DisplayMono {
|
|||
// draw dynamic RSSI bars
|
||||
int rssi_bar_height = 9;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
int radio_rssi_threshold = -60 - i * 10;
|
||||
int wifi_rssi_threshold = -60 - i * 10;
|
||||
int rssi_threshold = -60 - i * 10;
|
||||
uint8_t barwidth = std::min(4 - i, 3);
|
||||
if (mDisplayData->RadioRSSI > radio_rssi_threshold)
|
||||
if (mDisplayData->RadioRSSI > rssi_threshold)
|
||||
mDisplay->drawBox(widthShrink / 2 + mPixelshift, 8 + (rssi_bar_height + 1) * i, barwidth, rssi_bar_height);
|
||||
if (mDisplayData->WifiRSSI > wifi_rssi_threshold)
|
||||
if (mDisplayData->WifiRSSI > rssi_threshold)
|
||||
mDisplay->drawBox(mDispWidth - barwidth - widthShrink / 2 + mPixelshift, 8 + (rssi_bar_height + 1) * i, barwidth, rssi_bar_height);
|
||||
}
|
||||
// draw dynamic antenna and WiFi symbols
|
||||
|
@ -223,23 +220,22 @@ class DisplayMono128X64 : public DisplayMono {
|
|||
l_MAX_LINES = 5,
|
||||
};
|
||||
|
||||
uint8_t graph_first_line;
|
||||
uint8_t graph_last_line;
|
||||
uint8_t graph_first_line = 0;
|
||||
uint8_t graph_last_line = 0;
|
||||
|
||||
const uint8_t pixelShiftRange = 11; // number of pixels to shift from left to right (centered -> must be odd!)
|
||||
uint8_t widthShrink;
|
||||
uint8_t widthShrink = 0;
|
||||
|
||||
void calcLinePositions() {
|
||||
uint8_t yOff = 0;
|
||||
uint8_t i = 0;
|
||||
uint8_t asc, dsc;
|
||||
|
||||
do {
|
||||
setLineFont(i);
|
||||
asc = mDisplay->getAscent();
|
||||
uint8_t asc = mDisplay->getAscent();
|
||||
yOff += asc;
|
||||
mLineYOffsets[i] = yOff;
|
||||
dsc = mDisplay->getDescent();
|
||||
uint8_t dsc = mDisplay->getDescent();
|
||||
yOff -= dsc;
|
||||
if (l_Time == i) // prevent time and status line to touch
|
||||
yOff++; // -> one pixels space
|
||||
|
@ -248,8 +244,7 @@ class DisplayMono128X64 : public DisplayMono {
|
|||
}
|
||||
|
||||
inline void setLineFont(uint8_t line) {
|
||||
if ((line == l_TotalPower) ||
|
||||
(line == l_Ahoy))
|
||||
if (line == l_TotalPower) // || (line == l_Ahoy) -> l_TotalPower == l_Ahoy == 2
|
||||
mDisplay->setFont(u8g2_font_ncenB14_tr);
|
||||
else if ((line == l_YieldDay) ||
|
||||
(line == l_YieldTotal))
|
||||
|
|
|
@ -12,11 +12,11 @@ class DisplayMono64X48 : public DisplayMono {
|
|||
mExtra = 0;
|
||||
}
|
||||
|
||||
void config(display_t *cfg) {
|
||||
void config(display_t *cfg) override {
|
||||
mCfg = cfg;
|
||||
}
|
||||
|
||||
void init(DisplayData *displayData) {
|
||||
void init(DisplayData *displayData) override {
|
||||
u8g2_cb_t *rot = (u8g2_cb_t *)((mCfg->rot != 0x00) ? U8G2_R2 : U8G2_R0);
|
||||
// Wemos OLed Shield is not defined in u8 lib -> use nearest compatible
|
||||
monoInit(new U8G2_SSD1306_64X48_ER_F_HW_I2C(rot, 0xff, mCfg->disp_clk, mCfg->disp_data), displayData);
|
||||
|
@ -28,7 +28,7 @@ class DisplayMono64X48 : public DisplayMono {
|
|||
mDisplay->sendBuffer();
|
||||
}
|
||||
|
||||
void disp(void) {
|
||||
void disp(void) override {
|
||||
mDisplay->clearBuffer();
|
||||
|
||||
// calculate current pixelshift for pixelshift screensaver
|
||||
|
|
|
@ -12,11 +12,11 @@ class DisplayMono84X48 : public DisplayMono {
|
|||
mExtra = 0;
|
||||
}
|
||||
|
||||
void config(display_t *cfg) {
|
||||
void config(display_t *cfg) override {
|
||||
mCfg = cfg;
|
||||
}
|
||||
|
||||
void init(DisplayData *displayData) {
|
||||
void init(DisplayData *displayData) override {
|
||||
u8g2_cb_t *rot = (u8g2_cb_t *)((mCfg->rot != 0x00) ? U8G2_R2 : U8G2_R0);
|
||||
|
||||
monoInit(new U8G2_PCD8544_84X48_F_4W_SW_SPI(rot, mCfg->disp_clk, mCfg->disp_data, mCfg->disp_cs, mCfg->disp_dc, 0xff), displayData);
|
||||
|
@ -55,7 +55,7 @@ class DisplayMono84X48 : public DisplayMono {
|
|||
mDisplay->sendBuffer();
|
||||
}
|
||||
|
||||
void disp(void) {
|
||||
void disp(void) override {
|
||||
mDisplay->clearBuffer();
|
||||
|
||||
// Layout-Test
|
||||
|
@ -143,12 +143,11 @@ class DisplayMono84X48 : public DisplayMono {
|
|||
// draw dynamic RSSI bars
|
||||
int rssi_bar_height = 7;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
int radio_rssi_threshold = -60 - i * 10;
|
||||
int wifi_rssi_threshold = -60 - i * 10;
|
||||
int rssi_threshold = -60 - i * 10;
|
||||
uint8_t barwidth = std::min(4 - i, 3);
|
||||
if (mDisplayData->RadioRSSI > radio_rssi_threshold)
|
||||
if (mDisplayData->RadioRSSI > rssi_threshold)
|
||||
mDisplay->drawBox(0, 8 + (rssi_bar_height + 1) * i, barwidth, rssi_bar_height);
|
||||
if (mDisplayData->WifiRSSI > wifi_rssi_threshold)
|
||||
if (mDisplayData->WifiRSSI > rssi_threshold)
|
||||
mDisplay->drawBox(mDispWidth - barwidth, 8 + (rssi_bar_height + 1) * i, barwidth, rssi_bar_height);
|
||||
}
|
||||
|
||||
|
@ -184,30 +183,28 @@ class DisplayMono84X48 : public DisplayMono {
|
|||
l_MAX_LINES = 5,
|
||||
};
|
||||
|
||||
uint8_t graph_first_line;
|
||||
uint8_t graph_last_line;
|
||||
uint8_t graph_first_line = 0;
|
||||
uint8_t graph_last_line = 0;
|
||||
|
||||
void calcLinePositions() {
|
||||
uint8_t yOff = 0;
|
||||
uint8_t i = 0;
|
||||
uint8_t asc, dsc;
|
||||
|
||||
do {
|
||||
setLineFont(i);
|
||||
asc = mDisplay->getAscent();
|
||||
uint8_t asc = mDisplay->getAscent();
|
||||
yOff += asc;
|
||||
mLineYOffsets[i] = yOff;
|
||||
dsc = mDisplay->getDescent();
|
||||
uint8_t dsc = mDisplay->getDescent();
|
||||
if (l_TotalPower != i) // power line needs no descent spacing
|
||||
yOff -= dsc;
|
||||
yOff++; // instead lets spend one pixel space between all lines
|
||||
i++;
|
||||
} while(l_MAX_LINES>i);
|
||||
} while(l_MAX_LINES > i);
|
||||
}
|
||||
|
||||
inline void setLineFont(uint8_t line) {
|
||||
if ((line == l_TotalPower) ||
|
||||
(line == l_Ahoy))
|
||||
if (line == l_TotalPower) // || (line == l_Ahoy) -> l_TotalPower == l_Ahoy == 2
|
||||
mDisplay->setFont(u8g2_font_logisoso16_tr);
|
||||
else
|
||||
mDisplay->setFont(u8g2_font_5x8_symbols_ahoy);
|
||||
|
|
|
@ -67,7 +67,7 @@ void DisplayEPaper::refreshLoop() {
|
|||
case RefreshStatus::LOGO:
|
||||
_display->fillScreen(GxEPD_BLACK);
|
||||
_display->drawBitmap(0, 0, logo, 200, 200, GxEPD_WHITE);
|
||||
_display->display(false); // full update
|
||||
mSecondCnt = 2;
|
||||
mNextRefreshState = RefreshStatus::PARTITIALS;
|
||||
mRefreshState = RefreshStatus::WAIT;
|
||||
break;
|
||||
|
@ -79,11 +79,11 @@ void DisplayEPaper::refreshLoop() {
|
|||
break;
|
||||
|
||||
case RefreshStatus::WHITE:
|
||||
if(mSecondCnt == 0) {
|
||||
_display->fillScreen(GxEPD_WHITE);
|
||||
mNextRefreshState = RefreshStatus::PARTITIALS;
|
||||
mRefreshState = RefreshStatus::WAIT;
|
||||
}
|
||||
if(0 != mSecondCnt)
|
||||
break;
|
||||
_display->fillScreen(GxEPD_WHITE);
|
||||
mNextRefreshState = RefreshStatus::PARTITIALS;
|
||||
mRefreshState = RefreshStatus::WAIT;
|
||||
break;
|
||||
|
||||
case RefreshStatus::WAIT:
|
||||
|
@ -92,10 +92,13 @@ void DisplayEPaper::refreshLoop() {
|
|||
break;
|
||||
|
||||
case RefreshStatus::PARTITIALS:
|
||||
if(0 != mSecondCnt)
|
||||
break;
|
||||
headlineIP();
|
||||
versionFooter();
|
||||
mSecondCnt = 4; // display Logo time during boot up
|
||||
mRefreshState = RefreshStatus::DONE;
|
||||
mNextRefreshState = RefreshStatus::DONE;
|
||||
mRefreshState = RefreshStatus::WAIT;
|
||||
break;
|
||||
|
||||
default: // RefreshStatus::DONE
|
||||
|
|
|
@ -24,23 +24,15 @@ template<class HMSYSTEM>
|
|||
class HistoryData {
|
||||
private:
|
||||
struct storage_t {
|
||||
uint16_t refreshCycle;
|
||||
uint16_t loopCnt;
|
||||
uint16_t listIdx; // index for next Element to write into WattArr
|
||||
uint16_t dispIdx; // index for 1st Element to display from WattArr
|
||||
bool wrapped;
|
||||
uint16_t refreshCycle = 0;
|
||||
uint16_t loopCnt = 0;
|
||||
uint16_t listIdx = 0; // index for next Element to write into WattArr
|
||||
uint16_t dispIdx = 0; // index for 1st Element to display from WattArr
|
||||
bool wrapped = false;
|
||||
// ring buffer for watt history
|
||||
std::array<uint16_t, (HISTORY_DATA_ARR_LENGTH + 1)> data;
|
||||
|
||||
void reset() {
|
||||
loopCnt = 0;
|
||||
listIdx = 0;
|
||||
dispIdx = 0;
|
||||
wrapped = false;
|
||||
for(uint16_t i = 0; i < (HISTORY_DATA_ARR_LENGTH + 1); i++) {
|
||||
data[i] = 0;
|
||||
}
|
||||
}
|
||||
storage_t() { data.fill(0); }
|
||||
};
|
||||
|
||||
public:
|
||||
|
@ -50,21 +42,18 @@ class HistoryData {
|
|||
mConfig = config;
|
||||
mTs = ts;
|
||||
|
||||
mCurPwr.reset();
|
||||
mCurPwr.refreshCycle = mConfig->inst.sendInterval;
|
||||
mYieldDay.reset();
|
||||
mYieldDay.refreshCycle = 60;
|
||||
//mYieldDay.refreshCycle = 60;
|
||||
}
|
||||
|
||||
void tickerSecond() {
|
||||
Inverter<> *iv;
|
||||
record_t<> *rec;
|
||||
;
|
||||
float curPwr = 0;
|
||||
float maxPwr = 0;
|
||||
float yldDay = -0.1;
|
||||
for (uint8_t i = 0; i < mSys->getNumInverters(); i++) {
|
||||
iv = mSys->getInverterByPos(i);
|
||||
rec = iv->getRecordStruct(RealTimeRunData_Debug);
|
||||
Inverter<> *iv = mSys->getInverterByPos(i);
|
||||
record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug);
|
||||
if (iv == NULL)
|
||||
continue;
|
||||
curPwr += iv->getChannelFieldValue(CH0, FLD_PAC, rec);
|
||||
|
@ -80,7 +69,7 @@ class HistoryData {
|
|||
mMaximumDay = roundf(maxPwr);
|
||||
}
|
||||
|
||||
if((++mYieldDay.loopCnt % mYieldDay.refreshCycle) == 0) {
|
||||
/*if((++mYieldDay.loopCnt % mYieldDay.refreshCycle) == 0) {
|
||||
if (*mTs > mApp->getSunset()) {
|
||||
if ((!mDayStored) && (yldDay > 0)) {
|
||||
addValue(&mYieldDay, roundf(yldDay));
|
||||
|
@ -88,11 +77,12 @@ class HistoryData {
|
|||
}
|
||||
} else if (*mTs > mApp->getSunrise())
|
||||
mDayStored = false;
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
uint16_t valueAt(HistoryStorageType type, uint16_t i) {
|
||||
storage_t *s = (HistoryStorageType::POWER == type) ? &mCurPwr : &mYieldDay;
|
||||
//storage_t *s = (HistoryStorageType::POWER == type) ? &mCurPwr : &mYieldDay;
|
||||
storage_t *s = &mCurPwr;
|
||||
uint16_t idx = (s->dispIdx + i) % HISTORY_DATA_ARR_LENGTH;
|
||||
return s->data[idx];
|
||||
}
|
||||
|
@ -112,14 +102,13 @@ class HistoryData {
|
|||
}
|
||||
|
||||
private:
|
||||
IApp *mApp;
|
||||
HMSYSTEM *mSys;
|
||||
settings *mSettings;
|
||||
settings_t *mConfig;
|
||||
uint32_t *mTs;
|
||||
IApp *mApp = nullptr;
|
||||
HMSYSTEM *mSys = nullptr;
|
||||
settings *mSettings = nullptr;
|
||||
settings_t *mConfig = nullptr;
|
||||
uint32_t *mTs = nullptr;
|
||||
|
||||
storage_t mCurPwr;
|
||||
storage_t mYieldDay;
|
||||
bool mDayStored = false;
|
||||
uint16_t mMaximumDay = 0;
|
||||
};
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
#include <WiFi.h>
|
||||
#endif
|
||||
|
||||
#include <array>
|
||||
#if defined(ETHERNET)
|
||||
#include "../eth/ahoyeth.h"
|
||||
#endif
|
||||
|
@ -41,13 +42,16 @@ typedef struct {
|
|||
template<class HMSYSTEM>
|
||||
class PubMqtt {
|
||||
public:
|
||||
PubMqtt() {
|
||||
mRxCnt = 0;
|
||||
mTxCnt = 0;
|
||||
mSubscriptionCb = NULL;
|
||||
memset(mLastIvState, (uint8_t)InverterStatus::OFF, MAX_NUM_INVERTERS);
|
||||
memset(mIvLastRTRpub, 0, MAX_NUM_INVERTERS * 4);
|
||||
mLastAnyAvail = false;
|
||||
PubMqtt() : SendIvData() {
|
||||
mLastIvState.fill(InverterStatus::OFF);
|
||||
mIvLastRTRpub.fill(0);
|
||||
|
||||
mVal.fill(0);
|
||||
mTopic.fill(0);
|
||||
mSubTopic.fill(0);
|
||||
mClientId.fill(0);
|
||||
mLwtTopic.fill(0);
|
||||
mSendAlarm.fill(false);
|
||||
}
|
||||
|
||||
~PubMqtt() { }
|
||||
|
@ -61,22 +65,22 @@ class PubMqtt {
|
|||
mUptime = uptime;
|
||||
mIntervalTimeout = 1;
|
||||
|
||||
mSendIvData.setup(sys, utcTs, &mSendList);
|
||||
mSendIvData.setPublishFunc([this](const char *subTopic, const char *payload, bool retained, uint8_t qos) {
|
||||
SendIvData.setup(sys, utcTs, &mSendList);
|
||||
SendIvData.setPublishFunc([this](const char *subTopic, const char *payload, bool retained, uint8_t qos) {
|
||||
publish(subTopic, payload, retained, true, qos);
|
||||
});
|
||||
mDiscovery.running = false;
|
||||
|
||||
snprintf(mLwtTopic, MQTT_TOPIC_LEN + 5, "%s/mqtt", mCfgMqtt->topic);
|
||||
snprintf(mLwtTopic.data(), mLwtTopic.size(), "%s/mqtt", mCfgMqtt->topic);
|
||||
|
||||
if((strlen(mCfgMqtt->user) > 0) && (strlen(mCfgMqtt->pwd) > 0))
|
||||
mClient.setCredentials(mCfgMqtt->user, mCfgMqtt->pwd);
|
||||
|
||||
if(strlen(mCfgMqtt->clientId) > 0)
|
||||
snprintf(mClientId, 23, "%s", mCfgMqtt->clientId);
|
||||
snprintf(mClientId.data(), mClientId.size(), "%s", mCfgMqtt->clientId);
|
||||
else {
|
||||
snprintf(mClientId, 23, "%s-", mDevName);
|
||||
uint8_t pos = strlen(mClientId);
|
||||
snprintf(mClientId.data(), mClientId.size(), "%s-", mDevName);
|
||||
uint8_t pos = strlen(mClientId.data());
|
||||
mClientId[pos++] = WiFi.macAddress().substring( 9, 10).c_str()[0];
|
||||
mClientId[pos++] = WiFi.macAddress().substring(10, 11).c_str()[0];
|
||||
mClientId[pos++] = WiFi.macAddress().substring(12, 13).c_str()[0];
|
||||
|
@ -86,16 +90,16 @@ class PubMqtt {
|
|||
mClientId[pos++] = '\0';
|
||||
}
|
||||
|
||||
mClient.setClientId(mClientId);
|
||||
mClient.setClientId(mClientId.data());
|
||||
mClient.setServer(mCfgMqtt->broker, mCfgMqtt->port);
|
||||
mClient.setWill(mLwtTopic, QOS_0, true, mqttStr[MQTT_STR_LWT_NOT_CONN]);
|
||||
mClient.setWill(mLwtTopic.data(), QOS_0, true, mqttStr[MQTT_STR_LWT_NOT_CONN]);
|
||||
mClient.onConnect(std::bind(&PubMqtt::onConnect, this, std::placeholders::_1));
|
||||
mClient.onDisconnect(std::bind(&PubMqtt::onDisconnect, this, std::placeholders::_1));
|
||||
mClient.onMessage(std::bind(&PubMqtt::onMessage, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5, std::placeholders::_6));
|
||||
}
|
||||
|
||||
void loop() {
|
||||
mSendIvData.loop();
|
||||
SendIvData.loop();
|
||||
|
||||
#if defined(ESP8266)
|
||||
mClient.loop();
|
||||
|
@ -129,8 +133,8 @@ class PubMqtt {
|
|||
}
|
||||
|
||||
void tickerMinute() {
|
||||
snprintf(mVal, 40, "%d", (*mUptime));
|
||||
publish(subtopics[MQTT_UPTIME], mVal);
|
||||
snprintf(mVal.data(), mVal.size(), "%u", (*mUptime));
|
||||
publish(subtopics[MQTT_UPTIME], mVal.data());
|
||||
publish(subtopics[MQTT_RSSI], String(WiFi.RSSI()).c_str());
|
||||
publish(subtopics[MQTT_FREE_HEAP], String(ESP.getFreeHeap()).c_str());
|
||||
#ifndef ESP32
|
||||
|
@ -147,25 +151,24 @@ class PubMqtt {
|
|||
publish(subtopics[MQTT_COMM_START], String(sunrise + offsM).c_str(), true);
|
||||
publish(subtopics[MQTT_COMM_STOP], String(sunset + offsE).c_str(), true);
|
||||
|
||||
Inverter<> *iv;
|
||||
for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i++) {
|
||||
iv = mSys->getInverterByPos(i);
|
||||
Inverter<> *iv = mSys->getInverterByPos(i);
|
||||
if(NULL == iv)
|
||||
continue;
|
||||
|
||||
snprintf(mSubTopic, 32 + MAX_NAME_LENGTH, "%s/dis_night_comm", iv->config->name);
|
||||
publish(mSubTopic, ((iv->commEnabled) ? dict[STR_TRUE] : dict[STR_FALSE]), true);
|
||||
snprintf(mSubTopic.data(), mSubTopic.size(), "%s/dis_night_comm", iv->config->name);
|
||||
publish(mSubTopic.data(), ((iv->commEnabled) ? dict[STR_TRUE] : dict[STR_FALSE]), true);
|
||||
}
|
||||
|
||||
snprintf(mSubTopic, 32 + MAX_NAME_LENGTH, "comm_disabled");
|
||||
publish(mSubTopic, (((*mUtcTimestamp > (sunset + offsE)) || (*mUtcTimestamp < (sunrise + offsM))) ? dict[STR_TRUE] : dict[STR_FALSE]), true);
|
||||
snprintf(mSubTopic.data(), mSubTopic.size(), "comm_disabled");
|
||||
publish(mSubTopic.data(), (((*mUtcTimestamp > (sunset + offsE)) || (*mUtcTimestamp < (sunrise + offsM))) ? dict[STR_TRUE] : dict[STR_FALSE]), true);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void notAvailChanged(bool allNotAvail) {
|
||||
if(!allNotAvail)
|
||||
mSendIvData.resetYieldDay();
|
||||
SendIvData.resetYieldDay();
|
||||
}
|
||||
|
||||
bool tickerComm(bool disabled) {
|
||||
|
@ -180,9 +183,9 @@ class PubMqtt {
|
|||
|
||||
void tickerMidnight() {
|
||||
// set Total YieldDay to zero
|
||||
snprintf(mSubTopic, 32 + MAX_NAME_LENGTH, "total/%s", fields[FLD_YD]);
|
||||
snprintf(mVal, 2, "0");
|
||||
publish(mSubTopic, mVal, true);
|
||||
snprintf(mSubTopic.data(), mSubTopic.size(), "total/%s", fields[FLD_YD]);
|
||||
snprintf(mVal.data(), mVal.size(), "0");
|
||||
publish(mSubTopic.data(), mVal.data(), true);
|
||||
}
|
||||
|
||||
void payloadEventListener(uint8_t cmd, Inverter<> *iv) {
|
||||
|
@ -201,11 +204,11 @@ class PubMqtt {
|
|||
return;
|
||||
|
||||
if(addTopic)
|
||||
snprintf(mTopic, MQTT_TOPIC_LEN + 32 + MAX_NAME_LENGTH + 1, "%s/%s", mCfgMqtt->topic, subTopic);
|
||||
snprintf(mTopic.data(), mTopic.size(), "%s/%s", mCfgMqtt->topic, subTopic);
|
||||
else
|
||||
snprintf(mTopic, MQTT_TOPIC_LEN + 32 + MAX_NAME_LENGTH + 1, "%s", subTopic);
|
||||
snprintf(mTopic.data(), mTopic.size(), "%s", subTopic);
|
||||
|
||||
mClient.publish(mTopic, qos, retained, payload);
|
||||
mClient.publish(mTopic.data(), qos, retained, payload);
|
||||
yield();
|
||||
mTxCnt++;
|
||||
}
|
||||
|
@ -242,8 +245,8 @@ class PubMqtt {
|
|||
|
||||
void setPowerLimitAck(Inverter<> *iv) {
|
||||
if (NULL != iv) {
|
||||
snprintf(mSubTopic, 32 + MAX_NAME_LENGTH, "%s/%s", iv->config->name, subtopics[MQTT_ACK_PWR_LMT]);
|
||||
publish(mSubTopic, "true", true, true, QOS_2);
|
||||
snprintf(mSubTopic.data(), mSubTopic.size(), "%s/%s", iv->config->name, subtopics[MQTT_ACK_PWR_LMT]);
|
||||
publish(mSubTopic.data(), "true", true, true, QOS_2);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -259,15 +262,15 @@ class PubMqtt {
|
|||
publish(subtopics[MQTT_IP_ADDR], WiFi.localIP().toString().c_str(), true);
|
||||
#endif
|
||||
tickerMinute();
|
||||
publish(mLwtTopic, 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++) {
|
||||
snprintf(mVal, 20, "ctrl/limit/%d", i);
|
||||
subscribe(mVal, QOS_2);
|
||||
snprintf(mVal, 20, "ctrl/restart/%d", i);
|
||||
subscribe(mVal);
|
||||
snprintf(mVal, 20, "ctrl/power/%d", i);
|
||||
subscribe(mVal);
|
||||
snprintf(mVal.data(), mVal.size(), "ctrl/limit/%d", i);
|
||||
subscribe(mVal.data(), QOS_2);
|
||||
snprintf(mVal.data(), mVal.size(), "ctrl/restart/%d", i);
|
||||
subscribe(mVal.data());
|
||||
snprintf(mVal.data(), mVal.size(), "ctrl/power/%d", i);
|
||||
subscribe(mVal.data());
|
||||
}
|
||||
subscribe(subscr[MQTT_SUBS_SET_TIME]);
|
||||
}
|
||||
|
@ -317,7 +320,7 @@ class PubMqtt {
|
|||
if(NULL == strstr(topic, "limit"))
|
||||
root[F("val")] = atoi(pyld);
|
||||
else
|
||||
root[F("val")] = (int)(atof(pyld) * 10.0f);
|
||||
root[F("val")] = atof(pyld);
|
||||
|
||||
if(pyld[len-1] == 'W')
|
||||
limitAbs = true;
|
||||
|
@ -363,11 +366,9 @@ class PubMqtt {
|
|||
}
|
||||
|
||||
void discoveryConfigLoop(void) {
|
||||
char topic[64], name[32], uniq_id[32], buf[350];
|
||||
DynamicJsonDocument doc(256);
|
||||
|
||||
uint8_t fldTotal[4] = {FLD_PAC, FLD_YT, FLD_YD, FLD_PDC};
|
||||
const char* unitTotal[4] = {"W", "kWh", "Wh", "W"};
|
||||
constexpr static uint8_t fldTotal[] = {FLD_PAC, FLD_YT, FLD_YD, FLD_PDC};
|
||||
|
||||
String node_id = String(mDevName) + "_TOTAL";
|
||||
bool total = (mDiscovery.lastIvId == MAX_NUM_INVERTERS);
|
||||
|
@ -396,32 +397,41 @@ class PubMqtt {
|
|||
doc[F("mf")] = F("Hoymiles");
|
||||
JsonObject deviceObj = doc.as<JsonObject>(); // deviceObj is only pointer!?
|
||||
|
||||
std::array<char, 64> topic;
|
||||
std::array<char, 32> name;
|
||||
std::array<char, 32> uniq_id;
|
||||
std::array<char, 350> buf;
|
||||
topic.fill(0);
|
||||
name.fill(0);
|
||||
uniq_id.fill(0);
|
||||
buf.fill(0);
|
||||
const char *devCls, *stateCls;
|
||||
if (!total) {
|
||||
if (rec->assign[mDiscovery.sub].ch == CH0)
|
||||
snprintf(name, 32, "%s", iv->getFieldName(mDiscovery.sub, rec));
|
||||
snprintf(name.data(), name.size(), "%s", iv->getFieldName(mDiscovery.sub, rec));
|
||||
else
|
||||
snprintf(name, 32, "CH%d_%s", rec->assign[mDiscovery.sub].ch, iv->getFieldName(mDiscovery.sub, rec));
|
||||
snprintf(topic, 64, "/ch%d/%s", rec->assign[mDiscovery.sub].ch, iv->getFieldName(mDiscovery.sub, rec));
|
||||
snprintf(uniq_id, 32, "ch%d_%s", rec->assign[mDiscovery.sub].ch, iv->getFieldName(mDiscovery.sub, rec));
|
||||
snprintf(name.data(), name.size(), "CH%d_%s", rec->assign[mDiscovery.sub].ch, iv->getFieldName(mDiscovery.sub, rec));
|
||||
snprintf(topic.data(), name.size(), "/ch%d/%s", rec->assign[mDiscovery.sub].ch, iv->getFieldName(mDiscovery.sub, rec));
|
||||
snprintf(uniq_id.data(), uniq_id.size(), "ch%d_%s", rec->assign[mDiscovery.sub].ch, iv->getFieldName(mDiscovery.sub, rec));
|
||||
|
||||
devCls = getFieldDeviceClass(rec->assign[mDiscovery.sub].fieldId);
|
||||
stateCls = getFieldStateClass(rec->assign[mDiscovery.sub].fieldId);
|
||||
}
|
||||
|
||||
else { // total values
|
||||
snprintf(name, 32, "Total %s", fields[fldTotal[mDiscovery.sub]]);
|
||||
snprintf(topic, 64, "/%s", fields[fldTotal[mDiscovery.sub]]);
|
||||
snprintf(uniq_id, 32, "total_%s", fields[fldTotal[mDiscovery.sub]]);
|
||||
snprintf(name.data(), name.size(), "Total %s", fields[fldTotal[mDiscovery.sub]]);
|
||||
snprintf(topic.data(), topic.size(), "/%s", fields[fldTotal[mDiscovery.sub]]);
|
||||
snprintf(uniq_id.data(), uniq_id.size(), "total_%s", fields[fldTotal[mDiscovery.sub]]);
|
||||
devCls = getFieldDeviceClass(fldTotal[mDiscovery.sub]);
|
||||
stateCls = getFieldStateClass(fldTotal[mDiscovery.sub]);
|
||||
}
|
||||
|
||||
DynamicJsonDocument doc2(512);
|
||||
doc2[F("name")] = name;
|
||||
doc2[F("stat_t")] = String(mCfgMqtt->topic) + "/" + ((!total) ? String(iv->config->name) : "total" ) + String(topic);
|
||||
constexpr static const char* unitTotal[] = {"W", "kWh", "Wh", "W"};
|
||||
doc2[F("name")] = String(name.data());
|
||||
doc2[F("stat_t")] = String(mCfgMqtt->topic) + "/" + ((!total) ? String(iv->config->name) : "total" ) + String(topic.data());
|
||||
doc2[F("unit_of_meas")] = ((!total) ? (iv->getUnit(mDiscovery.sub, rec)) : (unitTotal[mDiscovery.sub]));
|
||||
doc2[F("uniq_id")] = ((!total) ? (String(iv->config->serial.u64, HEX)) : (node_id)) + "_" + uniq_id;
|
||||
doc2[F("uniq_id")] = ((!total) ? (String(iv->config->serial.u64, HEX)) : (node_id)) + "_" + uniq_id.data();
|
||||
doc2[F("dev")] = deviceObj;
|
||||
if (!(String(stateCls) == String("total_increasing")))
|
||||
doc2[F("exp_aft")] = MQTT_INTERVAL + 5; // add 5 sec if connection is bad or ESP too slow @TODO: stimmt das wirklich als expire!?
|
||||
|
@ -431,13 +441,12 @@ class PubMqtt {
|
|||
doc2[F("stat_cla")] = String(stateCls);
|
||||
|
||||
if (!total)
|
||||
snprintf(topic, 64, "%s/sensor/%s/ch%d_%s/config", MQTT_DISCOVERY_PREFIX, iv->config->name, rec->assign[mDiscovery.sub].ch, iv->getFieldName(mDiscovery.sub, rec));
|
||||
snprintf(topic.data(), topic.size(), "%s/sensor/%s/ch%d_%s/config", MQTT_DISCOVERY_PREFIX, iv->config->name, rec->assign[mDiscovery.sub].ch, iv->getFieldName(mDiscovery.sub, rec));
|
||||
else // total values
|
||||
snprintf(topic, 64, "%s/sensor/%s/total_%s/config", MQTT_DISCOVERY_PREFIX, node_id.c_str(), fields[fldTotal[mDiscovery.sub]]);
|
||||
snprintf(topic.data(), topic.size(), "%s/sensor/%s/total_%s/config", MQTT_DISCOVERY_PREFIX, node_id.c_str(), fields[fldTotal[mDiscovery.sub]]);
|
||||
size_t size = measureJson(doc2) + 1;
|
||||
memset(buf, 0, size);
|
||||
serializeJson(doc2, buf, size);
|
||||
publish(topic, buf, true, false);
|
||||
serializeJson(doc2, buf.data(), size);
|
||||
publish(topic.data(), buf.data(), true, false);
|
||||
|
||||
if(++mDiscovery.sub == ((!total) ? (rec->length) : 4)) {
|
||||
mDiscovery.sub = 0;
|
||||
|
@ -507,15 +516,15 @@ class PubMqtt {
|
|||
mLastIvState[id] = status;
|
||||
changed = true;
|
||||
|
||||
snprintf(mSubTopic, 32 + MAX_NAME_LENGTH, "%s/available", iv->config->name);
|
||||
snprintf(mVal, 40, "%d", (uint8_t)status);
|
||||
publish(mSubTopic, mVal, true);
|
||||
snprintf(mSubTopic.data(), mSubTopic.size(), "%s/available", iv->config->name);
|
||||
snprintf(mVal.data(), mVal.size(), "%d", (uint8_t)status);
|
||||
publish(mSubTopic.data(), mVal.data(), true);
|
||||
}
|
||||
}
|
||||
|
||||
if(changed) {
|
||||
snprintf(mVal, 32, "%d", ((allAvail) ? MQTT_STATUS_ONLINE : ((anyAvail) ? MQTT_STATUS_PARTIAL : MQTT_STATUS_OFFLINE)));
|
||||
publish("status", mVal, true);
|
||||
snprintf(mVal.data(), mVal.size(), "%d", ((allAvail) ? MQTT_STATUS_ONLINE : ((anyAvail) ? MQTT_STATUS_PARTIAL : MQTT_STATUS_OFFLINE)));
|
||||
publish("status", mVal.data(), true);
|
||||
}
|
||||
|
||||
return anyAvail;
|
||||
|
@ -537,19 +546,19 @@ class PubMqtt {
|
|||
|
||||
mSendAlarm[i] = false;
|
||||
|
||||
snprintf(mSubTopic, 32 + MAX_NAME_LENGTH, "%s/alarm/cnt", iv->config->name);
|
||||
snprintf(mVal, 40, "%d", iv->alarmCnt);
|
||||
publish(mSubTopic, mVal, false);
|
||||
snprintf(mSubTopic.data(), mSubTopic.size(), "%s/alarm/cnt", iv->config->name);
|
||||
snprintf(mVal.data(), mVal.size(), "%d", iv->alarmCnt);
|
||||
publish(mSubTopic.data(), mVal.data(), false);
|
||||
|
||||
for(uint8_t j = 0; j < 10; j++) {
|
||||
if(0 != iv->lastAlarm[j].code) {
|
||||
snprintf(mSubTopic, 32 + MAX_NAME_LENGTH, "%s/alarm/%d", iv->config->name, j);
|
||||
snprintf(mVal, 100, "{\"code\":%d,\"str\":\"%s\",\"start\":%d,\"end\":%d}",
|
||||
snprintf(mSubTopic.data(), mSubTopic.size(), "%s/alarm/%d", iv->config->name, j);
|
||||
snprintf(mVal.data(), mVal.size(), "{\"code\":%d,\"str\":\"%s\",\"start\":%d,\"end\":%d}",
|
||||
iv->lastAlarm[j].code,
|
||||
iv->getAlarmStr(iv->lastAlarm[j].code).c_str(),
|
||||
iv->lastAlarm[j].start + lastMidnight,
|
||||
iv->lastAlarm[j].end + lastMidnight);
|
||||
publish(mSubTopic, mVal, false);
|
||||
publish(mSubTopic.data(), mVal.data(), false);
|
||||
yield();
|
||||
}
|
||||
}
|
||||
|
@ -579,9 +588,9 @@ class PubMqtt {
|
|||
}
|
||||
}
|
||||
|
||||
snprintf(mSubTopic, 32 + MAX_NAME_LENGTH, "%s/ch%d/%s", iv->config->name, rec->assign[i].ch, fields[rec->assign[i].fieldId]);
|
||||
snprintf(mVal, 40, "%g", ah::round3(iv->getValue(i, rec)));
|
||||
publish(mSubTopic, mVal, retained);
|
||||
snprintf(mSubTopic.data(), mSubTopic.size(), "%s/ch%d/%s", iv->config->name, rec->assign[i].ch, fields[rec->assign[i].fieldId]);
|
||||
snprintf(mVal.data(), mVal.size(), "%g", ah::round3(iv->getValue(i, rec)));
|
||||
publish(mSubTopic.data(), mVal.data(), retained);
|
||||
|
||||
yield();
|
||||
}
|
||||
|
@ -596,38 +605,38 @@ class PubMqtt {
|
|||
if(mSendList.empty())
|
||||
return;
|
||||
|
||||
mSendIvData.start();
|
||||
SendIvData.start();
|
||||
mLastAnyAvail = anyAvail;
|
||||
}
|
||||
|
||||
espMqttClient mClient;
|
||||
cfgMqtt_t *mCfgMqtt;
|
||||
cfgMqtt_t *mCfgMqtt = nullptr;
|
||||
#if defined(ESP8266)
|
||||
WiFiEventHandler mHWifiCon, mHWifiDiscon;
|
||||
#endif
|
||||
|
||||
HMSYSTEM *mSys;
|
||||
PubMqttIvData<HMSYSTEM> mSendIvData;
|
||||
HMSYSTEM *mSys = nullptr;
|
||||
PubMqttIvData<HMSYSTEM> SendIvData;
|
||||
|
||||
uint32_t *mUtcTimestamp, *mUptime;
|
||||
uint32_t mRxCnt, mTxCnt;
|
||||
uint32_t *mUtcTimestamp = nullptr, *mUptime = nullptr;
|
||||
uint32_t mRxCnt = 0, mTxCnt = 0;
|
||||
std::queue<sendListCmdIv> mSendList;
|
||||
std::array<bool, MAX_NUM_INVERTERS> mSendAlarm{};
|
||||
subscriptionCb mSubscriptionCb;
|
||||
bool mLastAnyAvail;
|
||||
InverterStatus mLastIvState[MAX_NUM_INVERTERS];
|
||||
uint32_t mIvLastRTRpub[MAX_NUM_INVERTERS];
|
||||
uint16_t mIntervalTimeout;
|
||||
std::array<bool, MAX_NUM_INVERTERS> mSendAlarm;
|
||||
subscriptionCb mSubscriptionCb = nullptr;
|
||||
bool mLastAnyAvail = false;
|
||||
std::array<InverterStatus, MAX_NUM_INVERTERS> mLastIvState;
|
||||
std::array<uint32_t, MAX_NUM_INVERTERS> mIvLastRTRpub;
|
||||
uint16_t mIntervalTimeout = 0;
|
||||
|
||||
// last will topic and payload must be available through lifetime of 'espMqttClient'
|
||||
char mLwtTopic[MQTT_TOPIC_LEN+5];
|
||||
const char *mDevName, *mVersion;
|
||||
char mClientId[24]; // number of chars is limited to 23 up to v3.1 of MQTT
|
||||
std::array<char, (MQTT_TOPIC_LEN + 5)> mLwtTopic;
|
||||
const char *mDevName = nullptr, *mVersion = nullptr;
|
||||
std::array<char, 24> mClientId; // number of chars is limited to 23 up to v3.1 of MQTT
|
||||
// global buffer for mqtt topic. Used when publishing mqtt messages.
|
||||
char mTopic[MQTT_TOPIC_LEN + 32 + MAX_NAME_LENGTH + 1];
|
||||
char mSubTopic[32 + MAX_NAME_LENGTH + 1];
|
||||
char mVal[100];
|
||||
discovery_t mDiscovery;
|
||||
std::array<char, (MQTT_TOPIC_LEN + 32 + MAX_NAME_LENGTH + 1)> mTopic;
|
||||
std::array<char, (32 + MAX_NAME_LENGTH + 1)> mSubTopic;
|
||||
std::array<char, 100> mVal;
|
||||
discovery_t mDiscovery = {true, 0, 0, 0};
|
||||
};
|
||||
|
||||
#endif /*ENABLE_MQTT*/
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
//-----------------------------------------------------------------------------
|
||||
// 2024 Ahoy, https://ahoydtu.de
|
||||
// Creative Commons - https://creativecommons.org/licenses/by-nc-sa/4.0/deed
|
||||
//-----------------------------------------------------------------------------
|
||||
|
@ -6,6 +6,7 @@
|
|||
#ifndef __PUB_MQTT_IV_DATA_H__
|
||||
#define __PUB_MQTT_IV_DATA_H__
|
||||
|
||||
#include <array>
|
||||
#include "../utils/dbg.h"
|
||||
#include "../hm/hmSystem.h"
|
||||
#include "pubMqttDefs.h"
|
||||
|
@ -21,6 +22,8 @@ struct sendListCmdIv {
|
|||
template<class HMSYSTEM>
|
||||
class PubMqttIvData {
|
||||
public:
|
||||
PubMqttIvData() : mTotal{}, mSubTopic{}, mVal{} {}
|
||||
|
||||
void setup(HMSYSTEM *sys, uint32_t *utcTs, std::queue<sendListCmdIv> *sendList) {
|
||||
mSys = sys;
|
||||
mUtcTimestamp = utcTs;
|
||||
|
@ -72,13 +75,14 @@ class PubMqttIvData {
|
|||
mTotalFound = false;
|
||||
mSendTotalYd = true;
|
||||
mAllTotalFound = true;
|
||||
mAtLeastOneWasntSent = false;
|
||||
if(!mSendList->empty()) {
|
||||
mCmd = mSendList->front().cmd;
|
||||
mIvSend = mSendList->front().iv;
|
||||
|
||||
if((RealTimeRunData_Debug != mCmd) || !mRTRDataHasBeenSent) { // send RealTimeRunData only once
|
||||
mSendTotals = (RealTimeRunData_Debug == mCmd);
|
||||
memset(mTotal, 0, sizeof(float) * 4);
|
||||
memset(mTotal, 0, sizeof(float) * 5);
|
||||
mState = FIND_NXT_IV;
|
||||
} else
|
||||
mSendList->pop();
|
||||
|
@ -105,21 +109,21 @@ class PubMqttIvData {
|
|||
if(found) {
|
||||
record_t<> *rec = mIv->getRecordStruct(mCmd);
|
||||
if(MqttSentStatus::NEW_DATA == rec->mqttSentStatus) {
|
||||
snprintf(mSubTopic, 32 + MAX_NAME_LENGTH, "%s/last_success", mIv->config->name);
|
||||
snprintf(mVal, 40, "%d", mIv->getLastTs(rec));
|
||||
mPublish(mSubTopic, mVal, true, QOS_0);
|
||||
snprintf(mSubTopic.data(), mSubTopic.size(), "%s/last_success", mIv->config->name);
|
||||
snprintf(mVal.data(), mVal.size(), "%d", mIv->getLastTs(rec));
|
||||
mPublish(mSubTopic.data(), mVal.data(), true, QOS_0);
|
||||
|
||||
if((mIv->ivGen == IV_HMS) || (mIv->ivGen == IV_HMT)) {
|
||||
snprintf(mSubTopic, 32 + MAX_NAME_LENGTH, "%s/ch0/rssi", mIv->config->name);
|
||||
snprintf(mVal, 40, "%d", mIv->rssi);
|
||||
mPublish(mSubTopic, mVal, false, QOS_0);
|
||||
snprintf(mSubTopic.data(), mSubTopic.size(), "%s/ch0/rssi", mIv->config->name);
|
||||
snprintf(mVal.data(), mVal.size(), "%d", mIv->rssi);
|
||||
mPublish(mSubTopic.data(), mVal.data(), false, QOS_0);
|
||||
}
|
||||
rec->mqttSentStatus = MqttSentStatus::LAST_SUCCESS_SENT;
|
||||
}
|
||||
|
||||
mIv->isProducing(); // recalculate status
|
||||
mState = SEND_DATA;
|
||||
} else if(mSendTotals && mTotalFound) {
|
||||
} else if(mSendTotals && mTotalFound && mAtLeastOneWasntSent) {
|
||||
if(mYldTotalStore > mTotal[2])
|
||||
mSendTotalYd = false; // don't send yield total if last value was greater
|
||||
else
|
||||
|
@ -142,30 +146,31 @@ class PubMqttIvData {
|
|||
|
||||
if(mPos < rec->length) {
|
||||
bool retained = false;
|
||||
if (mCmd == RealTimeRunData_Debug) {
|
||||
if (RealTimeRunData_Debug == mCmd) {
|
||||
if((FLD_YT == rec->assign[mPos].fieldId) || (FLD_YD == rec->assign[mPos].fieldId))
|
||||
retained = true;
|
||||
|
||||
// calculate total values for RealTimeRunData_Debug
|
||||
if (CH0 == rec->assign[mPos].ch) {
|
||||
if(mIv->getStatus() != InverterStatus::OFF) {
|
||||
if(mIv->config->add2Total) {
|
||||
mTotalFound = true;
|
||||
switch (rec->assign[mPos].fieldId) {
|
||||
case FLD_PAC:
|
||||
mTotal[0] += mIv->getValue(mPos, rec);
|
||||
break;
|
||||
case FLD_YT:
|
||||
mTotal[1] += mIv->getValue(mPos, rec);
|
||||
break;
|
||||
case FLD_YD: {
|
||||
mTotal[2] += mIv->getValue(mPos, rec);
|
||||
break;
|
||||
}
|
||||
case FLD_PDC:
|
||||
mTotal[3] += mIv->getValue(mPos, rec);
|
||||
break;
|
||||
mTotalFound = true;
|
||||
switch (rec->assign[mPos].fieldId) {
|
||||
case FLD_PAC:
|
||||
mTotal[0] += mIv->getValue(mPos, rec);
|
||||
break;
|
||||
case FLD_YT:
|
||||
mTotal[1] += mIv->getValue(mPos, rec);
|
||||
break;
|
||||
case FLD_YD: {
|
||||
mTotal[2] += mIv->getValue(mPos, rec);
|
||||
break;
|
||||
}
|
||||
case FLD_PDC:
|
||||
mTotal[3] += mIv->getValue(mPos, rec);
|
||||
break;
|
||||
case FLD_MP:
|
||||
mTotal[4] += mIv->getValue(mPos, rec);
|
||||
break;
|
||||
}
|
||||
} else
|
||||
mAllTotalFound = false;
|
||||
|
@ -173,10 +178,33 @@ class PubMqttIvData {
|
|||
}
|
||||
|
||||
if (MqttSentStatus::LAST_SUCCESS_SENT == rec->mqttSentStatus) {
|
||||
mAtLeastOneWasntSent = true;
|
||||
if(InverterDevInform_All == mCmd) {
|
||||
snprintf(mSubTopic.data(), mSubTopic.size(), "%s/firmware", mIv->config->name);
|
||||
snprintf(mVal.data(), mVal.size(), "{\"version\":%d,\"build_year\":\"%d\",\"build_month_day\":%d,\"build_hour_min\":%d,\"bootloader\":%d}",
|
||||
static_cast<int>(mIv->getChannelFieldValue(CH0, FLD_FW_VERSION, rec)),
|
||||
static_cast<int>(mIv->getChannelFieldValue(CH0, FLD_FW_BUILD_YEAR, rec)),
|
||||
static_cast<int>(mIv->getChannelFieldValue(CH0, FLD_FW_BUILD_MONTH_DAY, rec)),
|
||||
static_cast<int>(mIv->getChannelFieldValue(CH0, FLD_FW_BUILD_HOUR_MINUTE, rec)),
|
||||
static_cast<int>(mIv->getChannelFieldValue(CH0, FLD_BOOTLOADER_VER, rec)));
|
||||
retained = true;
|
||||
} else if(InverterDevInform_Simple == mCmd) {
|
||||
snprintf(mSubTopic.data(), mSubTopic.size(), "%s/hardware", mIv->config->name);
|
||||
snprintf(mVal.data(), mVal.size(), "{\"part\":%d,\"version\":\"%d\",\"grid_profile_code\":%d,\"grid_profile_version\":%d}",
|
||||
static_cast<int>(mIv->getChannelFieldValue(CH0, FLD_PART_NUM, rec)),
|
||||
static_cast<int>(mIv->getChannelFieldValue(CH0, FLD_HW_VERSION, rec)),
|
||||
static_cast<int>(mIv->getChannelFieldValue(CH0, FLD_GRID_PROFILE_CODE, rec)),
|
||||
static_cast<int>(mIv->getChannelFieldValue(CH0, FLD_GRID_PROFILE_VERSION, rec)));
|
||||
retained = true;
|
||||
} 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)));
|
||||
}
|
||||
|
||||
uint8_t qos = (FLD_ACT_ACTIVE_PWR_LIMIT == rec->assign[mPos].fieldId) ? QOS_2 : QOS_0;
|
||||
snprintf(mSubTopic, 32 + MAX_NAME_LENGTH, "%s/ch%d/%s", mIv->config->name, rec->assign[mPos].ch, fields[rec->assign[mPos].fieldId]);
|
||||
snprintf(mVal, 40, "%g", ah::round3(mIv->getValue(mPos, rec)));
|
||||
mPublish(mSubTopic, mVal, retained, qos);
|
||||
if((FLD_EVT != rec->assign[mPos].fieldId)
|
||||
&& (FLD_LAST_ALARM_CODE != rec->assign[mPos].fieldId))
|
||||
mPublish(mSubTopic.data(), mVal.data(), retained, qos);
|
||||
}
|
||||
mPos++;
|
||||
} else {
|
||||
|
@ -189,24 +217,24 @@ class PubMqttIvData {
|
|||
}
|
||||
|
||||
inline void sendRadioStat(uint8_t start) {
|
||||
snprintf(mSubTopic, 32 + MAX_NAME_LENGTH, "%s/radio_stat", mIv->config->name);
|
||||
snprintf(mVal, 140, "{\"tx\":%d,\"success\":%d,\"fail\":%d,\"no_answer\":%d,\"retransmits\":%d,\"lossIvRx\":%d,\"lossIvTx\":%d,\"lossDtuRx\":%d,\"lossDtuTx\":%d}",
|
||||
snprintf(mSubTopic.data(), mSubTopic.size(), "%s/radio_stat", mIv->config->name);
|
||||
snprintf(mVal.data(), mVal.size(), "{\"tx\":%d,\"success\":%d,\"fail\":%d,\"no_answer\":%d,\"retransmits\":%d,\"lossIvRx\":%d,\"lossIvTx\":%d,\"lossDtuRx\":%d,\"lossDtuTx\":%d}",
|
||||
mIv->radioStatistics.txCnt,
|
||||
mIv->radioStatistics.rxSuccess,
|
||||
mIv->radioStatistics.rxFail,
|
||||
mIv->radioStatistics.rxFailNoAnser,
|
||||
mIv->radioStatistics.rxFailNoAnswer,
|
||||
mIv->radioStatistics.retransmits,
|
||||
mIv->radioStatistics.ivLoss,
|
||||
mIv->radioStatistics.ivSent,
|
||||
mIv->radioStatistics.dtuLoss,
|
||||
mIv->radioStatistics.dtuSent);
|
||||
mPublish(mSubTopic, mVal, false, QOS_0);
|
||||
mPublish(mSubTopic.data(), mVal.data(), false, QOS_0);
|
||||
}
|
||||
|
||||
void stateSendTotals() {
|
||||
uint8_t fieldId;
|
||||
mRTRDataHasBeenSent = true;
|
||||
if(mPos < 4) {
|
||||
if(mPos < 5) {
|
||||
uint8_t fieldId;
|
||||
bool retained = true;
|
||||
switch (mPos) {
|
||||
default:
|
||||
|
@ -232,37 +260,42 @@ class PubMqttIvData {
|
|||
fieldId = FLD_PDC;
|
||||
retained = false;
|
||||
break;
|
||||
case 4:
|
||||
fieldId = FLD_MP;
|
||||
retained = false;
|
||||
break;
|
||||
}
|
||||
snprintf(mSubTopic, 32 + MAX_NAME_LENGTH, "total/%s", fields[fieldId]);
|
||||
snprintf(mVal, 40, "%g", ah::round3(mTotal[mPos]));
|
||||
mPublish(mSubTopic, mVal, retained, QOS_0);
|
||||
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 {
|
||||
mSendList->pop();
|
||||
mPos = 0;
|
||||
mSendTotals = false;
|
||||
mState = IDLE;
|
||||
}
|
||||
}
|
||||
|
||||
HMSYSTEM *mSys;
|
||||
uint32_t *mUtcTimestamp;
|
||||
HMSYSTEM *mSys = nullptr;
|
||||
uint32_t *mUtcTimestamp = nullptr;
|
||||
pubMqttPublisherType mPublish;
|
||||
State mState;
|
||||
State mState = IDLE;
|
||||
StateFunction mTable[NUM_STATES];
|
||||
|
||||
uint8_t mCmd;
|
||||
uint8_t mLastIvId;
|
||||
bool mSendTotals, mTotalFound, mAllTotalFound, mSendTotalYd;
|
||||
float mTotal[4], mYldTotalStore;
|
||||
uint8_t mCmd = 0;
|
||||
uint8_t mLastIvId = 0;
|
||||
bool mSendTotals = false, mTotalFound = false, mAllTotalFound = false;
|
||||
bool mSendTotalYd = false, mAtLeastOneWasntSent = false;
|
||||
float mTotal[5], mYldTotalStore = 0;
|
||||
|
||||
Inverter<> *mIv, *mIvSend;
|
||||
uint8_t mPos;
|
||||
bool mRTRDataHasBeenSent;
|
||||
Inverter<> *mIv = nullptr, *mIvSend = nullptr;
|
||||
uint8_t mPos = 0;
|
||||
bool mRTRDataHasBeenSent = false;
|
||||
|
||||
char mSubTopic[32 + MAX_NAME_LENGTH + 1];
|
||||
char mVal[140];
|
||||
std::array<char, (32 + MAX_NAME_LENGTH + 1)> mSubTopic;
|
||||
std::array<char, 160> mVal;
|
||||
|
||||
std::queue<sendListCmdIv> *mSendList;
|
||||
std::queue<sendListCmdIv> *mSendList = nullptr;
|
||||
};
|
||||
|
||||
#endif /*__PUB_MQTT_IV_DATA_H__*/
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 2022 Ahoy, https://ahoydtu.de
|
||||
// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/
|
||||
// 2024 Ahoy, https://github.com/lumpapu/ahoy
|
||||
// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/4.0/deed
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#ifndef __PUB_SERIAL_H__
|
||||
|
@ -13,8 +13,6 @@
|
|||
template<class HMSYSTEM>
|
||||
class PubSerial {
|
||||
public:
|
||||
PubSerial() {}
|
||||
|
||||
void setup(settings_t *cfg, HMSYSTEM *sys, uint32_t *utcTs) {
|
||||
mCfg = cfg;
|
||||
mSys = sys;
|
||||
|
@ -46,9 +44,9 @@ class PubSerial {
|
|||
}
|
||||
|
||||
private:
|
||||
settings_t *mCfg;
|
||||
HMSYSTEM *mSys;
|
||||
uint32_t *mUtcTimestamp;
|
||||
settings_t *mCfg = nullptr;
|
||||
HMSYSTEM *mSys = nullptr;
|
||||
uint32_t *mUtcTimestamp = nullptr;
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -72,11 +72,10 @@ namespace ah {
|
|||
|
||||
String getTimeStrMs(uint64_t t) {
|
||||
char str[13];
|
||||
uint16_t m;
|
||||
if(0 == t)
|
||||
sprintf(str, "n/a");
|
||||
else {
|
||||
m = t % 1000;
|
||||
uint16_t m = t % 1000;
|
||||
t = t / 1000;
|
||||
sprintf(str, "%02d:%02d:%02d.%03d", hour(t), minute(t), second(t), m);
|
||||
}
|
||||
|
@ -86,14 +85,13 @@ namespace ah {
|
|||
uint64_t Serial2u64(const char *val) {
|
||||
char tmp[3];
|
||||
uint64_t ret = 0ULL;
|
||||
uint64_t u64;
|
||||
memset(tmp, 0, 3);
|
||||
for(uint8_t i = 0; i < 6; i++) {
|
||||
tmp[0] = val[i*2];
|
||||
tmp[1] = val[i*2 + 1];
|
||||
if((tmp[0] == '\0') || (tmp[1] == '\0'))
|
||||
break;
|
||||
u64 = strtol(tmp, NULL, 16);
|
||||
uint64_t u64 = strtol(tmp, NULL, 16);
|
||||
ret |= (u64 << ((5-i) << 3));
|
||||
}
|
||||
return ret;
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#include <functional>
|
||||
#include "dbg.h"
|
||||
#include "AsyncJson.h"
|
||||
#include "../appInterface.h"
|
||||
|
||||
// https://www.improv-wifi.com/serial/
|
||||
// https://github.com/jnthas/improv-wifi-demo/blob/main/src/esp32-wifiimprov/esp32-wifiimprov.ino
|
||||
|
@ -70,7 +71,7 @@ class Improv {
|
|||
TYPE_RPC_RESPONSE = 0x04
|
||||
};
|
||||
|
||||
void dumpBuf(uint8_t buf[], uint8_t len) {
|
||||
void dumpBuf(const uint8_t buf[], uint8_t len) {
|
||||
for(uint8_t i = 0; i < len; i++) {
|
||||
DHEX(buf[i]);
|
||||
DBGPRINT(F(" "));
|
||||
|
@ -78,7 +79,7 @@ class Improv {
|
|||
DBGPRINTLN("");
|
||||
}
|
||||
|
||||
inline uint8_t buildChecksum(uint8_t buf[], uint8_t len) {
|
||||
inline uint8_t buildChecksum(const uint8_t buf[], uint8_t len) {
|
||||
uint8_t calc = 0;
|
||||
for(uint8_t i = 0; i < len; i++) {
|
||||
calc += buf[i];
|
||||
|
@ -86,7 +87,7 @@ class Improv {
|
|||
return calc;
|
||||
}
|
||||
|
||||
inline bool checkChecksum(uint8_t buf[], uint8_t len) {
|
||||
inline bool checkChecksum(const uint8_t buf[], uint8_t len) {
|
||||
/*DHEX(buf[len], false);
|
||||
DBGPRINT(F(" == "), false);
|
||||
DBGHEXLN(buildChecksum(buf, len), false);*/
|
||||
|
@ -97,7 +98,7 @@ class Improv {
|
|||
if(len < 11)
|
||||
return false;
|
||||
|
||||
if(0 != strncmp((char*)buf, "IMPROV", 6))
|
||||
if(0 != strncmp(reinterpret_cast<char*>(buf), "IMPROV", 6))
|
||||
return false;
|
||||
|
||||
// version check (only version 1 is supported!)
|
||||
|
@ -199,7 +200,7 @@ class Improv {
|
|||
dumpBuf(buf, len);
|
||||
}
|
||||
|
||||
void parsePayload(uint8_t type, uint8_t buf[], uint8_t len) {
|
||||
void parsePayload(uint8_t type, const uint8_t buf[], uint8_t len) {
|
||||
if(TYPE_RPC == type) {
|
||||
if(GET_CURRENT_STATE == buf[0]) {
|
||||
setDebugEn(false);
|
||||
|
@ -212,9 +213,10 @@ class Improv {
|
|||
}
|
||||
}
|
||||
|
||||
IApp *mApp;
|
||||
const char *mDevName, *mVersion;
|
||||
bool mScanRunning;
|
||||
IApp *mApp = nullptr;
|
||||
const char *mDevName = nullptr;
|
||||
const char *mVersion = nullptr;
|
||||
bool mScanRunning = false;
|
||||
};
|
||||
#endif
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#define __SCHEDULER_H__
|
||||
|
||||
#include <functional>
|
||||
#include <array>
|
||||
#include "dbg.h"
|
||||
|
||||
namespace ah {
|
||||
|
@ -28,8 +29,6 @@ namespace ah {
|
|||
|
||||
class Scheduler {
|
||||
public:
|
||||
Scheduler() {}
|
||||
|
||||
void setup(bool directStart) {
|
||||
mUptime = 0;
|
||||
mTimestamp = (directStart) ? 1 : 0;
|
||||
|
@ -93,8 +92,7 @@ namespace ah {
|
|||
}
|
||||
|
||||
inline void resetTicker(void) {
|
||||
for (uint8_t i = 0; i < MAX_NUM_TICKER; i++)
|
||||
mTickerInUse[i] = false;
|
||||
mTickerInUse.fill(false);
|
||||
}
|
||||
|
||||
void getStat(uint8_t *max) {
|
||||
|
@ -127,8 +125,8 @@ namespace ah {
|
|||
mTicker[i].timeout = timeout;
|
||||
mTicker[i].reload = reload;
|
||||
mTicker[i].isTimestamp = isTimestamp;
|
||||
memset(mTicker[i].name, 0, 6);
|
||||
strncpy(mTicker[i].name, name, (strlen(name) < 6) ? strlen(name) : 5);
|
||||
strncpy(mTicker[i].name, name, 5);
|
||||
mTicker[i].name[5]=0;
|
||||
if(mMax == i)
|
||||
mMax = i + 1;
|
||||
return i;
|
||||
|
@ -159,11 +157,11 @@ namespace ah {
|
|||
}
|
||||
}
|
||||
|
||||
sP mTicker[MAX_NUM_TICKER];
|
||||
bool mTickerInUse[MAX_NUM_TICKER];
|
||||
uint32_t mMillis, mPrevMillis, mDiff;
|
||||
uint8_t mDiffSeconds;
|
||||
uint8_t mMax;
|
||||
std::array<sP, MAX_NUM_TICKER> mTicker;
|
||||
std::array<bool, MAX_NUM_TICKER> mTickerInUse;
|
||||
uint32_t mMillis = 0, mPrevMillis = 0, mDiff = 0;
|
||||
uint8_t mDiffSeconds = 0;
|
||||
uint8_t mMax = 0;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 2023 Ahoy, https://www.mikrocontroller.net/topic/525778
|
||||
// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/
|
||||
// 2024 Ahoy, https://github.com/lumpapu/ahoy
|
||||
// Creative Commons - https://creativecommons.org/licenses/by-nc-sa/4.0/deed
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#if defined(ESP32)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 2023 Ahoy, https://www.mikrocontroller.net/topic/525778
|
||||
// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/
|
||||
// 2024 Ahoy, https://github.com/lumpapu/ahoy
|
||||
// Creative Commons - https://creativecommons.org/licenses/by-nc-sa/4.0/deed
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#ifndef __SPI_PATCHER_H__
|
||||
|
@ -16,7 +16,7 @@
|
|||
|
||||
class SpiPatcher {
|
||||
protected:
|
||||
SpiPatcher(spi_host_device_t dev) :
|
||||
explicit SpiPatcher(spi_host_device_t dev) :
|
||||
mHostDevice(dev), mCurHandle(nullptr) {
|
||||
// Use binary semaphore instead of mutex for performance reasons
|
||||
mutex = xSemaphoreCreateBinaryStatic(&mutex_buffer);
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
|
||||
#define SYSLOG_MAX_PACKET_SIZE 256
|
||||
|
||||
DbgSyslog::DbgSyslog() : mSyslogBuffer{} {}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void DbgSyslog::setup(settings_t *config) {
|
||||
|
@ -67,12 +68,11 @@ void DbgSyslog::syslogCb (String msg)
|
|||
// Send mSyslogBuffer in chunks because mSyslogBuffer is larger than syslog packet size
|
||||
int packetStart = 0;
|
||||
int packetSize = 122; // syslog payload depends also on hostname and app
|
||||
char saveChar;
|
||||
if (isEolFound) {
|
||||
mSyslogBuffer[mSyslogBufFill-2]=0; // skip \r\n
|
||||
}
|
||||
while(packetStart < mSyslogBufFill) {
|
||||
saveChar = mSyslogBuffer[packetStart+packetSize];
|
||||
char saveChar = mSyslogBuffer[packetStart+packetSize];
|
||||
mSyslogBuffer[packetStart+packetSize] = 0;
|
||||
log(mConfig->sys.deviceName,SYSLOG_FACILITY, mSyslogSeverity, &mSyslogBuffer[packetStart]);
|
||||
mSyslogBuffer[packetStart+packetSize] = saveChar;
|
||||
|
|
|
@ -36,6 +36,7 @@
|
|||
|
||||
class DbgSyslog {
|
||||
public:
|
||||
DbgSyslog();
|
||||
void setup (settings_t *config);
|
||||
void syslogCb(String msg);
|
||||
void log(const char *hostname, uint8_t facility, uint8_t severity, char* msg);
|
||||
|
@ -43,7 +44,7 @@ class DbgSyslog {
|
|||
private:
|
||||
WiFiUDP mSyslogUdp;
|
||||
IPAddress mSyslogIP;
|
||||
settings_t *mConfig;
|
||||
settings_t *mConfig = nullptr;
|
||||
char mSyslogBuffer[SYSLOG_BUF_SIZE+1];
|
||||
uint16_t mSyslogBufFill = 0;
|
||||
int mSyslogSeverity = PRI_NOTICE;
|
||||
|
|
|
@ -22,14 +22,14 @@ class TimeMonitor {
|
|||
/**
|
||||
* A constructor for creating a TimeMonitor object
|
||||
*/
|
||||
TimeMonitor(void) {}
|
||||
TimeMonitor() {}
|
||||
|
||||
/**
|
||||
* A constructor for initializing a TimeMonitor object
|
||||
* @param timeout timeout in ms
|
||||
* @param start (optional) if true, start TimeMonitor immediately
|
||||
*/
|
||||
TimeMonitor(uint32_t timeout, bool start = false) {
|
||||
explicit TimeMonitor(uint32_t timeout, bool start = false) {
|
||||
if (start)
|
||||
startTimeMonitor(timeout);
|
||||
else
|
||||
|
@ -80,7 +80,7 @@ class TimeMonitor {
|
|||
* true: TimeMonitor already timed out
|
||||
* false: TimeMonitor still in time or TimeMonitor was stopped
|
||||
*/
|
||||
bool isTimeout(void) {
|
||||
bool isTimeout(void) const {
|
||||
if ((mStarted) && ((millis() - mStartTime) >= mTimeout))
|
||||
return true;
|
||||
else
|
||||
|
|
7
src/web/Protection.cpp
Normal file
7
src/web/Protection.cpp
Normal file
|
@ -0,0 +1,7 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 2024 Ahoy, https://github.com/lumpapu/ahoy
|
||||
// Creative Commons - https://creativecommons.org/licenses/by-nc-sa/4.0/deed
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#include "Protection.h"
|
||||
Protection *Protection::mInstance = nullptr;
|
122
src/web/Protection.h
Normal file
122
src/web/Protection.h
Normal file
|
@ -0,0 +1,122 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 2024 Ahoy, https://github.com/lumpapu/ahoy
|
||||
// Creative Commons - https://creativecommons.org/licenses/by-nc-sa/4.0/deed
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#ifndef __PROTECTION_H__
|
||||
#define __PROTECTION_H__
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
|
||||
#include "../config/config.h"
|
||||
#include "../utils/helper.h"
|
||||
|
||||
class Protection {
|
||||
protected:
|
||||
explicit Protection(const char *pwd) {
|
||||
mPwd = pwd;
|
||||
mLogoutTimeout = 0;
|
||||
mWebIp.fill(0);
|
||||
mApiIp.fill(0);
|
||||
mToken.fill(0);
|
||||
}
|
||||
|
||||
public:
|
||||
Protection(Protection &other) = delete;
|
||||
void operator=(const Protection &) = delete;
|
||||
|
||||
static Protection* getInstance(const char *pwd) {
|
||||
if(nullptr == mInstance)
|
||||
mInstance = new Protection(pwd);
|
||||
return mInstance;
|
||||
}
|
||||
|
||||
void tickSecond() { // auto logout
|
||||
if(0 != mLogoutTimeout) {
|
||||
if (0 == --mLogoutTimeout) {
|
||||
if(mPwd[0] != '\0')
|
||||
lock(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void lock(bool fromWeb) {
|
||||
mWebIp.fill(0);
|
||||
if(fromWeb)
|
||||
return;
|
||||
|
||||
mApiIp.fill(0);
|
||||
mToken.fill(0);
|
||||
}
|
||||
|
||||
char *unlock(const char *clientIp, bool loginFromWeb) {
|
||||
mLogoutTimeout = LOGOUT_TIMEOUT;
|
||||
|
||||
if(loginFromWeb)
|
||||
ah::ip2Arr(static_cast<uint8_t*>(mWebIp.data()), clientIp);
|
||||
else {
|
||||
ah::ip2Arr(static_cast<uint8_t*>(mApiIp.data()), clientIp);
|
||||
genToken();
|
||||
}
|
||||
|
||||
return reinterpret_cast<char*>(mToken.data());
|
||||
}
|
||||
|
||||
void resetLockTimeout(void) {
|
||||
if(0 != mLogoutTimeout)
|
||||
mLogoutTimeout = LOGOUT_TIMEOUT;
|
||||
}
|
||||
|
||||
bool isProtected(const char *clientIp, const char *token, bool askedFromWeb) const {
|
||||
if(mPwd[0] == '\0') // no password set
|
||||
return false;
|
||||
|
||||
if(askedFromWeb)
|
||||
return !isIdentical(clientIp, mWebIp);
|
||||
|
||||
if(nullptr == token)
|
||||
return true;
|
||||
|
||||
if('*' == token[0]) // call from WebUI
|
||||
return !isIdentical(clientIp, mWebIp);
|
||||
|
||||
if(isIdentical(clientIp, mApiIp))
|
||||
return (0 != strncmp(token, mToken.data(), 16));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
void genToken() {
|
||||
mToken.fill(0);
|
||||
for(uint8_t i = 0; i < 16; i++) {
|
||||
mToken[i] = random(1, 35);
|
||||
// convert to ascii number 1-9 (zero isn't allowed) or upper
|
||||
// case character A-Z
|
||||
mToken[i] += (mToken[i] < 10) ? 0x30 : 0x37;
|
||||
}
|
||||
}
|
||||
|
||||
bool isIdentical(const char *clientIp, const std::array<uint8_t, 4> cmp) const {
|
||||
std::array<uint8_t, 4> ip;
|
||||
ah::ip2Arr(static_cast<uint8_t*>(ip.data()), clientIp);
|
||||
for(uint8_t i = 0; i < 4; i++) {
|
||||
if(cmp[i] != ip[i])
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
protected:
|
||||
static Protection *mInstance;
|
||||
|
||||
private:
|
||||
const char *mPwd;
|
||||
uint16_t mLogoutTimeout = 0;
|
||||
std::array<uint8_t, 4> mWebIp, mApiIp;
|
||||
std::array<char, 17> mToken;
|
||||
};
|
||||
|
||||
#endif /*__PROTECTION_H__*/
|
|
@ -26,10 +26,6 @@
|
|||
#define F(sl) (sl)
|
||||
#endif
|
||||
|
||||
const uint8_t acList[] = {FLD_UAC, FLD_IAC, FLD_PAC, FLD_F, FLD_PF, FLD_T, FLD_YT, FLD_YD, FLD_PDC, FLD_EFF, FLD_Q, FLD_MP};
|
||||
const uint8_t acListHmt[] = {FLD_UAC_1N, FLD_IAC_1, FLD_PAC, FLD_F, FLD_PF, FLD_T, FLD_YT, FLD_YD, FLD_PDC, FLD_EFF, FLD_Q, FLD_MP};
|
||||
const uint8_t dcList[] = {FLD_UDC, FLD_IDC, FLD_PDC, FLD_YD, FLD_YT, FLD_IRR, FLD_MP};
|
||||
|
||||
template<class HMSYSTEM>
|
||||
class RestApi {
|
||||
public:
|
||||
|
@ -64,9 +60,9 @@ class RestApi {
|
|||
DynamicJsonDocument json(128);
|
||||
JsonObject dummy = json.as<JsonObject>();
|
||||
if(obj[F("path")] == "ctrl")
|
||||
setCtrl(obj, dummy);
|
||||
setCtrl(obj, dummy, "*");
|
||||
else if(obj[F("path")] == "setup")
|
||||
setSetup(obj, dummy);
|
||||
setSetup(obj, dummy, "*");
|
||||
}
|
||||
|
||||
private:
|
||||
|
@ -103,7 +99,6 @@ class RestApi {
|
|||
#endif /* !defined(ETHERNET) */
|
||||
else if(path == "live") getLive(request,root);
|
||||
else if (path == "powerHistory") getPowerHistory(request, root);
|
||||
else if (path == "yieldDayHistory") getYieldDayHistory(request, root);
|
||||
else {
|
||||
if(path.substring(0, 12) == "inverter/id/")
|
||||
getInverter(root, request->url().substring(17).toInt());
|
||||
|
@ -138,11 +133,11 @@ class RestApi {
|
|||
#endif
|
||||
}
|
||||
|
||||
void onApiPostBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) {
|
||||
void onApiPostBody(AsyncWebServerRequest *request, const uint8_t *data, size_t len, size_t index, size_t total) {
|
||||
DPRINTLN(DBG_VERBOSE, "onApiPostBody");
|
||||
|
||||
if(0 == index) {
|
||||
if(NULL != mTmpBuf)
|
||||
if(nullptr != mTmpBuf)
|
||||
delete[] mTmpBuf;
|
||||
mTmpBuf = new uint8_t[total+1];
|
||||
mTmpSize = total;
|
||||
|
@ -155,36 +150,40 @@ class RestApi {
|
|||
|
||||
DynamicJsonDocument json(1000);
|
||||
|
||||
DeserializationError err = deserializeJson(json, (const char *)mTmpBuf, mTmpSize);
|
||||
JsonObject obj = json.as<JsonObject>();
|
||||
|
||||
AsyncJsonResponse* response = new AsyncJsonResponse(false, 200);
|
||||
JsonObject root = response->getRoot();
|
||||
root[F("success")] = (err) ? false : true;
|
||||
if(!err) {
|
||||
String path = request->url().substring(5);
|
||||
if(path == "ctrl")
|
||||
root[F("success")] = setCtrl(obj, root);
|
||||
else if(path == "setup")
|
||||
root[F("success")] = setSetup(obj, root);
|
||||
else {
|
||||
root[F("success")] = false;
|
||||
root[F("error")] = F(PATH_NOT_FOUND) + path;
|
||||
}
|
||||
} else {
|
||||
switch (err.code()) {
|
||||
case DeserializationError::Ok: break;
|
||||
case DeserializationError::IncompleteInput: root[F("error")] = F(INCOMPLETE_INPUT); break;
|
||||
case DeserializationError::InvalidInput: root[F("error")] = F(INVALID_INPUT); break;
|
||||
case DeserializationError::NoMemory: root[F("error")] = F(NOT_ENOUGH_MEM); break;
|
||||
default: root[F("error")] = F(DESER_FAILED); break;
|
||||
DeserializationError err = deserializeJson(json, reinterpret_cast<const char*>(mTmpBuf), mTmpSize);
|
||||
if(!json.is<JsonObject>())
|
||||
root[F("error")] = F(DESER_FAILED);
|
||||
else {
|
||||
JsonObject obj = json.as<JsonObject>();
|
||||
|
||||
root[F("success")] = (err) ? false : true;
|
||||
if(!err) {
|
||||
String path = request->url().substring(5);
|
||||
if(path == "ctrl")
|
||||
root[F("success")] = setCtrl(obj, root, request->client()->remoteIP().toString().c_str());
|
||||
else if(path == "setup")
|
||||
root[F("success")] = setSetup(obj, root, request->client()->remoteIP().toString().c_str());
|
||||
else {
|
||||
root[F("success")] = false;
|
||||
root[F("error")] = F(PATH_NOT_FOUND) + path;
|
||||
}
|
||||
} else {
|
||||
switch (err.code()) {
|
||||
case DeserializationError::Ok: break;
|
||||
case DeserializationError::IncompleteInput: root[F("error")] = F(INCOMPLETE_INPUT); break;
|
||||
case DeserializationError::InvalidInput: root[F("error")] = F(INVALID_INPUT); break;
|
||||
case DeserializationError::NoMemory: root[F("error")] = F(NOT_ENOUGH_MEM); break;
|
||||
default: root[F("error")] = F(DESER_FAILED); break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
delete[] mTmpBuf;
|
||||
mTmpBuf = NULL;
|
||||
mTmpBuf = nullptr;
|
||||
}
|
||||
|
||||
void getNotFound(JsonObject obj, String url) {
|
||||
|
@ -204,7 +203,6 @@ class RestApi {
|
|||
ep[F("live")] = url + F("live");
|
||||
#if defined(ENABLE_HISTORY)
|
||||
ep[F("powerHistory")] = url + F("powerHistory");
|
||||
ep[F("yieldDayHistory")] = url + F("yieldDayHistory");
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -212,6 +210,9 @@ class RestApi {
|
|||
void onDwnldSetup(AsyncWebServerRequest *request) {
|
||||
AsyncWebServerResponse *response;
|
||||
|
||||
// save settings to have latest firmware changes in export
|
||||
mApp->saveSettings(false);
|
||||
|
||||
File fp = LittleFS.open("/settings.json", "r");
|
||||
if(!fp) {
|
||||
DPRINTLN(DBG_ERROR, F("failed to load settings"));
|
||||
|
@ -252,6 +253,8 @@ class RestApi {
|
|||
}
|
||||
|
||||
void getGeneric(AsyncWebServerRequest *request, JsonObject obj) {
|
||||
mApp->resetLockTimeout();
|
||||
|
||||
obj[F("wifi_rssi")] = (WiFi.status() != WL_CONNECTED) ? 0 : WiFi.RSSI();
|
||||
obj[F("ts_uptime")] = mApp->getUptime();
|
||||
obj[F("ts_now")] = mApp->getTimestamp();
|
||||
|
@ -259,11 +262,13 @@ class RestApi {
|
|||
obj[F("modules")] = String(mApp->getVersionModules());
|
||||
obj[F("build")] = String(AUTO_GIT_HASH);
|
||||
obj[F("env")] = String(ENV_NAME);
|
||||
obj[F("menu_prot")] = mApp->getProtection(request);
|
||||
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) (strlen(mConfig->sys.adminPwd) > 0);
|
||||
obj[F("menu_protEn")] = (bool) (mConfig->sys.adminPwd[0] != '\0');
|
||||
obj[F("cst_lnk")] = String(mConfig->plugin.customLink);
|
||||
obj[F("cst_lnk_txt")] = String(mConfig->plugin.customLinkText);
|
||||
obj[F("region")] = mConfig->sys.region;
|
||||
obj[F("timezone")] = mConfig->sys.timezone;
|
||||
|
||||
#if defined(ESP32)
|
||||
obj[F("esp_type")] = F("ESP32");
|
||||
|
@ -417,7 +422,7 @@ class RestApi {
|
|||
obj[F("name")] = String(iv->config->name);
|
||||
obj[F("rx_success")] = iv->radioStatistics.rxSuccess;
|
||||
obj[F("rx_fail")] = iv->radioStatistics.rxFail;
|
||||
obj[F("rx_fail_answer")] = iv->radioStatistics.rxFailNoAnser;
|
||||
obj[F("rx_fail_answer")] = iv->radioStatistics.rxFailNoAnswer;
|
||||
obj[F("frame_cnt")] = iv->radioStatistics.frmCnt;
|
||||
obj[F("tx_cnt")] = iv->radioStatistics.txCnt;
|
||||
obj[F("retransmits")] = iv->radioStatistics.retransmits;
|
||||
|
@ -453,7 +458,6 @@ class RestApi {
|
|||
obj2[F("channels")] = iv->channels;
|
||||
obj2[F("freq")] = iv->config->frequency;
|
||||
obj2[F("disnightcom")] = (bool)iv->config->disNightCom;
|
||||
obj2[F("add2total")] = (bool)iv->config->add2Total;
|
||||
if(0xff == iv->config->powerLevel) {
|
||||
if((IV_HMT == iv->ivGen) || (IV_HMS == iv->ivGen))
|
||||
obj2[F("pa")] = 30; // 20dBm
|
||||
|
@ -476,8 +480,6 @@ class RestApi {
|
|||
obj[F("strtWthtTm")] = (bool)mConfig->inst.startWithoutTime;
|
||||
obj[F("rdGrid")] = (bool)mConfig->inst.readGrid;
|
||||
obj[F("rstMaxMid")] = (bool)mConfig->inst.rstMaxValsMidNight;
|
||||
obj[F("yldEff")] = mConfig->inst.yieldEffiency;
|
||||
obj[F("gap")] = mConfig->inst.gapMs;
|
||||
}
|
||||
|
||||
void getInverter(JsonObject obj, uint8_t id) {
|
||||
|
@ -493,7 +495,7 @@ class RestApi {
|
|||
obj[F("name")] = String(iv->config->name);
|
||||
obj[F("serial")] = String(iv->config->serial.u64, HEX);
|
||||
obj[F("version")] = String(iv->getFwVersion());
|
||||
obj[F("power_limit_read")] = ah::round3(iv->actPowerLimit);
|
||||
obj[F("power_limit_read")] = iv->actPowerLimit;
|
||||
obj[F("power_limit_ack")] = iv->powerLimitAck;
|
||||
obj[F("max_pwr")] = iv->getMaxPower();
|
||||
obj[F("ts_last_success")] = rec->ts;
|
||||
|
@ -647,6 +649,9 @@ class RestApi {
|
|||
obj[F("fcsb")] = mConfig->cmt.pinFcsb;
|
||||
obj[F("gpio3")] = mConfig->cmt.pinIrq;
|
||||
obj[F("en")] = (bool) mConfig->cmt.enabled;
|
||||
std::pair<uint16_t, uint16_t> range = mRadioCmt->getFreqRangeMhz();
|
||||
obj[F("freq_min")] = range.first;
|
||||
obj[F("freq_max")] = range.second;
|
||||
}
|
||||
|
||||
void getRadioCmtInfo(JsonObject obj) {
|
||||
|
@ -674,6 +679,7 @@ class RestApi {
|
|||
obj[F("debug")] = mConfig->serial.debug;
|
||||
obj[F("priv")] = mConfig->serial.privacyLog;
|
||||
obj[F("wholeTrace")] = mConfig->serial.printWholeTrace;
|
||||
obj[F("log2mqtt")] = mConfig->serial.log2mqtt;
|
||||
}
|
||||
|
||||
void getStaticIp(JsonObject obj) {
|
||||
|
@ -745,6 +751,12 @@ class RestApi {
|
|||
warn.add(F(REBOOT_ESP_APPLY_CHANGES));
|
||||
if(0 == mApp->getTimestamp())
|
||||
warn.add(F(TIME_NOT_SET));
|
||||
#if !defined(ETHERNET)
|
||||
#if !defined(ESP32)
|
||||
if(mApp->getWasInCh12to14())
|
||||
warn.add(F(WAS_IN_CH_12_TO_14));
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
void getSetup(AsyncWebServerRequest *request, JsonObject obj) {
|
||||
|
@ -814,29 +826,26 @@ class RestApi {
|
|||
#endif /*ENABLE_HISTORY*/
|
||||
}
|
||||
|
||||
void getYieldDayHistory(AsyncWebServerRequest *request, JsonObject obj) {
|
||||
getGeneric(request, obj.createNestedObject(F("generic")));
|
||||
#if defined(ENABLE_HISTORY)
|
||||
obj[F("refresh")] = 86400; // 1 day
|
||||
uint16_t max = 0;
|
||||
for (uint16_t fld = 0; fld < HISTORY_DATA_ARR_LENGTH; fld++) {
|
||||
uint16_t value = mApp->getHistoryValue((uint8_t)HistoryStorageType::YIELD, fld);
|
||||
obj[F("value")][fld] = value;
|
||||
if (value > max)
|
||||
max = value;
|
||||
bool setCtrl(JsonObject jsonIn, JsonObject jsonOut, const char *clientIP) {
|
||||
if(jsonIn.containsKey(F("auth"))) {
|
||||
if(String(jsonIn[F("auth")]) == String(mConfig->sys.adminPwd)) {
|
||||
jsonOut[F("token")] = mApp->unlock(clientIP, false);
|
||||
jsonIn[F("token")] = jsonOut[F("token")];
|
||||
} else {
|
||||
jsonOut[F("error")] = F("ERR_AUTH");
|
||||
return false;
|
||||
}
|
||||
if(!jsonIn.containsKey(F("cmd")))
|
||||
return true;
|
||||
}
|
||||
obj[F("max")] = max;
|
||||
#else
|
||||
obj[F("refresh")] = 86400; // 1 day
|
||||
#endif /*ENABLE_HISTORY*/
|
||||
}
|
||||
|
||||
if(isProtected(jsonIn, jsonOut, clientIP))
|
||||
return false;
|
||||
|
||||
bool setCtrl(JsonObject jsonIn, JsonObject jsonOut) {
|
||||
Inverter<> *iv = mSys->getInverterByPos(jsonIn[F("id")]);
|
||||
bool accepted = true;
|
||||
if(NULL == iv) {
|
||||
jsonOut[F("error")] = F(INV_INDEX_INVALID) + jsonIn[F("id")].as<String>();
|
||||
jsonOut[F("error")] = F("ERR_INDEX");
|
||||
return false;
|
||||
}
|
||||
jsonOut[F("id")] = jsonIn[F("id")];
|
||||
|
@ -846,7 +855,7 @@ class RestApi {
|
|||
else if(F("restart") == jsonIn[F("cmd")])
|
||||
accepted = iv->setDevControlRequest(Restart);
|
||||
else if(0 == strncmp("limit_", jsonIn[F("cmd")].as<const char*>(), 6)) {
|
||||
iv->powerLimit[0] = jsonIn["val"];
|
||||
iv->powerLimit[0] = static_cast<uint16_t>(jsonIn["val"].as<float>() * 10.0);
|
||||
if(F("limit_persistent_relative") == jsonIn[F("cmd")])
|
||||
iv->powerLimit[1] = RelativPersistent;
|
||||
else if(F("limit_persistent_absolute") == jsonIn[F("cmd")])
|
||||
|
@ -863,19 +872,22 @@ class RestApi {
|
|||
DPRINTLN(DBG_INFO, F("dev cmd"));
|
||||
iv->setDevCommand(jsonIn[F("val")].as<int>());
|
||||
} else {
|
||||
jsonOut[F("error")] = F(UNKNOWN_CMD) + jsonIn["cmd"].as<String>() + "'";
|
||||
jsonOut[F("error")] = F("ERR_UNKNOWN_CMD");
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!accepted) {
|
||||
jsonOut[F("error")] = F(INV_DOES_NOT_ACCEPT_LIMIT_AT_MOMENT);
|
||||
jsonOut[F("error")] = F("ERR_LIMIT_NOT_ACCEPT");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool setSetup(JsonObject jsonIn, JsonObject jsonOut) {
|
||||
bool setSetup(JsonObject jsonIn, JsonObject jsonOut, const char *clientIP) {
|
||||
if(isProtected(jsonIn, jsonOut, clientIP))
|
||||
return false;
|
||||
|
||||
#if !defined(ETHERNET)
|
||||
if(F("scan_wifi") == jsonIn[F("cmd")])
|
||||
mApp->scanAvailNetworks();
|
||||
|
@ -914,30 +926,55 @@ class RestApi {
|
|||
iv->config->frequency = jsonIn[F("freq")];
|
||||
iv->config->powerLevel = jsonIn[F("pa")];
|
||||
iv->config->disNightCom = jsonIn[F("disnightcom")];
|
||||
iv->config->add2Total = jsonIn[F("add2total")];
|
||||
mApp->saveSettings(false); // without reboot
|
||||
} else {
|
||||
jsonOut[F("error")] = F(UNKNOWN_CMD);
|
||||
jsonOut[F("error")] = F("ERR_UNKNOWN_CMD");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
IApp *mApp;
|
||||
HMSYSTEM *mSys;
|
||||
HmRadio<> *mRadioNrf;
|
||||
#if defined(ESP32)
|
||||
CmtRadio<> *mRadioCmt;
|
||||
#endif
|
||||
AsyncWebServer *mSrv;
|
||||
settings_t *mConfig;
|
||||
bool isProtected(JsonObject jsonIn, JsonObject jsonOut, const char *clientIP) {
|
||||
if(mConfig->sys.adminPwd[0] != '\0') { // check if admin password is set
|
||||
if(strncmp("*", clientIP, 1) != 0) { // no call from MqTT
|
||||
const char* token = nullptr;
|
||||
if(jsonIn.containsKey(F("token")))
|
||||
token = jsonIn["token"];
|
||||
|
||||
uint32_t mTimezoneOffset;
|
||||
uint32_t mHeapFree, mHeapFreeBlk;
|
||||
uint8_t mHeapFrag;
|
||||
uint8_t *mTmpBuf = NULL;
|
||||
uint32_t mTmpSize;
|
||||
if(!mApp->isProtected(clientIP, token, false))
|
||||
return false;
|
||||
|
||||
jsonOut[F("error")] = F("ERR_PROTECTED");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
constexpr static uint8_t acList[] = {FLD_UAC, FLD_IAC, FLD_PAC, FLD_F, FLD_PF, FLD_T, FLD_YT,
|
||||
FLD_YD, FLD_PDC, FLD_EFF, FLD_Q, FLD_MP};
|
||||
constexpr static uint8_t acListHmt[] = {FLD_UAC_1N, FLD_IAC_1, FLD_PAC, FLD_F, FLD_PF, FLD_T,
|
||||
FLD_YT, FLD_YD, FLD_PDC, FLD_EFF, FLD_Q, FLD_MP};
|
||||
constexpr static uint8_t dcList[] = {FLD_UDC, FLD_IDC, FLD_PDC, FLD_YD, FLD_YT, FLD_IRR, FLD_MP};
|
||||
|
||||
private:
|
||||
IApp *mApp = nullptr;
|
||||
HMSYSTEM *mSys = nullptr;
|
||||
HmRadio<> *mRadioNrf = nullptr;
|
||||
#if defined(ESP32)
|
||||
CmtRadio<> *mRadioCmt = nullptr;
|
||||
#endif
|
||||
AsyncWebServer *mSrv = nullptr;
|
||||
settings_t *mConfig = nullptr;
|
||||
|
||||
uint32_t mTimezoneOffset = 0;
|
||||
uint32_t mHeapFree = 0, mHeapFreeBlk = 0;
|
||||
uint8_t mHeapFrag = 0;
|
||||
uint8_t *mTmpBuf = nullptr;
|
||||
uint32_t mTmpSize = 0;
|
||||
};
|
||||
|
||||
#endif /*__WEB_API_H__*/
|
||||
|
|
|
@ -3,9 +3,9 @@
|
|||
--fg: #000;
|
||||
--fg2: #fff;
|
||||
|
||||
--info: #0000dd;
|
||||
--warn: #ff7700;
|
||||
--success: #009900;
|
||||
--info: #00d;
|
||||
--warn: #f70;
|
||||
--success: #090;
|
||||
|
||||
--input-bg: #eee;
|
||||
--table-border: #ccc;
|
||||
|
|
|
@ -4,8 +4,8 @@
|
|||
--fg2: #fff;
|
||||
|
||||
--info: #0072c8;
|
||||
--warn: #ffaa00;
|
||||
--success: #00bb00;
|
||||
--warn: #fa0;
|
||||
--success: #0b0;
|
||||
|
||||
--input-bg: #333;
|
||||
--table-border: #333;
|
||||
|
@ -20,14 +20,14 @@
|
|||
|
||||
--invalid-bg: #400;
|
||||
|
||||
--total-head-title: #555511;
|
||||
--total-bg: #666622;
|
||||
--iv-head-title: #115511;
|
||||
--iv-head-bg: #226622;
|
||||
--total-head-title: #551;
|
||||
--total-bg: #662;
|
||||
--iv-head-title: #151;
|
||||
--iv-head-bg: #262;
|
||||
--iv-dis-title: #333;
|
||||
--iv-dis: #444;
|
||||
--ch-head-title: #112255;
|
||||
--ch-head-bg: #223366;
|
||||
--ch-head-title: #125;
|
||||
--ch-head-bg: #236;
|
||||
--ts-head: #333;
|
||||
--ts-bg: #555;
|
||||
}
|
||||
|
|
|
@ -20,14 +20,6 @@
|
|||
{#MAXIMUM}: <span id="pwrMax"></span> W. {#UPDATED} <span id="pwrRefresh"></span> {#SECONDS}
|
||||
</p>
|
||||
</div>
|
||||
<h3>{#TOTAL_YIELD_PER_DAY}</h3>
|
||||
<div>
|
||||
<div class="chartDiv" id="ydChart"> </div>
|
||||
<p>
|
||||
{#MAXIMUM}: <span id="ydMax"></span> Wh<br />
|
||||
{#UPDATED} <span id="ydRefresh"></span> {#SECONDS}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{#HTML_FOOTER}
|
||||
|
@ -87,6 +79,8 @@
|
|||
|
||||
function parsePowerHistory(obj){
|
||||
if (null != obj) {
|
||||
parseNav(obj.generic);
|
||||
parseESP(obj.generic);
|
||||
parseHistory(obj,"pwr", pwrExeOnce)
|
||||
document.getElementById("pwrLast").innerHTML = mLastValue
|
||||
document.getElementById("pwrMaxDay").innerHTML = obj.maxDay
|
||||
|
@ -94,20 +88,6 @@
|
|||
if (pwrExeOnce) {
|
||||
pwrExeOnce = false;
|
||||
window.setInterval("getAjax('/api/powerHistory', parsePowerHistory)", mRefresh * 1000);
|
||||
|
||||
setTimeout(() => {
|
||||
getAjax("/api/yieldDayHistory", parseYieldDayHistory);
|
||||
} , 20);
|
||||
}
|
||||
}
|
||||
function parseYieldDayHistory(obj) {
|
||||
if (null != obj) {
|
||||
parseNav(obj.generic);
|
||||
parseHistory(obj, "yd", ydExeOnce)
|
||||
}
|
||||
if (ydExeOnce) {
|
||||
ydExeOnce = false;
|
||||
window.setInterval("getAjax('/api/yieldDayHistory', parseYieldDayHistory)", mRefresh * 500);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -10,15 +10,15 @@
|
|||
<a id="nav11" class="acitve" href="/history?v={#VERSION}">{#NAV_HISTORY}</a>
|
||||
<a id="nav4" class="hide" href="/serial?v={#VERSION}">{#NAV_WEBSERIAL}</a>
|
||||
<a id="nav5" class="hide" href="/setup?v={#VERSION}">{#NAV_SETTINGS}</a>
|
||||
<span class="seperator"></span>
|
||||
<span class="separator"></span>
|
||||
<a id="nav6" class="hide" href="/update?v={#VERSION}">Update</a>
|
||||
<a id="nav7" class="hide" href="/system?v={#VERSION}">System</a>
|
||||
<span class="seperator"></span>
|
||||
<span class="separator"></span>
|
||||
<a id="nav8" href="/api" target="_blank">REST API</a>
|
||||
<a id="nav9" href="https://ahoydtu.de" target="_blank">{#NAV_DOCUMENTATION}</a>
|
||||
<a id="nav10" href="/about?v={#VERSION}">{#NAV_ABOUT}</a>
|
||||
<a id="nav12" href="#" class="hide" target="_blank">Custom Link</a>
|
||||
<span class="seperator"></span>
|
||||
<span class="separator"></span>
|
||||
<a id="nav0" class="hide" href="/login">Login</a>
|
||||
<a id="nav1" class="hide" href="/logout">Logout</a>
|
||||
</div>
|
||||
|
|
|
@ -41,27 +41,24 @@
|
|||
var release = null;
|
||||
|
||||
function apiCb(obj) {
|
||||
var e = document.getElementById("apiResult");
|
||||
var e = document.getElementById("apiResult")
|
||||
if(obj.success) {
|
||||
e.innerHTML = " {#COMMAND_EXE}";
|
||||
getAjax("/api/index", parse);
|
||||
}
|
||||
else
|
||||
e.innerHTML = " {#ERROR}: " + obj.error;
|
||||
e.innerHTML = " {#COMMAND_EXE}"
|
||||
getAjax("/api/index", parse)
|
||||
} else
|
||||
e.innerHTML = " {#ERROR}: " + obj.error
|
||||
}
|
||||
|
||||
function setTime() {
|
||||
var date = new Date();
|
||||
var obj = new Object();
|
||||
obj.cmd = "set_time";
|
||||
obj.val = parseInt(date.getTime() / 1000);
|
||||
getAjax("/api/setup", apiCb, "POST", JSON.stringify(obj));
|
||||
var date = new Date()
|
||||
var obj = {cmd: "set_time", token: "*", val: parseInt(date.getTime() / 1000)}
|
||||
getAjax("/api/setup", apiCb, "POST", JSON.stringify(obj))
|
||||
}
|
||||
|
||||
function parseGeneric(obj) {
|
||||
if(exeOnce)
|
||||
parseESP(obj);
|
||||
parseRssi(obj);
|
||||
parseESP(obj)
|
||||
parseRssi(obj)
|
||||
}
|
||||
|
||||
function parseSys(obj) {
|
||||
|
@ -73,9 +70,9 @@
|
|||
var min = parseInt(up / 60) % 60;
|
||||
var sec = up % 60;
|
||||
var e = document.getElementById("uptime");
|
||||
e.innerHTML = days + " Day";
|
||||
e.innerHTML = days + " {#DAY}";
|
||||
if(1 != days)
|
||||
e.innerHTML += "s";
|
||||
e.innerHTML += "{#S}";
|
||||
e.innerHTML += ", " + ("0"+hrs).substr(-2) + ":"
|
||||
+ ("0"+min).substr(-2) + ":"
|
||||
+ ("0"+sec).substr(-2);
|
||||
|
|
|
@ -12,12 +12,12 @@
|
|||
<textarea id="serial" class="mt-3" cols="80" rows="40" readonly></textarea>
|
||||
</div>
|
||||
<div class="row my-3">
|
||||
<div class="col-3">console active: <span class="dot" id="active"></span></div>
|
||||
<div class="col-3 col-sm-4 my-3">Uptime: <span id="uptime"></span></div>
|
||||
<div class="col-3">{#CONSOLE_ACTIVE}: <span class="dot" id="active"></span></div>
|
||||
<div class="col-3 col-sm-4 my-3">{#UPTIME}: <span id="uptime"></span></div>
|
||||
<div class="col-6 col-sm-4 a-r">
|
||||
<input type="button" value="clear" class="btn" id="clear"/>
|
||||
<input type="button" value="autoscroll" class="btn" id="scroll"/>
|
||||
<input type="button" value="copy" class="btn" id="copy"/>
|
||||
<input type="button" value="{#BTN_CLEAR}" class="btn" id="clear"/>
|
||||
<input type="button" value="{#BTN_AUTOSCROLL}" class="btn" id="scroll"/>
|
||||
<input type="button" value="{#BTN_COPY}" class="btn" id="copy"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -35,7 +35,7 @@
|
|||
var hrs = parseInt(up / 3600) % 24;
|
||||
var min = parseInt(up / 60) % 60;
|
||||
var sec = up % 60;
|
||||
document.getElementById("uptime").innerHTML = days + " Days, "
|
||||
document.getElementById("uptime").innerHTML = days + " {#DAYS}, "
|
||||
+ ("0"+hrs).substr(-2) + ":"
|
||||
+ ("0"+min).substr(-2) + ":"
|
||||
+ ("0"+sec).substr(-2);
|
||||
|
@ -65,7 +65,7 @@
|
|||
});
|
||||
document.getElementById("scroll").addEventListener("click", function() {
|
||||
mAutoScroll = !mAutoScroll;
|
||||
this.value = (mAutoScroll) ? "autoscroll" : "manual scroll";
|
||||
this.value = (mAutoScroll) ? "{#BTN_AUTOSCROLL}" : "{#BTN_MANUALSCROLL}";
|
||||
});
|
||||
document.getElementById("copy").addEventListener("click", function() {
|
||||
con.value = version + " - " + build + "\n---------------\n" + con.value;
|
||||
|
@ -80,10 +80,10 @@
|
|||
try {
|
||||
return document.execCommand("copy"); // Security exception may be thrown by some browsers.
|
||||
} catch (ex) {
|
||||
alert("Copy to clipboard failed" + ex);
|
||||
alert("{#CLIPBOARD_FAILED} " + ex);
|
||||
} finally {
|
||||
document.body.removeChild(ta);
|
||||
alert("Copied to clipboard");
|
||||
alert("{#COPIED_TO_CLIPBOARD}");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -26,6 +26,14 @@
|
|||
<div class="col-4 col-sm-9"><input type="checkbox" name="darkMode"/></div>
|
||||
<div class="col-12">{#DARK_MODE_NOTE}</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-8 col-sm-3">{#REGION}</div>
|
||||
<div class="col-4 col-sm-9" id="region"></div>
|
||||
</div>
|
||||
<div class="row mb-5">
|
||||
<div class="col-8 col-sm-3">{#TIMEZONE}</div>
|
||||
<div class="col-4 col-sm-9" id="timezone"></div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-8 col-sm-3">{#CUSTOM_LINK}</div>
|
||||
<div class="col-4 col-sm-9"><input type="text" name="cstLnk"/></div>
|
||||
|
@ -35,24 +43,8 @@
|
|||
<div class="col-4 col-sm-9"><input type="text" name="cstLnkTxt"/></div>
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset class="mb-4">
|
||||
<fieldset class="mb-4" id="serialCb">
|
||||
<legend class="des">{#SERIAL_CONSOLE}</legend>
|
||||
<div class="row mb-3">
|
||||
<div class="col-8 col-sm-3">{#LOG_PRINT_INVERTER_DATA}</div>
|
||||
<div class="col-4 col-sm-9"><input type="checkbox" name="serEn"/></div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-8 col-sm-3">{#LOG_SERIAL_DEBUG}</div>
|
||||
<div class="col-4 col-sm-9"><input type="checkbox" name="serDbg"/></div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-8 col-sm-3">{#LOG_PRIVACY_MODE}</div>
|
||||
<div class="col-4 col-sm-9"><input type="checkbox" name="priv"/></div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-8 col-sm-3">{#LOG_PRINT_TRACES}</div>
|
||||
<div class="col-4 col-sm-9"><input type="checkbox" name="wholeTrace"/></div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
|
@ -141,10 +133,6 @@
|
|||
<div class="col-8 my-2">{#INTERVAL} [s]</div>
|
||||
<div class="col-4"><input type="number" name="invInterval" title="Invalid input"/></div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-8 my-2">{#INV_GAP} [ms]</div>
|
||||
<div class="col-4"><input type="number" name="invGap" title="Invalid input"/></div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-8 mb-2">{#INV_RESET_MIDNIGHT}</div>
|
||||
<div class="col-4"><input type="checkbox" name="invRstMid"/></div>
|
||||
|
@ -169,10 +157,6 @@
|
|||
<div class="col-8">{#INV_READ_GRID_PROFILE}</div>
|
||||
<div class="col-4"><input type="checkbox" name="rdGrid"/></div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-8">{#INV_YIELD_EFF}</div>
|
||||
<div class="col-4"><input type="number" name="yldEff" step="any"/></div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
|
@ -499,9 +483,6 @@
|
|||
for(var i = 0; i < 31; i++) {
|
||||
esp32cmtPa.push([i, String(i-10) + " dBm"]);
|
||||
}
|
||||
for(var i = 12; i < 41; i++) {
|
||||
esp32cmtFreq.push([i, freqFmt.format(860 + i*0.25) + " MHz"]);
|
||||
}
|
||||
/*ENDIF_ESP32*/
|
||||
var led_high_active = [
|
||||
[0, "{#PIN_LOW_ACTIVE}"],
|
||||
|
@ -558,31 +539,26 @@
|
|||
}
|
||||
|
||||
function setTime() {
|
||||
var date = new Date();
|
||||
var obj = new Object();
|
||||
obj.cmd = "set_time";
|
||||
obj.val = parseInt(date.getTime() / 1000);
|
||||
getAjax("/api/setup", apiCbNtp, "POST", JSON.stringify(obj));
|
||||
setTimeout(function() {getAjax('/api/index', apiCbNtp2)}, 2000);
|
||||
var date = new Date()
|
||||
var obj = {cmd: "set_time", token: "*", val: parseInt(date.getTime() / 1000)}
|
||||
getAjax("/api/setup", apiCbNtp, "POST", JSON.stringify(obj))
|
||||
setTimeout(function() {getAjax('/api/index', apiCbNtp2)}, 2000)
|
||||
}
|
||||
|
||||
function scan() {
|
||||
var obj = new Object();
|
||||
obj.cmd = "scan_wifi";
|
||||
var obj = {cmd: "scan_wifi", token: "*"}
|
||||
getAjax("/api/setup", apiCbWifi, "POST", JSON.stringify(obj));
|
||||
setTimeout(function() {getAjax('/api/setup/networks', listNetworks)}, 5000);
|
||||
}
|
||||
|
||||
function syncTime() {
|
||||
var obj = new Object();
|
||||
obj.cmd = "sync_ntp";
|
||||
getAjax("/api/setup", apiCbNtp, "POST", JSON.stringify(obj));
|
||||
setTimeout(function() {getAjax('/api/index', apiCbNtp2)}, 2000);
|
||||
var obj = {cmd: "sync_ntp", token: "*"}
|
||||
getAjax("/api/setup", apiCbNtp, "POST", JSON.stringify(obj))
|
||||
setTimeout(function() {getAjax('/api/index', apiCbNtp2)}, 2000)
|
||||
}
|
||||
|
||||
function sendDiscoveryConfig() {
|
||||
var obj = new Object();
|
||||
obj.cmd = "discovery_cfg";
|
||||
var obj = {cmd: "discovery_cfg", token: "*"}
|
||||
getAjax("/api/setup", apiCbMqtt, "POST", JSON.stringify(obj));
|
||||
}
|
||||
|
||||
|
@ -625,7 +601,7 @@
|
|||
}
|
||||
|
||||
function ivGlob(obj) {
|
||||
for(var i of [["invInterval", "interval"], ["yldEff", "yldEff"], ["invGap", "gap"]])
|
||||
for(var i of [["invInterval", "interval"]])
|
||||
document.getElementsByName(i[0])[0].value = obj[i[1]];
|
||||
for(var i of ["Mid", "ComStop", "NotAvail", "MaxMid"])
|
||||
document.getElementsByName("invRst"+i)[0].checked = obj["rst" + i];
|
||||
|
@ -650,6 +626,14 @@
|
|||
el.push(mlCb("protMask" + i, a[i], chk))
|
||||
}
|
||||
d.append(...el);
|
||||
|
||||
var tz = []
|
||||
for(i = 0; i < 24; i += 0.5)
|
||||
tz.push([i, ((i-12 > 0) ? "+" : "") + String(i-12)]);
|
||||
document.getElementById("timezone").append(sel("timezone", tz, obj.timezone + 12))
|
||||
var region = [[0, "Europe (860 - 870 MHz)"], [1, "USA, Indonesia (905 - 925 MHz)"], [2, "Brazil (915 - 928 MHz)"]]
|
||||
document.getElementById("region").append(sel("region", region, obj.region))
|
||||
|
||||
}
|
||||
|
||||
function parseGeneric(obj) {
|
||||
|
@ -702,7 +686,6 @@
|
|||
add.ch_yield_cor = [];
|
||||
add.freq = 12;
|
||||
add.pa = 30;
|
||||
add.add2total = true;
|
||||
|
||||
var e = document.getElementById("inverter");
|
||||
e.innerHTML = ""; // remove all childs
|
||||
|
@ -713,6 +696,13 @@
|
|||
ivGlob(obj);
|
||||
}
|
||||
|
||||
function divRow(item0, item1) {
|
||||
return ml("div", {class: "row mb-3"}, [
|
||||
ml("div", {class: "col-3 mt-2"}, item0),
|
||||
ml("div", {class: "col-9"}, item1)
|
||||
])
|
||||
}
|
||||
|
||||
function ivModal(obj) {
|
||||
var lines = [];
|
||||
lines.push(ml("tr", {}, [
|
||||
|
@ -733,62 +723,36 @@
|
|||
|
||||
var cbEn = ml("input", {name: "enable", type: "checkbox"}, null);
|
||||
var cbDisNightCom = ml("input", {name: "disnightcom", type: "checkbox"}, null);
|
||||
var cbAddTotal = ml("input", {name: "add2total", type: "checkbox"}, null);
|
||||
cbEn.checked = (obj.enabled);
|
||||
cbDisNightCom.checked = (obj.disnightcom);
|
||||
cbAddTotal.checked = (obj.add2total);
|
||||
|
||||
var ser = ml("input", {name: "ser", class: "text", type: "number", max: 138999999999, value: obj.serial}, null);
|
||||
var html = ml("div", {}, [
|
||||
tabs(["{#TAB_GENERAL}", "{#TAB_INPUTS}", "{#TAB_RADIO}", "{#TAB_ADVANCED}"]),
|
||||
ml("div", {id: "div{#TAB_GENERAL}", class: "tab-content"}, [
|
||||
ml("div", {class: "row mb-3"}, [
|
||||
ml("div", {class: "col-2"}, "{#INV_ENABLE}"),
|
||||
ml("div", {class: "col-10"}, cbEn)
|
||||
]),
|
||||
ml("div", {class: "row mb-3"}, [
|
||||
ml("div", {class: "col-2 mt-2"}, "{#INV_SERIAL}"),
|
||||
ml("div", {class: "col-10"}, ser)
|
||||
]),
|
||||
ml("div", {class: "row mb-3"}, [
|
||||
ml("div", {class: "col-2 mt-2"}, "Name"),
|
||||
ml("div", {class: "col-10"}, ml("input", {name: "name", class: "text", type: "text", value: obj.name}, null))
|
||||
])
|
||||
divRow("{#INV_ENABLE}", cbEn),
|
||||
divRow("{#INV_SERIAL}", ser),
|
||||
divRow("Name", ml("input", {name: "name", class: "text", type: "text", value: obj.name}, null))
|
||||
]),
|
||||
ml("div", {id: "div{#TAB_INPUTS}", class: "tab-content hide"}, [
|
||||
ml("div", {class: "row mb-3"},
|
||||
ml("table", {class: "table"},
|
||||
ml("tbody", {}, lines)
|
||||
)
|
||||
ml("table", {class: "table"}, ml("tbody", {}, lines))
|
||||
)
|
||||
]),
|
||||
ml("div", {id: "div{#TAB_RADIO}", class: "tab-content hide"}, [
|
||||
ml("input", {type: "hidden", name: "isnrf"}, null),
|
||||
ml("div", {id: "setcmt"}, [
|
||||
ml("div", {class: "row mb-3"}, [
|
||||
ml("div", {class: "col-3 mt-2"}, "{#INV_FREQUENCY}"),
|
||||
ml("div", {class: "col-9"}, sel("freq", esp32cmtFreq, obj.freq))
|
||||
]),
|
||||
ml("div", {class: "row mb-3"}, [
|
||||
ml("div", {class: "col-3 mt-2"}, "{#INV_POWER_LEVEL}"),
|
||||
ml("div", {class: "col-9"}, sel("cmtpa", esp32cmtPa, obj.pa))
|
||||
]),
|
||||
divRow("{#INV_FREQUENCY}", sel("freq", esp32cmtFreq, obj.freq)),
|
||||
divRow("{#INV_POWER_LEVEL}", sel("cmtpa", esp32cmtPa, obj.pa))
|
||||
]),
|
||||
ml("div", {id: "setnrf"},
|
||||
ml("div", {class: "row mb-3"}, [
|
||||
ml("div", {class: "col-3 mt-2"}, "{#INV_POWER_LEVEL}"),
|
||||
ml("div", {class: "col-9"}, sel("nrfpa", nrfPa, obj.pa))
|
||||
]),
|
||||
divRow("{#INV_POWER_LEVEL}", sel("nrfpa", nrfPa, obj.pa))
|
||||
),
|
||||
]),
|
||||
ml("div", {id: "div{#TAB_ADVANCED}", class: "tab-content hide"}, [
|
||||
ml("div", {class: "row mb-3"}, [
|
||||
ml("div", {class: "col-10"}, "{#INV_PAUSE_DURING_NIGHT}"),
|
||||
ml("div", {class: "col-2"}, cbDisNightCom)
|
||||
]),
|
||||
ml("div", {class: "row mb-3"}, [
|
||||
ml("div", {class: "col-10"}, "{#INV_INCLUDE_MQTT_SUM}"),
|
||||
ml("div", {class: "col-2"}, cbAddTotal)
|
||||
])
|
||||
]),
|
||||
ml("div", {class: "row mt-5"}, [
|
||||
|
@ -818,7 +782,8 @@
|
|||
case 0x1000: nrf = true; break;
|
||||
case 0x1100:
|
||||
switch(sn & 0x000f) {
|
||||
case 0x0004: nrf = false; break;
|
||||
case 0x0004:
|
||||
case 0x0005: nrf = false; break;
|
||||
default: nrf = true; break;
|
||||
}
|
||||
break;
|
||||
|
@ -835,8 +800,9 @@
|
|||
|
||||
function ivSave() {
|
||||
var o = new Object();
|
||||
o.cmd = "save_iv";
|
||||
o.id = obj.id;
|
||||
o.cmd = "save_iv"
|
||||
o.token = "*"
|
||||
o.id = obj.id
|
||||
o.ser = parseInt(document.getElementsByName("ser")[0].value, 16);
|
||||
o.name = document.getElementsByName("name")[0].value;
|
||||
o.en = document.getElementsByName("enable")[0].checked;
|
||||
|
@ -854,7 +820,6 @@
|
|||
o.pa = document.getElementsByName("cmtpa")[0].value;
|
||||
o.freq = document.getElementsByName("freq")[0].value;
|
||||
o.disnightcom = document.getElementsByName("disnightcom")[0].checked;
|
||||
o.add2total = document.getElementsByName("add2total")[0].checked;
|
||||
getAjax("/api/setup", cb, "POST", JSON.stringify(o));
|
||||
}
|
||||
|
||||
|
@ -1019,12 +984,28 @@
|
|||
])
|
||||
);
|
||||
}
|
||||
|
||||
var i = 0
|
||||
while(obj.freq_max >= (obj.freq_min + i * 0.25)) {
|
||||
esp32cmtFreq.push([i, freqFmt.format(obj.freq_min + i * 0.25) + " MHz"])
|
||||
i++
|
||||
}
|
||||
}
|
||||
/*ENDIF_ESP32*/
|
||||
|
||||
function parseSerial(obj) {
|
||||
for(var i of [["serEn", "show_live_data"], ["serDbg", "debug"], ["priv", "priv"], ["wholeTrace", "wholeTrace"]])
|
||||
document.getElementsByName(i[0])[0].checked = obj[i[1]];
|
||||
var e = document.getElementById("serialCb")
|
||||
var l = [["serEn", "show_live_data", "{#LOG_PRINT_INVERTER_DATA}"], ["serDbg", "debug", "{#LOG_SERIAL_DEBUG}"], ["priv", "priv", "{#LOG_PRIVACY_MODE}"], ["wholeTrace", "wholeTrace", "{#LOG_PRINT_TRACES}"], ["log2mqtt", "log2mqtt", "{#LOG_TO_MQTT}"]]
|
||||
for(var i of l) {
|
||||
var cb = ml("input", {name: i[0], type: "checkbox"}, null)
|
||||
cb.checked = obj[i[1]]
|
||||
e.appendChild(
|
||||
ml("div", {class: "row mb-3"}, [
|
||||
ml("div", {class: "col-8 col-sm-3"}, i[2]),
|
||||
ml("div", {class: "col-4 col-sm-9"}, cb)
|
||||
])
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function parseDisplay(obj, type, system) {
|
||||
|
|
|
@ -16,11 +16,11 @@ span, li, h3, label, fieldset {
|
|||
color: var(--fg);
|
||||
}
|
||||
|
||||
fieldset, input[type=submit], .btn {
|
||||
fieldset, input[type="submit"], .btn {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
input[type=file] {
|
||||
input[type="file"] {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
@ -33,7 +33,7 @@ textarea {
|
|||
color: var(--fg2);
|
||||
}
|
||||
|
||||
svg rect {fill: #0000AA;}
|
||||
svg rect {fill: #00A;}
|
||||
svg.chart {
|
||||
background: #f2f2f2;
|
||||
border: 2px solid gray;
|
||||
|
@ -44,6 +44,7 @@ div.chartDivContainer {
|
|||
padding: 1px;
|
||||
margin: 1px;
|
||||
}
|
||||
|
||||
div.chartdivContainer span {
|
||||
color: var(--fg2);
|
||||
}
|
||||
|
@ -95,7 +96,7 @@ svg.icon {
|
|||
vertical-align: middle;
|
||||
display: inline-block;
|
||||
margin-top:-4x;
|
||||
padding: 5px 7px 5px 0px;
|
||||
padding: 5px 7px 5px 0;
|
||||
}
|
||||
|
||||
.icon-info {
|
||||
|
@ -138,10 +139,10 @@ svg.icon {
|
|||
background-color: var(--nav-active);
|
||||
}
|
||||
|
||||
span.seperator {
|
||||
span.separator {
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
margin: 5px 0px 5px;
|
||||
margin: 5px 0 5px;
|
||||
background-color: #494949;
|
||||
display: block;
|
||||
}
|
||||
|
@ -391,7 +392,7 @@ th {
|
|||
|
||||
#footer .left {
|
||||
color: #bbb;
|
||||
margin: 23px 0px 0px 25px;
|
||||
margin: 23px 0 0 25px;
|
||||
}
|
||||
|
||||
#footer ul {
|
||||
|
@ -525,7 +526,7 @@ input, select {
|
|||
font-size: 13pt;
|
||||
}
|
||||
|
||||
input[type=text], input[type=password], select, input[type=number] {
|
||||
input[type="text"], input[type="password"], select, input[type="number"] {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid #ccc;
|
||||
|
@ -551,7 +552,7 @@ input.btnDel {
|
|||
input.btn {
|
||||
background-color: var(--primary);
|
||||
color: #fff;
|
||||
border: 0px;
|
||||
border: 0;
|
||||
padding: 7px 20px 7px 20px;
|
||||
margin-bottom: 10px;
|
||||
text-transform: uppercase;
|
||||
|
@ -572,7 +573,7 @@ label {
|
|||
display: inline-block;
|
||||
font-size: 12pt;
|
||||
padding-right: 10px;
|
||||
margin: 10px 0px 0px 15px;
|
||||
margin: 10px 0 0 15px;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
|
@ -601,7 +602,7 @@ div.ModPwr, div.ModName, div.YieldCor {
|
|||
div.hr {
|
||||
height: 1px;
|
||||
border-top: 1px solid #ccc;
|
||||
margin: 10px 0px 10px;
|
||||
margin: 10px 0 10px;
|
||||
}
|
||||
|
||||
#note {
|
||||
|
|
|
@ -22,6 +22,15 @@
|
|||
var total = Array(6).fill(0);
|
||||
var tPwrAck;
|
||||
|
||||
function getErrStr(code) {
|
||||
if("ERR_AUTH") return "{#ERR_AUTH}"
|
||||
if("ERR_INDEX") return "{#ERR_INDEX}"
|
||||
if("ERR_UNKNOWN_CMD") return "{#ERR_UNKNOWN_CMD}"
|
||||
if("ERR_LIMIT_NOT_ACCEPT") return "{#ERR_LIMIT_NOT_ACCEPT}"
|
||||
if("ERR_UNKNOWN_CMD") return "{#ERR_AUTH}"
|
||||
return "n/a"
|
||||
}
|
||||
|
||||
function parseGeneric(obj) {
|
||||
if(true == exeOnce){
|
||||
parseNav(obj);
|
||||
|
@ -454,18 +463,20 @@
|
|||
val = 100;
|
||||
|
||||
var obj = new Object();
|
||||
obj.id = id;
|
||||
obj.cmd = cmd;
|
||||
obj.val = Math.round(val*10);
|
||||
getAjax("/api/ctrl", ctrlCb, "POST", JSON.stringify(obj));
|
||||
obj.id = id
|
||||
obj.token = "*"
|
||||
obj.cmd = cmd
|
||||
obj.val = val
|
||||
getAjax("/api/ctrl", ctrlCb, "POST", JSON.stringify(obj))
|
||||
}
|
||||
|
||||
function applyCtrl(id, cmd, val=0) {
|
||||
var obj = new Object();
|
||||
obj.id = id;
|
||||
obj.cmd = cmd;
|
||||
obj.val = val;
|
||||
getAjax("/api/ctrl", ctrlCb2, "POST", JSON.stringify(obj));
|
||||
obj.id = id
|
||||
obj.token = "*"
|
||||
obj.cmd = cmd
|
||||
obj.val = val
|
||||
getAjax("/api/ctrl", ctrlCb2, "POST", JSON.stringify(obj))
|
||||
}
|
||||
|
||||
function ctrlCb(obj) {
|
||||
|
@ -475,7 +486,7 @@
|
|||
tPwrAck = window.setInterval("getAjax('/api/inverter/pwrack/" + obj.id + "', updatePwrAck)", 1000);
|
||||
}
|
||||
else
|
||||
e.innerHTML = "{#ERROR}: " + obj["error"];
|
||||
e.innerHTML = "{#ERROR}: " + getErrStr(obj.error);
|
||||
}
|
||||
|
||||
function ctrlCb2(obj) {
|
||||
|
@ -483,7 +494,7 @@
|
|||
if(obj.success)
|
||||
e.innerHTML = "{#COMMAND_RECEIVED}";
|
||||
else
|
||||
e.innerHTML = "{#ERROR}: " + obj["error"];
|
||||
e.innerHTML = "{#ERROR}: " + getErrStr(obj.error);
|
||||
}
|
||||
|
||||
function updatePwrAck(obj) {
|
||||
|
|
|
@ -19,21 +19,9 @@
|
|||
#endif
|
||||
|
||||
#ifdef LANG_DE
|
||||
#define INV_INDEX_INVALID "Wechselrichterindex ungültig; "
|
||||
#define WAS_IN_CH_12_TO_14 "Der ESP war in WLAN Kanal 12 bis 14, was uU. zu Abstürzen führt"
|
||||
#else /*LANG_EN*/
|
||||
#define INV_INDEX_INVALID "inverter index invalid: "
|
||||
#endif
|
||||
|
||||
#ifdef LANG_DE
|
||||
#define UNKNOWN_CMD "unbekanntes Kommando: '"
|
||||
#else /*LANG_EN*/
|
||||
#define UNKNOWN_CMD "unknown cmd: '"
|
||||
#endif
|
||||
|
||||
#ifdef LANG_DE
|
||||
#define INV_DOES_NOT_ACCEPT_LIMIT_AT_MOMENT "Leistungsbegrenzung / Ansteuerung aktuell nicht möglich"
|
||||
#else /*LANG_EN*/
|
||||
#define INV_DOES_NOT_ACCEPT_LIMIT_AT_MOMENT "inverter does not accept dev control request at this moment"
|
||||
#define WAS_IN_CH_12_TO_14 "Your ESP was in wifi channel 12 to 14. It may cause reboots of your AhoyDTU"
|
||||
#endif
|
||||
|
||||
#ifdef LANG_DE
|
||||
|
|
|
@ -148,6 +148,16 @@
|
|||
"en": "(empty browser cache or use CTRL + F5 after reboot to apply this setting)",
|
||||
"de": "(der Browser-Cache muss geleert oder STRG + F5 gedrückt werden, um diese Einstellung zu aktivieren)"
|
||||
},
|
||||
{
|
||||
"token": "REGION",
|
||||
"en": "Region",
|
||||
"de": "Region"
|
||||
},
|
||||
{
|
||||
"token": "TIMEZONE",
|
||||
"en": "Timezone",
|
||||
"de": "Zeitzone"
|
||||
},
|
||||
{
|
||||
"token": "CUSTOM_LINK",
|
||||
"en": "Custom link (leave empty to hide element in navigation)",
|
||||
|
@ -193,6 +203,11 @@
|
|||
"en": "Print whole traces in Log",
|
||||
"de": "alle Informationen in Log schreiben"
|
||||
},
|
||||
{
|
||||
"token": "LOG_TO_MQTT",
|
||||
"en": "Send Serial debug over MqTT",
|
||||
"de": "sende serielles Log über MqTT"
|
||||
},
|
||||
{
|
||||
"token": "NETWORK",
|
||||
"en": "Network",
|
||||
|
@ -278,11 +293,6 @@
|
|||
"en": "Interval",
|
||||
"de": "Intervall"
|
||||
},
|
||||
{
|
||||
"token": "INV_GAP",
|
||||
"en": "Communication Gap",
|
||||
"de": "Kommunikationslücke"
|
||||
},
|
||||
{
|
||||
"token": "INV_RESET_MIDNIGHT",
|
||||
"en": "Reset values and YieldDay at midnight",
|
||||
|
@ -313,11 +323,6 @@
|
|||
"en": "Read Grid Profile",
|
||||
"de": "Grid-Profil auslesen"
|
||||
},
|
||||
{
|
||||
"token": "INV_YIELD_EFF",
|
||||
"en": "Yield Efficiency (default 1.0)",
|
||||
"de": "Ertragseffizienz (Standard 1.0)"
|
||||
},
|
||||
{
|
||||
"token": "NTP_INTERVAL",
|
||||
"en": "NTP Interval (in minutes, min. 5 minutes)",
|
||||
|
@ -653,11 +658,6 @@
|
|||
"en": "Pause communication during night (lat. and lon. need to be set)",
|
||||
"de": "Kommunikation während der Nacht pausieren (Breiten- und Längengrad müssen gesetzt sein"
|
||||
},
|
||||
{
|
||||
"token": "INV_INCLUDE_MQTT_SUM",
|
||||
"en": "Include inverter to sum of total (should be checked by default, MqTT only)",
|
||||
"de": "Wechselrichter in Liste der aufzuaddierenden Wechselrichter aufnehmen (nur MqTT)"
|
||||
},
|
||||
{
|
||||
"token": "BTN_SAVE",
|
||||
"en": "save",
|
||||
|
@ -670,7 +670,7 @@
|
|||
},
|
||||
{
|
||||
"token": "INV_DELETE_SURE",
|
||||
"en": "do you realy want to delete inverter?",
|
||||
"en": "do you really want to delete inverter?",
|
||||
"de": "Willst du den Wechselrichter wirklich löschen?"
|
||||
},
|
||||
{
|
||||
|
@ -865,6 +865,56 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "serial.html",
|
||||
"list": [
|
||||
{
|
||||
"token": "BTN_CLEAR",
|
||||
"en": "clear",
|
||||
"de": "löschen"
|
||||
},
|
||||
{
|
||||
"token": "BTN_AUTOSCROLL",
|
||||
"en": "autoscroll",
|
||||
"de": "automatisch scrollen"
|
||||
},
|
||||
{
|
||||
"token": "BTN_MANUALSCROLL",
|
||||
"en": "manual scroll",
|
||||
"de": "manuell scrollen"
|
||||
},
|
||||
{
|
||||
"token": "BTN_COPY",
|
||||
"en": "copy",
|
||||
"de": "kopieren"
|
||||
},
|
||||
{
|
||||
"token": "CONSOLE_ACTIVE",
|
||||
"en": "console active",
|
||||
"de": "Konsole aktiv"
|
||||
},
|
||||
{
|
||||
"token": "UPTIME",
|
||||
"en": "uptime",
|
||||
"de": "Laufzeit"
|
||||
},
|
||||
{
|
||||
"token": "DAYS",
|
||||
"en": "days",
|
||||
"de": "Tage"
|
||||
},
|
||||
{
|
||||
"token": "COPIED_TO_CLIPBOARD",
|
||||
"en": "Copied to clipboard",
|
||||
"de": "in die Zwischenablage kopiert"
|
||||
},
|
||||
{
|
||||
"token": "CLIPBOARD_FAILED",
|
||||
"en": "Copy failed",
|
||||
"de": "kopieren fehlgeschlagen"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "index.html",
|
||||
"list": [
|
||||
|
@ -938,6 +988,16 @@
|
|||
"en": "Error",
|
||||
"de": "Fehler"
|
||||
},
|
||||
{
|
||||
"token": "DAY",
|
||||
"en": "day",
|
||||
"de": "Tag"
|
||||
},
|
||||
{
|
||||
"token": "S",
|
||||
"en": "s",
|
||||
"de": "e"
|
||||
},
|
||||
{
|
||||
"token": "NTP_UNREACH",
|
||||
"en": "NTP timeserver unreachable",
|
||||
|
@ -951,7 +1011,7 @@
|
|||
{
|
||||
"token": "NIGHT_TIME",
|
||||
"en": "Night time, inverter polling disabled",
|
||||
"de": "Wechselrichterabfrage deaktivert (Nacht)"
|
||||
"de": "Wechselrichterabfrage deaktiviert (Nacht)"
|
||||
},
|
||||
{
|
||||
"token": "PAUSED_AT",
|
||||
|
@ -1070,7 +1130,7 @@
|
|||
},
|
||||
{
|
||||
"token": "WARN_DIFF_ENV",
|
||||
"en": "your environment does not match the update file!",
|
||||
"en": "your environment may not match the update file!",
|
||||
"de": "Die ausgewählte Firmware passt u.U. nicht zum Chipsatz!"
|
||||
},
|
||||
{
|
||||
|
@ -1386,7 +1446,7 @@
|
|||
{
|
||||
"token": "CMD_RECEIVED_WAIT_ACK",
|
||||
"en": "received command, waiting for inverter acknowledge ...",
|
||||
"de": "Befehl erhalten, warte auf Bestäigung von Wechselrichter ..."
|
||||
"de": "Befehl erhalten, warte auf Bestäigung vom Wechselrichter ..."
|
||||
},
|
||||
{
|
||||
"token": "COMMAND_RECEIVED",
|
||||
|
@ -1402,6 +1462,31 @@
|
|||
"token": "INV_ACK",
|
||||
"en": "inverter acknowledged active power control command",
|
||||
"de": "Wechselrichter hat die Leistungsbegrenzung akzeptiert"
|
||||
},
|
||||
{
|
||||
"token": "ERR_AUTH",
|
||||
"en": "authentication error",
|
||||
"de": "Authentifizierungsfehler"
|
||||
},
|
||||
{
|
||||
"token": "ERR_INDEX",
|
||||
"en": "inverter index invalid",
|
||||
"de": "Wechselrichterindex ungültig"
|
||||
},
|
||||
{
|
||||
"token": "ERR_UNKNOWN_CMD",
|
||||
"en": "unknown cmd",
|
||||
"de": "unbekanntes Kommando"
|
||||
},
|
||||
{
|
||||
"token": "ERR_LIMIT_NOT_ACCEPT",
|
||||
"en": "inverter does not accept dev control request at this moment",
|
||||
"de": "Leistungsbegrenzung / Ansteuerung aktuell nicht möglich"
|
||||
},
|
||||
{
|
||||
"token": "ERR_PROTECTED",
|
||||
"en": "not logged in, command not possible!",
|
||||
"de": "nicht angemeldet, Kommando nicht möglich!"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
115
src/web/web.h
115
src/web/web.h
|
@ -43,13 +43,7 @@ template <class HMSYSTEM>
|
|||
class Web {
|
||||
public:
|
||||
Web(void) : mWeb(80), mEvts("/events") {
|
||||
mProtected = true;
|
||||
mLogoutTimeout = 0;
|
||||
|
||||
memset(mSerialBuf, 0, WEB_SERIAL_BUF_SIZE);
|
||||
mSerialBufFill = 0;
|
||||
mSerialAddTime = true;
|
||||
mSerialClientConnnected = false;
|
||||
}
|
||||
|
||||
void setup(IApp *app, HMSYSTEM *sys, settings_t *config) {
|
||||
|
@ -106,16 +100,6 @@ class Web {
|
|||
}
|
||||
|
||||
void tickSecond() {
|
||||
if (0 != mLogoutTimeout) {
|
||||
mLogoutTimeout -= 1;
|
||||
if (0 == mLogoutTimeout) {
|
||||
if (strlen(mConfig->sys.adminPwd) > 0)
|
||||
mProtected = true;
|
||||
}
|
||||
|
||||
DPRINTLN(DBG_DEBUG, "auto logout in " + String(mLogoutTimeout));
|
||||
}
|
||||
|
||||
if (mSerialClientConnnected) {
|
||||
if (mSerialBufFill > 0) {
|
||||
mEvts.send(mSerialBuf, "serial", millis());
|
||||
|
@ -129,27 +113,6 @@ class Web {
|
|||
return &mWeb;
|
||||
}
|
||||
|
||||
void setProtection(bool protect) {
|
||||
mProtected = protect;
|
||||
}
|
||||
|
||||
bool isProtected(AsyncWebServerRequest *request) {
|
||||
bool prot;
|
||||
prot = mProtected;
|
||||
if(!prot) {
|
||||
if(strlen(mConfig->sys.adminPwd) > 0) {
|
||||
uint8_t ip[4];
|
||||
ah::ip2Arr(ip, request->client()->remoteIP().toString().c_str());
|
||||
for(uint8_t i = 0; i < 4; i++) {
|
||||
if(mLoginIp[i] != ip[i])
|
||||
prot = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return prot;
|
||||
}
|
||||
|
||||
void showUpdate2(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) {
|
||||
if (!index) {
|
||||
Serial.printf("Update Start: %s\n", filename.c_str());
|
||||
|
@ -260,7 +223,7 @@ class Web {
|
|||
}
|
||||
|
||||
void checkProtection(AsyncWebServerRequest *request) {
|
||||
if(isProtected(request)) {
|
||||
if(mApp->isProtected(request->client()->remoteIP().toString().c_str(), "", true)) {
|
||||
checkRedirect(request);
|
||||
return;
|
||||
}
|
||||
|
@ -347,8 +310,7 @@ class Web {
|
|||
|
||||
if (request->args() > 0) {
|
||||
if (String(request->arg("pwd")) == String(mConfig->sys.adminPwd)) {
|
||||
mProtected = false;
|
||||
ah::ip2Arr(mLoginIp, request->client()->remoteIP().toString().c_str());
|
||||
mApp->unlock(request->client()->remoteIP().toString().c_str(), true);
|
||||
request->redirect("/");
|
||||
}
|
||||
}
|
||||
|
@ -362,8 +324,7 @@ class Web {
|
|||
DPRINTLN(DBG_VERBOSE, F("onLogout"));
|
||||
|
||||
checkProtection(request);
|
||||
|
||||
mProtected = true;
|
||||
mApp->lock(true);
|
||||
|
||||
AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html; charset=UTF-8"), system_html, system_html_len);
|
||||
response->addHeader(F("Content-Encoding"), "gzip");
|
||||
|
@ -386,7 +347,6 @@ class Web {
|
|||
|
||||
void onCss(AsyncWebServerRequest *request) {
|
||||
DPRINTLN(DBG_VERBOSE, F("onCss"));
|
||||
mLogoutTimeout = LOGOUT_TIMEOUT;
|
||||
AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/css"), style_css, style_css_len);
|
||||
response->addHeader(F("Content-Encoding"), "gzip");
|
||||
if(request->hasParam("v")) {
|
||||
|
@ -477,7 +437,8 @@ class Web {
|
|||
request->arg("device").toCharArray(mConfig->sys.deviceName, DEVNAME_LEN);
|
||||
mConfig->sys.darkMode = (request->arg("darkMode") == "on");
|
||||
mConfig->sys.schedReboot = (request->arg("schedReboot") == "on");
|
||||
|
||||
mConfig->sys.region = (request->arg("region")).toInt();
|
||||
mConfig->sys.timezone = (request->arg("timezone")).toInt() - 12;
|
||||
|
||||
if (request->arg("cstLnk") != "") {
|
||||
request->arg("cstLnk").toCharArray(mConfig->plugin.customLink, MAX_CUSTOM_LINK_LEN);
|
||||
|
@ -490,7 +451,7 @@ class Web {
|
|||
// protection
|
||||
if (request->arg("adminpwd") != "{PWD}") {
|
||||
request->arg("adminpwd").toCharArray(mConfig->sys.adminPwd, PWD_LEN);
|
||||
mProtected = (strlen(mConfig->sys.adminPwd) > 0);
|
||||
mApp->lock(false);
|
||||
}
|
||||
mConfig->sys.protectionMask = 0x0000;
|
||||
for (uint8_t i = 0; i < 7; i++) {
|
||||
|
@ -518,14 +479,11 @@ class Web {
|
|||
mConfig->inst.startWithoutTime = (request->arg("strtWthtTm") == "on");
|
||||
mConfig->inst.readGrid = (request->arg("rdGrid") == "on");
|
||||
mConfig->inst.rstMaxValsMidNight = (request->arg("invRstMaxMid") == "on");
|
||||
mConfig->inst.yieldEffiency = (request->arg("yldEff")).toFloat();
|
||||
mConfig->inst.gapMs = (request->arg("invGap")).toInt();
|
||||
|
||||
|
||||
// pinout
|
||||
uint8_t pin;
|
||||
for (uint8_t i = 0; i < 16; i++) {
|
||||
pin = request->arg(String(pinArgNames[i])).toInt();
|
||||
uint8_t pin = request->arg(String(pinArgNames[i])).toInt();
|
||||
switch(i) {
|
||||
case 0: mConfig->nrf.pinCs = ((pin != 0xff) ? pin : DEF_NRF_CS_PIN); break;
|
||||
case 1: mConfig->nrf.pinCe = ((pin != 0xff) ? pin : DEF_NRF_CE_PIN); break;
|
||||
|
@ -589,6 +547,7 @@ class Web {
|
|||
mConfig->serial.privacyLog = (request->arg("priv") == "on");
|
||||
mConfig->serial.printWholeTrace = (request->arg("wholeTrace") == "on");
|
||||
mConfig->serial.showIv = (request->arg("serEn") == "on");
|
||||
mConfig->serial.log2mqtt = (request->arg("log2mqtt") == "on");
|
||||
|
||||
// display
|
||||
mConfig->plugin.display.pwrSaveAtIvOffline = (request->arg("disp_pwr") == "on");
|
||||
|
@ -666,8 +625,8 @@ class Web {
|
|||
// NOTE: Grouping for fields with channels and totals is currently not working
|
||||
// TODO: Handle grouping and sorting for independant from channel number
|
||||
// NOTE: Check packetsize for MAX_NUM_INVERTERS. Successfully Tested with 4 Inverters (each with 4 channels)
|
||||
const char * metricConstPrefix = "ahoy_solar_";
|
||||
const char * metricConstInverterFormat = " {inverter=\"%s\"} %d\n";
|
||||
const char* metricConstPrefix = "ahoy_solar_";
|
||||
const char* metricConstInverterFormat = " {inverter=\"%s\"} %d\n";
|
||||
typedef enum {
|
||||
metricsStateInverterInfo=0, metricsStateInverterEnabled=1, metricsStateInverterAvailable=2,
|
||||
metricsStateInverterProducing=3, metricsStateInverterPowerLimitRead=4, metricsStateInverterPowerLimitAck=5,
|
||||
|
@ -681,7 +640,7 @@ class Web {
|
|||
metricsStateStart,
|
||||
metricsStateEnd
|
||||
} MetricStep_t;
|
||||
MetricStep_t metricsStep;
|
||||
MetricStep_t metricsStep = metricsStateInverterInfo;
|
||||
typedef struct {
|
||||
const char *topic;
|
||||
const char *type;
|
||||
|
@ -693,12 +652,12 @@ class Web {
|
|||
{ "is_enabled", "gauge", metricConstInverterFormat, [](Inverter<> *iv)-> uint64_t {return iv->config->enabled;} },
|
||||
{ "is_available", "gauge", metricConstInverterFormat, [](Inverter<> *iv)-> uint64_t {return iv->isAvailable();} },
|
||||
{ "is_producing", "gauge", metricConstInverterFormat, [](Inverter<> *iv)-> uint64_t {return iv->isProducing();} },
|
||||
{ "power_limit_read", "gauge", metricConstInverterFormat, [](Inverter<> *iv)-> uint64_t {return (int64_t)ah::round3(iv->actPowerLimit);} },
|
||||
{ "power_limit_read", "gauge", metricConstInverterFormat, [](Inverter<> *iv)-> uint64_t {return iv->actPowerLimit;} },
|
||||
{ "power_limit_ack", "gauge", metricConstInverterFormat, [](Inverter<> *iv)-> uint64_t {return (iv->powerLimitAck)?1:0;} },
|
||||
{ "max_power", "gauge", metricConstInverterFormat, [](Inverter<> *iv)-> uint64_t {return iv->getMaxPower();} },
|
||||
{ "radio_rx_success", "counter" ,metricConstInverterFormat, [](Inverter<> *iv)-> uint64_t {return iv->radioStatistics.rxSuccess;} },
|
||||
{ "radio_rx_fail", "counter" ,metricConstInverterFormat, [](Inverter<> *iv)-> uint64_t {return iv->radioStatistics.rxFail;} },
|
||||
{ "radio_rx_fail_answer", "counter" ,metricConstInverterFormat, [](Inverter<> *iv)-> uint64_t {return iv->radioStatistics.rxFailNoAnser;} },
|
||||
{ "radio_rx_fail_answer", "counter" ,metricConstInverterFormat, [](Inverter<> *iv)-> uint64_t {return iv->radioStatistics.rxFailNoAnswer;} },
|
||||
{ "radio_frame_cnt", "counter" ,metricConstInverterFormat, [](Inverter<> *iv)-> uint64_t {return iv->radioStatistics.frmCnt;} },
|
||||
{ "radio_tx_cnt", "counter" ,metricConstInverterFormat, [](Inverter<> *iv)-> uint64_t {return iv->radioStatistics.txCnt;} },
|
||||
{ "radio_retransmits", "counter" ,metricConstInverterFormat, [](Inverter<> *iv)-> uint64_t {return iv->radioStatistics.retransmits;} },
|
||||
|
@ -707,9 +666,6 @@ class Web {
|
|||
{ "radio_dtu_loss_cnt", "counter" ,metricConstInverterFormat, [](Inverter<> *iv)-> uint64_t {return iv->radioStatistics.dtuLoss;} },
|
||||
{ "radio_dtu_sent_cnt", "counter" ,metricConstInverterFormat, [](Inverter<> *iv)-> uint64_t {return iv->radioStatistics.dtuSent;} }
|
||||
};
|
||||
int metricsInverterId;
|
||||
uint8_t metricsFieldId;
|
||||
bool metricDeclared, metricTotalDeclard;
|
||||
|
||||
void showMetrics(AsyncWebServerRequest *request) {
|
||||
DPRINTLN(DBG_VERBOSE, F("web::showMetrics"));
|
||||
|
@ -724,7 +680,6 @@ class Web {
|
|||
char type[60], topic[100], val[25];
|
||||
size_t len = 0;
|
||||
int alarmChannelId;
|
||||
int metricsChannelId;
|
||||
|
||||
// Perform grouping on metrics according to format specification
|
||||
// Each step must return at least one character. Otherwise the processing of AsyncWebServerResponse stops.
|
||||
|
@ -748,7 +703,7 @@ class Web {
|
|||
snprintf(topic,sizeof(topic),"%swifi_rssi_db{devicename=\"%s\"} %d\n",metricConstPrefix, mConfig->sys.deviceName, WiFi.RSSI());
|
||||
metrics += String(type) + String(topic);
|
||||
|
||||
len = snprintf((char *)buffer,maxLen,"%s",metrics.c_str());
|
||||
len = snprintf(reinterpret_cast<char*>(buffer), maxLen,"%s",metrics.c_str());
|
||||
// Next is Inverter information
|
||||
metricsStep = metricsStateInverterInfo;
|
||||
break;
|
||||
|
@ -776,7 +731,7 @@ class Web {
|
|||
(String("ahoy_solar_inverter_") + inverterMetrics[metricsStep].topic +
|
||||
inverterMetrics[metricsStep].format).c_str(),
|
||||
inverterMetrics[metricsStep].valueFunc);
|
||||
len = snprintf((char *)buffer,maxLen,"%s",metrics.c_str());
|
||||
len = snprintf(reinterpret_cast<char*>(buffer), maxLen, "%s", metrics.c_str());
|
||||
// ugly hack to increment the enum
|
||||
metricsStep = static_cast<MetricStep_t>( static_cast<int>(metricsStep) + 1);
|
||||
// Prepare Realtime Field loop, which may be startet next
|
||||
|
@ -796,7 +751,7 @@ class Web {
|
|||
metrics = "# Info: all realtime fields processed\n";
|
||||
metricsStep = metricsStateAlarmData;
|
||||
}
|
||||
len = snprintf((char *)buffer,maxLen,"%s",metrics.c_str());
|
||||
len = snprintf(reinterpret_cast<char *>(buffer), maxLen, "%s", metrics.c_str());
|
||||
break;
|
||||
|
||||
case metricStateRealtimeInverterId: // Iterate over all inverters for this field
|
||||
|
@ -806,7 +761,7 @@ class Web {
|
|||
iv = mSys->getInverterByPos(metricsInverterId);
|
||||
if (NULL != iv) {
|
||||
rec = iv->getRecordStruct(RealTimeRunData_Debug);
|
||||
for (metricsChannelId=0; metricsChannelId < rec->length;metricsChannelId++) {
|
||||
for (int metricsChannelId=0; metricsChannelId < rec->length;metricsChannelId++) {
|
||||
uint8_t channel = rec->assign[metricsChannelId].ch;
|
||||
|
||||
// Try inverter channel (channel 0) or any channel with maxPwr > 0
|
||||
|
@ -823,10 +778,10 @@ class Web {
|
|||
// report value
|
||||
if (0 == channel) {
|
||||
// Report a _total value if also channel values were reported. Otherwise report without _total
|
||||
char total[7];
|
||||
char total[7] = {0};
|
||||
if (metricDeclared) {
|
||||
// A declaration and value for channels have been delivered. So declare and deliver a _total metric
|
||||
strncpy(total, "_total", 6);
|
||||
snprintf(total, sizeof(total), "_total");
|
||||
}
|
||||
if (!metricTotalDeclard) {
|
||||
snprintf(type, sizeof(type), "# TYPE %s%s%s%s %s\n",metricConstPrefix, iv->getFieldName(metricsChannelId, rec), promUnit.c_str(), total, promType.c_str());
|
||||
|
@ -870,7 +825,7 @@ class Web {
|
|||
metricsFieldId++; // Process next field Id
|
||||
metricsStep = metricStateRealtimeFieldId;
|
||||
}
|
||||
len = snprintf((char *)buffer,maxLen,"%s",metrics.c_str());
|
||||
len = snprintf(reinterpret_cast<char *>(buffer), maxLen, "%s", metrics.c_str());
|
||||
break;
|
||||
|
||||
case metricsStateAlarmData: // Alarm Info loop : fit to one packet
|
||||
|
@ -894,7 +849,7 @@ class Web {
|
|||
}
|
||||
}
|
||||
}
|
||||
len = snprintf((char*)buffer,maxLen,"%s",metrics.c_str());
|
||||
len = snprintf(reinterpret_cast<char*>(buffer), maxLen, "%s", metrics.c_str());
|
||||
metricsStep = metricsStateEnd;
|
||||
break;
|
||||
|
||||
|
@ -913,10 +868,9 @@ class Web {
|
|||
|
||||
// Traverse all inverters and collect the metric via valueFunc
|
||||
String inverterMetric(char *buffer, size_t len, const char *format, std::function<uint64_t(Inverter<> *iv)> valueFunc) {
|
||||
Inverter<> *iv;
|
||||
String metric = "";
|
||||
for (int metricsInverterId = 0; metricsInverterId < mSys->getNumInverters();metricsInverterId++) {
|
||||
iv = mSys->getInverterByPos(metricsInverterId);
|
||||
for (int id = 0; id < mSys->getNumInverters();id++) {
|
||||
Inverter<> *iv = mSys->getInverterByPos(id);
|
||||
if (NULL != iv) {
|
||||
snprintf(buffer,len,format,iv->config->name, valueFunc(iv));
|
||||
metric += String(buffer);
|
||||
|
@ -937,24 +891,27 @@ class Web {
|
|||
if(shortUnit == "Hz") return {"_hertz", "gauge"};
|
||||
return {"", "gauge"};
|
||||
}
|
||||
|
||||
private:
|
||||
int metricsInverterId = 0;
|
||||
uint8_t metricsFieldId = 0;
|
||||
bool metricDeclared = false, metricTotalDeclard = false;
|
||||
#endif
|
||||
private:
|
||||
AsyncWebServer mWeb;
|
||||
AsyncEventSource mEvts;
|
||||
bool mProtected;
|
||||
uint32_t mLogoutTimeout;
|
||||
uint8_t mLoginIp[4];
|
||||
IApp *mApp;
|
||||
HMSYSTEM *mSys;
|
||||
IApp *mApp = nullptr;
|
||||
HMSYSTEM *mSys = nullptr;
|
||||
|
||||
settings_t *mConfig;
|
||||
settings_t *mConfig = nullptr;
|
||||
|
||||
bool mSerialAddTime;
|
||||
bool mSerialAddTime = true;
|
||||
char mSerialBuf[WEB_SERIAL_BUF_SIZE];
|
||||
uint16_t mSerialBufFill;
|
||||
bool mSerialClientConnnected;
|
||||
uint16_t mSerialBufFill = 0;
|
||||
bool mSerialClientConnnected = false;
|
||||
|
||||
File mUploadFp;
|
||||
bool mUploadFail;
|
||||
bool mUploadFail = false;
|
||||
};
|
||||
|
||||
#endif /*__WEB_H__*/
|
||||
|
|
|
@ -92,6 +92,8 @@ void ahoywifi::tickWifiLoop() {
|
|||
}
|
||||
#if !defined(ESP32)
|
||||
MDNS.update();
|
||||
if(WiFi.channel() > 11)
|
||||
mWasInCh12to14 = true;
|
||||
#endif
|
||||
return;
|
||||
case IN_AP_MODE:
|
||||
|
|
|
@ -37,6 +37,10 @@ class ahoywifi {
|
|||
}
|
||||
void setupStation(void);
|
||||
|
||||
bool getWasInCh12to14() const {
|
||||
return mWasInCh12to14;
|
||||
}
|
||||
|
||||
private:
|
||||
typedef enum WiFiStatus {
|
||||
DISCONNECTED = 0,
|
||||
|
@ -67,7 +71,7 @@ class ahoywifi {
|
|||
void welcome(String ip, String mode);
|
||||
|
||||
|
||||
settings_t *mConfig = NULL;
|
||||
settings_t *mConfig = nullptr;
|
||||
appWifiCb mAppWifiCb;
|
||||
|
||||
DNSServer mDns;
|
||||
|
@ -77,15 +81,16 @@ class ahoywifi {
|
|||
WiFiEventHandler wifiConnectHandler, wifiDisconnectHandler, wifiGotIPHandler;
|
||||
#endif
|
||||
|
||||
WiFiStatus_t mStaConn;
|
||||
uint8_t mCnt;
|
||||
uint32_t *mUtcTimestamp;
|
||||
WiFiStatus_t mStaConn = DISCONNECTED;
|
||||
uint8_t mCnt = 0;
|
||||
uint32_t *mUtcTimestamp = nullptr;
|
||||
|
||||
uint8_t mScanCnt;
|
||||
bool mScanActive;
|
||||
bool mGotDisconnect;
|
||||
uint8_t mScanCnt = 0;
|
||||
bool mScanActive = false;
|
||||
bool mGotDisconnect = false;
|
||||
std::list<uint8_t> mBSSIDList;
|
||||
bool mStopApAllowed;
|
||||
bool mStopApAllowed = false;
|
||||
bool mWasInCh12to14 = false;
|
||||
};
|
||||
|
||||
#endif /*__AHOYWIFI_H__*/
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue