mirror of
https://github.com/lumapu/ahoy.git
synced 2025-05-25 14:56:11 +02:00
Merge branch 'main' into hms
Update to latest release
This commit is contained in:
commit
4fd93be575
32 changed files with 646 additions and 633 deletions
42
.github/ISSUE_TEMPLATE/report-ahoy.md
vendored
42
.github/ISSUE_TEMPLATE/report-ahoy.md
vendored
|
@ -22,58 +22,22 @@ Retailer URL: ______
|
||||||
|
|
||||||
### Antenna:
|
### Antenna:
|
||||||
* [ ] circuit board
|
* [ ] circuit board
|
||||||
* [ ] external antenna
|
* [ ] external antenna (SMA)
|
||||||
|
|
||||||
### Power Stabilization:
|
### Power Stabilization:
|
||||||
* [ ] 100uF Electrolytic Capacitor
|
* [ ] 100uF Electrolytic Capacitor
|
||||||
connected between +3.3V and GND (Pin 1 & 2) of the NRF Module
|
connected between +3.3V and GND (Pin 1 & 2) of the NRF Module
|
||||||
* [ ] Voltage stabilizing motherboard
|
* [ ] Voltage stabilizing motherboard
|
||||||
|
|
||||||
### Connection diagram:
|
|
||||||
* [ ] Image of the your wiring attached
|
|
||||||
|
|
||||||
### Connection diagram I used:
|
|
||||||
| nRF24L01+ Pin | ESP8266 GPIO |
|
|
||||||
| ------------- | -------------- |
|
|
||||||
| Pin 1 GND [*] | GND |
|
|
||||||
| Pin 2 +3.3V | +3.3V |
|
|
||||||
| Pin 3 CE | GPIO2 CE D4 |
|
|
||||||
| Pin 4 CSN | GPIO15 CS D8 |
|
|
||||||
| Pin 5 SCK | GPIO14 SCLK D5 |
|
|
||||||
| Pin 6 MOSI | GPIO13 MOSI D7 |
|
|
||||||
| Pin 7 MISO | GPIO12 MISO D6 |
|
|
||||||
| Pin 8 IRQ | GPIO0 IRQ D3 |
|
|
||||||
|
|
||||||
| nRF24L01+ Pin | ESP32 GPIO |
|
|
||||||
| ------------- | --------------- |
|
|
||||||
| Pin 1 GND [*] | GND |
|
|
||||||
| Pin 2 +3.3V | +3.3V |
|
|
||||||
| Pin 3 CE | GPIO4 CE D4 |
|
|
||||||
| Pin 4 CSN | GPIO5 CS D5 |
|
|
||||||
| Pin 5 SCK | GPIO18 SCLK D18 |
|
|
||||||
| Pin 6 MOSI | GPIO23 MOSI D23 |
|
|
||||||
| Pin 7 MISO | GPIO19 MISO D19 |
|
|
||||||
| Pin 8 IRQ | GPIO0 IRQ D0 |
|
|
||||||
|
|
||||||
Note: [*] GND Pin 1 has a square mark on the nRF24L01+ module
|
|
||||||
|
|
||||||
## Software
|
|
||||||
* [ ] AhoyDTU
|
|
||||||
* [ ] OpenDTU
|
|
||||||
|
|
||||||
### Version / Git SHA:
|
### Version / Git SHA:
|
||||||
Version: _._.__
|
Version: _._.__
|
||||||
Github Hash: _______
|
Github Hash: _______
|
||||||
|
|
||||||
### Build & Flash Method:
|
### Build & Flash Method:
|
||||||
|
* [ ] AhoyDTU Webinstaller
|
||||||
|
* [ ] VSCode - Platform IO
|
||||||
* [ ] Arduino
|
* [ ] Arduino
|
||||||
* [ ] ESP Tools
|
* [ ] ESP Tools
|
||||||
* [ ] Platform IO
|
|
||||||
|
|
||||||
### Desktop OS:
|
|
||||||
* [ ] Linux
|
|
||||||
* [ ] Windows
|
|
||||||
* [ ] Mac OS
|
|
||||||
|
|
||||||
### Debugging:
|
### Debugging:
|
||||||
* [ ] USB Serial Log (attached)
|
* [ ] USB Serial Log (attached)
|
||||||
|
|
134
.github/ISSUE_TEMPLATE/report.yaml
vendored
134
.github/ISSUE_TEMPLATE/report.yaml
vendored
|
@ -1,18 +1,20 @@
|
||||||
name: "AhoyDTU bug"
|
name: "AhoyDTU bug"
|
||||||
description: "File a bug report"
|
description: "File a bug report"
|
||||||
title: "[ESP8266/ESP32/RaspberryPi] Problem Description / Beschreibung"
|
title: "[Bug]"
|
||||||
labels: ["bug", "needs-triage"]
|
labels: ["bug", "needs-triage"]
|
||||||
assignees:
|
assignees:
|
||||||
- stefan123t
|
- lumapu
|
||||||
body:
|
body:
|
||||||
- type: markdown
|
- type: markdown
|
||||||
attributes:
|
attributes:
|
||||||
value: |
|
value: |
|
||||||
Bitte die Posting Guide lines lesen, Vorlage kopieren und ausfüllen und in Eurem Support Forum Eintrag posten.
|
Bitte die Posting Guide lines lesen, Vorlage kopieren und ausfüllen und in Eurem Support Forum Eintrag posten.
|
||||||
Wir lesen auch gerne Deutsch, bitte fülle die u.a. Fragen aus damit wir Dir bestmöglich helfen können Danke!
|
Wir lesen auch gerne Deutsch, bitte fülle die u.a. Fragen aus damit wir Dir bestmöglich helfen können Danke!
|
||||||
|
Bitte unser FAQ als Hilfestellung prüfen: https://ahoydtu.de/faq
|
||||||
|
|
||||||
Please read, copy & fill in the template from our Posting Guide lines into your Support Forum post.
|
Please read, copy & fill in the template from our Posting Guide lines into your Support Forum post.
|
||||||
We do enjoy the english language, but we need a couple of things to best support you in your goal, please fill in all / most of the details given below. Thanks!
|
We do enjoy the english language, but we need a couple of things to best support you in your goal, please fill in all / most of the details given below. Thanks!
|
||||||
|
Check our FAQ: https://ahoydtu.de/faq
|
||||||
- type: markdown
|
- type: markdown
|
||||||
attributes:
|
attributes:
|
||||||
value: "## Hardware"
|
value: "## Hardware"
|
||||||
|
@ -27,14 +29,16 @@ body:
|
||||||
- RaspberryPi
|
- RaspberryPi
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: input
|
- type: dropdown
|
||||||
id: model
|
id: assembly-type
|
||||||
attributes:
|
attributes:
|
||||||
label: Model name
|
label: Assembly
|
||||||
description: Please give us a precise description of your hardware and/or a link to the vendor
|
description:
|
||||||
placeholder:
|
options:
|
||||||
|
- I did the assebly by myself
|
||||||
|
- the DTU was already assembled
|
||||||
validations:
|
validations:
|
||||||
required: false
|
required: true
|
||||||
- type: dropdown
|
- type: dropdown
|
||||||
id: nrf24l01-module
|
id: nrf24l01-module
|
||||||
attributes:
|
attributes:
|
||||||
|
@ -69,43 +73,11 @@ body:
|
||||||
* special **voltage stabilizing board**
|
* special **voltage stabilizing board**
|
||||||
* **nothing** (yet)
|
* **nothing** (yet)
|
||||||
options:
|
options:
|
||||||
- ~100uF Elko
|
- Elko (~100uF)
|
||||||
- board
|
- board
|
||||||
- nothing
|
- nothing
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: textarea
|
|
||||||
id: connection-diagram
|
|
||||||
attributes:
|
|
||||||
label: Connection diagram
|
|
||||||
description: Tell us which connection diagram you used?
|
|
||||||
value: |
|
|
||||||
## Connection diagram I used:
|
|
||||||
| nRF24L01+ Pin | ESP8266 GPIO |
|
|
||||||
| ------------- | -------------- |
|
|
||||||
| Pin 1 GND [*] | GND |
|
|
||||||
| Pin 2 +3.3V | +3.3V |
|
|
||||||
| Pin 3 CE | GPIO2 CE D4 |
|
|
||||||
| Pin 4 CSN | GPIO15 CS D8 |
|
|
||||||
| Pin 5 SCK | GPIO14 SCLK D5 |
|
|
||||||
| Pin 6 MOSI | GPIO13 MOSI D7 |
|
|
||||||
| Pin 7 MISO | GPIO12 MISO D6 |
|
|
||||||
| Pin 8 IRQ | GPIO0 IRQ D3 |
|
|
||||||
|
|
||||||
| nRF24L01+ Pin | ESP32 GPIO |
|
|
||||||
| ------------- | --------------- |
|
|
||||||
| Pin 1 GND [*] | GND |
|
|
||||||
| Pin 2 +3.3V | +3.3V |
|
|
||||||
| Pin 3 CE | GPIO4 CE D4 |
|
|
||||||
| Pin 4 CSN | GPIO5 CS D5 |
|
|
||||||
| Pin 5 SCK | GPIO18 SCLK D18 |
|
|
||||||
| Pin 6 MOSI | GPIO23 MOSI D23 |
|
|
||||||
| Pin 7 MISO | GPIO19 MISO D19 |
|
|
||||||
| Pin 8 IRQ | GPIO0 IRQ D0 |
|
|
||||||
|
|
||||||
Note: [*] GND Pin 1 has a square mark on the nRF24L01+ module
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: checkboxes
|
- type: checkboxes
|
||||||
id: connection-picture
|
id: connection-picture
|
||||||
attributes:
|
attributes:
|
||||||
|
@ -123,7 +95,7 @@ body:
|
||||||
attributes:
|
attributes:
|
||||||
label: Version
|
label: Version
|
||||||
description: What version of our software are you running ?
|
description: What version of our software are you running ?
|
||||||
placeholder: 0.5.17
|
placeholder: 0.6.0
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: input
|
- type: input
|
||||||
|
@ -131,7 +103,7 @@ body:
|
||||||
attributes:
|
attributes:
|
||||||
label: Github Hash
|
label: Github Hash
|
||||||
description: Which GitHub hash has the build of our software ?
|
description: Which GitHub hash has the build of our software ?
|
||||||
placeholder: 5402e9b
|
placeholder: abc1234
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: dropdown
|
- type: dropdown
|
||||||
|
@ -140,19 +112,11 @@ body:
|
||||||
label: Build & Flash Method
|
label: Build & Flash Method
|
||||||
description: What software do you use to flash / build & flash our firmware images ?
|
description: What software do you use to flash / build & flash our firmware images ?
|
||||||
options:
|
options:
|
||||||
|
- AhoyDTU Webinstaller
|
||||||
|
- VSCode - Platform IO (build & flash)
|
||||||
- ESP Tools (flash)
|
- ESP Tools (flash)
|
||||||
- Platform IO (build & flash)
|
- Arduino IDE
|
||||||
validations:
|
- was already installed
|
||||||
required: true
|
|
||||||
- type: dropdown
|
|
||||||
id: desktop-os
|
|
||||||
attributes:
|
|
||||||
label: Desktop
|
|
||||||
description: Which operating system are you using on your desktop to build & flash ?
|
|
||||||
options:
|
|
||||||
- Linux
|
|
||||||
- Mac OS
|
|
||||||
- Windows
|
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: textarea
|
- type: textarea
|
||||||
|
@ -160,49 +124,12 @@ body:
|
||||||
attributes:
|
attributes:
|
||||||
label: Setup
|
label: Setup
|
||||||
description: |
|
description: |
|
||||||
Which settings are configured under http://ahoy-dtu/setup ?
|
Which settings were modified to which values? Check this page on your DTU: http://ahoy-dtu/setup
|
||||||
Document any relevant setup values correctly.
|
Do not post private data here (SSID / passwords / serial numbers)!
|
||||||
Copy and paste the Inverter Section if you have multiple Inverters.
|
placeholder: |
|
||||||
value: |
|
Some examples:
|
||||||
### Device Host Name
|
- MqTT: only broker was added
|
||||||
- Device Name: AHOY-DTU
|
- Inverter: set intervall to 5 seconds ..
|
||||||
### WiFi
|
|
||||||
- SSID: YOUR_WIFI_SSID *don't paste here*
|
|
||||||
- Password: YOUR_WIFI_PWD *don't paste here*
|
|
||||||
### Inverter
|
|
||||||
#### Inverter 0
|
|
||||||
- Address: 1141752123456
|
|
||||||
- Name: HM-600
|
|
||||||
- Active Power Limit: 65535
|
|
||||||
- Active Power Limit Control Type: no powerlimit
|
|
||||||
- Max Module Power (Wp): 375, 375
|
|
||||||
- Module Name: link, rech
|
|
||||||
### General
|
|
||||||
- Interval [s]: 30
|
|
||||||
- Max retries per Payload: 5
|
|
||||||
### NTP Server
|
|
||||||
- NTP Server / IP: pool.ntp.org
|
|
||||||
- NTP Port: 123
|
|
||||||
### MQTT
|
|
||||||
- Broker / Server IP:
|
|
||||||
- Port: 1883
|
|
||||||
- Username (optional):
|
|
||||||
- Password (optional):
|
|
||||||
- Topic: inverter
|
|
||||||
### System Config
|
|
||||||
#### Pinout (Wemos)
|
|
||||||
- CS: D8 (GPIO15)
|
|
||||||
- CE: D4 (GPIO2)
|
|
||||||
- IRQ: D3 (GPIO0)
|
|
||||||
#### Radio (NRF24L01+)
|
|
||||||
- Amplifier Power Level: LOW
|
|
||||||
#### Serial Console
|
|
||||||
- print inverter data: [x]
|
|
||||||
- Serial Debug: [x]
|
|
||||||
- Interval [s]: 5
|
|
||||||
|
|
||||||
- Reboot device after successful save: [x]
|
|
||||||
- SAVE
|
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: textarea
|
- type: textarea
|
||||||
|
@ -219,12 +146,9 @@ body:
|
||||||
attributes:
|
attributes:
|
||||||
label: Error description
|
label: Error description
|
||||||
description: Please describe what you expected and what happened instead.
|
description: Please describe what you expected and what happened instead.
|
||||||
value: |
|
placeholder: |
|
||||||
1) Go to http://ahoy-dtu/setup
|
1) I went to https://ahoy-dtu.de/web_install and installed latest release
|
||||||
2) configure above settings
|
2) I did some configurations, especially ...
|
||||||
3) Reboot
|
...
|
||||||
4) I did this
|
|
||||||
5) I expected that
|
|
||||||
6) and something completely differen happened
|
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|
3
.github/workflows/compile_development.yml
vendored
3
.github/workflows/compile_development.yml
vendored
|
@ -49,6 +49,9 @@ jobs:
|
||||||
- name: Run PlatformIO
|
- name: Run PlatformIO
|
||||||
run: pio run -d src --environment esp8266-release --environment esp8266-release-prometheus --environment esp8285-release --environment esp32-wroom32-release --environment esp32-wroom32-release-prometheus --environment opendtufusionv1-release
|
run: pio run -d src --environment esp8266-release --environment esp8266-release-prometheus --environment esp8285-release --environment esp32-wroom32-release --environment esp32-wroom32-release-prometheus --environment opendtufusionv1-release
|
||||||
|
|
||||||
|
- name: Copy boot_app0.bin
|
||||||
|
run: cp ~/.platformio/packages/framework-arduinoespressif32/tools/partitions/boot_app0.bin src/.pio/build/opendtufusionv1-release/ota.bin
|
||||||
|
|
||||||
- name: Rename Binary files
|
- name: Rename Binary files
|
||||||
id: rename-binary-files
|
id: rename-binary-files
|
||||||
working-directory: src
|
working-directory: src
|
||||||
|
|
3
.github/workflows/compile_release.yml
vendored
3
.github/workflows/compile_release.yml
vendored
|
@ -53,6 +53,9 @@ jobs:
|
||||||
- name: Run PlatformIO
|
- name: Run PlatformIO
|
||||||
run: pio run -d src --environment esp8266-release --environment esp8266-release-prometheus --environment esp8285-release --environment esp32-wroom32-release --environment esp32-wroom32-release-prometheus --environment opendtufusionv1-release
|
run: pio run -d src --environment esp8266-release --environment esp8266-release-prometheus --environment esp8285-release --environment esp32-wroom32-release --environment esp32-wroom32-release-prometheus --environment opendtufusionv1-release
|
||||||
|
|
||||||
|
- name: Copy boot_app0.bin
|
||||||
|
run: cp ~/.platformio/packages/framework-arduinoespressif32/tools/partitions/boot_app0.bin src/.pio/build/opendtufusionv1-release/ota.bin
|
||||||
|
|
||||||
- name: Rename Binary files
|
- name: Rename Binary files
|
||||||
id: rename-binary-files
|
id: rename-binary-files
|
||||||
working-directory: src
|
working-directory: src
|
||||||
|
|
|
@ -1,13 +1,12 @@
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
This page describes how the module of a Wemos D1 mini and ESP8266 is wired to the radio module and is flashed with the latest Firmware.<br/>
|
On this page, you'll find detailed instructions on how to wire the module of a Wemos D1 mini or ESP32 to the radio module, as well as how to flash it with the latest firmware. This information will enable you to communicate with compatible inverters.
|
||||||
Further information will help you to communicate to the compatible inverters.
|
|
||||||
|
|
||||||
You find the full [User_Manual here](User_Manual.md)
|
You find the full [User_Manual here](User_Manual.md)
|
||||||
|
|
||||||
## Compatiblity
|
## Compatiblity
|
||||||
|
|
||||||
For now the following Inverters should work out of the box:
|
The following inverters are currently supported out of the box:
|
||||||
|
|
||||||
Hoymiles Inverters
|
Hoymiles Inverters
|
||||||
|
|
||||||
|
@ -55,12 +54,11 @@ Solenso Inverters:
|
||||||
|
|
||||||
## Things needed
|
## Things needed
|
||||||
|
|
||||||
In order to build your own Ahoy DTU, you will need some things.<br/>
|
If you're interested in building your own AhoyDTU, you'll need a few things to get started. While we've provided a list of recommended boards below, keep in mind that the maker community is constantly developing new and innovative options that we may not have covered in this readme..
|
||||||
This list is not closing as the Maker Community offers more Boards than we could cover in this Readme.<br/><br/>
|
|
||||||
|
|
||||||
We suggest to use a WEMOS D1 mini Board as well as a NRF24L01+ Breakout Board as a bare minimum.<br/>
|
For optimal performance, we recommend using a Wemos D1 mini or ESP32 along with a NRF24L01+ breakout board as a bare minimum. However, if you have experience working with other ESP boards, any board with at least 4MBytes of ROM may be suitable, depending on your skills.
|
||||||
Any other ESP8266 Board with at least 4MBytes of ROM could work as well, depending on your skills and goals.<br/>
|
|
||||||
Make sure the NRF24L01+ module has the "+" in its name as we depend on the 250kbps features provided only with the plus-variant.
|
Just be sure that the NRF24L01+ module you choose includes the "+" in its name, as we rely on the 250kbps features that are only provided by the plus-variant.
|
||||||
|
|
||||||
| **Parts** | **Price** |
|
| **Parts** | **Price** |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
|
@ -70,7 +68,7 @@ Make sure the NRF24L01+ module has the "+" in its name as we depend on the 250kb
|
||||||
| Jumper Wire Steckbrücken Steckbrett weiblich-weiblich | 2,49 Euro |
|
| Jumper Wire Steckbrücken Steckbrett weiblich-weiblich | 2,49 Euro |
|
||||||
| **Total costs** | **10,34 Euro** |
|
| **Total costs** | **10,34 Euro** |
|
||||||
|
|
||||||
To also run our sister project OpenDTU and be upwards compatible for the future we would recommend to spend some more money on an ESP32 board which has two CPU cores and a NRF24L01+ module with external antenna.
|
If you're interested in using our sister project OpenDTU or you want to future-proof your setup, we recommend investing in an ESP32 board that features two CPU cores. As Radio you can also use a NRF24L01+ module with an external antenna. While this option may cost a bit more, it will provide superior performance and ensure compatibility with upcoming developments.
|
||||||
|
|
||||||
| **Parts** | **Price** |
|
| **Parts** | **Price** |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
|
@ -154,7 +152,7 @@ Example wiring for a 38pin ESP32 module
|
||||||
|
|
||||||
##### ESP32 GPIO settings
|
##### ESP32 GPIO settings
|
||||||
|
|
||||||
For this wiring, set the 3 individual GPIOs under the /setup URL:
|
CS, CE, IRQ must be set according to how they are wired up. For the diagram above, set the 3 individual GPIOs under the /setup URL as follows:
|
||||||
|
|
||||||
```
|
```
|
||||||
CS D1 (GPIO5)
|
CS D1 (GPIO5)
|
||||||
|
@ -162,14 +160,14 @@ CE D2 (GPIO4)
|
||||||
IRQ D0 (GPIO16 - no IRQ!)
|
IRQ D0 (GPIO16 - no IRQ!)
|
||||||
```
|
```
|
||||||
|
|
||||||
ATTENTION: From development version 108 onwards, also MISO, MOSI and SCLK
|
IMPORTANT: From development version 108/release 0.6.0 onwards, also MISO, MOSI, and SCLK
|
||||||
are configurable. Their defaults are correct for 'standard' ESP32 boards
|
are configurable. On new installations, their defaults are correct for most ESP32 boards.
|
||||||
and non-settable for ESP8266 (as this chip cannot move them elsewhere).
|
These pins cannot be configured for ESP82xx boards, as this chip cannot move them elsewhere.
|
||||||
If you have an existing install though, you might see '0' in the web GUI.
|
|
||||||
|
|
||||||
Set MISO=19, MOSI=23, SCLK=18 in GUI and save for existing installs, this is the old
|
If you are upgrading an existing install though, you might see that these pins are set to '0' in the web GUI.
|
||||||
correct default for most ESP32 boards, for ESP82xx, a simple settings save should suffice.
|
Communication with the NRF module wont work. For upgrading an existing installations, set MISO=19, MOSI=23, SCLK=18 in the settings.
|
||||||
Reboot afterwards.
|
This is the correct default for most ESP32 boards. On ESP82xx, simply saving the settings without changes should suffice.
|
||||||
|
Save and reboot.
|
||||||
|
|
||||||
|
|
||||||
## Flash the Firmware on your Ahoy DTU Hardware
|
## Flash the Firmware on your Ahoy DTU Hardware
|
||||||
|
@ -266,9 +264,8 @@ When everything is wired up and the firmware is flashed, it is time to connect t
|
||||||
| /cmdstat | show stat from the home page | | yes |
|
| /cmdstat | show stat from the home page | | yes |
|
||||||
| /visualization | displays the information from your converter | | yes |
|
| /visualization | displays the information from your converter | | yes |
|
||||||
| /livedata | displays the live data | | yes |
|
| /livedata | displays the live data | | yes |
|
||||||
| /json | gets live-data in JSON format | json output from the livedata | no - enable via config_override.h |
|
|
||||||
| /metrics | gets live-data for prometheus | prometheus metrics from the livedata | no - enable via config_override.h |
|
| /metrics | gets live-data for prometheus | prometheus metrics from the livedata | no - enable via config_override.h |
|
||||||
| /api | | | yes |
|
| /api | gets configuration and live-data in JSON format | json output from the configuration or livedata | yes |
|
||||||
|
|
||||||
## MQTT command to set the DTU without webinterface
|
## MQTT command to set the DTU without webinterface
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@ This work is licensed under a
|
||||||
# 🖐 Ahoy!
|
# 🖐 Ahoy!
|
||||||

