Merge branch 'tbnobody:master' into AlarmMessageType
This commit is contained in:
commit
d07f91c0cc
13
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
13
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@ -47,7 +47,8 @@ body:
|
|||||||
label: Install Method
|
label: Install Method
|
||||||
description: How did you install OpenDTU?
|
description: How did you install OpenDTU?
|
||||||
options:
|
options:
|
||||||
- Pre-Compiled binary from GitHub
|
- Pre-Compiled binary from GitHub releases
|
||||||
|
- Pre-Compiled binary from GitHub actions/pull-request
|
||||||
- Self-Compiled
|
- Self-Compiled
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
@ -59,6 +60,14 @@ body:
|
|||||||
placeholder: "e.g. 359d513"
|
placeholder: "e.g. 359d513"
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
- type: input
|
||||||
|
id: environment
|
||||||
|
attributes:
|
||||||
|
label: What firmware variant (PIO Environment) are you using?
|
||||||
|
description: You can find this in by going to Info -> System
|
||||||
|
placeholder: "generic_esp32s3_usb"
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
- type: textarea
|
- type: textarea
|
||||||
id: logs
|
id: logs
|
||||||
attributes:
|
attributes:
|
||||||
@ -84,5 +93,5 @@ body:
|
|||||||
required: true
|
required: true
|
||||||
- label: I have updated the title field above with a concise description.
|
- label: I have updated the title field above with a concise description.
|
||||||
required: true
|
required: true
|
||||||
- label: I have double checked that my inverter does not contain a W in the model name (like HMS-xxxW) as they are not supported
|
- label: I have double checked that my inverter does not contain a W in the model name (like HMS-xxxW) as they are not supported.
|
||||||
required: true
|
required: true
|
||||||
|
|||||||
17
.github/workflows/build.yml
vendored
17
.github/workflows/build.yml
vendored
@ -43,7 +43,7 @@ jobs:
|
|||||||
environments: ${{ steps.envs.outputs.environments }}
|
environments: ${{ steps.envs.outputs.environments }}
|
||||||
|
|
||||||
build:
|
build:
|
||||||
name: Build Enviornments
|
name: Build Environments
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: get_default_envs
|
needs: get_default_envs
|
||||||
strategy:
|
strategy:
|
||||||
@ -79,18 +79,27 @@ jobs:
|
|||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
pip install --upgrade platformio setuptools
|
pip install --upgrade platformio setuptools
|
||||||
|
|
||||||
|
- name: Enable Corepack
|
||||||
|
run: |
|
||||||
|
cd webapp
|
||||||
|
corepack enable
|
||||||
|
|
||||||
- name: Setup Node.js and yarn
|
- name: Setup Node.js and yarn
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: "20"
|
node-version: "22"
|
||||||
cache: "yarn"
|
cache: "yarn"
|
||||||
cache-dependency-path: "webapp/yarn.lock"
|
cache-dependency-path: "webapp/yarn.lock"
|
||||||
|
|
||||||
- name: Install WebApp dependencies
|
- name: Install WebApp dependencies
|
||||||
run: yarn --cwd webapp install --frozen-lockfile
|
run: |
|
||||||
|
cd webapp
|
||||||
|
yarn install --frozen-lockfile
|
||||||
|
|
||||||
- name: Build WebApp
|
- name: Build WebApp
|
||||||
run: yarn --cwd webapp build
|
run: |
|
||||||
|
cd webapp
|
||||||
|
yarn build
|
||||||
|
|
||||||
- name: Build firmware
|
- name: Build firmware
|
||||||
run: pio run -e ${{ matrix.environment }}
|
run: pio run -e ${{ matrix.environment }}
|
||||||
|
|||||||
@ -18,6 +18,12 @@
|
|||||||
"fix"
|
"fix"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"title": "## 🌎 Web Application",
|
||||||
|
"labels": [
|
||||||
|
"webapp"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"title": "## 📚 Documentation",
|
"title": "## 📚 Documentation",
|
||||||
"labels": [
|
"labels": [
|
||||||
|
|||||||
2
.github/workflows/cpplint.yml
vendored
2
.github/workflows/cpplint.yml
vendored
@ -7,7 +7,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
|
|||||||
16
.github/workflows/yarnlint.yml
vendored
16
.github/workflows/yarnlint.yml
vendored
@ -6,17 +6,23 @@ jobs:
|
|||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
working-directory: webapp
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
|
- name: Enable Corepack
|
||||||
|
run: corepack enable
|
||||||
- name: Setup Node.js and yarn
|
- name: Setup Node.js and yarn
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: "18"
|
node-version: "22"
|
||||||
cache: "yarn"
|
cache: "yarn"
|
||||||
cache-dependency-path: "webapp/yarn.lock"
|
cache-dependency-path: "webapp/yarn.lock"
|
||||||
|
|
||||||
- name: Install WebApp dependencies
|
- name: Install WebApp dependencies
|
||||||
run: yarn --cwd webapp install --frozen-lockfile
|
run: yarn install --frozen-lockfile
|
||||||
|
|
||||||
- name: Linting
|
- name: Linting
|
||||||
run: yarn --cwd webapp lint
|
run: yarn lint
|
||||||
|
|||||||
28
.github/workflows/yarnprettier.yml
vendored
Normal file
28
.github/workflows/yarnprettier.yml
vendored
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
name: Yarn Prettier
|
||||||
|
|
||||||
|
on: [push, pull_request]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
working-directory: webapp
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Enable Corepack
|
||||||
|
run: corepack enable
|
||||||
|
- name: Setup Node.js and yarn
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: "22"
|
||||||
|
cache: "yarn"
|
||||||
|
cache-dependency-path: "webapp/yarn.lock"
|
||||||
|
|
||||||
|
- name: Install WebApp dependencies
|
||||||
|
run: yarn install --frozen-lockfile
|
||||||
|
|
||||||
|
- name: Check Formatting
|
||||||
|
run: yarn prettier --check src/
|
||||||
39
README.md
39
README.md
@ -40,41 +40,4 @@ Generated using: `git log --date=short --pretty=format:"* %h%x09%ad%x09%s" | gre
|
|||||||
|
|
||||||
## Currently supported Inverters
|
## Currently supported Inverters
|
||||||
|
|
||||||
| Model | Required RF Module | DC Inputs | MPP-Tracker | AC Phases |
|
A list of all currently supported inverters can be found [here](https://www.opendtu.solar/hardware/inverter_overview/)
|
||||||
| ---------------------| ------------------ | --------- | ----------- | --------- |
|
|
||||||
| Hoymiles HM-300-1T | NRF24L01+ | 1 | 1 | 1 |
|
|
||||||
| Hoymiles HM-350-1T | NRF24L01+ | 1 | 1 | 1 |
|
|
||||||
| Hoymiles HM-400-1T | NRF24L01+ | 1 | 1 | 1 |
|
|
||||||
| Hoymiles HM-600-2T | NRF24L01+ | 2 | 2 | 1 |
|
|
||||||
| Hoymiles HM-700-2T | NRF24L01+ | 2 | 2 | 1 |
|
|
||||||
| Hoymiles HM-800-2T | NRF24L01+ | 2 | 2 | 1 |
|
|
||||||
| Hoymiles HM-1000-4T | NRF24L01+ | 4 | 2 | 1 |
|
|
||||||
| Hoymiles HM-1200-4T | NRF24L01+ | 4 | 2 | 1 |
|
|
||||||
| Hoymiles HM-1500-4T | NRF24L01+ | 4 | 2 | 1 |
|
|
||||||
| Hoymiles HMS-300-1T | CMT2300A | 1 | 1 | 1 |
|
|
||||||
| Hoymiles HMS-350-1T | CMT2300A | 1 | 1 | 1 |
|
|
||||||
| Hoymiles HMS-400-1T | CMT2300A | 1 | 1 | 1 |
|
|
||||||
| Hoymiles HMS-450-1T | CMT2300A | 1 | 1 | 1 |
|
|
||||||
| Hoymiles HMS-500-1T | CMT2300A | 1 | 1 | 1 |
|
|
||||||
| Hoymiles HMS-600-2T | CMT2300A | 2 | 2 | 1 |
|
|
||||||
| Hoymiles HMS-700-2T | CMT2300A | 2 | 2 | 1 |
|
|
||||||
| Hoymiles HMS-800-2T | CMT2300A | 2 | 2 | 1 |
|
|
||||||
| Hoymiles HMS-900-2T | CMT2300A | 2 | 2 | 1 |
|
|
||||||
| Hoymiles HMS-1000-2T | CMT2300A | 2 | 2 | 1 |
|
|
||||||
| Hoymiles HMS-1600-4T | CMT2300A | 4 | 4 | 1 |
|
|
||||||
| Hoymiles HMS-1800-4T | CMT2300A | 4 | 4 | 1 |
|
|
||||||
| Hoymiles HMS-2000-4T | CMT2300A | 4 | 4 | 1 |
|
|
||||||
| Hoymiles HMT-1600-4T | CMT2300A | 4 | 2 | 3 |
|
|
||||||
| Hoymiles HMT-1800-4T | CMT2300A | 4 | 2 | 3 |
|
|
||||||
| Hoymiles HMT-2000-4T | CMT2300A | 4 | 2 | 3 |
|
|
||||||
| Hoymiles HMT-1800-6T | CMT2300A | 6 | 3 | 3 |
|
|
||||||
| Hoymiles HMT-2250-6T | CMT2300A | 6 | 3 | 3 |
|
|
||||||
| Solenso SOL-H350 | NRF24L01+ | 1 | 1 | 1 |
|
|
||||||
| Solenso SOL-H400 | NRF24L01+ | 1 | 1 | 1 |
|
|
||||||
| Solenso SOL-H800 | NRF24L01+ | 2 | 2 | 1 |
|
|
||||||
| TSUN TSOL-M350 | NRF24L01+ | 1 | 1 | 1 |
|
|
||||||
| TSUN TSOL-M800 | NRF24L01+ | 2 | 2 | 1 |
|
|
||||||
| TSUN TSOL-M1600 | NRF24L01+ | 4 | 2 | 1 |
|
|
||||||
| E-Star HERF-800 | NRF24L01+ | 2 | 2 | 1 |
|
|
||||||
| E-Star HERF-1600 | NRF24L01+ | 4 | 2 | 1 |
|
|
||||||
| E-Star HERF-1800 | NRF24L01+ | 4 | 2 | 1 |
|
|
||||||
|
|||||||
@ -1,6 +1,9 @@
|
|||||||
[
|
[
|
||||||
{
|
{
|
||||||
"name": "OpenDTU Fusion v1",
|
"name": "OpenDTU Fusion v1",
|
||||||
|
"links": [
|
||||||
|
{"name": "Information", "url": "https://github.com/markusdd/OpenDTUFusionDocs"}
|
||||||
|
],
|
||||||
"nrf24": {
|
"nrf24": {
|
||||||
"miso": 48,
|
"miso": 48,
|
||||||
"mosi": 35,
|
"mosi": 35,
|
||||||
@ -25,6 +28,9 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "OpenDTU Fusion v1 with SSD1306 Display",
|
"name": "OpenDTU Fusion v1 with SSD1306 Display",
|
||||||
|
"links": [
|
||||||
|
{"name": "Information", "url": "https://github.com/markusdd/OpenDTUFusionDocs"}
|
||||||
|
],
|
||||||
"nrf24": {
|
"nrf24": {
|
||||||
"miso": 48,
|
"miso": 48,
|
||||||
"mosi": 35,
|
"mosi": 35,
|
||||||
@ -54,6 +60,9 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "OpenDTU Fusion v1 with SH1106 Display",
|
"name": "OpenDTU Fusion v1 with SH1106 Display",
|
||||||
|
"links": [
|
||||||
|
{"name": "Information", "url": "https://github.com/markusdd/OpenDTUFusionDocs"}
|
||||||
|
],
|
||||||
"nrf24": {
|
"nrf24": {
|
||||||
"miso": 48,
|
"miso": 48,
|
||||||
"mosi": 35,
|
"mosi": 35,
|
||||||
@ -83,6 +92,9 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "OpenDTU Fusion v2 with CMT2300A and NRF24",
|
"name": "OpenDTU Fusion v2 with CMT2300A and NRF24",
|
||||||
|
"links": [
|
||||||
|
{"name": "Information", "url": "https://github.com/markusdd/OpenDTUFusionDocs"}
|
||||||
|
],
|
||||||
"nrf24": {
|
"nrf24": {
|
||||||
"miso": 48,
|
"miso": 48,
|
||||||
"mosi": 35,
|
"mosi": 35,
|
||||||
@ -115,6 +127,9 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "OpenDTU Fusion v2 with CMT2300A, NRF24 and SH1106 Display",
|
"name": "OpenDTU Fusion v2 with CMT2300A, NRF24 and SH1106 Display",
|
||||||
|
"links": [
|
||||||
|
{"name": "Information", "url": "https://github.com/markusdd/OpenDTUFusionDocs"}
|
||||||
|
],
|
||||||
"nrf24": {
|
"nrf24": {
|
||||||
"miso": 48,
|
"miso": 48,
|
||||||
"mosi": 35,
|
"mosi": 35,
|
||||||
@ -152,6 +167,9 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "OpenDTU Fusion v2 with CMT2300A, NRF24 and SSD1306 Display",
|
"name": "OpenDTU Fusion v2 with CMT2300A, NRF24 and SSD1306 Display",
|
||||||
|
"links": [
|
||||||
|
{"name": "Information", "url": "https://github.com/markusdd/OpenDTUFusionDocs"}
|
||||||
|
],
|
||||||
"nrf24": {
|
"nrf24": {
|
||||||
"miso": 48,
|
"miso": 48,
|
||||||
"mosi": 35,
|
"mosi": 35,
|
||||||
@ -186,5 +204,122 @@
|
|||||||
"data": 2,
|
"data": 2,
|
||||||
"clk": 1
|
"clk": 1
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "OpenDTU Fusion v2 PoE",
|
||||||
|
"links": [
|
||||||
|
{"name": "Information", "url": "https://github.com/markusdd/OpenDTUFusionDocs"}
|
||||||
|
],
|
||||||
|
"nrf24": {
|
||||||
|
"miso": 48,
|
||||||
|
"mosi": 35,
|
||||||
|
"clk": 36,
|
||||||
|
"irq": 47,
|
||||||
|
"en": 38,
|
||||||
|
"cs": 37
|
||||||
|
},
|
||||||
|
"cmt": {
|
||||||
|
"clk": 6,
|
||||||
|
"cs": 4,
|
||||||
|
"fcs": 21,
|
||||||
|
"sdio": 5,
|
||||||
|
"gpio2": 3,
|
||||||
|
"gpio3": 8
|
||||||
|
},
|
||||||
|
"w5500": {
|
||||||
|
"mosi": 40,
|
||||||
|
"miso": 41,
|
||||||
|
"sclk": 39,
|
||||||
|
"cs": 42,
|
||||||
|
"int": 44,
|
||||||
|
"rst": 43
|
||||||
|
},
|
||||||
|
"led": {
|
||||||
|
"led0": 17,
|
||||||
|
"led1": 18
|
||||||
|
},
|
||||||
|
"display": {
|
||||||
|
"type": 0,
|
||||||
|
"data": 2,
|
||||||
|
"clk": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "OpenDTU Fusion v2 PoE with SH1106 Display",
|
||||||
|
"links": [
|
||||||
|
{"name": "Information", "url": "https://github.com/markusdd/OpenDTUFusionDocs"}
|
||||||
|
],
|
||||||
|
"nrf24": {
|
||||||
|
"miso": 48,
|
||||||
|
"mosi": 35,
|
||||||
|
"clk": 36,
|
||||||
|
"irq": 47,
|
||||||
|
"en": 38,
|
||||||
|
"cs": 37
|
||||||
|
},
|
||||||
|
"cmt": {
|
||||||
|
"clk": 6,
|
||||||
|
"cs": 4,
|
||||||
|
"fcs": 21,
|
||||||
|
"sdio": 5,
|
||||||
|
"gpio2": 3,
|
||||||
|
"gpio3": 8
|
||||||
|
},
|
||||||
|
"w5500": {
|
||||||
|
"mosi": 40,
|
||||||
|
"miso": 41,
|
||||||
|
"sclk": 39,
|
||||||
|
"cs": 42,
|
||||||
|
"int": 44,
|
||||||
|
"rst": 43
|
||||||
|
},
|
||||||
|
"led": {
|
||||||
|
"led0": 17,
|
||||||
|
"led1": 18
|
||||||
|
},
|
||||||
|
"display": {
|
||||||
|
"type": 3,
|
||||||
|
"data": 2,
|
||||||
|
"clk": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "OpenDTU Fusion v2 PoE with SSD1306 Display",
|
||||||
|
"links": [
|
||||||
|
{"name": "Information", "url": "https://github.com/markusdd/OpenDTUFusionDocs"}
|
||||||
|
],
|
||||||
|
"nrf24": {
|
||||||
|
"miso": 48,
|
||||||
|
"mosi": 35,
|
||||||
|
"clk": 36,
|
||||||
|
"irq": 47,
|
||||||
|
"en": 38,
|
||||||
|
"cs": 37
|
||||||
|
},
|
||||||
|
"cmt": {
|
||||||
|
"clk": 6,
|
||||||
|
"cs": 4,
|
||||||
|
"fcs": 21,
|
||||||
|
"sdio": 5,
|
||||||
|
"gpio2": 3,
|
||||||
|
"gpio3": 8
|
||||||
|
},
|
||||||
|
"w5500": {
|
||||||
|
"mosi": 40,
|
||||||
|
"miso": 41,
|
||||||
|
"sclk": 39,
|
||||||
|
"cs": 42,
|
||||||
|
"int": 44,
|
||||||
|
"rst": 43
|
||||||
|
},
|
||||||
|
"led": {
|
||||||
|
"led0": 17,
|
||||||
|
"led1": 18
|
||||||
|
},
|
||||||
|
"display": {
|
||||||
|
"type": 2,
|
||||||
|
"data": 2,
|
||||||
|
"clk": 1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@ -6,29 +6,42 @@
|
|||||||
#include <TaskSchedulerDeclarations.h>
|
#include <TaskSchedulerDeclarations.h>
|
||||||
|
|
||||||
// mqtt discovery device classes
|
// mqtt discovery device classes
|
||||||
enum {
|
enum DeviceClassType {
|
||||||
DEVICE_CLS_NONE = 0,
|
DEVICE_CLS_NONE = 0,
|
||||||
DEVICE_CLS_CURRENT,
|
DEVICE_CLS_CURRENT,
|
||||||
DEVICE_CLS_ENERGY,
|
DEVICE_CLS_ENERGY,
|
||||||
DEVICE_CLS_PWR,
|
DEVICE_CLS_PWR,
|
||||||
DEVICE_CLS_VOLTAGE,
|
DEVICE_CLS_VOLTAGE,
|
||||||
DEVICE_CLS_FREQ,
|
DEVICE_CLS_FREQ,
|
||||||
DEVICE_CLS_TEMP,
|
|
||||||
DEVICE_CLS_POWER_FACTOR,
|
DEVICE_CLS_POWER_FACTOR,
|
||||||
DEVICE_CLS_REACTIVE_POWER
|
DEVICE_CLS_REACTIVE_POWER,
|
||||||
|
DEVICE_CLS_CONNECTIVITY,
|
||||||
|
DEVICE_CLS_DURATION,
|
||||||
|
DEVICE_CLS_SIGNAL_STRENGTH,
|
||||||
|
DEVICE_CLS_TEMPERATURE,
|
||||||
|
DEVICE_CLS_RESTART
|
||||||
};
|
};
|
||||||
const char* const deviceClasses[] = { 0, "current", "energy", "power", "voltage", "frequency", "temperature", "power_factor", "reactive_power" };
|
const char* const deviceClass_name[] = { 0, "current", "energy", "power", "voltage", "frequency", "power_factor", "reactive_power", "connectivity", "duration", "signal_strength", "temperature", "restart" };
|
||||||
enum {
|
|
||||||
|
enum StateClassType {
|
||||||
STATE_CLS_NONE = 0,
|
STATE_CLS_NONE = 0,
|
||||||
STATE_CLS_MEASUREMENT,
|
STATE_CLS_MEASUREMENT,
|
||||||
STATE_CLS_TOTAL_INCREASING
|
STATE_CLS_TOTAL_INCREASING
|
||||||
};
|
};
|
||||||
const char* const stateClasses[] = { 0, "measurement", "total_increasing" };
|
const char* const stateClass_name[] = { 0, "measurement", "total_increasing" };
|
||||||
|
|
||||||
|
enum CategoryType {
|
||||||
|
CATEGORY_NONE = 0,
|
||||||
|
CATEGORY_CONFIG,
|
||||||
|
CATEGORY_DIAGNOSTIC
|
||||||
|
};
|
||||||
|
const char* const category_name[] = { 0, "config", "diagnostic" };
|
||||||
|
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
FieldId_t fieldId; // field id
|
FieldId_t fieldId; // field id
|
||||||
uint8_t deviceClsId; // device class
|
DeviceClassType deviceClsId; // device class
|
||||||
uint8_t stateClsId; // state class
|
StateClassType stateClsId; // state class
|
||||||
} byteAssign_fieldDeviceClass_t;
|
} byteAssign_fieldDeviceClass_t;
|
||||||
|
|
||||||
const byteAssign_fieldDeviceClass_t deviceFieldAssignment[] = {
|
const byteAssign_fieldDeviceClass_t deviceFieldAssignment[] = {
|
||||||
@ -41,7 +54,7 @@ const byteAssign_fieldDeviceClass_t deviceFieldAssignment[] = {
|
|||||||
{ FLD_IAC, DEVICE_CLS_CURRENT, STATE_CLS_MEASUREMENT },
|
{ FLD_IAC, DEVICE_CLS_CURRENT, STATE_CLS_MEASUREMENT },
|
||||||
{ FLD_PAC, DEVICE_CLS_PWR, STATE_CLS_MEASUREMENT },
|
{ FLD_PAC, DEVICE_CLS_PWR, STATE_CLS_MEASUREMENT },
|
||||||
{ FLD_F, DEVICE_CLS_FREQ, STATE_CLS_MEASUREMENT },
|
{ FLD_F, DEVICE_CLS_FREQ, STATE_CLS_MEASUREMENT },
|
||||||
{ FLD_T, DEVICE_CLS_TEMP, STATE_CLS_MEASUREMENT },
|
{ FLD_T, DEVICE_CLS_TEMPERATURE, STATE_CLS_MEASUREMENT },
|
||||||
{ FLD_PF, DEVICE_CLS_POWER_FACTOR, STATE_CLS_MEASUREMENT },
|
{ FLD_PF, DEVICE_CLS_POWER_FACTOR, STATE_CLS_MEASUREMENT },
|
||||||
{ FLD_EFF, DEVICE_CLS_NONE, STATE_CLS_NONE },
|
{ FLD_EFF, DEVICE_CLS_NONE, STATE_CLS_NONE },
|
||||||
{ FLD_IRR, DEVICE_CLS_NONE, STATE_CLS_NONE },
|
{ FLD_IRR, DEVICE_CLS_NONE, STATE_CLS_NONE },
|
||||||
@ -58,13 +71,24 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
void loop();
|
void loop();
|
||||||
void publish(const String& subtopic, const String& payload);
|
static void publish(const String& subtopic, const String& payload);
|
||||||
void publishDtuSensor(const char* name, const char* device_class, const char* category, const char* icon, const char* unit_of_measure, const char* subTopic);
|
static void publish(const String& subtopic, const JsonDocument& doc);
|
||||||
void publishDtuBinarySensor(const char* name, const char* device_class, const char* category, const char* payload_on, const char* payload_off, const char* subTopic = "");
|
|
||||||
void publishInverterField(std::shared_ptr<InverterAbstract> inv, const ChannelType_t type, const ChannelNum_t channel, const byteAssign_fieldDeviceClass_t fieldType, const bool clear = false);
|
static void addCommonMetadata(JsonDocument& doc, const String& unit_of_measure, const String& icon, const DeviceClassType device_class, const StateClassType state_class, const CategoryType category);
|
||||||
void publishInverterButton(std::shared_ptr<InverterAbstract> inv, const char* caption, const char* icon, const char* category, const char* deviceClass, const char* subTopic, const char* payload);
|
|
||||||
void publishInverterNumber(std::shared_ptr<InverterAbstract> inv, const char* caption, const char* icon, const char* category, const char* commandTopic, const char* stateTopic, const char* unitOfMeasure, const int16_t min = 1, const int16_t max = 100, float step = 1.0);
|
// Binary Sensor
|
||||||
void publishInverterBinarySensor(std::shared_ptr<InverterAbstract> inv, const char* caption, const char* subTopic, const char* payload_on, const char* payload_off);
|
static void publishBinarySensor(JsonDocument& doc, const String& root_device, const String& unique_id_prefix, const String& name, const String& state_topic, const String& payload_on, const String& payload_off, const DeviceClassType device_class, const StateClassType state_class, const CategoryType category);
|
||||||
|
static void publishDtuBinarySensor(const String& name, const String& state_topic, const String& payload_on, const String& payload_off, const DeviceClassType device_class, const StateClassType state_class, const CategoryType category);
|
||||||
|
static void publishInverterBinarySensor(std::shared_ptr<InverterAbstract> inv, const String& name, const String& state_topic, const String& payload_on, const String& payload_off, const DeviceClassType device_class, const StateClassType state_class, const CategoryType category);
|
||||||
|
|
||||||
|
// Sensor
|
||||||
|
static void publishSensor(JsonDocument& doc, const String& root_device, const String& unique_id_prefix, const String& name, const String& state_topic, const String& unit_of_measure, const String& icon, const DeviceClassType device_class, const StateClassType state_class, const CategoryType category);
|
||||||
|
static void publishDtuSensor(const String& name, const String& state_topic, const String& unit_of_measure, const String& icon, const DeviceClassType device_class, const StateClassType state_class, const CategoryType category);
|
||||||
|
static void publishInverterSensor(std::shared_ptr<InverterAbstract> inv, const String& name, const String& state_topic, const String& unit_of_measure, const String& icon, const DeviceClassType device_class, const StateClassType state_class, const CategoryType category);
|
||||||
|
|
||||||
|
static void publishInverterField(std::shared_ptr<InverterAbstract> inv, const ChannelType_t type, const ChannelNum_t channel, const byteAssign_fieldDeviceClass_t fieldType, const bool clear = false);
|
||||||
|
static void publishInverterButton(std::shared_ptr<InverterAbstract> inv, const String& name, const String& state_topic, const String& payload, const String& icon, const DeviceClassType device_class, const StateClassType state_class, const CategoryType category);
|
||||||
|
static void publishInverterNumber(std::shared_ptr<InverterAbstract> inv, const String& name, const String& state_topic, const String& command_topic, const int16_t min, const int16_t max, float step, const String& unit_of_measure, const String& icon, const StateClassType state_class, const CategoryType category);
|
||||||
|
|
||||||
static void createInverterInfo(JsonDocument& doc, std::shared_ptr<InverterAbstract> inv);
|
static void createInverterInfo(JsonDocument& doc, std::shared_ptr<InverterAbstract> inv);
|
||||||
static void createDtuInfo(JsonDocument& doc);
|
static void createDtuInfo(JsonDocument& doc);
|
||||||
|
|||||||
@ -5,6 +5,8 @@
|
|||||||
#include <Hoymiles.h>
|
#include <Hoymiles.h>
|
||||||
#include <TaskSchedulerDeclarations.h>
|
#include <TaskSchedulerDeclarations.h>
|
||||||
#include <espMqttClient.h>
|
#include <espMqttClient.h>
|
||||||
|
#include <frozen/map.h>
|
||||||
|
#include <frozen/string.h>
|
||||||
|
|
||||||
class MqttHandleInverterClass {
|
class MqttHandleInverterClass {
|
||||||
public:
|
public:
|
||||||
@ -19,7 +21,6 @@ public:
|
|||||||
private:
|
private:
|
||||||
void loop();
|
void loop();
|
||||||
void publishField(std::shared_ptr<InverterAbstract> inv, const ChannelType_t type, const ChannelNum_t channel, const FieldId_t fieldId);
|
void publishField(std::shared_ptr<InverterAbstract> inv, const ChannelType_t type, const ChannelNum_t channel, const FieldId_t fieldId);
|
||||||
void onMqttMessage(const espMqttClientTypes::MessageProperties& properties, const char* topic, const uint8_t* payload, const size_t len, const size_t index, const size_t total);
|
|
||||||
|
|
||||||
Task _loopTask;
|
Task _loopTask;
|
||||||
|
|
||||||
@ -41,6 +42,29 @@ private:
|
|||||||
FLD_IRR,
|
FLD_IRR,
|
||||||
FLD_Q
|
FLD_Q
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum class Topic : unsigned {
|
||||||
|
LimitPersistentRelative,
|
||||||
|
LimitPersistentAbsolute,
|
||||||
|
LimitNonPersistentRelative,
|
||||||
|
LimitNonPersistentAbsolute,
|
||||||
|
Power,
|
||||||
|
Restart,
|
||||||
|
ResetRfStats,
|
||||||
|
};
|
||||||
|
|
||||||
|
static constexpr frozen::string _cmdtopic = "+/cmd/";
|
||||||
|
static constexpr frozen::map<frozen::string, Topic, 7> _subscriptions = {
|
||||||
|
{ "limit_persistent_relative", Topic::LimitPersistentRelative },
|
||||||
|
{ "limit_persistent_absolute", Topic::LimitPersistentAbsolute },
|
||||||
|
{ "limit_nonpersistent_relative", Topic::LimitNonPersistentRelative },
|
||||||
|
{ "limit_nonpersistent_absolute", Topic::LimitNonPersistentAbsolute },
|
||||||
|
{ "power", Topic::Power },
|
||||||
|
{ "restart", Topic::Restart },
|
||||||
|
{ "reset_rf_stats", Topic::ResetRfStats },
|
||||||
|
};
|
||||||
|
|
||||||
|
void onMqttMessage(Topic t, const espMqttClientTypes::MessageProperties& properties, const char* topic, const uint8_t* payload, const size_t len, const size_t index, const size_t total);
|
||||||
};
|
};
|
||||||
|
|
||||||
extern MqttHandleInverterClass MqttHandleInverter;
|
extern MqttHandleInverterClass MqttHandleInverter;
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "W5500.h"
|
||||||
#include <DNSServer.h>
|
#include <DNSServer.h>
|
||||||
#include <TaskSchedulerDeclarations.h>
|
#include <TaskSchedulerDeclarations.h>
|
||||||
#include <WiFi.h>
|
#include <WiFi.h>
|
||||||
@ -23,18 +24,18 @@ enum class network_event {
|
|||||||
NETWORK_EVENT_MAX
|
NETWORK_EVENT_MAX
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef std::function<void(network_event event)> NetworkEventCb;
|
typedef std::function<void(network_event event)> DtuNetworkEventCb;
|
||||||
|
|
||||||
typedef struct NetworkEventCbList {
|
typedef struct DtuNetworkEventCbList {
|
||||||
NetworkEventCb cb;
|
DtuNetworkEventCb cb;
|
||||||
network_event event;
|
network_event event;
|
||||||
|
|
||||||
NetworkEventCbList()
|
DtuNetworkEventCbList()
|
||||||
: cb(nullptr)
|
: cb(nullptr)
|
||||||
, event(network_event::NETWORK_UNKNOWN)
|
, event(network_event::NETWORK_UNKNOWN)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
} NetworkEventCbList_t;
|
} DtuNetworkEventCbList_t;
|
||||||
|
|
||||||
class NetworkSettingsClass {
|
class NetworkSettingsClass {
|
||||||
public:
|
public:
|
||||||
@ -53,7 +54,7 @@ public:
|
|||||||
bool isConnected() const;
|
bool isConnected() const;
|
||||||
network_mode NetworkMode() const;
|
network_mode NetworkMode() const;
|
||||||
|
|
||||||
bool onEvent(NetworkEventCb cbEvent, const network_event event = network_event::NETWORK_EVENT_MAX);
|
bool onEvent(DtuNetworkEventCb cbEvent, const network_event event = network_event::NETWORK_EVENT_MAX);
|
||||||
void raiseEvent(const network_event event);
|
void raiseEvent(const network_event event);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@ -62,7 +63,7 @@ private:
|
|||||||
void setStaticIp();
|
void setStaticIp();
|
||||||
void handleMDNS();
|
void handleMDNS();
|
||||||
void setupMode();
|
void setupMode();
|
||||||
void NetworkEvent(const WiFiEvent_t event);
|
void NetworkEvent(const WiFiEvent_t event, WiFiEventInfo_t info);
|
||||||
|
|
||||||
Task _loopTask;
|
Task _loopTask;
|
||||||
|
|
||||||
@ -81,8 +82,9 @@ private:
|
|||||||
bool _dnsServerStatus = false;
|
bool _dnsServerStatus = false;
|
||||||
network_mode _networkMode = network_mode::Undefined;
|
network_mode _networkMode = network_mode::Undefined;
|
||||||
bool _ethConnected = false;
|
bool _ethConnected = false;
|
||||||
std::vector<NetworkEventCbList_t> _cbEventList;
|
std::vector<DtuNetworkEventCbList_t> _cbEventList;
|
||||||
bool _lastMdnsEnabled = false;
|
bool _lastMdnsEnabled = false;
|
||||||
|
std::unique_ptr<W5500> _w5500;
|
||||||
};
|
};
|
||||||
|
|
||||||
extern NetworkSettingsClass NetworkSettings;
|
extern NetworkSettingsClass NetworkSettings;
|
||||||
@ -12,6 +12,7 @@
|
|||||||
|
|
||||||
struct PinMapping_t {
|
struct PinMapping_t {
|
||||||
char name[MAPPING_NAME_STRLEN + 1];
|
char name[MAPPING_NAME_STRLEN + 1];
|
||||||
|
|
||||||
int8_t nrf24_miso;
|
int8_t nrf24_miso;
|
||||||
int8_t nrf24_mosi;
|
int8_t nrf24_mosi;
|
||||||
int8_t nrf24_clk;
|
int8_t nrf24_clk;
|
||||||
@ -26,6 +27,14 @@ struct PinMapping_t {
|
|||||||
int8_t cmt_gpio3;
|
int8_t cmt_gpio3;
|
||||||
int8_t cmt_sdio;
|
int8_t cmt_sdio;
|
||||||
|
|
||||||
|
int8_t w5500_mosi;
|
||||||
|
int8_t w5500_miso;
|
||||||
|
int8_t w5500_sclk;
|
||||||
|
int8_t w5500_cs;
|
||||||
|
int8_t w5500_int;
|
||||||
|
int8_t w5500_rst;
|
||||||
|
|
||||||
|
#if CONFIG_ETH_USE_ESP32_EMAC
|
||||||
int8_t eth_phy_addr;
|
int8_t eth_phy_addr;
|
||||||
bool eth_enabled;
|
bool eth_enabled;
|
||||||
int eth_power;
|
int eth_power;
|
||||||
@ -33,11 +42,14 @@ struct PinMapping_t {
|
|||||||
int eth_mdio;
|
int eth_mdio;
|
||||||
eth_phy_type_t eth_type;
|
eth_phy_type_t eth_type;
|
||||||
eth_clock_mode_t eth_clk_mode;
|
eth_clock_mode_t eth_clk_mode;
|
||||||
|
#endif
|
||||||
|
|
||||||
uint8_t display_type;
|
uint8_t display_type;
|
||||||
uint8_t display_data;
|
uint8_t display_data;
|
||||||
uint8_t display_clk;
|
uint8_t display_clk;
|
||||||
uint8_t display_cs;
|
uint8_t display_cs;
|
||||||
uint8_t display_reset;
|
uint8_t display_reset;
|
||||||
|
|
||||||
int8_t led[PINMAPPING_LED_COUNT];
|
int8_t led[PINMAPPING_LED_COUNT];
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -49,7 +61,10 @@ public:
|
|||||||
|
|
||||||
bool isValidNrf24Config() const;
|
bool isValidNrf24Config() const;
|
||||||
bool isValidCmt2300Config() const;
|
bool isValidCmt2300Config() const;
|
||||||
|
bool isValidW5500Config() const;
|
||||||
|
#if CONFIG_ETH_USE_ESP32_EMAC
|
||||||
bool isValidEthConfig() const;
|
bool isValidEthConfig() const;
|
||||||
|
#endif
|
||||||
|
|
||||||
private:
|
private:
|
||||||
PinMapping_t _pinMapping;
|
PinMapping_t _pinMapping;
|
||||||
|
|||||||
18
include/RestartHelper.h
Normal file
18
include/RestartHelper.h
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <TaskSchedulerDeclarations.h>
|
||||||
|
|
||||||
|
class RestartHelperClass {
|
||||||
|
public:
|
||||||
|
RestartHelperClass();
|
||||||
|
void init(Scheduler& scheduler);
|
||||||
|
void triggerRestart();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void loop();
|
||||||
|
|
||||||
|
Task _rebootTask;
|
||||||
|
};
|
||||||
|
|
||||||
|
extern RestartHelperClass RestartHelper;
|
||||||
@ -9,7 +9,6 @@ public:
|
|||||||
static uint32_t getChipId();
|
static uint32_t getChipId();
|
||||||
static uint64_t generateDtuSerial();
|
static uint64_t generateDtuSerial();
|
||||||
static int getTimezoneOffset();
|
static int getTimezoneOffset();
|
||||||
static void restartDtu();
|
|
||||||
static bool checkJsonAlloc(const JsonDocument& doc, const char* function, const uint16_t line);
|
static bool checkJsonAlloc(const JsonDocument& doc, const char* function, const uint16_t line);
|
||||||
static void removeAllFiles();
|
static void removeAllFiles();
|
||||||
};
|
};
|
||||||
|
|||||||
29
include/W5500.h
Normal file
29
include/W5500.h
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <driver/spi_master.h>
|
||||||
|
#include <esp_eth.h> // required for esp_eth_handle_t
|
||||||
|
#include <esp_netif.h>
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
class W5500 {
|
||||||
|
private:
|
||||||
|
explicit W5500(spi_device_handle_t spi, gpio_num_t pin_int);
|
||||||
|
|
||||||
|
public:
|
||||||
|
W5500(const W5500&) = delete;
|
||||||
|
W5500& operator=(const W5500&) = delete;
|
||||||
|
~W5500();
|
||||||
|
|
||||||
|
static std::unique_ptr<W5500> setup(int8_t pin_mosi, int8_t pin_miso, int8_t pin_sclk, int8_t pin_cs, int8_t pin_int, int8_t pin_rst);
|
||||||
|
String macAddress();
|
||||||
|
|
||||||
|
private:
|
||||||
|
static bool connection_check_spi(spi_device_handle_t spi);
|
||||||
|
static bool connection_check_interrupt(gpio_num_t pin_int);
|
||||||
|
|
||||||
|
esp_eth_handle_t eth_handle;
|
||||||
|
esp_netif_t* eth_netif;
|
||||||
|
};
|
||||||
@ -30,6 +30,7 @@ class WebApiClass {
|
|||||||
public:
|
public:
|
||||||
WebApiClass();
|
WebApiClass();
|
||||||
void init(Scheduler& scheduler);
|
void init(Scheduler& scheduler);
|
||||||
|
void reload();
|
||||||
|
|
||||||
static bool checkCredentials(AsyncWebServerRequest* request);
|
static bool checkCredentials(AsyncWebServerRequest* request);
|
||||||
static bool checkCredentialsReadonly(AsyncWebServerRequest* request);
|
static bool checkCredentialsReadonly(AsyncWebServerRequest* request);
|
||||||
|
|||||||
@ -32,6 +32,7 @@ enum WebApiError {
|
|||||||
InverterChanged,
|
InverterChanged,
|
||||||
InverterDeleted,
|
InverterDeleted,
|
||||||
InverterOrdered,
|
InverterOrdered,
|
||||||
|
InverterStatsResetted,
|
||||||
|
|
||||||
LimitBase = 5000,
|
LimitBase = 5000,
|
||||||
LimitSerialZero,
|
LimitSerialZero,
|
||||||
|
|||||||
@ -14,4 +14,5 @@ private:
|
|||||||
void onInverterEdit(AsyncWebServerRequest* request);
|
void onInverterEdit(AsyncWebServerRequest* request);
|
||||||
void onInverterDelete(AsyncWebServerRequest* request);
|
void onInverterDelete(AsyncWebServerRequest* request);
|
||||||
void onInverterOrder(AsyncWebServerRequest* request);
|
void onInverterOrder(AsyncWebServerRequest* request);
|
||||||
|
void onInverterStatReset(AsyncWebServerRequest* request);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -8,9 +8,11 @@ class WebApiWsConsoleClass {
|
|||||||
public:
|
public:
|
||||||
WebApiWsConsoleClass();
|
WebApiWsConsoleClass();
|
||||||
void init(AsyncWebServer& server, Scheduler& scheduler);
|
void init(AsyncWebServer& server, Scheduler& scheduler);
|
||||||
|
void reload();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
AsyncWebSocket _ws;
|
AsyncWebSocket _ws;
|
||||||
|
AuthenticationMiddleware _simpleDigestAuth;
|
||||||
|
|
||||||
Task _wsCleanupTask;
|
Task _wsCleanupTask;
|
||||||
void wsCleanupTaskCb();
|
void wsCleanupTaskCb();
|
||||||
|
|||||||
@ -11,6 +11,7 @@ class WebApiWsLiveClass {
|
|||||||
public:
|
public:
|
||||||
WebApiWsLiveClass();
|
WebApiWsLiveClass();
|
||||||
void init(AsyncWebServer& server, Scheduler& scheduler);
|
void init(AsyncWebServer& server, Scheduler& scheduler);
|
||||||
|
void reload();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static void generateInverterCommonJsonResponse(JsonObject& root, std::shared_ptr<InverterAbstract> inv);
|
static void generateInverterCommonJsonResponse(JsonObject& root, std::shared_ptr<InverterAbstract> inv);
|
||||||
@ -24,6 +25,7 @@ private:
|
|||||||
void onWebsocketEvent(AsyncWebSocket* server, AsyncWebSocketClient* client, AwsEventType type, void* arg, uint8_t* data, size_t len);
|
void onWebsocketEvent(AsyncWebSocket* server, AsyncWebSocketClient* client, AwsEventType type, void* arg, uint8_t* data, size_t len);
|
||||||
|
|
||||||
AsyncWebSocket _ws;
|
AsyncWebSocket _ws;
|
||||||
|
AuthenticationMiddleware _simpleDigestAuth;
|
||||||
|
|
||||||
uint32_t _lastPublishStats[INV_MAX_COUNT] = { 0 };
|
uint32_t _lastPublishStats[INV_MAX_COUNT] = { 0 };
|
||||||
|
|
||||||
|
|||||||
@ -5,4 +5,5 @@
|
|||||||
|
|
||||||
|
|
||||||
extern const char *__COMPILED_GIT_HASH__;
|
extern const char *__COMPILED_GIT_HASH__;
|
||||||
|
extern const char *__COMPILED_GIT_BRANCH__;
|
||||||
// extern const char *__COMPILED_DATE_TIME_UTC_STR__;
|
// extern const char *__COMPILED_DATE_TIME_UTC_STR__;
|
||||||
|
|||||||
@ -1,142 +0,0 @@
|
|||||||
#include "cmt_spi3.h"
|
|
||||||
#include <Arduino.h>
|
|
||||||
#include <driver/spi_master.h>
|
|
||||||
#include <esp_rom_gpio.h> // for esp_rom_gpio_connect_out_signal
|
|
||||||
|
|
||||||
SemaphoreHandle_t paramLock = NULL;
|
|
||||||
#define SPI_PARAM_LOCK() \
|
|
||||||
do { \
|
|
||||||
} while (xSemaphoreTake(paramLock, portMAX_DELAY) != pdPASS)
|
|
||||||
#define SPI_PARAM_UNLOCK() xSemaphoreGive(paramLock)
|
|
||||||
|
|
||||||
// for ESP32 this is the so-called HSPI
|
|
||||||
// for ESP32-S2/S3/C3 this nomenclature does not really exist anymore,
|
|
||||||
// it is simply the first externally usable hardware SPI master controller
|
|
||||||
#define SPI_CMT SPI2_HOST
|
|
||||||
|
|
||||||
spi_device_handle_t spi_reg, spi_fifo;
|
|
||||||
|
|
||||||
void cmt_spi3_init(const int8_t pin_sdio, const int8_t pin_clk, const int8_t pin_cs, const int8_t pin_fcs, const uint32_t spi_speed)
|
|
||||||
{
|
|
||||||
paramLock = xSemaphoreCreateMutex();
|
|
||||||
|
|
||||||
spi_bus_config_t buscfg = {
|
|
||||||
.mosi_io_num = pin_sdio,
|
|
||||||
.miso_io_num = -1, // single wire MOSI/MISO
|
|
||||||
.sclk_io_num = pin_clk,
|
|
||||||
.quadwp_io_num = -1,
|
|
||||||
.quadhd_io_num = -1,
|
|
||||||
.max_transfer_sz = 32,
|
|
||||||
};
|
|
||||||
spi_device_interface_config_t devcfg = {
|
|
||||||
.command_bits = 1,
|
|
||||||
.address_bits = 7,
|
|
||||||
.dummy_bits = 0,
|
|
||||||
.mode = 0, // SPI mode 0
|
|
||||||
.cs_ena_pretrans = 1,
|
|
||||||
.cs_ena_posttrans = 1,
|
|
||||||
.clock_speed_hz = spi_speed,
|
|
||||||
.spics_io_num = pin_cs,
|
|
||||||
.flags = SPI_DEVICE_HALFDUPLEX | SPI_DEVICE_3WIRE,
|
|
||||||
.queue_size = 1,
|
|
||||||
.pre_cb = NULL,
|
|
||||||
.post_cb = NULL,
|
|
||||||
};
|
|
||||||
|
|
||||||
ESP_ERROR_CHECK(spi_bus_initialize(SPI_CMT, &buscfg, SPI_DMA_DISABLED));
|
|
||||||
ESP_ERROR_CHECK(spi_bus_add_device(SPI_CMT, &devcfg, &spi_reg));
|
|
||||||
|
|
||||||
// FiFo
|
|
||||||
spi_device_interface_config_t devcfg2 = {
|
|
||||||
.command_bits = 0,
|
|
||||||
.address_bits = 0,
|
|
||||||
.dummy_bits = 0,
|
|
||||||
.mode = 0, // SPI mode 0
|
|
||||||
.cs_ena_pretrans = 2,
|
|
||||||
.cs_ena_posttrans = (uint8_t)(1 / (spi_speed * 10e6 * 2) + 2), // >2 us
|
|
||||||
.clock_speed_hz = spi_speed,
|
|
||||||
.spics_io_num = pin_fcs,
|
|
||||||
.flags = SPI_DEVICE_HALFDUPLEX | SPI_DEVICE_3WIRE,
|
|
||||||
.queue_size = 1,
|
|
||||||
.pre_cb = NULL,
|
|
||||||
.post_cb = NULL,
|
|
||||||
};
|
|
||||||
ESP_ERROR_CHECK(spi_bus_add_device(SPI_CMT, &devcfg2, &spi_fifo));
|
|
||||||
|
|
||||||
esp_rom_gpio_connect_out_signal(pin_sdio, spi_periph_signal[SPI_CMT].spid_out, true, false);
|
|
||||||
delay(100);
|
|
||||||
}
|
|
||||||
|
|
||||||
void cmt_spi3_write(const uint8_t addr, const uint8_t dat)
|
|
||||||
{
|
|
||||||
uint8_t tx_data;
|
|
||||||
tx_data = ~dat;
|
|
||||||
spi_transaction_t t = {
|
|
||||||
.cmd = 1,
|
|
||||||
.addr = ~addr,
|
|
||||||
.length = 8,
|
|
||||||
.tx_buffer = &tx_data,
|
|
||||||
.rx_buffer = NULL
|
|
||||||
};
|
|
||||||
SPI_PARAM_LOCK();
|
|
||||||
ESP_ERROR_CHECK(spi_device_polling_transmit(spi_reg, &t));
|
|
||||||
SPI_PARAM_UNLOCK();
|
|
||||||
delayMicroseconds(100);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t cmt_spi3_read(const uint8_t addr)
|
|
||||||
{
|
|
||||||
uint8_t rx_data;
|
|
||||||
spi_transaction_t t = {
|
|
||||||
.cmd = 0,
|
|
||||||
.addr = ~addr,
|
|
||||||
.length = 8,
|
|
||||||
.rxlength = 8,
|
|
||||||
.tx_buffer = NULL,
|
|
||||||
.rx_buffer = &rx_data
|
|
||||||
};
|
|
||||||
SPI_PARAM_LOCK();
|
|
||||||
ESP_ERROR_CHECK(spi_device_polling_transmit(spi_reg, &t));
|
|
||||||
SPI_PARAM_UNLOCK();
|
|
||||||
delayMicroseconds(100);
|
|
||||||
return rx_data;
|
|
||||||
}
|
|
||||||
|
|
||||||
void cmt_spi3_write_fifo(const uint8_t* buf, const uint16_t len)
|
|
||||||
{
|
|
||||||
uint8_t tx_data;
|
|
||||||
|
|
||||||
spi_transaction_t t = {
|
|
||||||
.length = 8,
|
|
||||||
.tx_buffer = &tx_data, // reference to write data
|
|
||||||
.rx_buffer = NULL
|
|
||||||
};
|
|
||||||
|
|
||||||
SPI_PARAM_LOCK();
|
|
||||||
for (uint8_t i = 0; i < len; i++) {
|
|
||||||
tx_data = ~buf[i]; // negate buffer contents
|
|
||||||
ESP_ERROR_CHECK(spi_device_polling_transmit(spi_fifo, &t));
|
|
||||||
delayMicroseconds(4); // > 4 us
|
|
||||||
}
|
|
||||||
SPI_PARAM_UNLOCK();
|
|
||||||
}
|
|
||||||
|
|
||||||
void cmt_spi3_read_fifo(uint8_t* buf, const uint16_t len)
|
|
||||||
{
|
|
||||||
uint8_t rx_data;
|
|
||||||
|
|
||||||
spi_transaction_t t = {
|
|
||||||
.length = 8,
|
|
||||||
.rxlength = 8,
|
|
||||||
.tx_buffer = NULL,
|
|
||||||
.rx_buffer = &rx_data
|
|
||||||
};
|
|
||||||
|
|
||||||
SPI_PARAM_LOCK();
|
|
||||||
for (uint8_t i = 0; i < len; i++) {
|
|
||||||
ESP_ERROR_CHECK(spi_device_polling_transmit(spi_fifo, &t));
|
|
||||||
delayMicroseconds(4); // > 4 us
|
|
||||||
buf[i] = rx_data;
|
|
||||||
}
|
|
||||||
SPI_PARAM_UNLOCK();
|
|
||||||
}
|
|
||||||
155
lib/CMT2300a/cmt_spi3.cpp
Normal file
155
lib/CMT2300a/cmt_spi3.cpp
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
#include "cmt_spi3.h"
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <driver/spi_master.h>
|
||||||
|
#include <SpiManager.h>
|
||||||
|
|
||||||
|
SemaphoreHandle_t paramLock = NULL;
|
||||||
|
#define SPI_PARAM_LOCK() \
|
||||||
|
do { \
|
||||||
|
} while (xSemaphoreTake(paramLock, portMAX_DELAY) != pdPASS)
|
||||||
|
#define SPI_PARAM_UNLOCK() xSemaphoreGive(paramLock)
|
||||||
|
|
||||||
|
static void IRAM_ATTR pre_cb(spi_transaction_t *trans) {
|
||||||
|
gpio_set_level(*reinterpret_cast<gpio_num_t*>(trans->user), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void IRAM_ATTR post_cb(spi_transaction_t *trans) {
|
||||||
|
gpio_set_level(*reinterpret_cast<gpio_num_t*>(trans->user), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
spi_device_handle_t spi;
|
||||||
|
gpio_num_t cs_reg, cs_fifo;
|
||||||
|
|
||||||
|
void cmt_spi3_init(const int8_t pin_sdio, const int8_t pin_clk, const int8_t pin_cs, const int8_t pin_fcs, const int32_t spi_speed)
|
||||||
|
{
|
||||||
|
paramLock = xSemaphoreCreateMutex();
|
||||||
|
|
||||||
|
auto bus_config = std::make_shared<SpiBusConfig>(
|
||||||
|
static_cast<gpio_num_t>(pin_sdio),
|
||||||
|
GPIO_NUM_NC,
|
||||||
|
static_cast<gpio_num_t>(pin_clk)
|
||||||
|
);
|
||||||
|
|
||||||
|
spi_device_interface_config_t device_config {
|
||||||
|
.command_bits = 0, // set by transactions individually
|
||||||
|
.address_bits = 0, // set by transactions individually
|
||||||
|
.dummy_bits = 0,
|
||||||
|
.mode = 0, // SPI mode 0
|
||||||
|
.duty_cycle_pos = 0,
|
||||||
|
.cs_ena_pretrans = 2, // only 1 pre and post cycle would be required for register access
|
||||||
|
.cs_ena_posttrans = static_cast<uint8_t>(2 * spi_speed / 1000000), // >2 us
|
||||||
|
.clock_speed_hz = spi_speed,
|
||||||
|
.input_delay_ns = 0,
|
||||||
|
.spics_io_num = -1, // CS handled by callbacks
|
||||||
|
.flags = SPI_DEVICE_HALFDUPLEX | SPI_DEVICE_3WIRE,
|
||||||
|
.queue_size = 1,
|
||||||
|
.pre_cb = pre_cb,
|
||||||
|
.post_cb = post_cb,
|
||||||
|
};
|
||||||
|
|
||||||
|
spi = SpiManagerInst.alloc_device("", bus_config, device_config);
|
||||||
|
if (!spi)
|
||||||
|
ESP_ERROR_CHECK(ESP_FAIL);
|
||||||
|
|
||||||
|
cs_reg = static_cast<gpio_num_t>(pin_cs);
|
||||||
|
ESP_ERROR_CHECK(gpio_reset_pin(cs_reg));
|
||||||
|
ESP_ERROR_CHECK(gpio_set_level(cs_reg, 1));
|
||||||
|
ESP_ERROR_CHECK(gpio_set_direction(cs_reg, GPIO_MODE_OUTPUT));
|
||||||
|
|
||||||
|
cs_fifo = static_cast<gpio_num_t>(pin_fcs);
|
||||||
|
ESP_ERROR_CHECK(gpio_reset_pin(cs_fifo));
|
||||||
|
ESP_ERROR_CHECK(gpio_set_level(cs_fifo, 1));
|
||||||
|
ESP_ERROR_CHECK(gpio_set_direction(cs_fifo, GPIO_MODE_OUTPUT));
|
||||||
|
}
|
||||||
|
|
||||||
|
void cmt_spi3_write(const uint8_t addr, const uint8_t data)
|
||||||
|
{
|
||||||
|
spi_transaction_ext_t trans {
|
||||||
|
.base {
|
||||||
|
.flags = SPI_TRANS_VARIABLE_CMD | SPI_TRANS_VARIABLE_ADDR,
|
||||||
|
.cmd = 0,
|
||||||
|
.addr = addr,
|
||||||
|
.length = 8,
|
||||||
|
.rxlength = 0,
|
||||||
|
.user = &cs_reg, // CS for register access
|
||||||
|
.tx_buffer = &data,
|
||||||
|
.rx_buffer = nullptr,
|
||||||
|
},
|
||||||
|
.command_bits = 1,
|
||||||
|
.address_bits = 7,
|
||||||
|
.dummy_bits = 0,
|
||||||
|
};
|
||||||
|
SPI_PARAM_LOCK();
|
||||||
|
ESP_ERROR_CHECK(spi_device_polling_transmit(spi, reinterpret_cast<spi_transaction_t*>(&trans)));
|
||||||
|
SPI_PARAM_UNLOCK();
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t cmt_spi3_read(const uint8_t addr)
|
||||||
|
{
|
||||||
|
uint8_t data;
|
||||||
|
spi_transaction_ext_t trans {
|
||||||
|
.base {
|
||||||
|
.flags = SPI_TRANS_VARIABLE_CMD | SPI_TRANS_VARIABLE_ADDR,
|
||||||
|
.cmd = 1,
|
||||||
|
.addr = addr,
|
||||||
|
.length = 0,
|
||||||
|
.rxlength = 8,
|
||||||
|
.user = &cs_reg, // CS for register access
|
||||||
|
.tx_buffer = nullptr,
|
||||||
|
.rx_buffer = &data,
|
||||||
|
},
|
||||||
|
.command_bits = 1,
|
||||||
|
.address_bits = 7,
|
||||||
|
.dummy_bits = 0,
|
||||||
|
};
|
||||||
|
SPI_PARAM_LOCK();
|
||||||
|
ESP_ERROR_CHECK(spi_device_polling_transmit(spi, reinterpret_cast<spi_transaction_t*>(&trans)));
|
||||||
|
SPI_PARAM_UNLOCK();
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
void cmt_spi3_write_fifo(const uint8_t* buf, const uint16_t len)
|
||||||
|
{
|
||||||
|
spi_transaction_t trans {
|
||||||
|
.flags = 0,
|
||||||
|
.cmd = 0,
|
||||||
|
.addr = 0,
|
||||||
|
.length = 8,
|
||||||
|
.rxlength = 0,
|
||||||
|
.user = &cs_fifo, // CS for FIFO access
|
||||||
|
.tx_buffer = nullptr,
|
||||||
|
.rx_buffer = nullptr,
|
||||||
|
};
|
||||||
|
|
||||||
|
SPI_PARAM_LOCK();
|
||||||
|
spi_device_acquire_bus(spi, portMAX_DELAY);
|
||||||
|
for (uint8_t i = 0; i < len; i++) {
|
||||||
|
trans.tx_buffer = buf + i;
|
||||||
|
ESP_ERROR_CHECK(spi_device_polling_transmit(spi, &trans));
|
||||||
|
}
|
||||||
|
spi_device_release_bus(spi);
|
||||||
|
SPI_PARAM_UNLOCK();
|
||||||
|
}
|
||||||
|
|
||||||
|
void cmt_spi3_read_fifo(uint8_t* buf, const uint16_t len)
|
||||||
|
{
|
||||||
|
spi_transaction_t trans {
|
||||||
|
.flags = 0,
|
||||||
|
.cmd = 0,
|
||||||
|
.addr = 0,
|
||||||
|
.length = 0,
|
||||||
|
.rxlength = 8,
|
||||||
|
.user = &cs_fifo, // CS for FIFO access
|
||||||
|
.tx_buffer = nullptr,
|
||||||
|
.rx_buffer = nullptr,
|
||||||
|
};
|
||||||
|
|
||||||
|
SPI_PARAM_LOCK();
|
||||||
|
spi_device_acquire_bus(spi, portMAX_DELAY);
|
||||||
|
for (uint8_t i = 0; i < len; i++) {
|
||||||
|
trans.rx_buffer = buf + i;
|
||||||
|
ESP_ERROR_CHECK(spi_device_polling_transmit(spi, &trans));
|
||||||
|
}
|
||||||
|
spi_device_release_bus(spi);
|
||||||
|
SPI_PARAM_UNLOCK();
|
||||||
|
}
|
||||||
@ -3,7 +3,11 @@
|
|||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
void cmt_spi3_init(const int8_t pin_sdio, const int8_t pin_clk, const int8_t pin_cs, const int8_t pin_fcs, const uint32_t spi_speed);
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void cmt_spi3_init(const int8_t pin_sdio, const int8_t pin_clk, const int8_t pin_cs, const int8_t pin_fcs, const int32_t spi_speed);
|
||||||
|
|
||||||
void cmt_spi3_write(const uint8_t addr, const uint8_t dat);
|
void cmt_spi3_write(const uint8_t addr, const uint8_t dat);
|
||||||
uint8_t cmt_spi3_read(const uint8_t addr);
|
uint8_t cmt_spi3_read(const uint8_t addr);
|
||||||
@ -11,4 +15,8 @@ uint8_t cmt_spi3_read(const uint8_t addr);
|
|||||||
void cmt_spi3_write_fifo(const uint8_t* p_buf, const uint16_t len);
|
void cmt_spi3_write_fifo(const uint8_t* p_buf, const uint16_t len);
|
||||||
void cmt_spi3_read_fifo(uint8_t* p_buf, const uint16_t len);
|
void cmt_spi3_read_fifo(uint8_t* p_buf, const uint16_t len);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -4,6 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
#include "Hoymiles.h"
|
#include "Hoymiles.h"
|
||||||
#include "Utils.h"
|
#include "Utils.h"
|
||||||
|
#include "inverters/HERF_1CH.h"
|
||||||
#include "inverters/HERF_2CH.h"
|
#include "inverters/HERF_2CH.h"
|
||||||
#include "inverters/HERF_4CH.h"
|
#include "inverters/HERF_4CH.h"
|
||||||
#include "inverters/HMS_1CH.h"
|
#include "inverters/HMS_1CH.h"
|
||||||
@ -135,15 +136,7 @@ void HoymilesClass::loop()
|
|||||||
if (currentWeekDay != lastWeekDay) {
|
if (currentWeekDay != lastWeekDay) {
|
||||||
|
|
||||||
for (auto& inv : _inverters) {
|
for (auto& inv : _inverters) {
|
||||||
// Have to reset the offets first, otherwise it will
|
inv->performDailyTask();
|
||||||
// Substract the offset from zero which leads to a high value
|
|
||||||
inv->Statistics()->resetYieldDayCorrection();
|
|
||||||
if (inv->getZeroYieldDayOnMidnight()) {
|
|
||||||
inv->Statistics()->zeroDailyData();
|
|
||||||
}
|
|
||||||
if (inv->getClearEventlogOnMidnight()) {
|
|
||||||
inv->EventLog()->clearBuffer();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
lastWeekDay = currentWeekDay;
|
lastWeekDay = currentWeekDay;
|
||||||
@ -173,6 +166,8 @@ std::shared_ptr<InverterAbstract> HoymilesClass::addInverter(const char* name, c
|
|||||||
i = std::make_shared<HM_2CH>(_radioNrf.get(), serial);
|
i = std::make_shared<HM_2CH>(_radioNrf.get(), serial);
|
||||||
} else if (HM_1CH::isValidSerial(serial)) {
|
} else if (HM_1CH::isValidSerial(serial)) {
|
||||||
i = std::make_shared<HM_1CH>(_radioNrf.get(), serial);
|
i = std::make_shared<HM_1CH>(_radioNrf.get(), serial);
|
||||||
|
} else if (HERF_1CH::isValidSerial(serial)) {
|
||||||
|
i = std::make_shared<HERF_1CH>(_radioNrf.get(), serial);
|
||||||
} else if (HERF_2CH::isValidSerial(serial)) {
|
} else if (HERF_2CH::isValidSerial(serial)) {
|
||||||
i = std::make_shared<HERF_2CH>(_radioNrf.get(), serial);
|
i = std::make_shared<HERF_2CH>(_radioNrf.get(), serial);
|
||||||
} else if (HERF_4CH::isValidSerial(serial)) {
|
} else if (HERF_4CH::isValidSerial(serial)) {
|
||||||
@ -200,9 +195,9 @@ std::shared_ptr<InverterAbstract> HoymilesClass::getInverterByPos(const uint8_t
|
|||||||
|
|
||||||
std::shared_ptr<InverterAbstract> HoymilesClass::getInverterBySerial(const uint64_t serial)
|
std::shared_ptr<InverterAbstract> HoymilesClass::getInverterBySerial(const uint64_t serial)
|
||||||
{
|
{
|
||||||
for (uint8_t i = 0; i < _inverters.size(); i++) {
|
for (auto& inv : _inverters) {
|
||||||
if (_inverters[i]->serial() == serial) {
|
if (inv->serial() == serial) {
|
||||||
return _inverters[i];
|
return inv;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nullptr;
|
return nullptr;
|
||||||
@ -214,9 +209,7 @@ std::shared_ptr<InverterAbstract> HoymilesClass::getInverterByFragment(const fra
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<InverterAbstract> inv;
|
for (auto& inv : _inverters) {
|
||||||
for (uint8_t i = 0; i < _inverters.size(); i++) {
|
|
||||||
inv = _inverters[i];
|
|
||||||
serial_u p;
|
serial_u p;
|
||||||
p.u64 = inv->serial();
|
p.u64 = inv->serial();
|
||||||
|
|
||||||
|
|||||||
@ -66,16 +66,31 @@ void HoymilesRadio::handleReceivedPackage()
|
|||||||
|
|
||||||
} else if (verifyResult == FRAGMENT_ALL_MISSING_TIMEOUT) {
|
} else if (verifyResult == FRAGMENT_ALL_MISSING_TIMEOUT) {
|
||||||
Hoymiles.getMessageOutput()->println("Nothing received, resend count exeeded");
|
Hoymiles.getMessageOutput()->println("Nothing received, resend count exeeded");
|
||||||
|
// Statistics: Count RX Fail No Answer
|
||||||
|
if (inv->RadioStats.TxRequestData > 0) {
|
||||||
|
inv->RadioStats.RxFailNoAnswer++;
|
||||||
|
}
|
||||||
|
|
||||||
_commandQueue.pop();
|
_commandQueue.pop();
|
||||||
_busyFlag = false;
|
_busyFlag = false;
|
||||||
|
|
||||||
} else if (verifyResult == FRAGMENT_RETRANSMIT_TIMEOUT) {
|
} else if (verifyResult == FRAGMENT_RETRANSMIT_TIMEOUT) {
|
||||||
Hoymiles.getMessageOutput()->println("Retransmit timeout");
|
Hoymiles.getMessageOutput()->println("Retransmit timeout");
|
||||||
|
// Statistics: Count RX Fail Partial Answer
|
||||||
|
if (inv->RadioStats.TxRequestData > 0) {
|
||||||
|
inv->RadioStats.RxFailPartialAnswer++;
|
||||||
|
}
|
||||||
|
|
||||||
_commandQueue.pop();
|
_commandQueue.pop();
|
||||||
_busyFlag = false;
|
_busyFlag = false;
|
||||||
|
|
||||||
} else if (verifyResult == FRAGMENT_HANDLE_ERROR) {
|
} else if (verifyResult == FRAGMENT_HANDLE_ERROR) {
|
||||||
Hoymiles.getMessageOutput()->println("Packet handling error");
|
Hoymiles.getMessageOutput()->println("Packet handling error");
|
||||||
|
// Statistics: Count RX Fail Corrupt Data
|
||||||
|
if (inv->RadioStats.TxRequestData > 0) {
|
||||||
|
inv->RadioStats.RxFailCorruptData++;
|
||||||
|
}
|
||||||
|
|
||||||
_commandQueue.pop();
|
_commandQueue.pop();
|
||||||
_busyFlag = false;
|
_busyFlag = false;
|
||||||
|
|
||||||
@ -83,17 +98,26 @@ void HoymilesRadio::handleReceivedPackage()
|
|||||||
// Perform Retransmit
|
// Perform Retransmit
|
||||||
Hoymiles.getMessageOutput()->print("Request retransmit: ");
|
Hoymiles.getMessageOutput()->print("Request retransmit: ");
|
||||||
Hoymiles.getMessageOutput()->println(verifyResult);
|
Hoymiles.getMessageOutput()->println(verifyResult);
|
||||||
|
// Statistics: Count TX Re-Request Fragment
|
||||||
|
inv->RadioStats.TxReRequestFragment++;
|
||||||
|
|
||||||
sendRetransmitPacket(verifyResult);
|
sendRetransmitPacket(verifyResult);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// Successful received all packages
|
// Successful received all packages
|
||||||
Hoymiles.getMessageOutput()->println("Success");
|
Hoymiles.getMessageOutput()->println("Success");
|
||||||
|
// Statistics: Count RX Success
|
||||||
|
if (inv->RadioStats.TxRequestData > 0) {
|
||||||
|
inv->RadioStats.RxSuccess++;
|
||||||
|
}
|
||||||
|
|
||||||
_commandQueue.pop();
|
_commandQueue.pop();
|
||||||
_busyFlag = false;
|
_busyFlag = false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// If inverter was not found, assume the command is invalid
|
// If inverter was not found, assume the command is invalid
|
||||||
Hoymiles.getMessageOutput()->println("RX: Invalid inverter found");
|
Hoymiles.getMessageOutput()->println("RX: Invalid inverter found");
|
||||||
|
// Statistics: Count RX Fail Unknown Data
|
||||||
_commandQueue.pop();
|
_commandQueue.pop();
|
||||||
_busyFlag = false;
|
_busyFlag = false;
|
||||||
}
|
}
|
||||||
@ -105,6 +129,9 @@ void HoymilesRadio::handleReceivedPackage()
|
|||||||
auto inv = Hoymiles.getInverterBySerial(cmd->getTargetAddress());
|
auto inv = Hoymiles.getInverterBySerial(cmd->getTargetAddress());
|
||||||
if (nullptr != inv) {
|
if (nullptr != inv) {
|
||||||
inv->clearRxFragmentBuffer();
|
inv->clearRxFragmentBuffer();
|
||||||
|
// Statistics: TX Requests
|
||||||
|
inv->RadioStats.TxRequestData++;
|
||||||
|
|
||||||
sendEsbPacket(*cmd);
|
sendEsbPacket(*cmd);
|
||||||
} else {
|
} else {
|
||||||
Hoymiles.getMessageOutput()->println("TX: Invalid inverter found");
|
Hoymiles.getMessageOutput()->println("TX: Invalid inverter found");
|
||||||
|
|||||||
@ -34,7 +34,7 @@ uint32_t HoymilesRadio_CMT::getFrequencyFromChannel(const uint8_t channel) const
|
|||||||
uint8_t HoymilesRadio_CMT::getChannelFromFrequency(const uint32_t frequency) const
|
uint8_t HoymilesRadio_CMT::getChannelFromFrequency(const uint32_t frequency) const
|
||||||
{
|
{
|
||||||
if ((frequency % getChannelWidth()) != 0) {
|
if ((frequency % getChannelWidth()) != 0) {
|
||||||
Hoymiles.getMessageOutput()->printf("%.3f MHz is not divisible by %d kHz!\r\n", frequency / 1000000.0, getChannelWidth());
|
Hoymiles.getMessageOutput()->printf("%.3f MHz is not divisible by %" PRId32 " kHz!\r\n", frequency / 1000000.0, getChannelWidth());
|
||||||
return 0xFF; // ERROR
|
return 0xFF; // ERROR
|
||||||
}
|
}
|
||||||
if (frequency < getMinFrequency() || frequency > getMaxFrequency()) {
|
if (frequency < getMinFrequency() || frequency > getMaxFrequency()) {
|
||||||
@ -43,7 +43,7 @@ uint8_t HoymilesRadio_CMT::getChannelFromFrequency(const uint32_t frequency) con
|
|||||||
return 0xFF; // ERROR
|
return 0xFF; // ERROR
|
||||||
}
|
}
|
||||||
if (frequency < countryDefinition.at(_countryMode).Freq_Legal_Min || frequency > countryDefinition.at(_countryMode).Freq_Legal_Max) {
|
if (frequency < countryDefinition.at(_countryMode).Freq_Legal_Min || frequency > countryDefinition.at(_countryMode).Freq_Legal_Max) {
|
||||||
Hoymiles.getMessageOutput()->printf("!!! caution: %.2f MHz is out of region legal range! (%d - %d MHz)\r\n",
|
Hoymiles.getMessageOutput()->printf("!!! caution: %.2f MHz is out of region legal range! (%" PRId32 " - %" PRId32 " MHz)\r\n",
|
||||||
frequency / 1000000.0,
|
frequency / 1000000.0,
|
||||||
static_cast<uint32_t>(countryDefinition.at(_countryMode).Freq_Legal_Min / 1e6),
|
static_cast<uint32_t>(countryDefinition.at(_countryMode).Freq_Legal_Min / 1e6),
|
||||||
static_cast<uint32_t>(countryDefinition.at(_countryMode).Freq_Legal_Max / 1e6));
|
static_cast<uint32_t>(countryDefinition.at(_countryMode).Freq_Legal_Max / 1e6));
|
||||||
@ -167,9 +167,9 @@ void HoymilesRadio_CMT::loop()
|
|||||||
// Save packet in inverter rx buffer
|
// Save packet in inverter rx buffer
|
||||||
Hoymiles.getMessageOutput()->printf("RX %.2f MHz --> ", getFrequencyFromChannel(f.channel) / 1000000.0);
|
Hoymiles.getMessageOutput()->printf("RX %.2f MHz --> ", getFrequencyFromChannel(f.channel) / 1000000.0);
|
||||||
dumpBuf(f.fragment, f.len, false);
|
dumpBuf(f.fragment, f.len, false);
|
||||||
Hoymiles.getMessageOutput()->printf("| %d dBm\r\n", f.rssi);
|
Hoymiles.getMessageOutput()->printf("| %" PRId8 " dBm\r\n", f.rssi);
|
||||||
|
|
||||||
inv->addRxFragment(f.fragment, f.len);
|
inv->addRxFragment(f.fragment, f.len, f.rssi);
|
||||||
} else {
|
} else {
|
||||||
Hoymiles.getMessageOutput()->println("Inverter Not found!");
|
Hoymiles.getMessageOutput()->println("Inverter Not found!");
|
||||||
}
|
}
|
||||||
@ -194,9 +194,9 @@ void HoymilesRadio_CMT::setPALevel(const int8_t paLevel)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (_radio->setPALevel(paLevel)) {
|
if (_radio->setPALevel(paLevel)) {
|
||||||
Hoymiles.getMessageOutput()->printf("CMT TX power set to %d dBm\r\n", paLevel);
|
Hoymiles.getMessageOutput()->printf("CMT TX power set to %" PRId8 " dBm\r\n", paLevel);
|
||||||
} else {
|
} else {
|
||||||
Hoymiles.getMessageOutput()->printf("CMT TX power %d dBm is not defined! (min: -10 dBm, max: 20 dBm)\r\n", paLevel);
|
Hoymiles.getMessageOutput()->printf("CMT TX power %" PRId8 " dBm is not defined! (min: -10 dBm, max: 20 dBm)\r\n", paLevel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -76,11 +76,11 @@ void HoymilesRadio_NRF::loop()
|
|||||||
|
|
||||||
if (nullptr != inv) {
|
if (nullptr != inv) {
|
||||||
// Save packet in inverter rx buffer
|
// Save packet in inverter rx buffer
|
||||||
Hoymiles.getMessageOutput()->printf("RX Channel: %d --> ", f.channel);
|
Hoymiles.getMessageOutput()->printf("RX Channel: %" PRId8 " --> ", f.channel);
|
||||||
dumpBuf(f.fragment, f.len, false);
|
dumpBuf(f.fragment, f.len, false);
|
||||||
Hoymiles.getMessageOutput()->printf("| %d dBm\r\n", f.rssi);
|
Hoymiles.getMessageOutput()->printf("| %" PRId8 " dBm\r\n", f.rssi);
|
||||||
|
|
||||||
inv->addRxFragment(f.fragment, f.len);
|
inv->addRxFragment(f.fragment, f.len, f.rssi);
|
||||||
} else {
|
} else {
|
||||||
Hoymiles.getMessageOutput()->println("Inverter Not found!");
|
Hoymiles.getMessageOutput()->println("Inverter Not found!");
|
||||||
}
|
}
|
||||||
@ -183,7 +183,7 @@ void HoymilesRadio_NRF::sendEsbPacket(CommandAbstract& cmd)
|
|||||||
openWritingPipe(s);
|
openWritingPipe(s);
|
||||||
_radio->setRetries(3, 15);
|
_radio->setRetries(3, 15);
|
||||||
|
|
||||||
Hoymiles.getMessageOutput()->printf("TX %s Channel: %d --> ",
|
Hoymiles.getMessageOutput()->printf("TX %s Channel: %" PRId8 " --> ",
|
||||||
cmd.getCommandName().c_str(), _radio->getChannel());
|
cmd.getCommandName().c_str(), _radio->getChannel());
|
||||||
cmd.dumpDataPayload(Hoymiles.getMessageOutput());
|
cmd.dumpDataPayload(Hoymiles.getMessageOutput());
|
||||||
_radio->write(cmd.getDataPayload(), cmd.getDataSize());
|
_radio->write(cmd.getDataPayload(), cmd.getDataSize());
|
||||||
|
|||||||
@ -48,7 +48,7 @@ bool RealTimeRunDataCommand::handleResponse(const fragment_t fragment[], const u
|
|||||||
const uint8_t fragmentsSize = getTotalFragmentSize(fragment, max_fragment_id);
|
const uint8_t fragmentsSize = getTotalFragmentSize(fragment, max_fragment_id);
|
||||||
const uint8_t expectedSize = _inv->Statistics()->getExpectedByteCount();
|
const uint8_t expectedSize = _inv->Statistics()->getExpectedByteCount();
|
||||||
if (fragmentsSize < expectedSize) {
|
if (fragmentsSize < expectedSize) {
|
||||||
Hoymiles.getMessageOutput()->printf("ERROR in %s: Received fragment size: %d, min expected size: %d\r\n",
|
Hoymiles.getMessageOutput()->printf("ERROR in %s: Received fragment size: %" PRId8 ", min expected size: %" PRId8 "\r\n",
|
||||||
getCommandName().c_str(), fragmentsSize, expectedSize);
|
getCommandName().c_str(), fragmentsSize, expectedSize);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@ -48,7 +48,7 @@ bool SystemConfigParaCommand::handleResponse(const fragment_t fragment[], const
|
|||||||
const uint8_t fragmentsSize = getTotalFragmentSize(fragment, max_fragment_id);
|
const uint8_t fragmentsSize = getTotalFragmentSize(fragment, max_fragment_id);
|
||||||
const uint8_t expectedSize = _inv->SystemConfigPara()->getExpectedByteCount();
|
const uint8_t expectedSize = _inv->SystemConfigPara()->getExpectedByteCount();
|
||||||
if (fragmentsSize < expectedSize) {
|
if (fragmentsSize < expectedSize) {
|
||||||
Hoymiles.getMessageOutput()->printf("ERROR in %s: Received fragment size: %d, min expected size: %d\r\n",
|
Hoymiles.getMessageOutput()->printf("ERROR in %s: Received fragment size: %" PRId8 ", min expected size: %" PRId8 "\r\n",
|
||||||
getCommandName().c_str(), fragmentsSize, expectedSize);
|
getCommandName().c_str(), fragmentsSize, expectedSize);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
55
lib/Hoymiles/src/inverters/HERF_1CH.cpp
Normal file
55
lib/Hoymiles/src/inverters/HERF_1CH.cpp
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2022-2024 Thomas Basler and others
|
||||||
|
*/
|
||||||
|
#include "HERF_1CH.h"
|
||||||
|
|
||||||
|
static const byteAssign_t byteAssignment[] = {
|
||||||
|
{ TYPE_DC, CH0, FLD_UDC, UNIT_V, 2, 2, 10, false, 1 },
|
||||||
|
{ TYPE_DC, CH0, FLD_IDC, UNIT_A, 6, 2, 100, false, 2 },
|
||||||
|
{ TYPE_DC, CH0, FLD_PDC, UNIT_W, 10, 2, 10, false, 1 },
|
||||||
|
{ TYPE_DC, CH0, FLD_YD, UNIT_WH, 22, 2, 1, false, 0 },
|
||||||
|
{ TYPE_DC, CH0, FLD_YT, UNIT_KWH, 14, 4, 1000, false, 3 },
|
||||||
|
{ TYPE_DC, CH0, FLD_IRR, UNIT_PCT, CALC_CH_IRR, CH0, CMD_CALC, false, 3 },
|
||||||
|
|
||||||
|
{ TYPE_AC, CH0, FLD_UAC, UNIT_V, 26, 2, 10, false, 1 },
|
||||||
|
{ TYPE_AC, CH0, FLD_IAC, UNIT_A, 34, 2, 100, false, 2 },
|
||||||
|
{ TYPE_AC, CH0, FLD_PAC, UNIT_W, 30, 2, 10, false, 1 },
|
||||||
|
{ TYPE_AC, CH0, FLD_Q, UNIT_VAR, 40, 2, 10, false, 1 }, // to be verified
|
||||||
|
{ TYPE_AC, CH0, FLD_F, UNIT_HZ, 28, 2, 100, false, 2 },
|
||||||
|
{ TYPE_AC, CH0, FLD_PF, UNIT_NONE, 36, 2, 1000, false, 3 },
|
||||||
|
|
||||||
|
{ TYPE_INV, CH0, FLD_T, UNIT_C, 38, 2, 10, true, 1 }, // to be verified
|
||||||
|
{ TYPE_INV, CH0, FLD_EVT_LOG, UNIT_NONE, 40, 2, 1, false, 0 }, // to be verified
|
||||||
|
|
||||||
|
{ TYPE_INV, CH0, FLD_YD, UNIT_WH, CALC_TOTAL_YD, 0, CMD_CALC, false, 0 },
|
||||||
|
{ TYPE_INV, CH0, FLD_YT, UNIT_KWH, CALC_TOTAL_YT, 0, CMD_CALC, false, 3 },
|
||||||
|
{ TYPE_INV, CH0, FLD_PDC, UNIT_W, CALC_TOTAL_PDC, 0, CMD_CALC, false, 1 },
|
||||||
|
{ TYPE_INV, CH0, FLD_EFF, UNIT_PCT, CALC_TOTAL_EFF, 0, CMD_CALC, false, 3 }
|
||||||
|
};
|
||||||
|
|
||||||
|
HERF_1CH::HERF_1CH(HoymilesRadio* radio, const uint64_t serial)
|
||||||
|
: HM_Abstract(radio, serial) {};
|
||||||
|
|
||||||
|
bool HERF_1CH::isValidSerial(const uint64_t serial)
|
||||||
|
{
|
||||||
|
// serial >= 0x284100000000 && serial <= 0x2841ffffffff
|
||||||
|
uint16_t preSerial = (serial >> 32) & 0xffff;
|
||||||
|
return preSerial == 0x2841;
|
||||||
|
}
|
||||||
|
|
||||||
|
String HERF_1CH::typeName() const
|
||||||
|
{
|
||||||
|
return "HERF-300-1T";
|
||||||
|
}
|
||||||
|
|
||||||
|
const byteAssign_t* HERF_1CH::getByteAssignment() const
|
||||||
|
{
|
||||||
|
return byteAssignment;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t HERF_1CH::getByteAssignmentSize() const
|
||||||
|
{
|
||||||
|
return sizeof(byteAssignment) / sizeof(byteAssignment[0]);
|
||||||
|
}
|
||||||
13
lib/Hoymiles/src/inverters/HERF_1CH.h
Normal file
13
lib/Hoymiles/src/inverters/HERF_1CH.h
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "HM_Abstract.h"
|
||||||
|
|
||||||
|
class HERF_1CH : public HM_Abstract {
|
||||||
|
public:
|
||||||
|
explicit HERF_1CH(HoymilesRadio* radio, const uint64_t serial);
|
||||||
|
static bool isValidSerial(const uint64_t serial);
|
||||||
|
String typeName() const;
|
||||||
|
const byteAssign_t* getByteAssignment() const;
|
||||||
|
uint8_t getByteAssignmentSize() const;
|
||||||
|
};
|
||||||
@ -42,7 +42,7 @@ bool HMS_2CH::isValidSerial(const uint64_t serial)
|
|||||||
{
|
{
|
||||||
// serial >= 0x114400000000 && serial <= 0x1144ffffffff
|
// serial >= 0x114400000000 && serial <= 0x1144ffffffff
|
||||||
uint16_t preSerial = (serial >> 32) & 0xffff;
|
uint16_t preSerial = (serial >> 32) & 0xffff;
|
||||||
return preSerial == 0x1144 || preSerial == 0x1143;
|
return preSerial == 0x1144 || preSerial == 0x1143 || preSerial == 0x1410;
|
||||||
}
|
}
|
||||||
|
|
||||||
String HMS_2CH::typeName() const
|
String HMS_2CH::typeName() const
|
||||||
|
|||||||
@ -137,6 +137,11 @@ bool InverterAbstract::getClearEventlogOnMidnight() const
|
|||||||
return _clearEventlogOnMidnight;
|
return _clearEventlogOnMidnight;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int8_t InverterAbstract::getLastRssi() const
|
||||||
|
{
|
||||||
|
return _lastRssi;
|
||||||
|
}
|
||||||
|
|
||||||
bool InverterAbstract::sendChangeChannelRequest()
|
bool InverterAbstract::sendChangeChannelRequest()
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
@ -185,8 +190,10 @@ void InverterAbstract::clearRxFragmentBuffer()
|
|||||||
_rxFragmentRetransmitCnt = 0;
|
_rxFragmentRetransmitCnt = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void InverterAbstract::addRxFragment(const uint8_t fragment[], const uint8_t len)
|
void InverterAbstract::addRxFragment(const uint8_t fragment[], const uint8_t len, const int8_t rssi)
|
||||||
{
|
{
|
||||||
|
_lastRssi = rssi;
|
||||||
|
|
||||||
if (len < 11) {
|
if (len < 11) {
|
||||||
Hoymiles.getMessageOutput()->printf("FATAL: (%s, %d) fragment too short\r\n", __FILE__, __LINE__);
|
Hoymiles.getMessageOutput()->printf("FATAL: (%s, %d) fragment too short\r\n", __FILE__, __LINE__);
|
||||||
return;
|
return;
|
||||||
@ -208,7 +215,7 @@ void InverterAbstract::addRxFragment(const uint8_t fragment[], const uint8_t len
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (fragmentId >= MAX_RF_FRAGMENT_COUNT) {
|
if (fragmentId >= MAX_RF_FRAGMENT_COUNT) {
|
||||||
Hoymiles.getMessageOutput()->printf("ERROR: fragment id %d is too large for buffer and ignored\r\n", fragmentId);
|
Hoymiles.getMessageOutput()->printf("ERROR: fragment id %" PRId8 " is too large for buffer and ignored\r\n", fragmentId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -272,3 +279,22 @@ uint8_t InverterAbstract::verifyAllFragments(CommandAbstract& cmd)
|
|||||||
|
|
||||||
return FRAGMENT_OK;
|
return FRAGMENT_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void InverterAbstract::performDailyTask()
|
||||||
|
{
|
||||||
|
// Have to reset the offets first, otherwise it will
|
||||||
|
// Substract the offset from zero which leads to a high value
|
||||||
|
Statistics()->resetYieldDayCorrection();
|
||||||
|
if (getZeroYieldDayOnMidnight()) {
|
||||||
|
Statistics()->zeroDailyData();
|
||||||
|
}
|
||||||
|
if (getClearEventlogOnMidnight()) {
|
||||||
|
EventLog()->clearBuffer();
|
||||||
|
}
|
||||||
|
resetRadioStats();
|
||||||
|
}
|
||||||
|
|
||||||
|
void InverterAbstract::resetRadioStats()
|
||||||
|
{
|
||||||
|
RadioStats = {};
|
||||||
|
}
|
||||||
|
|||||||
@ -61,10 +61,36 @@ public:
|
|||||||
void setClearEventlogOnMidnight(const bool enabled);
|
void setClearEventlogOnMidnight(const bool enabled);
|
||||||
bool getClearEventlogOnMidnight() const;
|
bool getClearEventlogOnMidnight() const;
|
||||||
|
|
||||||
|
int8_t getLastRssi() const;
|
||||||
|
|
||||||
void clearRxFragmentBuffer();
|
void clearRxFragmentBuffer();
|
||||||
void addRxFragment(const uint8_t fragment[], const uint8_t len);
|
void addRxFragment(const uint8_t fragment[], const uint8_t len, const int8_t rssi);
|
||||||
uint8_t verifyAllFragments(CommandAbstract& cmd);
|
uint8_t verifyAllFragments(CommandAbstract& cmd);
|
||||||
|
|
||||||
|
void performDailyTask();
|
||||||
|
|
||||||
|
void resetRadioStats();
|
||||||
|
|
||||||
|
struct {
|
||||||
|
// TX Request Data
|
||||||
|
uint32_t TxRequestData;
|
||||||
|
|
||||||
|
// TX Re-Request Fragment
|
||||||
|
uint32_t TxReRequestFragment;
|
||||||
|
|
||||||
|
// RX Success
|
||||||
|
uint32_t RxSuccess;
|
||||||
|
|
||||||
|
// RX Fail Partial Answer
|
||||||
|
uint32_t RxFailPartialAnswer;
|
||||||
|
|
||||||
|
// RX Fail No Answer
|
||||||
|
uint32_t RxFailNoAnswer;
|
||||||
|
|
||||||
|
// RX Fail Corrupt Data
|
||||||
|
uint32_t RxFailCorruptData;
|
||||||
|
} RadioStats = {};
|
||||||
|
|
||||||
virtual bool sendStatsRequest() = 0;
|
virtual bool sendStatsRequest() = 0;
|
||||||
virtual bool sendAlarmLogRequest(const bool force = false) = 0;
|
virtual bool sendAlarmLogRequest(const bool force = false) = 0;
|
||||||
virtual bool sendDevInfoRequest() = 0;
|
virtual bool sendDevInfoRequest() = 0;
|
||||||
@ -107,6 +133,8 @@ private:
|
|||||||
bool _zeroYieldDayOnMidnight = false;
|
bool _zeroYieldDayOnMidnight = false;
|
||||||
bool _clearEventlogOnMidnight = false;
|
bool _clearEventlogOnMidnight = false;
|
||||||
|
|
||||||
|
int8_t _lastRssi = -127;
|
||||||
|
|
||||||
std::unique_ptr<AlarmLogParser> _alarmLogParser;
|
std::unique_ptr<AlarmLogParser> _alarmLogParser;
|
||||||
std::unique_ptr<DevInfoParser> _devInfoParser;
|
std::unique_ptr<DevInfoParser> _devInfoParser;
|
||||||
std::unique_ptr<GridProfileParser> _gridProfileParser;
|
std::unique_ptr<GridProfileParser> _gridProfileParser;
|
||||||
|
|||||||
@ -1,15 +1,16 @@
|
|||||||
# Class overview
|
# Class overview
|
||||||
|
|
||||||
| Class | Models | Serial range |
|
| Class | Models | Serial range |
|
||||||
| --------------| --------------------------- | ------------ |
|
| --------------| --------------------------- | ------------- -- |
|
||||||
| HM_1CH | HM-300/350/400-1T | 1121 |
|
| HM_1CH | HM-300/350/400-1T | 1121 |
|
||||||
| HM_2CH | HM-600/700/800-2T | 1141 |
|
| HM_2CH | HM-600/700/800-2T | 1141 |
|
||||||
| HM_4CH | HM-1000/1200/1500-4T | 1161 |
|
| HM_4CH | HM-1000/1200/1500-4T | 1161 |
|
||||||
| HMS_1CH | HMS-300/350/400/450/500-1T | 1124 |
|
| HMS_1CH | HMS-300/350/400/450/500-1T | 1124 |
|
||||||
| HMS_1CHv2 | HMS-500-1T v2 | 1125 |
|
| HMS_1CHv2 | HMS-500-1T v2 | 1125 |
|
||||||
| HMS_2CH | HMS-600/700/800/900/1000-2T | 1143, 1144 |
|
| HMS_2CH | HMS-600/700/800/900/1000-2T | 1143, 1144, 1410 |
|
||||||
| HMS_4CH | HMS-1600/1800/2000-4T | 1164 |
|
| HMS_4CH | HMS-1600/1800/2000-4T | 1164 |
|
||||||
| HMT_4CH | HMT-1600/1800/2000-4T | 1361 |
|
| HMT_4CH | HMT-1600/1800/2000-4T | 1361 |
|
||||||
| HMT_6CH | HMT-1800/2250-6T | 1382 |
|
| HMT_6CH | HMT-1800/2250-6T | 1382 |
|
||||||
| HERF_2CH | HERF 800 | 2821 |
|
| HERF_1CH | HERF 300 | 2841 |
|
||||||
| HERF_4CH | HERF 1800 | 2801 |
|
| HERF_2CH | HERF 800 | 2821 |
|
||||||
|
| HERF_4CH | HERF 1800 | 2801 |
|
||||||
|
|||||||
13
lib/SpiManager/library.json
Normal file
13
lib/SpiManager/library.json
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"name": "SpiManager",
|
||||||
|
"keywords": "spi",
|
||||||
|
"description": "Library for managing the allocation of dedicated or shared SPI buses on the ESP32.",
|
||||||
|
"authors": {
|
||||||
|
"name": "Lennart Ferlemann"
|
||||||
|
},
|
||||||
|
"version": "0.0.1",
|
||||||
|
"frameworks": "arduino",
|
||||||
|
"platforms": [
|
||||||
|
"espressif32"
|
||||||
|
]
|
||||||
|
}
|
||||||
52
lib/SpiManager/src/SpiBus.cpp
Normal file
52
lib/SpiManager/src/SpiBus.cpp
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
#include "SpiBus.h"
|
||||||
|
#include "SpiBusConfig.h"
|
||||||
|
#include "SpiCallback.h"
|
||||||
|
|
||||||
|
SpiBus::SpiBus(const std::string& _id, spi_host_device_t _host_device)
|
||||||
|
: id(_id)
|
||||||
|
, host_device(_host_device)
|
||||||
|
, cur_config(nullptr)
|
||||||
|
{
|
||||||
|
spi_bus_config_t bus_config {
|
||||||
|
.mosi_io_num = -1,
|
||||||
|
.miso_io_num = -1,
|
||||||
|
.sclk_io_num = -1,
|
||||||
|
.quadwp_io_num = -1,
|
||||||
|
.quadhd_io_num = -1,
|
||||||
|
.data4_io_num = -1,
|
||||||
|
.data5_io_num = -1,
|
||||||
|
.data6_io_num = -1,
|
||||||
|
.data7_io_num = -1,
|
||||||
|
.max_transfer_sz = SPI_MAX_DMA_LEN,
|
||||||
|
.flags = 0,
|
||||||
|
.intr_flags = 0
|
||||||
|
};
|
||||||
|
ESP_ERROR_CHECK(spi_bus_initialize(host_device, &bus_config, SPI_DMA_CH_AUTO));
|
||||||
|
}
|
||||||
|
|
||||||
|
SpiBus::~SpiBus()
|
||||||
|
{
|
||||||
|
ESP_ERROR_CHECK(spi_bus_free(host_device));
|
||||||
|
}
|
||||||
|
|
||||||
|
spi_device_handle_t SpiBus::add_device(const std::shared_ptr<SpiBusConfig>& bus_config, spi_device_interface_config_t& device_config)
|
||||||
|
{
|
||||||
|
if (!SpiCallback::patch(shared_from_this(), bus_config, device_config))
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
spi_device_handle_t device;
|
||||||
|
ESP_ERROR_CHECK(spi_bus_add_device(host_device, &device_config, &device));
|
||||||
|
return device;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: add remove_device (with spi_device_acquire_bus)
|
||||||
|
|
||||||
|
void SpiBus::apply_config(SpiBusConfig* config)
|
||||||
|
{
|
||||||
|
if (cur_config)
|
||||||
|
cur_config->unpatch(host_device);
|
||||||
|
cur_config = config;
|
||||||
|
if (cur_config)
|
||||||
|
cur_config->patch(host_device);
|
||||||
|
}
|
||||||
49
lib/SpiManager/src/SpiBus.h
Normal file
49
lib/SpiManager/src/SpiBus.h
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <driver/spi_master.h>
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
class SpiBusConfig;
|
||||||
|
|
||||||
|
class SpiBus : public std::enable_shared_from_this<SpiBus> {
|
||||||
|
public:
|
||||||
|
explicit SpiBus(const std::string& id, spi_host_device_t host_device);
|
||||||
|
SpiBus(const SpiBus&) = delete;
|
||||||
|
SpiBus& operator=(const SpiBus&) = delete;
|
||||||
|
~SpiBus();
|
||||||
|
|
||||||
|
inline __attribute__((always_inline)) void require_config(SpiBusConfig* config)
|
||||||
|
{
|
||||||
|
if (config == cur_config)
|
||||||
|
return;
|
||||||
|
apply_config(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline __attribute__((always_inline)) void free_config(SpiBusConfig* config)
|
||||||
|
{
|
||||||
|
if (config != cur_config)
|
||||||
|
return;
|
||||||
|
apply_config(nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline const std::string& get_id() const
|
||||||
|
{
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline spi_host_device_t get_host_device() const
|
||||||
|
{
|
||||||
|
return host_device;
|
||||||
|
}
|
||||||
|
|
||||||
|
spi_device_handle_t add_device(const std::shared_ptr<SpiBusConfig>& bus_config, spi_device_interface_config_t& device_config);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void apply_config(SpiBusConfig* config);
|
||||||
|
|
||||||
|
std::string id;
|
||||||
|
spi_host_device_t host_device;
|
||||||
|
SpiBusConfig* cur_config;
|
||||||
|
};
|
||||||
71
lib/SpiManager/src/SpiBusConfig.cpp
Normal file
71
lib/SpiManager/src/SpiBusConfig.cpp
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
#include "SpiBusConfig.h"
|
||||||
|
|
||||||
|
#include <driver/gpio.h>
|
||||||
|
#include <esp_rom_gpio.h>
|
||||||
|
#include <soc/spi_periph.h>
|
||||||
|
|
||||||
|
SpiBusConfig::SpiBusConfig(gpio_num_t _pin_mosi, gpio_num_t _pin_miso, gpio_num_t _pin_sclk)
|
||||||
|
: pin_mosi(_pin_mosi)
|
||||||
|
, pin_miso(_pin_miso)
|
||||||
|
, pin_sclk(_pin_sclk)
|
||||||
|
{
|
||||||
|
if (pin_mosi != GPIO_NUM_NC) {
|
||||||
|
ESP_ERROR_CHECK(gpio_reset_pin(pin_mosi));
|
||||||
|
ESP_ERROR_CHECK(gpio_set_direction(pin_mosi, GPIO_MODE_INPUT_OUTPUT));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pin_miso != GPIO_NUM_NC) {
|
||||||
|
ESP_ERROR_CHECK(gpio_reset_pin(pin_miso));
|
||||||
|
ESP_ERROR_CHECK(gpio_set_direction(pin_miso, GPIO_MODE_INPUT));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pin_sclk != GPIO_NUM_NC) {
|
||||||
|
ESP_ERROR_CHECK(gpio_reset_pin(pin_sclk));
|
||||||
|
ESP_ERROR_CHECK(gpio_set_direction(pin_sclk, GPIO_MODE_INPUT_OUTPUT));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SpiBusConfig::~SpiBusConfig()
|
||||||
|
{
|
||||||
|
if (pin_mosi != GPIO_NUM_NC)
|
||||||
|
ESP_ERROR_CHECK(gpio_reset_pin(pin_mosi));
|
||||||
|
|
||||||
|
if (pin_miso != GPIO_NUM_NC)
|
||||||
|
ESP_ERROR_CHECK(gpio_reset_pin(pin_miso));
|
||||||
|
|
||||||
|
if (pin_sclk != GPIO_NUM_NC)
|
||||||
|
ESP_ERROR_CHECK(gpio_reset_pin(pin_sclk));
|
||||||
|
}
|
||||||
|
|
||||||
|
void SpiBusConfig::patch(spi_host_device_t host_device)
|
||||||
|
{
|
||||||
|
if (pin_mosi != GPIO_NUM_NC) {
|
||||||
|
esp_rom_gpio_connect_out_signal(pin_mosi, spi_periph_signal[host_device].spid_out, false, false);
|
||||||
|
esp_rom_gpio_connect_in_signal(pin_mosi, spi_periph_signal[host_device].spid_in, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pin_miso != GPIO_NUM_NC)
|
||||||
|
esp_rom_gpio_connect_in_signal(pin_miso, spi_periph_signal[host_device].spiq_in, false);
|
||||||
|
|
||||||
|
if (pin_sclk != GPIO_NUM_NC) {
|
||||||
|
esp_rom_gpio_connect_out_signal(pin_sclk, spi_periph_signal[host_device].spiclk_out, false, false);
|
||||||
|
esp_rom_gpio_connect_in_signal(pin_sclk, spi_periph_signal[host_device].spiclk_in, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SpiBusConfig::unpatch(spi_host_device_t host_device)
|
||||||
|
{
|
||||||
|
if (pin_mosi != GPIO_NUM_NC) {
|
||||||
|
esp_rom_gpio_connect_out_signal(pin_mosi, SIG_GPIO_OUT_IDX, false, false);
|
||||||
|
esp_rom_gpio_connect_in_signal(GPIO_MATRIX_CONST_ONE_INPUT, spi_periph_signal[host_device].spid_in, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pin_miso != GPIO_NUM_NC)
|
||||||
|
esp_rom_gpio_connect_in_signal(GPIO_MATRIX_CONST_ONE_INPUT, spi_periph_signal[host_device].spiq_in, false);
|
||||||
|
|
||||||
|
if (pin_sclk != GPIO_NUM_NC) {
|
||||||
|
esp_rom_gpio_connect_out_signal(pin_sclk, SIG_GPIO_OUT_IDX, false, false);
|
||||||
|
esp_rom_gpio_connect_in_signal(GPIO_MATRIX_CONST_ONE_INPUT, spi_periph_signal[host_device].spiclk_in, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
21
lib/SpiManager/src/SpiBusConfig.h
Normal file
21
lib/SpiManager/src/SpiBusConfig.h
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <hal/gpio_types.h>
|
||||||
|
#include <hal/spi_types.h>
|
||||||
|
|
||||||
|
class SpiBusConfig {
|
||||||
|
public:
|
||||||
|
explicit SpiBusConfig(gpio_num_t pin_mosi, gpio_num_t pin_miso, gpio_num_t pin_sclk);
|
||||||
|
SpiBusConfig(const SpiBusConfig&) = delete;
|
||||||
|
SpiBusConfig& operator=(const SpiBusConfig&) = delete;
|
||||||
|
~SpiBusConfig();
|
||||||
|
|
||||||
|
void patch(spi_host_device_t host_device);
|
||||||
|
void unpatch(spi_host_device_t host_device);
|
||||||
|
|
||||||
|
private:
|
||||||
|
gpio_num_t pin_mosi;
|
||||||
|
gpio_num_t pin_miso;
|
||||||
|
gpio_num_t pin_sclk;
|
||||||
|
};
|
||||||
69
lib/SpiManager/src/SpiCallback.cpp
Normal file
69
lib/SpiManager/src/SpiCallback.cpp
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
#include "SpiCallback.h"
|
||||||
|
|
||||||
|
#include "SpiBus.h"
|
||||||
|
#include <array>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
namespace SpiCallback {
|
||||||
|
namespace {
|
||||||
|
struct CallbackData {
|
||||||
|
std::shared_ptr<SpiBus> bus;
|
||||||
|
std::shared_ptr<SpiBusConfig> config;
|
||||||
|
transaction_cb_t inner_pre_cb;
|
||||||
|
transaction_cb_t inner_post_cb;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::array<std::optional<CallbackData>, SPI_MANAGER_CALLBACK_COUNT> instances;
|
||||||
|
|
||||||
|
template <int N>
|
||||||
|
void IRAM_ATTR fn_pre_cb(spi_transaction_t* trans)
|
||||||
|
{
|
||||||
|
instances[N]->bus->require_config(instances[N]->config.get());
|
||||||
|
if (instances[N]->inner_pre_cb)
|
||||||
|
instances[N]->inner_pre_cb(trans);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <int N>
|
||||||
|
void IRAM_ATTR fn_post_cb(spi_transaction_t* trans)
|
||||||
|
{
|
||||||
|
if (instances[N]->inner_post_cb)
|
||||||
|
instances[N]->inner_post_cb(trans);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <int N>
|
||||||
|
inline __attribute__((always_inline)) bool alloc(CallbackData*& instance, transaction_cb_t& pre_cb, transaction_cb_t& post_cb)
|
||||||
|
{
|
||||||
|
if constexpr (N > 0) {
|
||||||
|
if (alloc<N - 1>(instance, pre_cb, post_cb))
|
||||||
|
return true;
|
||||||
|
if (!instances[N - 1]) {
|
||||||
|
instances[N - 1].emplace();
|
||||||
|
instance = &*instances[N - 1];
|
||||||
|
pre_cb = fn_pre_cb<N - 1>;
|
||||||
|
post_cb = fn_post_cb<N - 1>;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool patch(const std::shared_ptr<SpiBus>& bus, const std::shared_ptr<SpiBusConfig>& bus_config, spi_device_interface_config_t& device_config)
|
||||||
|
{
|
||||||
|
CallbackData* instance;
|
||||||
|
transaction_cb_t pre_cb;
|
||||||
|
transaction_cb_t post_cb;
|
||||||
|
if (!alloc<SPI_MANAGER_CALLBACK_COUNT>(instance, pre_cb, post_cb))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
instance->bus = bus;
|
||||||
|
instance->config = bus_config;
|
||||||
|
instance->inner_pre_cb = device_config.pre_cb;
|
||||||
|
instance->inner_post_cb = device_config.post_cb;
|
||||||
|
device_config.pre_cb = pre_cb;
|
||||||
|
device_config.post_cb = post_cb;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
15
lib/SpiManager/src/SpiCallback.h
Normal file
15
lib/SpiManager/src/SpiCallback.h
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <driver/spi_master.h>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
// Pre and post callbacks for 2 buses with 3 devices each
|
||||||
|
#define SPI_MANAGER_CALLBACK_COUNT 6
|
||||||
|
|
||||||
|
class SpiBus;
|
||||||
|
class SpiBusConfig;
|
||||||
|
|
||||||
|
namespace SpiCallback {
|
||||||
|
bool patch(const std::shared_ptr<SpiBus>& bus, const std::shared_ptr<SpiBusConfig>& bus_config, spi_device_interface_config_t& device_config);
|
||||||
|
}
|
||||||
114
lib/SpiManager/src/SpiManager.cpp
Normal file
114
lib/SpiManager/src/SpiManager.cpp
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
#include "SpiManager.h"
|
||||||
|
|
||||||
|
#ifdef ARDUINO
|
||||||
|
#include <SPI.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
SpiManager::SpiManager()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef ARDUINO
|
||||||
|
|
||||||
|
std::optional<uint8_t> SpiManager::to_arduino(spi_host_device_t host_device)
|
||||||
|
{
|
||||||
|
switch (host_device) {
|
||||||
|
#if CONFIG_IDF_TARGET_ESP32
|
||||||
|
case SPI1_HOST:
|
||||||
|
return FSPI;
|
||||||
|
case SPI2_HOST:
|
||||||
|
return HSPI;
|
||||||
|
case SPI3_HOST:
|
||||||
|
return VSPI;
|
||||||
|
#elif CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3
|
||||||
|
case SPI2_HOST:
|
||||||
|
return FSPI;
|
||||||
|
case SPI3_HOST:
|
||||||
|
return HSPI;
|
||||||
|
#elif CONFIG_IDF_TARGET_ESP32C3
|
||||||
|
case SPI2_HOST:
|
||||||
|
return FSPI;
|
||||||
|
#endif
|
||||||
|
default:
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
bool SpiManager::register_bus(spi_host_device_t host_device)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < SPI_MANAGER_NUM_BUSES; ++i) {
|
||||||
|
if (available_buses[i])
|
||||||
|
continue;
|
||||||
|
|
||||||
|
available_buses[i] = host_device;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SpiManager::claim_bus(spi_host_device_t& host_device)
|
||||||
|
{
|
||||||
|
for (int i = SPI_MANAGER_NUM_BUSES - 1; i >= 0; --i) {
|
||||||
|
if (!available_buses[i])
|
||||||
|
continue;
|
||||||
|
|
||||||
|
host_device = *available_buses[i];
|
||||||
|
available_buses[i].reset();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef ARDUINO
|
||||||
|
|
||||||
|
std::optional<uint8_t> SpiManager::claim_bus_arduino()
|
||||||
|
{
|
||||||
|
spi_host_device_t host_device;
|
||||||
|
if (!claim_bus(host_device))
|
||||||
|
return std::nullopt;
|
||||||
|
return to_arduino(host_device);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
spi_device_handle_t SpiManager::alloc_device(const std::string& bus_id, const std::shared_ptr<SpiBusConfig>& bus_config, spi_device_interface_config_t& device_config)
|
||||||
|
{
|
||||||
|
std::shared_ptr<SpiBus> shared_bus = get_shared_bus(bus_id);
|
||||||
|
if (!shared_bus)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
return shared_bus->add_device(bus_config, device_config);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<SpiBus> SpiManager::get_shared_bus(const std::string& bus_id)
|
||||||
|
{
|
||||||
|
// look for existing shared bus
|
||||||
|
for (int i = 0; i < SPI_MANAGER_NUM_BUSES; ++i) {
|
||||||
|
if (!shared_buses[i])
|
||||||
|
continue;
|
||||||
|
if (shared_buses[i]->get_id() == bus_id)
|
||||||
|
return shared_buses[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
// create new shared bus
|
||||||
|
for (int i = 0; i < SPI_MANAGER_NUM_BUSES; ++i) {
|
||||||
|
if (shared_buses[i])
|
||||||
|
continue;
|
||||||
|
|
||||||
|
spi_host_device_t host_device;
|
||||||
|
if (!claim_bus(host_device))
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
shared_buses[i] = std::make_shared<SpiBus>(bus_id, host_device);
|
||||||
|
return shared_buses[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
SpiManager SpiManagerInst;
|
||||||
41
lib/SpiManager/src/SpiManager.h
Normal file
41
lib/SpiManager/src/SpiManager.h
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "SpiBus.h"
|
||||||
|
#include "SpiBusConfig.h"
|
||||||
|
|
||||||
|
#include <driver/spi_master.h>
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <memory>
|
||||||
|
#include <optional>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#define SPI_MANAGER_NUM_BUSES SOC_SPI_PERIPH_NUM
|
||||||
|
|
||||||
|
class SpiManager {
|
||||||
|
public:
|
||||||
|
explicit SpiManager();
|
||||||
|
SpiManager(const SpiManager&) = delete;
|
||||||
|
SpiManager& operator=(const SpiManager&) = delete;
|
||||||
|
|
||||||
|
#ifdef ARDUINO
|
||||||
|
static std::optional<uint8_t> to_arduino(spi_host_device_t host_device);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
bool register_bus(spi_host_device_t host_device);
|
||||||
|
bool claim_bus(spi_host_device_t& host_device);
|
||||||
|
#ifdef ARDUINO
|
||||||
|
std::optional<uint8_t> claim_bus_arduino();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
spi_device_handle_t alloc_device(const std::string& bus_id, const std::shared_ptr<SpiBusConfig>& bus_config, spi_device_interface_config_t& device_config);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::shared_ptr<SpiBus> get_shared_bus(const std::string& bus_id);
|
||||||
|
|
||||||
|
std::array<std::optional<spi_host_device_t>, SPI_MANAGER_NUM_BUSES> available_buses;
|
||||||
|
std::array<std::shared_ptr<SpiBus>, SPI_MANAGER_NUM_BUSES> shared_buses;
|
||||||
|
};
|
||||||
|
|
||||||
|
extern SpiManager SpiManagerInst;
|
||||||
@ -36,9 +36,20 @@ def get_build_version():
|
|||||||
return build_version
|
return build_version
|
||||||
|
|
||||||
|
|
||||||
|
def get_build_branch():
|
||||||
|
try:
|
||||||
|
branch_name = porcelain.active_branch('.').decode('utf-8') # '.' refers to the repository root dir
|
||||||
|
except Exception as err:
|
||||||
|
branch_name = "master"
|
||||||
|
print("Firmware Branch: " + branch_name)
|
||||||
|
return branch_name
|
||||||
|
|
||||||
|
|
||||||
def get_firmware_specifier_build_flag():
|
def get_firmware_specifier_build_flag():
|
||||||
build_version = get_build_version()
|
build_version = get_build_version()
|
||||||
build_flag = "-D AUTO_GIT_HASH=\\\"" + build_version + "\\\""
|
build_flag = "-D AUTO_GIT_HASH=\\\"" + build_version + "\\\""
|
||||||
|
build_branch = get_build_branch()
|
||||||
|
build_flag += " -D AUTO_GIT_BRANCH=\\\"" + branch_name + "\\\""
|
||||||
return (build_flag)
|
return (build_flag)
|
||||||
|
|
||||||
|
|
||||||
@ -64,6 +75,8 @@ def do_main():
|
|||||||
if 1:
|
if 1:
|
||||||
# Add the description of the current git revision
|
# Add the description of the current git revision
|
||||||
lines += 'const char *__COMPILED_GIT_HASH__ = "%s";\n' % (get_build_version())
|
lines += 'const char *__COMPILED_GIT_HASH__ = "%s";\n' % (get_build_version())
|
||||||
|
# ... and git branch
|
||||||
|
lines += 'const char *__COMPILED_GIT_BRANCH__ = "%s";\n' % (get_build_branch())
|
||||||
|
|
||||||
updateFileIfChanged(targetfile, bytes(lines, "utf-8"))
|
updateFileIfChanged(targetfile, bytes(lines, "utf-8"))
|
||||||
|
|
||||||
|
|||||||
@ -18,20 +18,64 @@
|
|||||||
|
|
||||||
Import("env")
|
Import("env")
|
||||||
|
|
||||||
|
env = DefaultEnvironment()
|
||||||
platform = env.PioPlatform()
|
platform = env.PioPlatform()
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
from os.path import join, getsize
|
import csv
|
||||||
|
import subprocess
|
||||||
|
import shutil
|
||||||
|
from os.path import join, getsize, exists, isdir
|
||||||
|
from os import listdir
|
||||||
|
|
||||||
sys.path.append(join(platform.get_package_dir("tool-esptoolpy")))
|
sys.path.append(join(platform.get_package_dir("tool-esptoolpy")))
|
||||||
import esptool
|
import esptool
|
||||||
|
|
||||||
|
def esp32_build_filesystem(fs_name, fs_size):
|
||||||
|
filesystem_dir = env.subst("$PROJECT_DATA_DIR")
|
||||||
|
print("Creating %dKiB filesystem with content:" % (int(fs_size, 0)/1024) )
|
||||||
|
if not isdir(filesystem_dir) or not listdir(filesystem_dir):
|
||||||
|
print("No files added -> will NOT create littlefs.bin and NOT overwrite fs partition!")
|
||||||
|
return False
|
||||||
|
# this does not work on GitHub, results in 'mklittlefs: No such file or directory'
|
||||||
|
tool = shutil.which(env.subst(env["MKFSTOOL"]))
|
||||||
|
if tool is None or not exists(tool):
|
||||||
|
print("Using fallback mklittlefs")
|
||||||
|
tool = "~/.platformio/packages/tool-mklittlefs/mklittlefs"
|
||||||
|
|
||||||
|
cmd = (tool, "-c", filesystem_dir, "-s", fs_size, fs_name)
|
||||||
|
returncode = subprocess.call(cmd, shell=False)
|
||||||
|
print("Return Code:", returncode)
|
||||||
|
return True
|
||||||
|
|
||||||
def esp32_create_combined_bin(source, target, env):
|
def esp32_create_combined_bin(source, target, env):
|
||||||
print("Generating combined binary for serial flashing")
|
print("Generating combined binary for serial flashing")
|
||||||
|
|
||||||
# The offset from begin of the file where the app0 partition starts
|
# The offset from begin of the file where the app0 partition starts
|
||||||
# This is defined in the partition .csv file
|
# This is defined in the partition .csv file
|
||||||
app_offset = 0x10000
|
app_offset = 0x10000
|
||||||
|
fs_offset = -1
|
||||||
|
fs_name = env.subst("$BUILD_DIR/littlefs.bin")
|
||||||
|
|
||||||
|
with open(env.BoardConfig().get("build.partitions")) as csv_file:
|
||||||
|
print("Read partitions from ", env.BoardConfig().get("build.partitions"))
|
||||||
|
csv_reader = csv.reader(csv_file, delimiter=',')
|
||||||
|
line_count = 0
|
||||||
|
for row in csv_reader:
|
||||||
|
if line_count == 0:
|
||||||
|
print(f'{", ".join(row)}')
|
||||||
|
line_count += 1
|
||||||
|
else:
|
||||||
|
if (len(row) < 4):
|
||||||
|
continue
|
||||||
|
print(f'{row[0]} {row[1]} {row[2]} {row[3]} {row[4]}')
|
||||||
|
line_count += 1
|
||||||
|
if(row[0] == 'app0'):
|
||||||
|
app_offset = int(row[3], base=16)
|
||||||
|
elif(row[0] == 'spiffs'):
|
||||||
|
partition_size = row[4]
|
||||||
|
if esp32_build_filesystem(fs_name, partition_size):
|
||||||
|
fs_offset = int(row[3], base=16)
|
||||||
|
|
||||||
new_file_name = env.subst("$BUILD_DIR/${PROGNAME}.factory.bin")
|
new_file_name = env.subst("$BUILD_DIR/${PROGNAME}.factory.bin")
|
||||||
sections = env.subst(env.get("FLASH_EXTRA_IMAGES"))
|
sections = env.subst(env.get("FLASH_EXTRA_IMAGES"))
|
||||||
@ -77,6 +121,10 @@ def esp32_create_combined_bin(source, target, env):
|
|||||||
print(f" - {hex(app_offset)} | {firmware_name}")
|
print(f" - {hex(app_offset)} | {firmware_name}")
|
||||||
cmd += [hex(app_offset), firmware_name]
|
cmd += [hex(app_offset), firmware_name]
|
||||||
|
|
||||||
|
if fs_offset != -1:
|
||||||
|
print(f" - {hex(fs_offset)} | {fs_name}")
|
||||||
|
cmd += [hex(fs_offset), fs_name]
|
||||||
|
|
||||||
print('Using esptool.py arguments: %s' % ' '.join(cmd))
|
print('Using esptool.py arguments: %s' % ' '.join(cmd))
|
||||||
|
|
||||||
esptool.main(cmd)
|
esptool.main(cmd)
|
||||||
|
|||||||
@ -19,7 +19,9 @@ extra_configs =
|
|||||||
custom_ci_action = generic,generic_esp32,generic_esp32s3,generic_esp32s3_usb
|
custom_ci_action = generic,generic_esp32,generic_esp32s3,generic_esp32s3_usb
|
||||||
|
|
||||||
framework = arduino
|
framework = arduino
|
||||||
platform = espressif32@6.8.1
|
platform = espressif32@6.9.0
|
||||||
|
platform_packages =
|
||||||
|
platformio/tool-mklittlefs
|
||||||
|
|
||||||
build_flags =
|
build_flags =
|
||||||
-DPIOENV=\"$PIOENV\"
|
-DPIOENV=\"$PIOENV\"
|
||||||
@ -39,13 +41,13 @@ build_unflags =
|
|||||||
-std=gnu++11
|
-std=gnu++11
|
||||||
|
|
||||||
lib_deps =
|
lib_deps =
|
||||||
mathieucarbou/ESPAsyncWebServer @ 3.1.2
|
mathieucarbou/ESPAsyncWebServer @ 3.3.12
|
||||||
bblanchon/ArduinoJson @ 7.1.0
|
bblanchon/ArduinoJson @ 7.2.0
|
||||||
https://github.com/bertmelis/espMqttClient.git#v1.7.0
|
https://github.com/bertmelis/espMqttClient.git#v1.7.0
|
||||||
nrf24/RF24 @ 1.4.9
|
nrf24/RF24 @ 1.4.9
|
||||||
olikraus/U8g2 @ 2.35.19
|
olikraus/U8g2 @ 2.35.30
|
||||||
buelowp/sunset @ 1.1.7
|
buelowp/sunset @ 1.1.7
|
||||||
https://github.com/arkhipenko/TaskScheduler#testing
|
arkhipenko/TaskScheduler @ 3.8.5
|
||||||
|
|
||||||
extra_scripts =
|
extra_scripts =
|
||||||
pre:pio-scripts/auto_firmware_version.py
|
pre:pio-scripts/auto_firmware_version.py
|
||||||
@ -227,6 +229,7 @@ build_flags = ${env.build_flags}
|
|||||||
-DLED0=17
|
-DLED0=17
|
||||||
-DLED1=18
|
-DLED1=18
|
||||||
-DARDUINO_USB_MODE=1
|
-DARDUINO_USB_MODE=1
|
||||||
|
-DARDUINO_USB_CDC_ON_BOOT=1
|
||||||
|
|
||||||
[env:opendtufusionv2]
|
[env:opendtufusionv2]
|
||||||
board = esp32-s3-devkitc-1
|
board = esp32-s3-devkitc-1
|
||||||
@ -250,3 +253,32 @@ build_flags = ${env.build_flags}
|
|||||||
-DCMT_SDIO=5
|
-DCMT_SDIO=5
|
||||||
-DARDUINO_USB_MODE=1
|
-DARDUINO_USB_MODE=1
|
||||||
-DARDUINO_USB_CDC_ON_BOOT=1
|
-DARDUINO_USB_CDC_ON_BOOT=1
|
||||||
|
|
||||||
|
[env:opendtufusionv2_poe]
|
||||||
|
board = esp32-s3-devkitc-1
|
||||||
|
upload_protocol = esp-builtin
|
||||||
|
debug_tool = esp-builtin
|
||||||
|
debug_speed = 12000
|
||||||
|
build_flags = ${env.build_flags}
|
||||||
|
-DHOYMILES_PIN_MISO=48
|
||||||
|
-DHOYMILES_PIN_MOSI=35
|
||||||
|
-DHOYMILES_PIN_SCLK=36
|
||||||
|
-DHOYMILES_PIN_IRQ=47
|
||||||
|
-DHOYMILES_PIN_CE=38
|
||||||
|
-DHOYMILES_PIN_CS=37
|
||||||
|
-DLED0=17
|
||||||
|
-DLED1=18
|
||||||
|
-DCMT_CLK=6
|
||||||
|
-DCMT_CS=4
|
||||||
|
-DCMT_FCS=21
|
||||||
|
-DCMT_GPIO2=3
|
||||||
|
-DCMT_GPIO3=8
|
||||||
|
-DCMT_SDIO=5
|
||||||
|
-DW5500_MOSI=40
|
||||||
|
-DW5500_MISO=41
|
||||||
|
-DW5500_SCLK=39
|
||||||
|
-DW5500_CS=42
|
||||||
|
-DW5500_INT=44
|
||||||
|
-DW5500_RST=43
|
||||||
|
-DARDUINO_USB_MODE=1
|
||||||
|
-DARDUINO_USB_CDC_ON_BOOT=1
|
||||||
|
|||||||
@ -87,7 +87,7 @@ void DisplayGraphicDiagramClass::redraw(uint8_t screenSaverOffsetX, uint8_t xPos
|
|||||||
if (maxWatts > 999) {
|
if (maxWatts > 999) {
|
||||||
snprintf(fmtText, sizeof(fmtText), "%2.1fkW", maxWatts / 1000);
|
snprintf(fmtText, sizeof(fmtText), "%2.1fkW", maxWatts / 1000);
|
||||||
} else {
|
} else {
|
||||||
snprintf(fmtText, sizeof(fmtText), "%dW", static_cast<uint16_t>(maxWatts));
|
snprintf(fmtText, sizeof(fmtText), "%" PRId16 "W", static_cast<uint16_t>(maxWatts));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isFullscreen) {
|
if (isFullscreen) {
|
||||||
|
|||||||
@ -8,20 +8,7 @@
|
|||||||
#include "PinMapping.h"
|
#include "PinMapping.h"
|
||||||
#include "SunPosition.h"
|
#include "SunPosition.h"
|
||||||
#include <Hoymiles.h>
|
#include <Hoymiles.h>
|
||||||
|
#include <SpiManager.h>
|
||||||
// the NRF shall use the second externally usable HW SPI controller
|
|
||||||
// for ESP32 that is the so-called VSPI, for ESP32-S2/S3 it is now called implicitly
|
|
||||||
// HSPI, as it has shifted places for these chip generations
|
|
||||||
// for all generations, this is equivalent to SPI3_HOST in the lower level driver
|
|
||||||
// For ESP32-C2, the only externally usable HW SPI controller is SPI2, its signal names
|
|
||||||
// being prefixed with FSPI.
|
|
||||||
#if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3
|
|
||||||
#define SPI_NRF HSPI
|
|
||||||
#elif CONFIG_IDF_TARGET_ESP32C3
|
|
||||||
#define SPI_NRF FSPI
|
|
||||||
#else
|
|
||||||
#define SPI_NRF VSPI
|
|
||||||
#endif
|
|
||||||
|
|
||||||
InverterSettingsClass InverterSettings;
|
InverterSettingsClass InverterSettings;
|
||||||
|
|
||||||
@ -44,7 +31,10 @@ void InverterSettingsClass::init(Scheduler& scheduler)
|
|||||||
|
|
||||||
if (PinMapping.isValidNrf24Config() || PinMapping.isValidCmt2300Config()) {
|
if (PinMapping.isValidNrf24Config() || PinMapping.isValidCmt2300Config()) {
|
||||||
if (PinMapping.isValidNrf24Config()) {
|
if (PinMapping.isValidNrf24Config()) {
|
||||||
SPIClass* spiClass = new SPIClass(SPI_NRF);
|
auto spi_bus = SpiManagerInst.claim_bus_arduino();
|
||||||
|
ESP_ERROR_CHECK(spi_bus ? ESP_OK : ESP_FAIL);
|
||||||
|
|
||||||
|
SPIClass* spiClass = new SPIClass(*spi_bus);
|
||||||
spiClass->begin(pin.nrf24_clk, pin.nrf24_miso, pin.nrf24_mosi, pin.nrf24_cs);
|
spiClass->begin(pin.nrf24_clk, pin.nrf24_miso, pin.nrf24_mosi, pin.nrf24_cs);
|
||||||
Hoymiles.initNRF(spiClass, pin.nrf24_en, pin.nrf24_irq);
|
Hoymiles.initNRF(spiClass, pin.nrf24_en, pin.nrf24_irq);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,6 +7,7 @@
|
|||||||
#include "MqttSettings.h"
|
#include "MqttSettings.h"
|
||||||
#include "NetworkSettings.h"
|
#include "NetworkSettings.h"
|
||||||
#include <Hoymiles.h>
|
#include <Hoymiles.h>
|
||||||
|
#include <CpuTemperature.h>
|
||||||
|
|
||||||
MqttHandleDtuClass MqttHandleDtu;
|
MqttHandleDtuClass MqttHandleDtu;
|
||||||
|
|
||||||
@ -34,8 +35,17 @@ void MqttHandleDtuClass::loop()
|
|||||||
MqttSettings.publish("dtu/uptime", String(millis() / 1000));
|
MqttSettings.publish("dtu/uptime", String(millis() / 1000));
|
||||||
MqttSettings.publish("dtu/ip", NetworkSettings.localIP().toString());
|
MqttSettings.publish("dtu/ip", NetworkSettings.localIP().toString());
|
||||||
MqttSettings.publish("dtu/hostname", NetworkSettings.getHostname());
|
MqttSettings.publish("dtu/hostname", NetworkSettings.getHostname());
|
||||||
|
MqttSettings.publish("dtu/heap/size", String(ESP.getHeapSize()));
|
||||||
|
MqttSettings.publish("dtu/heap/free", String(ESP.getFreeHeap()));
|
||||||
|
MqttSettings.publish("dtu/heap/minfree", String(ESP.getMinFreeHeap()));
|
||||||
|
MqttSettings.publish("dtu/heap/maxalloc", String(ESP.getMaxAllocHeap()));
|
||||||
if (NetworkSettings.NetworkMode() == network_mode::WiFi) {
|
if (NetworkSettings.NetworkMode() == network_mode::WiFi) {
|
||||||
MqttSettings.publish("dtu/rssi", String(WiFi.RSSI()));
|
MqttSettings.publish("dtu/rssi", String(WiFi.RSSI()));
|
||||||
MqttSettings.publish("dtu/bssid", WiFi.BSSIDstr());
|
MqttSettings.publish("dtu/bssid", WiFi.BSSIDstr());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
float temperature = CpuTemperature.read();
|
||||||
|
if (!std::isnan(temperature)) {
|
||||||
|
MqttSettings.publish("dtu/temperature", String(temperature));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,8 +7,8 @@
|
|||||||
#include "MqttSettings.h"
|
#include "MqttSettings.h"
|
||||||
#include "NetworkSettings.h"
|
#include "NetworkSettings.h"
|
||||||
#include "Utils.h"
|
#include "Utils.h"
|
||||||
#include "defaults.h"
|
|
||||||
#include "__compiled_constants.h"
|
#include "__compiled_constants.h"
|
||||||
|
#include "defaults.h"
|
||||||
|
|
||||||
MqttHandleHassClass MqttHandleHass;
|
MqttHandleHassClass MqttHandleHass;
|
||||||
|
|
||||||
@ -58,29 +58,46 @@ void MqttHandleHassClass::publishConfig()
|
|||||||
const CONFIG_T& config = Configuration.get();
|
const CONFIG_T& config = Configuration.get();
|
||||||
|
|
||||||
// publish DTU sensors
|
// publish DTU sensors
|
||||||
publishDtuSensor("IP", "", "diagnostic", "mdi:network-outline", "", "");
|
publishDtuSensor("IP", "dtu/ip", "", "mdi:network-outline", DEVICE_CLS_NONE, STATE_CLS_NONE, CATEGORY_DIAGNOSTIC);
|
||||||
publishDtuSensor("WiFi Signal", "signal_strength", "diagnostic", "", "dBm", "rssi");
|
publishDtuSensor("WiFi Signal", "dtu/rssi", "dBm", "", DEVICE_CLS_SIGNAL_STRENGTH, STATE_CLS_NONE, CATEGORY_DIAGNOSTIC);
|
||||||
publishDtuSensor("Uptime", "duration", "diagnostic", "", "s", "");
|
publishDtuSensor("Uptime", "dtu/uptime", "s", "", DEVICE_CLS_DURATION, STATE_CLS_NONE, CATEGORY_DIAGNOSTIC);
|
||||||
publishDtuBinarySensor("Status", "connectivity", "diagnostic", config.Mqtt.Lwt.Value_Online, config.Mqtt.Lwt.Value_Offline, config.Mqtt.Lwt.Topic);
|
publishDtuSensor("Temperature", "dtu/temperature", "°C", "", DEVICE_CLS_TEMPERATURE, STATE_CLS_MEASUREMENT, CATEGORY_DIAGNOSTIC);
|
||||||
|
publishDtuSensor("Heap Size", "dtu/heap/size", "Bytes", "mdi:memory", DEVICE_CLS_NONE, STATE_CLS_NONE, CATEGORY_DIAGNOSTIC);
|
||||||
|
publishDtuSensor("Heap Free", "dtu/heap/free", "Bytes", "mdi:memory", DEVICE_CLS_NONE, STATE_CLS_NONE, CATEGORY_DIAGNOSTIC);
|
||||||
|
publishDtuSensor("Largest Free Heap Block", "dtu/heap/maxalloc", "Bytes", "mdi:memory", DEVICE_CLS_NONE, STATE_CLS_NONE, CATEGORY_DIAGNOSTIC);
|
||||||
|
publishDtuSensor("Lifetime Minimum Free Heap", "dtu/heap/minfree", "Bytes", "mdi:memory", DEVICE_CLS_NONE, STATE_CLS_NONE, CATEGORY_DIAGNOSTIC);
|
||||||
|
|
||||||
yield();
|
publishDtuSensor("Yield Total", "ac/yieldtotal", "kWh", "", DEVICE_CLS_ENERGY, STATE_CLS_TOTAL_INCREASING, CATEGORY_NONE);
|
||||||
|
publishDtuSensor("Yield Day", "ac/yieldday", "Wh", "", DEVICE_CLS_ENERGY, STATE_CLS_TOTAL_INCREASING, CATEGORY_NONE);
|
||||||
|
publishDtuSensor("AC Power", "ac/power", "W", "", DEVICE_CLS_PWR, STATE_CLS_MEASUREMENT, CATEGORY_NONE);
|
||||||
|
|
||||||
|
publishDtuBinarySensor("Status", config.Mqtt.Lwt.Topic, config.Mqtt.Lwt.Value_Online, config.Mqtt.Lwt.Value_Offline, DEVICE_CLS_CONNECTIVITY, STATE_CLS_NONE, CATEGORY_DIAGNOSTIC);
|
||||||
|
|
||||||
// Loop all inverters
|
// Loop all inverters
|
||||||
for (uint8_t i = 0; i < Hoymiles.getNumInverters(); i++) {
|
for (uint8_t i = 0; i < Hoymiles.getNumInverters(); i++) {
|
||||||
auto inv = Hoymiles.getInverterByPos(i);
|
auto inv = Hoymiles.getInverterByPos(i);
|
||||||
|
|
||||||
publishInverterButton(inv, "Turn Inverter Off", "mdi:power-plug-off", "config", "", "cmd/power", "0");
|
publishInverterButton(inv, "Turn Inverter Off", "cmd/power", "0", "mdi:power-plug-off", DEVICE_CLS_NONE, STATE_CLS_NONE, CATEGORY_CONFIG);
|
||||||
publishInverterButton(inv, "Turn Inverter On", "mdi:power-plug", "config", "", "cmd/power", "1");
|
publishInverterButton(inv, "Turn Inverter On", "cmd/power", "1", "mdi:power-plug", DEVICE_CLS_NONE, STATE_CLS_NONE, CATEGORY_CONFIG);
|
||||||
publishInverterButton(inv, "Restart Inverter", "", "config", "restart", "cmd/restart", "1");
|
publishInverterButton(inv, "Restart Inverter", "cmd/restart", "1", "", DEVICE_CLS_RESTART, STATE_CLS_NONE, CATEGORY_CONFIG);
|
||||||
|
publishInverterButton(inv, "Reset Radio Statistics", "cmd/reset_rf_stats", "1", "", DEVICE_CLS_NONE, STATE_CLS_NONE, CATEGORY_CONFIG);
|
||||||
|
|
||||||
publishInverterNumber(inv, "Limit NonPersistent Relative", "mdi:speedometer", "config", "cmd/limit_nonpersistent_relative", "status/limit_relative", "%", 0, 100, 0.1);
|
publishInverterNumber(inv, "Limit NonPersistent Relative", "status/limit_relative", "cmd/limit_nonpersistent_relative", 0, 100, 0.1, "%", "mdi:speedometer", STATE_CLS_NONE, CATEGORY_CONFIG);
|
||||||
publishInverterNumber(inv, "Limit Persistent Relative", "mdi:speedometer", "config", "cmd/limit_persistent_relative", "status/limit_relative", "%", 0, 100, 0.1);
|
publishInverterNumber(inv, "Limit Persistent Relative", "status/limit_relative", "cmd/limit_persistent_relative", 0, 100, 0.1, "%", "mdi:speedometer", STATE_CLS_NONE, CATEGORY_CONFIG);
|
||||||
|
|
||||||
publishInverterNumber(inv, "Limit NonPersistent Absolute", "mdi:speedometer", "config", "cmd/limit_nonpersistent_absolute", "status/limit_absolute", "W", 0, MAX_INVERTER_LIMIT);
|
publishInverterNumber(inv, "Limit NonPersistent Absolute", "status/limit_absolute", "cmd/limit_nonpersistent_absolute", 0, MAX_INVERTER_LIMIT, 1, "W", "mdi:speedometer", STATE_CLS_NONE, CATEGORY_CONFIG);
|
||||||
publishInverterNumber(inv, "Limit Persistent Absolute", "mdi:speedometer", "config", "cmd/limit_persistent_absolute", "status/limit_absolute", "W", 0, MAX_INVERTER_LIMIT);
|
publishInverterNumber(inv, "Limit Persistent Absolute", "status/limit_absolute", "cmd/limit_persistent_absolute", 0, MAX_INVERTER_LIMIT, 1, "W", "mdi:speedometer", STATE_CLS_NONE, CATEGORY_CONFIG);
|
||||||
|
|
||||||
publishInverterBinarySensor(inv, "Reachable", "status/reachable", "1", "0");
|
publishInverterBinarySensor(inv, "Reachable", "status/reachable", "1", "0", DEVICE_CLS_CONNECTIVITY, STATE_CLS_NONE, CATEGORY_DIAGNOSTIC);
|
||||||
publishInverterBinarySensor(inv, "Producing", "status/producing", "1", "0");
|
publishInverterBinarySensor(inv, "Producing", "status/producing", "1", "0", DEVICE_CLS_NONE, STATE_CLS_NONE, CATEGORY_NONE);
|
||||||
|
|
||||||
|
publishInverterSensor(inv, "TX Requests", "radio/tx_request", "", "", DEVICE_CLS_NONE, STATE_CLS_NONE, CATEGORY_DIAGNOSTIC);
|
||||||
|
publishInverterSensor(inv, "RX Success", "radio/rx_success", "", "", DEVICE_CLS_NONE, STATE_CLS_NONE, CATEGORY_DIAGNOSTIC);
|
||||||
|
publishInverterSensor(inv, "RX Fail Receive Nothing", "radio/rx_fail_nothing", "", "", DEVICE_CLS_NONE, STATE_CLS_NONE, CATEGORY_DIAGNOSTIC);
|
||||||
|
publishInverterSensor(inv, "RX Fail Receive Partial", "radio/rx_fail_partial", "", "", DEVICE_CLS_NONE, STATE_CLS_NONE, CATEGORY_DIAGNOSTIC);
|
||||||
|
publishInverterSensor(inv, "RX Fail Receive Corrupt", "radio/rx_fail_corrupt", "", "", DEVICE_CLS_NONE, STATE_CLS_NONE, CATEGORY_DIAGNOSTIC);
|
||||||
|
publishInverterSensor(inv, "TX Re-Request Fragment", "radio/tx_re_request", "", "", DEVICE_CLS_NONE, STATE_CLS_NONE, CATEGORY_DIAGNOSTIC);
|
||||||
|
publishInverterSensor(inv, "RSSI", "radio/rssi", "dBm", "", DEVICE_CLS_SIGNAL_STRENGTH, STATE_CLS_NONE, CATEGORY_DIAGNOSTIC);
|
||||||
|
|
||||||
// Loop all channels
|
// Loop all channels
|
||||||
for (auto& t : inv->Statistics()->getChannelTypes()) {
|
for (auto& t : inv->Statistics()->getChannelTypes()) {
|
||||||
@ -94,8 +111,6 @@ void MqttHandleHassClass::publishConfig()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
yield();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -128,8 +143,6 @@ void MqttHandleHassClass::publishInverterField(std::shared_ptr<InverterAbstract>
|
|||||||
|
|
||||||
if (!clear) {
|
if (!clear) {
|
||||||
const String stateTopic = MqttSettings.getPrefix() + MqttHandleInverter.getTopic(inv, type, channel, fieldType.fieldId);
|
const String stateTopic = MqttSettings.getPrefix() + MqttHandleInverter.getTopic(inv, type, channel, fieldType.fieldId);
|
||||||
const char* devCls = deviceClasses[fieldType.deviceClsId];
|
|
||||||
const char* stateCls = stateClasses[fieldType.stateClsId];
|
|
||||||
|
|
||||||
String name;
|
String name;
|
||||||
if (type != TYPE_DC) {
|
if (type != TYPE_DC) {
|
||||||
@ -138,46 +151,34 @@ void MqttHandleHassClass::publishInverterField(std::shared_ptr<InverterAbstract>
|
|||||||
name = "CH" + chanNum + " " + fieldName;
|
name = "CH" + chanNum + " " + fieldName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String unit_of_measure = inv->Statistics()->getChannelFieldUnit(type, channel, fieldType.fieldId);
|
||||||
|
|
||||||
JsonDocument root;
|
JsonDocument root;
|
||||||
|
createInverterInfo(root, inv);
|
||||||
|
addCommonMetadata(root, unit_of_measure, "", fieldType.deviceClsId, fieldType.stateClsId, CATEGORY_NONE);
|
||||||
|
|
||||||
root["name"] = name;
|
root["name"] = name;
|
||||||
root["stat_t"] = stateTopic;
|
root["stat_t"] = stateTopic;
|
||||||
root["uniq_id"] = serial + "_ch" + chanNum + "_" + fieldName;
|
root["uniq_id"] = serial + "_ch" + chanNum + "_" + fieldName;
|
||||||
|
|
||||||
String unit_of_measure = inv->Statistics()->getChannelFieldUnit(type, channel, fieldType.fieldId);
|
|
||||||
if (unit_of_measure != "") {
|
|
||||||
root["unit_of_meas"] = unit_of_measure;
|
|
||||||
}
|
|
||||||
|
|
||||||
createInverterInfo(root, inv);
|
|
||||||
|
|
||||||
if (Configuration.get().Mqtt.Hass.Expire) {
|
if (Configuration.get().Mqtt.Hass.Expire) {
|
||||||
root["exp_aft"] = Hoymiles.getNumInverters() * max<uint32_t>(Hoymiles.PollInterval(), Configuration.get().Mqtt.PublishInterval) * inv->getReachableThreshold();
|
root["exp_aft"] = Hoymiles.getNumInverters() * max<uint32_t>(Hoymiles.PollInterval(), Configuration.get().Mqtt.PublishInterval) * inv->getReachableThreshold();
|
||||||
}
|
}
|
||||||
if (devCls != 0) {
|
|
||||||
root["dev_cla"] = devCls;
|
|
||||||
}
|
|
||||||
if (stateCls != 0) {
|
|
||||||
root["stat_cla"] = stateCls;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) {
|
publish(configTopic, root);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
String buffer;
|
|
||||||
serializeJson(root, buffer);
|
|
||||||
publish(configTopic, buffer);
|
|
||||||
} else {
|
} else {
|
||||||
publish(configTopic, "");
|
publish(configTopic, "");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MqttHandleHassClass::publishInverterButton(std::shared_ptr<InverterAbstract> inv, const char* caption, const char* icon, const char* category, const char* deviceClass, const char* subTopic, const char* payload)
|
void MqttHandleHassClass::publishInverterButton(
|
||||||
|
std::shared_ptr<InverterAbstract> inv, const String& name, const String& state_topic, const String& payload,
|
||||||
|
const String& icon,
|
||||||
|
const DeviceClassType device_class, const StateClassType state_class, const CategoryType category)
|
||||||
{
|
{
|
||||||
const String serial = inv->serialString();
|
const String serial = inv->serialString();
|
||||||
|
|
||||||
String buttonId = caption;
|
String buttonId = name;
|
||||||
buttonId.replace(" ", "_");
|
buttonId.replace(" ", "_");
|
||||||
buttonId.toLowerCase();
|
buttonId.toLowerCase();
|
||||||
|
|
||||||
@ -185,41 +186,30 @@ void MqttHandleHassClass::publishInverterButton(std::shared_ptr<InverterAbstract
|
|||||||
+ "/" + buttonId
|
+ "/" + buttonId
|
||||||
+ "/config";
|
+ "/config";
|
||||||
|
|
||||||
const String cmdTopic = MqttSettings.getPrefix() + serial + "/" + subTopic;
|
const String cmdTopic = MqttSettings.getPrefix() + serial + "/" + state_topic;
|
||||||
|
|
||||||
JsonDocument root;
|
JsonDocument root;
|
||||||
|
createInverterInfo(root, inv);
|
||||||
|
addCommonMetadata(root, "", icon, device_class, state_class, category);
|
||||||
|
|
||||||
root["name"] = caption;
|
root["name"] = name;
|
||||||
root["uniq_id"] = serial + "_" + buttonId;
|
root["uniq_id"] = serial + "_" + buttonId;
|
||||||
if (strcmp(icon, "")) {
|
|
||||||
root["ic"] = icon;
|
|
||||||
}
|
|
||||||
if (strcmp(deviceClass, "")) {
|
|
||||||
root["dev_cla"] = deviceClass;
|
|
||||||
}
|
|
||||||
root["ent_cat"] = category;
|
|
||||||
root["cmd_t"] = cmdTopic;
|
root["cmd_t"] = cmdTopic;
|
||||||
root["payload_press"] = payload;
|
root["payload_press"] = payload;
|
||||||
|
|
||||||
createInverterInfo(root, inv);
|
publish(configTopic, root);
|
||||||
|
|
||||||
if (!Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
String buffer;
|
|
||||||
serializeJson(root, buffer);
|
|
||||||
publish(configTopic, buffer);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MqttHandleHassClass::publishInverterNumber(
|
void MqttHandleHassClass::publishInverterNumber(
|
||||||
std::shared_ptr<InverterAbstract> inv, const char* caption, const char* icon, const char* category,
|
std::shared_ptr<InverterAbstract> inv, const String& name,
|
||||||
const char* commandTopic, const char* stateTopic, const char* unitOfMeasure,
|
const String& stateTopic, const String& command_topic,
|
||||||
const int16_t min, const int16_t max, float step)
|
const int16_t min, const int16_t max, float step,
|
||||||
|
const String& unit_of_measure, const String& icon,
|
||||||
|
const StateClassType state_class, const CategoryType category)
|
||||||
{
|
{
|
||||||
const String serial = inv->serialString();
|
const String serial = inv->serialString();
|
||||||
|
|
||||||
String buttonId = caption;
|
String buttonId = name;
|
||||||
buttonId.replace(" ", "_");
|
buttonId.replace(" ", "_");
|
||||||
buttonId.toLowerCase();
|
buttonId.toLowerCase();
|
||||||
|
|
||||||
@ -227,150 +217,22 @@ void MqttHandleHassClass::publishInverterNumber(
|
|||||||
+ "/" + buttonId
|
+ "/" + buttonId
|
||||||
+ "/config";
|
+ "/config";
|
||||||
|
|
||||||
const String cmdTopic = MqttSettings.getPrefix() + serial + "/" + commandTopic;
|
const String cmdTopic = MqttSettings.getPrefix() + serial + "/" + command_topic;
|
||||||
const String statTopic = MqttSettings.getPrefix() + serial + "/" + stateTopic;
|
const String statTopic = MqttSettings.getPrefix() + serial + "/" + stateTopic;
|
||||||
|
|
||||||
JsonDocument root;
|
JsonDocument root;
|
||||||
|
createInverterInfo(root, inv);
|
||||||
|
addCommonMetadata(root, unit_of_measure, icon, DEVICE_CLS_NONE, state_class, category);
|
||||||
|
|
||||||
root["name"] = caption;
|
root["name"] = name;
|
||||||
root["uniq_id"] = serial + "_" + buttonId;
|
root["uniq_id"] = serial + "_" + buttonId;
|
||||||
if (strcmp(icon, "")) {
|
|
||||||
root["ic"] = icon;
|
|
||||||
}
|
|
||||||
root["ent_cat"] = category;
|
|
||||||
root["cmd_t"] = cmdTopic;
|
root["cmd_t"] = cmdTopic;
|
||||||
root["stat_t"] = statTopic;
|
root["stat_t"] = statTopic;
|
||||||
root["unit_of_meas"] = unitOfMeasure;
|
|
||||||
root["min"] = min;
|
root["min"] = min;
|
||||||
root["max"] = max;
|
root["max"] = max;
|
||||||
root["step"] = step;
|
root["step"] = step;
|
||||||
|
|
||||||
createInverterInfo(root, inv);
|
publish(configTopic, root);
|
||||||
|
|
||||||
if (!Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
String buffer;
|
|
||||||
serializeJson(root, buffer);
|
|
||||||
publish(configTopic, buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MqttHandleHassClass::publishInverterBinarySensor(std::shared_ptr<InverterAbstract> inv, const char* caption, const char* subTopic, const char* payload_on, const char* payload_off)
|
|
||||||
{
|
|
||||||
const String serial = inv->serialString();
|
|
||||||
|
|
||||||
String sensorId = caption;
|
|
||||||
sensorId.replace(" ", "_");
|
|
||||||
sensorId.toLowerCase();
|
|
||||||
|
|
||||||
const String configTopic = "binary_sensor/dtu_" + serial
|
|
||||||
+ "/" + sensorId
|
|
||||||
+ "/config";
|
|
||||||
|
|
||||||
const String statTopic = MqttSettings.getPrefix() + serial + "/" + subTopic;
|
|
||||||
|
|
||||||
JsonDocument root;
|
|
||||||
|
|
||||||
root["name"] = caption;
|
|
||||||
root["uniq_id"] = serial + "_" + sensorId;
|
|
||||||
root["stat_t"] = statTopic;
|
|
||||||
root["pl_on"] = payload_on;
|
|
||||||
root["pl_off"] = payload_off;
|
|
||||||
|
|
||||||
createInverterInfo(root, inv);
|
|
||||||
|
|
||||||
if (!Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
String buffer;
|
|
||||||
serializeJson(root, buffer);
|
|
||||||
publish(configTopic, buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MqttHandleHassClass::publishDtuSensor(const char* name, const char* device_class, const char* category, const char* icon, const char* unit_of_measure, const char* subTopic)
|
|
||||||
{
|
|
||||||
String id = name;
|
|
||||||
id.toLowerCase();
|
|
||||||
id.replace(" ", "_");
|
|
||||||
String topic = subTopic;
|
|
||||||
if (topic == "") {
|
|
||||||
topic = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
JsonDocument root;
|
|
||||||
|
|
||||||
root["name"] = name;
|
|
||||||
root["uniq_id"] = getDtuUniqueId() + "_" + id;
|
|
||||||
if (strcmp(device_class, "")) {
|
|
||||||
root["dev_cla"] = device_class;
|
|
||||||
}
|
|
||||||
if (strcmp(category, "")) {
|
|
||||||
root["ent_cat"] = category;
|
|
||||||
}
|
|
||||||
if (strcmp(icon, "")) {
|
|
||||||
root["ic"] = icon;
|
|
||||||
}
|
|
||||||
if (strcmp(unit_of_measure, "")) {
|
|
||||||
root["unit_of_meas"] = unit_of_measure;
|
|
||||||
}
|
|
||||||
root["stat_t"] = MqttSettings.getPrefix() + "dtu" + "/" + topic;
|
|
||||||
|
|
||||||
root["avty_t"] = MqttSettings.getPrefix() + Configuration.get().Mqtt.Lwt.Topic;
|
|
||||||
|
|
||||||
const CONFIG_T& config = Configuration.get();
|
|
||||||
root["pl_avail"] = config.Mqtt.Lwt.Value_Online;
|
|
||||||
root["pl_not_avail"] = config.Mqtt.Lwt.Value_Offline;
|
|
||||||
|
|
||||||
createDtuInfo(root);
|
|
||||||
|
|
||||||
if (!Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
String buffer;
|
|
||||||
const String configTopic = "sensor/" + getDtuUniqueId() + "/" + id + "/config";
|
|
||||||
serializeJson(root, buffer);
|
|
||||||
publish(configTopic, buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MqttHandleHassClass::publishDtuBinarySensor(const char* name, const char* device_class, const char* category, const char* payload_on, const char* payload_off, const char* subTopic)
|
|
||||||
{
|
|
||||||
String id = name;
|
|
||||||
id.toLowerCase();
|
|
||||||
id.replace(" ", "_");
|
|
||||||
|
|
||||||
String topic = subTopic;
|
|
||||||
if (!strcmp(subTopic, "")) {
|
|
||||||
topic = String("dtu/") + "/" + id;
|
|
||||||
}
|
|
||||||
|
|
||||||
JsonDocument root;
|
|
||||||
|
|
||||||
root["name"] = name;
|
|
||||||
root["uniq_id"] = getDtuUniqueId() + "_" + id;
|
|
||||||
root["stat_t"] = MqttSettings.getPrefix() + topic;
|
|
||||||
root["pl_on"] = payload_on;
|
|
||||||
root["pl_off"] = payload_off;
|
|
||||||
|
|
||||||
if (strcmp(device_class, "")) {
|
|
||||||
root["dev_cla"] = device_class;
|
|
||||||
}
|
|
||||||
if (strcmp(category, "")) {
|
|
||||||
root["ent_cat"] = category;
|
|
||||||
}
|
|
||||||
|
|
||||||
createDtuInfo(root);
|
|
||||||
|
|
||||||
if (!Utils::checkJsonAlloc(root, __FUNCTION__, __LINE__)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
String buffer;
|
|
||||||
const String configTopic = "binary_sensor/" + getDtuUniqueId() + "/" + id + "/config";
|
|
||||||
serializeJson(root, buffer);
|
|
||||||
publish(configTopic, buffer);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MqttHandleHassClass::createInverterInfo(JsonDocument& root, std::shared_ptr<InverterAbstract> inv)
|
void MqttHandleHassClass::createInverterInfo(JsonDocument& root, std::shared_ptr<InverterAbstract> inv)
|
||||||
@ -433,4 +295,129 @@ void MqttHandleHassClass::publish(const String& subtopic, const String& payload)
|
|||||||
String topic = Configuration.get().Mqtt.Hass.Topic;
|
String topic = Configuration.get().Mqtt.Hass.Topic;
|
||||||
topic += subtopic;
|
topic += subtopic;
|
||||||
MqttSettings.publishGeneric(topic, payload, Configuration.get().Mqtt.Hass.Retain);
|
MqttSettings.publishGeneric(topic, payload, Configuration.get().Mqtt.Hass.Retain);
|
||||||
|
yield();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MqttHandleHassClass::publish(const String& subtopic, const JsonDocument& doc)
|
||||||
|
{
|
||||||
|
if (!Utils::checkJsonAlloc(doc, __FUNCTION__, __LINE__)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String buffer;
|
||||||
|
serializeJson(doc, buffer);
|
||||||
|
publish(subtopic, buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MqttHandleHassClass::addCommonMetadata(
|
||||||
|
JsonDocument& doc,
|
||||||
|
const String& unit_of_measure, const String& icon,
|
||||||
|
const DeviceClassType device_class, const StateClassType state_class, const CategoryType category)
|
||||||
|
{
|
||||||
|
if (unit_of_measure != "") {
|
||||||
|
doc["unit_of_meas"] = unit_of_measure;
|
||||||
|
}
|
||||||
|
if (icon != "") {
|
||||||
|
doc["ic"] = icon;
|
||||||
|
}
|
||||||
|
if (device_class != DEVICE_CLS_NONE) {
|
||||||
|
doc["dev_cla"] = deviceClass_name[device_class];
|
||||||
|
}
|
||||||
|
if (state_class != STATE_CLS_NONE) {
|
||||||
|
doc["stat_cla"] = stateClass_name[state_class];;
|
||||||
|
}
|
||||||
|
if (category != CATEGORY_NONE) {
|
||||||
|
doc["ent_cat"] = category_name[category];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MqttHandleHassClass::publishBinarySensor(
|
||||||
|
JsonDocument& doc,
|
||||||
|
const String& root_device, const String& unique_id_prefix, const String& name, const String& state_topic, const String& payload_on, const String& payload_off,
|
||||||
|
const DeviceClassType device_class, const StateClassType state_class, const CategoryType category)
|
||||||
|
{
|
||||||
|
String sensor_id = name;
|
||||||
|
sensor_id.toLowerCase();
|
||||||
|
sensor_id.replace(" ", "_");
|
||||||
|
|
||||||
|
doc["name"] = name;
|
||||||
|
doc["uniq_id"] = unique_id_prefix + "_" + sensor_id;
|
||||||
|
doc["stat_t"] = MqttSettings.getPrefix() + state_topic;
|
||||||
|
doc["pl_on"] = payload_on;
|
||||||
|
doc["pl_off"] = payload_off;
|
||||||
|
|
||||||
|
addCommonMetadata(doc, "", "", device_class, state_class, category);
|
||||||
|
|
||||||
|
const String configTopic = "binary_sensor/" + root_device + "/" + sensor_id + "/config";
|
||||||
|
publish(configTopic, doc);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MqttHandleHassClass::publishDtuBinarySensor(
|
||||||
|
const String& name, const String& state_topic, const String& payload_on, const String& payload_off,
|
||||||
|
const DeviceClassType device_class, const StateClassType state_class, const CategoryType category)
|
||||||
|
{
|
||||||
|
const String dtuId = getDtuUniqueId();
|
||||||
|
|
||||||
|
JsonDocument root;
|
||||||
|
createDtuInfo(root);
|
||||||
|
publishBinarySensor(root, dtuId, dtuId, name, state_topic, payload_on, payload_off, device_class, state_class, category);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MqttHandleHassClass::publishInverterBinarySensor(
|
||||||
|
std::shared_ptr<InverterAbstract> inv, const String& name, const String& state_topic, const String& payload_on, const String& payload_off,
|
||||||
|
const DeviceClassType device_class, const StateClassType state_class, const CategoryType category)
|
||||||
|
{
|
||||||
|
const String serial = inv->serialString();
|
||||||
|
|
||||||
|
JsonDocument root;
|
||||||
|
createInverterInfo(root, inv);
|
||||||
|
publishBinarySensor(root, "dtu_" + serial, serial, name, serial + "/" + state_topic, payload_on, payload_off, device_class, state_class, category);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MqttHandleHassClass::publishSensor(
|
||||||
|
JsonDocument& doc,
|
||||||
|
const String& root_device, const String& unique_id_prefix, const String& name, const String& state_topic,
|
||||||
|
const String& unit_of_measure, const String& icon,
|
||||||
|
const DeviceClassType device_class, const StateClassType state_class, const CategoryType category)
|
||||||
|
{
|
||||||
|
String sensor_id = name;
|
||||||
|
sensor_id.toLowerCase();
|
||||||
|
sensor_id.replace(" ", "_");
|
||||||
|
|
||||||
|
doc["name"] = name;
|
||||||
|
doc["uniq_id"] = unique_id_prefix + "_" + sensor_id;
|
||||||
|
doc["stat_t"] = MqttSettings.getPrefix() + state_topic;
|
||||||
|
|
||||||
|
addCommonMetadata(doc, unit_of_measure, icon, device_class, state_class, category);
|
||||||
|
|
||||||
|
const CONFIG_T& config = Configuration.get();
|
||||||
|
doc["avty_t"] = MqttSettings.getPrefix() + config.Mqtt.Lwt.Topic;
|
||||||
|
doc["pl_avail"] = config.Mqtt.Lwt.Value_Online;
|
||||||
|
doc["pl_not_avail"] = config.Mqtt.Lwt.Value_Offline;
|
||||||
|
|
||||||
|
const String configTopic = "sensor/" + root_device + "/" + sensor_id + "/config";
|
||||||
|
publish(configTopic, doc);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MqttHandleHassClass::publishDtuSensor(
|
||||||
|
const String& name, const String& state_topic,
|
||||||
|
const String& unit_of_measure, const String& icon,
|
||||||
|
const DeviceClassType device_class, const StateClassType state_class, const CategoryType category)
|
||||||
|
{
|
||||||
|
const String dtuId = getDtuUniqueId();
|
||||||
|
|
||||||
|
JsonDocument root;
|
||||||
|
createDtuInfo(root);
|
||||||
|
publishSensor(root, dtuId, dtuId, name, state_topic, unit_of_measure, icon, device_class, state_class, category);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MqttHandleHassClass::publishInverterSensor(
|
||||||
|
std::shared_ptr<InverterAbstract> inv, const String& name, const String& state_topic,
|
||||||
|
const String& unit_of_measure, const String& icon,
|
||||||
|
const DeviceClassType device_class, const StateClassType state_class, const CategoryType category)
|
||||||
|
{
|
||||||
|
const String serial = inv->serialString();
|
||||||
|
|
||||||
|
JsonDocument root;
|
||||||
|
createInverterInfo(root, inv);
|
||||||
|
publishSensor(root, "dtu_" + serial, serial, name, serial + "/" + state_topic, unit_of_measure, icon, device_class, state_class, category);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,13 +7,6 @@
|
|||||||
#include "MqttSettings.h"
|
#include "MqttSettings.h"
|
||||||
#include <ctime>
|
#include <ctime>
|
||||||
|
|
||||||
#define TOPIC_SUB_LIMIT_PERSISTENT_RELATIVE "limit_persistent_relative"
|
|
||||||
#define TOPIC_SUB_LIMIT_PERSISTENT_ABSOLUTE "limit_persistent_absolute"
|
|
||||||
#define TOPIC_SUB_LIMIT_NONPERSISTENT_RELATIVE "limit_nonpersistent_relative"
|
|
||||||
#define TOPIC_SUB_LIMIT_NONPERSISTENT_ABSOLUTE "limit_nonpersistent_absolute"
|
|
||||||
#define TOPIC_SUB_POWER "power"
|
|
||||||
#define TOPIC_SUB_RESTART "restart"
|
|
||||||
|
|
||||||
#define PUBLISH_MAX_INTERVAL 60000
|
#define PUBLISH_MAX_INTERVAL 60000
|
||||||
|
|
||||||
MqttHandleInverterClass MqttHandleInverter;
|
MqttHandleInverterClass MqttHandleInverter;
|
||||||
@ -50,6 +43,15 @@ void MqttHandleInverterClass::loop()
|
|||||||
// Name
|
// Name
|
||||||
MqttSettings.publish(subtopic + "/name", inv->name());
|
MqttSettings.publish(subtopic + "/name", inv->name());
|
||||||
|
|
||||||
|
// Radio Statistics
|
||||||
|
MqttSettings.publish(subtopic + "/radio/tx_request", String(inv->RadioStats.TxRequestData));
|
||||||
|
MqttSettings.publish(subtopic + "/radio/tx_re_request", String(inv->RadioStats.TxReRequestFragment));
|
||||||
|
MqttSettings.publish(subtopic + "/radio/rx_success", String(inv->RadioStats.RxSuccess));
|
||||||
|
MqttSettings.publish(subtopic + "/radio/rx_fail_nothing", String(inv->RadioStats.RxFailNoAnswer));
|
||||||
|
MqttSettings.publish(subtopic + "/radio/rx_fail_partial", String(inv->RadioStats.RxFailPartialAnswer));
|
||||||
|
MqttSettings.publish(subtopic + "/radio/rx_fail_corrupt", String(inv->RadioStats.RxFailCorruptData));
|
||||||
|
MqttSettings.publish(subtopic + "/radio/rssi", String(inv->getLastRssi()));
|
||||||
|
|
||||||
if (inv->DevInfo()->getLastUpdate() > 0) {
|
if (inv->DevInfo()->getLastUpdate() > 0) {
|
||||||
// Bootloader Version
|
// Bootloader Version
|
||||||
MqttSettings.publish(subtopic + "/device/bootloaderversion", String(inv->DevInfo()->getFwBootloaderVersion()));
|
MqttSettings.publish(subtopic + "/device/bootloaderversion", String(inv->DevInfo()->getFwBootloaderVersion()));
|
||||||
@ -146,7 +148,7 @@ String MqttHandleInverterClass::getTopic(std::shared_ptr<InverterAbstract> inv,
|
|||||||
return inv->serialString() + "/" + chanNum + "/" + chanName;
|
return inv->serialString() + "/" + chanNum + "/" + chanName;
|
||||||
}
|
}
|
||||||
|
|
||||||
void MqttHandleInverterClass::onMqttMessage(const espMqttClientTypes::MessageProperties& properties, const char* topic, const uint8_t* payload, const size_t len, const size_t index, const size_t total)
|
void MqttHandleInverterClass::onMqttMessage(Topic t, const espMqttClientTypes::MessageProperties& properties, const char* topic, const uint8_t* payload, const size_t len, const size_t index, const size_t total)
|
||||||
{
|
{
|
||||||
const CONFIG_T& config = Configuration.get();
|
const CONFIG_T& config = Configuration.get();
|
||||||
|
|
||||||
@ -154,15 +156,11 @@ void MqttHandleInverterClass::onMqttMessage(const espMqttClientTypes::MessagePro
|
|||||||
strncpy(token_topic, topic, MQTT_MAX_TOPIC_STRLEN + 40); // convert const char* to char*
|
strncpy(token_topic, topic, MQTT_MAX_TOPIC_STRLEN + 40); // convert const char* to char*
|
||||||
|
|
||||||
char* serial_str;
|
char* serial_str;
|
||||||
char* subtopic;
|
|
||||||
char* setting;
|
|
||||||
char* rest = &token_topic[strlen(config.Mqtt.Topic)];
|
char* rest = &token_topic[strlen(config.Mqtt.Topic)];
|
||||||
|
|
||||||
serial_str = strtok_r(rest, "/", &rest);
|
serial_str = strtok_r(rest, "/", &rest);
|
||||||
subtopic = strtok_r(rest, "/", &rest);
|
|
||||||
setting = strtok_r(rest, "/", &rest);
|
|
||||||
|
|
||||||
if (serial_str == NULL || subtopic == NULL || setting == NULL) {
|
if (serial_str == NULL) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -175,33 +173,30 @@ void MqttHandleInverterClass::onMqttMessage(const espMqttClientTypes::MessagePro
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if subtopic is unequal cmd
|
std::string strValue(reinterpret_cast<const char*>(payload), len);
|
||||||
if (strcmp(subtopic, "cmd")) {
|
float payload_val = -1;
|
||||||
|
try {
|
||||||
|
payload_val = std::stof(strValue);
|
||||||
|
} catch (std::invalid_argument const& e) {
|
||||||
|
MessageOutput.printf("MQTT handler: cannot parse payload of topic '%s' as float: %s\r\n",
|
||||||
|
topic, strValue.c_str());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
char* strlimit = new char[len + 1];
|
switch (t) {
|
||||||
memcpy(strlimit, payload, len);
|
case Topic::LimitPersistentRelative:
|
||||||
strlimit[len] = '\0';
|
|
||||||
const float payload_val = strtof(strlimit, NULL);
|
|
||||||
delete[] strlimit;
|
|
||||||
|
|
||||||
if (payload_val < 0) {
|
|
||||||
MessageOutput.printf("MQTT payload < 0 received --> ignoring\r\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!strcmp(setting, TOPIC_SUB_LIMIT_PERSISTENT_RELATIVE)) {
|
|
||||||
// Set inverter limit relative persistent
|
// Set inverter limit relative persistent
|
||||||
MessageOutput.printf("Limit Persistent: %.1f %%\r\n", payload_val);
|
MessageOutput.printf("Limit Persistent: %.1f %%\r\n", payload_val);
|
||||||
inv->sendActivePowerControlRequest(payload_val, PowerLimitControlType::RelativPersistent);
|
inv->sendActivePowerControlRequest(payload_val, PowerLimitControlType::RelativPersistent);
|
||||||
|
break;
|
||||||
|
|
||||||
} else if (!strcmp(setting, TOPIC_SUB_LIMIT_PERSISTENT_ABSOLUTE)) {
|
case Topic::LimitPersistentAbsolute:
|
||||||
// Set inverter limit absolute persistent
|
// Set inverter limit absolute persistent
|
||||||
MessageOutput.printf("Limit Persistent: %.1f W\r\n", payload_val);
|
MessageOutput.printf("Limit Persistent: %.1f W\r\n", payload_val);
|
||||||
inv->sendActivePowerControlRequest(payload_val, PowerLimitControlType::AbsolutPersistent);
|
inv->sendActivePowerControlRequest(payload_val, PowerLimitControlType::AbsolutPersistent);
|
||||||
|
break;
|
||||||
|
|
||||||
} else if (!strcmp(setting, TOPIC_SUB_LIMIT_NONPERSISTENT_RELATIVE)) {
|
case Topic::LimitNonPersistentRelative:
|
||||||
// Set inverter limit relative non persistent
|
// Set inverter limit relative non persistent
|
||||||
MessageOutput.printf("Limit Non-Persistent: %.1f %%\r\n", payload_val);
|
MessageOutput.printf("Limit Non-Persistent: %.1f %%\r\n", payload_val);
|
||||||
if (!properties.retain) {
|
if (!properties.retain) {
|
||||||
@ -209,8 +204,9 @@ void MqttHandleInverterClass::onMqttMessage(const espMqttClientTypes::MessagePro
|
|||||||
} else {
|
} else {
|
||||||
MessageOutput.println("Ignored because retained");
|
MessageOutput.println("Ignored because retained");
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
} else if (!strcmp(setting, TOPIC_SUB_LIMIT_NONPERSISTENT_ABSOLUTE)) {
|
case Topic::LimitNonPersistentAbsolute:
|
||||||
// Set inverter limit absolute non persistent
|
// Set inverter limit absolute non persistent
|
||||||
MessageOutput.printf("Limit Non-Persistent: %.1f W\r\n", payload_val);
|
MessageOutput.printf("Limit Non-Persistent: %.1f W\r\n", payload_val);
|
||||||
if (!properties.retain) {
|
if (!properties.retain) {
|
||||||
@ -218,13 +214,15 @@ void MqttHandleInverterClass::onMqttMessage(const espMqttClientTypes::MessagePro
|
|||||||
} else {
|
} else {
|
||||||
MessageOutput.println("Ignored because retained");
|
MessageOutput.println("Ignored because retained");
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
} else if (!strcmp(setting, TOPIC_SUB_POWER)) {
|
case Topic::Power:
|
||||||
// Turn inverter on or off
|
// Turn inverter on or off
|
||||||
MessageOutput.printf("Set inverter power to: %d\r\n", static_cast<int32_t>(payload_val));
|
MessageOutput.printf("Set inverter power to: %" PRId32 "\r\n", static_cast<int32_t>(payload_val));
|
||||||
inv->sendPowerControlRequest(static_cast<int32_t>(payload_val) > 0);
|
inv->sendPowerControlRequest(static_cast<int32_t>(payload_val) > 0);
|
||||||
|
break;
|
||||||
|
|
||||||
} else if (!strcmp(setting, TOPIC_SUB_RESTART)) {
|
case Topic::Restart:
|
||||||
// Restart inverter
|
// Restart inverter
|
||||||
MessageOutput.printf("Restart inverter\r\n");
|
MessageOutput.printf("Restart inverter\r\n");
|
||||||
if (!properties.retain && payload_val == 1) {
|
if (!properties.retain && payload_val == 1) {
|
||||||
@ -232,34 +230,41 @@ void MqttHandleInverterClass::onMqttMessage(const espMqttClientTypes::MessagePro
|
|||||||
} else {
|
} else {
|
||||||
MessageOutput.println("Ignored because retained or numeric value not '1'");
|
MessageOutput.println("Ignored because retained or numeric value not '1'");
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Topic::ResetRfStats:
|
||||||
|
// Reset RF Stats
|
||||||
|
MessageOutput.printf("Reset RF stats\r\n");
|
||||||
|
if (!properties.retain && payload_val == 1) {
|
||||||
|
inv->resetRadioStats();
|
||||||
|
} else {
|
||||||
|
MessageOutput.println("Ignored because retained or numeric value not '1'");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MqttHandleInverterClass::subscribeTopics()
|
void MqttHandleInverterClass::subscribeTopics()
|
||||||
{
|
{
|
||||||
using std::placeholders::_1;
|
String const& prefix = MqttSettings.getPrefix();
|
||||||
using std::placeholders::_2;
|
|
||||||
using std::placeholders::_3;
|
|
||||||
using std::placeholders::_4;
|
|
||||||
using std::placeholders::_5;
|
|
||||||
using std::placeholders::_6;
|
|
||||||
|
|
||||||
const String topic = MqttSettings.getPrefix();
|
auto subscribe = [&prefix, this](char const* subTopic, Topic t) {
|
||||||
MqttSettings.subscribe(String(topic + "+/cmd/" + TOPIC_SUB_LIMIT_PERSISTENT_RELATIVE), 0, std::bind(&MqttHandleInverterClass::onMqttMessage, this, _1, _2, _3, _4, _5, _6));
|
String fullTopic(prefix + _cmdtopic.data() + subTopic);
|
||||||
MqttSettings.subscribe(String(topic + "+/cmd/" + TOPIC_SUB_LIMIT_PERSISTENT_ABSOLUTE), 0, std::bind(&MqttHandleInverterClass::onMqttMessage, this, _1, _2, _3, _4, _5, _6));
|
MqttSettings.subscribe(fullTopic.c_str(), 0,
|
||||||
MqttSettings.subscribe(String(topic + "+/cmd/" + TOPIC_SUB_LIMIT_NONPERSISTENT_RELATIVE), 0, std::bind(&MqttHandleInverterClass::onMqttMessage, this, _1, _2, _3, _4, _5, _6));
|
std::bind(&MqttHandleInverterClass::onMqttMessage, this, t,
|
||||||
MqttSettings.subscribe(String(topic + "+/cmd/" + TOPIC_SUB_LIMIT_NONPERSISTENT_ABSOLUTE), 0, std::bind(&MqttHandleInverterClass::onMqttMessage, this, _1, _2, _3, _4, _5, _6));
|
std::placeholders::_1, std::placeholders::_2,
|
||||||
MqttSettings.subscribe(String(topic + "+/cmd/" + TOPIC_SUB_POWER), 0, std::bind(&MqttHandleInverterClass::onMqttMessage, this, _1, _2, _3, _4, _5, _6));
|
std::placeholders::_3, std::placeholders::_4,
|
||||||
MqttSettings.subscribe(String(topic + "+/cmd/" + TOPIC_SUB_RESTART), 0, std::bind(&MqttHandleInverterClass::onMqttMessage, this, _1, _2, _3, _4, _5, _6));
|
std::placeholders::_5, std::placeholders::_6));
|
||||||
|
};
|
||||||
|
|
||||||
|
for (auto const& s : _subscriptions) {
|
||||||
|
subscribe(s.first.data(), s.second);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MqttHandleInverterClass::unsubscribeTopics()
|
void MqttHandleInverterClass::unsubscribeTopics()
|
||||||
{
|
{
|
||||||
const String topic = MqttSettings.getPrefix();
|
String const& prefix = MqttSettings.getPrefix() + _cmdtopic.data();
|
||||||
MqttSettings.unsubscribe(String(topic + "+/cmd/" + TOPIC_SUB_LIMIT_PERSISTENT_RELATIVE));
|
for (auto const& s : _subscriptions) {
|
||||||
MqttSettings.unsubscribe(String(topic + "+/cmd/" + TOPIC_SUB_LIMIT_PERSISTENT_ABSOLUTE));
|
MqttSettings.unsubscribe(prefix + s.first.data());
|
||||||
MqttSettings.unsubscribe(String(topic + "+/cmd/" + TOPIC_SUB_LIMIT_NONPERSISTENT_RELATIVE));
|
}
|
||||||
MqttSettings.unsubscribe(String(topic + "+/cmd/" + TOPIC_SUB_LIMIT_NONPERSISTENT_ABSOLUTE));
|
|
||||||
MqttSettings.unsubscribe(String(topic + "+/cmd/" + TOPIC_SUB_POWER));
|
|
||||||
MqttSettings.unsubscribe(String(topic + "+/cmd/" + TOPIC_SUB_RESTART));
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,10 +7,10 @@
|
|||||||
#include "MessageOutput.h"
|
#include "MessageOutput.h"
|
||||||
#include "PinMapping.h"
|
#include "PinMapping.h"
|
||||||
#include "Utils.h"
|
#include "Utils.h"
|
||||||
|
#include "__compiled_constants.h"
|
||||||
#include "defaults.h"
|
#include "defaults.h"
|
||||||
#include <ESPmDNS.h>
|
#include <ESPmDNS.h>
|
||||||
#include <ETH.h>
|
#include <ETH.h>
|
||||||
#include "__compiled_constants.h"
|
|
||||||
|
|
||||||
NetworkSettingsClass::NetworkSettingsClass()
|
NetworkSettingsClass::NetworkSettingsClass()
|
||||||
: _loopTask(TASK_IMMEDIATE, TASK_FOREVER, std::bind(&NetworkSettingsClass::loop, this))
|
: _loopTask(TASK_IMMEDIATE, TASK_FOREVER, std::bind(&NetworkSettingsClass::loop, this))
|
||||||
@ -23,20 +23,41 @@ NetworkSettingsClass::NetworkSettingsClass()
|
|||||||
void NetworkSettingsClass::init(Scheduler& scheduler)
|
void NetworkSettingsClass::init(Scheduler& scheduler)
|
||||||
{
|
{
|
||||||
using std::placeholders::_1;
|
using std::placeholders::_1;
|
||||||
|
using std::placeholders::_2;
|
||||||
|
|
||||||
WiFi.setScanMethod(WIFI_ALL_CHANNEL_SCAN);
|
WiFi.setScanMethod(WIFI_ALL_CHANNEL_SCAN);
|
||||||
WiFi.setSortMethod(WIFI_CONNECT_AP_BY_SIGNAL);
|
WiFi.setSortMethod(WIFI_CONNECT_AP_BY_SIGNAL);
|
||||||
|
|
||||||
WiFi.disconnect(true, true);
|
WiFi.disconnect(true, true);
|
||||||
|
|
||||||
WiFi.onEvent(std::bind(&NetworkSettingsClass::NetworkEvent, this, _1));
|
WiFi.onEvent(std::bind(&NetworkSettingsClass::NetworkEvent, this, _1, _2));
|
||||||
|
|
||||||
|
if (PinMapping.isValidW5500Config()) {
|
||||||
|
PinMapping_t& pin = PinMapping.get();
|
||||||
|
_w5500 = W5500::setup(pin.w5500_mosi, pin.w5500_miso, pin.w5500_sclk, pin.w5500_cs, pin.w5500_int, pin.w5500_rst);
|
||||||
|
if (_w5500)
|
||||||
|
MessageOutput.println("W5500: Connection successful");
|
||||||
|
else
|
||||||
|
MessageOutput.println("W5500: Connection error!!");
|
||||||
|
}
|
||||||
|
#if CONFIG_ETH_USE_ESP32_EMAC
|
||||||
|
else if (PinMapping.isValidEthConfig()) {
|
||||||
|
PinMapping_t& pin = PinMapping.get();
|
||||||
|
#if ESP_ARDUINO_VERSION_MAJOR < 3
|
||||||
|
ETH.begin(pin.eth_phy_addr, pin.eth_power, pin.eth_mdc, pin.eth_mdio, pin.eth_type, pin.eth_clk_mode);
|
||||||
|
#else
|
||||||
|
ETH.begin(pin.eth_type, pin.eth_phy_addr, pin.eth_mdc, pin.eth_mdio, pin.eth_power, pin.eth_clk_mode);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
setupMode();
|
setupMode();
|
||||||
|
|
||||||
scheduler.addTask(_loopTask);
|
scheduler.addTask(_loopTask);
|
||||||
_loopTask.enable();
|
_loopTask.enable();
|
||||||
}
|
}
|
||||||
|
|
||||||
void NetworkSettingsClass::NetworkEvent(const WiFiEvent_t event)
|
void NetworkSettingsClass::NetworkEvent(const WiFiEvent_t event, WiFiEventInfo_t info)
|
||||||
{
|
{
|
||||||
switch (event) {
|
switch (event) {
|
||||||
case ARDUINO_EVENT_ETH_START:
|
case ARDUINO_EVENT_ETH_START:
|
||||||
@ -76,7 +97,8 @@ void NetworkSettingsClass::NetworkEvent(const WiFiEvent_t event)
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case ARDUINO_EVENT_WIFI_STA_DISCONNECTED:
|
case ARDUINO_EVENT_WIFI_STA_DISCONNECTED:
|
||||||
MessageOutput.println("WiFi disconnected");
|
// Reason codes can be found here: https://github.com/espressif/esp-idf/blob/5454d37d496a8c58542eb450467471404c606501/components/esp_wifi/include/esp_wifi_types_generic.h#L79-L141
|
||||||
|
MessageOutput.printf("WiFi disconnected: %" PRId8 "\r\n", info.wifi_sta_disconnected.reason);
|
||||||
if (_networkMode == network_mode::WiFi) {
|
if (_networkMode == network_mode::WiFi) {
|
||||||
MessageOutput.println("Try reconnecting");
|
MessageOutput.println("Try reconnecting");
|
||||||
WiFi.disconnect(true, false);
|
WiFi.disconnect(true, false);
|
||||||
@ -95,12 +117,12 @@ void NetworkSettingsClass::NetworkEvent(const WiFiEvent_t event)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool NetworkSettingsClass::onEvent(NetworkEventCb cbEvent, const network_event event)
|
bool NetworkSettingsClass::onEvent(DtuNetworkEventCb cbEvent, const network_event event)
|
||||||
{
|
{
|
||||||
if (!cbEvent) {
|
if (!cbEvent) {
|
||||||
return pdFALSE;
|
return pdFALSE;
|
||||||
}
|
}
|
||||||
NetworkEventCbList_t newEventHandler;
|
DtuNetworkEventCbList_t newEventHandler;
|
||||||
newEventHandler.cb = cbEvent;
|
newEventHandler.cb = cbEvent;
|
||||||
newEventHandler.event = event;
|
newEventHandler.event = event;
|
||||||
_cbEventList.push_back(newEventHandler);
|
_cbEventList.push_back(newEventHandler);
|
||||||
@ -109,8 +131,7 @@ bool NetworkSettingsClass::onEvent(NetworkEventCb cbEvent, const network_event e
|
|||||||
|
|
||||||
void NetworkSettingsClass::raiseEvent(const network_event event)
|
void NetworkSettingsClass::raiseEvent(const network_event event)
|
||||||
{
|
{
|
||||||
for (uint32_t i = 0; i < _cbEventList.size(); i++) {
|
for (auto& entry : _cbEventList) {
|
||||||
const NetworkEventCbList_t entry = _cbEventList[i];
|
|
||||||
if (entry.cb) {
|
if (entry.cb) {
|
||||||
if (entry.event == event || entry.event == network_event::NETWORK_EVENT_MAX) {
|
if (entry.event == event || entry.event == network_event::NETWORK_EVENT_MAX) {
|
||||||
entry.cb(event);
|
entry.cb(event);
|
||||||
@ -167,11 +188,6 @@ void NetworkSettingsClass::setupMode()
|
|||||||
WiFi.mode(WIFI_MODE_NULL);
|
WiFi.mode(WIFI_MODE_NULL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (PinMapping.isValidEthConfig()) {
|
|
||||||
PinMapping_t& pin = PinMapping.get();
|
|
||||||
ETH.begin(pin.eth_phy_addr, pin.eth_power, pin.eth_mdc, pin.eth_mdio, pin.eth_type, pin.eth_clk_mode);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void NetworkSettingsClass::enableAdminMode()
|
void NetworkSettingsClass::enableAdminMode()
|
||||||
@ -210,7 +226,7 @@ void NetworkSettingsClass::loop()
|
|||||||
if (_adminEnabled && _adminTimeoutCounterMax > 0) {
|
if (_adminEnabled && _adminTimeoutCounterMax > 0) {
|
||||||
_adminTimeoutCounter++;
|
_adminTimeoutCounter++;
|
||||||
if (_adminTimeoutCounter % 10 == 0) {
|
if (_adminTimeoutCounter % 10 == 0) {
|
||||||
MessageOutput.printf("Admin AP remaining seconds: %d / %d\r\n", _adminTimeoutCounter, _adminTimeoutCounterMax);
|
MessageOutput.printf("Admin AP remaining seconds: %" PRId32 " / %" PRId32 "\r\n", _adminTimeoutCounter, _adminTimeoutCounterMax);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_connectTimeoutTimer++;
|
_connectTimeoutTimer++;
|
||||||
@ -399,6 +415,9 @@ String NetworkSettingsClass::macAddress() const
|
|||||||
{
|
{
|
||||||
switch (_networkMode) {
|
switch (_networkMode) {
|
||||||
case network_mode::Ethernet:
|
case network_mode::Ethernet:
|
||||||
|
if (_w5500) {
|
||||||
|
return _w5500->macAddress();
|
||||||
|
}
|
||||||
return ETH.macAddress();
|
return ETH.macAddress();
|
||||||
break;
|
break;
|
||||||
case network_mode::WiFi:
|
case network_mode::WiFi:
|
||||||
|
|||||||
@ -84,6 +84,58 @@
|
|||||||
#define CMT_SDIO -1
|
#define CMT_SDIO -1
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifndef W5500_MOSI
|
||||||
|
#define W5500_MOSI -1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef W5500_MISO
|
||||||
|
#define W5500_MISO -1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef W5500_SCLK
|
||||||
|
#define W5500_SCLK -1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef W5500_CS
|
||||||
|
#define W5500_CS -1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef W5500_INT
|
||||||
|
#define W5500_INT -1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef W5500_RST
|
||||||
|
#define W5500_RST -1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if CONFIG_ETH_USE_ESP32_EMAC
|
||||||
|
|
||||||
|
#ifndef ETH_PHY_ADDR
|
||||||
|
#define ETH_PHY_ADDR -1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef ETH_PHY_POWER
|
||||||
|
#define ETH_PHY_POWER -1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef ETH_PHY_MDC
|
||||||
|
#define ETH_PHY_MDC -1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef ETH_PHY_MDIO
|
||||||
|
#define ETH_PHY_MDIO -1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef ETH_PHY_TYPE
|
||||||
|
#define ETH_PHY_TYPE ETH_PHY_LAN8720
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef ETH_CLK_MODE
|
||||||
|
#define ETH_CLK_MODE ETH_CLOCK_GPIO0_IN
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
PinMappingClass PinMapping;
|
PinMappingClass PinMapping;
|
||||||
|
|
||||||
PinMappingClass::PinMappingClass()
|
PinMappingClass::PinMappingClass()
|
||||||
@ -103,18 +155,26 @@ PinMappingClass::PinMappingClass()
|
|||||||
_pinMapping.cmt_gpio3 = CMT_GPIO3;
|
_pinMapping.cmt_gpio3 = CMT_GPIO3;
|
||||||
_pinMapping.cmt_sdio = CMT_SDIO;
|
_pinMapping.cmt_sdio = CMT_SDIO;
|
||||||
|
|
||||||
|
_pinMapping.w5500_mosi = W5500_MOSI;
|
||||||
|
_pinMapping.w5500_miso = W5500_MISO;
|
||||||
|
_pinMapping.w5500_sclk = W5500_SCLK;
|
||||||
|
_pinMapping.w5500_cs = W5500_CS;
|
||||||
|
_pinMapping.w5500_int = W5500_INT;
|
||||||
|
_pinMapping.w5500_rst = W5500_RST;
|
||||||
|
|
||||||
|
#if CONFIG_ETH_USE_ESP32_EMAC
|
||||||
#ifdef OPENDTU_ETHERNET
|
#ifdef OPENDTU_ETHERNET
|
||||||
_pinMapping.eth_enabled = true;
|
_pinMapping.eth_enabled = true;
|
||||||
#else
|
#else
|
||||||
_pinMapping.eth_enabled = false;
|
_pinMapping.eth_enabled = false;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
_pinMapping.eth_phy_addr = ETH_PHY_ADDR;
|
_pinMapping.eth_phy_addr = ETH_PHY_ADDR;
|
||||||
_pinMapping.eth_power = ETH_PHY_POWER;
|
_pinMapping.eth_power = ETH_PHY_POWER;
|
||||||
_pinMapping.eth_mdc = ETH_PHY_MDC;
|
_pinMapping.eth_mdc = ETH_PHY_MDC;
|
||||||
_pinMapping.eth_mdio = ETH_PHY_MDIO;
|
_pinMapping.eth_mdio = ETH_PHY_MDIO;
|
||||||
_pinMapping.eth_type = ETH_PHY_TYPE;
|
_pinMapping.eth_type = ETH_PHY_TYPE;
|
||||||
_pinMapping.eth_clk_mode = ETH_CLK_MODE;
|
_pinMapping.eth_clk_mode = ETH_CLK_MODE;
|
||||||
|
#endif
|
||||||
|
|
||||||
_pinMapping.display_type = DISPLAY_TYPE;
|
_pinMapping.display_type = DISPLAY_TYPE;
|
||||||
_pinMapping.display_data = DISPLAY_DATA;
|
_pinMapping.display_data = DISPLAY_DATA;
|
||||||
@ -164,18 +224,26 @@ bool PinMappingClass::init(const String& deviceMapping)
|
|||||||
_pinMapping.cmt_gpio3 = doc[i]["cmt"]["gpio3"] | CMT_GPIO3;
|
_pinMapping.cmt_gpio3 = doc[i]["cmt"]["gpio3"] | CMT_GPIO3;
|
||||||
_pinMapping.cmt_sdio = doc[i]["cmt"]["sdio"] | CMT_SDIO;
|
_pinMapping.cmt_sdio = doc[i]["cmt"]["sdio"] | CMT_SDIO;
|
||||||
|
|
||||||
|
_pinMapping.w5500_mosi = doc[i]["w5500"]["mosi"] | W5500_MOSI;
|
||||||
|
_pinMapping.w5500_miso = doc[i]["w5500"]["miso"] | W5500_MISO;
|
||||||
|
_pinMapping.w5500_sclk = doc[i]["w5500"]["sclk"] | W5500_SCLK;
|
||||||
|
_pinMapping.w5500_cs = doc[i]["w5500"]["cs"] | W5500_CS;
|
||||||
|
_pinMapping.w5500_int = doc[i]["w5500"]["int"] | W5500_INT;
|
||||||
|
_pinMapping.w5500_rst = doc[i]["w5500"]["rst"] | W5500_RST;
|
||||||
|
|
||||||
|
#if CONFIG_ETH_USE_ESP32_EMAC
|
||||||
#ifdef OPENDTU_ETHERNET
|
#ifdef OPENDTU_ETHERNET
|
||||||
_pinMapping.eth_enabled = doc[i]["eth"]["enabled"] | true;
|
_pinMapping.eth_enabled = doc[i]["eth"]["enabled"] | true;
|
||||||
#else
|
#else
|
||||||
_pinMapping.eth_enabled = doc[i]["eth"]["enabled"] | false;
|
_pinMapping.eth_enabled = doc[i]["eth"]["enabled"] | false;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
_pinMapping.eth_phy_addr = doc[i]["eth"]["phy_addr"] | ETH_PHY_ADDR;
|
_pinMapping.eth_phy_addr = doc[i]["eth"]["phy_addr"] | ETH_PHY_ADDR;
|
||||||
_pinMapping.eth_power = doc[i]["eth"]["power"] | ETH_PHY_POWER;
|
_pinMapping.eth_power = doc[i]["eth"]["power"] | ETH_PHY_POWER;
|
||||||
_pinMapping.eth_mdc = doc[i]["eth"]["mdc"] | ETH_PHY_MDC;
|
_pinMapping.eth_mdc = doc[i]["eth"]["mdc"] | ETH_PHY_MDC;
|
||||||
_pinMapping.eth_mdio = doc[i]["eth"]["mdio"] | ETH_PHY_MDIO;
|
_pinMapping.eth_mdio = doc[i]["eth"]["mdio"] | ETH_PHY_MDIO;
|
||||||
_pinMapping.eth_type = doc[i]["eth"]["type"] | ETH_PHY_TYPE;
|
_pinMapping.eth_type = doc[i]["eth"]["type"] | ETH_PHY_TYPE;
|
||||||
_pinMapping.eth_clk_mode = doc[i]["eth"]["clk_mode"] | ETH_CLK_MODE;
|
_pinMapping.eth_clk_mode = doc[i]["eth"]["clk_mode"] | ETH_CLK_MODE;
|
||||||
|
#endif
|
||||||
|
|
||||||
_pinMapping.display_type = doc[i]["display"]["type"] | DISPLAY_TYPE;
|
_pinMapping.display_type = doc[i]["display"]["type"] | DISPLAY_TYPE;
|
||||||
_pinMapping.display_data = doc[i]["display"]["data"] | DISPLAY_DATA;
|
_pinMapping.display_data = doc[i]["display"]["data"] | DISPLAY_DATA;
|
||||||
@ -211,7 +279,21 @@ bool PinMappingClass::isValidCmt2300Config() const
|
|||||||
&& _pinMapping.cmt_sdio >= 0;
|
&& _pinMapping.cmt_sdio >= 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool PinMappingClass::isValidW5500Config() const
|
||||||
|
{
|
||||||
|
return _pinMapping.w5500_mosi >= 0
|
||||||
|
&& _pinMapping.w5500_miso >= 0
|
||||||
|
&& _pinMapping.w5500_sclk >= 0
|
||||||
|
&& _pinMapping.w5500_cs >= 0
|
||||||
|
&& _pinMapping.w5500_int >= 0
|
||||||
|
&& _pinMapping.w5500_rst >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if CONFIG_ETH_USE_ESP32_EMAC
|
||||||
bool PinMappingClass::isValidEthConfig() const
|
bool PinMappingClass::isValidEthConfig() const
|
||||||
{
|
{
|
||||||
return _pinMapping.eth_enabled;
|
return _pinMapping.eth_enabled
|
||||||
|
&& _pinMapping.eth_mdc >= 0
|
||||||
|
&& _pinMapping.eth_mdio >= 0;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|||||||
36
src/RestartHelper.cpp
Normal file
36
src/RestartHelper.cpp
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2024 Thomas Basler and others
|
||||||
|
*/
|
||||||
|
#include "RestartHelper.h"
|
||||||
|
#include "Display_Graphic.h"
|
||||||
|
#include "Led_Single.h"
|
||||||
|
#include <Esp.h>
|
||||||
|
|
||||||
|
RestartHelperClass RestartHelper;
|
||||||
|
|
||||||
|
RestartHelperClass::RestartHelperClass()
|
||||||
|
: _rebootTask(1 * TASK_SECOND, TASK_FOREVER, std::bind(&RestartHelperClass::loop, this))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void RestartHelperClass::init(Scheduler& scheduler)
|
||||||
|
{
|
||||||
|
scheduler.addTask(_rebootTask);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RestartHelperClass::triggerRestart()
|
||||||
|
{
|
||||||
|
_rebootTask.enable();
|
||||||
|
_rebootTask.restart();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RestartHelperClass::loop()
|
||||||
|
{
|
||||||
|
if (_rebootTask.isFirstIteration()) {
|
||||||
|
LedSingle.turnAllOff();
|
||||||
|
Display.setStatus(false);
|
||||||
|
} else {
|
||||||
|
ESP.restart();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -4,10 +4,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "Utils.h"
|
#include "Utils.h"
|
||||||
#include "Display_Graphic.h"
|
|
||||||
#include "Led_Single.h"
|
|
||||||
#include "MessageOutput.h"
|
#include "MessageOutput.h"
|
||||||
#include <Esp.h>
|
#include "PinMapping.h"
|
||||||
#include <LittleFS.h>
|
#include <LittleFS.h>
|
||||||
|
|
||||||
uint32_t Utils::getChipId()
|
uint32_t Utils::getChipId()
|
||||||
@ -59,20 +57,10 @@ int Utils::getTimezoneOffset()
|
|||||||
return static_cast<int>(difftime(rawtime, gmt));
|
return static_cast<int>(difftime(rawtime, gmt));
|
||||||
}
|
}
|
||||||
|
|
||||||
void Utils::restartDtu()
|
|
||||||
{
|
|
||||||
LedSingle.turnAllOff();
|
|
||||||
Display.setStatus(false);
|
|
||||||
yield();
|
|
||||||
delay(1000);
|
|
||||||
yield();
|
|
||||||
ESP.restart();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Utils::checkJsonAlloc(const JsonDocument& doc, const char* function, const uint16_t line)
|
bool Utils::checkJsonAlloc(const JsonDocument& doc, const char* function, const uint16_t line)
|
||||||
{
|
{
|
||||||
if (doc.overflowed()) {
|
if (doc.overflowed()) {
|
||||||
MessageOutput.printf("Alloc failed: %s, %d\r\n", function, line);
|
MessageOutput.printf("Alloc failed: %s, %" PRId16 "\r\n", function, line);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
153
src/W5500.cpp
Normal file
153
src/W5500.cpp
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2024 Thomas Basler and others
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "W5500.h"
|
||||||
|
|
||||||
|
#include <SpiManager.h>
|
||||||
|
#include <driver/spi_master.h>
|
||||||
|
|
||||||
|
// Internal Arduino functions from WiFiGeneric
|
||||||
|
void tcpipInit();
|
||||||
|
void add_esp_interface_netif(esp_interface_t interface, esp_netif_t* esp_netif);
|
||||||
|
|
||||||
|
W5500::W5500(spi_device_handle_t spi, gpio_num_t pin_int)
|
||||||
|
: eth_handle(nullptr)
|
||||||
|
, eth_netif(nullptr)
|
||||||
|
{
|
||||||
|
// Arduino function to start networking stack if not already started
|
||||||
|
tcpipInit();
|
||||||
|
|
||||||
|
ESP_ERROR_CHECK(tcpip_adapter_set_default_eth_handlers());
|
||||||
|
|
||||||
|
eth_w5500_config_t w5500_config = ETH_W5500_DEFAULT_CONFIG(spi);
|
||||||
|
w5500_config.int_gpio_num = pin_int;
|
||||||
|
|
||||||
|
eth_mac_config_t mac_config = ETH_MAC_DEFAULT_CONFIG();
|
||||||
|
mac_config.rx_task_stack_size = 4096;
|
||||||
|
esp_eth_mac_t* mac = esp_eth_mac_new_w5500(&w5500_config, &mac_config);
|
||||||
|
|
||||||
|
eth_phy_config_t phy_config = ETH_PHY_DEFAULT_CONFIG();
|
||||||
|
phy_config.reset_gpio_num = -1;
|
||||||
|
esp_eth_phy_t* phy = esp_eth_phy_new_w5500(&phy_config);
|
||||||
|
|
||||||
|
esp_eth_config_t eth_config = ETH_DEFAULT_CONFIG(mac, phy);
|
||||||
|
ESP_ERROR_CHECK(esp_eth_driver_install(ð_config, ð_handle));
|
||||||
|
|
||||||
|
// Configure MAC address
|
||||||
|
uint8_t mac_addr[6];
|
||||||
|
ESP_ERROR_CHECK(esp_read_mac(mac_addr, ESP_MAC_ETH));
|
||||||
|
ESP_ERROR_CHECK(esp_eth_ioctl(eth_handle, ETH_CMD_S_MAC_ADDR, mac_addr));
|
||||||
|
|
||||||
|
esp_netif_config_t netif_config = ESP_NETIF_DEFAULT_ETH();
|
||||||
|
eth_netif = esp_netif_new(&netif_config);
|
||||||
|
|
||||||
|
ESP_ERROR_CHECK(esp_netif_attach(eth_netif, esp_eth_new_netif_glue(eth_handle)));
|
||||||
|
|
||||||
|
// Add to Arduino
|
||||||
|
add_esp_interface_netif(ESP_IF_ETH, eth_netif);
|
||||||
|
|
||||||
|
ESP_ERROR_CHECK(esp_eth_start(eth_handle));
|
||||||
|
}
|
||||||
|
|
||||||
|
W5500::~W5500()
|
||||||
|
{
|
||||||
|
// TODO(LennartF22): support cleanup at some point?
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<W5500> W5500::setup(int8_t pin_mosi, int8_t pin_miso, int8_t pin_sclk, int8_t pin_cs, int8_t pin_int, int8_t pin_rst)
|
||||||
|
{
|
||||||
|
gpio_reset_pin(static_cast<gpio_num_t>(pin_rst));
|
||||||
|
gpio_set_level(static_cast<gpio_num_t>(pin_rst), 0);
|
||||||
|
gpio_set_direction(static_cast<gpio_num_t>(pin_rst), GPIO_MODE_OUTPUT);
|
||||||
|
|
||||||
|
gpio_reset_pin(static_cast<gpio_num_t>(pin_cs));
|
||||||
|
gpio_reset_pin(static_cast<gpio_num_t>(pin_int));
|
||||||
|
|
||||||
|
auto bus_config = std::make_shared<SpiBusConfig>(
|
||||||
|
static_cast<gpio_num_t>(pin_mosi),
|
||||||
|
static_cast<gpio_num_t>(pin_miso),
|
||||||
|
static_cast<gpio_num_t>(pin_sclk));
|
||||||
|
|
||||||
|
spi_device_interface_config_t device_config {
|
||||||
|
.command_bits = 16, // actually address phase
|
||||||
|
.address_bits = 8, // actually command phase
|
||||||
|
.dummy_bits = 0,
|
||||||
|
.mode = 0,
|
||||||
|
.duty_cycle_pos = 0,
|
||||||
|
.cs_ena_pretrans = 0, // only 0 supported
|
||||||
|
.cs_ena_posttrans = 0, // only 0 supported
|
||||||
|
.clock_speed_hz = 20000000, // stable with OpenDTU Fusion shield
|
||||||
|
.input_delay_ns = 0,
|
||||||
|
.spics_io_num = pin_cs,
|
||||||
|
.flags = 0,
|
||||||
|
.queue_size = 20,
|
||||||
|
.pre_cb = nullptr,
|
||||||
|
.post_cb = nullptr,
|
||||||
|
};
|
||||||
|
|
||||||
|
spi_device_handle_t spi = SpiManagerInst.alloc_device("", bus_config, device_config);
|
||||||
|
if (!spi)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
// Reset sequence
|
||||||
|
delayMicroseconds(500);
|
||||||
|
gpio_set_level(static_cast<gpio_num_t>(pin_rst), 1);
|
||||||
|
delayMicroseconds(1000);
|
||||||
|
|
||||||
|
if (!connection_check_spi(spi))
|
||||||
|
return nullptr;
|
||||||
|
if (!connection_check_interrupt(static_cast<gpio_num_t>(pin_int)))
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
// Use Arduino functions to temporarily attach interrupt to enable the GPIO ISR service
|
||||||
|
// (if we used ESP-IDF functions, a warning would be printed the first time anyone uses attachInterrupt)
|
||||||
|
attachInterrupt(pin_int, nullptr, FALLING);
|
||||||
|
detachInterrupt(pin_int);
|
||||||
|
|
||||||
|
// Return to default state once again after connection check and temporary interrupt registration
|
||||||
|
gpio_reset_pin(static_cast<gpio_num_t>(pin_int));
|
||||||
|
|
||||||
|
return std::unique_ptr<W5500>(new W5500(spi, static_cast<gpio_num_t>(pin_int)));
|
||||||
|
}
|
||||||
|
|
||||||
|
String W5500::macAddress()
|
||||||
|
{
|
||||||
|
uint8_t mac_addr[6] = {};
|
||||||
|
esp_eth_ioctl(eth_handle, ETH_CMD_G_MAC_ADDR, mac_addr);
|
||||||
|
|
||||||
|
char mac_addr_str[18];
|
||||||
|
snprintf(
|
||||||
|
mac_addr_str, sizeof(mac_addr_str), "%02X:%02X:%02X:%02X:%02X:%02X",
|
||||||
|
mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5]);
|
||||||
|
return String(mac_addr_str);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool W5500::connection_check_spi(spi_device_handle_t spi)
|
||||||
|
{
|
||||||
|
spi_transaction_t trans = {
|
||||||
|
.flags = SPI_TRANS_USE_RXDATA,
|
||||||
|
.cmd = 0x0039, // actually address (VERSIONR)
|
||||||
|
.addr = (0b00000 << 3) | (0 << 2) | (0b00 < 0), // actually command (common register, read, VDM)
|
||||||
|
.length = 8,
|
||||||
|
.rxlength = 8,
|
||||||
|
.user = nullptr,
|
||||||
|
.tx_buffer = nullptr,
|
||||||
|
.rx_data = {},
|
||||||
|
};
|
||||||
|
ESP_ERROR_CHECK(spi_device_polling_transmit(spi, &trans));
|
||||||
|
|
||||||
|
// Version number (VERSIONR) is always 0x04
|
||||||
|
return *reinterpret_cast<uint8_t*>(&trans.rx_data) == 0x04;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool W5500::connection_check_interrupt(gpio_num_t pin_int)
|
||||||
|
{
|
||||||
|
gpio_set_direction(pin_int, GPIO_MODE_INPUT);
|
||||||
|
gpio_set_pull_mode(pin_int, GPIO_PULLDOWN_ONLY);
|
||||||
|
int level = gpio_get_level(pin_int);
|
||||||
|
|
||||||
|
// Interrupt line must be high
|
||||||
|
return level == 1;
|
||||||
|
}
|
||||||
@ -39,6 +39,12 @@ void WebApiClass::init(Scheduler& scheduler)
|
|||||||
_server.begin();
|
_server.begin();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void WebApiClass::reload()
|
||||||
|
{
|
||||||
|
_webApiWsConsole.reload();
|
||||||
|
_webApiWsLive.reload();
|
||||||
|
}
|
||||||
|
|
||||||
bool WebApiClass::checkCredentials(AsyncWebServerRequest* request)
|
bool WebApiClass::checkCredentials(AsyncWebServerRequest* request)
|
||||||
{
|
{
|
||||||
CONFIG_T& config = Configuration.get();
|
CONFIG_T& config = Configuration.get();
|
||||||
@ -131,7 +137,7 @@ bool WebApiClass::sendJsonResponse(AsyncWebServerRequest* request, AsyncJsonResp
|
|||||||
root["code"] = WebApiError::GenericInternalServerError;
|
root["code"] = WebApiError::GenericInternalServerError;
|
||||||
root["type"] = "danger";
|
root["type"] = "danger";
|
||||||
response->setCode(500);
|
response->setCode(500);
|
||||||
MessageOutput.printf("WebResponse failed: %s, %d\r\n", function, line);
|
MessageOutput.printf("WebResponse failed: %s, %" PRId16 "\r\n", function, line);
|
||||||
ret_val = false;
|
ret_val = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -4,6 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
#include "WebApi_config.h"
|
#include "WebApi_config.h"
|
||||||
#include "Configuration.h"
|
#include "Configuration.h"
|
||||||
|
#include "RestartHelper.h"
|
||||||
#include "Utils.h"
|
#include "Utils.h"
|
||||||
#include "WebApi.h"
|
#include "WebApi.h"
|
||||||
#include "WebApi_errors.h"
|
#include "WebApi_errors.h"
|
||||||
@ -61,7 +62,7 @@ void WebApiConfigClass::onConfigDelete(AsyncWebServerRequest* request)
|
|||||||
|
|
||||||
auto& retMsg = response->getRoot();
|
auto& retMsg = response->getRoot();
|
||||||
|
|
||||||
if (!(root.containsKey("delete"))) {
|
if (!(root["delete"].is<bool>())) {
|
||||||
retMsg["message"] = "Values are missing!";
|
retMsg["message"] = "Values are missing!";
|
||||||
retMsg["code"] = WebApiError::GenericValueMissing;
|
retMsg["code"] = WebApiError::GenericValueMissing;
|
||||||
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
@ -82,7 +83,7 @@ void WebApiConfigClass::onConfigDelete(AsyncWebServerRequest* request)
|
|||||||
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
|
|
||||||
Utils::removeAllFiles();
|
Utils::removeAllFiles();
|
||||||
Utils::restartDtu();
|
RestartHelper.triggerRestart();
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebApiConfigClass::onConfigListGet(AsyncWebServerRequest* request)
|
void WebApiConfigClass::onConfigListGet(AsyncWebServerRequest* request)
|
||||||
@ -124,7 +125,7 @@ void WebApiConfigClass::onConfigUploadFinish(AsyncWebServerRequest* request)
|
|||||||
response->addHeader("Connection", "close");
|
response->addHeader("Connection", "close");
|
||||||
response->addHeader("Access-Control-Allow-Origin", "*");
|
response->addHeader("Access-Control-Allow-Origin", "*");
|
||||||
request->send(response);
|
request->send(response);
|
||||||
Utils::restartDtu();
|
RestartHelper.triggerRestart();
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebApiConfigClass::onConfigUpload(AsyncWebServerRequest* request, String filename, size_t index, uint8_t* data, size_t len, bool final)
|
void WebApiConfigClass::onConfigUpload(AsyncWebServerRequest* request, String filename, size_t index, uint8_t* data, size_t len, bool final)
|
||||||
|
|||||||
@ -6,7 +6,7 @@
|
|||||||
#include "Configuration.h"
|
#include "Configuration.h"
|
||||||
#include "Display_Graphic.h"
|
#include "Display_Graphic.h"
|
||||||
#include "PinMapping.h"
|
#include "PinMapping.h"
|
||||||
#include "Utils.h"
|
#include "RestartHelper.h"
|
||||||
#include "WebApi.h"
|
#include "WebApi.h"
|
||||||
#include "WebApi_errors.h"
|
#include "WebApi_errors.h"
|
||||||
#include "helper.h"
|
#include "helper.h"
|
||||||
@ -50,6 +50,15 @@ void WebApiDeviceClass::onDeviceAdminGet(AsyncWebServerRequest* request)
|
|||||||
cmtPinObj["gpio2"] = pin.cmt_gpio2;
|
cmtPinObj["gpio2"] = pin.cmt_gpio2;
|
||||||
cmtPinObj["gpio3"] = pin.cmt_gpio3;
|
cmtPinObj["gpio3"] = pin.cmt_gpio3;
|
||||||
|
|
||||||
|
auto w5500PinObj = curPin["w5500"].to<JsonObject>();
|
||||||
|
w5500PinObj["sclk"] = pin.w5500_sclk;
|
||||||
|
w5500PinObj["mosi"] = pin.w5500_mosi;
|
||||||
|
w5500PinObj["miso"] = pin.w5500_miso;
|
||||||
|
w5500PinObj["cs"] = pin.w5500_cs;
|
||||||
|
w5500PinObj["int"] = pin.w5500_int;
|
||||||
|
w5500PinObj["rst"] = pin.w5500_rst;
|
||||||
|
|
||||||
|
#if CONFIG_ETH_USE_ESP32_EMAC
|
||||||
auto ethPinObj = curPin["eth"].to<JsonObject>();
|
auto ethPinObj = curPin["eth"].to<JsonObject>();
|
||||||
ethPinObj["enabled"] = pin.eth_enabled;
|
ethPinObj["enabled"] = pin.eth_enabled;
|
||||||
ethPinObj["phy_addr"] = pin.eth_phy_addr;
|
ethPinObj["phy_addr"] = pin.eth_phy_addr;
|
||||||
@ -58,6 +67,7 @@ void WebApiDeviceClass::onDeviceAdminGet(AsyncWebServerRequest* request)
|
|||||||
ethPinObj["mdio"] = pin.eth_mdio;
|
ethPinObj["mdio"] = pin.eth_mdio;
|
||||||
ethPinObj["type"] = pin.eth_type;
|
ethPinObj["type"] = pin.eth_type;
|
||||||
ethPinObj["clk_mode"] = pin.eth_clk_mode;
|
ethPinObj["clk_mode"] = pin.eth_clk_mode;
|
||||||
|
#endif
|
||||||
|
|
||||||
auto displayPinObj = curPin["display"].to<JsonObject>();
|
auto displayPinObj = curPin["display"].to<JsonObject>();
|
||||||
displayPinObj["type"] = pin.display_type;
|
displayPinObj["type"] = pin.display_type;
|
||||||
@ -103,8 +113,8 @@ void WebApiDeviceClass::onDeviceAdminPost(AsyncWebServerRequest* request)
|
|||||||
|
|
||||||
auto& retMsg = response->getRoot();
|
auto& retMsg = response->getRoot();
|
||||||
|
|
||||||
if (!(root.containsKey("curPin")
|
if (!(root["curPin"].is<JsonObject>()
|
||||||
|| root.containsKey("display"))) {
|
|| root["display"].is<JsonObject>())) {
|
||||||
retMsg["message"] = "Values are missing!";
|
retMsg["message"] = "Values are missing!";
|
||||||
retMsg["code"] = WebApiError::GenericValueMissing;
|
retMsg["code"] = WebApiError::GenericValueMissing;
|
||||||
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
@ -149,6 +159,6 @@ void WebApiDeviceClass::onDeviceAdminPost(AsyncWebServerRequest* request)
|
|||||||
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
|
|
||||||
if (performRestart) {
|
if (performRestart) {
|
||||||
Utils::restartDtu();
|
RestartHelper.triggerRestart();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -49,7 +49,7 @@ void WebApiDtuClass::onDtuAdminGet(AsyncWebServerRequest* request)
|
|||||||
|
|
||||||
// DTU Serial is read as HEX
|
// DTU Serial is read as HEX
|
||||||
char buffer[sizeof(uint64_t) * 8 + 1];
|
char buffer[sizeof(uint64_t) * 8 + 1];
|
||||||
snprintf(buffer, sizeof(buffer), "%0x%08x",
|
snprintf(buffer, sizeof(buffer), "%0" PRIx32 "%08" PRIx32,
|
||||||
((uint32_t)((config.Dtu.Serial >> 32) & 0xFFFFFFFF)),
|
((uint32_t)((config.Dtu.Serial >> 32) & 0xFFFFFFFF)),
|
||||||
((uint32_t)(config.Dtu.Serial & 0xFFFFFFFF)));
|
((uint32_t)(config.Dtu.Serial & 0xFFFFFFFF)));
|
||||||
root["serial"] = buffer;
|
root["serial"] = buffer;
|
||||||
@ -90,12 +90,12 @@ void WebApiDtuClass::onDtuAdminPost(AsyncWebServerRequest* request)
|
|||||||
|
|
||||||
auto& retMsg = response->getRoot();
|
auto& retMsg = response->getRoot();
|
||||||
|
|
||||||
if (!(root.containsKey("serial")
|
if (!(root["serial"].is<String>()
|
||||||
&& root.containsKey("pollinterval")
|
&& root["pollinterval"].is<uint32_t>()
|
||||||
&& root.containsKey("nrf_palevel")
|
&& root["nrf_palevel"].is<uint8_t>()
|
||||||
&& root.containsKey("cmt_palevel")
|
&& root["cmt_palevel"].is<int8_t>()
|
||||||
&& root.containsKey("cmt_frequency")
|
&& root["cmt_frequency"].is<uint32_t>()
|
||||||
&& root.containsKey("cmt_country"))) {
|
&& root["cmt_country"].is<uint8_t>())) {
|
||||||
retMsg["message"] = "Values are missing!";
|
retMsg["message"] = "Values are missing!";
|
||||||
retMsg["code"] = WebApiError::GenericValueMissing;
|
retMsg["code"] = WebApiError::GenericValueMissing;
|
||||||
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
|
|||||||
@ -4,6 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
#include "WebApi_firmware.h"
|
#include "WebApi_firmware.h"
|
||||||
#include "Configuration.h"
|
#include "Configuration.h"
|
||||||
|
#include "RestartHelper.h"
|
||||||
#include "Update.h"
|
#include "Update.h"
|
||||||
#include "Utils.h"
|
#include "Utils.h"
|
||||||
#include "WebApi.h"
|
#include "WebApi.h"
|
||||||
@ -37,7 +38,7 @@ void WebApiFirmwareClass::onFirmwareUpdateFinish(AsyncWebServerRequest* request)
|
|||||||
response->addHeader("Connection", "close");
|
response->addHeader("Connection", "close");
|
||||||
response->addHeader("Access-Control-Allow-Origin", "*");
|
response->addHeader("Access-Control-Allow-Origin", "*");
|
||||||
request->send(response);
|
request->send(response);
|
||||||
Utils::restartDtu();
|
RestartHelper.triggerRestart();
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebApiFirmwareClass::onFirmwareUpdateUpload(AsyncWebServerRequest* request, String filename, size_t index, uint8_t* data, size_t len, bool final)
|
void WebApiFirmwareClass::onFirmwareUpdateUpload(AsyncWebServerRequest* request, String filename, size_t index, uint8_t* data, size_t len, bool final)
|
||||||
|
|||||||
@ -21,6 +21,7 @@ void WebApiInverterClass::init(AsyncWebServer& server, Scheduler& scheduler)
|
|||||||
server.on("/api/inverter/edit", HTTP_POST, std::bind(&WebApiInverterClass::onInverterEdit, this, _1));
|
server.on("/api/inverter/edit", HTTP_POST, std::bind(&WebApiInverterClass::onInverterEdit, this, _1));
|
||||||
server.on("/api/inverter/del", HTTP_POST, std::bind(&WebApiInverterClass::onInverterDelete, this, _1));
|
server.on("/api/inverter/del", HTTP_POST, std::bind(&WebApiInverterClass::onInverterDelete, this, _1));
|
||||||
server.on("/api/inverter/order", HTTP_POST, std::bind(&WebApiInverterClass::onInverterOrder, this, _1));
|
server.on("/api/inverter/order", HTTP_POST, std::bind(&WebApiInverterClass::onInverterOrder, this, _1));
|
||||||
|
server.on("/api/inverter/stats_reset", HTTP_GET, std::bind(&WebApiInverterClass::onInverterStatReset, this, _1));
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebApiInverterClass::onInverterList(AsyncWebServerRequest* request)
|
void WebApiInverterClass::onInverterList(AsyncWebServerRequest* request)
|
||||||
@ -44,7 +45,7 @@ void WebApiInverterClass::onInverterList(AsyncWebServerRequest* request)
|
|||||||
|
|
||||||
// Inverter Serial is read as HEX
|
// Inverter Serial is read as HEX
|
||||||
char buffer[sizeof(uint64_t) * 8 + 1];
|
char buffer[sizeof(uint64_t) * 8 + 1];
|
||||||
snprintf(buffer, sizeof(buffer), "%0x%08x",
|
snprintf(buffer, sizeof(buffer), "%0" PRIx32 "%08" PRIx32,
|
||||||
((uint32_t)((config.Inverter[i].Serial >> 32) & 0xFFFFFFFF)),
|
((uint32_t)((config.Inverter[i].Serial >> 32) & 0xFFFFFFFF)),
|
||||||
((uint32_t)(config.Inverter[i].Serial & 0xFFFFFFFF)));
|
((uint32_t)(config.Inverter[i].Serial & 0xFFFFFFFF)));
|
||||||
obj["serial"] = buffer;
|
obj["serial"] = buffer;
|
||||||
@ -95,8 +96,8 @@ void WebApiInverterClass::onInverterAdd(AsyncWebServerRequest* request)
|
|||||||
|
|
||||||
auto& retMsg = response->getRoot();
|
auto& retMsg = response->getRoot();
|
||||||
|
|
||||||
if (!(root.containsKey("serial")
|
if (!(root["serial"].is<String>()
|
||||||
&& root.containsKey("name"))) {
|
&& root["name"].is<String>())) {
|
||||||
retMsg["message"] = "Values are missing!";
|
retMsg["message"] = "Values are missing!";
|
||||||
retMsg["code"] = WebApiError::GenericValueMissing;
|
retMsg["code"] = WebApiError::GenericValueMissing;
|
||||||
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
@ -165,7 +166,10 @@ void WebApiInverterClass::onInverterEdit(AsyncWebServerRequest* request)
|
|||||||
|
|
||||||
auto& retMsg = response->getRoot();
|
auto& retMsg = response->getRoot();
|
||||||
|
|
||||||
if (!(root.containsKey("id") && root.containsKey("serial") && root.containsKey("name") && root.containsKey("channel"))) {
|
if (!(root["id"].is<uint8_t>()
|
||||||
|
&& root["serial"].is<String>()
|
||||||
|
&& root["name"].is<String>()
|
||||||
|
&& root["channel"].is<JsonArray>())) {
|
||||||
retMsg["message"] = "Values are missing!";
|
retMsg["message"] = "Values are missing!";
|
||||||
retMsg["code"] = WebApiError::GenericValueMissing;
|
retMsg["code"] = WebApiError::GenericValueMissing;
|
||||||
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
@ -281,7 +285,7 @@ void WebApiInverterClass::onInverterDelete(AsyncWebServerRequest* request)
|
|||||||
|
|
||||||
auto& retMsg = response->getRoot();
|
auto& retMsg = response->getRoot();
|
||||||
|
|
||||||
if (!(root.containsKey("id"))) {
|
if (!(root["id"].is<uint8_t>())) {
|
||||||
retMsg["message"] = "Values are missing!";
|
retMsg["message"] = "Values are missing!";
|
||||||
retMsg["code"] = WebApiError::GenericValueMissing;
|
retMsg["code"] = WebApiError::GenericValueMissing;
|
||||||
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
@ -323,7 +327,7 @@ void WebApiInverterClass::onInverterOrder(AsyncWebServerRequest* request)
|
|||||||
|
|
||||||
auto& retMsg = response->getRoot();
|
auto& retMsg = response->getRoot();
|
||||||
|
|
||||||
if (!(root.containsKey("order"))) {
|
if (!(root["order"].is<JsonArray>())) {
|
||||||
retMsg["message"] = "Values are missing!";
|
retMsg["message"] = "Values are missing!";
|
||||||
retMsg["code"] = WebApiError::GenericValueMissing;
|
retMsg["code"] = WebApiError::GenericValueMissing;
|
||||||
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
@ -346,3 +350,24 @@ void WebApiInverterClass::onInverterOrder(AsyncWebServerRequest* request)
|
|||||||
|
|
||||||
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void WebApiInverterClass::onInverterStatReset(AsyncWebServerRequest* request)
|
||||||
|
{
|
||||||
|
if (!WebApi.checkCredentials(request)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AsyncJsonResponse* response = new AsyncJsonResponse();
|
||||||
|
auto retMsg = response->getRoot();
|
||||||
|
auto serial = WebApi.parseSerialFromRequest(request);
|
||||||
|
auto inv = Hoymiles.getInverterBySerial(serial);
|
||||||
|
|
||||||
|
if (inv != nullptr) {
|
||||||
|
inv->resetRadioStats();
|
||||||
|
retMsg["type"] = "success";
|
||||||
|
retMsg["message"] = "Stats resetted";
|
||||||
|
retMsg["code"] = WebApiError::InverterStatsResetted;
|
||||||
|
}
|
||||||
|
|
||||||
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
|
}
|
||||||
|
|||||||
@ -64,9 +64,9 @@ void WebApiLimitClass::onLimitPost(AsyncWebServerRequest* request)
|
|||||||
|
|
||||||
auto& retMsg = response->getRoot();
|
auto& retMsg = response->getRoot();
|
||||||
|
|
||||||
if (!(root.containsKey("serial")
|
if (!(root["serial"].is<String>()
|
||||||
&& root.containsKey("limit_value")
|
&& root["limit_value"].is<float>()
|
||||||
&& root.containsKey("limit_type"))) {
|
&& root["limit_type"].is<uint16_t>())) {
|
||||||
retMsg["message"] = "Values are missing!";
|
retMsg["message"] = "Values are missing!";
|
||||||
retMsg["code"] = WebApiError::GenericValueMissing;
|
retMsg["code"] = WebApiError::GenericValueMissing;
|
||||||
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "WebApi_maintenance.h"
|
#include "WebApi_maintenance.h"
|
||||||
#include "Utils.h"
|
#include "RestartHelper.h"
|
||||||
#include "WebApi.h"
|
#include "WebApi.h"
|
||||||
#include "WebApi_errors.h"
|
#include "WebApi_errors.h"
|
||||||
#include <AsyncJson.h>
|
#include <AsyncJson.h>
|
||||||
@ -30,7 +30,7 @@ void WebApiMaintenanceClass::onRebootPost(AsyncWebServerRequest* request)
|
|||||||
|
|
||||||
auto& retMsg = response->getRoot();
|
auto& retMsg = response->getRoot();
|
||||||
|
|
||||||
if (!(root.containsKey("reboot"))) {
|
if (!(root["reboot"].is<bool>())) {
|
||||||
retMsg["message"] = "Values are missing!";
|
retMsg["message"] = "Values are missing!";
|
||||||
retMsg["code"] = WebApiError::GenericValueMissing;
|
retMsg["code"] = WebApiError::GenericValueMissing;
|
||||||
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
@ -43,7 +43,7 @@ void WebApiMaintenanceClass::onRebootPost(AsyncWebServerRequest* request)
|
|||||||
retMsg["code"] = WebApiError::MaintenanceRebootTriggered;
|
retMsg["code"] = WebApiError::MaintenanceRebootTriggered;
|
||||||
|
|
||||||
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
Utils::restartDtu();
|
RestartHelper.triggerRestart();
|
||||||
} else {
|
} else {
|
||||||
retMsg["message"] = "Reboot cancled!";
|
retMsg["message"] = "Reboot cancled!";
|
||||||
retMsg["code"] = WebApiError::MaintenanceRebootCancled;
|
retMsg["code"] = WebApiError::MaintenanceRebootCancled;
|
||||||
|
|||||||
@ -107,29 +107,29 @@ void WebApiMqttClass::onMqttAdminPost(AsyncWebServerRequest* request)
|
|||||||
|
|
||||||
auto& retMsg = response->getRoot();
|
auto& retMsg = response->getRoot();
|
||||||
|
|
||||||
if (!(root.containsKey("mqtt_enabled")
|
if (!(root["mqtt_enabled"].is<bool>()
|
||||||
&& root.containsKey("mqtt_hostname")
|
&& root["mqtt_hostname"].is<String>()
|
||||||
&& root.containsKey("mqtt_port")
|
&& root["mqtt_port"].is<uint>()
|
||||||
&& root.containsKey("mqtt_clientid")
|
&& root["mqtt_clientid"].is<String>()
|
||||||
&& root.containsKey("mqtt_username")
|
&& root["mqtt_username"].is<String>()
|
||||||
&& root.containsKey("mqtt_password")
|
&& root["mqtt_password"].is<String>()
|
||||||
&& root.containsKey("mqtt_topic")
|
&& root["mqtt_topic"].is<String>()
|
||||||
&& root.containsKey("mqtt_retain")
|
&& root["mqtt_retain"].is<bool>()
|
||||||
&& root.containsKey("mqtt_tls")
|
&& root["mqtt_tls"].is<bool>()
|
||||||
&& root.containsKey("mqtt_tls_cert_login")
|
&& root["mqtt_tls_cert_login"].is<bool>()
|
||||||
&& root.containsKey("mqtt_client_cert")
|
&& root["mqtt_client_cert"].is<String>()
|
||||||
&& root.containsKey("mqtt_client_key")
|
&& root["mqtt_client_key"].is<String>()
|
||||||
&& root.containsKey("mqtt_lwt_topic")
|
&& root["mqtt_lwt_topic"].is<String>()
|
||||||
&& root.containsKey("mqtt_lwt_online")
|
&& root["mqtt_lwt_online"].is<String>()
|
||||||
&& root.containsKey("mqtt_lwt_offline")
|
&& root["mqtt_lwt_offline"].is<String>()
|
||||||
&& root.containsKey("mqtt_lwt_qos")
|
&& root["mqtt_lwt_qos"].is<uint8_t>()
|
||||||
&& root.containsKey("mqtt_publish_interval")
|
&& root["mqtt_publish_interval"].is<uint32_t>()
|
||||||
&& root.containsKey("mqtt_clean_session")
|
&& root["mqtt_clean_session"].is<bool>()
|
||||||
&& root.containsKey("mqtt_hass_enabled")
|
&& root["mqtt_hass_enabled"].is<bool>()
|
||||||
&& root.containsKey("mqtt_hass_expire")
|
&& root["mqtt_hass_expire"].is<bool>()
|
||||||
&& root.containsKey("mqtt_hass_retain")
|
&& root["mqtt_hass_retain"].is<bool>()
|
||||||
&& root.containsKey("mqtt_hass_topic")
|
&& root["mqtt_hass_topic"].is<String>()
|
||||||
&& root.containsKey("mqtt_hass_individualpanels"))) {
|
&& root["mqtt_hass_individualpanels"].is<bool>())) {
|
||||||
retMsg["message"] = "Values are missing!";
|
retMsg["message"] = "Values are missing!";
|
||||||
retMsg["code"] = WebApiError::GenericValueMissing;
|
retMsg["code"] = WebApiError::GenericValueMissing;
|
||||||
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
|
|||||||
@ -88,16 +88,16 @@ void WebApiNetworkClass::onNetworkAdminPost(AsyncWebServerRequest* request)
|
|||||||
|
|
||||||
auto& retMsg = response->getRoot();
|
auto& retMsg = response->getRoot();
|
||||||
|
|
||||||
if (!(root.containsKey("ssid")
|
if (!(root["ssid"].is<String>()
|
||||||
&& root.containsKey("password")
|
&& root["password"].is<String>()
|
||||||
&& root.containsKey("hostname")
|
&& root["hostname"].is<String>()
|
||||||
&& root.containsKey("dhcp")
|
&& root["dhcp"].is<bool>()
|
||||||
&& root.containsKey("ipaddress")
|
&& root["ipaddress"].is<String>()
|
||||||
&& root.containsKey("netmask")
|
&& root["netmask"].is<String>()
|
||||||
&& root.containsKey("gateway")
|
&& root["gateway"].is<String>()
|
||||||
&& root.containsKey("dns1")
|
&& root["dns1"].is<String>()
|
||||||
&& root.containsKey("dns2")
|
&& root["dns2"].is<String>()
|
||||||
&& root.containsKey("aptimeout"))) {
|
&& root["aptimeout"].is<uint>())) {
|
||||||
retMsg["message"] = "Values are missing!";
|
retMsg["message"] = "Values are missing!";
|
||||||
retMsg["code"] = WebApiError::GenericValueMissing;
|
retMsg["code"] = WebApiError::GenericValueMissing;
|
||||||
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
|
|||||||
@ -100,11 +100,11 @@ void WebApiNtpClass::onNtpAdminPost(AsyncWebServerRequest* request)
|
|||||||
|
|
||||||
auto& retMsg = response->getRoot();
|
auto& retMsg = response->getRoot();
|
||||||
|
|
||||||
if (!(root.containsKey("ntp_server")
|
if (!(root["ntp_server"].is<String>()
|
||||||
&& root.containsKey("ntp_timezone")
|
&& root["ntp_timezone"].is<String>()
|
||||||
&& root.containsKey("longitude")
|
&& root["longitude"].is<double>()
|
||||||
&& root.containsKey("latitude")
|
&& root["latitude"].is<double>()
|
||||||
&& root.containsKey("sunsettype"))) {
|
&& root["sunsettype"].is<uint8_t>())) {
|
||||||
retMsg["message"] = "Values are missing!";
|
retMsg["message"] = "Values are missing!";
|
||||||
retMsg["code"] = WebApiError::GenericValueMissing;
|
retMsg["code"] = WebApiError::GenericValueMissing;
|
||||||
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
@ -193,12 +193,12 @@ void WebApiNtpClass::onNtpTimePost(AsyncWebServerRequest* request)
|
|||||||
|
|
||||||
auto& retMsg = response->getRoot();
|
auto& retMsg = response->getRoot();
|
||||||
|
|
||||||
if (!(root.containsKey("year")
|
if (!(root["year"].is<uint>()
|
||||||
&& root.containsKey("month")
|
&& root["month"].is<uint>()
|
||||||
&& root.containsKey("day")
|
&& root["day"].is<uint>()
|
||||||
&& root.containsKey("hour")
|
&& root["hour"].is<uint>()
|
||||||
&& root.containsKey("minute")
|
&& root["minute"].is<uint>()
|
||||||
&& root.containsKey("second"))) {
|
&& root["second"].is<uint>())) {
|
||||||
retMsg["message"] = "Values are missing!";
|
retMsg["message"] = "Values are missing!";
|
||||||
retMsg["code"] = WebApiError::GenericValueMissing;
|
retMsg["code"] = WebApiError::GenericValueMissing;
|
||||||
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
|
|||||||
@ -57,9 +57,9 @@ void WebApiPowerClass::onPowerPost(AsyncWebServerRequest* request)
|
|||||||
|
|
||||||
auto& retMsg = response->getRoot();
|
auto& retMsg = response->getRoot();
|
||||||
|
|
||||||
if (!(root.containsKey("serial")
|
if (!(root["serial"].is<String>()
|
||||||
&& (root.containsKey("power")
|
&& (root["power"].is<bool>()
|
||||||
|| root.containsKey("restart")))) {
|
|| root["restart"].is<bool>()))) {
|
||||||
retMsg["message"] = "Values are missing!";
|
retMsg["message"] = "Values are missing!";
|
||||||
retMsg["code"] = WebApiError::GenericValueMissing;
|
retMsg["code"] = WebApiError::GenericValueMissing;
|
||||||
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
@ -84,8 +84,8 @@ void WebApiPowerClass::onPowerPost(AsyncWebServerRequest* request)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (root.containsKey("power")) {
|
if (root["power"].is<bool>()) {
|
||||||
uint16_t power = root["power"].as<bool>();
|
bool power = root["power"].as<bool>();
|
||||||
inv->sendPowerControlRequest(power);
|
inv->sendPowerControlRequest(power);
|
||||||
} else {
|
} else {
|
||||||
if (root["restart"].as<bool>()) {
|
if (root["restart"].as<bool>()) {
|
||||||
|
|||||||
@ -42,23 +42,23 @@ void WebApiPrometheusClass::onPrometheusMetricsGet(AsyncWebServerRequest* reques
|
|||||||
|
|
||||||
stream->print("# HELP opendtu_heap_size System memory size\n");
|
stream->print("# HELP opendtu_heap_size System memory size\n");
|
||||||
stream->print("# TYPE opendtu_heap_size gauge\n");
|
stream->print("# TYPE opendtu_heap_size gauge\n");
|
||||||
stream->printf("opendtu_heap_size %zu\n", ESP.getHeapSize());
|
stream->printf("opendtu_heap_size %" PRId32 "\n", ESP.getHeapSize());
|
||||||
|
|
||||||
stream->print("# HELP opendtu_free_heap_size System free memory\n");
|
stream->print("# HELP opendtu_free_heap_size System free memory\n");
|
||||||
stream->print("# TYPE opendtu_free_heap_size gauge\n");
|
stream->print("# TYPE opendtu_free_heap_size gauge\n");
|
||||||
stream->printf("opendtu_free_heap_size %zu\n", ESP.getFreeHeap());
|
stream->printf("opendtu_free_heap_size %" PRId32 "\n", ESP.getFreeHeap());
|
||||||
|
|
||||||
stream->print("# HELP opendtu_biggest_heap_block Biggest free heap block\n");
|
stream->print("# HELP opendtu_biggest_heap_block Biggest free heap block\n");
|
||||||
stream->print("# TYPE opendtu_biggest_heap_block gauge\n");
|
stream->print("# TYPE opendtu_biggest_heap_block gauge\n");
|
||||||
stream->printf("opendtu_biggest_heap_block %zu\n", ESP.getMaxAllocHeap());
|
stream->printf("opendtu_biggest_heap_block %" PRId32 "\n", ESP.getMaxAllocHeap());
|
||||||
|
|
||||||
stream->print("# HELP opendtu_heap_min_free Minimum free memory since boot\n");
|
stream->print("# HELP opendtu_heap_min_free Minimum free memory since boot\n");
|
||||||
stream->print("# TYPE opendtu_heap_min_free gauge\n");
|
stream->print("# TYPE opendtu_heap_min_free gauge\n");
|
||||||
stream->printf("opendtu_heap_min_free %zu\n", ESP.getMinFreeHeap());
|
stream->printf("opendtu_heap_min_free %" PRId32 "\n", ESP.getMinFreeHeap());
|
||||||
|
|
||||||
stream->print("# HELP wifi_rssi WiFi RSSI\n");
|
stream->print("# HELP wifi_rssi WiFi RSSI\n");
|
||||||
stream->print("# TYPE wifi_rssi gauge\n");
|
stream->print("# TYPE wifi_rssi gauge\n");
|
||||||
stream->printf("wifi_rssi %d\n", WiFi.RSSI());
|
stream->printf("wifi_rssi %" PRId8 "\n", WiFi.RSSI());
|
||||||
|
|
||||||
stream->print("# HELP wifi_station WiFi Station info\n");
|
stream->print("# HELP wifi_station WiFi Station info\n");
|
||||||
stream->print("# TYPE wifi_station gauge\n");
|
stream->print("# TYPE wifi_station gauge\n");
|
||||||
@ -73,14 +73,14 @@ void WebApiPrometheusClass::onPrometheusMetricsGet(AsyncWebServerRequest* reques
|
|||||||
stream->print("# HELP opendtu_last_update last update from inverter in s\n");
|
stream->print("# HELP opendtu_last_update last update from inverter in s\n");
|
||||||
stream->print("# TYPE opendtu_last_update gauge\n");
|
stream->print("# TYPE opendtu_last_update gauge\n");
|
||||||
}
|
}
|
||||||
stream->printf("opendtu_last_update{serial=\"%s\",unit=\"%d\",name=\"%s\"} %d\n",
|
stream->printf("opendtu_last_update{serial=\"%s\",unit=\"%" PRId8 "\",name=\"%s\"} %" PRId32 "\n",
|
||||||
serial.c_str(), i, name, inv->Statistics()->getLastUpdate() / 1000);
|
serial.c_str(), i, name, inv->Statistics()->getLastUpdate() / 1000);
|
||||||
|
|
||||||
if (i == 0) {
|
if (i == 0) {
|
||||||
stream->print("# HELP opendtu_inverter_limit_relative current relative limit of the inverter\n");
|
stream->print("# HELP opendtu_inverter_limit_relative current relative limit of the inverter\n");
|
||||||
stream->print("# TYPE opendtu_inverter_limit_relative gauge\n");
|
stream->print("# TYPE opendtu_inverter_limit_relative gauge\n");
|
||||||
}
|
}
|
||||||
stream->printf("opendtu_inverter_limit_relative{serial=\"%s\",unit=\"%d\",name=\"%s\"} %f\n",
|
stream->printf("opendtu_inverter_limit_relative{serial=\"%s\",unit=\"%" PRId8 "\",name=\"%s\"} %f\n",
|
||||||
serial.c_str(), i, name, inv->SystemConfigPara()->getLimitPercent() / 100.0);
|
serial.c_str(), i, name, inv->SystemConfigPara()->getLimitPercent() / 100.0);
|
||||||
|
|
||||||
if (inv->DevInfo()->getMaxPower() > 0) {
|
if (inv->DevInfo()->getMaxPower() > 0) {
|
||||||
@ -88,7 +88,7 @@ void WebApiPrometheusClass::onPrometheusMetricsGet(AsyncWebServerRequest* reques
|
|||||||
stream->print("# HELP opendtu_inverter_limit_absolute current relative limit of the inverter\n");
|
stream->print("# HELP opendtu_inverter_limit_absolute current relative limit of the inverter\n");
|
||||||
stream->print("# TYPE opendtu_inverter_limit_absolute gauge\n");
|
stream->print("# TYPE opendtu_inverter_limit_absolute gauge\n");
|
||||||
}
|
}
|
||||||
stream->printf("opendtu_inverter_limit_absolute{serial=\"%s\",unit=\"%d\",name=\"%s\"} %f\n",
|
stream->printf("opendtu_inverter_limit_absolute{serial=\"%s\",unit=\"%" PRId8 "\",name=\"%s\"} %f\n",
|
||||||
serial.c_str(), i, name, inv->SystemConfigPara()->getLimitPercent() * inv->DevInfo()->getMaxPower() / 100.0);
|
serial.c_str(), i, name, inv->SystemConfigPara()->getLimitPercent() * inv->DevInfo()->getMaxPower() / 100.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -126,7 +126,7 @@ void WebApiPrometheusClass::addField(AsyncResponseStream* stream, const String&
|
|||||||
stream->printf("# HELP opendtu_%s in %s\n", chanName, inv->Statistics()->getChannelFieldUnit(type, channel, fieldId));
|
stream->printf("# HELP opendtu_%s in %s\n", chanName, inv->Statistics()->getChannelFieldUnit(type, channel, fieldId));
|
||||||
stream->printf("# TYPE opendtu_%s %s\n", chanName, metricName);
|
stream->printf("# TYPE opendtu_%s %s\n", chanName, metricName);
|
||||||
}
|
}
|
||||||
stream->printf("opendtu_%s{serial=\"%s\",unit=\"%d\",name=\"%s\",type=\"%s\",channel=\"%d\"} %s\n",
|
stream->printf("opendtu_%s{serial=\"%s\",unit=\"%" PRId8 "\",name=\"%s\",type=\"%s\",channel=\"%d\"} %s\n",
|
||||||
chanName,
|
chanName,
|
||||||
serial.c_str(),
|
serial.c_str(),
|
||||||
idx,
|
idx,
|
||||||
@ -150,7 +150,7 @@ void WebApiPrometheusClass::addPanelInfo(AsyncResponseStream* stream, const Stri
|
|||||||
stream->print("# HELP opendtu_PanelInfo panel information\n");
|
stream->print("# HELP opendtu_PanelInfo panel information\n");
|
||||||
stream->print("# TYPE opendtu_PanelInfo gauge\n");
|
stream->print("# TYPE opendtu_PanelInfo gauge\n");
|
||||||
}
|
}
|
||||||
stream->printf("opendtu_PanelInfo{serial=\"%s\",unit=\"%d\",name=\"%s\",channel=\"%d\",panelname=\"%s\"} 1\n",
|
stream->printf("opendtu_PanelInfo{serial=\"%s\",unit=\"%" PRId8 "\",name=\"%s\",channel=\"%d\",panelname=\"%s\"} 1\n",
|
||||||
serial.c_str(),
|
serial.c_str(),
|
||||||
idx,
|
idx,
|
||||||
inv->name(),
|
inv->name(),
|
||||||
@ -161,7 +161,7 @@ void WebApiPrometheusClass::addPanelInfo(AsyncResponseStream* stream, const Stri
|
|||||||
stream->print("# HELP opendtu_MaxPower panel maximum output power\n");
|
stream->print("# HELP opendtu_MaxPower panel maximum output power\n");
|
||||||
stream->print("# TYPE opendtu_MaxPower gauge\n");
|
stream->print("# TYPE opendtu_MaxPower gauge\n");
|
||||||
}
|
}
|
||||||
stream->printf("opendtu_MaxPower{serial=\"%s\",unit=\"%d\",name=\"%s\",channel=\"%d\"} %d\n",
|
stream->printf("opendtu_MaxPower{serial=\"%s\",unit=\"%" PRId8 "\",name=\"%s\",channel=\"%d\"} %d\n",
|
||||||
serial.c_str(),
|
serial.c_str(),
|
||||||
idx,
|
idx,
|
||||||
inv->name(),
|
inv->name(),
|
||||||
@ -172,7 +172,7 @@ void WebApiPrometheusClass::addPanelInfo(AsyncResponseStream* stream, const Stri
|
|||||||
stream->print("# HELP opendtu_YieldTotalOffset panel yield offset (for used inverters)\n");
|
stream->print("# HELP opendtu_YieldTotalOffset panel yield offset (for used inverters)\n");
|
||||||
stream->print("# TYPE opendtu_YieldTotalOffset gauge\n");
|
stream->print("# TYPE opendtu_YieldTotalOffset gauge\n");
|
||||||
}
|
}
|
||||||
stream->printf("opendtu_YieldTotalOffset{serial=\"%s\",unit=\"%d\",name=\"%s\",channel=\"%d\"} %f\n",
|
stream->printf("opendtu_YieldTotalOffset{serial=\"%s\",unit=\"%" PRId8 "\",name=\"%s\",channel=\"%" PRId16 "\"} %f\n",
|
||||||
serial.c_str(),
|
serial.c_str(),
|
||||||
idx,
|
idx,
|
||||||
inv->name(),
|
inv->name(),
|
||||||
|
|||||||
@ -48,8 +48,8 @@ void WebApiSecurityClass::onSecurityPost(AsyncWebServerRequest* request)
|
|||||||
|
|
||||||
auto& retMsg = response->getRoot();
|
auto& retMsg = response->getRoot();
|
||||||
|
|
||||||
if (!root.containsKey("password")
|
if (!root["password"].is<String>()
|
||||||
&& root.containsKey("allow_readonly")) {
|
&& root["allow_readonly"].is<bool>()) {
|
||||||
retMsg["message"] = "Values are missing!";
|
retMsg["message"] = "Values are missing!";
|
||||||
retMsg["code"] = WebApiError::GenericValueMissing;
|
retMsg["code"] = WebApiError::GenericValueMissing;
|
||||||
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
@ -71,6 +71,8 @@ void WebApiSecurityClass::onSecurityPost(AsyncWebServerRequest* request)
|
|||||||
WebApi.writeConfig(retMsg);
|
WebApi.writeConfig(retMsg);
|
||||||
|
|
||||||
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
|
||||||
|
|
||||||
|
WebApi.reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebApiSecurityClass::onAuthenticateGet(AsyncWebServerRequest* request)
|
void WebApiSecurityClass::onAuthenticateGet(AsyncWebServerRequest* request)
|
||||||
|
|||||||
@ -21,16 +21,30 @@ void WebApiWsConsoleClass::init(AsyncWebServer& server, Scheduler& scheduler)
|
|||||||
|
|
||||||
scheduler.addTask(_wsCleanupTask);
|
scheduler.addTask(_wsCleanupTask);
|
||||||
_wsCleanupTask.enable();
|
_wsCleanupTask.enable();
|
||||||
|
|
||||||
|
_simpleDigestAuth.setUsername(AUTH_USERNAME);
|
||||||
|
_simpleDigestAuth.setRealm("console websocket");
|
||||||
|
|
||||||
|
reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebApiWsConsoleClass::reload()
|
||||||
|
{
|
||||||
|
_ws.removeMiddleware(&_simpleDigestAuth);
|
||||||
|
|
||||||
|
auto const& config = Configuration.get();
|
||||||
|
|
||||||
|
if (config.Security.AllowReadonly) { return; }
|
||||||
|
|
||||||
|
_ws.enable(false);
|
||||||
|
_simpleDigestAuth.setPassword(config.Security.Password);
|
||||||
|
_ws.addMiddleware(&_simpleDigestAuth);
|
||||||
|
_ws.closeAll();
|
||||||
|
_ws.enable(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebApiWsConsoleClass::wsCleanupTaskCb()
|
void WebApiWsConsoleClass::wsCleanupTaskCb()
|
||||||
{
|
{
|
||||||
// see: https://github.com/me-no-dev/ESPAsyncWebServer#limiting-the-number-of-web-socket-clients
|
// see: https://github.com/me-no-dev/ESPAsyncWebServer#limiting-the-number-of-web-socket-clients
|
||||||
_ws.cleanupClients();
|
_ws.cleanupClients();
|
||||||
|
|
||||||
if (Configuration.get().Security.AllowReadonly) {
|
|
||||||
_ws.setAuthentication("", "");
|
|
||||||
} else {
|
|
||||||
_ws.setAuthentication(AUTH_USERNAME, Configuration.get().Security.Password);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -36,18 +36,31 @@ void WebApiWsLiveClass::init(AsyncWebServer& server, Scheduler& scheduler)
|
|||||||
|
|
||||||
scheduler.addTask(_sendDataTask);
|
scheduler.addTask(_sendDataTask);
|
||||||
_sendDataTask.enable();
|
_sendDataTask.enable();
|
||||||
|
_simpleDigestAuth.setUsername(AUTH_USERNAME);
|
||||||
|
_simpleDigestAuth.setRealm("live websocket");
|
||||||
|
|
||||||
|
reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebApiWsLiveClass::reload()
|
||||||
|
{
|
||||||
|
_ws.removeMiddleware(&_simpleDigestAuth);
|
||||||
|
|
||||||
|
auto const& config = Configuration.get();
|
||||||
|
|
||||||
|
if (config.Security.AllowReadonly) { return; }
|
||||||
|
|
||||||
|
_ws.enable(false);
|
||||||
|
_simpleDigestAuth.setPassword(config.Security.Password);
|
||||||
|
_ws.addMiddleware(&_simpleDigestAuth);
|
||||||
|
_ws.closeAll();
|
||||||
|
_ws.enable(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebApiWsLiveClass::wsCleanupTaskCb()
|
void WebApiWsLiveClass::wsCleanupTaskCb()
|
||||||
{
|
{
|
||||||
// see: https://github.com/me-no-dev/ESPAsyncWebServer#limiting-the-number-of-web-socket-clients
|
// see: https://github.com/me-no-dev/ESPAsyncWebServer#limiting-the-number-of-web-socket-clients
|
||||||
_ws.cleanupClients();
|
_ws.cleanupClients();
|
||||||
|
|
||||||
if (Configuration.get().Security.AllowReadonly) {
|
|
||||||
_ws.setAuthentication("", "");
|
|
||||||
} else {
|
|
||||||
_ws.setAuthentication(AUTH_USERNAME, Configuration.get().Security.Password);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebApiWsLiveClass::sendDataTaskCb()
|
void WebApiWsLiveClass::sendDataTaskCb()
|
||||||
@ -134,6 +147,13 @@ void WebApiWsLiveClass::generateInverterCommonJsonResponse(JsonObject& root, std
|
|||||||
} else {
|
} else {
|
||||||
root["limit_absolute"] = -1;
|
root["limit_absolute"] = -1;
|
||||||
}
|
}
|
||||||
|
root["radio_stats"]["tx_request"] = inv->RadioStats.TxRequestData;
|
||||||
|
root["radio_stats"]["tx_re_request"] = inv->RadioStats.TxReRequestFragment;
|
||||||
|
root["radio_stats"]["rx_success"] = inv->RadioStats.RxSuccess;
|
||||||
|
root["radio_stats"]["rx_fail_nothing"] = inv->RadioStats.RxFailNoAnswer;
|
||||||
|
root["radio_stats"]["rx_fail_partial"] = inv->RadioStats.RxFailPartialAnswer;
|
||||||
|
root["radio_stats"]["rx_fail_corrupt"] = inv->RadioStats.RxFailCorruptData;
|
||||||
|
root["radio_stats"]["rssi"] = inv->getLastRssi();
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebApiWsLiveClass::generateInverterChannelJsonResponse(JsonObject& root, std::shared_ptr<InverterAbstract> inv)
|
void WebApiWsLiveClass::generateInverterChannelJsonResponse(JsonObject& root, std::shared_ptr<InverterAbstract> inv)
|
||||||
|
|||||||
13
src/main.cpp
13
src/main.cpp
@ -16,6 +16,7 @@
|
|||||||
#include "NetworkSettings.h"
|
#include "NetworkSettings.h"
|
||||||
#include "NtpSettings.h"
|
#include "NtpSettings.h"
|
||||||
#include "PinMapping.h"
|
#include "PinMapping.h"
|
||||||
|
#include "RestartHelper.h"
|
||||||
#include "Scheduler.h"
|
#include "Scheduler.h"
|
||||||
#include "SunPosition.h"
|
#include "SunPosition.h"
|
||||||
#include "Utils.h"
|
#include "Utils.h"
|
||||||
@ -25,12 +26,21 @@
|
|||||||
#include <LittleFS.h>
|
#include <LittleFS.h>
|
||||||
#include <TaskScheduler.h>
|
#include <TaskScheduler.h>
|
||||||
#include <esp_heap_caps.h>
|
#include <esp_heap_caps.h>
|
||||||
|
#include <SpiManager.h>
|
||||||
|
|
||||||
|
#include <driver/uart.h>
|
||||||
|
|
||||||
void setup()
|
void setup()
|
||||||
{
|
{
|
||||||
// Move all dynamic allocations >512byte to psram (if available)
|
// Move all dynamic allocations >512byte to psram (if available)
|
||||||
heap_caps_malloc_extmem_enable(512);
|
heap_caps_malloc_extmem_enable(512);
|
||||||
|
|
||||||
|
// Initialize SpiManager
|
||||||
|
SpiManagerInst.register_bus(SPI2_HOST);
|
||||||
|
#if SOC_SPI_PERIPH_NUM > 2
|
||||||
|
SpiManagerInst.register_bus(SPI3_HOST);
|
||||||
|
#endif
|
||||||
|
|
||||||
// Initialize serial output
|
// Initialize serial output
|
||||||
Serial.begin(SERIAL_BAUDRATE);
|
Serial.begin(SERIAL_BAUDRATE);
|
||||||
#if ARDUINO_USB_CDC_ON_BOOT
|
#if ARDUINO_USB_CDC_ON_BOOT
|
||||||
@ -143,7 +153,7 @@ void setup()
|
|||||||
if (config.Dtu.Serial == DTU_SERIAL) {
|
if (config.Dtu.Serial == DTU_SERIAL) {
|
||||||
MessageOutput.print("generate serial based on ESP chip id: ");
|
MessageOutput.print("generate serial based on ESP chip id: ");
|
||||||
const uint64_t dtuId = Utils::generateDtuSerial();
|
const uint64_t dtuId = Utils::generateDtuSerial();
|
||||||
MessageOutput.printf("%0x%08x... ",
|
MessageOutput.printf("%0" PRIx32 "%08" PRIx32 "... ",
|
||||||
((uint32_t)((dtuId >> 32) & 0xFFFFFFFF)),
|
((uint32_t)((dtuId >> 32) & 0xFFFFFFFF)),
|
||||||
((uint32_t)(dtuId & 0xFFFFFFFF)));
|
((uint32_t)(dtuId & 0xFFFFFFFF)));
|
||||||
config.Dtu.Serial = dtuId;
|
config.Dtu.Serial = dtuId;
|
||||||
@ -154,6 +164,7 @@ void setup()
|
|||||||
InverterSettings.init(scheduler);
|
InverterSettings.init(scheduler);
|
||||||
|
|
||||||
Datastore.init(scheduler);
|
Datastore.init(scheduler);
|
||||||
|
RestartHelper.init(scheduler);
|
||||||
}
|
}
|
||||||
|
|
||||||
void loop()
|
void loop()
|
||||||
|
|||||||
@ -1,22 +1,12 @@
|
|||||||
/* eslint-env node */
|
/* eslint-env node */
|
||||||
import path from "node:path";
|
|
||||||
import { fileURLToPath } from "node:url";
|
|
||||||
|
|
||||||
import { FlatCompat } from "@eslint/eslintrc";
|
|
||||||
import js from "@eslint/js";
|
import js from "@eslint/js";
|
||||||
import pluginVue from 'eslint-plugin-vue'
|
import pluginVue from 'eslint-plugin-vue'
|
||||||
|
import vueTsEslintConfig from '@vue/eslint-config-typescript'
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
|
||||||
const __dirname = path.dirname(__filename);
|
|
||||||
const compat = new FlatCompat({
|
|
||||||
baseDirectory: __dirname,
|
|
||||||
recommendedConfig: js.configs.recommended,
|
|
||||||
});
|
|
||||||
|
|
||||||
export default [
|
export default [
|
||||||
js.configs.recommended,
|
js.configs.recommended,
|
||||||
...pluginVue.configs['flat/essential'],
|
...pluginVue.configs['flat/essential'],
|
||||||
...compat.extends("@vue/eslint-config-typescript/recommended"),
|
...vueTsEslintConfig(),
|
||||||
{
|
{
|
||||||
files: [
|
files: [
|
||||||
"**/*.vue",
|
"**/*.vue",
|
||||||
|
|||||||
@ -17,34 +17,35 @@
|
|||||||
"bootstrap": "^5.3.3",
|
"bootstrap": "^5.3.3",
|
||||||
"bootstrap-icons-vue": "^1.11.3",
|
"bootstrap-icons-vue": "^1.11.3",
|
||||||
"mitt": "^3.0.1",
|
"mitt": "^3.0.1",
|
||||||
"sortablejs": "^1.15.2",
|
"sortablejs": "^1.15.3",
|
||||||
"spark-md5": "^3.0.2",
|
"spark-md5": "^3.0.2",
|
||||||
"vue": "^3.4.35",
|
"vue": "^3.5.11",
|
||||||
"vue-i18n": "^9.13.1",
|
"vue-i18n": "9.13.1",
|
||||||
"vue-router": "^4.4.2"
|
"vue-router": "^4.4.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@intlify/unplugin-vue-i18n": "^4.0.0",
|
"@intlify/unplugin-vue-i18n": "^4.0.0",
|
||||||
"@tsconfig/node18": "^18.2.4",
|
"@tsconfig/node22": "^22.0.0",
|
||||||
"@types/bootstrap": "^5.2.10",
|
"@types/bootstrap": "^5.2.10",
|
||||||
"@types/node": "^22.1.0",
|
"@types/node": "^22.7.4",
|
||||||
"@types/pulltorefreshjs": "^0.1.7",
|
"@types/pulltorefreshjs": "^0.1.7",
|
||||||
"@types/sortablejs": "^1.15.8",
|
"@types/sortablejs": "^1.15.8",
|
||||||
"@types/spark-md5": "^3.0.4",
|
"@types/spark-md5": "^3.0.4",
|
||||||
"@vitejs/plugin-vue": "^5.1.2",
|
"@vitejs/plugin-vue": "^5.1.4",
|
||||||
"@vue/eslint-config-typescript": "^13.0.0",
|
"@vue/eslint-config-typescript": "^14.0.0",
|
||||||
"@vue/tsconfig": "^0.5.1",
|
"@vue/tsconfig": "^0.5.1",
|
||||||
"eslint": "^9.8.0",
|
"eslint": "^9.12.0",
|
||||||
"eslint-plugin-vue": "^9.27.0",
|
"eslint-plugin-vue": "^9.28.0",
|
||||||
"npm-run-all": "^4.1.5",
|
"npm-run-all": "^4.1.5",
|
||||||
"prettier": "^3.3.3",
|
"prettier": "^3.3.3",
|
||||||
"pulltorefreshjs": "^0.1.22",
|
"pulltorefreshjs": "^0.1.22",
|
||||||
"sass": "^1.77.6",
|
"sass": "^1.77.6",
|
||||||
"terser": "^5.31.3",
|
"terser": "^5.34.1",
|
||||||
"typescript": "^5.5.4",
|
"typescript": "^5.6.2",
|
||||||
"vite": "^5.3.5",
|
"vite": "^5.4.8",
|
||||||
"vite-plugin-compression": "^0.5.1",
|
"vite-plugin-compression": "^0.5.1",
|
||||||
"vite-plugin-css-injected-by-js": "^3.5.1",
|
"vite-plugin-css-injected-by-js": "^3.5.2",
|
||||||
"vue-tsc": "^2.0.29"
|
"vue-tsc": "^2.1.6"
|
||||||
}
|
},
|
||||||
|
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,10 +1,12 @@
|
|||||||
<template>
|
<template>
|
||||||
<table class="table table-hover">
|
<table class="table table-hover">
|
||||||
<thead>
|
<thead>
|
||||||
<th scope="col">{{ $t('eventlog.Start') }}</th>
|
<tr>
|
||||||
<th scope="col">{{ $t('eventlog.Stop') }}</th>
|
<th scope="col">{{ $t('eventlog.Start') }}</th>
|
||||||
<th scope="col">{{ $t('eventlog.Id') }}</th>
|
<th scope="col">{{ $t('eventlog.Stop') }}</th>
|
||||||
<th scope="col">{{ $t('eventlog.Message') }}</th>
|
<th scope="col">{{ $t('eventlog.Id') }}</th>
|
||||||
|
<th scope="col">{{ $t('eventlog.Message') }}</th>
|
||||||
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<template v-for="event in eventLogList.count" :key="event">
|
<template v-for="event in eventLogList.count" :key="event">
|
||||||
|
|||||||
@ -216,7 +216,9 @@ export default defineComponent({
|
|||||||
this.$router.push('/');
|
this.$router.push('/');
|
||||||
},
|
},
|
||||||
onClick() {
|
onClick() {
|
||||||
this.$refs.navbarCollapse && (this.$refs.navbarCollapse as HTMLElement).classList.remove('show');
|
if (this.$refs.navbarCollapse) {
|
||||||
|
(this.$refs.navbarCollapse as HTMLElement).classList.remove('show');
|
||||||
|
}
|
||||||
},
|
},
|
||||||
getEasterSunday(year: number): Date {
|
getEasterSunday(year: number): Date {
|
||||||
const f = Math.floor;
|
const f = Math.floor;
|
||||||
|
|||||||
@ -141,7 +141,19 @@
|
|||||||
"Unknown": "Unbekannt",
|
"Unknown": "Unbekannt",
|
||||||
"ShowGridProfile": "Zeige Grid Profil",
|
"ShowGridProfile": "Zeige Grid Profil",
|
||||||
"GridProfile": "Grid Profil",
|
"GridProfile": "Grid Profil",
|
||||||
"LoadingInverter": "Warte auf Daten... (kann bis zu 10 Sekunden dauern)"
|
"LoadingInverter": "Warte auf Daten... (kann bis zu 10 Sekunden dauern)",
|
||||||
|
"RadioStats": "Funkstatistik",
|
||||||
|
"TxRequest": "Gesendete Anfragen",
|
||||||
|
"RxSuccess": "Empfang Erfolgreich",
|
||||||
|
"RxFailNothing": "Empfang Fehler: Nichts empfangen",
|
||||||
|
"RxFailPartial": "Empfang Fehler: Teilweise empfangen",
|
||||||
|
"RxFailCorrupt": "Empfang Fehler: Beschädigt empfangen",
|
||||||
|
"TxReRequest": "Gesendete Fragment Wiederanforderungen",
|
||||||
|
"StatsReset": "Statistiken zurücksetzen",
|
||||||
|
"StatsResetting": "Zurücksetzen...",
|
||||||
|
"Rssi": "RSSI des zuletzt empfangenen Paketes",
|
||||||
|
"RssiHint": "HM-Wechselrichter unterstützen nur RSSI-Werte < -64 dBm und > -64 dBm. In diesem Fall wird -80 dBm und -30 dBm angezeigt.",
|
||||||
|
"dBm": "{dbm} dBm"
|
||||||
},
|
},
|
||||||
"eventlog": {
|
"eventlog": {
|
||||||
"Start": "Beginn",
|
"Start": "Beginn",
|
||||||
|
|||||||
@ -141,7 +141,19 @@
|
|||||||
"Unknown": "Unknown",
|
"Unknown": "Unknown",
|
||||||
"ShowGridProfile": "Show Grid Profile",
|
"ShowGridProfile": "Show Grid Profile",
|
||||||
"GridProfile": "Grid Profile",
|
"GridProfile": "Grid Profile",
|
||||||
"LoadingInverter": "Waiting for data... (can take up to 10 seconds)"
|
"LoadingInverter": "Waiting for data... (can take up to 10 seconds)",
|
||||||
|
"RadioStats": "Radio Statistics",
|
||||||
|
"TxRequest": "TX Request Count",
|
||||||
|
"RxSuccess": "RX Success",
|
||||||
|
"RxFailNothing": "RX Fail: Receive Nothing",
|
||||||
|
"RxFailPartial": "RX Fail: Receive Partial",
|
||||||
|
"RxFailCorrupt": "RX Fail: Receive Corrupt",
|
||||||
|
"TxReRequest": "TX Re-Request Fragment",
|
||||||
|
"StatsReset": "Reset Statistics",
|
||||||
|
"StatsResetting": "Resetting...",
|
||||||
|
"Rssi": "RSSI of last received packet",
|
||||||
|
"RssiHint": "HM inverters only support RSSI values < -64 dBm and > -64 dBm. In this case, -80 dbm and -30 dbm is shown.",
|
||||||
|
"dBm": "{dbm} dBm"
|
||||||
},
|
},
|
||||||
"eventlog": {
|
"eventlog": {
|
||||||
"Start": "Start",
|
"Start": "Start",
|
||||||
|
|||||||
@ -141,7 +141,19 @@
|
|||||||
"Unknown": "Inconnu",
|
"Unknown": "Inconnu",
|
||||||
"ShowGridProfile": "Show Grid Profile",
|
"ShowGridProfile": "Show Grid Profile",
|
||||||
"GridProfile": "Grid Profile",
|
"GridProfile": "Grid Profile",
|
||||||
"LoadingInverter": "Waiting for data... (can take up to 10 seconds)"
|
"LoadingInverter": "Waiting for data... (can take up to 10 seconds)",
|
||||||
|
"RadioStats": "Radio Statistics",
|
||||||
|
"TxRequest": "TX Request Count",
|
||||||
|
"RxSuccess": "RX Success",
|
||||||
|
"RxFailNothing": "RX Fail: Receive Nothing",
|
||||||
|
"RxFailPartial": "RX Fail: Receive Partial",
|
||||||
|
"RxFailCorrupt": "RX Fail: Receive Corrupt",
|
||||||
|
"TxReRequest": "TX Re-Request Fragment",
|
||||||
|
"StatsReset": "Reset Statistics",
|
||||||
|
"StatsResetting": "Resetting...",
|
||||||
|
"Rssi": "RSSI of last received packet",
|
||||||
|
"RssiHint": "HM inverters only support RSSI values < -64 dBm and > -64 dBm. In this case, -80 dbm and -30 dbm is shown.",
|
||||||
|
"dBm": "{dbm} dBm"
|
||||||
},
|
},
|
||||||
"eventlog": {
|
"eventlog": {
|
||||||
"Start": "Départ",
|
"Start": "Départ",
|
||||||
|
|||||||
@ -21,6 +21,16 @@ export interface InverterStatistics {
|
|||||||
Irradiation?: ValueObject;
|
Irradiation?: ValueObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface RadioStatistics {
|
||||||
|
tx_request: number;
|
||||||
|
tx_re_request: number;
|
||||||
|
rx_success: number;
|
||||||
|
rx_fail_nothing: number;
|
||||||
|
rx_fail_partial: number;
|
||||||
|
rx_fail_corrupt: number;
|
||||||
|
rssi: number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface Inverter {
|
export interface Inverter {
|
||||||
serial: string;
|
serial: string;
|
||||||
name: string;
|
name: string;
|
||||||
@ -35,6 +45,7 @@ export interface Inverter {
|
|||||||
AC: InverterStatistics[];
|
AC: InverterStatistics[];
|
||||||
DC: InverterStatistics[];
|
DC: InverterStatistics[];
|
||||||
INV: InverterStatistics[];
|
INV: InverterStatistics[];
|
||||||
|
radio_stats: RadioStatistics;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Total {
|
export interface Total {
|
||||||
|
|||||||
@ -48,4 +48,4 @@ export interface Device {
|
|||||||
display: Display;
|
display: Display;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PinMapping extends Array<Device> {}
|
export type PinMapping = Array<Device>;
|
||||||
|
|||||||
@ -84,7 +84,7 @@ export default defineComponent({
|
|||||||
this.socket.onmessage = (event) => {
|
this.socket.onmessage = (event) => {
|
||||||
console.log(event);
|
console.log(event);
|
||||||
|
|
||||||
let outstr = new String(event.data);
|
let outstr = String(event.data);
|
||||||
let removedNewline = false;
|
let removedNewline = false;
|
||||||
if (outstr.endsWith('\n')) {
|
if (outstr.endsWith('\n')) {
|
||||||
outstr = outstr.substring(0, outstr.length - 1);
|
outstr = outstr.substring(0, outstr.length - 1);
|
||||||
@ -108,7 +108,9 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
// Send heartbeat packets regularly * 59s Send a heartbeat
|
// Send heartbeat packets regularly * 59s Send a heartbeat
|
||||||
heartCheck() {
|
heartCheck() {
|
||||||
this.heartInterval && clearTimeout(this.heartInterval);
|
if (this.heartInterval) {
|
||||||
|
clearTimeout(this.heartInterval);
|
||||||
|
}
|
||||||
this.heartInterval = setInterval(() => {
|
this.heartInterval = setInterval(() => {
|
||||||
if (this.socket.readyState === 1) {
|
if (this.socket.readyState === 1) {
|
||||||
// Connection status
|
// Connection status
|
||||||
@ -126,7 +128,9 @@ export default defineComponent({
|
|||||||
// continue regardless of error
|
// continue regardless of error
|
||||||
}
|
}
|
||||||
|
|
||||||
this.heartInterval && clearTimeout(this.heartInterval);
|
if (this.heartInterval) {
|
||||||
|
clearTimeout(this.heartInterval);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
getOutDate(): string {
|
getOutDate(): string {
|
||||||
const u = new Date();
|
const u = new Date();
|
||||||
|
|||||||
@ -202,7 +202,7 @@
|
|||||||
min="0"
|
min="0"
|
||||||
max="100"
|
max="100"
|
||||||
id="inputDisplayContrast"
|
id="inputDisplayContrast"
|
||||||
v-model="deviceConfigList.display.contrast"
|
v-model.number="deviceConfigList.display.contrast"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -239,7 +239,7 @@
|
|||||||
min="0"
|
min="0"
|
||||||
max="100"
|
max="100"
|
||||||
:id="getLedIdFromNumber(index)"
|
:id="getLedIdFromNumber(index)"
|
||||||
v-model="ledSetting.brightness"
|
v-model.number="ledSetting.brightness"
|
||||||
@change="syncSliders"
|
@change="syncSliders"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -385,7 +385,7 @@ export default defineComponent({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const srcId = this.getNumberFromLedId((event.target as Element).id);
|
const srcId = this.getNumberFromLedId((event.target as Element).id);
|
||||||
this.deviceConfigList.led.every((v) => (v.brightness = this.deviceConfigList.led[srcId].brightness));
|
this.deviceConfigList.led.map((v) => (v.brightness = this.deviceConfigList.led[srcId].brightness));
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -48,7 +48,7 @@
|
|||||||
<input
|
<input
|
||||||
type="range"
|
type="range"
|
||||||
class="form-control form-range"
|
class="form-control form-range"
|
||||||
v-model="dtuConfigList.cmt_palevel"
|
v-model.number="dtuConfigList.cmt_palevel"
|
||||||
min="-10"
|
min="-10"
|
||||||
max="20"
|
max="20"
|
||||||
id="inputCmtPaLevel"
|
id="inputCmtPaLevel"
|
||||||
@ -89,7 +89,7 @@
|
|||||||
<input
|
<input
|
||||||
type="range"
|
type="range"
|
||||||
class="form-control form-range"
|
class="form-control form-range"
|
||||||
v-model="dtuConfigList.cmt_frequency"
|
v-model.number="dtuConfigList.cmt_frequency"
|
||||||
:min="cmtMinFrequency"
|
:min="cmtMinFrequency"
|
||||||
:max="cmtMaxFrequency"
|
:max="cmtMaxFrequency"
|
||||||
:step="dtuConfigList.cmt_chan_width"
|
:step="dtuConfigList.cmt_chan_width"
|
||||||
|
|||||||
@ -201,6 +201,7 @@
|
|||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<BootstrapAlert class="m-3" :show="!inverter.hasOwnProperty('INV')">
|
<BootstrapAlert class="m-3" :show="!inverter.hasOwnProperty('INV')">
|
||||||
<div class="d-flex justify-content-center align-items-center">
|
<div class="d-flex justify-content-center align-items-center">
|
||||||
<div class="spinner-border m-1" role="status">
|
<div class="spinner-border m-1" role="status">
|
||||||
@ -209,6 +210,120 @@
|
|||||||
<span>{{ $t('home.LoadingInverter') }}</span>
|
<span>{{ $t('home.LoadingInverter') }}</span>
|
||||||
</div>
|
</div>
|
||||||
</BootstrapAlert>
|
</BootstrapAlert>
|
||||||
|
|
||||||
|
<div class="accordion mt-5" id="accordionExample">
|
||||||
|
<div class="accordion-item">
|
||||||
|
<h2 class="accordion-header">
|
||||||
|
<button
|
||||||
|
class="accordion-button collapsed"
|
||||||
|
type="button"
|
||||||
|
data-bs-toggle="collapse"
|
||||||
|
data-bs-target="#collapseStats"
|
||||||
|
aria-expanded="true"
|
||||||
|
aria-controls="collapseStats"
|
||||||
|
>
|
||||||
|
<BIconBroadcast /> {{ $t('home.RadioStats') }}
|
||||||
|
</button>
|
||||||
|
</h2>
|
||||||
|
<div
|
||||||
|
id="collapseStats"
|
||||||
|
class="accordion-collapse collapse"
|
||||||
|
data-bs-parent="#accordionExample"
|
||||||
|
>
|
||||||
|
<div class="accordion-body">
|
||||||
|
<table class="table table-striped table-hover">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>{{ $t('home.TxRequest') }}</td>
|
||||||
|
<td>{{ $n(inverter.radio_stats.tx_request) }}</td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{{ $t('home.RxSuccess') }}</td>
|
||||||
|
<td>{{ $n(inverter.radio_stats.rx_success) }}</td>
|
||||||
|
<td>
|
||||||
|
{{
|
||||||
|
ratio(
|
||||||
|
inverter.radio_stats.rx_success,
|
||||||
|
inverter.radio_stats.tx_request
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{{ $t('home.RxFailNothing') }}</td>
|
||||||
|
<td>{{ $n(inverter.radio_stats.rx_fail_nothing) }}</td>
|
||||||
|
<td>
|
||||||
|
{{
|
||||||
|
ratio(
|
||||||
|
inverter.radio_stats.rx_fail_nothing,
|
||||||
|
inverter.radio_stats.tx_request
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{{ $t('home.RxFailPartial') }}</td>
|
||||||
|
<td>{{ $n(inverter.radio_stats.rx_fail_partial) }}</td>
|
||||||
|
<td>
|
||||||
|
{{
|
||||||
|
ratio(
|
||||||
|
inverter.radio_stats.rx_fail_partial,
|
||||||
|
inverter.radio_stats.tx_request
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{{ $t('home.RxFailCorrupt') }}</td>
|
||||||
|
<td>{{ $n(inverter.radio_stats.rx_fail_corrupt) }}</td>
|
||||||
|
<td>
|
||||||
|
{{
|
||||||
|
ratio(
|
||||||
|
inverter.radio_stats.rx_fail_corrupt,
|
||||||
|
inverter.radio_stats.tx_request
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{{ $t('home.TxReRequest') }}</td>
|
||||||
|
<td>{{ $n(inverter.radio_stats.tx_re_request) }}</td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
{{ $t('home.Rssi') }}
|
||||||
|
<BIconInfoCircle v-tooltip :title="$t('home.RssiHint')" />
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ $t('home.dBm', { dbm: $n(inverter.radio_stats.rssi) }) }}
|
||||||
|
</td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<button
|
||||||
|
:disabled="!isLogged || performRadioStatsReset"
|
||||||
|
type="button"
|
||||||
|
class="btn btn-danger"
|
||||||
|
@click="onResetRadioStats(inverter.serial)"
|
||||||
|
>
|
||||||
|
<template v-if="!performRadioStatsReset">
|
||||||
|
{{ $t('home.StatsReset') }}
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<span
|
||||||
|
class="spinner-border spinner-border-sm"
|
||||||
|
aria-hidden="true"
|
||||||
|
></span>
|
||||||
|
<span role="status"> {{ $t('home.StatsResetting') }}</span>
|
||||||
|
</template>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -390,9 +505,11 @@ import { authHeader, authUrl, handleResponse, isLoggedIn } from '@/utils/authent
|
|||||||
import * as bootstrap from 'bootstrap';
|
import * as bootstrap from 'bootstrap';
|
||||||
import {
|
import {
|
||||||
BIconArrowCounterclockwise,
|
BIconArrowCounterclockwise,
|
||||||
|
BIconBroadcast,
|
||||||
BIconCheckCircleFill,
|
BIconCheckCircleFill,
|
||||||
BIconCpu,
|
BIconCpu,
|
||||||
BIconExclamationCircleFill,
|
BIconExclamationCircleFill,
|
||||||
|
BIconInfoCircle,
|
||||||
BIconJournalText,
|
BIconJournalText,
|
||||||
BIconOutlet,
|
BIconOutlet,
|
||||||
BIconPower,
|
BIconPower,
|
||||||
@ -415,9 +532,11 @@ export default defineComponent({
|
|||||||
InverterTotalInfo,
|
InverterTotalInfo,
|
||||||
ModalDialog,
|
ModalDialog,
|
||||||
BIconArrowCounterclockwise,
|
BIconArrowCounterclockwise,
|
||||||
|
BIconBroadcast,
|
||||||
BIconCheckCircleFill,
|
BIconCheckCircleFill,
|
||||||
BIconCpu,
|
BIconCpu,
|
||||||
BIconExclamationCircleFill,
|
BIconExclamationCircleFill,
|
||||||
|
BIconInfoCircle,
|
||||||
BIconJournalText,
|
BIconJournalText,
|
||||||
BIconOutlet,
|
BIconOutlet,
|
||||||
BIconPower,
|
BIconPower,
|
||||||
@ -461,6 +580,7 @@ export default defineComponent({
|
|||||||
alertMessageLimit: '',
|
alertMessageLimit: '',
|
||||||
alertTypeLimit: 'info',
|
alertTypeLimit: 'info',
|
||||||
showAlertLimit: false,
|
showAlertLimit: false,
|
||||||
|
performRadioStatsReset: false,
|
||||||
|
|
||||||
powerSettingView: {} as bootstrap.Modal,
|
powerSettingView: {} as bootstrap.Modal,
|
||||||
powerSettingSerial: '',
|
powerSettingSerial: '',
|
||||||
@ -614,7 +734,9 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
// Send heartbeat packets regularly * 59s Send a heartbeat
|
// Send heartbeat packets regularly * 59s Send a heartbeat
|
||||||
heartCheck(duration: number = 59) {
|
heartCheck(duration: number = 59) {
|
||||||
this.heartInterval && clearTimeout(this.heartInterval);
|
if (this.heartInterval) {
|
||||||
|
clearTimeout(this.heartInterval);
|
||||||
|
}
|
||||||
this.heartInterval = setInterval(() => {
|
this.heartInterval = setInterval(() => {
|
||||||
if (this.socket.readyState === 1) {
|
if (this.socket.readyState === 1) {
|
||||||
// Connection status
|
// Connection status
|
||||||
@ -627,7 +749,9 @@ export default defineComponent({
|
|||||||
/** To break off websocket Connect */
|
/** To break off websocket Connect */
|
||||||
closeSocket() {
|
closeSocket() {
|
||||||
this.socket.close();
|
this.socket.close();
|
||||||
this.heartInterval && clearTimeout(this.heartInterval);
|
if (this.heartInterval) {
|
||||||
|
clearTimeout(this.heartInterval);
|
||||||
|
}
|
||||||
this.isFirstFetchAfterConnect = true;
|
this.isFirstFetchAfterConnect = true;
|
||||||
},
|
},
|
||||||
onShowEventlog(serial: string) {
|
onShowEventlog(serial: string) {
|
||||||
@ -690,6 +814,14 @@ export default defineComponent({
|
|||||||
|
|
||||||
this.limitSettingView.show();
|
this.limitSettingView.show();
|
||||||
},
|
},
|
||||||
|
onResetRadioStats(serial: string) {
|
||||||
|
this.performRadioStatsReset = true;
|
||||||
|
fetch('/api/inverter/stats_reset?inv=' + serial, { headers: authHeader() })
|
||||||
|
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
||||||
|
.then(() => {
|
||||||
|
this.performRadioStatsReset = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
onSetLimitSettings(setPersistent: boolean) {
|
onSetLimitSettings(setPersistent: boolean) {
|
||||||
this.targetLimitList.limit_type = (setPersistent ? 256 : 0) + this.targetLimitType;
|
this.targetLimitList.limit_type = (setPersistent ? 256 : 0) + this.targetLimitType;
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
@ -786,6 +918,12 @@ export default defineComponent({
|
|||||||
});
|
});
|
||||||
return total;
|
return total;
|
||||||
},
|
},
|
||||||
|
ratio(val_small: number, val_large: number): string {
|
||||||
|
if (val_large == 0) {
|
||||||
|
return '-';
|
||||||
|
}
|
||||||
|
return this.$n(val_small / val_large, 'percent');
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -52,9 +52,7 @@ export default defineComponent({
|
|||||||
.then((data) => {
|
.then((data) => {
|
||||||
this.systemDataList = data;
|
this.systemDataList = data;
|
||||||
this.dataLoading = false;
|
this.dataLoading = false;
|
||||||
if (this.allowVersionInfo) {
|
this.getUpdateInfo();
|
||||||
this.getUpdateInfo();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
getUpdateInfo() {
|
getUpdateInfo() {
|
||||||
@ -76,6 +74,10 @@ export default defineComponent({
|
|||||||
this.systemDataList.git_is_hash = true;
|
this.systemDataList.git_is_hash = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!this.allowVersionInfo) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const fetchUrl =
|
const fetchUrl =
|
||||||
'https://api.github.com/repos/tbnobody/OpenDTU/compare/' + this.systemDataList.git_hash + '...HEAD';
|
'https://api.github.com/repos/tbnobody/OpenDTU/compare/' + this.systemDataList.git_hash + '...HEAD';
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"extends": [
|
"extends": [
|
||||||
"@tsconfig/node18/tsconfig.json",
|
"@tsconfig/node22/tsconfig.json",
|
||||||
"@vue/tsconfig/tsconfig.json"
|
"@vue/tsconfig/tsconfig.json"
|
||||||
],
|
],
|
||||||
"include": [
|
"include": [
|
||||||
|
|||||||
@ -6,7 +6,7 @@
|
|||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["./src/*"]
|
"@/*": ["./src/*"]
|
||||||
},
|
},
|
||||||
"lib": ["ES2021", "DOM"],
|
"lib": ["ES2023", "DOM"],
|
||||||
"moduleResolution": "Node",
|
"moduleResolution": "Node",
|
||||||
|
|
||||||
/* Linting */
|
/* Linting */
|
||||||
|
|||||||
@ -14,7 +14,7 @@ let proxy_target;
|
|||||||
try {
|
try {
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
proxy_target = require('./vite.user.ts').proxy_target;
|
proxy_target = require('./vite.user.ts').proxy_target;
|
||||||
} catch (error) {
|
} catch {
|
||||||
proxy_target = '192.168.20.110';
|
proxy_target = '192.168.20.110';
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -45,6 +45,7 @@ export default defineConfig({
|
|||||||
outDir: '../webapp_dist',
|
outDir: '../webapp_dist',
|
||||||
emptyOutDir: true,
|
emptyOutDir: true,
|
||||||
minify: 'terser',
|
minify: 'terser',
|
||||||
|
chunkSizeWarningLimit: 1024,
|
||||||
rollupOptions: {
|
rollupOptions: {
|
||||||
output: {
|
output: {
|
||||||
// Only create one js file
|
// Only create one js file
|
||||||
|
|||||||
934
webapp/yarn.lock
934
webapp/yarn.lock
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Loading…
Reference in New Issue
Block a user