Merge pull request #220 from grindylow/development02
Merge Development for new release
58
.github/workflows/compile_development.yml
vendored
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
name: Ahoy Dev-Build for ESP8266/ESP32
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: development*
|
||||||
|
paths-ignore:
|
||||||
|
- '**.md' # Do no build on *.md changes
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
ref: development02
|
||||||
|
- uses: benjlevesque/short-sha@v1.2
|
||||||
|
id: short-sha
|
||||||
|
with:
|
||||||
|
length: 7
|
||||||
|
- name: cache-pip
|
||||||
|
uses: actions/cache@v3
|
||||||
|
with:
|
||||||
|
path: ~/.cache/pip
|
||||||
|
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-pip-
|
||||||
|
- name: cache-platformio
|
||||||
|
uses: actions/cache@v3
|
||||||
|
with:
|
||||||
|
path: ~/.platformio
|
||||||
|
key: ${{ runner.os }}-${{ hashFiles('**/lockfiles') }}
|
||||||
|
- name: setup-python
|
||||||
|
uses: actions/setup-python@v3
|
||||||
|
- name: install-platformio
|
||||||
|
run: |
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
pip install --upgrade platformio
|
||||||
|
- name: update-html
|
||||||
|
working-directory: tools/esp8266/html
|
||||||
|
run: python convert.py
|
||||||
|
- name: Run PlatformIO
|
||||||
|
run: pio run -d tools/esp8266 --environment esp8266-release --environment esp32-wroom32-release
|
||||||
|
- name: rename-binary-files
|
||||||
|
id: rename-binary-files
|
||||||
|
working-directory: tools/esp8266/scripts
|
||||||
|
run: python getVersion.py
|
||||||
|
- name: set-version
|
||||||
|
uses: cschleiden/replace-tokens@v1
|
||||||
|
with:
|
||||||
|
files: tools/esp8266/User_Manual.md
|
||||||
|
env:
|
||||||
|
VERSION: ${{ steps.rename-binary-files.outputs.name }}
|
||||||
|
- name: create-artifact
|
||||||
|
run: zip --junk-paths ${{ steps.rename-binary-files.outputs.name }}.zip tools/esp8266/.pio/build/out/* tools/esp8266/User_Manual.md
|
||||||
|
- uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: ${{ steps.rename-binary-files.outputs.name }}_dev_build
|
||||||
|
path: ./${{ steps.rename-binary-files.outputs.name }}.zip
|
4
.github/workflows/compile_esp8266.yml
vendored
|
@ -39,7 +39,7 @@ jobs:
|
||||||
working-directory: tools/esp8266/html
|
working-directory: tools/esp8266/html
|
||||||
run: python convert.py
|
run: python convert.py
|
||||||
- name: Run PlatformIO
|
- name: Run PlatformIO
|
||||||
run: pio run -d tools/esp8266 --environment esp8266-release
|
run: pio run -d tools/esp8266 --environment esp8266-release --environment esp32-wroom32-release
|
||||||
- name: rename-binary-files
|
- name: rename-binary-files
|
||||||
id: rename-binary-files
|
id: rename-binary-files
|
||||||
working-directory: tools/esp8266/scripts
|
working-directory: tools/esp8266/scripts
|
||||||
|
@ -72,4 +72,4 @@ jobs:
|
||||||
upload_url: ${{ steps.create-release.outputs.upload_url }}
|
upload_url: ${{ steps.create-release.outputs.upload_url }}
|
||||||
asset_path: ./${{ steps.rename-binary-files.outputs.name }}.zip
|
asset_path: ./${{ steps.rename-binary-files.outputs.name }}.zip
|
||||||
asset_name: ${{ steps.rename-binary-files.outputs.name }}.zip
|
asset_name: ${{ steps.rename-binary-files.outputs.name }}.zip
|
||||||
asset_content_type: application/zip
|
asset_content_type: application/zip
|
||||||
|
|
33
README.md
|
@ -1,17 +1,28 @@
|
||||||
|
 
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
# ahoy
|
# ahoy
|
||||||
Various tools, examples, and documentation for communicating with Hoymiles microinverters.
|
Ahoi is a project to bypass the original Hoymiles cloud solution.
|
||||||
|
In order to use this project, it is important what the area of application looks like.
|
||||||
|
With each version it is necessary to have an NRF24L01+.
|
||||||
|
|
||||||
In particular:
|
Click on the link below you are interested in.
|
||||||
|
There you will find further explanations on how to proceed. (*Note: It is still under construction!*)
|
||||||
|
|
||||||
* `doc/hoymiles-format-description.txt` is a [detailed description of the communications format](doc/hoymiles-format-description.md) and the history of this project
|
##### Most updated section
|
||||||
* `doc/getting-started-ESP8266.md` shows the [hardware setup for an ESP8266-based system](doc/getting-started-ESP8266.md)
|
- [ESP8266](tools/esp8266/) that includes an web interface
|
||||||
* The `tools` folder contains various software tools for RaspberryPi, Arduino and ESP8266/ESP32:
|
|
||||||
* A [version for ESP8266](tools/esp8266/) that includes an web interface 
|
|
||||||
* A [version for Arduino Nano](tools/nano/NRF24_SendRcv/)
|
|
||||||
* An [alternative Version of the above](tools/NRF24_SendRcv/)
|
|
||||||
* A [different implementation](tools/HoyDtuSim/)
|
|
||||||
* An [implementation for Raspberry Pi](tools/rpi/) that polls an inverter and archives results as log files/stdout as well as posting them to an MQTT broker.
|
|
||||||
|
|
||||||
Contributors are always welcome!
|
##### will be updated as needed
|
||||||
|
- [Arduino Nano](tools/nano/NRF24_SendRcv/)
|
||||||
|
- [Raspberry Pi](tools/rpi/)
|
||||||
|
- [others](tools/nano/NRF24_SendRcv/)
|
||||||
|
|
||||||
|
If errors occur or you have suggestions for ideas, please feel free to contact us [here](https://github.com/grindylow/ahoy/issues).
|
||||||
|
|
||||||
|
## Contact
|
||||||
|
We run a Discord Server that can be used to get in touch with the Developers and Users.
|
||||||
|
|
||||||
|
https://discord.gg/WzhxEY62mB
|
||||||
|
|
||||||
|
**Contributors are always welcome!**
|
||||||
|
|
283
tools/cases/DollaTek_NRF24L01_Case/case.scad
Normal file
|
@ -0,0 +1,283 @@
|
||||||
|
$fn=100;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
translate([-w/2-10,0,d+bpt+tpt])
|
||||||
|
rotate(a=180, v=[0,1,0])
|
||||||
|
box_top();
|
||||||
|
translate([w/2+10,0,0])
|
||||||
|
box_bottom();
|
||||||
|
|
||||||
|
|
||||||
|
//*************************************************//
|
||||||
|
//PARAMETERS
|
||||||
|
//*************************************************//
|
||||||
|
//BOX
|
||||||
|
//--------------------------------------------------
|
||||||
|
//inner box width + tolerances
|
||||||
|
w=90;
|
||||||
|
//inner box length + tolerances
|
||||||
|
h=30;
|
||||||
|
//inner box depth
|
||||||
|
d=35;
|
||||||
|
//wall tickness
|
||||||
|
wt=3;
|
||||||
|
//bottom plate tickness
|
||||||
|
bpt=3;
|
||||||
|
//top plate tickness
|
||||||
|
tpt=3;
|
||||||
|
//top cutout depth
|
||||||
|
cd=5;
|
||||||
|
//top cutout tickness
|
||||||
|
cwt=1.5;
|
||||||
|
//--------------------------------------------------
|
||||||
|
//BOARD MOUNTING HOLES
|
||||||
|
//--------------------------------------------------
|
||||||
|
//position
|
||||||
|
cx=0;
|
||||||
|
cy=0;
|
||||||
|
//holes size (radius)
|
||||||
|
chs_i=1.5;
|
||||||
|
chs_o=4;
|
||||||
|
//distancer height
|
||||||
|
dsth=3;
|
||||||
|
//holes distance h
|
||||||
|
chd_h=90;
|
||||||
|
//holes distance v
|
||||||
|
chd_v=90;
|
||||||
|
//--------------------------------------------------
|
||||||
|
//TOP PART MOUNTING HOLES
|
||||||
|
//--------------------------------------------------
|
||||||
|
//position
|
||||||
|
cx1=0;
|
||||||
|
cy1=0;
|
||||||
|
//holes size (radius)
|
||||||
|
chs_i1=1.6;
|
||||||
|
chs_o1=3;
|
||||||
|
//holes distance h
|
||||||
|
chd_h1=h-2*chs_i1-2;
|
||||||
|
//holes distance v
|
||||||
|
chd_v1=w-2*chs_i1-2;
|
||||||
|
//*************************************************//
|
||||||
|
//*************************************************//
|
||||||
|
//MODULES
|
||||||
|
//*************************************************//
|
||||||
|
|
||||||
|
//top box part
|
||||||
|
module box_top()
|
||||||
|
{
|
||||||
|
difference()
|
||||||
|
{
|
||||||
|
union()
|
||||||
|
{
|
||||||
|
difference()
|
||||||
|
{
|
||||||
|
translate([ 0, 0, d+bpt-cd-0.2])
|
||||||
|
rounded_cube( w+2*wt, h+2*wt, tpt+cd+0.2, 6);
|
||||||
|
|
||||||
|
translate([ 0, 0, d+bpt-cd-1])
|
||||||
|
rounded_cube( w+2*cwt+0.3, h+2*cwt+0.3, cd+1, 4);
|
||||||
|
}
|
||||||
|
/*************************/
|
||||||
|
|
||||||
|
//add here...
|
||||||
|
|
||||||
|
/*************************/
|
||||||
|
}
|
||||||
|
//TOP PART HOLES
|
||||||
|
//tph_t_cut();
|
||||||
|
/*************************/
|
||||||
|
|
||||||
|
//subtract here...
|
||||||
|
|
||||||
|
/*************************/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//bottom box
|
||||||
|
module box_bottom()
|
||||||
|
{
|
||||||
|
difference()
|
||||||
|
{
|
||||||
|
union()
|
||||||
|
{
|
||||||
|
difference()
|
||||||
|
{
|
||||||
|
rounded_cube( w+2*wt, h+2*wt, d+bpt, 6);
|
||||||
|
|
||||||
|
translate([ 0, 0, -cd])
|
||||||
|
difference()
|
||||||
|
{
|
||||||
|
translate([ 0, 0, d+bpt])
|
||||||
|
rounded_cube( w+2*wt+1, h+2*wt+1, cd+1, 4);
|
||||||
|
|
||||||
|
translate([ 0, 0, d+bpt-1])
|
||||||
|
rounded_cube( w+2*cwt, h+2*cwt, cd+3, 4);
|
||||||
|
}
|
||||||
|
translate([ 0, 0, bpt])
|
||||||
|
rounded_cube( w, h, d+bpt, 4);
|
||||||
|
}
|
||||||
|
//BOARD DISTANCER
|
||||||
|
//board_distancer();
|
||||||
|
//TOP PLATE DISTANCER
|
||||||
|
//top_distancer();
|
||||||
|
/*************************/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*************************/
|
||||||
|
}
|
||||||
|
|
||||||
|
//BOARD HOLES
|
||||||
|
//bh_cut();
|
||||||
|
//TOP PART HOLES
|
||||||
|
//tph_b_cut();
|
||||||
|
/*************************/
|
||||||
|
|
||||||
|
//subtract here...
|
||||||
|
|
||||||
|
translate([25, 0, 14])
|
||||||
|
rotate([0,90,0])
|
||||||
|
cylinder(r=3.5,h=40);
|
||||||
|
|
||||||
|
translate([-48, 0, 7])
|
||||||
|
cube( [10, 10, 5], true);
|
||||||
|
|
||||||
|
/*************************/
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************/
|
||||||
|
module board_distancer()
|
||||||
|
{
|
||||||
|
translate([cx,cy,0])
|
||||||
|
{
|
||||||
|
translate([+chd_h/2,chd_v/2,0])
|
||||||
|
cylinder(r=chs_o,h=bpt+dsth);
|
||||||
|
translate([-chd_h/2,chd_v/2,0])
|
||||||
|
cylinder(r=chs_o,h=bpt+dsth);
|
||||||
|
translate([+chd_h/2,-chd_v/2,0])
|
||||||
|
cylinder(r=chs_o,h=bpt+dsth);
|
||||||
|
translate([-chd_h/2,-chd_v/2,0])
|
||||||
|
cylinder(r=chs_o,h=bpt+dsth);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
module top_distancer()
|
||||||
|
{
|
||||||
|
translate([cx1,cy1,0])
|
||||||
|
{
|
||||||
|
translate([-chd_v1/2, chd_h1/2, 0])
|
||||||
|
cylinder(r=chs_o1,h=d+bpt);
|
||||||
|
translate([-chd_v1/2, -chd_h1/2, 0])
|
||||||
|
cylinder(r=chs_o1,h=d+bpt);
|
||||||
|
translate([chd_v1/2, chd_h1/2, 0])
|
||||||
|
cylinder(r=chs_o1,h=d+bpt);
|
||||||
|
translate([chd_v1/2, -chd_h1/2, 0])
|
||||||
|
cylinder(r=chs_o1,h=d+bpt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
module bh_cut()
|
||||||
|
{
|
||||||
|
translate([cx,cy,0])
|
||||||
|
{
|
||||||
|
translate([-chd_h/2,-chd_v/2,-1])
|
||||||
|
cylinder(r=chs_i,h=bpt+dsth+2);
|
||||||
|
translate([+chd_h/2,-chd_v/2,-1])
|
||||||
|
cylinder(r=chs_i,h=bpt+dsth+2);
|
||||||
|
translate([-chd_h/2,chd_v/2,-1])
|
||||||
|
cylinder(r=chs_i,h=bpt+dsth+2);
|
||||||
|
translate([+chd_h/2,chd_v/2,-1])
|
||||||
|
cylinder(r=chs_i,h=bpt+dsth+2);
|
||||||
|
|
||||||
|
translate([-chd_h/2,-chd_v/2,1.3])
|
||||||
|
fhex(5.5,3);
|
||||||
|
translate([+chd_h/2,-chd_v/2,1.3])
|
||||||
|
fhex(5.5,3);
|
||||||
|
translate([-chd_h/2,chd_v/2,1.3])
|
||||||
|
fhex(5.5,3);
|
||||||
|
translate([+chd_h/2,chd_v/2,1.3])
|
||||||
|
fhex(5.5,3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module tph_t_cut()
|
||||||
|
{
|
||||||
|
translate([cx1,cy1,0])
|
||||||
|
{
|
||||||
|
translate([-chd_v1/2, chd_h1/2, -2])
|
||||||
|
cylinder(r=chs_i1,h=d+bpt+tpt+3);
|
||||||
|
translate([chd_v1/2, chd_h1/2, -2])
|
||||||
|
cylinder(r=chs_i1,h=d+bpt+tpt+3);
|
||||||
|
|
||||||
|
translate([-chd_v1/2, -chd_h1/2, -2])
|
||||||
|
cylinder(r=chs_i1,h=d+bpt+tpt+3);
|
||||||
|
translate([chd_v1/2, -chd_h1/2, -2])
|
||||||
|
cylinder(r=chs_i1,h=d+bpt+tpt+3);
|
||||||
|
|
||||||
|
translate([-chd_v1/2, chd_h1/2, d+bpt+tpt-1.3])
|
||||||
|
fhex(5.5,3);
|
||||||
|
|
||||||
|
translate([chd_v1/2, chd_h1/2, d+bpt+tpt-1.3])
|
||||||
|
fhex(5.5,3);
|
||||||
|
|
||||||
|
translate([-chd_v1/2, -chd_h1/2, d+bpt+tpt-1.3])
|
||||||
|
fhex(5.5,3);
|
||||||
|
|
||||||
|
translate([chd_v1/2, -chd_h1/2, d+bpt+tpt-1.3])
|
||||||
|
fhex(5.5,3);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
module tph_b_cut()
|
||||||
|
{
|
||||||
|
translate([cx1,cy1,0])
|
||||||
|
{
|
||||||
|
translate([-chd_v1/2, chd_h1/2, -2])
|
||||||
|
cylinder(r=chs_i1,h=d+bpt+3);
|
||||||
|
translate([chd_v1/2, chd_h1/2, -2])
|
||||||
|
cylinder(r=chs_i1,h=d+bpt+3);
|
||||||
|
|
||||||
|
translate([-chd_v1/2, -chd_h1/2, -2])
|
||||||
|
cylinder(r=chs_i1,h=d+bpt+3);
|
||||||
|
translate([chd_v1/2, -chd_h1/2, -2])
|
||||||
|
cylinder(r=chs_i1,h=d+bpt+3);
|
||||||
|
|
||||||
|
translate([-chd_v1/2, chd_h1/2, -1])
|
||||||
|
cylinder(r=3,h=4);
|
||||||
|
|
||||||
|
translate([chd_v1/2, chd_h1/2, -1])
|
||||||
|
cylinder(r=3,h=4);
|
||||||
|
|
||||||
|
translate([-chd_v1/2, -chd_h1/2, -1])
|
||||||
|
cylinder(r=3,h=4);
|
||||||
|
|
||||||
|
translate([chd_v1/2, -chd_h1/2, -1])
|
||||||
|
cylinder(r=3,h=4);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
module rounded_cube( x, y, z, r)
|
||||||
|
{
|
||||||
|
translate([-x/2+r,-y/2+r,0])
|
||||||
|
linear_extrude(height=z)
|
||||||
|
minkowski()
|
||||||
|
{
|
||||||
|
square([x-2*r,y-2*r],true);
|
||||||
|
translate([x/2-r,y/2-r,0])
|
||||||
|
circle(r);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module fhex(wid,height)
|
||||||
|
{
|
||||||
|
hull()
|
||||||
|
{
|
||||||
|
cube([wid/1.7,wid,height],center = true);
|
||||||
|
rotate([0,0,120])cube([wid/1.7,wid,height],center = true);
|
||||||
|
rotate([0,0,240])cube([wid/1.7,wid,height],center = true);
|
||||||
|
}
|
||||||
|
}
|
BIN
tools/cases/DollaTek_NRF24L01_Case/case.stl
Normal file
BIN
tools/cases/DollaTek_NRF24L01_Case/case_photo.jpeg
Normal file
After Width: | Height: | Size: 213 KiB |
BIN
tools/cases/DollaTek_NRF24L01_Case/case_render.png
Normal file
After Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 1.1 MiB |
Before Width: | Height: | Size: 432 KiB After Width: | Height: | Size: 432 KiB |
Before Width: | Height: | Size: 475 KiB After Width: | Height: | Size: 475 KiB |
Before Width: | Height: | Size: 477 KiB After Width: | Height: | Size: 477 KiB |
Before Width: | Height: | Size: 448 KiB After Width: | Height: | Size: 448 KiB |
1
tools/esp8266/.gitignore
vendored
|
@ -3,3 +3,4 @@
|
||||||
.vscode/c_cpp_properties.json
|
.vscode/c_cpp_properties.json
|
||||||
.vscode/launch.json
|
.vscode/launch.json
|
||||||
.vscode/ipch
|
.vscode/ipch
|
||||||
|
config_override.h
|
||||||
|
|
|
@ -1,5 +1,14 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
- v0.5.16
|
||||||
|
* Add alarm messages dictonary in the hminverter class
|
||||||
|
* show last alarm message in the overview after receiving the message id from the AlarmData command
|
||||||
|
* Added No-PowerLimit function/setting (thx @lumapu)
|
||||||
|
* Bug fix #195 trailing and leading spaces in setup parameters (thx @lumapu)
|
||||||
|
* Added parametric CAD model for a case (thx @cubinet-code)
|
||||||
|
* Code styling improvements (eg. dynamic creation of html code) (thx @stefan123t, @lumapu)
|
||||||
|
* Mqtt publish action is now after successful parse a payload, no own ticker
|
||||||
|
* Fixes/improvements #183, #184, #216, #213, #196, #176, #171
|
||||||
- v0.5.15
|
- v0.5.15
|
||||||
* Bug fix: mqtt payload handling (thx @klahus1, silverserver)
|
* Bug fix: mqtt payload handling (thx @klahus1, silverserver)
|
||||||
* Bug fix: eeprom alignment fixed (thx @klahus1)
|
* Bug fix: eeprom alignment fixed (thx @klahus1)
|
||||||
|
|
|
@ -1,19 +1,60 @@
|
||||||
## Table of Contents
|
## Table of Contents
|
||||||
|
|
||||||
- [Overview](#overview)
|
- [Overview](#overview)
|
||||||
- [Compile](#compile)
|
|
||||||
* [Optional Configuration before compilation](#optional-configuration-before-compilation)
|
|
||||||
- [Flash ESP with Firmware](#flash-esp-with-firmware)
|
|
||||||
- [Usage](#usage)
|
|
||||||
- [Compatiblity](#compatiblity)
|
- [Compatiblity](#compatiblity)
|
||||||
|
- [Things needed](#things-needed)
|
||||||
|
- [Wiring things up](#wiring-things-up)
|
||||||
|
+ [ESP8266 wiring example](#esp8266-wiring-example)
|
||||||
|
- [Flash the Firmware on your Ahoy DTU Hardware](#flash-the-firmware-on-your-ahoy-dtu-hardware)
|
||||||
|
+ [Compiling your own Version (the geek way)](#compiling-your-own-version)
|
||||||
|
- [Optional Configuration before compilation](#optional-configuration-before-compilation)
|
||||||
|
+ [Using a ready-to-flash binary using nodemcu-pyflasher (the easy way)](#using-a-ready-to-flash-binary-using-nodemcu-pyflasher)
|
||||||
|
- [Connect to your Ahoy DTU](#connect-to-your-ahoy-dtu)
|
||||||
|
+ [Your Ahoy DTO is very verbose using the Serial Console](#your-ahoy-dto-is-very-verbose-using-the-serial-console)
|
||||||
|
+ [Connect to the Ahoy DTU Webinterface using your Browser](#connect-to-the-ahoy-dtu-webinterface-using-your-browser)
|
||||||
|
- [HTTP based Pages](#http-based-pages)
|
||||||
|
- [MQTT command to set the DTU without webinterface](#mqtt-command-to-set-the-dtu-without-webinterface)
|
||||||
- [Used Libraries](#used-libraries)
|
- [Used Libraries](#used-libraries)
|
||||||
- [Contact](#contact)
|
- [Contact](#contact)
|
||||||
|
- [ToDo's - remove when done](#todo)
|
||||||
|
|
||||||
***
|
***
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
This code is intended to run on a Wemos D1mini or similar. The code is based on 'Hubi's code, which can be found here: <https://www.mikrocontroller.net/topic/525778?page=3#7033371>
|
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/>
|
||||||
|
Further information will help you to communicate to the compatible inverters.
|
||||||
|
|
||||||
|
## Compatiblity
|
||||||
|
For now the following Inverters should work out of the box:
|
||||||
|
|
||||||
|
Hoymiles Inverters
|
||||||
|
- HM300
|
||||||
|
- HM350
|
||||||
|
- HM400
|
||||||
|
- HM600
|
||||||
|
- HM700
|
||||||
|
- HM800
|
||||||
|
- HM1000?
|
||||||
|
- HM1200
|
||||||
|
- HM1500
|
||||||
|
|
||||||
|
TSun Inverters:
|
||||||
|
- TSOL-350
|
||||||
|
- TSOL-400
|
||||||
|
- othery may work as well (need to be veryfied).
|
||||||
|
|
||||||
|
|
||||||
|
## Things needed
|
||||||
|
In order to build your own Ahoy DTU, you will need some things.<br/>
|
||||||
|
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.<br/>
|
||||||
|
Make sure it has the "+" in its name as we depend on some features provided with the plus-variant.<br/>
|
||||||
|
Any other ESP8266 Board with at least 4MBytes of ROM could work as well, depending on your skills.
|
||||||
|
|
||||||
|
|
||||||
|
## Wiring things up
|
||||||
|
|
||||||
The NRF24L01+ radio module is connected to the
|
The NRF24L01+ radio module is connected to the
|
||||||
standard SPI pins:
|
standard SPI pins:
|
||||||
|
@ -21,67 +62,119 @@ standard SPI pins:
|
||||||
- MISO (Master In Slave Out) and
|
- MISO (Master In Slave Out) and
|
||||||
- MOSI (Master Out Slave In)
|
- MOSI (Master Out Slave In)
|
||||||
|
|
||||||
Additional there are 3 pins, which can be set individual:
|
*These pins need to be configured in the config.h.*
|
||||||
|
|
||||||
|
Additional, there are 3 pins, which can be set individual:
|
||||||
- CS (Chip Select),
|
- CS (Chip Select),
|
||||||
- CE (Chip Enable) and
|
- CE (Chip Enable) and
|
||||||
- IRQ (Interrupt)
|
- IRQ (Interrupt)
|
||||||
|
|
||||||
These pins can be changed from the /setup URL
|
*These pins can be changed from the /setup URL.*
|
||||||
|
|
||||||
|
#### ESP8266 wiring example
|
||||||
|
ToDo: (this one needs to be reworked - also a generified one would be helpful)
|
||||||
|
<img src="https://github.com/grindylow/ahoy/blob/main/doc/ESP8266_nRF24L01%2B_bb.png" width="300">
|
||||||
|
|
||||||
|
|
||||||
## Compile
|
|
||||||
|
|
||||||
This code can be compiled using Visual Studio Code and **PlatformIO** Addon. The settings were:
|
|
||||||
|
|
||||||
- Board: Generic ESP8266 Module
|
## Flash the Firmware on your Ahoy DTU Hardware
|
||||||
- Flash-Size: 4MB
|
Once your Hardware is ready to run, you need to flash the Ahoy DTU Firmware to your Board.
|
||||||
- Install libraries (not included in the Arduino IDE 1.8.19):
|
You can either build your own using your own configuration or use one or our pre-compiled generic builds.
|
||||||
- Time Arduino Time library (TimeLib.h)
|
|
||||||
- RF24 Optimized high speed nRF24L01+ driver class documentation
|
|
||||||
- PubSubClient A client library for MQTT messaging. By Nick O'Leary
|
|
||||||
- ArduinoJson Arduino Json library
|
|
||||||
|
|
||||||
### Optional Configuration before compilation
|
|
||||||
|
|
||||||
- number of supported inverters (set to 3 by default) `defines.h`
|
#### Compiling your own Version
|
||||||
- DTU radio id `hmRadio.h`
|
This information suits you if you want to configure and build your own firmware.
|
||||||
|
|
||||||
|
This code comes to you as a **PlatformIO** project and can be compiled using the **PlatformIO** Addon.<br/>
|
||||||
|
Visual Studio Code, AtomIDE and other IDE's support the PlatformIO Addon.<br/>
|
||||||
|
If you do not want to compile your own build, you can use one of our ready-to-flash binaries.
|
||||||
|
|
||||||
|
##### Optional Configuration before compilation
|
||||||
|
|
||||||
|
- number of supported inverters (set to 3 by default) `config.h`
|
||||||
|
- DTU radio id `config.h` (default = 1234567801)
|
||||||
- unformated list in webbrowser `/livedata` `config.h`, `LIVEDATA_VISUALIZED`
|
- unformated list in webbrowser `/livedata` `config.h`, `LIVEDATA_VISUALIZED`
|
||||||
|
|
||||||
|
Alternativly, instead of modifying `config.h`, `config_override_example.h` can be copied to `config_override.h` and customized.
|
||||||
## Flash ESP with Firmware
|
config_override.h is excluded from version control and stays local.
|
||||||
|
|
||||||
1. flash the ESP with the compiled firmware using the UART pins or any preinstalled firmware with OTA capabilities
|
|
||||||
2. repower the ESP
|
|
||||||
3. the ESP will start as access point (AP) if there is no network config stored in its eeprom
|
|
||||||
4. connect to the AP, you will be forwarded to the setup page
|
|
||||||
5. configure your WiFi settings, save, repower
|
|
||||||
6. check your router or serial console for the IP address of the module. You can try ping the configured device name as well.
|
|
||||||
|
|
||||||
|
|
||||||
## Usage
|
#### Using a ready-to-flash binary using nodemcu-pyflasher
|
||||||
|
This information suits you if you just want to use an easy way.
|
||||||
|
|
||||||
Connect the ESP to power and to your serial console (optional). The webinterface has the following abilities:
|
1. download the flash-tool [nodemcu-pyflasher](https://github.com/marcelstoer/nodemcu-pyflasher)
|
||||||
|
2. download latest release bin-file from [ahoy_](https://github.com/grindylow/ahoy/releases)
|
||||||
|
3. open flash-tool and connect the target device to your computer.
|
||||||
|
4. Set the correct serial port and select the correct *.bin file
|
||||||
|
5. click on "Flash NodeMCU"
|
||||||
|
6. flash the ESP with the compiled firmware using the UART pins or
|
||||||
|
7. repower the ESP
|
||||||
|
8. the ESP will start as access point (AP) if there is no network config stored in its eeprom
|
||||||
|
9. connect to the AP, you will be forwarded to the setup page
|
||||||
|
10. configure your WiFi settings, save, repower
|
||||||
|
11. check your router or serial console for the IP address of the module. You can try ping the configured device name as well.
|
||||||
|
|
||||||
|
|
||||||
|
Once your Ahoy DTU is running, you can use the Over The Air (OTA) capabilities to update your firmware.
|
||||||
|
|
||||||
- OTA Update (over the air update)
|
|
||||||
- Configuration (Wifi, inverter(s), Pinout, MQTT)
|
! ATTENTION: If you update from a very low version to the newest, please make sure to wipe all flash data!
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Connect to your Ahoy DTU
|
||||||
|
When everything is wired up and the firmware is flashed, it is time to connect to your Ahoy DTU.
|
||||||
|
|
||||||
|
|
||||||
|
#### Your Ahoy DTU is very verbose using the Serial Console
|
||||||
|
When connected to your computer, you can open a Serial Console to obtain additional information.
|
||||||
|
This might be useful in case of any troubles that might occur as well as to simply obtain information about the converted values which were read out of the inverter(s).
|
||||||
|
|
||||||
|
|
||||||
|
#### Connect to the Ahoy DTU Webinterface using your Browser
|
||||||
|
After you have sucessfully flashed and powered your Ahoy DTU, you can access it via your Browser.
|
||||||
|
If your Ahoy DTU was able to log into the configured WiFi Network, it will try to obtain an IP-Address from your local DHCP Server (in most cases thats your Router).
|
||||||
|
In case it could not connect to your configured Network, it will provide its own WiFi Network that you can connect to for furter configuration. The WiFi SSID *(the WiFi Name)* and Passwort is configured in the config.h and defaults to the SSID "AHOY-DTU" with the Passwort "esp_8266".
|
||||||
|
The Ahoy DTU will keep that Network open for a certain amount of time (also configurable in the config.h and defaults to 60secs). If nothing connects to it and that time runs up, it will retry to connect to the configured network an so on.
|
||||||
|
|
||||||
|
If connected to your local Network, you just have to find out the used IP Address. In most cases your Router will give you a hint.
|
||||||
|
If you connect to the WiFi the Ahoy DTU opens in case it could not connect to any other Network, the IP-Address of your Ahoy DTU is 192.168.1.1.
|
||||||
|
Just open the IP-Address in your browser.
|
||||||
|
|
||||||
|
The webinterface has the following abilities:
|
||||||
|
- OTA Update (Over The Air Update)
|
||||||
|
- Configuration (Wifi, inverter(s), NTP Server, Pinout, MQTT, Amplifier Power Level, Debug)
|
||||||
- visual display of the connected inverters / modules
|
- visual display of the connected inverters / modules
|
||||||
- some statistics about communication (debug)
|
- some statistics about communication (debug)
|
||||||
|
|
||||||
|
|
||||||
|
##### HTTP based Pages
|
||||||
|
To take control of your Ahoy DTU, you can directly call one of the following sub-pages (e.g. http://192.168.1.1/setup ).
|
||||||
|
|
||||||
|
| page | use | output |
|
||||||
|
| ---- | ------ | ------ |
|
||||||
|
| /uptime | displays the uptime uf your Ahoy DTU | 0 Days, 01:37:34; now: 2022-08-21 11:13:53 |
|
||||||
|
| /reboot | reboots the Ahoy DTU | |
|
||||||
|
| /erase | erases the EEPROM | |
|
||||||
|
| /factory | resets to the factory defaults configured in config.h | |
|
||||||
|
| /setup | opens the setup page | |
|
||||||
|
| /save | | |
|
||||||
|
| /cmdstat | show stat from the home page | |
|
||||||
|
| /visualization | displays the information from your converter | |
|
||||||
|
| /livedata | displays the live data | |
|
||||||
|
| /json | gets live-data in JSON format | json output from the livedata |
|
||||||
|
| /api | | |
|
||||||
|
|
||||||
The serial console will print the converted values which were read out of the inverter(s)
|
|
||||||
|
|
||||||
|
## MQTT command to set the DTU without webinterface
|
||||||
|
[Read here](https://github.com/grindylow/ahoy/blob/development02/tools/esp8266/User_Manual.md)
|
||||||
|
|
||||||
|
|
||||||
## Compatiblity
|
|
||||||
|
|
||||||
For now the following inverters should work out of the box:
|
|
||||||
|
|
||||||
- HM300
|
|
||||||
- HM350
|
|
||||||
- HM400
|
|
||||||
- HM600
|
|
||||||
- HM700
|
|
||||||
- HM800
|
|
||||||
- HM1200
|
|
||||||
- HM1500
|
|
||||||
|
|
||||||
## Used Libraries
|
## Used Libraries
|
||||||
|
|
||||||
|
@ -94,7 +187,14 @@ For now the following inverters should work out of the box:
|
||||||
- `PubSubClient` 2.8
|
- `PubSubClient` 2.8
|
||||||
- `ArduinoJson` 6.19.4
|
- `ArduinoJson` 6.19.4
|
||||||
|
|
||||||
|
|
||||||
## Contact
|
## Contact
|
||||||
We run a Discord Server that can be used to get in touch with the Developers and Users.
|
We run a Discord Server that can be used to get in touch with the Developers and Users.
|
||||||
|
|
||||||
https://discord.gg/WzhxEY62mB
|
https://discord.gg/WzhxEY62mB
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## ToDo
|
||||||
|
|
||||||
|
[See this post](https://github.com/grindylow/ahoy/issues/142)
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# User Manual Ahoy DTU (on ESP8266)
|
# User Manual Ahoy DTU (on ESP8266)
|
||||||
16.08.2022
|
Version #{VERSION}#
|
||||||
## Introduction
|
## Introduction
|
||||||
See the repository [here](https://github.com/grindylow/ahoy/blob/main/tools/esp8266/README.md)
|
See the repository [here](https://github.com/grindylow/ahoy/blob/main/tools/esp8266/README.md)
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@ app::app() {
|
||||||
mWifi = new ahoywifi(this, &mSysConfig, &mConfig);
|
mWifi = new ahoywifi(this, &mSysConfig, &mConfig);
|
||||||
|
|
||||||
resetSystem();
|
resetSystem();
|
||||||
loadDefaultConfig();
|
loadDefaultConfig();
|
||||||
|
|
||||||
mSys = new HmSystemType();
|
mSys = new HmSystemType();
|
||||||
}
|
}
|
||||||
|
@ -52,19 +52,21 @@ void app::loop(void) {
|
||||||
bool apActive = mWifi->loop();
|
bool apActive = mWifi->loop();
|
||||||
mWebInst->loop();
|
mWebInst->loop();
|
||||||
|
|
||||||
if(checkTicker(&mUptimeTicker, mUptimeInterval)) {
|
if(millis() - mPrevMillis >= 1000) {
|
||||||
|
mPrevMillis += 1000;
|
||||||
mUptimeSecs++;
|
mUptimeSecs++;
|
||||||
if(0 != mTimestamp)
|
if(0 != mTimestamp)
|
||||||
mTimestamp++;
|
mTimestamp++;
|
||||||
else {
|
}
|
||||||
if(!apActive) {
|
|
||||||
mTimestamp = mWifi->getNtpTime();
|
if(checkTicker(&mNtpRefreshTicker, mNtpRefreshInterval)) {
|
||||||
DPRINTLN(DBG_INFO, "[NTP]: " + getDateTimeStr(mTimestamp));
|
if(!apActive) {
|
||||||
}
|
mTimestamp = mWifi->getNtpTime();
|
||||||
|
DPRINTLN(DBG_INFO, "[NTP]: " + getDateTimeStr(mTimestamp));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
mSys->Radio.loop();
|
mSys->Radio.loop();
|
||||||
|
|
||||||
yield();
|
yield();
|
||||||
|
@ -114,31 +116,31 @@ void app::loop(void) {
|
||||||
}
|
}
|
||||||
if(NULL != iv && p->packet[0] == (TX_REQ_DEVCONTROL + 0x80)) { // response from dev control command
|
if(NULL != iv && p->packet[0] == (TX_REQ_DEVCONTROL + 0x80)) { // response from dev control command
|
||||||
DPRINTLN(DBG_DEBUG, F("Response from devcontrol request received"));
|
DPRINTLN(DBG_DEBUG, F("Response from devcontrol request received"));
|
||||||
iv->devControlRequest = false;
|
iv->devControlRequest = false;
|
||||||
switch (p->packet[12]){
|
switch (p->packet[12]) {
|
||||||
case ActivePowerContr:
|
case ActivePowerContr:
|
||||||
if (iv->devControlCmd >= ActivePowerContr && iv->devControlCmd <= PFSet){ // ok inverter accepted the set point copy it to dtu eeprom
|
if (iv->devControlCmd >= ActivePowerContr && iv->devControlCmd <= PFSet) { // ok inverter accepted the set point copy it to dtu eeprom
|
||||||
if ((iv->powerLimit[1] & 0xff00) >0){ // User want to have it persistent
|
if ((iv->powerLimit[1] & 0xff00) > 0) { // User want to have it persistent
|
||||||
mEep->write(ADDR_INV_PWR_LIM + iv->id * 2,iv->powerLimit[0]);
|
mEep->write(ADDR_INV_PWR_LIM + iv->id * 2, iv->powerLimit[0]);
|
||||||
mEep->write(ADDR_INV_PWR_LIM_CON + iv->id * 2,iv->powerLimit[1]);
|
mEep->write(ADDR_INV_PWR_LIM_CON + iv->id * 2, iv->powerLimit[1]);
|
||||||
updateCrc();
|
updateCrc();
|
||||||
mEep->commit();
|
mEep->commit();
|
||||||
DPRINTLN(DBG_INFO, F("Inverter ") + String(iv->id) + F(" has accepted power limit set point ") + String(iv->powerLimit[0]) + F(" with PowerLimitControl ") + String(iv->powerLimit[1]) + F(", written to dtu eeprom"));
|
DPRINTLN(DBG_INFO, F("Inverter ") + String(iv->id) + F(" has accepted power limit set point ") + String(iv->powerLimit[0]) + F(" with PowerLimitControl ") + String(iv->powerLimit[1]) + F(", written to dtu eeprom"));
|
||||||
} else {
|
} else
|
||||||
DPRINTLN(DBG_INFO, F("Inverter ") + String(iv->id) + F(" has accepted power limit set point ") + String(iv->powerLimit[0]) + F(" with PowerLimitControl ") + String(iv->powerLimit[1]));
|
DPRINTLN(DBG_INFO, F("Inverter ") + String(iv->id) + F(" has accepted power limit set point ") + String(iv->powerLimit[0]) + F(" with PowerLimitControl ") + String(iv->powerLimit[1]));
|
||||||
|
iv->devControlCmd = Init;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
if (iv->devControlCmd == ActivePowerContr) {
|
||||||
|
//case inverter did not accept the sent limit; set back to last stored limit
|
||||||
|
mEep->read(ADDR_INV_PWR_LIM + iv->id * 2, (uint16_t *)&(iv->powerLimit[0]));
|
||||||
|
mEep->read(ADDR_INV_PWR_LIM_CON + iv->id * 2, (uint16_t *)&(iv->powerLimit[1]));
|
||||||
|
DPRINTLN(DBG_INFO, F("Inverter has not accepted power limit set point"));
|
||||||
}
|
}
|
||||||
iv->devControlCmd = Init;
|
iv->devControlCmd = Init;
|
||||||
}
|
break;
|
||||||
break;
|
|
||||||
default:
|
|
||||||
if (iv->devControlCmd == ActivePowerContr){
|
|
||||||
//case inverter did not accept the sent limit; set back to last stored limit
|
|
||||||
mEep->read(ADDR_INV_PWR_LIM + iv->id * 2, (uint16_t *)&(iv->powerLimit[0]));
|
|
||||||
mEep->read(ADDR_INV_PWR_LIM_CON + iv->id * 2, (uint16_t *)&(iv->powerLimit[1]));
|
|
||||||
DPRINTLN(DBG_INFO, F("Inverter has not accepted power limit set point"));
|
|
||||||
}
|
|
||||||
iv->devControlCmd = Init;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -160,20 +162,7 @@ void app::loop(void) {
|
||||||
if((++mMqttTicker >= mMqttInterval) && (mMqttInterval != 0xffff) && mMqttActive) {
|
if((++mMqttTicker >= mMqttInterval) && (mMqttInterval != 0xffff) && mMqttActive) {
|
||||||
mMqttTicker = 0;
|
mMqttTicker = 0;
|
||||||
mMqtt.isConnected(true); // really needed? See comment from HorstG-57 #176
|
mMqtt.isConnected(true); // really needed? See comment from HorstG-57 #176
|
||||||
char topic[30], val[10];
|
char val[10];
|
||||||
for(uint8_t id = 0; id < mSys->getNumInverters(); id++) {
|
|
||||||
Inverter<> *iv = mSys->getInverterByPos(id);
|
|
||||||
if(NULL != iv) {
|
|
||||||
if(iv->isAvailable(mTimestamp)) {
|
|
||||||
for(uint8_t i = 0; i < iv->listLen; i++) {
|
|
||||||
snprintf(topic, 30, "%s/ch%d/%s", iv->name, iv->assign[i].ch, fields[iv->assign[i].fieldId]);
|
|
||||||
snprintf(val, 10, "%.3f", iv->getValue(i));
|
|
||||||
mMqtt.sendMsg(topic, val);
|
|
||||||
yield();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
snprintf(val, 10, "%ld", millis()/1000);
|
snprintf(val, 10, "%ld", millis()/1000);
|
||||||
|
|
||||||
#ifndef __MQTT_NO_DISCOVERCONFIG__
|
#ifndef __MQTT_NO_DISCOVERCONFIG__
|
||||||
|
@ -239,6 +228,10 @@ void app::loop(void) {
|
||||||
|
|
||||||
if(!mPayload[iv->id].complete) {
|
if(!mPayload[iv->id].complete) {
|
||||||
mRxFailed++;
|
mRxFailed++;
|
||||||
|
iv->setQueuedCmdFinished(); // command failed
|
||||||
|
if(mConfig.serialDebug) {
|
||||||
|
DPRINTLN(DBG_INFO, F("enqueued cmd failed/timeout"));
|
||||||
|
}
|
||||||
if(mConfig.serialDebug) {
|
if(mConfig.serialDebug) {
|
||||||
DPRINT(DBG_INFO, F("Inverter #") + String(iv->id) + " ");
|
DPRINT(DBG_INFO, F("Inverter #") + String(iv->id) + " ");
|
||||||
DPRINTLN(DBG_INFO, F("no Payload received! (retransmits: ") + String(mPayload[iv->id].retransmits) + ")");
|
DPRINTLN(DBG_INFO, F("no Payload received! (retransmits: ") + String(mPayload[iv->id].retransmits) + ")");
|
||||||
|
@ -249,12 +242,12 @@ void app::loop(void) {
|
||||||
|
|
||||||
yield();
|
yield();
|
||||||
if(mConfig.serialDebug)
|
if(mConfig.serialDebug)
|
||||||
DPRINTLN(DBG_DEBUG, F("app:loop WiFi WiFi.status ") + String(WiFi.status()) );
|
DPRINTLN(DBG_DEBUG, F("app:loop WiFi WiFi.status ") + String(WiFi.status()));
|
||||||
DPRINTLN(DBG_INFO, F("Requesting Inverter SN ") + String(iv->serial.u64, HEX));
|
DPRINTLN(DBG_INFO, F("Requesting Inverter SN ") + String(iv->serial.u64, HEX));
|
||||||
if(iv->devControlRequest && iv->powerLimit[0] > 0){ // prevent to "switch off"
|
if(iv->devControlRequest && (iv->powerLimit[0] > 0) && (NoPowerLimit != iv->powerLimit[1])) { // prevent to "switch off"
|
||||||
if(mConfig.serialDebug)
|
if(mConfig.serialDebug)
|
||||||
DPRINTLN(DBG_INFO, F("Devcontrol request ") + String(iv->devControlCmd) + F(" power limit ") + String(iv->powerLimit[0]));
|
DPRINTLN(DBG_INFO, F("Devcontrol request ") + String(iv->devControlCmd) + F(" power limit ") + String(iv->powerLimit[0]));
|
||||||
mSys->Radio.sendControlPacket(iv->radioId.u64,iv->devControlCmd ,iv->powerLimit);
|
mSys->Radio.sendControlPacket(iv->radioId.u64, iv->devControlCmd ,iv->powerLimit);
|
||||||
iv->enqueCommand<InfoCommand>(SystemConfigPara);
|
iv->enqueCommand<InfoCommand>(SystemConfigPara);
|
||||||
} else {
|
} else {
|
||||||
mSys->Radio.sendTimePacket(iv->radioId.u64,iv->getQueuedCmd(), mPayload[iv->id].ts,iv->alarmMesIndex);
|
mSys->Radio.sendTimePacket(iv->radioId.u64,iv->getQueuedCmd(), mPayload[iv->id].ts,iv->alarmMesIndex);
|
||||||
|
@ -346,8 +339,11 @@ void app::processPayload(bool retransmit) {
|
||||||
else {
|
else {
|
||||||
mPayload[iv->id].complete = true;
|
mPayload[iv->id].complete = true;
|
||||||
iv->ts = mPayload[iv->id].ts;
|
iv->ts = mPayload[iv->id].ts;
|
||||||
uint8_t payload[128] = {0};
|
uint8_t payload[128];
|
||||||
uint8_t offs = 0;
|
uint8_t offs = 0;
|
||||||
|
|
||||||
|
memset(payload, 0, 128);
|
||||||
|
|
||||||
for(uint8_t i = 0; i < (mPayload[iv->id].maxPackId); i ++) {
|
for(uint8_t i = 0; i < (mPayload[iv->id].maxPackId); i ++) {
|
||||||
memcpy(&payload[offs], mPayload[iv->id].data[i], (mPayload[iv->id].len[i]));
|
memcpy(&payload[offs], mPayload[iv->id].data[i], (mPayload[iv->id].len[i]));
|
||||||
offs += (mPayload[iv->id].len[i]);
|
offs += (mPayload[iv->id].len[i]);
|
||||||
|
@ -366,11 +362,34 @@ void app::processPayload(bool retransmit) {
|
||||||
yield();
|
yield();
|
||||||
}
|
}
|
||||||
iv->doCalculations(); // cmd value decides which parser is used to decode payload
|
iv->doCalculations(); // cmd value decides which parser is used to decode payload
|
||||||
|
|
||||||
|
iv->setQueuedCmdFinished();
|
||||||
|
|
||||||
|
// MQTT send out
|
||||||
|
if(mMqttActive) {
|
||||||
|
char topic[30], val[10];
|
||||||
|
for (uint8_t id = 0; id < mSys->getNumInverters(); id++)
|
||||||
|
{
|
||||||
|
Inverter<> *iv = mSys->getInverterByPos(id);
|
||||||
|
if (NULL != iv)
|
||||||
|
{
|
||||||
|
if (iv->isAvailable(mTimestamp))
|
||||||
|
{
|
||||||
|
for (uint8_t i = 0; i < iv->listLen; i++)
|
||||||
|
{
|
||||||
|
snprintf(topic, 30, "%s/ch%d/%s", iv->name, iv->assign[i].ch, fields[iv->assign[i].fieldId]);
|
||||||
|
snprintf(val, 10, "%.3f", iv->getValue(i));
|
||||||
|
mMqtt.sendMsg(topic, val);
|
||||||
|
yield();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef __MQTT_AFTER_RX__
|
#ifdef __MQTT_AFTER_RX__
|
||||||
doMQTT = true;
|
doMQTT = true;
|
||||||
#endif
|
#endif
|
||||||
iv->setQueuedCmdFinished();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
yield();
|
yield();
|
||||||
|
@ -483,9 +502,10 @@ String app::getStatistics(void) {
|
||||||
Inverter<> *iv;
|
Inverter<> *iv;
|
||||||
for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i++) {
|
for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i++) {
|
||||||
iv = mSys->getInverterByPos(i);
|
iv = mSys->getInverterByPos(i);
|
||||||
|
content += F("Inverter #") + String(i) + F(": ");
|
||||||
if(NULL != iv) {
|
if(NULL != iv) {
|
||||||
bool avail = true;
|
bool avail = true;
|
||||||
content += F("Inverter '") + String(iv->name) + F(" (FW-Version: ") + String(iv->fwVersion) +F(")") + F("' is ");
|
content += String(iv->name) + F(" (v") + String(iv->fwVersion) +F(")") + F(" is ");
|
||||||
if(!iv->isAvailable(mTimestamp)) {
|
if(!iv->isAvailable(mTimestamp)) {
|
||||||
content += F("not ");
|
content += F("not ");
|
||||||
avail = false;
|
avail = false;
|
||||||
|
@ -500,9 +520,8 @@ String app::getStatistics(void) {
|
||||||
content += F("-> last successful transmission: ") + getDateTimeStr(iv->getLastTs()) + "\n";
|
content += F("-> last successful transmission: ") + getDateTimeStr(iv->getLastTs()) + "\n";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else
|
||||||
content += F("Inverter ") + String(i) + F(" not (correctly) configured\n");
|
content += F("n/a\n");
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!mSys->Radio.isChipConnected())
|
if(!mSys->Radio.isChipConnected())
|
||||||
|
@ -523,119 +542,6 @@ String app::getStatistics(void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
|
||||||
String app::getLiveData(void)
|
|
||||||
{
|
|
||||||
String modHtml;
|
|
||||||
for (uint8_t id = 0; id < mSys->getNumInverters(); id++)
|
|
||||||
{
|
|
||||||
Inverter<> *iv = mSys->getInverterByPos(id);
|
|
||||||
if (NULL != iv)
|
|
||||||
{
|
|
||||||
#ifdef LIVEDATA_VISUALIZED
|
|
||||||
uint8_t modNum, pos;
|
|
||||||
switch (iv->type)
|
|
||||||
{
|
|
||||||
default:
|
|
||||||
case INV_TYPE_1CH:
|
|
||||||
modNum = 1;
|
|
||||||
break;
|
|
||||||
case INV_TYPE_2CH:
|
|
||||||
modNum = 2;
|
|
||||||
break;
|
|
||||||
case INV_TYPE_4CH:
|
|
||||||
modNum = 4;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
modHtml += F("<div class=\"iv\">"
|
|
||||||
"<div class=\"ch-iv\"><span class=\"head\">") +
|
|
||||||
String(iv->name) + F(" Limit ") + String(iv->actPowerLimit);
|
|
||||||
if (true)
|
|
||||||
{ // live Power Limit from inverter is always in %
|
|
||||||
modHtml += F(" %</span>");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
modHtml += F(" W</span>");
|
|
||||||
}
|
|
||||||
uint8_t list[] = {FLD_UAC, FLD_IAC, FLD_PAC, FLD_F, FLD_PCT, FLD_T, FLD_YT, FLD_YD, FLD_PDC, FLD_EFF, FLD_PRA, FLD_ALARM_MES_ID};
|
|
||||||
|
|
||||||
for (uint8_t fld = 0; fld < 12; fld++)
|
|
||||||
{
|
|
||||||
pos = (iv->getPosByChFld(CH0, list[fld]));
|
|
||||||
if (0xff != pos)
|
|
||||||
{
|
|
||||||
modHtml += F("<div class=\"subgrp\">");
|
|
||||||
modHtml += F("<span class=\"value\">") + String(iv->getValue(pos));
|
|
||||||
modHtml += F("<span class=\"unit\">") + String(iv->getUnit(pos)) + F("</span></span>");
|
|
||||||
modHtml += F("<span class=\"info\">") + String(iv->getFieldName(pos)) + F("</span>");
|
|
||||||
modHtml += F("</div>");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
modHtml += "</div>";
|
|
||||||
|
|
||||||
for (uint8_t ch = 1; ch <= modNum; ch++)
|
|
||||||
{
|
|
||||||
modHtml += F("<div class=\"ch\"><span class=\"head\">");
|
|
||||||
if (iv->chName[ch - 1][0] == 0)
|
|
||||||
modHtml += F("CHANNEL ") + String(ch);
|
|
||||||
else
|
|
||||||
modHtml += String(iv->chName[ch - 1]);
|
|
||||||
modHtml += F("</span>");
|
|
||||||
for (uint8_t j = 0; j < 6; j++)
|
|
||||||
{
|
|
||||||
switch (j)
|
|
||||||
{
|
|
||||||
default:
|
|
||||||
pos = (iv->getPosByChFld(ch, FLD_UDC));
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
pos = (iv->getPosByChFld(ch, FLD_IDC));
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
pos = (iv->getPosByChFld(ch, FLD_PDC));
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
pos = (iv->getPosByChFld(ch, FLD_YD));
|
|
||||||
break;
|
|
||||||
case 4:
|
|
||||||
pos = (iv->getPosByChFld(ch, FLD_YT));
|
|
||||||
break;
|
|
||||||
case 5:
|
|
||||||
pos = (iv->getPosByChFld(ch, FLD_IRR));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (0xff != pos)
|
|
||||||
{
|
|
||||||
modHtml += F("<span class=\"value\">") + String(iv->getValue(pos));
|
|
||||||
modHtml += F("<span class=\"unit\">") + String(iv->getUnit(pos)) + F("</span></span>");
|
|
||||||
modHtml += F("<span class=\"info\">") + String(iv->getFieldName(pos)) + F("</span>");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
modHtml += "</div>";
|
|
||||||
yield();
|
|
||||||
}
|
|
||||||
modHtml += F("<div class=\"ts\">Last received data requested at: ") + getDateTimeStr(iv->ts) + F("</div>");
|
|
||||||
modHtml += F("</div>");
|
|
||||||
#else
|
|
||||||
// dump all data to web frontend
|
|
||||||
modHtml = F("<pre>");
|
|
||||||
char topic[30], val[10];
|
|
||||||
for (uint8_t i = 0; i < iv->listLen; i++)
|
|
||||||
{
|
|
||||||
snprintf(topic, 30, "%s/ch%d/%s", iv->name, iv->assign[i].ch, iv->getFieldName(i));
|
|
||||||
snprintf(val, 10, "%.3f %s", iv->getValue(i), iv->getUnit(i));
|
|
||||||
modHtml += String(topic) + ": " + String(val) + "\n";
|
|
||||||
}
|
|
||||||
modHtml += F("</pre>");
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return modHtml;
|
|
||||||
}
|
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
String app::getJson(void) {
|
String app::getJson(void) {
|
||||||
DPRINTLN(DBG_VERBOSE, F("app::showJson"));
|
DPRINTLN(DBG_VERBOSE, F("app::showJson"));
|
||||||
|
@ -750,8 +656,10 @@ const char* app::getFieldStateClass(uint8_t fieldId) {
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
void app::resetSystem(void) {
|
void app::resetSystem(void) {
|
||||||
mUptimeSecs = 0;
|
mUptimeSecs = 0;
|
||||||
mUptimeTicker = 0xffffffff;
|
mPrevMillis = 0;
|
||||||
mUptimeInterval = 1000;
|
|
||||||
|
mNtpRefreshTicker = 0;
|
||||||
|
mNtpRefreshInterval = NTP_REFRESH_INTERVAL; // [ms]
|
||||||
|
|
||||||
#ifdef AP_ONLY
|
#ifdef AP_ONLY
|
||||||
mTimestamp = 1;
|
mTimestamp = 1;
|
||||||
|
@ -852,11 +760,16 @@ void app::loadEEpconfig(void) {
|
||||||
// it is "doppelt-gemoppelt" because the inverter shall remember the setting if the dtu makes a power cycle / reboot
|
// it is "doppelt-gemoppelt" because the inverter shall remember the setting if the dtu makes a power cycle / reboot
|
||||||
if (iv->powerLimit[0] != 0xffff) {
|
if (iv->powerLimit[0] != 0xffff) {
|
||||||
iv->devControlCmd = ActivePowerContr; // set active power limit
|
iv->devControlCmd = ActivePowerContr; // set active power limit
|
||||||
if (iv->powerLimit[1] & 0x0001){
|
DPRINT(DBG_INFO, F("add inverter: ") + String(name) + ", SN: " + String(invSerial, HEX));
|
||||||
DPRINTLN(DBG_INFO, F("add inverter: ") + String(name) + ", SN: " + String(invSerial, HEX) + ", Power Limit: " + String(iv->powerLimit[0]) + " in %");
|
if(iv->powerLimit[1] != NoPowerLimit) {
|
||||||
} else {
|
DBGPRINT(F(", Power Limit: ") + String(iv->powerLimit[0]));
|
||||||
DPRINTLN(DBG_INFO, F("add inverter: ") + String(name) + ", SN: " + String(invSerial, HEX) + ", Power Limit: " + String(iv->powerLimit[0]) + " in Watt");
|
if ((iv->powerLimit[1] & 0x0001) == 0x0001)
|
||||||
|
DBGPRINTLN(F(" in %"));
|
||||||
|
else
|
||||||
|
DBGPRINTLN(F(" in Watt"));
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
DBGPRINTLN(F(" "));
|
||||||
}
|
}
|
||||||
for(uint8_t j = 0; j < 4; j++) {
|
for(uint8_t j = 0; j < 4; j++) {
|
||||||
mEep->read(ADDR_INV_CH_NAME + (i * 4 * MAX_NAME_LENGTH) + j * MAX_NAME_LENGTH, iv->chName[j], MAX_NAME_LENGTH);
|
mEep->read(ADDR_INV_CH_NAME + (i * 4 * MAX_NAME_LENGTH) + j * MAX_NAME_LENGTH, iv->chName[j], MAX_NAME_LENGTH);
|
||||||
|
@ -879,17 +792,15 @@ void app::saveValues(void) {
|
||||||
mEep->write(ADDR_CFG, (uint8_t*)&mConfig, CFG_LEN);
|
mEep->write(ADDR_CFG, (uint8_t*)&mConfig, CFG_LEN);
|
||||||
Inverter<> *iv;
|
Inverter<> *iv;
|
||||||
for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i ++) {
|
for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i ++) {
|
||||||
iv = mSys->getInverterByPos(i);
|
iv = mSys->getInverterByPos(i, false);
|
||||||
if(NULL != iv) {
|
mEep->write(ADDR_INV_ADDR + (i * 8), iv->serial.u64);
|
||||||
mEep->write(ADDR_INV_ADDR + (i * 8), iv->serial.u64);
|
mEep->write(ADDR_INV_PWR_LIM + i * 2, iv->powerLimit[0]);
|
||||||
mEep->write(ADDR_INV_PWR_LIM + i * 2, iv->powerLimit[0]);
|
mEep->write(ADDR_INV_PWR_LIM_CON + i * 2, iv->powerLimit[1]);
|
||||||
mEep->write(ADDR_INV_PWR_LIM_CON + i * 2, iv->powerLimit[1]);
|
mEep->write(ADDR_INV_NAME + (i * MAX_NAME_LENGTH), iv->name, MAX_NAME_LENGTH);
|
||||||
mEep->write(ADDR_INV_NAME + (i * MAX_NAME_LENGTH), iv->name, MAX_NAME_LENGTH);
|
// max channel power / name
|
||||||
// max channel power / name
|
for(uint8_t j = 0; j < 4; j++) {
|
||||||
for(uint8_t j = 0; j < 4; j++) {
|
mEep->write(ADDR_INV_CH_PWR + (i * 2 * 4) + (j*2), iv->chMaxPwr[j]);
|
||||||
mEep->write(ADDR_INV_CH_PWR + (i * 2 * 4) + (j*2), iv->chMaxPwr[j]);
|
mEep->write(ADDR_INV_CH_NAME + (i * 4 * MAX_NAME_LENGTH) + j * MAX_NAME_LENGTH, iv->chName[j], MAX_NAME_LENGTH);
|
||||||
mEep->write(ADDR_INV_CH_NAME + (i * 4 * MAX_NAME_LENGTH) + j * MAX_NAME_LENGTH, iv->chName[j], MAX_NAME_LENGTH);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -946,4 +857,4 @@ void app::resetPayload(Inverter<>* iv)
|
||||||
mPayload[iv->id].complete = false;
|
mPayload[iv->id].complete = false;
|
||||||
mPayload[iv->id].requested = true;
|
mPayload[iv->id].requested = true;
|
||||||
mPayload[iv->id].ts = mTimestamp;
|
mPayload[iv->id].ts = mTimestamp;
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,7 +71,6 @@ class app {
|
||||||
void saveValues(void);
|
void saveValues(void);
|
||||||
void resetPayload(Inverter<>* iv);
|
void resetPayload(Inverter<>* iv);
|
||||||
String getStatistics(void);
|
String getStatistics(void);
|
||||||
String getLiveData(void);
|
|
||||||
String getJson(void);
|
String getJson(void);
|
||||||
bool getWifiApActive(void);
|
bool getWifiApActive(void);
|
||||||
|
|
||||||
|
@ -80,9 +79,10 @@ class app {
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t Serial2u64(const char *val) {
|
uint64_t Serial2u64(const char *val) {
|
||||||
char tmp[3] = {0};
|
char tmp[3];
|
||||||
uint64_t ret = 0ULL;
|
uint64_t ret = 0ULL;
|
||||||
uint64_t u64;
|
uint64_t u64;
|
||||||
|
memset(tmp, 0, 3);
|
||||||
for(uint8_t i = 0; i < 6; i++) {
|
for(uint8_t i = 0; i < 6; i++) {
|
||||||
tmp[0] = val[i*2];
|
tmp[0] = val[i*2];
|
||||||
tmp[1] = val[i*2 + 1];
|
tmp[1] = val[i*2 + 1];
|
||||||
|
@ -95,7 +95,7 @@ class app {
|
||||||
}
|
}
|
||||||
|
|
||||||
String getDateTimeStr(time_t t) {
|
String getDateTimeStr(time_t t) {
|
||||||
char str[20] = {0};
|
char str[20];
|
||||||
if(0 == t)
|
if(0 == t)
|
||||||
sprintf(str, "n/a");
|
sprintf(str, "n/a");
|
||||||
else
|
else
|
||||||
|
@ -113,9 +113,11 @@ class app {
|
||||||
|
|
||||||
void eraseSettings(bool all = false) {
|
void eraseSettings(bool all = false) {
|
||||||
//DPRINTLN(DBG_VERBOSE, F("main.h:eraseSettings"));
|
//DPRINTLN(DBG_VERBOSE, F("main.h:eraseSettings"));
|
||||||
uint8_t buf[64] = {0};
|
uint8_t buf[64];
|
||||||
uint16_t addr = (all) ? ADDR_START : ADDR_START_SETTINGS;
|
uint16_t addr = (all) ? ADDR_START : ADDR_START_SETTINGS;
|
||||||
uint16_t end;
|
uint16_t end;
|
||||||
|
|
||||||
|
memset(buf, 0xff, 64);
|
||||||
do {
|
do {
|
||||||
end = addr + 64;
|
end = addr + 64;
|
||||||
if(end > (ADDR_SETTINGS_CRC + 2))
|
if(end > (ADDR_SETTINGS_CRC + 2))
|
||||||
|
@ -219,10 +221,11 @@ class app {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
uint32_t mUptimeTicker;
|
|
||||||
uint16_t mUptimeInterval;
|
|
||||||
uint32_t mUptimeSecs;
|
uint32_t mUptimeSecs;
|
||||||
|
uint32_t mPrevMillis;
|
||||||
uint8_t mHeapStatCnt;
|
uint8_t mHeapStatCnt;
|
||||||
|
uint32_t mNtpRefreshTicker;
|
||||||
|
uint32_t mNtpRefreshInterval;
|
||||||
|
|
||||||
|
|
||||||
bool mWifiSettingsValid;
|
bool mWifiSettingsValid;
|
||||||
|
|
|
@ -36,6 +36,9 @@
|
||||||
#define DEF_RF24_CE_PIN 2
|
#define DEF_RF24_CE_PIN 2
|
||||||
#define DEF_RF24_IRQ_PIN 0
|
#define DEF_RF24_IRQ_PIN 0
|
||||||
|
|
||||||
|
// default radio ID
|
||||||
|
#define DTU_RADIO_ID ((uint64_t)0x1234567801ULL)
|
||||||
|
|
||||||
// default NRF24 power, possible values (0 - 3)
|
// default NRF24 power, possible values (0 - 3)
|
||||||
#define DEF_AMPLIFIERPOWER 2
|
#define DEF_AMPLIFIERPOWER 2
|
||||||
|
|
||||||
|
@ -69,12 +72,15 @@
|
||||||
// threshold of minimum power on which the inverter is marked as inactive
|
// threshold of minimum power on which the inverter is marked as inactive
|
||||||
#define INACT_PWR_THRESH 3
|
#define INACT_PWR_THRESH 3
|
||||||
|
|
||||||
// default ntp server uri
|
// default NTP server uri
|
||||||
#define DEF_NTP_SERVER_NAME "pool.ntp.org"
|
#define DEF_NTP_SERVER_NAME "pool.ntp.org"
|
||||||
|
|
||||||
// default ntp server port
|
// default NTP server port
|
||||||
#define DEF_NTP_PORT 8888
|
#define DEF_NTP_PORT 8888
|
||||||
|
|
||||||
|
// NTP refresh interval in ms (default 12h)
|
||||||
|
#define NTP_REFRESH_INTERVAL 12 * 3600 * 1000
|
||||||
|
|
||||||
// default mqtt interval
|
// default mqtt interval
|
||||||
#define MQTT_INTERVAL 60
|
#define MQTT_INTERVAL 60
|
||||||
|
|
||||||
|
@ -96,4 +102,8 @@
|
||||||
// changes the style of "/setup" page, visualized = nicer
|
// changes the style of "/setup" page, visualized = nicer
|
||||||
#define LIVEDATA_VISUALIZED
|
#define LIVEDATA_VISUALIZED
|
||||||
|
|
||||||
|
#if __has_include("config_override.h")
|
||||||
|
#include "config_override.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
#endif /*__CONFIG_H__*/
|
#endif /*__CONFIG_H__*/
|
||||||
|
|
30
tools/esp8266/config_override_example.h
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
// 2022 Ahoy, https://www.mikrocontroller.net/topic/525778
|
||||||
|
// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#ifndef __CONFIG_OVERRIDE_H__
|
||||||
|
#define __CONFIG_OVERRIDE_H__
|
||||||
|
|
||||||
|
// override fallback WiFi info
|
||||||
|
|
||||||
|
// each ovveride must be preceeded with an #undef statement
|
||||||
|
#undef FB_WIFI_SSID
|
||||||
|
#define FB_WIFI_SSID "MY_SSID"
|
||||||
|
|
||||||
|
// each ovveride must be preceeded with an #undef statement
|
||||||
|
#undef FB_WIFI_PWD
|
||||||
|
#define FB_WIFI_PWD "MY_WIFI_KEY"
|
||||||
|
|
||||||
|
// ESP32 default pinout
|
||||||
|
#undef DEF_RF24_CS_PIN
|
||||||
|
#define DEF_RF24_CS_PIN 5
|
||||||
|
#undef DEF_RF24_CE_PIN
|
||||||
|
#define DEF_RF24_CE_PIN 4
|
||||||
|
#undef DEF_RF24_IRQ_PIN
|
||||||
|
#define DEF_RF24_IRQ_PIN 16
|
||||||
|
|
||||||
|
#undef DTU_RADIO_ID
|
||||||
|
#define DTU_RADIO_ID ((uint64_t)0x1234567802ULL)
|
||||||
|
|
||||||
|
#endif /*__CONFIG_OVERRIDE_H__*/
|
|
@ -13,7 +13,7 @@
|
||||||
//-------------------------------------
|
//-------------------------------------
|
||||||
#define VERSION_MAJOR 0
|
#define VERSION_MAJOR 0
|
||||||
#define VERSION_MINOR 5
|
#define VERSION_MINOR 5
|
||||||
#define VERSION_PATCH 15
|
#define VERSION_PATCH 16
|
||||||
|
|
||||||
|
|
||||||
//-------------------------------------
|
//-------------------------------------
|
||||||
|
@ -58,6 +58,7 @@ typedef enum {
|
||||||
} DevControlCmdType;
|
} DevControlCmdType;
|
||||||
|
|
||||||
typedef enum { // ToDo: to be verified by field tests
|
typedef enum { // ToDo: to be verified by field tests
|
||||||
|
NoPowerLimit = 0xffff, // ahoy internal value, no hoymiles value!
|
||||||
AbsolutNonPersistent = 0UL, // 0x0000
|
AbsolutNonPersistent = 0UL, // 0x0000
|
||||||
RelativNonPersistent = 1UL, // 0x0001
|
RelativNonPersistent = 1UL, // 0x0001
|
||||||
AbsolutPersistent = 256UL, // 0x0100
|
AbsolutPersistent = 256UL, // 0x0100
|
||||||
|
@ -104,6 +105,7 @@ typedef enum { // ToDo: to be verified by field tests
|
||||||
#define MQTT_PORT_LEN 2 // uint16_t
|
#define MQTT_PORT_LEN 2 // uint16_t
|
||||||
#define MQTT_DISCOVERY_PREFIX "homeassistant"
|
#define MQTT_DISCOVERY_PREFIX "homeassistant"
|
||||||
#define MQTT_MAX_PACKET_SIZE 384
|
#define MQTT_MAX_PACKET_SIZE 384
|
||||||
|
#define MQTT_RECONNECT_DELAY 5000
|
||||||
|
|
||||||
#define SER_ENABLE_LEN 1 // uint8_t
|
#define SER_ENABLE_LEN 1 // uint8_t
|
||||||
#define SER_DEBUG_LEN 1 // uint8_t
|
#define SER_DEBUG_LEN 1 // uint8_t
|
||||||
|
|
|
@ -23,9 +23,13 @@ const char* const units[] = {"V", "A", "W", "Wh", "kWh", "Hz", "°C", "%","VAr",
|
||||||
|
|
||||||
// field types
|
// field types
|
||||||
enum {FLD_UDC = 0, FLD_IDC, FLD_PDC, FLD_YD, FLD_YW, FLD_YT,
|
enum {FLD_UDC = 0, FLD_IDC, FLD_PDC, FLD_YD, FLD_YW, FLD_YT,
|
||||||
FLD_UAC, FLD_IAC, FLD_PAC, FLD_F, FLD_T, FLD_PCT, FLD_EFF, FLD_IRR, FLD_PRA,FLD_ALARM_MES_ID,FLD_FW_VERSION,FLD_FW_BUILD_YEAR,FLD_FW_BUILD_MONTH_DAY,FLD_HW_ID,FLD_ACT_PWR_LIMIT};
|
FLD_UAC, FLD_IAC, FLD_PAC, FLD_F, FLD_T, FLD_PCT, FLD_EFF,
|
||||||
|
FLD_IRR, FLD_PRA,FLD_ALARM_MES_ID,FLD_FW_VERSION,FLD_FW_BUILD_YEAR,
|
||||||
|
FLD_FW_BUILD_MONTH_DAY,FLD_HW_ID,FLD_ACT_PWR_LIMIT,FLD_LAST_ALARM_CODE};
|
||||||
|
|
||||||
const char* const fields[] = {"U_DC", "I_DC", "P_DC", "YieldDay", "YieldWeek", "YieldTotal",
|
const char* const fields[] = {"U_DC", "I_DC", "P_DC", "YieldDay", "YieldWeek", "YieldTotal",
|
||||||
"U_AC", "I_AC", "P_AC", "Freq", "Temp", "Pct", "Efficiency", "Irradiation","P_ACr","ALARM_MES_ID","FWVersion","FWBuildYear","FWBuildMonthDay","HWPartId","PowerLimit"};
|
"U_AC", "I_AC", "P_AC", "Freq", "Temp", "Pct", "Efficiency", "Irradiation","P_ACr",
|
||||||
|
"ALARM_MES_ID","FWVersion","FWBuildYear","FWBuildMonthDay","HWPartId","PowerLimit","LastAlarmCode"};
|
||||||
|
|
||||||
// mqtt discovery device classes
|
// mqtt discovery device classes
|
||||||
enum {DEVICE_CLS_NONE = 0, DEVICE_CLS_CURRENT, DEVICE_CLS_ENERGY, DEVICE_CLS_PWR, DEVICE_CLS_VOLTAGE, DEVICE_CLS_FREQ, DEVICE_CLS_TEMP};
|
enum {DEVICE_CLS_NONE = 0, DEVICE_CLS_CURRENT, DEVICE_CLS_ENERGY, DEVICE_CLS_PWR, DEVICE_CLS_VOLTAGE, DEVICE_CLS_FREQ, DEVICE_CLS_TEMP};
|
||||||
|
@ -97,6 +101,10 @@ const byteAssign_t SystemConfigParaAssignment[] = {
|
||||||
};
|
};
|
||||||
#define HMSYSTEM_LIST_LEN (sizeof(SystemConfigParaAssignment) / sizeof(byteAssign_t))
|
#define HMSYSTEM_LIST_LEN (sizeof(SystemConfigParaAssignment) / sizeof(byteAssign_t))
|
||||||
|
|
||||||
|
const byteAssign_t AlarmDataAssignment[] = {
|
||||||
|
{ FLD_LAST_ALARM_CODE, UNIT_NONE, CH0, 0, 2, 1 }
|
||||||
|
};
|
||||||
|
#define HMALARMDATA_LIST_LEN (sizeof(AlarmDataAssignment) / sizeof(byteAssign_t))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -115,17 +115,20 @@ class Inverter {
|
||||||
RECORDTYPE *record; // pointer for values
|
RECORDTYPE *record; // pointer for values
|
||||||
uint16_t chMaxPwr[4]; // maximum power of the modules (Wp)
|
uint16_t chMaxPwr[4]; // maximum power of the modules (Wp)
|
||||||
char chName[4][MAX_NAME_LENGTH]; // human readable name for channel
|
char chName[4][MAX_NAME_LENGTH]; // human readable name for channel
|
||||||
|
String lastAlarmMsg;
|
||||||
bool initialized; // needed to check if the inverter was correctly added (ESP32 specific - union types are never null)
|
bool initialized; // needed to check if the inverter was correctly added (ESP32 specific - union types are never null)
|
||||||
|
|
||||||
Inverter() {
|
Inverter() {
|
||||||
ts = 0;
|
ts = 0;
|
||||||
powerLimit[0] = 0xffff; // 65535 W Limit -> unlimited
|
powerLimit[0] = 0xffff; // 65535 W Limit -> unlimited
|
||||||
powerLimit[1] = 0x0000; //
|
powerLimit[1] = NoPowerLimit; //
|
||||||
actPowerLimit = 0xffff; // init feedback from inverter to -1
|
actPowerLimit = 0xffff; // init feedback from inverter to -1
|
||||||
devControlRequest = false;
|
devControlRequest = false;
|
||||||
devControlCmd = 0xff;
|
devControlCmd = InitDataState;
|
||||||
initialized = false;
|
initialized = false;
|
||||||
fwVersion = 0;
|
fwVersion = 0;
|
||||||
|
lastAlarmMsg = "nothing";
|
||||||
|
alarmMesIndex = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
~Inverter() {
|
~Inverter() {
|
||||||
|
@ -141,7 +144,8 @@ class Inverter {
|
||||||
|
|
||||||
void setQueuedCmdFinished(){
|
void setQueuedCmdFinished(){
|
||||||
if (!_commandQueue.empty()){
|
if (!_commandQueue.empty()){
|
||||||
_commandQueue.pop(); // Will destroy CommandAbstract Class Object (?)
|
// Will destroy CommandAbstract Class Object (?)
|
||||||
|
_commandQueue.pop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -150,7 +154,14 @@ class Inverter {
|
||||||
if (_commandQueue.empty()){
|
if (_commandQueue.empty()){
|
||||||
// Fill with default commands
|
// Fill with default commands
|
||||||
enqueCommand<InfoCommand>(RealTimeRunData_Debug);
|
enqueCommand<InfoCommand>(RealTimeRunData_Debug);
|
||||||
//enqueCommand<InfoCommand>(SystemConfigPara);
|
if (fwVersion == 0)
|
||||||
|
{ // info needed maybe after "one night" (=> DC>0 to DC=0 and to DC>0) or reboot
|
||||||
|
enqueCommand<InfoCommand>(InverterDevInform_All);
|
||||||
|
}
|
||||||
|
if (actPowerLimit == 0xffff)
|
||||||
|
{ // info needed maybe after "one nigth" (=> DC>0 to DC=0 and to DC>0) or reboot
|
||||||
|
enqueCommand<InfoCommand>(SystemConfigPara);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return _commandQueue.front().get()->getCmd();
|
return _commandQueue.front().get()->getCmd();
|
||||||
}
|
}
|
||||||
|
@ -164,8 +175,6 @@ class Inverter {
|
||||||
memset(name, 0, MAX_NAME_LENGTH);
|
memset(name, 0, MAX_NAME_LENGTH);
|
||||||
memset(chName, 0, MAX_NAME_LENGTH * 4);
|
memset(chName, 0, MAX_NAME_LENGTH * 4);
|
||||||
memset(record, 0, sizeof(RECORDTYPE) * listLen);
|
memset(record, 0, sizeof(RECORDTYPE) * listLen);
|
||||||
enqueCommand<InfoCommand>(InverterDevInform_All);
|
|
||||||
enqueCommand<InfoCommand>(SystemConfigPara);
|
|
||||||
initialized = true;
|
initialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -206,12 +215,25 @@ class Inverter {
|
||||||
val <<= 8;
|
val <<= 8;
|
||||||
val |= buf[ptr];
|
val |= buf[ptr];
|
||||||
} while(++ptr != end);
|
} while(++ptr != end);
|
||||||
record[pos] = (RECORDTYPE)(val) / (RECORDTYPE)(div);
|
if ((RECORDTYPE)(div) > 1){
|
||||||
|
record[pos] = (RECORDTYPE)(val) / (RECORDTYPE)(div);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
record[pos] = (RECORDTYPE)(val);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
if (cmd == RealTimeRunData_Debug) {
|
if (cmd == RealTimeRunData_Debug) {
|
||||||
// get last alarm message index and save it in the inverter object
|
// get last alarm message index and save it in the inverter object
|
||||||
if (getPosByChFld(0, FLD_ALARM_MES_ID) == pos){
|
if (getPosByChFld(0, FLD_ALARM_MES_ID) == pos){
|
||||||
alarmMesIndex = record[pos];
|
if (alarmMesIndex < record[pos]){
|
||||||
|
alarmMesIndex = record[pos];
|
||||||
|
//enqueCommand<InfoCommand>(AlarmUpdate); // What is the function of AlarmUpdate?
|
||||||
|
enqueCommand<InfoCommand>(AlarmData);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
alarmMesIndex = record[pos]; // no change
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (cmd == InverterDevInform_All) {
|
if (cmd == InverterDevInform_All) {
|
||||||
|
@ -228,6 +250,11 @@ class Inverter {
|
||||||
DPRINT(DBG_DEBUG, F("Inverter actual power limit: ") + String(actPowerLimit));
|
DPRINT(DBG_DEBUG, F("Inverter actual power limit: ") + String(actPowerLimit));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (cmd == AlarmData){
|
||||||
|
if (getPosByChFld(0, FLD_LAST_ALARM_CODE) == pos){
|
||||||
|
lastAlarmMsg = getAlarmStr(record[pos]);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
RECORDTYPE getValue(uint8_t pos) {
|
RECORDTYPE getValue(uint8_t pos) {
|
||||||
|
@ -263,52 +290,273 @@ class Inverter {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t getLastTs(void) {
|
uint32_t getLastTs(void)
|
||||||
|
{
|
||||||
DPRINTLN(DBG_VERBOSE, F("hmInverter.h:getLastTs"));
|
DPRINTLN(DBG_VERBOSE, F("hmInverter.h:getLastTs"));
|
||||||
return ts;
|
return ts;
|
||||||
}
|
}
|
||||||
|
|
||||||
void getAssignment() {
|
void getAssignment()
|
||||||
|
{
|
||||||
DPRINTLN(DBG_DEBUG, F("hmInverter.h:getAssignment"));
|
DPRINTLN(DBG_DEBUG, F("hmInverter.h:getAssignment"));
|
||||||
uint8_t cmd = getQueuedCmd();
|
// Default assignment;
|
||||||
switch (cmd)
|
if (INV_TYPE_1CH == type)
|
||||||
{
|
{
|
||||||
case RealTimeRunData_Debug:
|
listLen = (uint8_t)(HM1CH_LIST_LEN);
|
||||||
if (INV_TYPE_1CH == type)
|
assign = (byteAssign_t *)hm1chAssignment;
|
||||||
{
|
channels = 1;
|
||||||
listLen = (uint8_t)(HM1CH_LIST_LEN);
|
}
|
||||||
assign = (byteAssign_t *)hm1chAssignment;
|
else if (INV_TYPE_2CH == type)
|
||||||
channels = 1;
|
{
|
||||||
}
|
listLen = (uint8_t)(HM2CH_LIST_LEN);
|
||||||
else if (INV_TYPE_2CH == type)
|
assign = (byteAssign_t *)hm2chAssignment;
|
||||||
{
|
channels = 2;
|
||||||
listLen = (uint8_t)(HM2CH_LIST_LEN);
|
}
|
||||||
assign = (byteAssign_t *)hm2chAssignment;
|
else if (INV_TYPE_4CH == type)
|
||||||
channels = 2;
|
{
|
||||||
}
|
listLen = (uint8_t)(HM4CH_LIST_LEN);
|
||||||
else if (INV_TYPE_4CH == type)
|
assign = (byteAssign_t *)hm4chAssignment;
|
||||||
{
|
channels = 4;
|
||||||
listLen = (uint8_t)(HM4CH_LIST_LEN);
|
}
|
||||||
assign = (byteAssign_t *)hm4chAssignment;
|
else
|
||||||
channels = 4;
|
{
|
||||||
}
|
listLen = 0;
|
||||||
else
|
channels = 0;
|
||||||
{
|
assign = NULL;
|
||||||
listLen = 0;
|
}
|
||||||
channels = 0;
|
|
||||||
assign = NULL;
|
switch (getQueuedCmd()) {
|
||||||
}
|
case RealTimeRunData_Debug:
|
||||||
|
// Do nothing will use default
|
||||||
|
break;
|
||||||
|
case InverterDevInform_All:
|
||||||
|
listLen = (uint8_t)(HMINFO_LIST_LEN);
|
||||||
|
assign = (byteAssign_t *)InfoAssignment;
|
||||||
|
break;
|
||||||
|
case SystemConfigPara:
|
||||||
|
listLen = (uint8_t)(HMSYSTEM_LIST_LEN);
|
||||||
|
assign = (byteAssign_t *)SystemConfigParaAssignment;
|
||||||
|
break;
|
||||||
|
case AlarmData:
|
||||||
|
listLen = (uint8_t)(HMALARMDATA_LIST_LEN);
|
||||||
|
assign = (byteAssign_t *)AlarmDataAssignment;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
DPRINTLN(DBG_INFO, "Parser not implemented");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
String getAlarmStr(u_int16_t alarmCode)
|
||||||
|
{
|
||||||
|
switch (alarmCode)
|
||||||
|
{
|
||||||
|
case 1:
|
||||||
|
return String(F("Inverter start"));
|
||||||
break;
|
break;
|
||||||
case InverterDevInform_All:
|
case 2:
|
||||||
listLen = (uint8_t)(HMINFO_LIST_LEN);
|
return String(F("DTU command failed"));
|
||||||
assign = (byteAssign_t *)InfoAssignment;
|
|
||||||
break;
|
break;
|
||||||
case SystemConfigPara:
|
case 121:
|
||||||
listLen = (uint8_t)(HMSYSTEM_LIST_LEN);
|
return String(F("Over temperature protection"));
|
||||||
assign = (byteAssign_t *)SystemConfigParaAssignment;
|
break;
|
||||||
|
case 125:
|
||||||
|
return String(F("Grid configuration parameter error"));
|
||||||
|
break;
|
||||||
|
case 126:
|
||||||
|
return String(F("Software error code 126"));
|
||||||
|
break;
|
||||||
|
case 127:
|
||||||
|
return String(F("Firmware error"));
|
||||||
|
break;
|
||||||
|
case 128:
|
||||||
|
return String(F("Software error code 128"));
|
||||||
|
break;
|
||||||
|
case 129:
|
||||||
|
return String(F("Software error code 129"));
|
||||||
|
break;
|
||||||
|
case 130:
|
||||||
|
return String(F("Offline"));
|
||||||
|
break;
|
||||||
|
case 141:
|
||||||
|
return String(F("Grid overvoltage"));
|
||||||
|
break;
|
||||||
|
case 142:
|
||||||
|
return String(F("Average grid overvoltage"));
|
||||||
|
break;
|
||||||
|
case 143:
|
||||||
|
return String(F("Grid undervoltage"));
|
||||||
|
break;
|
||||||
|
case 144:
|
||||||
|
return String(F("Grid overfrequency"));
|
||||||
|
break;
|
||||||
|
case 145:
|
||||||
|
return String(F("Grid underfrequency"));
|
||||||
|
break;
|
||||||
|
case 146:
|
||||||
|
return String(F("Rapid grid frequency change"));
|
||||||
|
break;
|
||||||
|
case 147:
|
||||||
|
return String(F("Power grid outage"));
|
||||||
|
break;
|
||||||
|
case 148:
|
||||||
|
return String(F("Grid disconnection"));
|
||||||
|
break;
|
||||||
|
case 149:
|
||||||
|
return String(F("Island detected"));
|
||||||
|
break;
|
||||||
|
case 205:
|
||||||
|
return String(F("Input port 1 & 2 overvoltage"));
|
||||||
|
break;
|
||||||
|
case 206:
|
||||||
|
return String(F("Input port 3 & 4 overvoltage"));
|
||||||
|
break;
|
||||||
|
case 207:
|
||||||
|
return String(F("Input port 1 & 2 undervoltage"));
|
||||||
|
break;
|
||||||
|
case 208:
|
||||||
|
return String(F("Input port 3 & 4 undervoltage"));
|
||||||
|
break;
|
||||||
|
case 209:
|
||||||
|
return String(F("Port 1 no input"));
|
||||||
|
break;
|
||||||
|
case 210:
|
||||||
|
return String(F("Port 2 no input"));
|
||||||
|
break;
|
||||||
|
case 211:
|
||||||
|
return String(F("Port 3 no input"));
|
||||||
|
break;
|
||||||
|
case 212:
|
||||||
|
return String(F("Port 4 no input"));
|
||||||
|
break;
|
||||||
|
case 213:
|
||||||
|
return String(F("PV-1 & PV-2 abnormal wiring"));
|
||||||
|
break;
|
||||||
|
case 214:
|
||||||
|
return String(F("PV-3 & PV-4 abnormal wiring"));
|
||||||
|
break;
|
||||||
|
case 215:
|
||||||
|
return String(F("PV-1 Input overvoltage"));
|
||||||
|
break;
|
||||||
|
case 216:
|
||||||
|
return String(F("PV-1 Input undervoltage"));
|
||||||
|
break;
|
||||||
|
case 217:
|
||||||
|
return String(F("PV-2 Input overvoltage"));
|
||||||
|
break;
|
||||||
|
case 218:
|
||||||
|
return String(F("PV-2 Input undervoltage"));
|
||||||
|
break;
|
||||||
|
case 219:
|
||||||
|
return String(F("PV-3 Input overvoltage"));
|
||||||
|
break;
|
||||||
|
case 220:
|
||||||
|
return String(F("PV-3 Input undervoltage"));
|
||||||
|
break;
|
||||||
|
case 221:
|
||||||
|
return String(F("PV-4 Input overvoltage"));
|
||||||
|
break;
|
||||||
|
case 222:
|
||||||
|
return String(F("PV-4 Input undervoltage"));
|
||||||
|
break;
|
||||||
|
case 301:
|
||||||
|
return String(F("Hardware error code 301"));
|
||||||
|
break;
|
||||||
|
case 302:
|
||||||
|
return String(F("Hardware error code 302"));
|
||||||
|
break;
|
||||||
|
case 303:
|
||||||
|
return String(F("Hardware error code 303"));
|
||||||
|
break;
|
||||||
|
case 304:
|
||||||
|
return String(F("Hardware error code 304"));
|
||||||
|
break;
|
||||||
|
case 305:
|
||||||
|
return String(F("Hardware error code 305"));
|
||||||
|
break;
|
||||||
|
case 306:
|
||||||
|
return String(F("Hardware error code 306"));
|
||||||
|
break;
|
||||||
|
case 307:
|
||||||
|
return String(F("Hardware error code 307"));
|
||||||
|
break;
|
||||||
|
case 308:
|
||||||
|
return String(F("Hardware error code 308"));
|
||||||
|
break;
|
||||||
|
case 309:
|
||||||
|
return String(F("Hardware error code 309"));
|
||||||
|
break;
|
||||||
|
case 310:
|
||||||
|
return String(F("Hardware error code 310"));
|
||||||
|
break;
|
||||||
|
case 311:
|
||||||
|
return String(F("Hardware error code 311"));
|
||||||
|
break;
|
||||||
|
case 312:
|
||||||
|
return String(F("Hardware error code 312"));
|
||||||
|
break;
|
||||||
|
case 313:
|
||||||
|
return String(F("Hardware error code 313"));
|
||||||
|
break;
|
||||||
|
case 314:
|
||||||
|
return String(F("Hardware error code 314"));
|
||||||
|
break;
|
||||||
|
case 5041:
|
||||||
|
return String(F("Error code-04 Port 1"));
|
||||||
|
break;
|
||||||
|
case 5042:
|
||||||
|
return String(F("Error code-04 Port 2"));
|
||||||
|
break;
|
||||||
|
case 5043:
|
||||||
|
return String(F("Error code-04 Port 3"));
|
||||||
|
break;
|
||||||
|
case 5044:
|
||||||
|
return String(F("Error code-04 Port 4"));
|
||||||
|
break;
|
||||||
|
case 5051:
|
||||||
|
return String(F("PV Input 1 Overvoltage/Undervoltage"));
|
||||||
|
break;
|
||||||
|
case 5052:
|
||||||
|
return String(F("PV Input 2 Overvoltage/Undervoltage"));
|
||||||
|
break;
|
||||||
|
case 5053:
|
||||||
|
return String(F("PV Input 3 Overvoltage/Undervoltage"));
|
||||||
|
break;
|
||||||
|
case 5054:
|
||||||
|
return String(F("PV Input 4 Overvoltage/Undervoltage"));
|
||||||
|
break;
|
||||||
|
case 5060:
|
||||||
|
return String(F("Abnormal bias"));
|
||||||
|
break;
|
||||||
|
case 5070:
|
||||||
|
return String(F("Over temperature protection"));
|
||||||
|
break;
|
||||||
|
case 5080:
|
||||||
|
return String(F("Grid Overvoltage/Undervoltage"));
|
||||||
|
break;
|
||||||
|
case 5090:
|
||||||
|
return String(F("Grid Overfrequency/Underfrequency"));
|
||||||
|
break;
|
||||||
|
case 5100:
|
||||||
|
return String(F("Island detected"));
|
||||||
|
break;
|
||||||
|
case 5120:
|
||||||
|
return String(F("EEPROM reading and writing error"));
|
||||||
|
break;
|
||||||
|
case 5150:
|
||||||
|
return String(F("10 min value grid overvoltage"));
|
||||||
|
break;
|
||||||
|
case 5200:
|
||||||
|
return String(F("Firmware error"));
|
||||||
|
break;
|
||||||
|
case 8310:
|
||||||
|
return String(F("Shut down"));
|
||||||
|
break;
|
||||||
|
case 9000:
|
||||||
|
return String(F("Microinverter is suspected of being stolen"));
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
DPRINTLN(DBG_INFO, "Parser not implemented");
|
return String(F("Unknown"));
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,6 @@
|
||||||
#define DEFAULT_RECV_CHANNEL 3
|
#define DEFAULT_RECV_CHANNEL 3
|
||||||
#define SPI_SPEED 1000000
|
#define SPI_SPEED 1000000
|
||||||
|
|
||||||
#define DTU_RADIO_ID ((uint64_t)0x1234567801ULL)
|
|
||||||
#define DUMMY_RADIO_ID ((uint64_t)0xDEADBEEF01ULL)
|
#define DUMMY_RADIO_ID ((uint64_t)0xDEADBEEF01ULL)
|
||||||
|
|
||||||
#define RF_CHANNELS 5
|
#define RF_CHANNELS 5
|
||||||
|
@ -192,10 +191,9 @@ class HmRadio {
|
||||||
mTxBuf[10] = cmd; // cid
|
mTxBuf[10] = cmd; // cid
|
||||||
mTxBuf[11] = 0x00;
|
mTxBuf[11] = 0x00;
|
||||||
CP_U32_LittleEndian(&mTxBuf[12], ts);
|
CP_U32_LittleEndian(&mTxBuf[12], ts);
|
||||||
if (cmd == RealTimeRunData_Debug || cmd == AlarmData || cmd == AlarmUpdate ){
|
if (cmd == RealTimeRunData_Debug || cmd == AlarmData ){
|
||||||
mTxBuf[18] = (alarmMesId >> 8) & 0xff;
|
mTxBuf[18] = (alarmMesId >> 8) & 0xff;
|
||||||
mTxBuf[19] = (alarmMesId ) & 0xff;
|
mTxBuf[19] = (alarmMesId ) & 0xff;
|
||||||
//mTxBuf[19] = 0x05; // ToDo: Shall be the last received Alarm Index Number
|
|
||||||
} else {
|
} else {
|
||||||
mTxBuf[18] = 0x00;
|
mTxBuf[18] = 0x00;
|
||||||
mTxBuf[19] = 0x00;
|
mTxBuf[19] = 0x00;
|
||||||
|
|
|
@ -2,6 +2,8 @@ import re
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
def convert2Header(inFile):
|
def convert2Header(inFile):
|
||||||
fileType = inFile.split(".")[1]
|
fileType = inFile.split(".")[1]
|
||||||
define = inFile.split(".")[0].upper()
|
define = inFile.split(".")[0].upper()
|
||||||
|
@ -12,8 +14,10 @@ def convert2Header(inFile):
|
||||||
print("ok")
|
print("ok")
|
||||||
outName = "html/" + "h/" + inFileVarName + ".h"
|
outName = "html/" + "h/" + inFileVarName + ".h"
|
||||||
inFile = "html/" + inFile
|
inFile = "html/" + inFile
|
||||||
|
Path("html/h").mkdir(exist_ok=True)
|
||||||
else:
|
else:
|
||||||
outName = "h/" + inFileVarName + ".h"
|
outName = "h/" + inFileVarName + ".h"
|
||||||
|
Path("h").mkdir(exist_ok=True)
|
||||||
|
|
||||||
f = open(inFile, "r")
|
f = open(inFile, "r")
|
||||||
data = f.read().replace('\n', '')
|
data = f.read().replace('\n', '')
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
#ifndef __INDEX_HTML_H__
|
|
||||||
#define __INDEX_HTML_H__
|
|
||||||
const char index_html[] PROGMEM = "<!doctype html><html><head><title>Index - {DEVICE}</title><link rel=\"stylesheet\" type=\"text/css\" href=\"style.css\"/><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"><script type=\"text/javascript\">getAjax('/uptime', 'uptime');getAjax('/cmdstat', 'cmds');window.setInterval(\"getAjax('/uptime', 'uptime')\", {JS_TS});window.setInterval(\"getAjax('/cmdstat', 'cmds')\", {JS_TS});function getAjax(url, resid) {var http = null;http = new XMLHttpRequest();if(http != null) {http.open(\"GET\", url, true);http.onreadystatechange = print;http.send(null);}function print() {if(http.readyState == 4) {document.getElementById(resid).innerHTML = http.responseText;}}}function getInverterInfo(data){var http = null;http = new XMLHttpRequest();if(http != null) {http.open(\"POST\", \"/api\");http.setRequestHeader(\"Accept\", \"application/json\");http.setRequestHeader(\"Content-Type\", \"application/json\");http.send(data);}}</script></head><body><h1>AHOY - {DEVICE}</h1><div id=\"content\" class=\"content\"><p><a href=\"/visualization\">Visualization</a><br/><br/><a href=\"/setup\">Setup</a><br/></p><p><span class=\"des\">Uptime: </span><span id=\"uptime\"></span></p><p><span class=\"des\">Statistics: </span><pre id=\"cmds\"></pre></p><p>Every {TS}seconds the values are updated</p><div id=\"note\">This project was started from <a href=\"https://www.mikrocontroller.net/topic/525778\" target=\"_blank\">this discussion. (Mikrocontroller.net)</a><br/>New updates can be found on Github: <a href=\"https://github.com/grindylow/ahoy\" target=\"_blank\">https://github.com/grindylow/ahoy</a><br/><br/>Please report issues using the feature provided by <a href=\"https://github.com/grindylow/ahoy/issues\">Github</a><br/><br/>Discuss with us on <a href=\"https://discord.gg/WzhxEY62mB\">Discord</a><br/><p class=\"lic\"><a href=\"https://creativecommons.org/licenses/by-nc-sa/3.0/de\">Creative Commons - https://creativecommons.org/licenses/by-nc-sa/3.0/de/</a><br/>Check the licenses which are published on <a href=\"https://github.com/grindylow/ahoy\">https://github.com/grindylow/ahoy</a>as well</p></div></div><div id=\"footer\"><p class=\"left\">© 2022</p><p class=\"left\"><a href=\"/update\">Update Firmware</a></p><p class=\"right\">AHOY :: {VERSION}</p><p class=\"right\"><a href=\"/reboot\">Reboot</a></p><p class=\"right\">Git SHA: {BUILD}</p></div></body></html>";
|
|
||||||
#endif /*__INDEX_HTML_H__*/
|
|
|
@ -1,4 +0,0 @@
|
||||||
#ifndef __STYLE_CSS_H__
|
|
||||||
#define __STYLE_CSS_H__
|
|
||||||
const char style_css[] PROGMEM = "h1 {margin:0;padding:20pt;font-size:22pt;color:#fff;background-color:#006ec0;display:block;text-transform:uppercase;}html, body {font-family:Arial;margin:0;padding:0;}p {text-align:justify;font-size:13pt;}p.lic, p.lic a {font-size:8pt;color:#999;}.des {margin-top:20px;font-size:13pt;color:#006ec0;}.s_active, .s_collapsible:hover {background-color:#006ec0;}.s_content {display:none;overflow:hidden;}.s_collapsible {background-color:#044e86;color:white;cursor:pointer;padding:18px;width:100%;border:none;text-align:left;outline:none;font-size:15px;margin-bottom:4px;}.subdes {font-size:12pt;color:#006ec0;margin-left:7px;}.subsubdes {font-size:12pt;color:#006ec0;margin:0 0 7px 12px;}.hide {display:none;}a:link, a:visited {text-decoration:none;font-size:13pt;color:#006ec0;}a:hover, a:focus {color:#f00;}a.erase {background-color:#006ec0;color:#fff;padding:7px;display:inline-block;margin-top:30px;}#content {padding:15px 15px 60px 15px;}#footer {position:fixed;bottom:0px;height:45px;background-color:#006ec0;width:100%;border-top:5px solid #fff;}#footer p, #footer a {color:#fff;padding:0 7px 0 7px;font-size:10pt !important;}div.content {background-color:#fff;padding-bottom:65px;overflow:auto;}input, select {padding:7px;font-size:13pt;}input.text, select {width:70%;box-sizing:border-box;margin-bottom:10px;border:1px solid #ccc;}input.sh {max-width:150px !important;margin-right:10px;}input.btn {background-color:#006ec0;color:#fff;border:0px;float:right;margin:10px 0 30px;text-transform:uppercase;}input.cb {margin-bottom:20px;}label {width:20%;display:inline-block;font-size:12pt;padding-right:10px;margin:10px 0px 0px 15px;vertical-align:top;}fieldset {margin-bottom:15px;}.left {float:left;}.right {float:right;}div.ch-iv {width:100%;background-color:#32b004;display:inline-block;margin-bottom:15px;padding-bottom:20px;overflow:auto;}div.ch {width:220px;min-height:350px;background-color:#006ec0;display:inline-block;margin:0 10px 15px 10px;overflow:auto;padding-bottom:20px;}div.ch .value, div.ch .info, div.ch .head, div.ch-iv .value, div.ch-iv .info, div.ch-iv .head {color:#fff;display:block;width:100%;text-align:center;}.subgrp {float:left;width:220px;}div.ch .unit, div.ch-iv .unit {font-size:19px;margin-left:10px;}div.ch .value, div.ch-iv .value {margin-top:20px;font-size:24px;}div.ch .info, div.ch-iv .info {margin-top:3px;font-size:10px;}div.ch .head {background-color:#003c80;padding:10px 0 10px 0;}div.ch-iv .head {background-color:#1c6800;padding:10px 0 10px 0;}div.iv {max-width:960px;margin-bottom:40px;}div.ts {font-size:13px;background-color:#ddd;border-top:7px solid #999;padding:7px;}div.modpwr, div.modname {width:70%;display:inline-block;}#note {margin:50px 10px 10px 10px;padding-top:10px;width:100%;border-top:1px solid #bbb;}@media(max-width:500px) {div.ch .unit, div.ch-iv .unit {font-size:18px;}div.ch {width:170px;min-height:100px }.subgrp {width:180px;}}";
|
|
||||||
#endif /*__STYLE_CSS_H__*/
|
|
|
@ -1,4 +0,0 @@
|
||||||
#ifndef __VISUALIZATION_HTML_H__
|
|
||||||
#define __VISUALIZATION_HTML_H__
|
|
||||||
const char visualization_html[] PROGMEM = "<!doctype html><html><head><title>Index - {DEVICE}</title><link rel=\"stylesheet\" type=\"text/css\" href=\"style.css\"/><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"><meta name=\"apple-mobile-web-app-capable\" content=\"yes\"><script type=\"text/javascript\">getAjax('/livedata', 'livedata');window.setInterval(\"getAjax('/livedata', 'livedata')\", {JS_TS});function getAjax(url, resid) {var http = null;http = new XMLHttpRequest();if(http != null) {http.open(\"GET\", url, true);http.onreadystatechange = print;http.send(null);}function print() {if(http.readyState == 4) {document.getElementById(resid).innerHTML = http.responseText;}}}</script><style type=\"text/css\"></style></head><body><h1>AHOY - {DEVICE}</h1><div id=\"content\" class=\"content\"><div id=\"livedata\"></div><p>Every {TS}seconds the values are updated</p></div><div id=\"footer\"><p class=\"left\">© 2022</p><p class=\"left\"><a href=\"/\">Home</a></p><p class=\"right\">AHOY :: {VERSION}</p></div></body></html>";
|
|
||||||
#endif /*__VISUALIZATION_HTML_H__*/
|
|
|
@ -102,7 +102,7 @@
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend class="des">MQTT</legend>
|
<legend class="des">MQTT</legend>
|
||||||
<label for="mqttAddr">Broker / Server IP</label>
|
<label for="mqttAddr">Broker / Server IP</label>
|
||||||
<input type="text" class="text" name="mqttAddr" value="{MQTT_ADDR}"/>
|
<input type="text" class="text" name="mqttAddr" value="{MQTT_ADDR}" maxlength="32" />
|
||||||
<label for="mqttPort">Port</label>
|
<label for="mqttPort">Port</label>
|
||||||
<input type="text" class="text" name="mqttPort" value="{MQTT_PORT}"/>
|
<input type="text" class="text" name="mqttPort" value="{MQTT_PORT}"/>
|
||||||
<label for="mqttUser">Username (optional)</label>
|
<label for="mqttUser">Username (optional)</label>
|
||||||
|
@ -128,7 +128,7 @@
|
||||||
<p class="des">Serial Console</p>
|
<p class="des">Serial Console</p>
|
||||||
<label for="serEn">print inverter data</label>
|
<label for="serEn">print inverter data</label>
|
||||||
<input type="checkbox" class="cb" name="serEn" {SER_VAL_CB}/><br/>
|
<input type="checkbox" class="cb" name="serEn" {SER_VAL_CB}/><br/>
|
||||||
<label for="serDbg">print RF24 debug</label>
|
<label for="serDbg">Serial Debug</label>
|
||||||
<input type="checkbox" class="cb" name="serDbg" {SER_DBG_CB}/><br/>
|
<input type="checkbox" class="cb" name="serDbg" {SER_DBG_CB}/><br/>
|
||||||
<label for="serIntvl">Interval [s]</label>
|
<label for="serIntvl">Interval [s]</label>
|
||||||
<input type="text" class="text" name="serIntvl" value="{SER_INTVL}"/>
|
<input type="text" class="text" name="serIntvl" value="{SER_INTVL}"/>
|
||||||
|
|
|
@ -85,7 +85,8 @@ class mqtt {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
boolean resub = false;
|
boolean resub = false;
|
||||||
if(!mClient->connected()) {
|
if(!mClient->connected() && (millis() - lastReconnect) > MQTT_RECONNECT_DELAY ) {
|
||||||
|
lastReconnect = millis();
|
||||||
if(strlen(mDevName) > 0) {
|
if(strlen(mDevName) > 0) {
|
||||||
// der Server und der Port müssen neu gesetzt werden,
|
// der Server und der Port müssen neu gesetzt werden,
|
||||||
// da ein MQTT_CONNECTION_LOST -3 die Werte zerstört hat.
|
// da ein MQTT_CONNECTION_LOST -3 die Werte zerstört hat.
|
||||||
|
@ -95,14 +96,14 @@ class mqtt {
|
||||||
resub = mClient->connect(mDevName, mCfg->user, mCfg->pwd);
|
resub = mClient->connect(mDevName, mCfg->user, mCfg->pwd);
|
||||||
else
|
else
|
||||||
resub = mClient->connect(mDevName);
|
resub = mClient->connect(mDevName);
|
||||||
}
|
// ein Subscribe ist nur nach einem connect notwendig
|
||||||
// ein Subscribe ist nur nach einem connect notwendig
|
if(resub) {
|
||||||
if(resub) {
|
char topic[MQTT_TOPIC_LEN + 13 ]; // "/devcontrol/#" --> + 6 byte
|
||||||
char topic[MQTT_TOPIC_LEN + 13 ]; // "/devcontrol/#" --> + 6 byte
|
// ToDo: "/devcontrol/#" is hardcoded
|
||||||
// ToDo: "/devcontrol/#" is hardcoded
|
snprintf(topic, MQTT_TOPIC_LEN + 13, "%s/devcontrol/#", mCfg->topic);
|
||||||
snprintf(topic, MQTT_TOPIC_LEN + 13, "%s/devcontrol/#", mCfg->topic);
|
DPRINTLN(DBG_INFO, F("subscribe to ") + String(topic));
|
||||||
DPRINTLN(DBG_INFO, F("subscribe to ") + String(topic));
|
mClient->subscribe(topic); // subscribe to mTopic + "/devcontrol/#"
|
||||||
mClient->subscribe(topic); // subscribe to mTopic + "/devcontrol/#"
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -113,6 +114,7 @@ class mqtt {
|
||||||
bool mAddressSet;
|
bool mAddressSet;
|
||||||
mqttConfig_t *mCfg;
|
mqttConfig_t *mCfg;
|
||||||
char mDevName[DEVNAME_LEN];
|
char mDevName[DEVNAME_LEN];
|
||||||
|
unsigned long lastReconnect = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif /*__MQTT_H_*/
|
#endif /*__MQTT_H_*/
|
||||||
|
|
|
@ -14,9 +14,11 @@ src_dir = .
|
||||||
[env]
|
[env]
|
||||||
framework = arduino
|
framework = arduino
|
||||||
|
|
||||||
|
build_flags =
|
||||||
|
-include "config.h"
|
||||||
; ;;;;; Possible Debug options ;;;;;;
|
; ;;;;; Possible Debug options ;;;;;;
|
||||||
; https://docs.platformio.org/en/latest/platforms/espressif8266.html#debug-level
|
; https://docs.platformio.org/en/latest/platforms/espressif8266.html#debug-level
|
||||||
;build_flags = -DDEBUG_ESP_PORT=Serial
|
;-DDEBUG_ESP_PORT=Serial
|
||||||
;-DDEBUG_ESP_CORE
|
;-DDEBUG_ESP_CORE
|
||||||
;-DDEBUG_ESP_WIFI
|
;-DDEBUG_ESP_WIFI
|
||||||
;-DDEBUG_ESP_HTTP_CLIENT
|
;-DDEBUG_ESP_HTTP_CLIENT
|
||||||
|
|
|
@ -24,6 +24,12 @@ def readVersion(path, infile):
|
||||||
src = path + ".pio/build/esp8266-release/firmware.bin"
|
src = path + ".pio/build/esp8266-release/firmware.bin"
|
||||||
dst = path + ".pio/build/out/" + versionout
|
dst = path + ".pio/build/out/" + versionout
|
||||||
os.rename(src, dst)
|
os.rename(src, dst)
|
||||||
|
|
||||||
|
versionout = version[:-1] + "_esp32_" + sha + ".bin"
|
||||||
|
src = path + ".pio/build/esp32-wroom32-release/firmware.bin"
|
||||||
|
dst = path + ".pio/build/out/" + versionout
|
||||||
|
os.rename(src, dst)
|
||||||
|
|
||||||
print("::set-output name=name::" + versionnumber[:-1] )
|
print("::set-output name=name::" + versionnumber[:-1] )
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,23 @@
|
||||||
#include "html/h/setup_html.h"
|
#include "html/h/setup_html.h"
|
||||||
#include "html/h/visualization_html.h"
|
#include "html/h/visualization_html.h"
|
||||||
|
|
||||||
|
|
||||||
|
const uint16_t pwrLimitOptionValues[] {
|
||||||
|
NoPowerLimit,
|
||||||
|
AbsolutNonPersistent,
|
||||||
|
AbsolutPersistent,
|
||||||
|
RelativNonPersistent,
|
||||||
|
RelativPersistent
|
||||||
|
};
|
||||||
|
|
||||||
|
const char* const pwrLimitOptions[] {
|
||||||
|
"no power limit",
|
||||||
|
"absolute in Watt non persistent",
|
||||||
|
"absolute in Watt persistent",
|
||||||
|
"relativ in percent non persistent",
|
||||||
|
"relativ in percent persistent"
|
||||||
|
};
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
web::web(app *main, sysConfig_t *sysCfg, config_t *config, char version[]) {
|
web::web(app *main, sysConfig_t *sysCfg, config_t *config, char version[]) {
|
||||||
mMain = main;
|
mMain = main;
|
||||||
|
@ -185,7 +202,7 @@ void web::showSetup(void) {
|
||||||
inv += F("<input type=\"text\" class=\"text\" name=\"inv") + String(i) + F("Addr\" value=\"");
|
inv += F("<input type=\"text\" class=\"text\" name=\"inv") + String(i) + F("Addr\" value=\"");
|
||||||
if(NULL != iv)
|
if(NULL != iv)
|
||||||
inv += String(iv->serial.u64, HEX);
|
inv += String(iv->serial.u64, HEX);
|
||||||
inv += F("\"/ maxlength=\"12\" onkeyup=\"checkSerial()\">");
|
inv += F("\"/ maxlength=\"12\">");
|
||||||
|
|
||||||
inv += F("<label for=\"inv") + String(i) + F("Name\">Name*</label>");
|
inv += F("<label for=\"inv") + String(i) + F("Name\">Name*</label>");
|
||||||
inv += F("<input type=\"text\" class=\"text\" name=\"inv") + String(i) + F("Name\" value=\"");
|
inv += F("<input type=\"text\" class=\"text\" name=\"inv") + String(i) + F("Name\" value=\"");
|
||||||
|
@ -200,21 +217,16 @@ void web::showSetup(void) {
|
||||||
inv += F("\"/ maxlength=\"") + String(6) + "\">";
|
inv += F("\"/ maxlength=\"") + String(6) + "\">";
|
||||||
|
|
||||||
inv += F("<label for=\"inv") + String(i) + F("ActivePowerLimitConType\">Active Power Limit Control Type</label>");
|
inv += F("<label for=\"inv") + String(i) + F("ActivePowerLimitConType\">Active Power Limit Control Type</label>");
|
||||||
inv += F("<select name=\"inv") + String(i);
|
inv += F("<select name=\"inv") + String(i) + F("PowerLimitControl\">");
|
||||||
// UGLY! But I do not know it a better way
|
for(uint8_t j = 0; j < 5; j++) {
|
||||||
// ToDo: Need Cookies, IndexDB or PWA for that or in general client browser storage
|
inv += F("<option value=\"") + String(pwrLimitOptionValues[j]) + F("\"");
|
||||||
if(NULL != iv){
|
if(NULL != iv) {
|
||||||
if(iv->powerLimit[1] == AbsolutNonPersistent)
|
if(iv->powerLimit[1] == pwrLimitOptionValues[j])
|
||||||
inv += F("PowerLimitControl\"><option value=\"0\">absolute in Watt non persistent</option><option value=\"1\">relativ in percent non persistent</option><option value=\"256\">absolute in Watt persistent</option><option value=\"257\">relativ in percent persistent</option></select>");
|
inv += F(" selected");
|
||||||
if(iv->powerLimit[1] == RelativNonPersistent)
|
}
|
||||||
inv += F("PowerLimitControl\"><option value=\"1\">relativ in percent non persistent</option><option value=\"0\">absolute in Watt non persistent</option><option value=\"256\">absolute in Watt persistent</option><option value=\"257\">relativ in percent persistent</option></select>");
|
inv += F(">") + String(pwrLimitOptions[j]) + F("</option>");
|
||||||
if(iv->powerLimit[1] == AbsolutPersistent)
|
}
|
||||||
inv += F("PowerLimitControl\"><option value=\"256\">absolute in Watt persistent</option><option value=\"1\">relativ in percent non persistent</option><option value=\"0\">absolute in Watt non persistent</option><option value=\"257\">relativ in percent persistent</option></select>");
|
inv += F("</select>");
|
||||||
if(iv->powerLimit[1] == RelativPersistent)
|
|
||||||
inv += F("PowerLimitControl\"><option value=\"257\">relativ in percent persistent</option><option value=\"256\">absolute in Watt persistent</option><option value=\"1\">relativ in percent non persistent</option><option value=\"0\">absolute in Watt non persistent</option></select>");
|
|
||||||
} else
|
|
||||||
inv += F("PowerLimitControl\"><option value=\"0\">absolute in Watt non persistent</option><option value=\"1\">relativ in percent non persistent</option><option value=\"256\">absolute in Watt persistent</option><option value=\"257\">relativ in percent persistent</option></select>");
|
|
||||||
// UGLY! But I do not know it a better way --//
|
|
||||||
|
|
||||||
inv += F("<label for=\"inv") + String(i) + F("ModPwr0\" name=\"lbl") + String(i);
|
inv += F("<label for=\"inv") + String(i) + F("ModPwr0\" name=\"lbl") + String(i);
|
||||||
inv += F("ModPwr\">Max Module Power (Wp)</label><div class=\"modpwr\">");
|
inv += F("ModPwr\">Max Module Power (Wp)</label><div class=\"modpwr\">");
|
||||||
|
@ -320,13 +332,14 @@ void web::showSave(void) {
|
||||||
iv->powerLimit[1] = actPwrLimitControl;
|
iv->powerLimit[1] = actPwrLimitControl;
|
||||||
iv->devControlCmd = ActivePowerContr;
|
iv->devControlCmd = ActivePowerContr;
|
||||||
iv->devControlRequest = true;
|
iv->devControlRequest = true;
|
||||||
if (iv->powerLimit[1] & 0x0001)
|
if ((iv->powerLimit[1] & 0x0001) == 0x0001)
|
||||||
DPRINTLN(DBG_INFO, F("Power limit for inverter ") + String(iv->id) + F(" set to ") + String(iv->powerLimit[0]) + F("%") );
|
DPRINTLN(DBG_INFO, F("Power limit for inverter ") + String(iv->id) + F(" set to ") + String(iv->powerLimit[0]) + F("%") );
|
||||||
else
|
else {
|
||||||
DPRINTLN(DBG_INFO, F("Power limit for inverter ") + String(iv->id) + F(" set to ") + String(iv->powerLimit[0]) + F("W") );
|
DPRINTLN(DBG_INFO, F("Power limit for inverter ") + String(iv->id) + F(" set to ") + String(iv->powerLimit[0]) + F("W") );
|
||||||
DPRINTLN(DBG_INFO, F("Power Limit Control Setting ") + String(iv->powerLimit[1]));
|
DPRINTLN(DBG_INFO, F("Power Limit Control Setting ") + String(iv->powerLimit[1]));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (actPwrLimit == 0xffff){ // set to 100%
|
if (actPwrLimit == 0xffff) { // set to 100%
|
||||||
iv->powerLimit[0] = 100;
|
iv->powerLimit[0] = 100;
|
||||||
iv->powerLimit[1] = RelativPersistent;
|
iv->powerLimit[1] = RelativPersistent;
|
||||||
iv->devControlCmd = ActivePowerContr;
|
iv->devControlCmd = ActivePowerContr;
|
||||||
|
@ -371,7 +384,9 @@ void web::showSave(void) {
|
||||||
|
|
||||||
// mqtt
|
// mqtt
|
||||||
if(mWeb->arg("mqttAddr") != "") {
|
if(mWeb->arg("mqttAddr") != "") {
|
||||||
mWeb->arg("mqttAddr").toCharArray(mConfig->mqtt.broker, MQTT_ADDR_LEN);
|
String addr = mWeb->arg("mqttAddr");
|
||||||
|
addr.trim();
|
||||||
|
addr.toCharArray(mConfig->mqtt.broker, MQTT_ADDR_LEN);
|
||||||
mWeb->arg("mqttUser").toCharArray(mConfig->mqtt.user, MQTT_USER_LEN);
|
mWeb->arg("mqttUser").toCharArray(mConfig->mqtt.user, MQTT_USER_LEN);
|
||||||
mWeb->arg("mqttPwd").toCharArray(mConfig->mqtt.pwd, MQTT_PWD_LEN);
|
mWeb->arg("mqttPwd").toCharArray(mConfig->mqtt.pwd, MQTT_PWD_LEN);
|
||||||
mWeb->arg("mqttTopic").toCharArray(mConfig->mqtt.topic, MQTT_TOPIC_LEN);
|
mWeb->arg("mqttTopic").toCharArray(mConfig->mqtt.topic, MQTT_TOPIC_LEN);
|
||||||
|
@ -421,7 +436,84 @@ void web::showVisualization(void) {
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
void web::showLiveData(void) {
|
void web::showLiveData(void) {
|
||||||
DPRINTLN(DBG_VERBOSE, F("web::showLiveData"));
|
DPRINTLN(DBG_VERBOSE, F("web::showLiveData"));
|
||||||
mWeb->send(200, F("text/html"), mMain->getLiveData());
|
|
||||||
|
String modHtml;
|
||||||
|
for (uint8_t id = 0; id < mMain->mSys->getNumInverters(); id++) {
|
||||||
|
Inverter<> *iv = mMain->mSys->getInverterByPos(id);
|
||||||
|
if (NULL != iv) {
|
||||||
|
#ifdef LIVEDATA_VISUALIZED
|
||||||
|
uint8_t modNum, pos;
|
||||||
|
switch (iv->type) {
|
||||||
|
default:
|
||||||
|
case INV_TYPE_1CH: modNum = 1; break;
|
||||||
|
case INV_TYPE_2CH: modNum = 2; break;
|
||||||
|
case INV_TYPE_4CH: modNum = 4; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
modHtml += F("<div class=\"iv\">"
|
||||||
|
"<div class=\"ch-iv\"><span class=\"head\">")
|
||||||
|
+ String(iv->name) + F(" Limit ")
|
||||||
|
+ String(iv->actPowerLimit) + F("%");
|
||||||
|
if(NoPowerLimit == iv->powerLimit[1])
|
||||||
|
modHtml += F(" (not controlled)");
|
||||||
|
modHtml += F(" | last Alarm: ") + iv->lastAlarmMsg + F("</span>");
|
||||||
|
|
||||||
|
uint8_t list[] = {FLD_UAC, FLD_IAC, FLD_PAC, FLD_F, FLD_PCT, FLD_T, FLD_YT, FLD_YD, FLD_PDC, FLD_EFF, FLD_PRA, FLD_ALARM_MES_ID};
|
||||||
|
|
||||||
|
for (uint8_t fld = 0; fld < 11; fld++) {
|
||||||
|
pos = (iv->getPosByChFld(CH0, list[fld]));
|
||||||
|
if (0xff != pos) {
|
||||||
|
modHtml += F("<div class=\"subgrp\">");
|
||||||
|
modHtml += F("<span class=\"value\">") + String(iv->getValue(pos));
|
||||||
|
modHtml += F("<span class=\"unit\">") + String(iv->getUnit(pos)) + F("</span></span>");
|
||||||
|
modHtml += F("<span class=\"info\">") + String(iv->getFieldName(pos)) + F("</span>");
|
||||||
|
modHtml += F("</div>");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
modHtml += "</div>";
|
||||||
|
|
||||||
|
for (uint8_t ch = 1; ch <= modNum; ch++) {
|
||||||
|
modHtml += F("<div class=\"ch\"><span class=\"head\">");
|
||||||
|
if (iv->chName[ch - 1][0] == 0)
|
||||||
|
modHtml += F("CHANNEL ") + String(ch);
|
||||||
|
else
|
||||||
|
modHtml += String(iv->chName[ch - 1]);
|
||||||
|
modHtml += F("</span>");
|
||||||
|
for (uint8_t j = 0; j < 6; j++) {
|
||||||
|
switch (j) {
|
||||||
|
default: pos = (iv->getPosByChFld(ch, FLD_UDC)); break;
|
||||||
|
case 1: pos = (iv->getPosByChFld(ch, FLD_IDC)); break;
|
||||||
|
case 2: pos = (iv->getPosByChFld(ch, FLD_PDC)); break;
|
||||||
|
case 3: pos = (iv->getPosByChFld(ch, FLD_YD)); break;
|
||||||
|
case 4: pos = (iv->getPosByChFld(ch, FLD_YT)); break;
|
||||||
|
case 5: pos = (iv->getPosByChFld(ch, FLD_IRR)); break;
|
||||||
|
}
|
||||||
|
if (0xff != pos) {
|
||||||
|
modHtml += F("<span class=\"value\">") + String(iv->getValue(pos));
|
||||||
|
modHtml += F("<span class=\"unit\">") + String(iv->getUnit(pos)) + F("</span></span>");
|
||||||
|
modHtml += F("<span class=\"info\">") + String(iv->getFieldName(pos)) + F("</span>");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
modHtml += "</div>";
|
||||||
|
yield();
|
||||||
|
}
|
||||||
|
modHtml += F("<div class=\"ts\">Last received data requested at: ") + mMain->getDateTimeStr(iv->ts) + F("</div>");
|
||||||
|
modHtml += F("</div>");
|
||||||
|
#else
|
||||||
|
// dump all data to web frontend
|
||||||
|
modHtml = F("<pre>");
|
||||||
|
char topic[30], val[10];
|
||||||
|
for (uint8_t i = 0; i < iv->listLen; i++) {
|
||||||
|
snprintf(topic, 30, "%s/ch%d/%s", iv->name, iv->assign[i].ch, iv->getFieldName(i));
|
||||||
|
snprintf(val, 10, "%.3f %s", iv->getValue(i), iv->getUnit(i));
|
||||||
|
modHtml += String(topic) + ": " + String(val) + "\n";
|
||||||
|
}
|
||||||
|
modHtml += F("</pre>");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mWeb->send(200, F("text/html"), modHtml);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -450,10 +542,11 @@ void web::showWebApi(void)
|
||||||
if (response["tx_request"] == (uint8_t)TX_REQ_INFO)
|
if (response["tx_request"] == (uint8_t)TX_REQ_INFO)
|
||||||
{
|
{
|
||||||
// if the AlarmData is requested set the Alarm Index to the requested one
|
// if the AlarmData is requested set the Alarm Index to the requested one
|
||||||
if (cmd == AlarmData){
|
if (cmd == AlarmData || cmd == AlarmUpdate){
|
||||||
iv->alarmMesIndex = response["payload"];
|
// set the AlarmMesIndex for the request from user input
|
||||||
|
iv->alarmMesIndex = response["payload"];
|
||||||
}
|
}
|
||||||
DPRINTLN(DBG_INFO, F("Will make tx-request 0x15 with subcmd ") + String(cmd) + F(" and payload ") + String(response["payload"]));
|
DPRINTLN(DBG_INFO, F("Will make tx-request 0x15 with subcmd ") + String(cmd) + F(" and payload ") + String((uint16_t) response["payload"]));
|
||||||
// process payload from web request corresponding to the cmd
|
// process payload from web request corresponding to the cmd
|
||||||
iv->enqueCommand<InfoCommand>(cmd);
|
iv->enqueCommand<InfoCommand>(cmd);
|
||||||
}
|
}
|
||||||
|
@ -488,6 +581,14 @@ void web::showWebApi(void)
|
||||||
iv->devControlRequest = true; // queue it in the request loop
|
iv->devControlRequest = true; // queue it in the request loop
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (response["cmd"] == (uint8_t)TurnOff){
|
||||||
|
iv->devControlCmd = TurnOff;
|
||||||
|
iv->devControlRequest = true; // queue it in the request loop
|
||||||
|
}
|
||||||
|
if (response["cmd"] == (uint8_t)TurnOn){
|
||||||
|
iv->devControlCmd = TurnOn;
|
||||||
|
iv->devControlRequest = true; // queue it in the request loop
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
mWeb->send(200, "text/json", "{success:true}");
|
mWeb->send(200, "text/json", "{success:true}");
|
||||||
|
|