|

|
||||||
|
|
||||||
**Communicate with Hoymiles inverters via radio**. Get actual values like power, current, daily energy and set parameters like the power limit via web interface or MQTT. In this repository you will find different approaches means Hardware / Software to realize the described functionalities.
|
This repository offers hardware and software solutions for communicating with Hoymiles inverters via radio. With our system, you can easily obtain real-time values such as power, current, and daily energy. Additionally, you can set parameters like the power limit of your inverter to achieve zero export. You can access these functionalities through our user-friendly web interface, MQTT, or JSON. Whether you're monitoring your solar panel system's performance or fine-tuning its settings, our solutions make it easy to achieve your goals.
|
||||||
|
|
||||||
Table of approaches:
|
Table of approaches:
|
||||||
|
|
||||||
|
@ -43,9 +43,7 @@ Table of approaches:
|
||||||
- [The root of development](https://www.mikrocontroller.net/topic/525778)
|
- [The root of development](https://www.mikrocontroller.net/topic/525778)
|
||||||
|
|
||||||
### Development
|
### Development
|
||||||
If you encounter issues use the issues here on github.
|
If you run into any issues, please feel free to use the issue tracker here on Github. When describing your issue, please be as detailed and precise as possible, and take a moment to consider whether the issue is related to our software. This will help us to provide more effective solutions to your problem.
|
||||||
|
|
||||||
Please try to describe your issues as precise as possible and think about if this is a issue with the software here in the repository or other software components.
|
|
||||||
|
|
||||||
**Contributors are always welcome!**
|
**Contributors are always welcome!**
|
||||||
|
|
||||||
|
|
45
ahoy.code-workspace
Normal file
45
ahoy.code-workspace
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
{
|
||||||
|
"folders": [
|
||||||
|
{
|
||||||
|
"path": "."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"files.associations": {
|
||||||
|
"algorithm": "cpp",
|
||||||
|
"array": "cpp",
|
||||||
|
"chrono": "cpp",
|
||||||
|
"deque": "cpp",
|
||||||
|
"format": "cpp",
|
||||||
|
"forward_list": "cpp",
|
||||||
|
"functional": "cpp",
|
||||||
|
"initializer_list": "cpp",
|
||||||
|
"iterator": "cpp",
|
||||||
|
"list": "cpp",
|
||||||
|
"memory": "cpp",
|
||||||
|
"queue": "cpp",
|
||||||
|
"random": "cpp",
|
||||||
|
"regex": "cpp",
|
||||||
|
"vector": "cpp",
|
||||||
|
"xhash": "cpp",
|
||||||
|
"xlocmon": "cpp",
|
||||||
|
"xlocnum": "cpp",
|
||||||
|
"xmemory": "cpp",
|
||||||
|
"xstring": "cpp",
|
||||||
|
"xtree": "cpp",
|
||||||
|
"xutility": "cpp",
|
||||||
|
"*.tcc": "cpp",
|
||||||
|
"string": "cpp",
|
||||||
|
"unordered_map": "cpp",
|
||||||
|
"unordered_set": "cpp",
|
||||||
|
"string_view": "cpp",
|
||||||
|
"sstream": "cpp",
|
||||||
|
"istream": "cpp",
|
||||||
|
"ostream": "cpp"
|
||||||
|
},
|
||||||
|
"editor.formatOnSave": false
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,7 +12,7 @@ Prometheus metrics provided at `/metrics`.
|
||||||
| name | Inverter name from setup |
|
| name | Inverter name from setup |
|
||||||
| serial | Serial number of inverter |
|
| serial | Serial number of inverter |
|
||||||
| inverter | Inverter name from setup |
|
| inverter | Inverter name from setup |
|
||||||
| channel | Channel name from setup |
|
| channel | Channel (Module) name from setup. Label only available if max power level of module is set to non-zero. Be sure to have a cannel name set in configuration. |
|
||||||
|
|
||||||
## Exported Metrics
|
## Exported Metrics
|
||||||
| Metric name | Type | Description | Labels |
|
| Metric name | Type | Description | Labels |
|
||||||
|
|
|
@ -50,6 +50,7 @@ def readVersion(path, infile):
|
||||||
versionnumber += line[p+13:].rstrip() + "."
|
versionnumber += line[p+13:].rstrip() + "."
|
||||||
|
|
||||||
os.mkdir(path + "firmware/")
|
os.mkdir(path + "firmware/")
|
||||||
|
os.mkdir(path + "firmware/s3/")
|
||||||
sha = os.getenv("SHA",default="sha")
|
sha = os.getenv("SHA",default="sha")
|
||||||
|
|
||||||
versionout = version[:-1] + "_" + sha + "_esp8266.bin"
|
versionout = version[:-1] + "_" + sha + "_esp8266.bin"
|
||||||
|
@ -80,7 +81,7 @@ def readVersion(path, infile):
|
||||||
|
|
||||||
versionout = version[:-1] + "_" + sha + "_esp32s3.bin"
|
versionout = version[:-1] + "_" + sha + "_esp32s3.bin"
|
||||||
src = path + ".pio/build/opendtufusionv1-release/firmware.bin"
|
src = path + ".pio/build/opendtufusionv1-release/firmware.bin"
|
||||||
dst = path + "firmware/" + versionout
|
dst = path + "firmware/s3/" + versionout
|
||||||
os.rename(src, dst)
|
os.rename(src, dst)
|
||||||
|
|
||||||
# other ESP32 bin files
|
# other ESP32 bin files
|
||||||
|
@ -89,6 +90,14 @@ def readVersion(path, infile):
|
||||||
os.rename(src + "bootloader.bin", dst + "bootloader.bin")
|
os.rename(src + "bootloader.bin", dst + "bootloader.bin")
|
||||||
os.rename(src + "partitions.bin", dst + "partitions.bin")
|
os.rename(src + "partitions.bin", dst + "partitions.bin")
|
||||||
genOtaBin(path + "firmware/")
|
genOtaBin(path + "firmware/")
|
||||||
|
|
||||||
|
# other ESP32S3 bin files
|
||||||
|
src = path + ".pio/build/opendtufusionv1-release/"
|
||||||
|
dst = path + "firmware/s3/"
|
||||||
|
os.rename(src + "bootloader.bin", dst + "bootloader.bin")
|
||||||
|
os.rename(src + "partitions.bin", dst + "partitions.bin")
|
||||||
|
os.rename(src + "ota.bin", dst + "ota.bin")
|
||||||
|
|
||||||
os.rename("../scripts/gh-action-dev-build-flash.html", path + "install.html")
|
os.rename("../scripts/gh-action-dev-build-flash.html", path + "install.html")
|
||||||
|
|
||||||
print("name=" + versionnumber[:-1] )
|
print("name=" + versionnumber[:-1] )
|
||||||
|
|
|
@ -1,33 +1,15 @@
|
||||||
Changelog v0.6.0
|
Changelog v0.6.9
|
||||||
|
|
||||||
## General
|
* improved MqTT
|
||||||
* improved night time calculation time to 1 minute after last communication pause #515
|
* fix WiFi hostname during boot up
|
||||||
* refactored code for better readability
|
* improved login: only one session at the same time is possible
|
||||||
* improved Hoymiles commuinication (retransmits, immediate power limit transmission, timing at all)
|
* fix UI: login screen for small displays; burger menu height; small modifications for import button (in setup)
|
||||||
* renamed firmware binaries
|
* improved WiFi reconnect
|
||||||
* add login / logout to menu
|
* optimized performance: browser caching was improved to reduce requests from ESP
|
||||||
* add display support for `SH1106`, `SSD1306`, `Nokia` and `ePaper 1.54"` (ESP32 only)
|
* improved NRF24 communication for more stable data transmission
|
||||||
* add yield total correction - move your yield to a new inverter or correct an already used inverter
|
* added / fixed MqTT subscription `ctrl/power/[IV-ID]`
|
||||||
* added import / export feature
|
* improved save settings
|
||||||
* added `Prometheus` endpoints
|
* improved UI in setup: now `.` and `,` are allowed as floating point seperator
|
||||||
* improved wifi connection and stability (connect to strongest AP)
|
* fix zero yield day functionality
|
||||||
* addded Hoymiles alarm IDs to log
|
* LEDs are now configurable to show if 1st inverter is available and if MqTT is connected
|
||||||
* improved `System` information page (eg. radio statitistics)
|
* LED are configurable to active high or low
|
||||||
* improved UI (repsonsive design, (optional) dark mode)
|
|
||||||
* improved system stability (reduced `heap-fragmentation`, don't break settings on failure) #644, #645
|
|
||||||
* added support for 2nd generation of Hoymiles inverters, MI series
|
|
||||||
* improved JSON API for more stable WebUI
|
|
||||||
* added option to disable input display in `/live` (`max-power` has to be set to `0`)
|
|
||||||
* updated documentation
|
|
||||||
* improved settings on ESP32 devices while setting SPI pins (for `NRF24` radio)
|
|
||||||
|
|
||||||
## MqTT
|
|
||||||
* added `comm_disabled` #529
|
|
||||||
* added fixed interval option #542, #523
|
|
||||||
* improved communication, only required publishes
|
|
||||||
* improved retained flags
|
|
||||||
* added `set_power_limit` acknowledge MQTT publish #553
|
|
||||||
* added feature to reset values on midnight, communication pause or if the inverters are not available
|
|
||||||
* partially added Hoymiles alarm ID
|
|
||||||
* improved autodiscover (added total values on multi-inverter setup)
|
|
||||||
* improved `clientID` a part of the MAC address is added to have an unique name
|
|
||||||
|
|
28
src/app.cpp
28
src/app.cpp
|
@ -59,6 +59,7 @@ void app::setup() {
|
||||||
|
|
||||||
mMiPayload.setup(this, &mSys, &mNrfRadio, &mStat, mConfig->nrf.maxRetransPerPyld, &mTimestamp);
|
mMiPayload.setup(this, &mSys, &mNrfRadio, &mStat, mConfig->nrf.maxRetransPerPyld, &mTimestamp);
|
||||||
mMiPayload.enableSerialDebug(mConfig->serial.debug);
|
mMiPayload.enableSerialDebug(mConfig->serial.debug);
|
||||||
|
mMiPayload.addPayloadListener(std::bind(&app::payloadEventListener, this, std::placeholders::_1));
|
||||||
|
|
||||||
if(!mNrfRadio.isChipConnected())
|
if(!mNrfRadio.isChipConnected())
|
||||||
DPRINTLN(DBG_WARN, F("WARNING! your NRF24 module can't be reached, check the wiring"));
|
DPRINTLN(DBG_WARN, F("WARNING! your NRF24 module can't be reached, check the wiring"));
|
||||||
|
@ -84,6 +85,7 @@ void app::setup() {
|
||||||
mMqtt.setup(&mConfig->mqtt, mConfig->sys.deviceName, mVersion, &mSys, &mTimestamp);
|
mMqtt.setup(&mConfig->mqtt, mConfig->sys.deviceName, mVersion, &mSys, &mTimestamp);
|
||||||
mMqtt.setSubscriptionCb(std::bind(&app::mqttSubRxCb, this, std::placeholders::_1));
|
mMqtt.setSubscriptionCb(std::bind(&app::mqttSubRxCb, this, std::placeholders::_1));
|
||||||
mPayload.addAlarmListener(std::bind(&PubMqttType::alarmEventListener, &mMqtt, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
|
mPayload.addAlarmListener(std::bind(&PubMqttType::alarmEventListener, &mMqtt, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
|
||||||
|
mMiPayload.addAlarmListener(std::bind(&PubMqttType::alarmEventListener, &mMqtt, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
setupLed();
|
setupLed();
|
||||||
|
@ -448,30 +450,38 @@ void app::mqttSubRxCb(JsonObject obj) {
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
void app::setupLed(void) {
|
void app::setupLed(void) {
|
||||||
/** LED connection diagram
|
uint8_t led_off = (mConfig->led.led_high_active) ? LOW : HIGH;
|
||||||
* \\
|
|
||||||
* PIN ---- |<----- 3.3V
|
|
||||||
*
|
|
||||||
* */
|
|
||||||
if (mConfig->led.led0 != 0xff) {
|
if (mConfig->led.led0 != 0xff) {
|
||||||
pinMode(mConfig->led.led0, OUTPUT);
|
pinMode(mConfig->led.led0, OUTPUT);
|
||||||
digitalWrite(mConfig->led.led0, HIGH); // LED off
|
digitalWrite(mConfig->led.led0, led_off);
|
||||||
}
|
}
|
||||||
if (mConfig->led.led1 != 0xff) {
|
if (mConfig->led.led1 != 0xff) {
|
||||||
pinMode(mConfig->led.led1, OUTPUT);
|
pinMode(mConfig->led.led1, OUTPUT);
|
||||||
digitalWrite(mConfig->led.led1, HIGH); // LED off
|
digitalWrite(mConfig->led.led1, led_off);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
void app::updateLed(void) {
|
void app::updateLed(void) {
|
||||||
|
uint8_t led_off = (mConfig->led.led_high_active) ? LOW : HIGH;
|
||||||
|
uint8_t led_on = (mConfig->led.led_high_active) ? HIGH : LOW;
|
||||||
|
|
||||||
if (mConfig->led.led0 != 0xff) {
|
if (mConfig->led.led0 != 0xff) {
|
||||||
Inverter<> *iv = mSys.getInverterByPos(0);
|
Inverter<> *iv = mSys.getInverterByPos(0);
|
||||||
if (NULL != iv) {
|
if (NULL != iv) {
|
||||||
if (iv->isProducing(mTimestamp))
|
if (iv->isProducing(mTimestamp))
|
||||||
digitalWrite(mConfig->led.led0, LOW); // LED on
|
digitalWrite(mConfig->led.led0, led_on);
|
||||||
else
|
else
|
||||||
digitalWrite(mConfig->led.led0, HIGH); // LED off
|
digitalWrite(mConfig->led.led0, led_off);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mConfig->led.led1 != 0xff) {
|
||||||
|
if (getMqttIsConnected()) {
|
||||||
|
digitalWrite(mConfig->led.led1, led_on);
|
||||||
|
} else {
|
||||||
|
digitalWrite(mConfig->led.led1, led_off);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
18
src/app.h
18
src/app.h
|
@ -106,6 +106,10 @@ class app : public IApp, public ah::Scheduler {
|
||||||
return mSettings.getLastSaveSucceed();
|
return mSettings.getLastSaveSucceed();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool getShouldReboot() {
|
||||||
|
return mSaveReboot;
|
||||||
|
}
|
||||||
|
|
||||||
statistics_t *getStatistics() {
|
statistics_t *getStatistics() {
|
||||||
return &mStat;
|
return &mStat;
|
||||||
}
|
}
|
||||||
|
@ -155,8 +159,12 @@ class app : public IApp, public ah::Scheduler {
|
||||||
}
|
}
|
||||||
|
|
||||||
void ivSendHighPrio(Inverter<> *iv) {
|
void ivSendHighPrio(Inverter<> *iv) {
|
||||||
if(mIVCommunicationOn) // only send commands if communcation is enabled
|
if(mIVCommunicationOn) { // only send commands if communcation is enabled
|
||||||
|
if (iv->ivGen == IV_HM)
|
||||||
mPayload.ivSendHighPrio(iv);
|
mPayload.ivSendHighPrio(iv);
|
||||||
|
else if (iv->ivGen == IV_MI)
|
||||||
|
mMiPayload.ivSendHighPrio(iv);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool getMqttIsConnected() {
|
bool getMqttIsConnected() {
|
||||||
|
@ -171,8 +179,8 @@ class app : public IApp, public ah::Scheduler {
|
||||||
return mMqtt.getRxCnt();
|
return mMqtt.getRxCnt();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool getProtection() {
|
bool getProtection(AsyncWebServerRequest *request) {
|
||||||
return mWeb.getProtection();
|
return mWeb.isProtected(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
void getNrfRadioCounters(uint32_t *sendCnt, uint32_t *retransmits) {
|
void getNrfRadioCounters(uint32_t *sendCnt, uint32_t *retransmits) {
|
||||||
|
@ -242,8 +250,8 @@ class app : public IApp, public ah::Scheduler {
|
||||||
|
|
||||||
void mqttSubRxCb(JsonObject obj);
|
void mqttSubRxCb(JsonObject obj);
|
||||||
|
|
||||||
void setupLed(void);
|
void setupLed();
|
||||||
void updateLed(void);
|
void updateLed();
|
||||||
|
|
||||||
void tickReboot(void) {
|
void tickReboot(void) {
|
||||||
DPRINTLN(DBG_INFO, F("Rebooting..."));
|
DPRINTLN(DBG_INFO, F("Rebooting..."));
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
|
|
||||||
#include "defines.h"
|
#include "defines.h"
|
||||||
#include "hm/hmSystem.h"
|
#include "hm/hmSystem.h"
|
||||||
|
#include "ESPAsyncWebServer.h"
|
||||||
|
|
||||||
// abstract interface to App. Make members of App accessible from child class
|
// abstract interface to App. Make members of App accessible from child class
|
||||||
// like web or API without forward declaration
|
// like web or API without forward declaration
|
||||||
|
@ -19,6 +20,7 @@ class IApp {
|
||||||
virtual bool eraseSettings(bool eraseWifi) = 0;
|
virtual bool eraseSettings(bool eraseWifi) = 0;
|
||||||
virtual bool getSavePending() = 0;
|
virtual bool getSavePending() = 0;
|
||||||
virtual bool getLastSaveSucceed() = 0;
|
virtual bool getLastSaveSucceed() = 0;
|
||||||
|
virtual bool getShouldReboot() = 0;
|
||||||
virtual void setOnUpdate() = 0;
|
virtual void setOnUpdate() = 0;
|
||||||
virtual void setRebootFlag() = 0;
|
virtual void setRebootFlag() = 0;
|
||||||
virtual const char *getVersion() = 0;
|
virtual const char *getVersion() = 0;
|
||||||
|
@ -47,7 +49,7 @@ class IApp {
|
||||||
virtual uint32_t getMqttRxCnt() = 0;
|
virtual uint32_t getMqttRxCnt() = 0;
|
||||||
virtual uint32_t getMqttTxCnt() = 0;
|
virtual uint32_t getMqttTxCnt() = 0;
|
||||||
|
|
||||||
virtual bool getProtection() = 0;
|
virtual bool getProtection(AsyncWebServerRequest *request) = 0;
|
||||||
|
|
||||||
virtual void getNrfRadioCounters(uint32_t *sendCnt, uint32_t *retransmits) = 0;
|
virtual void getNrfRadioCounters(uint32_t *sendCnt, uint32_t *retransmits) = 0;
|
||||||
//virtual void getCmtRadioCounters(uint32_t *sendCnt, uint32_t *retransmits) = 0;
|
//virtual void getCmtRadioCounters(uint32_t *sendCnt, uint32_t *retransmits) = 0;
|
||||||
|
|
|
@ -6,6 +6,11 @@
|
||||||
#ifndef __SETTINGS_H__
|
#ifndef __SETTINGS_H__
|
||||||
#define __SETTINGS_H__
|
#define __SETTINGS_H__
|
||||||
|
|
||||||
|
#if defined(F) && defined(ESP32)
|
||||||
|
#undef F
|
||||||
|
#define F(sl) (sl)
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include <ArduinoJson.h>
|
#include <ArduinoJson.h>
|
||||||
#include <LittleFS.h>
|
#include <LittleFS.h>
|
||||||
|
@ -108,6 +113,7 @@ typedef struct {
|
||||||
typedef struct {
|
typedef struct {
|
||||||
uint8_t led0; // first LED pin
|
uint8_t led0; // first LED pin
|
||||||
uint8_t led1; // second LED pin
|
uint8_t led1; // second LED pin
|
||||||
|
bool led_high_active; // determines if LEDs are high or low active
|
||||||
} cfgLed_t;
|
} cfgLed_t;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
@ -253,18 +259,18 @@ class settings {
|
||||||
root.shrinkToFit();
|
root.shrinkToFit();
|
||||||
if(!err && (root.size() > 0)) {
|
if(!err && (root.size() > 0)) {
|
||||||
mCfg.valid = true;
|
mCfg.valid = true;
|
||||||
jsonWifi(root[F("wifi")]);
|
if(root.containsKey(F("wifi"))) jsonWifi(root[F("wifi")]);
|
||||||
jsonNrf(root[F("nrf")]);
|
if(root.containsKey(F("nrf"))) jsonNrf(root[F("nrf")]);
|
||||||
#if defined(ESP32)
|
#if defined(ESP32)
|
||||||
jsonCmt(root[F("cmt")]);
|
if(root.containsKey(F("cmt"))) jsonCmt(root[F("cmt")]);
|
||||||
#endif
|
#endif
|
||||||
jsonNtp(root[F("ntp")]);
|
if(root.containsKey(F("ntp"))) jsonNtp(root[F("ntp")]);
|
||||||
jsonSun(root[F("sun")]);
|
if(root.containsKey(F("sun"))) jsonSun(root[F("sun")]);
|
||||||
jsonSerial(root[F("serial")]);
|
if(root.containsKey(F("serial"))) jsonSerial(root[F("serial")]);
|
||||||
jsonMqtt(root[F("mqtt")]);
|
if(root.containsKey(F("mqtt"))) jsonMqtt(root[F("mqtt")]);
|
||||||
jsonLed(root[F("led")]);
|
if(root.containsKey(F("led"))) jsonLed(root[F("led")]);
|
||||||
jsonPlugin(root[F("plugin")]);
|
if(root.containsKey(F("plugin"))) jsonPlugin(root[F("plugin")]);
|
||||||
jsonInst(root[F("inst")]);
|
if(root.containsKey(F("inst"))) jsonInst(root[F("inst")]);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
Serial.println(F("failed to parse json, using default config"));
|
Serial.println(F("failed to parse json, using default config"));
|
||||||
|
@ -399,6 +405,7 @@ class settings {
|
||||||
|
|
||||||
mCfg.led.led0 = DEF_PIN_OFF;
|
mCfg.led.led0 = DEF_PIN_OFF;
|
||||||
mCfg.led.led1 = DEF_PIN_OFF;
|
mCfg.led.led1 = DEF_PIN_OFF;
|
||||||
|
mCfg.led.led_high_active = false;
|
||||||
|
|
||||||
memset(&mCfg.inst, 0, sizeof(cfgInst_t));
|
memset(&mCfg.inst, 0, sizeof(cfgInst_t));
|
||||||
|
|
||||||
|
@ -429,17 +436,17 @@ class settings {
|
||||||
ah::ip2Char(mCfg.sys.ip.dns2, buf); obj[F("dns2")] = String(buf);
|
ah::ip2Char(mCfg.sys.ip.dns2, buf); obj[F("dns2")] = String(buf);
|
||||||
ah::ip2Char(mCfg.sys.ip.gateway, buf); obj[F("gtwy")] = String(buf);
|
ah::ip2Char(mCfg.sys.ip.gateway, buf); obj[F("gtwy")] = String(buf);
|
||||||
} else {
|
} else {
|
||||||
snprintf(mCfg.sys.stationSsid, SSID_LEN, "%s", obj[F("ssid")].as<const char*>());
|
getChar(obj, F("ssid"), mCfg.sys.stationSsid, SSID_LEN);
|
||||||
snprintf(mCfg.sys.stationPwd, PWD_LEN, "%s", obj[F("pwd")].as<const char*>());
|
getChar(obj, F("pwd"), mCfg.sys.stationPwd, PWD_LEN);
|
||||||
snprintf(mCfg.sys.deviceName, DEVNAME_LEN, "%s", obj[F("dev")].as<const char*>());
|
getChar(obj, F("dev"), mCfg.sys.deviceName, DEVNAME_LEN);
|
||||||
snprintf(mCfg.sys.adminPwd, PWD_LEN, "%s", obj[F("adm")].as<const char*>());
|
getChar(obj, F("adm"), mCfg.sys.adminPwd, PWD_LEN);
|
||||||
mCfg.sys.protectionMask = obj[F("prot_mask")];
|
getVal<uint16_t>(obj, F("prot_mask"), &mCfg.sys.protectionMask);
|
||||||
mCfg.sys.darkMode = obj[F("dark")];
|
getVal<bool>(obj, F("dark"), &mCfg.sys.darkMode);
|
||||||
ah::ip2Arr(mCfg.sys.ip.ip, obj[F("ip")].as<const char*>());
|
if(obj.containsKey(F("ip"))) ah::ip2Arr(mCfg.sys.ip.ip, obj[F("ip")].as<const char*>());
|
||||||
ah::ip2Arr(mCfg.sys.ip.mask, obj[F("mask")].as<const char*>());
|
if(obj.containsKey(F("mask"))) ah::ip2Arr(mCfg.sys.ip.mask, obj[F("mask")].as<const char*>());
|
||||||
ah::ip2Arr(mCfg.sys.ip.dns1, obj[F("dns1")].as<const char*>());
|
if(obj.containsKey(F("dns1"))) ah::ip2Arr(mCfg.sys.ip.dns1, obj[F("dns1")].as<const char*>());
|
||||||
ah::ip2Arr(mCfg.sys.ip.dns2, obj[F("dns2")].as<const char*>());
|
if(obj.containsKey(F("dns2"))) ah::ip2Arr(mCfg.sys.ip.dns2, obj[F("dns2")].as<const char*>());
|
||||||
ah::ip2Arr(mCfg.sys.ip.gateway, obj[F("gtwy")].as<const char*>());
|
if(obj.containsKey(F("gtwy"))) ah::ip2Arr(mCfg.sys.ip.gateway, obj[F("gtwy")].as<const char*>());
|
||||||
|
|
||||||
if(mCfg.sys.protectionMask == 0)
|
if(mCfg.sys.protectionMask == 0)
|
||||||
mCfg.sys.protectionMask = DEF_PROT_INDEX | DEF_PROT_LIVE | DEF_PROT_SERIAL | DEF_PROT_SETUP
|
mCfg.sys.protectionMask = DEF_PROT_INDEX | DEF_PROT_LIVE | DEF_PROT_SERIAL | DEF_PROT_SETUP
|
||||||
|
@ -460,15 +467,15 @@ class settings {
|
||||||
obj[F("pwr")] = mCfg.nrf.amplifierPower;
|
obj[F("pwr")] = mCfg.nrf.amplifierPower;
|
||||||
obj[F("en")] = (bool) mCfg.nrf.enabled;
|
obj[F("en")] = (bool) mCfg.nrf.enabled;
|
||||||
} else {
|
} else {
|
||||||
mCfg.nrf.sendInterval = obj[F("intvl")];
|
getVal<uint16_t>(obj, F("intvl"), &mCfg.nrf.sendInterval);
|
||||||
mCfg.nrf.maxRetransPerPyld = obj[F("maxRetry")];
|
getVal<uint8_t>(obj, F("maxRetry"), &mCfg.nrf.maxRetransPerPyld);
|
||||||
mCfg.nrf.pinCs = obj[F("cs")];
|
getVal<uint8_t>(obj, F("cs"), &mCfg.nrf.pinCs);
|
||||||
mCfg.nrf.pinCe = obj[F("ce")];
|
getVal<uint8_t>(obj, F("ce"), &mCfg.nrf.pinCe);
|
||||||
mCfg.nrf.pinIrq = obj[F("irq")];
|
getVal<uint8_t>(obj, F("irq"), &mCfg.nrf.pinIrq);
|
||||||
mCfg.nrf.pinSclk = obj[F("sclk")];
|
getVal<uint8_t>(obj, F("sclk"), &mCfg.nrf.pinSclk);
|
||||||
mCfg.nrf.pinMosi = obj[F("mosi")];
|
getVal<uint8_t>(obj, F("mosi"), &mCfg.nrf.pinMosi);
|
||||||
mCfg.nrf.pinMiso = obj[F("miso")];
|
getVal<uint8_t>(obj, F("miso"), &mCfg.nrf.pinMiso);
|
||||||
mCfg.nrf.amplifierPower = obj[F("pwr")];
|
getVal<uint8_t>(obj, F("pwr"), &mCfg.nrf.amplifierPower);
|
||||||
mCfg.nrf.enabled = (bool) obj[F("en")];
|
mCfg.nrf.enabled = (bool) obj[F("en")];
|
||||||
if((obj[F("cs")] == obj[F("ce")])) {
|
if((obj[F("cs")] == obj[F("ce")])) {
|
||||||
mCfg.nrf.pinCs = DEF_CS_PIN;
|
mCfg.nrf.pinCs = DEF_CS_PIN;
|
||||||
|
@ -500,8 +507,8 @@ class settings {
|
||||||
obj[F("addr")] = mCfg.ntp.addr;
|
obj[F("addr")] = mCfg.ntp.addr;
|
||||||
obj[F("port")] = mCfg.ntp.port;
|
obj[F("port")] = mCfg.ntp.port;
|
||||||
} else {
|
} else {
|
||||||
snprintf(mCfg.ntp.addr, NTP_ADDR_LEN, "%s", obj[F("addr")].as<const char*>());
|
getChar(obj, F("addr"), mCfg.ntp.addr, NTP_ADDR_LEN);
|
||||||
mCfg.ntp.port = obj[F("port")];
|
getVal<uint16_t>(obj, F("port"), &mCfg.ntp.port);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -512,10 +519,10 @@ class settings {
|
||||||
obj[F("dis")] = mCfg.sun.disNightCom;
|
obj[F("dis")] = mCfg.sun.disNightCom;
|
||||||
obj[F("offs")] = mCfg.sun.offsetSec;
|
obj[F("offs")] = mCfg.sun.offsetSec;
|
||||||
} else {
|
} else {
|
||||||
mCfg.sun.lat = obj[F("lat")];
|
getVal<float>(obj, F("lat"), &mCfg.sun.lat);
|
||||||
mCfg.sun.lon = obj[F("lon")];
|
getVal<float>(obj, F("lon"), &mCfg.sun.lon);
|
||||||
mCfg.sun.disNightCom = obj[F("dis")];
|
getVal<bool>(obj, F("dis"), &mCfg.sun.disNightCom);
|
||||||
mCfg.sun.offsetSec = obj[F("offs")];
|
getVal<uint16_t>(obj, F("offs"), &mCfg.sun.offsetSec);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -525,9 +532,9 @@ class settings {
|
||||||
obj[F("show")] = mCfg.serial.showIv;
|
obj[F("show")] = mCfg.serial.showIv;
|
||||||
obj[F("debug")] = mCfg.serial.debug;
|
obj[F("debug")] = mCfg.serial.debug;
|
||||||
} else {
|
} else {
|
||||||
mCfg.serial.interval = obj[F("intvl")];
|
getVal<uint16_t>(obj, F("intvl"), &mCfg.serial.interval);
|
||||||
mCfg.serial.showIv = obj[F("show")];
|
getVal<bool>(obj, F("show"), &mCfg.serial.showIv);
|
||||||
mCfg.serial.debug = obj[F("debug")];
|
getVal<bool>(obj, F("debug"), &mCfg.serial.debug);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -541,12 +548,12 @@ class settings {
|
||||||
obj[F("intvl")] = mCfg.mqtt.interval;
|
obj[F("intvl")] = mCfg.mqtt.interval;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
mCfg.mqtt.port = obj[F("port")];
|
getVal<uint16_t>(obj, F("port"), &mCfg.mqtt.port);
|
||||||
mCfg.mqtt.interval = obj[F("intvl")];
|
getVal<uint16_t>(obj, F("intvl"), &mCfg.mqtt.interval);
|
||||||
snprintf(mCfg.mqtt.broker, MQTT_ADDR_LEN, "%s", obj[F("broker")].as<const char*>());
|
getChar(obj, F("broker"), mCfg.mqtt.broker, MQTT_ADDR_LEN);
|
||||||
snprintf(mCfg.mqtt.user, MQTT_USER_LEN, "%s", obj[F("user")].as<const char*>());
|
getChar(obj, F("user"), mCfg.mqtt.user, MQTT_USER_LEN);
|
||||||
snprintf(mCfg.mqtt.pwd, MQTT_PWD_LEN, "%s", obj[F("pwd")].as<const char*>());
|
getChar(obj, F("pwd"), mCfg.mqtt.pwd, MQTT_PWD_LEN);
|
||||||
snprintf(mCfg.mqtt.topic, MQTT_TOPIC_LEN, "%s", obj[F("topic")].as<const char*>());
|
getChar(obj, F("topic"), mCfg.mqtt.topic, MQTT_TOPIC_LEN);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -554,9 +561,11 @@ class settings {
|
||||||
if(set) {
|
if(set) {
|
||||||
obj[F("0")] = mCfg.led.led0;
|
obj[F("0")] = mCfg.led.led0;
|
||||||
obj[F("1")] = mCfg.led.led1;
|
obj[F("1")] = mCfg.led.led1;
|
||||||
|
obj[F("act_high")] = mCfg.led.led_high_active;
|
||||||
} else {
|
} else {
|
||||||
mCfg.led.led0 = obj[F("0")];
|
getVal<uint8_t>(obj, F("0"), &mCfg.led.led0);
|
||||||
mCfg.led.led1 = obj[F("1")];
|
getVal<uint8_t>(obj, F("1"), &mCfg.led.led1);
|
||||||
|
getVal<bool>(obj, F("act_high"), &mCfg.led.led_high_active);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -578,19 +587,19 @@ class settings {
|
||||||
disp[F("dc")] = mCfg.plugin.display.disp_dc;
|
disp[F("dc")] = mCfg.plugin.display.disp_dc;
|
||||||
} else {
|
} else {
|
||||||
JsonObject disp = obj["disp"];
|
JsonObject disp = obj["disp"];
|
||||||
mCfg.plugin.display.type = disp[F("type")];
|
getVal<uint8_t>(disp, F("type"), &mCfg.plugin.display.type);
|
||||||
mCfg.plugin.display.pwrSaveAtIvOffline = (bool)disp[F("pwrSafe")];
|
getVal<bool>(disp, F("pwrSafe"), &mCfg.plugin.display.pwrSaveAtIvOffline);
|
||||||
mCfg.plugin.display.pxShift = (bool)disp[F("pxShift")];
|
getVal<bool>(disp, F("pxShift"), &mCfg.plugin.display.pxShift);
|
||||||
mCfg.plugin.display.rot = disp[F("rotation")];
|
getVal<uint8_t>(disp, F("rotation"), &mCfg.plugin.display.rot);
|
||||||
//mCfg.plugin.display.wakeUp = disp[F("wake")];
|
//mCfg.plugin.display.wakeUp = disp[F("wake")];
|
||||||
//mCfg.plugin.display.sleepAt = disp[F("sleep")];
|
//mCfg.plugin.display.sleepAt = disp[F("sleep")];
|
||||||
mCfg.plugin.display.contrast = disp[F("contrast")];
|
getVal<uint8_t>(disp, F("contrast"), &mCfg.plugin.display.contrast);
|
||||||
mCfg.plugin.display.disp_data = disp[F("data")];
|
getVal<uint8_t>(disp, F("data"), &mCfg.plugin.display.disp_data);
|
||||||
mCfg.plugin.display.disp_clk = disp[F("clock")];
|
getVal<uint8_t>(disp, F("clock"), &mCfg.plugin.display.disp_clk);
|
||||||
mCfg.plugin.display.disp_cs = disp[F("cs")];
|
getVal<uint8_t>(disp, F("cs"), &mCfg.plugin.display.disp_cs);
|
||||||
mCfg.plugin.display.disp_reset = disp[F("reset")];
|
getVal<uint8_t>(disp, F("reset"), &mCfg.plugin.display.disp_reset);
|
||||||
mCfg.plugin.display.disp_busy = disp[F("busy")];
|
getVal<uint8_t>(disp, F("busy"), &mCfg.plugin.display.disp_busy);
|
||||||
mCfg.plugin.display.disp_dc = disp[F("dc")];
|
getVal<uint8_t>(disp, F("dc"), &mCfg.plugin.display.disp_dc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -602,10 +611,10 @@ class settings {
|
||||||
obj[F("rstComStop")] = (bool)mCfg.inst.rstValsCommStop;
|
obj[F("rstComStop")] = (bool)mCfg.inst.rstValsCommStop;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
mCfg.inst.enabled = (bool)obj[F("en")];
|
getVal<bool>(obj, F("en"), &mCfg.inst.enabled);
|
||||||
mCfg.inst.rstYieldMidNight = (bool)obj["rstMidNight"];
|
getVal<bool>(obj, F("rstMidNight"), &mCfg.inst.rstYieldMidNight);
|
||||||
mCfg.inst.rstValsNotAvail = (bool)obj["rstNotAvail"];
|
getVal<bool>(obj, F("rstNotAvail"), &mCfg.inst.rstValsNotAvail);
|
||||||
mCfg.inst.rstValsCommStop = (bool)obj["rstComStop"];
|
getVal<bool>(obj, F("rstComStop"), &mCfg.inst.rstValsCommStop);
|
||||||
}
|
}
|
||||||
|
|
||||||
JsonArray ivArr;
|
JsonArray ivArr;
|
||||||
|
@ -615,13 +624,10 @@ class settings {
|
||||||
if(set) {
|
if(set) {
|
||||||
if(mCfg.inst.iv[i].serial.u64 != 0ULL)
|
if(mCfg.inst.iv[i].serial.u64 != 0ULL)
|
||||||
jsonIv(ivArr.createNestedObject(), &mCfg.inst.iv[i], true);
|
jsonIv(ivArr.createNestedObject(), &mCfg.inst.iv[i], true);
|
||||||
}
|
} else if(!obj[F("iv")][i].isNull())
|
||||||
else {
|
|
||||||
if(!obj[F("iv")][i].isNull())
|
|
||||||
jsonIv(obj[F("iv")][i], &mCfg.inst.iv[i]);
|
jsonIv(obj[F("iv")][i], &mCfg.inst.iv[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
void jsonIv(JsonObject obj, cfgIv_t *cfg, bool set = false) {
|
void jsonIv(JsonObject obj, cfgIv_t *cfg, bool set = false) {
|
||||||
if(set) {
|
if(set) {
|
||||||
|
@ -634,17 +640,41 @@ class settings {
|
||||||
obj[F("chName")][i] = cfg->chName[i];
|
obj[F("chName")][i] = cfg->chName[i];
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
cfg->enabled = (bool)obj[F("en")];
|
getVal<bool>(obj, F("en"), &cfg->enabled);
|
||||||
snprintf(cfg->name, MAX_NAME_LENGTH, "%s", obj[F("name")].as<const char*>());
|
getChar(obj, F("name"), cfg->name, MAX_NAME_LENGTH);
|
||||||
cfg->serial.u64 = obj[F("sn")];
|
getVal<uint64_t>(obj, F("sn"), &cfg->serial.u64);
|
||||||
for(uint8_t i = 0; i < 4; i++) {
|
for(uint8_t i = 0; i < 4; i++) {
|
||||||
cfg->yieldCor[i] = obj[F("yield")][i];
|
if(obj.containsKey(F("yield"))) cfg->yieldCor[i] = obj[F("yield")][i];
|
||||||
cfg->chMaxPwr[i] = obj[F("pwr")][i];
|
if(obj.containsKey(F("pwr"))) cfg->chMaxPwr[i] = obj[F("pwr")][i];
|
||||||
snprintf(cfg->chName[i], MAX_NAME_LENGTH, "%s", obj[F("chName")][i].as<const char*>());
|
if(obj.containsKey(F("chName"))) snprintf(cfg->chName[i], MAX_NAME_LENGTH, "%s", obj[F("chName")][i].as<const char*>());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if defined(ESP32)
|
||||||
|
void getChar(JsonObject obj, const char *key, char *dst, int maxLen) {
|
||||||
|
if(obj.containsKey(key))
|
||||||
|
snprintf(dst, maxLen, "%s", obj[key].as<const char*>());
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T=uint8_t>
|
||||||
|
void getVal(JsonObject obj, const char *key, T *dst) {
|
||||||
|
if(obj.containsKey(key))
|
||||||
|
*dst = obj[key];
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
void getChar(JsonObject obj, const __FlashStringHelper *key, char *dst, int maxLen) {
|
||||||
|
if(obj.containsKey(key))
|
||||||
|
snprintf(dst, maxLen, "%s", obj[key].as<const char*>());
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T=uint8_t>
|
||||||
|
void getVal(JsonObject obj, const __FlashStringHelper *key, T *dst) {
|
||||||
|
if(obj.containsKey(key))
|
||||||
|
*dst = obj[key];
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
settings_t mCfg;
|
settings_t mCfg;
|
||||||
bool mLastSaveSucceed;
|
bool mLastSaveSucceed;
|
||||||
};
|
};
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
//-------------------------------------
|
//-------------------------------------
|
||||||
#define VERSION_MAJOR 0
|
#define VERSION_MAJOR 0
|
||||||
#define VERSION_MINOR 6
|
#define VERSION_MINOR 6
|
||||||
#define VERSION_PATCH 0
|
#define VERSION_PATCH 9
|
||||||
|
|
||||||
//-------------------------------------
|
//-------------------------------------
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
|
|
@ -76,8 +76,8 @@ class HmPayload {
|
||||||
DPRINTLN(DBG_DEBUG, F("zeroYieldDay"));
|
DPRINTLN(DBG_DEBUG, F("zeroYieldDay"));
|
||||||
record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug);
|
record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug);
|
||||||
uint8_t pos;
|
uint8_t pos;
|
||||||
for(uint8_t ch = 0; ch < iv->channels; ch++) {
|
for(uint8_t ch = 0; ch <= iv->channels; ch++) {
|
||||||
pos = iv->getPosByChFld(CH0, FLD_YD, rec);
|
pos = iv->getPosByChFld(ch, FLD_YD, rec);
|
||||||
iv->setValue(pos, rec, 0.0f);
|
iv->setValue(pos, rec, 0.0f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -112,20 +112,22 @@ class HmPayload {
|
||||||
process(false); // no retransmit
|
process(false); // no retransmit
|
||||||
|
|
||||||
if (!mPayload[iv->id].complete) {
|
if (!mPayload[iv->id].complete) {
|
||||||
if (MAX_PAYLOAD_ENTRIES == mPayload[iv->id].maxPackId)
|
if (mSerialDebug)
|
||||||
mStat->rxFailNoAnser++; // got nothing
|
|
||||||
else
|
|
||||||
mStat->rxFail++; // got fragments but not complete response
|
|
||||||
|
|
||||||
iv->setQueuedCmdFinished(); // command failed
|
|
||||||
if (mSerialDebug) {
|
|
||||||
DPRINTLN(DBG_INFO, F("enqueued cmd failed/timeout"));
|
|
||||||
DPRINT_IVID(DBG_INFO, iv->id);
|
DPRINT_IVID(DBG_INFO, iv->id);
|
||||||
DBGPRINT(F("no Payload received! (retransmits: "));
|
if (MAX_PAYLOAD_ENTRIES == mPayload[iv->id].maxPackId) {
|
||||||
|
mStat->rxFailNoAnser++; // got nothing
|
||||||
|
if (mSerialDebug)
|
||||||
|
DBGPRINTLN(F("enqueued cmd failed/timeout"));
|
||||||
|
} else {
|
||||||
|
mStat->rxFail++; // got fragments but not complete response
|
||||||
|
if (mSerialDebug) {
|
||||||
|
DBGPRINT(F("no complete Payload received! (retransmits: "));
|
||||||
DBGPRINT(String(mPayload[iv->id].retransmits));
|
DBGPRINT(String(mPayload[iv->id].retransmits));
|
||||||
DBGPRINTLN(F(")"));
|
DBGPRINTLN(F(")"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
iv->setQueuedCmdFinished(); // command failed
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -209,6 +211,8 @@ class HmPayload {
|
||||||
|
|
||||||
iv->clearCmdQueue();
|
iv->clearCmdQueue();
|
||||||
iv->enqueCommand<InfoCommand>(SystemConfigPara); // read back power limit
|
iv->enqueCommand<InfoCommand>(SystemConfigPara); // read back power limit
|
||||||
|
if(mHighPrioIv == NULL) // do it immediately if possible
|
||||||
|
mHighPrioIv = iv;
|
||||||
}
|
}
|
||||||
iv->devControlCmd = Init;
|
iv->devControlCmd = Init;
|
||||||
}
|
}
|
||||||
|
@ -392,8 +396,8 @@ class HmPayload {
|
||||||
}
|
}
|
||||||
|
|
||||||
void reset(uint8_t id) {
|
void reset(uint8_t id) {
|
||||||
DPRINT(DBG_INFO, "resetPayload: id: ");
|
DPRINT_IVID(DBG_INFO, id);
|
||||||
DBGPRINTLN(String(id));
|
DBGPRINTLN(F("resetPayload"));
|
||||||
memset(mPayload[id].len, 0, MAX_PAYLOAD_ENTRIES);
|
memset(mPayload[id].len, 0, MAX_PAYLOAD_ENTRIES);
|
||||||
mPayload[id].txCmd = 0;
|
mPayload[id].txCmd = 0;
|
||||||
mPayload[id].gotFragment = false;
|
mPayload[id].gotFragment = false;
|
||||||
|
|
|
@ -31,12 +31,15 @@ template <uint8_t IRQ_PIN = DEF_IRQ_PIN, uint8_t CE_PIN = DEF_CE_PIN, uint8_t CS
|
||||||
class HmRadio {
|
class HmRadio {
|
||||||
public:
|
public:
|
||||||
HmRadio() : mNrf24(CE_PIN, CS_PIN, SPI_SPEED) {
|
HmRadio() : mNrf24(CE_PIN, CS_PIN, SPI_SPEED) {
|
||||||
|
if(mSerialDebug) {
|
||||||
DPRINT(DBG_VERBOSE, F("hmRadio.h : HmRadio():mNrf24(CE_PIN: "));
|
DPRINT(DBG_VERBOSE, F("hmRadio.h : HmRadio():mNrf24(CE_PIN: "));
|
||||||
DPRINT(DBG_VERBOSE, String(CE_PIN));
|
DBGPRINT(String(CE_PIN));
|
||||||
DPRINT(DBG_VERBOSE, F(", CS_PIN: "));
|
DBGPRINT(F(", CS_PIN: "));
|
||||||
DPRINT(DBG_VERBOSE, String(CS_PIN));
|
DBGPRINT(String(CS_PIN));
|
||||||
DPRINT(DBG_VERBOSE, F(", SPI_SPEED: "));
|
DBGPRINT(F(", SPI_SPEED: "));
|
||||||
DPRINTLN(DBG_VERBOSE, String(SPI_SPEED) + ")");
|
DBGPRINT(String(SPI_SPEED));
|
||||||
|
DBGPRINTLN(F(")"));
|
||||||
|
}
|
||||||
|
|
||||||
// Depending on the program, the module can work on 2403, 2423, 2440, 2461 or 2475MHz.
|
// Depending on the program, the module can work on 2403, 2423, 2440, 2461 or 2475MHz.
|
||||||
// Channel List 2403, 2423, 2440, 2461, 2475MHz
|
// Channel List 2403, 2423, 2440, 2461, 2475MHz
|
||||||
|
@ -102,7 +105,7 @@ class HmRadio {
|
||||||
mNrf24.enableDynamicPayloads();
|
mNrf24.enableDynamicPayloads();
|
||||||
mNrf24.setCRCLength(RF24_CRC_16);
|
mNrf24.setCRCLength(RF24_CRC_16);
|
||||||
mNrf24.setAddressWidth(5);
|
mNrf24.setAddressWidth(5);
|
||||||
mNrf24.openReadingPipe(1, DTU_RADIO_ID);
|
mNrf24.openReadingPipe(1, reinterpret_cast<uint8_t*>(&DTU_RADIO_ID));
|
||||||
|
|
||||||
// enable all receiving interrupts
|
// enable all receiving interrupts
|
||||||
mNrf24.maskIRQ(false, false, false);
|
mNrf24.maskIRQ(false, false, false);
|
||||||
|
@ -126,32 +129,31 @@ class HmRadio {
|
||||||
bool tx_ok, tx_fail, rx_ready;
|
bool tx_ok, tx_fail, rx_ready;
|
||||||
mNrf24.whatHappened(tx_ok, tx_fail, rx_ready); // resets the IRQ pin to HIGH
|
mNrf24.whatHappened(tx_ok, tx_fail, rx_ready); // resets the IRQ pin to HIGH
|
||||||
mNrf24.flush_tx(); // empty TX FIFO
|
mNrf24.flush_tx(); // empty TX FIFO
|
||||||
//DBGPRINTLN("TX whatHappened Ch" + String(mRfChLst[mTxChIdx]) + " " + String(tx_ok) + String(tx_fail) + String(rx_ready));
|
|
||||||
|
|
||||||
// start listening on the default RX channel
|
// start listening
|
||||||
mRxChIdx = 0;
|
|
||||||
mNrf24.setChannel(mRfChLst[mRxChIdx]);
|
mNrf24.setChannel(mRfChLst[mRxChIdx]);
|
||||||
mNrf24.startListening();
|
mNrf24.startListening();
|
||||||
|
|
||||||
//uint32_t debug_ms = millis();
|
uint32_t startMicros = micros();
|
||||||
uint16_t cnt = 300; // that is 60 times 5 channels
|
uint32_t loopMillis = millis();
|
||||||
while (0 < cnt--) {
|
while (millis()-loopMillis < 400) {
|
||||||
uint32_t startMillis = millis();
|
while (micros()-startMicros < 5110) { // listen (4088us or?) 5110us to each channel
|
||||||
while (millis()-startMillis < 4) { // listen 4ms to each channel
|
|
||||||
if (mIrqRcvd) {
|
if (mIrqRcvd) {
|
||||||
mIrqRcvd = false;
|
mIrqRcvd = false;
|
||||||
if (getReceived()) { // everything received
|
if (getReceived()) { // everything received
|
||||||
//DBGPRINTLN("RX finished Cnt: " + String(300-cnt) + " time used: " + String(millis()-debug_ms)+ " ms");
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
yield();
|
yield();
|
||||||
}
|
}
|
||||||
switchRxCh(); // switch to next RX channel
|
// switch to next RX channel
|
||||||
|
startMicros = micros();
|
||||||
|
if(++mRxChIdx >= RF_CHANNELS)
|
||||||
|
mRxChIdx = 0;
|
||||||
|
mNrf24.setChannel(mRfChLst[mRxChIdx]);
|
||||||
yield();
|
yield();
|
||||||
}
|
}
|
||||||
// not finished but time is over
|
// not finished but time is over
|
||||||
//DBGPRINTLN("RX not finished: 300 time used: " + String(millis()-debug_ms)+ " ms");
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -184,6 +186,7 @@ class HmRadio {
|
||||||
} else { //MI 2nd gen. specific
|
} else { //MI 2nd gen. specific
|
||||||
switch (cmd) {
|
switch (cmd) {
|
||||||
case TurnOn:
|
case TurnOn:
|
||||||
|
//mTxBuf[0] = 0x50;
|
||||||
mTxBuf[9] = 0x55;
|
mTxBuf[9] = 0x55;
|
||||||
mTxBuf[10] = 0xaa;
|
mTxBuf[10] = 0xaa;
|
||||||
break;
|
break;
|
||||||
|
@ -202,11 +205,14 @@ class HmRadio {
|
||||||
}
|
}
|
||||||
cnt++;
|
cnt++;
|
||||||
}
|
}
|
||||||
sendPacket(invId, cnt, isRetransmit);
|
sendPacket(invId, cnt, isRetransmit, isNoMI);
|
||||||
}
|
}
|
||||||
|
|
||||||
void prepareDevInformCmd(uint64_t invId, uint8_t cmd, uint32_t ts, uint16_t alarmMesId, bool isRetransmit, uint8_t reqfld=TX_REQ_INFO) { // might not be necessary to add additional arg.
|
void prepareDevInformCmd(uint64_t invId, uint8_t cmd, uint32_t ts, uint16_t alarmMesId, bool isRetransmit, uint8_t reqfld=TX_REQ_INFO) { // might not be necessary to add additional arg.
|
||||||
DPRINTLN(DBG_DEBUG, F("prepareDevInformCmd 0x") + String(cmd, HEX));
|
if(mSerialDebug) {
|
||||||
|
DPRINT(DBG_DEBUG, F("prepareDevInformCmd 0x"));
|
||||||
|
DPRINTLN(DBG_DEBUG,String(cmd, HEX));
|
||||||
|
}
|
||||||
initPacket(invId, reqfld, ALL_FRAMES);
|
initPacket(invId, reqfld, ALL_FRAMES);
|
||||||
mTxBuf[10] = cmd; // cid
|
mTxBuf[10] = cmd; // cid
|
||||||
mTxBuf[11] = 0x00;
|
mTxBuf[11] = 0x00;
|
||||||
|
@ -218,9 +224,9 @@ class HmRadio {
|
||||||
sendPacket(invId, 24, isRetransmit);
|
sendPacket(invId, 24, isRetransmit);
|
||||||
}
|
}
|
||||||
|
|
||||||
void sendCmdPacket(uint64_t invId, uint8_t mid, uint8_t pid, bool isRetransmit) {
|
void sendCmdPacket(uint64_t invId, uint8_t mid, uint8_t pid, bool isRetransmit, bool appendCrc16=true) {
|
||||||
initPacket(invId, mid, pid);
|
initPacket(invId, mid, pid);
|
||||||
sendPacket(invId, 10, isRetransmit);
|
sendPacket(invId, 10, isRetransmit, appendCrc16);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t getDataRate(void) {
|
uint8_t getDataRate(void) {
|
||||||
|
@ -244,7 +250,6 @@ class HmRadio {
|
||||||
bool getReceived(void) {
|
bool getReceived(void) {
|
||||||
bool tx_ok, tx_fail, rx_ready;
|
bool tx_ok, tx_fail, rx_ready;
|
||||||
mNrf24.whatHappened(tx_ok, tx_fail, rx_ready); // resets the IRQ pin to HIGH
|
mNrf24.whatHappened(tx_ok, tx_fail, rx_ready); // resets the IRQ pin to HIGH
|
||||||
//DBGPRINTLN("RX whatHappened Ch" + String(mRfChLst[mRxChIdx]) + " " + String(tx_ok) + String(tx_fail) + String(rx_ready));
|
|
||||||
|
|
||||||
bool isLastPackage = false;
|
bool isLastPackage = false;
|
||||||
while(mNrf24.available()) {
|
while(mNrf24.available()) {
|
||||||
|
@ -255,31 +260,28 @@ class HmRadio {
|
||||||
p.ch = mRfChLst[mRxChIdx];
|
p.ch = mRfChLst[mRxChIdx];
|
||||||
p.len = len;
|
p.len = len;
|
||||||
mNrf24.read(p.packet, len);
|
mNrf24.read(p.packet, len);
|
||||||
|
if (p.packet[0] != 0x00) {
|
||||||
mBufCtrl.push(p);
|
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] > 0x81); // > 0x81 indicates last packet received
|
isLastPackage = (p.packet[9] > ALL_FRAMES); // > ALL_FRAMES indicates last packet received
|
||||||
else if (p.packet[0] == ( 0x0f + ALL_FRAMES) ) // response from MI get information command
|
else if (p.packet[0] == ( 0x0f + ALL_FRAMES) ) // response from MI get information command
|
||||||
isLastPackage = (p.packet[9] > 0x11); // > 0x11 indicates last packet received
|
isLastPackage = (p.packet[9] > 0x10); // > 0x10 indicates last packet received
|
||||||
else if (p.packet[0] != 0x00 && p.packet[0] != 0x88 && p.packet[0] != 0x92)
|
else if ((p.packet[0] != 0x88) && (p.packet[0] != 0x92)) // ignore fragment number zero and MI status messages //#0 was p.packet[0] != 0x00 &&
|
||||||
// ignore fragment number zero and MI status messages
|
|
||||||
isLastPackage = true; // response from dev control command
|
isLastPackage = true; // response from dev control command
|
||||||
yield();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
yield();
|
||||||
|
}
|
||||||
return isLastPackage;
|
return isLastPackage;
|
||||||
}
|
}
|
||||||
|
|
||||||
void switchRxCh() {
|
|
||||||
mNrf24.stopListening();
|
|
||||||
// get next channel index
|
|
||||||
if(++mRxChIdx >= RF_CHANNELS)
|
|
||||||
mRxChIdx = 0;
|
|
||||||
mNrf24.setChannel(mRfChLst[mRxChIdx]);
|
|
||||||
mNrf24.startListening();
|
|
||||||
}
|
|
||||||
|
|
||||||
void initPacket(uint64_t invId, uint8_t mid, uint8_t pid) {
|
void initPacket(uint64_t invId, uint8_t mid, uint8_t pid) {
|
||||||
DPRINTLN(DBG_VERBOSE, F("initPacket, mid: ") + String(mid, HEX) + F(" pid: ") + String(pid, HEX));
|
if(mSerialDebug) {
|
||||||
|
DPRINT(DBG_VERBOSE, F("initPacket, mid: "));
|
||||||
|
DHEX(mid);
|
||||||
|
DBGPRINT(F(" pid: "));
|
||||||
|
DBGHEXLN(pid);
|
||||||
|
}
|
||||||
memset(mTxBuf, 0, MAX_RF_PAYLOAD_SIZE);
|
memset(mTxBuf, 0, MAX_RF_PAYLOAD_SIZE);
|
||||||
mTxBuf[0] = mid; // message id
|
mTxBuf[0] = mid; // message id
|
||||||
CP_U32_BigEndian(&mTxBuf[1], (invId >> 8));
|
CP_U32_BigEndian(&mTxBuf[1], (invId >> 8));
|
||||||
|
@ -287,12 +289,12 @@ class HmRadio {
|
||||||
mTxBuf[9] = pid;
|
mTxBuf[9] = pid;
|
||||||
}
|
}
|
||||||
|
|
||||||
void sendPacket(uint64_t invId, uint8_t len, bool isRetransmit) {
|
void sendPacket(uint64_t invId, uint8_t len, bool isRetransmit, bool appendCrc16=true) {
|
||||||
//DPRINTLN(DBG_VERBOSE, F("hmRadio.h:sendPacket"));
|
//DPRINTLN(DBG_VERBOSE, F("hmRadio.h:sendPacket"));
|
||||||
//DPRINTLN(DBG_VERBOSE, "sent packet: #" + String(mSendCnt));
|
//DPRINTLN(DBG_VERBOSE, "sent packet: #" + String(mSendCnt));
|
||||||
|
|
||||||
// append crc's
|
// append crc's
|
||||||
if (len > 10) {
|
if (appendCrc16 && (len > 10)) {
|
||||||
// crc control data
|
// crc control data
|
||||||
uint16_t crc = ah::crc16(&mTxBuf[10], len - 10);
|
uint16_t crc = ah::crc16(&mTxBuf[10], len - 10);
|
||||||
mTxBuf[len++] = (crc >> 8) & 0xff;
|
mTxBuf[len++] = (crc >> 8) & 0xff;
|
||||||
|
@ -302,6 +304,10 @@ class HmRadio {
|
||||||
mTxBuf[len] = ah::crc8(mTxBuf, len);
|
mTxBuf[len] = ah::crc8(mTxBuf, len);
|
||||||
len++;
|
len++;
|
||||||
|
|
||||||
|
// set TX and RX channels
|
||||||
|
mTxChIdx = (mTxChIdx + 1) % RF_CHANNELS;
|
||||||
|
mRxChIdx = (mTxChIdx + 2) % RF_CHANNELS;
|
||||||
|
|
||||||
if(mSerialDebug) {
|
if(mSerialDebug) {
|
||||||
DPRINT(DBG_INFO, F("TX "));
|
DPRINT(DBG_INFO, F("TX "));
|
||||||
DBGPRINT(String(len));
|
DBGPRINT(String(len));
|
||||||
|
@ -316,10 +322,6 @@ class HmRadio {
|
||||||
mNrf24.openWritingPipe(reinterpret_cast<uint8_t*>(&invId));
|
mNrf24.openWritingPipe(reinterpret_cast<uint8_t*>(&invId));
|
||||||
mNrf24.startWrite(mTxBuf, len, false); // false = request ACK response
|
mNrf24.startWrite(mTxBuf, len, false); // false = request ACK response
|
||||||
|
|
||||||
// switch TX channel for next packet
|
|
||||||
if(++mTxChIdx >= RF_CHANNELS)
|
|
||||||
mTxChIdx = 0;
|
|
||||||
|
|
||||||
if(isRetransmit)
|
if(isRetransmit)
|
||||||
mRetransmits++;
|
mRetransmits++;
|
||||||
else
|
else
|
||||||
|
|
|
@ -25,7 +25,6 @@ typedef struct {
|
||||||
uint8_t txId;
|
uint8_t txId;
|
||||||
uint8_t invId;
|
uint8_t invId;
|
||||||
uint8_t retransmits;
|
uint8_t retransmits;
|
||||||
//uint8_t skipfirstrepeat;
|
|
||||||
bool gotFragment;
|
bool gotFragment;
|
||||||
/*
|
/*
|
||||||
uint8_t data[MAX_PAYLOAD_ENTRIES][MAX_RF_PAYLOAD_SIZE];
|
uint8_t data[MAX_PAYLOAD_ENTRIES][MAX_RF_PAYLOAD_SIZE];
|
||||||
|
@ -70,8 +69,8 @@ class MiPayload {
|
||||||
}
|
}
|
||||||
|
|
||||||
void loop() {
|
void loop() {
|
||||||
if(NULL != mHighPrioIv) { // && mHighPrioIv->ivGen == IV_MI) {
|
if (NULL != mHighPrioIv) {
|
||||||
ivSend(mHighPrioIv, true); // for devcontrol commands?
|
ivSend(mHighPrioIv, true); // for e.g. devcontrol commands
|
||||||
mHighPrioIv = NULL;
|
mHighPrioIv = NULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -87,22 +86,23 @@ class MiPayload {
|
||||||
process(false); // no retransmit
|
process(false); // no retransmit
|
||||||
|
|
||||||
if (!mPayload[iv->id].complete) {
|
if (!mPayload[iv->id].complete) {
|
||||||
if (!mPayload[iv->id].gotFragment)
|
|
||||||
mStat->rxFailNoAnser++; // got nothing
|
|
||||||
else
|
|
||||||
mStat->rxFail++; // got fragments but not complete response
|
|
||||||
|
|
||||||
iv->setQueuedCmdFinished(); // command failed
|
|
||||||
if (mSerialDebug)
|
if (mSerialDebug)
|
||||||
DPRINT_IVID(DBG_INFO, iv->id);
|
DPRINT_IVID(DBG_INFO, iv->id);
|
||||||
|
if (!mPayload[iv->id].gotFragment) {
|
||||||
|
mStat->rxFailNoAnser++; // got nothing
|
||||||
|
if (mSerialDebug)
|
||||||
DBGPRINTLN(F("enqueued cmd failed/timeout"));
|
DBGPRINTLN(F("enqueued cmd failed/timeout"));
|
||||||
|
} else {
|
||||||
|
mStat->rxFail++; // got "fragments" (part of the required messages)
|
||||||
|
// but no complete set of responses
|
||||||
if (mSerialDebug) {
|
if (mSerialDebug) {
|
||||||
DPRINT_IVID(DBG_INFO, iv->id);
|
DBGPRINT(F("no complete Payload received! (retransmits: "));
|
||||||
DBGPRINT(F("no Payload received! (retransmits: "));
|
|
||||||
DBGPRINT(String(mPayload[iv->id].retransmits));
|
DBGPRINT(String(mPayload[iv->id].retransmits));
|
||||||
DBGPRINTLN(F(")"));
|
DBGPRINTLN(F(")"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
iv->setQueuedCmdFinished(); // command failed
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -148,9 +148,10 @@ class MiPayload {
|
||||||
if (cmd == 0x01 || cmd == SystemConfigPara ) { //0x1 and 0x05 for HM-types
|
if (cmd == 0x01 || cmd == SystemConfigPara ) { //0x1 and 0x05 for HM-types
|
||||||
cmd = 0x0f; // for MI, these seem to make part of the Polling the device software and hardware version number command
|
cmd = 0x0f; // for MI, these seem to make part of the Polling the device software and hardware version number command
|
||||||
cmd2 = cmd == SystemConfigPara ? 0x01 : 0x00; //perhaps we can only try to get second frame?
|
cmd2 = cmd == SystemConfigPara ? 0x01 : 0x00; //perhaps we can only try to get second frame?
|
||||||
mRadio->sendCmdPacket(iv->radioId.u64, cmd, cmd2, false);
|
mRadio.sendCmdPacket(iv->radioId.u64, cmd, cmd2, false, false);
|
||||||
} else {
|
} else {
|
||||||
mRadio->prepareDevInformCmd(iv->radioId.u64, cmd2, mPayload[iv->id].ts, iv->alarmMesIndex, false, cmd);
|
//mSys->Radio.prepareDevInformCmd(iv->radioId.u64, cmd2, mPayload[iv->id].ts, iv->alarmMesIndex, false, cmd);
|
||||||
|
mRadio.sendCmdPacket(iv->radioId.u64, cmd, cmd2, false, false);
|
||||||
};
|
};
|
||||||
|
|
||||||
mPayload[iv->id].txCmd = cmd;
|
mPayload[iv->id].txCmd = cmd;
|
||||||
|
@ -232,42 +233,39 @@ const byteAssign_t InfoAssignment[] = {
|
||||||
iv->setValue(i, rec, (float) ((p->packet[(12+2*i)] << 8) + p->packet[(13+2*i)])/1);
|
iv->setValue(i, rec, (float) ((p->packet[(12+2*i)] << 8) + p->packet[(13+2*i)])/1);
|
||||||
}
|
}
|
||||||
iv->isConnected = true;
|
iv->isConnected = true;
|
||||||
|
mPayload[iv->id].gotFragment = true;
|
||||||
if(mSerialDebug) {
|
if(mSerialDebug) {
|
||||||
DPRINT_IVID(DBG_INFO, iv->id);
|
DPRINT_IVID(DBG_INFO, iv->id);
|
||||||
DPRINT(DBG_INFO,F("HW_VER is "));
|
DPRINT(DBG_INFO,F("HW_VER is "));
|
||||||
DBGPRINTLN(String((p->packet[24] << 8) + p->packet[25]));
|
DBGPRINTLN(String((p->packet[24] << 8) + p->packet[25]));
|
||||||
}
|
}
|
||||||
/*iv->setQueuedCmdFinished();
|
|
||||||
mSys->Radio.sendCmdPacket(iv->radioId.u64, 0x0f, 0x01, false);*/
|
|
||||||
} else if ( p->packet[9] == 0x01 || p->packet[9] == 0x10 ) {//second frame for MI, 3rd gen. answers in 0x10
|
} else if ( p->packet[9] == 0x01 || p->packet[9] == 0x10 ) {//second frame for MI, 3rd gen. answers in 0x10
|
||||||
DPRINT_IVID(DBG_INFO, iv->id);
|
DPRINT_IVID(DBG_INFO, iv->id);
|
||||||
if ( p->packet[9] == 0x01 ) {
|
if ( p->packet[9] == 0x01 ) {
|
||||||
DBGPRINTLN(F("got 2nd frame (hw info)"));
|
DBGPRINTLN(F("got 2nd frame (hw info)"));
|
||||||
} else {
|
|
||||||
DBGPRINTLN(F("3rd gen. inverter!")); // see table in OpenDTU code, DevInfoParser.cpp devInfo[]
|
|
||||||
}
|
|
||||||
// xlsx: HW_ECapValue is total energy?!? (data coll. inst. #154)
|
|
||||||
DPRINT(DBG_INFO,F("HW_PartNo "));
|
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]));
|
DBGPRINTLN(String((uint32_t) (((p->packet[10] << 8) | p->packet[11]) << 8 | p->packet[12]) << 8 | p->packet[13]));
|
||||||
//DBGPRINTLN(String((p->packet[12] << 8) + p->packet[13]));
|
mPayload[iv->id].gotFragment = true;
|
||||||
if ( p->packet[9] == 0x01 ) {
|
|
||||||
iv->setValue(iv->getPosByChFld(0, FLD_YT, rec), rec, (float) ((p->packet[20] << 8) + p->packet[21])/1);
|
iv->setValue(iv->getPosByChFld(0, FLD_YT, rec), rec, (float) ((p->packet[20] << 8) + p->packet[21])/1);
|
||||||
if(mSerialDebug) {
|
if(mSerialDebug) {
|
||||||
DPRINT(DBG_INFO,F("HW_ECapValue "));
|
|
||||||
DBGPRINTLN(String((p->packet[20] << 8) + p->packet[21]));
|
|
||||||
|
|
||||||
DPRINT(DBG_INFO,F("HW_FB_TLmValue "));
|
DPRINT(DBG_INFO,F("HW_FB_TLmValue "));
|
||||||
DBGPRINTLN(String((p->packet[14] << 8) + p->packet[15]));
|
DBGPRINTLN(String((p->packet[14] << 8) + p->packet[15]));
|
||||||
DPRINT(DBG_INFO,F("HW_FB_ReSPRT "));
|
DPRINT(DBG_INFO,F("HW_FB_ReSPRT "));
|
||||||
DBGPRINTLN(String((p->packet[16] << 8) + p->packet[17]));
|
DBGPRINTLN(String((p->packet[16] << 8) + p->packet[17]));
|
||||||
DPRINT(DBG_INFO,F("HW_GridSamp_ResValule "));
|
DPRINT(DBG_INFO,F("HW_GridSamp_ResValule "));
|
||||||
DBGPRINTLN(String((p->packet[18] << 8) + p->packet[19]));
|
DBGPRINTLN(String((p->packet[18] << 8) + p->packet[19]));
|
||||||
|
DPRINT(DBG_INFO,F("HW_ECapValue "));
|
||||||
|
DBGPRINTLN(String((p->packet[20] << 8) + p->packet[21]));
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
DBGPRINTLN(F("3rd gen. inverter!")); // see table in OpenDTU code, DevInfoParser.cpp devInfo[]
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if ( p->packet[9] == 0x12 ) {//3rd frame
|
} else if ( p->packet[9] == 0x12 ) {//3rd frame
|
||||||
DPRINT_IVID(DBG_INFO, iv->id);
|
DPRINT_IVID(DBG_INFO, iv->id);
|
||||||
DBGPRINTLN(F("got 3rd frame (hw info)"));
|
DBGPRINTLN(F("got 3rd frame (hw info)"));
|
||||||
iv->setQueuedCmdFinished();
|
iv->setQueuedCmdFinished();
|
||||||
|
mPayload[iv->id].complete = true;
|
||||||
mStat->rxSuccess++;
|
mStat->rxSuccess++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -347,7 +345,9 @@ const byteAssign_t InfoAssignment[] = {
|
||||||
payloadLen -= 2;
|
payloadLen -= 2;
|
||||||
|
|
||||||
if (mSerialDebug) {
|
if (mSerialDebug) {
|
||||||
DPRINT(DBG_INFO, F("Payload (") + String(payloadLen) + "): ");
|
DPRINT(DBG_INFO, F("Payload ("));
|
||||||
|
DBGPRINT(String(payloadLen));
|
||||||
|
DBGPRINT("): ");
|
||||||
ah::dumpBuf(payload, payloadLen);
|
ah::dumpBuf(payload, payloadLen);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -442,14 +442,14 @@ const byteAssign_t InfoAssignment[] = {
|
||||||
mPayload[iv->id].retransmits = mMaxRetrans;
|
mPayload[iv->id].retransmits = mMaxRetrans;
|
||||||
} else if ( cmd == 0x0f ) {
|
} else if ( cmd == 0x0f ) {
|
||||||
//hard/firmware request
|
//hard/firmware request
|
||||||
mRadio->sendCmdPacket(iv->radioId.u64, 0x0f, 0x00, true);
|
mRadio.sendCmdPacket(iv->radioId.u64, 0x0f, 0x00, true, false);
|
||||||
//iv->setQueuedCmdFinished();
|
//iv->setQueuedCmdFinished();
|
||||||
//cmd = iv->getQueuedCmd();
|
//cmd = iv->getQueuedCmd();
|
||||||
} else {
|
} else {
|
||||||
bool change = false;
|
bool change = false;
|
||||||
if ( cmd >= 0x36 && cmd < 0x39 ) { // MI-1500 Data command
|
if ( cmd >= 0x36 && cmd < 0x39 ) { // MI-1500 Data command
|
||||||
//cmd++; // just request the next channel
|
if (cmd > 0x36 && mPayload[iv->id].retransmits==1) // first request for the upper channels
|
||||||
//change = true;
|
change = true;
|
||||||
} else if ( cmd == 0x09 ) {//MI single or dual channel device
|
} else if ( cmd == 0x09 ) {//MI single or dual channel device
|
||||||
if ( mPayload[iv->id].dataAB[CH1] && iv->type == INV_TYPE_2CH ) {
|
if ( mPayload[iv->id].dataAB[CH1] && iv->type == INV_TYPE_2CH ) {
|
||||||
if (!mPayload[iv->id].stsAB[CH1] && mPayload[iv->id].retransmits<2) {}
|
if (!mPayload[iv->id].stsAB[CH1] && mPayload[iv->id].retransmits<2) {}
|
||||||
|
@ -479,8 +479,8 @@ const byteAssign_t InfoAssignment[] = {
|
||||||
}
|
}
|
||||||
DBGPRINT(F(" 0x"));
|
DBGPRINT(F(" 0x"));
|
||||||
DBGHEXLN(cmd);
|
DBGHEXLN(cmd);
|
||||||
//mSys->Radio.sendCmdPacket(iv->radioId.u64, cmd, cmd, true);
|
mRadio.sendCmdPacket(iv->radioId.u64, cmd, cmd, true, false);
|
||||||
mRadio->prepareDevInformCmd(iv->radioId.u64, cmd, mPayload[iv->id].ts, iv->alarmMesIndex, true, cmd);
|
//mSys->Radio.prepareDevInformCmd(iv->radioId.u64, cmd, mPayload[iv->id].ts, iv->alarmMesIndex, true, cmd);
|
||||||
yield();
|
yield();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -496,7 +496,8 @@ const byteAssign_t InfoAssignment[] = {
|
||||||
|
|
||||||
DBGPRINT(F("prepareDevInformCmd 0x"));
|
DBGPRINT(F("prepareDevInformCmd 0x"));
|
||||||
DBGHEXLN(mPayload[iv->id].txCmd);
|
DBGHEXLN(mPayload[iv->id].txCmd);
|
||||||
mRadio->prepareDevInformCmd(iv->radioId.u64, mPayload[iv->id].txCmd, mPayload[iv->id].ts, iv->alarmMesIndex, true);
|
//mSys->Radio.prepareDevInformCmd(iv->radioId.u64, mPayload[iv->id].txCmd, mPayload[iv->id].ts, iv->alarmMesIndex, true);
|
||||||
|
mRadio.sendCmdPacket(iv->radioId.u64, mPayload[iv->id].txCmd, mPayload[iv->id].txCmd, false, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/*else { // payload complete
|
/*else { // payload complete
|
||||||
|
@ -641,7 +642,6 @@ const byteAssign_t InfoAssignment[] = {
|
||||||
( p->packet[0] == 0x91 || p->packet[0] == (0x37 + ALL_FRAMES) ) ? CH2 :
|
( p->packet[0] == 0x91 || p->packet[0] == (0x37 + ALL_FRAMES) ) ? CH2 :
|
||||||
p->packet[0] == (0x38 + ALL_FRAMES) ? CH3 :
|
p->packet[0] == (0x38 + ALL_FRAMES) ? CH3 :
|
||||||
CH4;
|
CH4;
|
||||||
//DPRINTLN(DBG_INFO, F("(#") + String(iv->id) + F(") data msg 0x") + String(p->packet[0], HEX) + F(" channel ") + datachan);
|
|
||||||
// count in RF_communication_protocol.xlsx is with offset = -1
|
// count in RF_communication_protocol.xlsx is with offset = -1
|
||||||
iv->setValue(iv->getPosByChFld(datachan, FLD_UDC, rec), rec, (float)((p->packet[9] << 8) + p->packet[10])/10);
|
iv->setValue(iv->getPosByChFld(datachan, FLD_UDC, rec), rec, (float)((p->packet[9] << 8) + p->packet[10])/10);
|
||||||
yield();
|
yield();
|
||||||
|
@ -656,12 +656,11 @@ const byteAssign_t InfoAssignment[] = {
|
||||||
yield();
|
yield();
|
||||||
iv->setValue(iv->getPosByChFld(0, FLD_T, rec), rec, (float) ((int16_t)(p->packet[21] << 8) + p->packet[22])/10);
|
iv->setValue(iv->getPosByChFld(0, FLD_T, rec), rec, (float) ((int16_t)(p->packet[21] << 8) + p->packet[22])/10);
|
||||||
iv->setValue(iv->getPosByChFld(0, FLD_IRR, rec), rec, (float) (calcIrradiation(iv, datachan)));
|
iv->setValue(iv->getPosByChFld(0, FLD_IRR, rec), rec, (float) (calcIrradiation(iv, datachan)));
|
||||||
//AC Power is missing; we may have to calculate, as no respective data is in payload
|
|
||||||
|
|
||||||
if ( datachan < 3 ) {
|
if ( datachan < 3 ) {
|
||||||
mPayload[iv->id].dataAB[datachan] = true;
|
mPayload[iv->id].dataAB[datachan] = true;
|
||||||
}
|
}
|
||||||
if ( !mPayload[iv->id].dataAB[CH0] && mPayload[iv->id].dataAB[CH2] && mPayload[iv->id].dataAB[CH2] ) {
|
if ( !mPayload[iv->id].dataAB[CH0] && mPayload[iv->id].dataAB[CH1] && mPayload[iv->id].dataAB[CH2] ) {
|
||||||
mPayload[iv->id].dataAB[CH0] = true;
|
mPayload[iv->id].dataAB[CH0] = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -687,17 +686,13 @@ const byteAssign_t InfoAssignment[] = {
|
||||||
mSys->Radio.prepareDevInformCmd(iv->radioId.u64, cmd, mPayload[iv->id].ts, iv->alarmMesIndex, false, cmd);
|
mSys->Radio.prepareDevInformCmd(iv->radioId.u64, cmd, mPayload[iv->id].ts, iv->alarmMesIndex, false, cmd);
|
||||||
mPayload[iv->id].txCmd = cmd;*/
|
mPayload[iv->id].txCmd = cmd;*/
|
||||||
mPayload[iv->id].txCmd++;
|
mPayload[iv->id].txCmd++;
|
||||||
if (mPayload[iv->id].retransmits)
|
mPayload[iv->id].retransmits = 0; // reserve retransmissions for each response
|
||||||
mPayload[iv->id].retransmits--; // reserve retransmissions for each response
|
|
||||||
mPayload[iv->id].complete = false;
|
mPayload[iv->id].complete = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (p->packet[0] == (0x39 + ALL_FRAMES) ) {
|
/*else if ( p->packet[0] == (0x39 + ALL_FRAMES) ) {
|
||||||
/*uint8_t cmd = p->packet[0] - ALL_FRAMES + 1;
|
|
||||||
mSys->Radio.prepareDevInformCmd(iv->radioId.u64, cmd, mPayload[iv->id].ts, iv->alarmMesIndex, false, cmd);
|
|
||||||
mPayload[iv->id].txCmd = cmd;*/
|
|
||||||
mPayload[iv->id].complete = true;
|
mPayload[iv->id].complete = true;
|
||||||
}
|
}*/
|
||||||
|
|
||||||
/*if (iv->alarmMesIndex < rec->record[iv->getPosByChFld(0, FLD_EVT, rec)]){
|
/*if (iv->alarmMesIndex < rec->record[iv->getPosByChFld(0, FLD_EVT, rec)]){
|
||||||
iv->alarmMesIndex = rec->record[iv->getPosByChFld(0, FLD_EVT, rec)];
|
iv->alarmMesIndex = rec->record[iv->getPosByChFld(0, FLD_EVT, rec)];
|
||||||
|
@ -709,15 +704,6 @@ const byteAssign_t InfoAssignment[] = {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( mPayload[iv->id].complete || //4ch device
|
|
||||||
(iv->type != INV_TYPE_4CH //other devices
|
|
||||||
&& mPayload[iv->id].dataAB[CH0]
|
|
||||||
&& mPayload[iv->id].stsAB[CH0])) {
|
|
||||||
miComplete(iv);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
if(AlarmData == mPayload[iv->id].txCmd) {
|
if(AlarmData == mPayload[iv->id].txCmd) {
|
||||||
uint8_t i = 0;
|
uint8_t i = 0;
|
||||||
|
@ -732,12 +718,20 @@ const byteAssign_t InfoAssignment[] = {
|
||||||
yield();
|
yield();
|
||||||
}
|
}
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
|
//if ( mPayload[iv->id].complete || //4ch device
|
||||||
|
if ( p->packet[0] == (0x39 + ALL_FRAMES) || //4ch device - last message
|
||||||
|
(iv->type != INV_TYPE_4CH //other devices
|
||||||
|
&& mPayload[iv->id].dataAB[CH0]
|
||||||
|
&& mPayload[iv->id].stsAB[CH0])) {
|
||||||
|
miComplete(iv);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void miComplete(Inverter<> *iv) {
|
void miComplete(Inverter<> *iv) {
|
||||||
if (mPayload[iv->id].complete)
|
if ( mPayload[iv->id].complete ) // && iv->type != INV_TYPE_4CH)
|
||||||
return; //if we got second message as well in repreated attempt
|
return; //if we got second message as well in repreated attempt
|
||||||
mPayload[iv->id].complete = true; // For 2 CH devices, this might be too short...
|
mPayload[iv->id].complete = true;
|
||||||
DPRINT_IVID(DBG_INFO, iv->id);
|
DPRINT_IVID(DBG_INFO, iv->id);
|
||||||
DBGPRINTLN(F("got all msgs"));
|
DBGPRINTLN(F("got all msgs"));
|
||||||
record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug);
|
record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug);
|
||||||
|
@ -758,7 +752,7 @@ const byteAssign_t InfoAssignment[] = {
|
||||||
iv->setQueuedCmdFinished();
|
iv->setQueuedCmdFinished();
|
||||||
mStat->rxSuccess++;
|
mStat->rxSuccess++;
|
||||||
yield();
|
yield();
|
||||||
notify(mPayload[iv->id].txCmd);
|
notify(RealTimeRunData_Debug); //iv->type == INV_TYPE_4CH ? 0x36 : 0x09 );
|
||||||
}
|
}
|
||||||
|
|
||||||
bool build(uint8_t id, bool *complete) {
|
bool build(uint8_t id, bool *complete) {
|
||||||
|
@ -778,6 +772,27 @@ const byteAssign_t InfoAssignment[] = {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* uint16_t mParseAlarmLog(uint8_t id, uint8_t pyld[], uint8_t len, uint32_t *start, uint32_t *endTime) {
|
||||||
|
uint8_t startOff = 2 + id * ALARM_LOG_ENTRY_SIZE;
|
||||||
|
if((startOff + ALARM_LOG_ENTRY_SIZE) > len)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
uint16_t wCode = ((uint16_t)pyld[startOff]) << 8 | pyld[startOff+1];
|
||||||
|
uint32_t startTimeOffset = 0, endTimeOffset = 0;
|
||||||
|
|
||||||
|
if (((wCode >> 13) & 0x01) == 1) // check if is AM or PM
|
||||||
|
startTimeOffset = 12 * 60 * 60;
|
||||||
|
if (((wCode >> 12) & 0x01) == 1) // check if is AM or PM
|
||||||
|
endTimeOffset = 12 * 60 * 60;
|
||||||
|
|
||||||
|
*start = (((uint16_t)pyld[startOff + 4] << 8) | ((uint16_t)pyld[startOff + 5])) + startTimeOffset;
|
||||||
|
*endTime = (((uint16_t)pyld[startOff + 6] << 8) | ((uint16_t)pyld[startOff + 7])) + endTimeOffset;
|
||||||
|
|
||||||
|
DPRINTLN(DBG_INFO, "Alarm #" + String(pyld[startOff+1]) + " '" + String(getAlarmStr(pyld[startOff+1])) + "' start: " + ah::getTimeStr(*start) + ", end: " + ah::getTimeStr(*endTime));
|
||||||
|
return pyld[startOff+1];
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
void reset(uint8_t id, bool clrSts = false) {
|
void reset(uint8_t id, bool clrSts = false) {
|
||||||
DPRINT_IVID(DBG_INFO, id);
|
DPRINT_IVID(DBG_INFO, id);
|
||||||
DBGPRINTLN(F("resetPayload"));
|
DBGPRINTLN(F("resetPayload"));
|
||||||
|
|
|
@ -16,17 +16,6 @@ include_dir = .
|
||||||
framework = arduino
|
framework = arduino
|
||||||
board_build.filesystem = littlefs
|
board_build.filesystem = littlefs
|
||||||
upload_speed = 921600
|
upload_speed = 921600
|
||||||
|
|
||||||
;build_flags =
|
|
||||||
; ;;;;; Possible Debug options ;;;;;;
|
|
||||||
; https://docs.platformio.org/en/latest/platforms/espressif8266.html#debug-level
|
|
||||||
;-DDEBUG_ESP_PORT=Serial
|
|
||||||
;-DDEBUG_ESP_CORE
|
|
||||||
;-DDEBUG_ESP_WIFI
|
|
||||||
;-DDEBUG_ESP_HTTP_CLIENT
|
|
||||||
;-DDEBUG_ESP_HTTP_SERVER
|
|
||||||
;-DDEBUG_ESP_OOM
|
|
||||||
|
|
||||||
monitor_speed = 115200
|
monitor_speed = 115200
|
||||||
|
|
||||||
extra_scripts =
|
extra_scripts =
|
||||||
|
@ -38,9 +27,9 @@ lib_deps =
|
||||||
nrf24/RF24 @ ^1.4.5
|
nrf24/RF24 @ ^1.4.5
|
||||||
paulstoffregen/Time @ ^1.6.1
|
paulstoffregen/Time @ ^1.6.1
|
||||||
https://github.com/bertmelis/espMqttClient#v1.4.2
|
https://github.com/bertmelis/espMqttClient#v1.4.2
|
||||||
bblanchon/ArduinoJson @ ^6.21.0
|
bblanchon/ArduinoJson @ ^6.21.2
|
||||||
https://github.com/JChristensen/Timezone @ ^1.2.4
|
https://github.com/JChristensen/Timezone @ ^1.2.4
|
||||||
olikraus/U8g2 @ ^2.34.16
|
olikraus/U8g2 @ ^2.34.17
|
||||||
zinggjm/GxEPD2 @ ^1.5.0
|
zinggjm/GxEPD2 @ ^1.5.0
|
||||||
|
|
||||||
|
|
||||||
|
@ -95,7 +84,13 @@ platform = espressif8266
|
||||||
board = esp8285
|
board = esp8285
|
||||||
board_build.ldscript = eagle.flash.1m64.ld
|
board_build.ldscript = eagle.flash.1m64.ld
|
||||||
board_build.f_cpu = 80000000L
|
board_build.f_cpu = 80000000L
|
||||||
build_flags = -DDEBUG_LEVEL=DBG_DEBUG -DDEBUG_ESP_CORE -DDEBUG_ESP_WIFI -DDEBUG_ESP_HTTP_CLIENT -DDEBUG_ESP_HTTP_SERVER -DDEBUG_ESP_OOM -DDEBUG_ESP_PORT=Serial
|
build_flags = -DDEBUG_LEVEL=DBG_DEBUG
|
||||||
|
-DDEBUG_ESP_CORE
|
||||||
|
-DDEBUG_ESP_WIFI
|
||||||
|
-DDEBUG_ESP_HTTP_CLIENT
|
||||||
|
-DDEBUG_ESP_HTTP_SERVER
|
||||||
|
-DDEBUG_ESP_OOM
|
||||||
|
-DDEBUG_ESP_PORT=Serial
|
||||||
build_type = debug
|
build_type = debug
|
||||||
monitor_filters =
|
monitor_filters =
|
||||||
;default ; Remove typical terminal control codes from input
|
;default ; Remove typical terminal control codes from input
|
||||||
|
@ -103,7 +98,7 @@ monitor_filters =
|
||||||
log2file ; Log data to a file “platformio-device-monitor-*.log” located in the current working directory
|
log2file ; Log data to a file “platformio-device-monitor-*.log” located in the current working directory
|
||||||
|
|
||||||
[env:esp32-wroom32-release]
|
[env:esp32-wroom32-release]
|
||||||
platform = espressif32
|
platform = espressif32@>=6.1.0
|
||||||
board = lolin_d32
|
board = lolin_d32
|
||||||
build_flags = -D RELEASE -std=gnu++14
|
build_flags = -D RELEASE -std=gnu++14
|
||||||
build_unflags = -std=gnu++11
|
build_unflags = -std=gnu++11
|
||||||
|
@ -114,9 +109,11 @@ monitor_filters =
|
||||||
esp32_exception_decoder
|
esp32_exception_decoder
|
||||||
|
|
||||||
[env:esp32-wroom32-release-prometheus]
|
[env:esp32-wroom32-release-prometheus]
|
||||||
platform = espressif32
|
platform = espressif32@>=6.1.0
|
||||||
board = lolin_d32
|
board = lolin_d32
|
||||||
build_flags = -D RELEASE -std=gnu++14 -DENABLE_PROMETHEUS_EP
|
build_flags = -D RELEASE
|
||||||
|
-std=gnu++14
|
||||||
|
-DENABLE_PROMETHEUS_EP
|
||||||
build_unflags = -std=gnu++11
|
build_unflags = -std=gnu++11
|
||||||
monitor_filters =
|
monitor_filters =
|
||||||
;default ; Remove typical terminal control codes from input
|
;default ; Remove typical terminal control codes from input
|
||||||
|
@ -125,9 +122,16 @@ monitor_filters =
|
||||||
esp32_exception_decoder
|
esp32_exception_decoder
|
||||||
|
|
||||||
[env:esp32-wroom32-debug]
|
[env:esp32-wroom32-debug]
|
||||||
platform = espressif32
|
platform = espressif32@>=6.1.0
|
||||||
board = lolin_d32
|
board = lolin_d32
|
||||||
build_flags = -DDEBUG_LEVEL=DBG_DEBUG -DDEBUG_ESP_CORE -DDEBUG_ESP_WIFI -DDEBUG_ESP_HTTP_CLIENT -DDEBUG_ESP_HTTP_SERVER -DDEBUG_ESP_OOM -DDEBUG_ESP_PORT=Serial -std=gnu++14
|
build_flags = -DDEBUG_LEVEL=DBG_DEBUG
|
||||||
|
-DDEBUG_ESP_CORE
|
||||||
|
-DDEBUG_ESP_WIFI
|
||||||
|
-DDEBUG_ESP_HTTP_CLIENT
|
||||||
|
-DDEBUG_ESP_HTTP_SERVER
|
||||||
|
-DDEBUG_ESP_OOM
|
||||||
|
-DDEBUG_ESP_PORT=Serial
|
||||||
|
-std=gnu++14
|
||||||
build_unflags = -std=gnu++11
|
build_unflags = -std=gnu++11
|
||||||
build_type = debug
|
build_type = debug
|
||||||
monitor_filters =
|
monitor_filters =
|
||||||
|
@ -136,8 +140,12 @@ monitor_filters =
|
||||||
log2file ; Log data to a file “platformio-device-monitor-*.log” located in the current working directory
|
log2file ; Log data to a file “platformio-device-monitor-*.log” located in the current working directory
|
||||||
|
|
||||||
[env:opendtufusionv1-release]
|
[env:opendtufusionv1-release]
|
||||||
platform = espressif32
|
platform = espressif32@>=6.1.0
|
||||||
board = esp32-s3-devkitc-1
|
board = esp32-s3-devkitc-1
|
||||||
|
upload_protocol = esp-builtin
|
||||||
|
upload_speed = 115200
|
||||||
|
debug_tool = esp-builtin
|
||||||
|
debug_speed = 12000
|
||||||
build_flags = -D RELEASE -std=gnu++14
|
build_flags = -D RELEASE -std=gnu++14
|
||||||
build_unflags = -std=gnu++11
|
build_unflags = -std=gnu++11
|
||||||
monitor_filters =
|
monitor_filters =
|
||||||
|
|
|
@ -49,6 +49,7 @@ class PubMqtt {
|
||||||
mTxCnt = 0;
|
mTxCnt = 0;
|
||||||
mSubscriptionCb = NULL;
|
mSubscriptionCb = NULL;
|
||||||
memset(mLastIvState, MQTT_STATUS_NOT_AVAIL_NOT_PROD, MAX_NUM_INVERTERS);
|
memset(mLastIvState, MQTT_STATUS_NOT_AVAIL_NOT_PROD, MAX_NUM_INVERTERS);
|
||||||
|
memset(mIvLastRTRpub, 0, MAX_NUM_INVERTERS * 4);
|
||||||
mLastAnyAvail = false;
|
mLastAnyAvail = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -246,6 +247,8 @@ class PubMqtt {
|
||||||
subscribe(mVal);
|
subscribe(mVal);
|
||||||
snprintf(mVal, 20, "ctrl/restart/%d", i);
|
snprintf(mVal, 20, "ctrl/restart/%d", i);
|
||||||
subscribe(mVal);
|
subscribe(mVal);
|
||||||
|
snprintf(mVal, 20, "ctrl/power/%d", i);
|
||||||
|
subscribe(mVal);
|
||||||
}
|
}
|
||||||
subscribe(subscr[MQTT_SUBS_SET_TIME]);
|
subscribe(subscr[MQTT_SUBS_SET_TIME]);
|
||||||
}
|
}
|
||||||
|
@ -522,7 +525,13 @@ class PubMqtt {
|
||||||
void sendData(Inverter<> *iv, uint8_t curInfoCmd) {
|
void sendData(Inverter<> *iv, uint8_t curInfoCmd) {
|
||||||
record_t<> *rec = iv->getRecordStruct(curInfoCmd);
|
record_t<> *rec = iv->getRecordStruct(curInfoCmd);
|
||||||
|
|
||||||
if (iv->getLastTs(rec) > 0) {
|
uint32_t lastTs = iv->getLastTs(rec);
|
||||||
|
bool pubData = (lastTs > 0);
|
||||||
|
if (curInfoCmd == RealTimeRunData_Debug)
|
||||||
|
pubData &= (lastTs != mIvLastRTRpub[iv->id]);
|
||||||
|
|
||||||
|
if (pubData) {
|
||||||
|
mIvLastRTRpub[iv->id] = lastTs;
|
||||||
for (uint8_t i = 0; i < rec->length; i++) {
|
for (uint8_t i = 0; i < rec->length; i++) {
|
||||||
bool retained = false;
|
bool retained = false;
|
||||||
if (curInfoCmd == RealTimeRunData_Debug) {
|
if (curInfoCmd == RealTimeRunData_Debug) {
|
||||||
|
@ -653,6 +662,7 @@ class PubMqtt {
|
||||||
subscriptionCb mSubscriptionCb;
|
subscriptionCb mSubscriptionCb;
|
||||||
bool mLastAnyAvail;
|
bool mLastAnyAvail;
|
||||||
uint8_t mLastIvState[MAX_NUM_INVERTERS];
|
uint8_t mLastIvState[MAX_NUM_INVERTERS];
|
||||||
|
uint32_t mIvLastRTRpub[MAX_NUM_INVERTERS];
|
||||||
uint16_t mIntervalTimeout;
|
uint16_t mIntervalTimeout;
|
||||||
|
|
||||||
// last will topic and payload must be available trough lifetime of 'espMqttClient'
|
// last will topic and payload must be available trough lifetime of 'espMqttClient'
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
// 2023 Ahoy, https://www.mikrocontroller.net/topic/525778
|
// 2023 Ahoy, https://www.mikrocontroller.net/topic/525778
|
||||||
// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/
|
// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/4.0/deed
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
|
|
||||||
#ifndef __DBG_H__
|
#ifndef __DBG_H__
|
||||||
|
|
|
@ -78,19 +78,19 @@ class RestApi {
|
||||||
JsonObject root = response->getRoot();
|
JsonObject root = response->getRoot();
|
||||||
|
|
||||||
String path = request->url().substring(5);
|
String path = request->url().substring(5);
|
||||||
if(path == "html/system") getHtmlSystem(root);
|
if(path == "html/system") getHtmlSystem(request, root);
|
||||||
else if(path == "html/logout") getHtmlLogout(root);
|
else if(path == "html/logout") getHtmlLogout(request, root);
|
||||||
else if(path == "html/reboot") getHtmlReboot(root);
|
else if(path == "html/reboot") getHtmlReboot(request, root);
|
||||||
else if(path == "html/save") getHtmlSave(root);
|
else if(path == "html/save") getHtmlSave(request, root);
|
||||||
else if(path == "system") getSysInfo(root);
|
else if(path == "system") getSysInfo(request, root);
|
||||||
else if(path == "generic") getGeneric(root);
|
else if(path == "generic") getGeneric(request, root);
|
||||||
else if(path == "reboot") getReboot(root);
|
else if(path == "reboot") getReboot(request, root);
|
||||||
else if(path == "statistics") getStatistics(root);
|
else if(path == "statistics") getStatistics(root);
|
||||||
else if(path == "inverter/list") getInverterList(root);
|
else if(path == "inverter/list") getInverterList(root);
|
||||||
else if(path == "index") getIndex(root);
|
else if(path == "index") getIndex(request, root);
|
||||||
else if(path == "setup") getSetup(root);
|
else if(path == "setup") getSetup(request, root);
|
||||||
else if(path == "setup/networks") getNetworks(root);
|
else if(path == "setup/networks") getNetworks(root);
|
||||||
else if(path == "live") getLive(root);
|
else if(path == "live") getLive(request, root);
|
||||||
else if(path == "record/info") getRecord(root, InverterDevInform_All);
|
else if(path == "record/info") getRecord(root, InverterDevInform_All);
|
||||||
else if(path == "record/alarm") getRecord(root, AlarmData);
|
else if(path == "record/alarm") getRecord(root, AlarmData);
|
||||||
else if(path == "record/config") getRecord(root, SystemConfigPara);
|
else if(path == "record/config") getRecord(root, SystemConfigPara);
|
||||||
|
@ -190,10 +190,10 @@ class RestApi {
|
||||||
fp.close();
|
fp.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
void getGeneric(JsonObject obj) {
|
void getGeneric(AsyncWebServerRequest *request, JsonObject obj) {
|
||||||
obj[F("wifi_rssi")] = (WiFi.status() != WL_CONNECTED) ? 0 : WiFi.RSSI();
|
obj[F("wifi_rssi")] = (WiFi.status() != WL_CONNECTED) ? 0 : WiFi.RSSI();
|
||||||
obj[F("ts_uptime")] = mApp->getUptime();
|
obj[F("ts_uptime")] = mApp->getUptime();
|
||||||
obj[F("menu_prot")] = mApp->getProtection();
|
obj[F("menu_prot")] = mApp->getProtection(request);
|
||||||
obj[F("menu_mask")] = (uint16_t)(mConfig->sys.protectionMask );
|
obj[F("menu_mask")] = (uint16_t)(mConfig->sys.protectionMask );
|
||||||
obj[F("menu_protEn")] = (bool) (strlen(mConfig->sys.adminPwd) > 0);
|
obj[F("menu_protEn")] = (bool) (strlen(mConfig->sys.adminPwd) > 0);
|
||||||
|
|
||||||
|
@ -204,7 +204,7 @@ class RestApi {
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void getSysInfo(JsonObject obj) {
|
void getSysInfo(AsyncWebServerRequest *request, JsonObject obj) {
|
||||||
obj[F("ssid")] = mConfig->sys.stationSsid;
|
obj[F("ssid")] = mConfig->sys.stationSsid;
|
||||||
obj[F("device_name")] = mConfig->sys.deviceName;
|
obj[F("device_name")] = mConfig->sys.deviceName;
|
||||||
obj[F("dark_mode")] = (bool)mConfig->sys.darkMode;
|
obj[F("dark_mode")] = (bool)mConfig->sys.darkMode;
|
||||||
|
@ -219,7 +219,7 @@ class RestApi {
|
||||||
obj[F("heap_free")] = mHeapFree;
|
obj[F("heap_free")] = mHeapFree;
|
||||||
obj[F("sketch_total")] = ESP.getFreeSketchSpace();
|
obj[F("sketch_total")] = ESP.getFreeSketchSpace();
|
||||||
obj[F("sketch_used")] = ESP.getSketchSize() / 1024; // in kb
|
obj[F("sketch_used")] = ESP.getSketchSize() / 1024; // in kb
|
||||||
getGeneric(obj);
|
getGeneric(request, obj);
|
||||||
|
|
||||||
getRadioNrf(obj.createNestedObject(F("radio")));
|
getRadioNrf(obj.createNestedObject(F("radio")));
|
||||||
getStatistics(obj.createNestedObject(F("statistics")));
|
getStatistics(obj.createNestedObject(F("statistics")));
|
||||||
|
@ -253,34 +253,35 @@ class RestApi {
|
||||||
obj[F("schMax")] = max;
|
obj[F("schMax")] = max;
|
||||||
}
|
}
|
||||||
|
|
||||||
void getHtmlSystem(JsonObject obj) {
|
void getHtmlSystem(AsyncWebServerRequest *request, JsonObject obj) {
|
||||||
getSysInfo(obj.createNestedObject(F("system")));
|
getSysInfo(request, obj.createNestedObject(F("system")));
|
||||||
getGeneric(obj.createNestedObject(F("generic")));
|
getGeneric(request, obj.createNestedObject(F("generic")));
|
||||||
obj[F("html")] = F("<a href=\"/factory\" class=\"btn\">Factory Reset</a><br/><br/><a href=\"/reboot\" class=\"btn\">Reboot</a>");
|
obj[F("html")] = F("<a href=\"/factory\" class=\"btn\">Factory Reset</a><br/><br/><a href=\"/reboot\" class=\"btn\">Reboot</a>");
|
||||||
}
|
}
|
||||||
|
|
||||||
void getHtmlLogout(JsonObject obj) {
|
void getHtmlLogout(AsyncWebServerRequest *request, JsonObject obj) {
|
||||||
getGeneric(obj.createNestedObject(F("generic")));
|
getGeneric(request, obj.createNestedObject(F("generic")));
|
||||||
obj[F("refresh")] = 3;
|
obj[F("refresh")] = 3;
|
||||||
obj[F("refresh_url")] = "/";
|
obj[F("refresh_url")] = "/";
|
||||||
obj[F("html")] = F("succesfully logged out");
|
obj[F("html")] = F("succesfully logged out");
|
||||||
}
|
}
|
||||||
|
|
||||||
void getHtmlReboot(JsonObject obj) {
|
void getHtmlReboot(AsyncWebServerRequest *request, JsonObject obj) {
|
||||||
getGeneric(obj.createNestedObject(F("generic")));
|
getGeneric(request, obj.createNestedObject(F("generic")));
|
||||||
obj[F("refresh")] = 20;
|
obj[F("refresh")] = 20;
|
||||||
obj[F("refresh_url")] = "/";
|
obj[F("refresh_url")] = "/";
|
||||||
obj[F("html")] = F("rebooting ...");
|
obj[F("html")] = F("rebooting ...");
|
||||||
}
|
}
|
||||||
|
|
||||||
void getHtmlSave(JsonObject obj) {
|
void getHtmlSave(AsyncWebServerRequest *request, JsonObject obj) {
|
||||||
getGeneric(obj.createNestedObject(F("generic")));
|
getGeneric(request, obj.createNestedObject(F("generic")));
|
||||||
obj["pending"] = (bool)mApp->getSavePending();
|
obj["pending"] = (bool)mApp->getSavePending();
|
||||||
obj["success"] = (bool)mApp->getLastSaveSucceed();
|
obj["success"] = (bool)mApp->getLastSaveSucceed();
|
||||||
|
obj["reboot"] = (bool)mApp->getShouldReboot();
|
||||||
}
|
}
|
||||||
|
|
||||||
void getReboot(JsonObject obj) {
|
void getReboot(AsyncWebServerRequest *request, JsonObject obj) {
|
||||||
getGeneric(obj.createNestedObject(F("generic")));
|
getGeneric(request, obj.createNestedObject(F("generic")));
|
||||||
obj[F("refresh")] = 10;
|
obj[F("refresh")] = 10;
|
||||||
obj[F("refresh_url")] = "/";
|
obj[F("refresh_url")] = "/";
|
||||||
obj[F("html")] = F("reboot. Autoreload after 10 seconds");
|
obj[F("html")] = F("reboot. Autoreload after 10 seconds");
|
||||||
|
@ -392,6 +393,7 @@ class RestApi {
|
||||||
obj[F("miso")] = mConfig->nrf.pinMiso;
|
obj[F("miso")] = mConfig->nrf.pinMiso;
|
||||||
obj[F("led0")] = mConfig->led.led0;
|
obj[F("led0")] = mConfig->led.led0;
|
||||||
obj[F("led1")] = mConfig->led.led1;
|
obj[F("led1")] = mConfig->led.led1;
|
||||||
|
obj[F("led_high_active")] = mConfig->led.led_high_active;
|
||||||
}
|
}
|
||||||
|
|
||||||
void getRadioCmt(JsonObject obj) {
|
void getRadioCmt(JsonObject obj) {
|
||||||
|
@ -438,8 +440,8 @@ class RestApi {
|
||||||
obj[F("disp_bsy")] = (mConfig->plugin.display.type < 10) ? DEF_PIN_OFF : mConfig->plugin.display.disp_busy;
|
obj[F("disp_bsy")] = (mConfig->plugin.display.type < 10) ? DEF_PIN_OFF : mConfig->plugin.display.disp_busy;
|
||||||
}
|
}
|
||||||
|
|
||||||
void getIndex(JsonObject obj) {
|
void getIndex(AsyncWebServerRequest *request, JsonObject obj) {
|
||||||
getGeneric(obj.createNestedObject(F("generic")));
|
getGeneric(request, obj.createNestedObject(F("generic")));
|
||||||
obj[F("ts_now")] = mApp->getTimestamp();
|
obj[F("ts_now")] = mApp->getTimestamp();
|
||||||
obj[F("ts_sunrise")] = mApp->getSunrise();
|
obj[F("ts_sunrise")] = mApp->getSunrise();
|
||||||
obj[F("ts_sunset")] = mApp->getSunset();
|
obj[F("ts_sunset")] = mApp->getSunset();
|
||||||
|
@ -487,9 +489,9 @@ class RestApi {
|
||||||
info.add(F("MQTT publishes in a fixed interval of ") + String(mConfig->mqtt.interval) + F(" seconds"));
|
info.add(F("MQTT publishes in a fixed interval of ") + String(mConfig->mqtt.interval) + F(" seconds"));
|
||||||
}
|
}
|
||||||
|
|
||||||
void getSetup(JsonObject obj) {
|
void getSetup(AsyncWebServerRequest *request, JsonObject obj) {
|
||||||
getGeneric(obj.createNestedObject(F("generic")));
|
getGeneric(request, obj.createNestedObject(F("generic")));
|
||||||
getSysInfo(obj.createNestedObject(F("system")));
|
getSysInfo(request, obj.createNestedObject(F("system")));
|
||||||
//getInverterList(obj.createNestedObject(F("inverter")));
|
//getInverterList(obj.createNestedObject(F("inverter")));
|
||||||
getMqtt(obj.createNestedObject(F("mqtt")));
|
getMqtt(obj.createNestedObject(F("mqtt")));
|
||||||
getNtp(obj.createNestedObject(F("ntp")));
|
getNtp(obj.createNestedObject(F("ntp")));
|
||||||
|
@ -506,8 +508,8 @@ class RestApi {
|
||||||
mApp->getAvailNetworks(obj);
|
mApp->getAvailNetworks(obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
void getLive(JsonObject obj) {
|
void getLive(AsyncWebServerRequest *request, JsonObject obj) {
|
||||||
getGeneric(obj.createNestedObject(F("generic")));
|
getGeneric(request, obj.createNestedObject(F("generic")));
|
||||||
obj[F("refresh")] = mConfig->nrf.sendInterval;
|
obj[F("refresh")] = mConfig->nrf.sendInterval;
|
||||||
|
|
||||||
for (uint8_t fld = 0; fld < sizeof(acList); fld++) {
|
for (uint8_t fld = 0; fld < sizeof(acList); fld++) {
|
||||||
|
|
|
@ -103,7 +103,7 @@ function parseVersion(obj) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseESP(obj) {
|
function parseESP(obj) {
|
||||||
document.getElementById("esp_type").append(
|
document.getElementById("esp_type").replaceChildren(
|
||||||
document.createTextNode("Board: " + obj["esp_type"])
|
document.createTextNode("Board: " + obj["esp_type"])
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
<link rel="stylesheet" type="text/css" href="colors.css"/>
|
<link rel="stylesheet" type="text/css" href="style.css?v={#VERSION}"/>
|
||||||
<link rel="stylesheet" type="text/css" href="style.css"/>
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<script type="text/javascript" src="api.js"></script>
|
<script type="text/javascript" src="api.js?v={#VERSION}"></script>
|
||||||
|
<link rel="stylesheet" type="text/css" href="colors.css?v={#VERSION}"/>
|
||||||
|
<meta name="robots" content="noindex, nofollow" />
|
||||||
|
|
|
@ -1,21 +1,21 @@
|
||||||
<div class="topnav">
|
<div class="topnav">
|
||||||
<a href="/" class="title">AhoyDTU</a>
|
<a href="/?v={#VERSION}" class="title">AhoyDTU</a>
|
||||||
<a href="javascript:void(0);" class="icon" onclick="topnav()">
|
<a href="javascript:void(0);" class="icon" onclick="topnav()">
|
||||||
<span></span>
|
<span></span>
|
||||||
<span></span>
|
<span></span>
|
||||||
<span></span>
|
<span></span>
|
||||||
</a>
|
</a>
|
||||||
<div id="topnav" class="mobile">
|
<div id="topnav" class="mobile">
|
||||||
<a id="nav3" class="hide" href="/live">Live</a>
|
<a id="nav3" class="hide" href="/live?v={#VERSION}">Live</a>
|
||||||
<a id="nav4" class="hide" href="/serial">Serial / Control</a>
|
<a id="nav4" class="hide" href="/serial?v={#VERSION}">Serial / Control</a>
|
||||||
<a id="nav5" class="hide" href="/setup">Settings</a>
|
<a id="nav5" class="hide" href="/setup?v={#VERSION}">Settings</a>
|
||||||
<span class="seperator"></span>
|
<span class="seperator"></span>
|
||||||
<a id="nav6" class="hide" href="/update">Update</a>
|
<a id="nav6" class="hide" href="/update?v={#VERSION}">Update</a>
|
||||||
<a id="nav7" class="hide" href="/system">System</a>
|
<a id="nav7" class="hide" href="/system?v={#VERSION}">System</a>
|
||||||
<span class="seperator"></span>
|
<span class="seperator"></span>
|
||||||
<a id="nav8" href="/api" target="_blank">REST API</a>
|
<a id="nav8" href="/api" target="_blank">REST API</a>
|
||||||
<a id="nav9" href="https://ahoydtu.de" target="_blank">Documentation</a>
|
<a id="nav9" href="https://ahoydtu.de" target="_blank">Documentation</a>
|
||||||
<a id="nav10" href="/about">About</a>
|
<a id="nav10" href="/about?v={#VERSION}">About</a>
|
||||||
<span class="seperator"></span>
|
<span class="seperator"></span>
|
||||||
<a id="nav0" class="hide" href="/login">Login</a>
|
<a id="nav0" class="hide" href="/login">Login</a>
|
||||||
<a id="nav1" class="hide" href="/logout">Logout</a>
|
<a id="nav1" class="hide" href="/logout">Logout</a>
|
||||||
|
|
|
@ -11,8 +11,8 @@
|
||||||
<form action="/login" method="post">
|
<form action="/login" method="post">
|
||||||
<div class="row"><h2>AhoyDTU</h2></div>
|
<div class="row"><h2>AhoyDTU</h2></div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-8"><input type="password" name="pwd" autofocus></div>
|
<div class="col-12 col-sm-8 mb-3"><input type="password" name="pwd" autofocus></div>
|
||||||
<div class="col-4"><input type="submit" name="login" value="login" class="btn"></div>
|
<div class="col-12 col-sm-4"><input type="submit" name="login" value="login" class="btn"></div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -8,11 +8,13 @@
|
||||||
{#HTML_NAV}
|
{#HTML_NAV}
|
||||||
<div id="wrapper">
|
<div id="wrapper">
|
||||||
<div id="content">
|
<div id="content">
|
||||||
<div id="html" class="mt-3 mb-3"></div>
|
<div id="html" class="mt-3 mb-3">Saving settings...</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{#HTML_FOOTER}
|
{#HTML_FOOTER}
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
|
var intervalId = null;
|
||||||
|
|
||||||
function parseGeneric(obj) {
|
function parseGeneric(obj) {
|
||||||
parseNav(obj);
|
parseNav(obj);
|
||||||
parseESP(obj);
|
parseESP(obj);
|
||||||
|
@ -21,18 +23,24 @@
|
||||||
|
|
||||||
function parseHtml(obj) {
|
function parseHtml(obj) {
|
||||||
var html = "";
|
var html = "";
|
||||||
if(obj.pending)
|
if(!obj.pending) {
|
||||||
html = "saving settings ...";
|
if(intervalId != null) {
|
||||||
else {
|
clearInterval(intervalId);
|
||||||
if(obj.success)
|
}
|
||||||
html = "settings successfully saved";
|
if(obj.success) {
|
||||||
else
|
|
||||||
html = "failed saving settings";
|
|
||||||
|
|
||||||
var meta = document.createElement('meta');
|
var meta = document.createElement('meta');
|
||||||
meta.httpEquiv = "refresh"
|
meta.httpEquiv = "refresh"
|
||||||
meta.content = 1 + "; URL=/setup";
|
if(!obj.reboot) {
|
||||||
|
html = "Settings successfully saved. Automatic page reload in 3 seconds.";
|
||||||
|
meta.content = 3;
|
||||||
|
} else {
|
||||||
|
html = "Settings successfully saved. Rebooting. Automatic redirect in 20 seconds.";
|
||||||
|
meta.content = 20 + "; URL=/";
|
||||||
|
}
|
||||||
document.getElementsByTagName('head')[0].appendChild(meta);
|
document.getElementsByTagName('head')[0].appendChild(meta);
|
||||||
|
} else {
|
||||||
|
html = "Failed saving settings.";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
document.getElementById("html").innerHTML = html;
|
document.getElementById("html").innerHTML = html;
|
||||||
}
|
}
|
||||||
|
@ -41,11 +49,9 @@
|
||||||
if(null != obj) {
|
if(null != obj) {
|
||||||
parseGeneric(obj["generic"]);
|
parseGeneric(obj["generic"]);
|
||||||
parseHtml(obj);
|
parseHtml(obj);
|
||||||
window.setInterval("getAjax('/api/html/save', parse)", 1100);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
intervalId = window.setInterval("getAjax('/api/html/save', parse)", 2500);
|
||||||
getAjax("/api/html/save", parse);
|
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html>
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<title>Serial Console</title>
|
<title>Serial Console</title>
|
||||||
{#HTML_HEADER}
|
{#HTML_HEADER}
|
||||||
|
|
|
@ -1,25 +1,14 @@
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html>
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<title>Setup</title>
|
<title>Setup</title>
|
||||||
{#HTML_HEADER}
|
{#HTML_HEADER}
|
||||||
<script type="text/javascript">
|
|
||||||
function load() {
|
|
||||||
for(it of document.getElementsByClassName("s_collapsible")) {
|
|
||||||
it.addEventListener("click", function() {
|
|
||||||
this.classList.toggle("active");
|
|
||||||
var content = this.nextElementSibling;
|
|
||||||
content.style.display = (content.style.display === "block") ? "none" : "block";
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</head>
|
</head>
|
||||||
<body onload="load()">
|
<body>
|
||||||
{#HTML_NAV}
|
{#HTML_NAV}
|
||||||
<div id="wrapper">
|
<div id="wrapper">
|
||||||
<div id="content">
|
<div id="content">
|
||||||
<form method="post" action="/save">
|
<form method="post" action="/save" id="settings">
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<button type="button" class="s_collapsible mt-4">System Config</button>
|
<button type="button" class="s_collapsible mt-4">System Config</button>
|
||||||
<div class="s_content">
|
<div class="s_content">
|
||||||
|
@ -32,6 +21,7 @@
|
||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
<div class="col-8 col-sm-3">Dark Mode</div>
|
<div class="col-8 col-sm-3">Dark Mode</div>
|
||||||
<div class="col-4 col-sm-9"><input type="checkbox" name="darkMode"/></div>
|
<div class="col-4 col-sm-9"><input type="checkbox" name="darkMode"/></div>
|
||||||
|
<div class="col-8 col-sm-3">(empty browser cache or use CTRL + F5 after reboot to apply this setting)</div>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<fieldset class="mb-4">
|
<fieldset class="mb-4">
|
||||||
|
@ -150,11 +140,11 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
<div class="col-12 col-sm-3 my-2">Interval [s]</div>
|
<div class="col-12 col-sm-3 my-2">Interval [s]</div>
|
||||||
<div class="col-12 col-sm-9"><input type="text" name="invInterval" pattern="[0-9]+" title="Invalid input"/></div>
|
<div class="col-12 col-sm-9"><input type="number" name="invInterval" title="Invalid input"/></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
<div class="col-12 col-sm-3 my-2">Max retries per Payload</div>
|
<div class="col-12 col-sm-3 my-2">Max retries per Payload</div>
|
||||||
<div class="col-12 col-sm-9"><input type="text" name="invRetry"/></div>
|
<div class="col-12 col-sm-9"><input type="number" name="invRetry"/></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
<div class="col-8 col-sm-3 mb-2">Reset values and YieldDay at midnight</div>
|
<div class="col-8 col-sm-3 mb-2">Reset values and YieldDay at midnight</div>
|
||||||
|
@ -181,7 +171,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
<div class="col-12 col-sm-3 my-2">NTP Port</div>
|
<div class="col-12 col-sm-3 my-2">NTP Port</div>
|
||||||
<div class="col-12 col-sm-9"><input type="text" name="ntpPort"/></div>
|
<div class="col-12 col-sm-9"><input type="number" name="ntpPort"/></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
<div class="col-12 col-sm-3 my-2">set system time</div>
|
<div class="col-12 col-sm-3 my-2">set system time</div>
|
||||||
|
@ -198,15 +188,13 @@
|
||||||
<div class="s_content">
|
<div class="s_content">
|
||||||
<fieldset class="mb-4">
|
<fieldset class="mb-4">
|
||||||
<legend class="des">Sunrise & Sunset</legend>
|
<legend class="des">Sunrise & Sunset</legend>
|
||||||
<p>Use a decimal separator: '.' (dot) for Latitude and Longitude</p>
|
|
||||||
|
|
||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
<div class="col-12 col-sm-3 my-2">Latitude (decimal)</div>
|
<div class="col-12 col-sm-3 my-2">Latitude (decimal)</div>
|
||||||
<div class="col-12 col-sm-9"><input type="text" name="sunLat"/></div>
|
<div class="col-12 col-sm-9"><input type="number" name="sunLat" step="any"/></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
<div class="col-12 col-sm-3 my-2">Longitude (decimal)</div>
|
<div class="col-12 col-sm-3 my-2">Longitude (decimal)</div>
|
||||||
<div class="col-12 col-sm-9"><input type="text" name="sunLon"/></div>
|
<div class="col-12 col-sm-9"><input type="number" name="sunLon" step="any"/></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
<div class="col-12 col-sm-3 my-2">Offset (pre sunrise, post sunset)</div>
|
<div class="col-12 col-sm-3 my-2">Offset (pre sunrise, post sunset)</div>
|
||||||
|
@ -229,7 +217,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
<div class="col-12 col-sm-3 my-2">Port</div>
|
<div class="col-12 col-sm-3 my-2">Port</div>
|
||||||
<div class="col-12 col-sm-9"><input type="text" name="mqttPort"/></div>
|
<div class="col-12 col-sm-9"><input type="number" name="mqttPort"/></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
<div class="col-12 col-sm-3 my-2">Username (optional)</div>
|
<div class="col-12 col-sm-3 my-2">Username (optional)</div>
|
||||||
|
@ -246,7 +234,7 @@
|
||||||
<p class="des">Send Inverter data in a fixed interval, even if there is no change. A value of '0' disables the fixed interval. The data is published once it was successfully received from inverter. (default: 0)</p>
|
<p class="des">Send Inverter data in a fixed interval, even if there is no change. A value of '0' disables the fixed interval. The data is published once it was successfully received from inverter. (default: 0)</p>
|
||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
<div class="col-12 col-sm-3 my-2">Interval [s]</div>
|
<div class="col-12 col-sm-3 my-2">Interval [s]</div>
|
||||||
<div class="col-12 col-sm-9"><input type="text" name="mqttInterval" pattern="[0-9]+" title="Invalid input" /></div>
|
<div class="col-12 col-sm-9"><input type="number" name="mqttInterval" title="Invalid input" /></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
<div class="col-12 col-sm-3 my-2">Discovery Config (homeassistant)</div>
|
<div class="col-12 col-sm-3 my-2">Discovery Config (homeassistant)</div>
|
||||||
|
@ -295,11 +283,13 @@
|
||||||
<fieldset class="mb-4">
|
<fieldset class="mb-4">
|
||||||
<legend class="des">Import / Export JSON Settings</legend>
|
<legend class="des">Import / Export JSON Settings</legend>
|
||||||
<div class="row mb-4 mt-4">
|
<div class="row mb-4 mt-4">
|
||||||
<div class="col-12 col-sm-3 my-2">Import</div>
|
<div class="col-12 col-sm-3">Import</div>
|
||||||
<div class="col-12 col-sm-9">
|
<div class="col-12 col-sm-9">
|
||||||
<form id="form" method="POST" action="/upload" enctype="multipart/form-data" accept-charset="utf-8">
|
<form id="form" method="POST" action="/upload" enctype="multipart/form-data" accept-charset="utf-8">
|
||||||
<input type="file" name="upload">
|
<div class="row">
|
||||||
<input type="button" class="btn" value="Import" onclick="hide()">
|
<div class="col-12 col-sm-8 my-2"><input type="file" name="upload"></div>
|
||||||
|
<div class="col-12 col-sm-4 my-2"><input type="button" class="btn" value="Import" onclick="hide()"></div>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -415,9 +405,31 @@
|
||||||
[47, "GPIO47"],
|
[47, "GPIO47"],
|
||||||
[48, "GPIO48"],
|
[48, "GPIO48"],
|
||||||
];
|
];
|
||||||
|
var led_high_active = [
|
||||||
|
[0, "low active"],
|
||||||
|
[1, "high active"],
|
||||||
|
];
|
||||||
|
|
||||||
const re = /11[2,4,6][1,2,4].*/;
|
const re = /11[2,4,6][1,2,4].*/;
|
||||||
|
|
||||||
|
window.onload = function() {
|
||||||
|
for(it of document.getElementsByClassName("s_collapsible")) {
|
||||||
|
it.addEventListener("click", function() {
|
||||||
|
this.classList.toggle("active");
|
||||||
|
var content = this.nextElementSibling;
|
||||||
|
content.style.display = (content.style.display === "block") ? "none" : "block";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById("settings").addEventListener("submit", function() {
|
||||||
|
var inputs = document.querySelectorAll("input[type='number']");
|
||||||
|
for (var i = 0; i < inputs.length; i++) {
|
||||||
|
if (inputs[i].value.indexOf(",") != -1)
|
||||||
|
inputs[i].value = inputs[i].value.replace(",", ".");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
document.getElementById("btnAdd").addEventListener("click", function() {
|
document.getElementById("btnAdd").addEventListener("click", function() {
|
||||||
if(highestId <= (maxInv-1)) {
|
if(highestId <= (maxInv-1)) {
|
||||||
ivHtml(JSON.parse('{"enabled":true,"name":"","serial":"","channels":4,"ch_max_pwr":[0,0,0,0],"ch_name":["","","",""],"ch_yield_cor":[0,0,0,0]}'), highestId);
|
ivHtml(JSON.parse('{"enabled":true,"name":"","serial":"","channels":4,"ch_max_pwr":[0,0,0,0],"ch_name":["","","",""],"ch_yield_cor":[0,0,0,0]}'), highestId);
|
||||||
|
@ -674,6 +686,14 @@
|
||||||
])
|
])
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
e.append(
|
||||||
|
ml("div", { class: "row mb-3" }, [
|
||||||
|
ml("div", { class: "col-12 col-sm-3 my-2" }, "LED polarity"),
|
||||||
|
ml("div", { class: "col-12 col-sm-9" },
|
||||||
|
sel('pinLedHighActive', led_high_active, obj['led_high_active'])
|
||||||
|
)
|
||||||
|
])
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseNrfRadio(obj, type, system) {
|
function parseNrfRadio(obj, type, system) {
|
||||||
|
|
|
@ -92,7 +92,7 @@ svg.icon {
|
||||||
.title {
|
.title {
|
||||||
background-color: var(--primary);
|
background-color: var(--primary);
|
||||||
color: #fff !important;
|
color: #fff !important;
|
||||||
padding-left: 80px !important
|
padding: 15px 14px 16px 80px !important
|
||||||
}
|
}
|
||||||
|
|
||||||
.topnav .icon span {
|
.topnav .icon span {
|
||||||
|
@ -546,6 +546,18 @@ div.hr {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#login {
|
||||||
|
width: 450px;
|
||||||
|
height: 200px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
background-color: var(--input-bg);
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
margin-top: -160px;
|
||||||
|
margin-left: -225px;
|
||||||
|
}
|
||||||
|
|
||||||
@media(max-width: 500px) {
|
@media(max-width: 500px) {
|
||||||
div.ch .unit, div.ch-iv .unit {
|
div.ch .unit, div.ch-iv .unit {
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
|
@ -559,6 +571,11 @@ div.hr {
|
||||||
.subgrp {
|
.subgrp {
|
||||||
width: 180px;
|
width: 180px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#login {
|
||||||
|
margin-left: -150px;
|
||||||
|
width: 300px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#serial {
|
#serial {
|
||||||
|
@ -578,18 +595,6 @@ div.hr {
|
||||||
margin-top: 15px;
|
margin-top: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#login {
|
|
||||||
width: 450px;
|
|
||||||
height: 200px;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
background-color: var(--input-bg);
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
margin-top: -160px;
|
|
||||||
margin-left: -225px;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.head {
|
.head {
|
||||||
background-color: var(--primary);
|
background-color: var(--primary);
|
||||||
|
|
166
src/web/web.h
166
src/web/web.h
|
@ -34,7 +34,7 @@
|
||||||
|
|
||||||
#define WEB_SERIAL_BUF_SIZE 2048
|
#define WEB_SERIAL_BUF_SIZE 2048
|
||||||
|
|
||||||
const char* const pinArgNames[] = {"pinCs", "pinCe", "pinIrq", "pinSclk", "pinMosi", "pinMiso", "pinLed0", "pinLed1", "pinCsb", "pinFcsb", "pinGpio3"};
|
const char* const pinArgNames[] = {"pinCs", "pinCe", "pinIrq", "pinSclk", "pinMosi", "pinMiso", "pinLed0", "pinLed1", "pinLedHighActive", "pinCsb", "pinFcsb", "pinGpio3"};
|
||||||
|
|
||||||
template <class HMSYSTEM>
|
template <class HMSYSTEM>
|
||||||
class Web {
|
class Web {
|
||||||
|
@ -126,8 +126,21 @@ class Web {
|
||||||
mProtected = protect;
|
mProtected = protect;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool getProtection() {
|
bool isProtected(AsyncWebServerRequest *request) {
|
||||||
return mProtected;
|
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) {
|
void showUpdate2(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) {
|
||||||
|
@ -216,7 +229,7 @@ class Web {
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void checkRedirect(AsyncWebServerRequest *request) {
|
inline void checkRedirect(AsyncWebServerRequest *request) {
|
||||||
if ((mConfig->sys.protectionMask & PROT_MASK_INDEX) != PROT_MASK_INDEX)
|
if ((mConfig->sys.protectionMask & PROT_MASK_INDEX) != PROT_MASK_INDEX)
|
||||||
request->redirect(F("/index"));
|
request->redirect(F("/index"));
|
||||||
else if ((mConfig->sys.protectionMask & PROT_MASK_LIVE) != PROT_MASK_LIVE)
|
else if ((mConfig->sys.protectionMask & PROT_MASK_LIVE) != PROT_MASK_LIVE)
|
||||||
|
@ -229,21 +242,29 @@ class Web {
|
||||||
request->redirect(F("/login"));
|
request->redirect(F("/login"));
|
||||||
}
|
}
|
||||||
|
|
||||||
void onUpdate(AsyncWebServerRequest *request) {
|
void checkProtection(AsyncWebServerRequest *request) {
|
||||||
DPRINTLN(DBG_VERBOSE, F("onUpdate"));
|
if(isProtected(request)) {
|
||||||
|
|
||||||
if (CHECK_MASK(mConfig->sys.protectionMask, PROT_MASK_UPDATE)) {
|
|
||||||
if (mProtected) {
|
|
||||||
checkRedirect(request);
|
checkRedirect(request);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html; charset=UTF-8"), update_html, update_html_len);
|
void getPage(AsyncWebServerRequest *request, uint8_t mask, const uint8_t *zippedHtml, uint32_t len) {
|
||||||
|
if (CHECK_MASK(mConfig->sys.protectionMask, mask))
|
||||||
|
checkProtection(request);
|
||||||
|
|
||||||
|
AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html; charset=UTF-8"), zippedHtml, len);
|
||||||
response->addHeader(F("Content-Encoding"), "gzip");
|
response->addHeader(F("Content-Encoding"), "gzip");
|
||||||
|
response->addHeader(F("content-type"), "text/html; charset=UTF-8");
|
||||||
|
if(request->hasParam("v"))
|
||||||
|
response->addHeader(F("Cache-Control"), F("max-age=604800"));
|
||||||
request->send(response);
|
request->send(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void onUpdate(AsyncWebServerRequest *request) {
|
||||||
|
getPage(request, PROT_MASK_UPDATE, update_html, update_html_len);
|
||||||
|
}
|
||||||
|
|
||||||
void showUpdate(AsyncWebServerRequest *request) {
|
void showUpdate(AsyncWebServerRequest *request) {
|
||||||
bool reboot = (!Update.hasError());
|
bool reboot = (!Update.hasError());
|
||||||
|
|
||||||
|
@ -288,18 +309,7 @@ class Web {
|
||||||
}
|
}
|
||||||
|
|
||||||
void onIndex(AsyncWebServerRequest *request) {
|
void onIndex(AsyncWebServerRequest *request) {
|
||||||
DPRINTLN(DBG_VERBOSE, F("onIndex"));
|
getPage(request, PROT_MASK_INDEX, index_html, index_html_len);
|
||||||
|
|
||||||
if (CHECK_MASK(mConfig->sys.protectionMask, PROT_MASK_INDEX)) {
|
|
||||||
if (mProtected) {
|
|
||||||
checkRedirect(request);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html; charset=UTF-8"), index_html, index_html_len);
|
|
||||||
response->addHeader(F("Content-Encoding"), "gzip");
|
|
||||||
request->send(response);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void onLogin(AsyncWebServerRequest *request) {
|
void onLogin(AsyncWebServerRequest *request) {
|
||||||
|
@ -308,6 +318,7 @@ class Web {
|
||||||
if (request->args() > 0) {
|
if (request->args() > 0) {
|
||||||
if (String(request->arg("pwd")) == String(mConfig->sys.adminPwd)) {
|
if (String(request->arg("pwd")) == String(mConfig->sys.adminPwd)) {
|
||||||
mProtected = false;
|
mProtected = false;
|
||||||
|
ah::ip2Arr(mLoginIp, request->client()->remoteIP().toString().c_str());
|
||||||
request->redirect("/");
|
request->redirect("/");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -320,10 +331,7 @@ class Web {
|
||||||
void onLogout(AsyncWebServerRequest *request) {
|
void onLogout(AsyncWebServerRequest *request) {
|
||||||
DPRINTLN(DBG_VERBOSE, F("onLogout"));
|
DPRINTLN(DBG_VERBOSE, F("onLogout"));
|
||||||
|
|
||||||
if (mProtected) {
|
checkProtection(request);
|
||||||
checkRedirect(request);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
mProtected = true;
|
mProtected = true;
|
||||||
|
|
||||||
|
@ -340,6 +348,9 @@ class Web {
|
||||||
else
|
else
|
||||||
response = request->beginResponse_P(200, F("text/css"), colorBright_css, colorBright_css_len);
|
response = request->beginResponse_P(200, F("text/css"), colorBright_css, colorBright_css_len);
|
||||||
response->addHeader(F("Content-Encoding"), "gzip");
|
response->addHeader(F("Content-Encoding"), "gzip");
|
||||||
|
if(request->hasParam("v")) {
|
||||||
|
response->addHeader(F("Cache-Control"), F("max-age=604800"));
|
||||||
|
}
|
||||||
request->send(response);
|
request->send(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -348,6 +359,9 @@ class Web {
|
||||||
mLogoutTimeout = LOGOUT_TIMEOUT;
|
mLogoutTimeout = LOGOUT_TIMEOUT;
|
||||||
AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/css"), style_css, style_css_len);
|
AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/css"), style_css, style_css_len);
|
||||||
response->addHeader(F("Content-Encoding"), "gzip");
|
response->addHeader(F("Content-Encoding"), "gzip");
|
||||||
|
if(request->hasParam("v")) {
|
||||||
|
response->addHeader(F("Cache-Control"), F("max-age=604800"));
|
||||||
|
}
|
||||||
request->send(response);
|
request->send(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -356,6 +370,8 @@ class Web {
|
||||||
|
|
||||||
AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/javascript"), api_js, api_js_len);
|
AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/javascript"), api_js, api_js_len);
|
||||||
response->addHeader(F("Content-Encoding"), "gzip");
|
response->addHeader(F("Content-Encoding"), "gzip");
|
||||||
|
if(request->hasParam("v"))
|
||||||
|
response->addHeader(F("Cache-Control"), F("max-age=604800"));
|
||||||
request->send(response);
|
request->send(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -367,9 +383,7 @@ class Web {
|
||||||
}
|
}
|
||||||
|
|
||||||
void showNotFound(AsyncWebServerRequest *request) {
|
void showNotFound(AsyncWebServerRequest *request) {
|
||||||
if (mProtected)
|
checkProtection(request);
|
||||||
checkRedirect(request);
|
|
||||||
else
|
|
||||||
request->redirect("/setup");
|
request->redirect("/setup");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -381,10 +395,7 @@ class Web {
|
||||||
}
|
}
|
||||||
|
|
||||||
void showErase(AsyncWebServerRequest *request) {
|
void showErase(AsyncWebServerRequest *request) {
|
||||||
if (mProtected) {
|
checkProtection(request);
|
||||||
checkRedirect(request);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
DPRINTLN(DBG_VERBOSE, F("showErase"));
|
DPRINTLN(DBG_VERBOSE, F("showErase"));
|
||||||
mApp->eraseSettings(false);
|
mApp->eraseSettings(false);
|
||||||
|
@ -392,10 +403,7 @@ class Web {
|
||||||
}
|
}
|
||||||
|
|
||||||
void showFactoryRst(AsyncWebServerRequest *request) {
|
void showFactoryRst(AsyncWebServerRequest *request) {
|
||||||
if (mProtected) {
|
checkProtection(request);
|
||||||
checkRedirect(request);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
DPRINTLN(DBG_VERBOSE, F("showFactoryRst"));
|
DPRINTLN(DBG_VERBOSE, F("showFactoryRst"));
|
||||||
String content = "";
|
String content = "";
|
||||||
|
@ -422,27 +430,13 @@ class Web {
|
||||||
}
|
}
|
||||||
|
|
||||||
void onSetup(AsyncWebServerRequest *request) {
|
void onSetup(AsyncWebServerRequest *request) {
|
||||||
DPRINTLN(DBG_VERBOSE, F("onSetup"));
|
getPage(request, PROT_MASK_SETUP, setup_html, setup_html_len);
|
||||||
|
|
||||||
if (CHECK_MASK(mConfig->sys.protectionMask, PROT_MASK_SETUP)) {
|
|
||||||
if (mProtected) {
|
|
||||||
checkRedirect(request);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html; charset=UTF-8"), setup_html, setup_html_len);
|
|
||||||
response->addHeader(F("Content-Encoding"), "gzip");
|
|
||||||
request->send(response);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void showSave(AsyncWebServerRequest *request) {
|
void showSave(AsyncWebServerRequest *request) {
|
||||||
DPRINTLN(DBG_VERBOSE, F("showSave"));
|
DPRINTLN(DBG_VERBOSE, F("showSave"));
|
||||||
|
|
||||||
if (mProtected) {
|
checkProtection(request);
|
||||||
checkRedirect(request);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (request->args() == 0)
|
if (request->args() == 0)
|
||||||
return;
|
return;
|
||||||
|
@ -521,7 +515,7 @@ class Web {
|
||||||
|
|
||||||
// pinout
|
// pinout
|
||||||
uint8_t pin;
|
uint8_t pin;
|
||||||
for (uint8_t i = 0; i < 11; i++) {
|
for (uint8_t i = 0; i < 12; i++) {
|
||||||
pin = request->arg(String(pinArgNames[i])).toInt();
|
pin = request->arg(String(pinArgNames[i])).toInt();
|
||||||
switch(i) {
|
switch(i) {
|
||||||
case 0: mConfig->nrf.pinCs = ((pin != 0xff) ? pin : DEF_CS_PIN); break;
|
case 0: mConfig->nrf.pinCs = ((pin != 0xff) ? pin : DEF_CS_PIN); break;
|
||||||
|
@ -532,9 +526,10 @@ class Web {
|
||||||
case 5: mConfig->nrf.pinMiso = ((pin != 0xff) ? pin : DEF_MISO_PIN); break;
|
case 5: mConfig->nrf.pinMiso = ((pin != 0xff) ? pin : DEF_MISO_PIN); break;
|
||||||
case 6: mConfig->led.led0 = pin; break;
|
case 6: mConfig->led.led0 = pin; break;
|
||||||
case 7: mConfig->led.led1 = pin; break;
|
case 7: mConfig->led.led1 = pin; break;
|
||||||
case 8: mConfig->cmt.pinCsb = pin; break;
|
case 8: mConfig->led.led_high_active = pin; break; // this is not really a pin but a polarity, but handling it close to here makes sense
|
||||||
case 9: mConfig->cmt.pinFcsb = pin; break;
|
case 9: mConfig->cmt.pinCsb = pin; break;
|
||||||
case 10: mConfig->cmt.pinIrq = pin; break;
|
case 10: mConfig->cmt.pinFcsb = pin; break;
|
||||||
|
case 11: mConfig->cmt.pinIrq = pin; break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -609,33 +604,16 @@ class Web {
|
||||||
}
|
}
|
||||||
|
|
||||||
void onLive(AsyncWebServerRequest *request) {
|
void onLive(AsyncWebServerRequest *request) {
|
||||||
DPRINTLN(DBG_VERBOSE, F("onLive"));
|
getPage(request, PROT_MASK_LIVE, visualization_html, visualization_html_len);
|
||||||
|
|
||||||
if (CHECK_MASK(mConfig->sys.protectionMask, PROT_MASK_LIVE)) {
|
|
||||||
if (mProtected) {
|
|
||||||
checkRedirect(request);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html; charset=UTF-8"), visualization_html, visualization_html_len);
|
|
||||||
response->addHeader(F("Content-Encoding"), "gzip");
|
|
||||||
response->addHeader(F("content-type"), "text/html; charset=UTF-8");
|
|
||||||
|
|
||||||
request->send(response);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void onAbout(AsyncWebServerRequest *request) {
|
void onAbout(AsyncWebServerRequest *request) {
|
||||||
if (CHECK_MASK(mConfig->sys.protectionMask, PROT_MASK_LIVE)) {
|
|
||||||
if (mProtected) {
|
|
||||||
checkRedirect(request);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html; charset=UTF-8"), about_html, about_html_len);
|
AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html; charset=UTF-8"), about_html, about_html_len);
|
||||||
response->addHeader(F("Content-Encoding"), "gzip");
|
response->addHeader(F("Content-Encoding"), "gzip");
|
||||||
response->addHeader(F("content-type"), "text/html; charset=UTF-8");
|
response->addHeader(F("content-type"), "text/html; charset=UTF-8");
|
||||||
|
if(request->hasParam("v")) {
|
||||||
|
response->addHeader(F("Cache-Control"), F("max-age=604800"));
|
||||||
|
}
|
||||||
|
|
||||||
request->send(response);
|
request->send(response);
|
||||||
}
|
}
|
||||||
|
@ -647,33 +625,11 @@ class Web {
|
||||||
}
|
}
|
||||||
|
|
||||||
void onSerial(AsyncWebServerRequest *request) {
|
void onSerial(AsyncWebServerRequest *request) {
|
||||||
DPRINTLN(DBG_VERBOSE, F("onSerial"));
|
getPage(request, PROT_MASK_SERIAL, serial_html, serial_html_len);
|
||||||
|
|
||||||
if (CHECK_MASK(mConfig->sys.protectionMask, PROT_MASK_SERIAL)) {
|
|
||||||
if (mProtected) {
|
|
||||||
checkRedirect(request);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html; charset=UTF-8"), serial_html, serial_html_len);
|
|
||||||
response->addHeader(F("Content-Encoding"), "gzip");
|
|
||||||
request->send(response);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void onSystem(AsyncWebServerRequest *request) {
|
void onSystem(AsyncWebServerRequest *request) {
|
||||||
DPRINTLN(DBG_VERBOSE, F("onSystem"));
|
getPage(request, PROT_MASK_SYSTEM, system_html, system_html_len);
|
||||||
|
|
||||||
if (CHECK_MASK(mConfig->sys.protectionMask, PROT_MASK_SYSTEM)) {
|
|
||||||
if (mProtected) {
|
|
||||||
checkRedirect(request);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html; charset=UTF-8"), system_html, system_html_len);
|
|
||||||
response->addHeader(F("Content-Encoding"), "gzip");
|
|
||||||
request->send(response);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -773,6 +729,8 @@ class Web {
|
||||||
rec = iv->getRecordStruct(RealTimeRunData_Debug);
|
rec = iv->getRecordStruct(RealTimeRunData_Debug);
|
||||||
if (metricsChannelId < rec->length) {
|
if (metricsChannelId < rec->length) {
|
||||||
uint8_t channel = rec->assign[metricsChannelId].ch;
|
uint8_t channel = rec->assign[metricsChannelId].ch;
|
||||||
|
// Skip entry if maxPwr is 0 and it's not the inverter channel (channel 0)
|
||||||
|
if (0 == channel || 0 != iv->config->chMaxPwr[channel-1]) {
|
||||||
std::tie(promUnit, promType) = convertToPromUnits(iv->getUnit(metricsChannelId, rec));
|
std::tie(promUnit, promType) = convertToPromUnits(iv->getUnit(metricsChannelId, rec));
|
||||||
snprintf(type, sizeof(type), "# TYPE ahoy_solar_%s%s %s", iv->getFieldName(metricsChannelId, rec), promUnit.c_str(), promType.c_str());
|
snprintf(type, sizeof(type), "# TYPE ahoy_solar_%s%s %s", iv->getFieldName(metricsChannelId, rec), promUnit.c_str(), promType.c_str());
|
||||||
if (0 == channel) {
|
if (0 == channel) {
|
||||||
|
@ -782,6 +740,9 @@ class Web {
|
||||||
}
|
}
|
||||||
snprintf(val, sizeof(val), "%.3f", iv->getValue(metricsChannelId, rec));
|
snprintf(val, sizeof(val), "%.3f", iv->getValue(metricsChannelId, rec));
|
||||||
len = snprintf((char*)buffer,maxLen,"%s\n%s %s\n",type,topic,val);
|
len = snprintf((char*)buffer,maxLen,"%s\n%s %s\n",type,topic,val);
|
||||||
|
} else {
|
||||||
|
len = snprintf((char*)buffer,maxLen,"#\n"); // At least one char to send otherwise the transmission ends.
|
||||||
|
}
|
||||||
|
|
||||||
metricsChannelId++;
|
metricsChannelId++;
|
||||||
} else {
|
} else {
|
||||||
|
@ -849,6 +810,7 @@ class Web {
|
||||||
AsyncEventSource mEvts;
|
AsyncEventSource mEvts;
|
||||||
bool mProtected;
|
bool mProtected;
|
||||||
uint32_t mLogoutTimeout;
|
uint32_t mLogoutTimeout;
|
||||||
|
uint8_t mLoginIp[4];
|
||||||
IApp *mApp;
|
IApp *mApp;
|
||||||
HMSYSTEM *mSys;
|
HMSYSTEM *mSys;
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
// 2023 Ahoy, https://www.mikrocontroller.net/topic/525778
|
// 2023 Ahoy, https://ahoydtu.de
|
||||||
// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/
|
// Creative Commons - https://creativecommons.org/licenses/by-nc-sa/4.0/deed
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
|
|
||||||
#if defined(ESP32) && defined(F)
|
#if defined(ESP32) && defined(F)
|
||||||
|
@ -31,6 +31,7 @@ void ahoywifi::setup(settings_t *config, uint32_t *utcTimestamp, appWifiCb cb) {
|
||||||
mStaConn = DISCONNECTED;
|
mStaConn = DISCONNECTED;
|
||||||
mCnt = 0;
|
mCnt = 0;
|
||||||
mScanActive = false;
|
mScanActive = false;
|
||||||
|
mScanCnt = 0;
|
||||||
|
|
||||||
#if defined(ESP8266)
|
#if defined(ESP8266)
|
||||||
wifiConnectHandler = WiFi.onStationModeConnected(std::bind(&ahoywifi::onConnect, this, std::placeholders::_1));
|
wifiConnectHandler = WiFi.onStationModeConnected(std::bind(&ahoywifi::onConnect, this, std::placeholders::_1));
|
||||||
|
@ -115,7 +116,7 @@ void ahoywifi::tickWifiLoop() {
|
||||||
DBGPRINTLN(F(" seconds"));
|
DBGPRINTLN(F(" seconds"));
|
||||||
if(mScanActive) {
|
if(mScanActive) {
|
||||||
getBSSIDs();
|
getBSSIDs();
|
||||||
if(!mScanActive) // scan completed
|
if((!mScanActive) && (!mBSSIDList.empty())) // scan completed
|
||||||
if ((mCnt % timeout) < timeout - 2)
|
if ((mCnt % timeout) < timeout - 2)
|
||||||
mCnt = timeout - 2;
|
mCnt = timeout - 2;
|
||||||
}
|
}
|
||||||
|
@ -166,6 +167,9 @@ void ahoywifi::setupAp(void) {
|
||||||
DBGPRINTLN(mApIp.toString());
|
DBGPRINTLN(mApIp.toString());
|
||||||
DBGPRINTLN(F("---------\n"));
|
DBGPRINTLN(F("---------\n"));
|
||||||
|
|
||||||
|
if(String(mConfig->sys.deviceName) != "")
|
||||||
|
WiFi.hostname(mConfig->sys.deviceName);
|
||||||
|
|
||||||
WiFi.mode(WIFI_AP_STA);
|
WiFi.mode(WIFI_AP_STA);
|
||||||
WiFi.softAPConfig(mApIp, mApIp, IPAddress(255, 255, 255, 0));
|
WiFi.softAPConfig(mApIp, mApIp, IPAddress(255, 255, 255, 0));
|
||||||
WiFi.softAP(WIFI_AP_SSID, WIFI_AP_PWD);
|
WiFi.softAP(WIFI_AP_SSID, WIFI_AP_PWD);
|
||||||
|
@ -300,8 +304,7 @@ void ahoywifi::getAvailNetworks(JsonObject obj) {
|
||||||
void ahoywifi::getBSSIDs() {
|
void ahoywifi::getBSSIDs() {
|
||||||
int n = WiFi.scanComplete();
|
int n = WiFi.scanComplete();
|
||||||
if (n < 0) {
|
if (n < 0) {
|
||||||
mScanCnt++;
|
if (++mScanCnt < 20)
|
||||||
if (mScanCnt < 20)
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if(n > 0) {
|
if(n > 0) {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue