mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2026-05-08 23:15:52 +00:00
Compare commits
62 Commits
a813d38108
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ad5d13168b | ||
|
|
ae5beccb9d | ||
|
|
764c660b14 | ||
|
|
ca0f32b087 | ||
|
|
8ad91de54a | ||
|
|
58da157272 | ||
|
|
b5014bf9ac | ||
|
|
14351436a7 | ||
|
|
469d412951 | ||
|
|
6edbac86e2 | ||
|
|
db2be70d66 | ||
|
|
c36f231990 | ||
|
|
26102121e1 | ||
|
|
8e64c6303e | ||
|
|
74c76eb90b | ||
|
|
daffdcf58e | ||
|
|
4bc4fa903f | ||
|
|
29380f0303 | ||
|
|
6b2370b79d | ||
|
|
dbc636c9bf | ||
|
|
0c0660c04b | ||
|
|
c9fd076394 | ||
|
|
35550553be | ||
|
|
06ff219385 | ||
|
|
e705a5629f | ||
|
|
cb3c9653ce | ||
|
|
0b5a83f6ae | ||
|
|
a079169005 | ||
|
|
845c51d5f9 | ||
|
|
d6d3a034ad | ||
|
|
ece08d96ee | ||
|
|
854f4d559a | ||
|
|
f186f2a8f2 | ||
|
|
6b68cb7c61 | ||
|
|
a1e0288e09 | ||
|
|
e6c173bdf9 | ||
|
|
d9b6de0652 | ||
|
|
c54da18822 | ||
|
|
555801dc5c | ||
|
|
6f81945da6 | ||
|
|
865c309475 | ||
|
|
77b8b21aea | ||
|
|
2f5edffec6 | ||
|
|
71de64502e | ||
|
|
6994d3559a | ||
|
|
a7d484d218 | ||
|
|
a810c41acd | ||
|
|
2fbfdf94ab | ||
|
|
2d7c8f0863 | ||
|
|
c3b734ab47 | ||
|
|
644abf105d | ||
|
|
5a8a451774 | ||
|
|
dae139aa01 | ||
|
|
b13fcd8939 | ||
|
|
26b42b4eea | ||
|
|
c9005e8aa9 | ||
|
|
6658b11adf | ||
|
|
e542f5809f | ||
|
|
ce1dd6233d | ||
|
|
fe488443da | ||
|
|
b264a39780 | ||
|
|
d2302eaa85 |
8
.github/workflows/dev_release.yml
vendored
8
.github/workflows/dev_release.yml
vendored
@@ -28,7 +28,7 @@ jobs:
|
|||||||
node-version: 24
|
node-version: 24
|
||||||
|
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Enable Corepack
|
- name: Enable Corepack
|
||||||
run: corepack enable pnpm
|
run: corepack enable pnpm
|
||||||
@@ -47,7 +47,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Build webUI
|
- name: Build webUI
|
||||||
run: |
|
run: |
|
||||||
platformio run -e build_webUI
|
platformio run -e build-webUI
|
||||||
|
|
||||||
- name: Build modbus
|
- name: Build modbus
|
||||||
run: |
|
run: |
|
||||||
@@ -62,13 +62,13 @@ jobs:
|
|||||||
platformio run
|
platformio run
|
||||||
|
|
||||||
- name: Commit the generated files
|
- name: Commit the generated files
|
||||||
uses: stefanzweifel/git-auto-commit-action@v5
|
uses: stefanzweifel/git-auto-commit-action@v7
|
||||||
with:
|
with:
|
||||||
commit_message: "chore: update generated files for v${{steps.build_info.outputs.VERSION}}"
|
commit_message: "chore: update generated files for v${{steps.build_info.outputs.VERSION}}"
|
||||||
|
|
||||||
- name: Create GitHub Release
|
- name: Create GitHub Release
|
||||||
id: 'automatic_releases'
|
id: 'automatic_releases'
|
||||||
uses: emsesp/action-automatic-releases@v1.0.0
|
uses: emsesp/action-automatic-releases@v1.0.1
|
||||||
with:
|
with:
|
||||||
repo_token: '${{ secrets.GITHUB_TOKEN }}'
|
repo_token: '${{ secrets.GITHUB_TOKEN }}'
|
||||||
title: Development Build v${{steps.build_info.outputs.VERSION}}
|
title: Development Build v${{steps.build_info.outputs.VERSION}}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ jobs:
|
|||||||
contents: read
|
contents: read
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: GitHub Releases To Discord
|
- name: GitHub Releases To Discord
|
||||||
uses: SethCohen/github-releases-to-discord@v1.13.1
|
uses: SethCohen/github-releases-to-discord@v1.13.1
|
||||||
|
|||||||
2
.github/workflows/pr_check.yml
vendored
2
.github/workflows/pr_check.yml
vendored
@@ -15,7 +15,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Install python 3.13
|
- name: Install python 3.13
|
||||||
uses: actions/setup-python@v6
|
uses: actions/setup-python@v6
|
||||||
|
|||||||
2
.github/workflows/sonar_check.yml
vendored
2
.github/workflows/sonar_check.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
|||||||
BUILD_WRAPPER_OUT_DIR: bw-output
|
BUILD_WRAPPER_OUT_DIR: bw-output
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Install Build Wrapper
|
- name: Install Build Wrapper
|
||||||
|
|||||||
6
.github/workflows/stable_release.yml
vendored
6
.github/workflows/stable_release.yml
vendored
@@ -26,7 +26,7 @@ jobs:
|
|||||||
node-version: 24
|
node-version: 24
|
||||||
|
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Enable Corepack
|
- name: Enable Corepack
|
||||||
run: corepack enable pnpm
|
run: corepack enable pnpm
|
||||||
@@ -39,7 +39,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Build webUI
|
- name: Build webUI
|
||||||
run: |
|
run: |
|
||||||
platformio run -e build_webUI
|
platformio run -e build-webUI
|
||||||
|
|
||||||
- name: Build modbus
|
- name: Build modbus
|
||||||
run: |
|
run: |
|
||||||
@@ -54,7 +54,7 @@ jobs:
|
|||||||
platformio run
|
platformio run
|
||||||
|
|
||||||
- name: Create GitHub Release
|
- name: Create GitHub Release
|
||||||
uses: emsesp/action-automatic-releases@v1.0.0
|
uses: emsesp/action-automatic-releases@v1.0.1
|
||||||
with:
|
with:
|
||||||
repo_token: '${{ secrets.GITHUB_TOKEN }}'
|
repo_token: '${{ secrets.GITHUB_TOKEN }}'
|
||||||
prerelease: false
|
prerelease: false
|
||||||
|
|||||||
6
.github/workflows/test_release.yml
vendored
6
.github/workflows/test_release.yml
vendored
@@ -28,7 +28,7 @@ jobs:
|
|||||||
node-version: 24
|
node-version: 24
|
||||||
|
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Enable Corepack
|
- name: Enable Corepack
|
||||||
run: corepack enable pnpm
|
run: corepack enable pnpm
|
||||||
@@ -47,7 +47,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Build webUI
|
- name: Build webUI
|
||||||
run: |
|
run: |
|
||||||
platformio run -e build_webUI
|
platformio run -e build-webUI
|
||||||
|
|
||||||
- name: Build modbus
|
- name: Build modbus
|
||||||
run: |
|
run: |
|
||||||
@@ -63,7 +63,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Create GitHub Release
|
- name: Create GitHub Release
|
||||||
id: 'automatic_releases'
|
id: 'automatic_releases'
|
||||||
uses: emsesp/action-automatic-releases@v1.0.0
|
uses: emsesp/action-automatic-releases@v1.0.1
|
||||||
with:
|
with:
|
||||||
repo_token: '${{ secrets.GITHUB_TOKEN }}'
|
repo_token: '${{ secrets.GITHUB_TOKEN }}'
|
||||||
title: Test Build v${{steps.build_info.outputs.VERSION}}
|
title: Test Build v${{steps.build_info.outputs.VERSION}}
|
||||||
|
|||||||
@@ -12,13 +12,15 @@ For more details go to [emsesp.org](https://emsesp.org/).
|
|||||||
- prometheus metrics for temperature/analog/scheduler/custom [#2962](https://github.com/emsesp/EMS-ESP32/issues/2962)
|
- prometheus metrics for temperature/analog/scheduler/custom [#2962](https://github.com/emsesp/EMS-ESP32/issues/2962)
|
||||||
- boiler pumpkick [#2965](https://github.com/emsesp/EMS-ESP32/discussions/2965)
|
- boiler pumpkick [#2965](https://github.com/emsesp/EMS-ESP32/discussions/2965)
|
||||||
- heatpump reset [#2933](https://github.com/emsesp/EMS-ESP32/issues/2933)
|
- heatpump reset [#2933](https://github.com/emsesp/EMS-ESP32/issues/2933)
|
||||||
- e-mail notification using ReadyMail Client
|
|
||||||
- 2.nd freshwater module (dhw4, dhw5) [#2991](https://github.com/emsesp/EMS-ESP32/issues/2991)
|
- 2.nd freshwater module (dhw4, dhw5) [#2991](https://github.com/emsesp/EMS-ESP32/issues/2991)
|
||||||
|
- full system backup and restore
|
||||||
|
|
||||||
## Fixed
|
## Fixed
|
||||||
|
|
||||||
- SRC climate creation [#2936](https://github.com/emsesp/EMS-ESP32/issues/2936) and [#2960](https://github.com/emsesp/EMS-ESP32/issues/2960)
|
- SRC climate creation [#2936](https://github.com/emsesp/EMS-ESP32/issues/2936) and [#2960](https://github.com/emsesp/EMS-ESP32/issues/2960)
|
||||||
- missing translations [#3015](https://github.com/emsesp/EMS-ESP32/issues/3015)
|
- missing translations [#3015](https://github.com/emsesp/EMS-ESP32/issues/3015)
|
||||||
|
- custom entities check fetch length
|
||||||
|
- modbus initialization [#3064](https://github.com/emsesp/EMS-ESP32/issues/3064)
|
||||||
|
|
||||||
## Changed
|
## Changed
|
||||||
|
|
||||||
@@ -30,7 +32,7 @@ For more details go to [emsesp.org](https://emsesp.org/).
|
|||||||
- support `minflowtemp` and `baseflowtemp` [#2969](https://github.com/emsesp/EMS-ESP32/discussions/2969)
|
- support `minflowtemp` and `baseflowtemp` [#2969](https://github.com/emsesp/EMS-ESP32/discussions/2969)
|
||||||
- update version if it is 00.00 in first read [#2981](https://github.com/emsesp/EMS-ESP32/issues/2981)
|
- update version if it is 00.00 in first read [#2981](https://github.com/emsesp/EMS-ESP32/issues/2981)
|
||||||
- device class for % values [#2980](https://github.com/emsesp/EMS-ESP32/issues/2980)
|
- device class for % values [#2980](https://github.com/emsesp/EMS-ESP32/issues/2980)
|
||||||
- use tasmota core 2026.03.30
|
|
||||||
- secure mqtt uses ESP_SSLClient
|
|
||||||
- fetch telegrams: set length to fetch [#3017](https://github.com/emsesp/EMS-ESP32/issues/3017)
|
- fetch telegrams: set length to fetch [#3017](https://github.com/emsesp/EMS-ESP32/issues/3017)
|
||||||
- move http client from stack to heap
|
- move http client from stack to heap
|
||||||
|
- heap optimizations [#3021](https://github.com/emsesp/EMS-ESP32/discussions/3021)
|
||||||
|
- check and read 0x470 as summer2_typeids[0] only if received [#2686](https://github.com/emsesp/EMS-ESP32/issues/2686), [#3055](https://github.com/emsesp/EMS-ESP32/issues/3055)
|
||||||
|
|||||||
4
Makefile
4
Makefile
@@ -47,8 +47,8 @@ MAKEFLAGS += -j$(JOBS) -l$(shell echo $$(($(JOBS) * 2)))
|
|||||||
#----------------------------------------------------------------------
|
#----------------------------------------------------------------------
|
||||||
TARGET := emsesp
|
TARGET := emsesp
|
||||||
BUILD := build
|
BUILD := build
|
||||||
SOURCES := src/core src/devices src/web src/test lib_standalone lib/semver lib/espMqttClient/src lib/espMqttClient/src/* lib/ArduinoJson/src lib/uuid-common/src lib/uuid-console/src lib/uuid-log/src lib/PButton
|
SOURCES := src/core src/devices src/web src/test lib_standalone lib/espMqttClient/src lib/espMqttClient/src/* lib/ArduinoJson/src lib/uuid-common/src lib/uuid-console/src lib/uuid-log/src lib/PButton
|
||||||
INCLUDES := src/core src/devices src/web src/test lib_standalone lib/* lib/semver lib/espMqttClient/src lib/espMqttClient/src/Transport lib/ArduinoJson/src lib/uuid-common/src lib/uuid-console/src lib/uuid-log/src lib/uuid-telnet/src lib/uuid-syslog/src
|
INCLUDES := src/core src/devices src/web src/test lib_standalone lib/* lib/espMqttClient/src lib/espMqttClient/src/Transport lib/ArduinoJson/src lib/uuid-common/src lib/uuid-console/src lib/uuid-log/src lib/uuid-telnet/src lib/uuid-syslog/src
|
||||||
LIBRARIES :=
|
LIBRARIES :=
|
||||||
|
|
||||||
CPPCHECK = cppcheck
|
CPPCHECK = cppcheck
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
},
|
},
|
||||||
"core": "esp32",
|
"core": "esp32",
|
||||||
"extra_flags": [
|
"extra_flags": [
|
||||||
"-DNO_TLS_SUPPORT",
|
"-DTASMOTA_SDK",
|
||||||
"-DARDUINO_LOLIN_C3_MINI",
|
"-DARDUINO_LOLIN_C3_MINI",
|
||||||
"-DARDUINO_USB_MODE=1",
|
"-DARDUINO_USB_MODE=1",
|
||||||
"-DARDUINO_USB_CDC_ON_BOOT=1"
|
"-DARDUINO_USB_CDC_ON_BOOT=1"
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
"core": "esp32",
|
"core": "esp32",
|
||||||
"extra_flags": [
|
"extra_flags": [
|
||||||
"-DBOARD_HAS_PSRAM",
|
"-DBOARD_HAS_PSRAM",
|
||||||
"-DNO_TLS_SUPPORT",
|
"-DTASMOTA_SDK",
|
||||||
"-DARDUINO_USB_CDC_ON_BOOT=1",
|
"-DARDUINO_USB_CDC_ON_BOOT=1",
|
||||||
"-DARDUINO_USB_MODE=0"
|
"-DARDUINO_USB_MODE=0"
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
"arduino",
|
"arduino",
|
||||||
"espidf"
|
"espidf"
|
||||||
],
|
],
|
||||||
"name": "Tasmota ESP32-S3 32M Flash OPI PSRAM, 4608KB Code/OTA, 2MB FS",
|
"name": "Espressif ESP32-S3 32M Flash OPI PSRAM, 4608KB Code/OTA, 2MB FS",
|
||||||
"upload": {
|
"upload": {
|
||||||
"flash_size": "32MB",
|
"flash_size": "32MB",
|
||||||
"maximum_ram_size": 327680,
|
"maximum_ram_size": 327680,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"build": {
|
"build": {
|
||||||
"core": "esp32",
|
"core": "esp32",
|
||||||
"extra_flags": "-DNO_TLS_SUPPORT",
|
"extra_flags": "-DTASMOTA_SDK",
|
||||||
"f_cpu": "240000000L",
|
"f_cpu": "240000000L",
|
||||||
"f_flash": "40000000L",
|
"f_flash": "40000000L",
|
||||||
"flash_mode": "dio",
|
"flash_mode": "dio",
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
"arduino",
|
"arduino",
|
||||||
"espidf"
|
"espidf"
|
||||||
],
|
],
|
||||||
"name": "Tasmota ESP32 16M Flash, 4608KB Code/OTA, 2MB FS",
|
"name": "Espressif ESP32 16M Flash, 4608KB Code/OTA, 2MB FS",
|
||||||
"upload": {
|
"upload": {
|
||||||
"flash_size": "16MB",
|
"flash_size": "16MB",
|
||||||
"maximum_ram_size": 327680,
|
"maximum_ram_size": 327680,
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
"arduino",
|
"arduino",
|
||||||
"espidf"
|
"espidf"
|
||||||
],
|
],
|
||||||
"name": "Tasmota ESP32 16M Flash DIO PSRAM, 4608KB Code/OTA, 2MB FS",
|
"name": "Espressif ESP32 16M Flash DIO PSRAM, 4608KB Code/OTA, 2MB FS",
|
||||||
"upload": {
|
"upload": {
|
||||||
"flash_size": "16MB",
|
"flash_size": "16MB",
|
||||||
"maximum_ram_size": 327680,
|
"maximum_ram_size": 327680,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"build": {
|
"build": {
|
||||||
"core": "esp32",
|
"core": "esp32",
|
||||||
"extra_flags": "-DNO_TLS_SUPPORT",
|
"extra_flags": "-DTASMOTA_SDK",
|
||||||
"f_cpu": "240000000L",
|
"f_cpu": "240000000L",
|
||||||
"f_flash": "40000000L",
|
"f_flash": "40000000L",
|
||||||
"flash_mode": "dio",
|
"flash_mode": "dio",
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
"build": {
|
"build": {
|
||||||
"core": "esp32",
|
"core": "esp32",
|
||||||
"extra_flags": [
|
"extra_flags": [
|
||||||
"-DNO_TLS_SUPPORT",
|
|
||||||
"-DARDUINO_XIAO_ESP32C6",
|
"-DARDUINO_XIAO_ESP32C6",
|
||||||
"-DARDUINO_USB_MODE=1",
|
"-DARDUINO_USB_MODE=1",
|
||||||
"-DARDUINO_USB_CDC_ON_BOOT=1"
|
"-DARDUINO_USB_CDC_ON_BOOT=1"
|
||||||
|
|||||||
@@ -38,6 +38,7 @@
|
|||||||
"vite.config.ts",
|
"vite.config.ts",
|
||||||
"lib/esp32-psram/**",
|
"lib/esp32-psram/**",
|
||||||
"test/test_api/test_api.h",
|
"test/test_api/test_api.h",
|
||||||
"lib_standalone/**"
|
"lib_standalone/**",
|
||||||
|
"**/*.js"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -112,10 +112,10 @@ telegram_type_id,name,is_fetched
|
|||||||
0x02A0,RC300Curves,
|
0x02A0,RC300Curves,
|
||||||
0x02A1,RC300Curves,
|
0x02A1,RC300Curves,
|
||||||
0x02A2,RC300Curves,
|
0x02A2,RC300Curves,
|
||||||
0x02A5,RC300Monitor,fetched
|
0x02A5,RC300Monitor,
|
||||||
0x02A6,CRFMonitor,
|
0x02A6,RC300Monitor,
|
||||||
0x02A7,RC300Monitor,
|
0x02A7,RC300Monitor,
|
||||||
0x02A8,CRFMonitor,
|
0x02A8,RC300Monitor,
|
||||||
0x02A9,RC300Monitor,
|
0x02A9,RC300Monitor,
|
||||||
0x02AA,RC300Monitor,
|
0x02AA,RC300Monitor,
|
||||||
0x02AB,RC300Monitor,
|
0x02AB,RC300Monitor,
|
||||||
@@ -171,6 +171,7 @@ telegram_type_id,name,is_fetched
|
|||||||
0x0468,HPSet,
|
0x0468,HPSet,
|
||||||
0x0469,HPSet,
|
0x0469,HPSet,
|
||||||
0x046A,HPSet,
|
0x046A,HPSet,
|
||||||
|
0x0470,RC300Summer2,fetched
|
||||||
0x0471,RC300Summer2,
|
0x0471,RC300Summer2,
|
||||||
0x0472,RC300Summer2,
|
0x0472,RC300Summer2,
|
||||||
0x0473,RC300Summer2,
|
0x0473,RC300Summer2,
|
||||||
|
|||||||
|
@@ -17,7 +17,7 @@
|
|||||||
"preview-standalone": "typesafe-i18n --no-watch && vite build && concurrently -c \"auto\" \"pnpm:mock-rest\" \"vite preview\"",
|
"preview-standalone": "typesafe-i18n --no-watch && vite build && concurrently -c \"auto\" \"pnpm:mock-rest\" \"vite preview\"",
|
||||||
"standalone": "concurrently -c \"auto\" \"typesafe-i18n\" \"pnpm:mock-rest\" \"vite dev\"",
|
"standalone": "concurrently -c \"auto\" \"typesafe-i18n\" \"pnpm:mock-rest\" \"vite dev\"",
|
||||||
"typesafe-i18n": "typesafe-i18n --no-watch",
|
"typesafe-i18n": "typesafe-i18n --no-watch",
|
||||||
"build_webUI": "typesafe-i18n --no-watch && vite build && node progmem-generator.js",
|
"build-webUI": "typesafe-i18n --no-watch && vite build && node progmem-generator.js",
|
||||||
"format": "prettier -l -w '**/*.{ts,tsx,js,css,json,md}'",
|
"format": "prettier -l -w '**/*.{ts,tsx,js,css,json,md}'",
|
||||||
"lint": "eslint . --fix",
|
"lint": "eslint . --fix",
|
||||||
"standalone-devcontainer": "concurrently -c \"auto\" \"typesafe-i18n\" \"pnpm:mock-rest\" \"vite --host\""
|
"standalone-devcontainer": "concurrently -c \"auto\" \"typesafe-i18n\" \"pnpm:mock-rest\" \"vite --host\""
|
||||||
@@ -26,11 +26,11 @@
|
|||||||
"@alova/adapter-xhr": "2.3.1",
|
"@alova/adapter-xhr": "2.3.1",
|
||||||
"@emotion/react": "^11.14.0",
|
"@emotion/react": "^11.14.0",
|
||||||
"@emotion/styled": "^11.14.1",
|
"@emotion/styled": "^11.14.1",
|
||||||
"@mui/icons-material": "^9.0.0",
|
"@mui/icons-material": "^9.0.1",
|
||||||
"@mui/material": "^9.0.0",
|
"@mui/material": "^9.0.1",
|
||||||
"@preact/compat": "^18.3.2",
|
"@preact/compat": "^18.3.2",
|
||||||
"@table-library/react-table-library": "4.1.15",
|
"@table-library/react-table-library": "4.1.15",
|
||||||
"alova": "^3.5.1",
|
"alova": "3.5.1",
|
||||||
"async-validator": "^4.2.5",
|
"async-validator": "^4.2.5",
|
||||||
"etag": "^1.8.1",
|
"etag": "^1.8.1",
|
||||||
"formidable": "^3.5.4",
|
"formidable": "^3.5.4",
|
||||||
@@ -38,11 +38,11 @@
|
|||||||
"magic-string": "^0.30.21",
|
"magic-string": "^0.30.21",
|
||||||
"mime-types": "^3.0.2",
|
"mime-types": "^3.0.2",
|
||||||
"preact": "^10.29.1",
|
"preact": "^10.29.1",
|
||||||
"react": "^19.2.5",
|
"react": "^19.2.6",
|
||||||
"react-dom": "^19.2.5",
|
"react-dom": "^19.2.6",
|
||||||
"react-icons": "^5.6.0",
|
"react-icons": "^5.6.0",
|
||||||
"react-router": "^7.14.1",
|
"react-router": "^7.15.0",
|
||||||
"react-toastify": "^11.0.5",
|
"react-toastify": "^11.1.0",
|
||||||
"typesafe-i18n": "^5.27.1",
|
"typesafe-i18n": "^5.27.1",
|
||||||
"typescript": "^6.0.3"
|
"typescript": "^6.0.3"
|
||||||
},
|
},
|
||||||
@@ -55,16 +55,17 @@
|
|||||||
"@types/node": "^25.6.0",
|
"@types/node": "^25.6.0",
|
||||||
"@types/react": "^19.2.14",
|
"@types/react": "^19.2.14",
|
||||||
"@types/react-dom": "^19.2.3",
|
"@types/react-dom": "^19.2.3",
|
||||||
"axe-core": "^4.11.3",
|
"axe-core": "^4.11.4",
|
||||||
"concurrently": "^9.2.1",
|
"concurrently": "^9.2.1",
|
||||||
"eslint": "^10.2.0",
|
"eslint": "^10.3.0",
|
||||||
"eslint-config-prettier": "^10.1.8",
|
"eslint-config-prettier": "^10.1.8",
|
||||||
"prettier": "^3.8.3",
|
"prettier": "^3.8.3",
|
||||||
"rollup-plugin-visualizer": "^7.0.1",
|
"rollup-plugin-visualizer": "^7.0.1",
|
||||||
"terser": "^5.46.1",
|
"terser": "^5.47.0",
|
||||||
"typescript-eslint": "^8.58.2",
|
"typescript-eslint": "^8.59.2",
|
||||||
"vite": "^8.0.8",
|
"vite": "^8.0.11",
|
||||||
"vite-plugin-imagemin": "^0.6.1"
|
"vite-plugin-imagemin": "^0.6.1",
|
||||||
|
"vite-tsconfig-paths": "^6.1.1"
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@10.33.0+sha512.10568bb4a6afb58c9eb3630da90cc9516417abebd3fabbe6739f0ae795728da1491e9db5a544c76ad8eb7570f5c4bb3d6c637b2cb41bfdcdb47fa823c8649319"
|
"packageManager": "pnpm@10.33.4"
|
||||||
}
|
}
|
||||||
|
|||||||
824
interface/pnpm-lock.yaml
generated
824
interface/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -24,20 +24,26 @@ let bundleStats = {
|
|||||||
other: { count: 0, uncompressed: 0, compressed: 0 }
|
other: { count: 0, uncompressed: 0, compressed: 0 }
|
||||||
};
|
};
|
||||||
|
|
||||||
const generateWWWClass =
|
// AsyncWebHandler that performs the lookup.
|
||||||
() => `typedef std::function<void(const char * uri, const String & contentType, const uint8_t * content, size_t len, const String & hash)> RouteRegistrationHandler;
|
const generateWWWClass = () => `// Bundle Statistics:
|
||||||
// Bundle Statistics:
|
|
||||||
// - Total compressed size: ${(totalSize / 1000).toFixed(1)} KB
|
// - Total compressed size: ${(totalSize / 1000).toFixed(1)} KB
|
||||||
// - Total uncompressed size: ${(Object.values(bundleStats).reduce((sum, stat) => sum + stat.uncompressed, 0) / 1000).toFixed(1)} KB
|
// - Total uncompressed size: ${(Object.values(bundleStats).reduce((sum, stat) => sum + stat.uncompressed, 0) / 1000).toFixed(1)} KB
|
||||||
// - Compression ratio: ${(((Object.values(bundleStats).reduce((sum, stat) => sum + stat.uncompressed, 0) - totalSize) / Object.values(bundleStats).reduce((sum, stat) => sum + stat.uncompressed, 0)) * 100).toFixed(1)}%
|
// - Compression ratio: ${(((Object.values(bundleStats).reduce((sum, stat) => sum + stat.uncompressed, 0) - totalSize) / Object.values(bundleStats).reduce((sum, stat) => sum + stat.uncompressed, 0)) * 100).toFixed(1)}%
|
||||||
// - Generated on: ${new Date().toISOString()}
|
// - Generated on: ${new Date().toISOString()}
|
||||||
|
|
||||||
class WWWData {
|
struct WWWAsset {
|
||||||
${INDENT}public:
|
${INDENT}const char * uri;
|
||||||
${INDENT.repeat(2)}static void registerRoutes(RouteRegistrationHandler handler) {
|
${INDENT}const char * contentType;
|
||||||
${fileInfo.map((f) => `${INDENT.repeat(3)}handler("${f.uri}", "${f.mimeType}", ${f.variable}, ${f.size}, ${f.hash});`).join('\n')}
|
${INDENT}const uint8_t * content;
|
||||||
${INDENT.repeat(2)}}
|
${INDENT}size_t len;
|
||||||
|
${INDENT}const char * etag; // already includes enclosing double quotes
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static const WWWAsset WWW_ASSETS[] = {
|
||||||
|
${fileInfo.map((f) => `${INDENT}{"${f.uri}", "${f.mimeType}", ${f.variable}, ${f.size}, "\\"${f.rawHash}\\""},`).join('\n')}
|
||||||
|
};
|
||||||
|
|
||||||
|
static constexpr size_t WWW_ASSETS_COUNT = sizeof(WWW_ASSETS) / sizeof(WWW_ASSETS[0]);
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const getFilesSync = (dir, files = []) => {
|
const getFilesSync = (dir, files = []) => {
|
||||||
@@ -72,6 +78,7 @@ const writeFile = (relativeFilePath, buffer) => {
|
|||||||
const zipBuffer = zlib.gzipSync(buffer, { level: 9 });
|
const zipBuffer = zlib.gzipSync(buffer, { level: 9 });
|
||||||
// const hash = crypto.createHash('sha256').update(zipBuffer).digest('hex');
|
// const hash = crypto.createHash('sha256').update(zipBuffer).digest('hex');
|
||||||
const hash = etag(zipBuffer); // use smaller md5 instead of sha256
|
const hash = etag(zipBuffer); // use smaller md5 instead of sha256
|
||||||
|
const rawHash = hash.replace(/^"|"$/g, '');
|
||||||
|
|
||||||
zipBuffer.forEach((b) => {
|
zipBuffer.forEach((b) => {
|
||||||
if (!(size % bytesPerLine)) {
|
if (!(size % bytesPerLine)) {
|
||||||
@@ -94,7 +101,8 @@ const writeFile = (relativeFilePath, buffer) => {
|
|||||||
mimeType,
|
mimeType,
|
||||||
variable,
|
variable,
|
||||||
size,
|
size,
|
||||||
hash
|
hash,
|
||||||
|
rawHash
|
||||||
});
|
});
|
||||||
|
|
||||||
totalSize += size;
|
totalSize += size;
|
||||||
|
|||||||
@@ -1,19 +1,13 @@
|
|||||||
import { type FC, Suspense, lazy, memo, useContext, useEffect, useRef } from 'react';
|
import { type FC, memo, useContext, useEffect, useRef } from 'react';
|
||||||
import { Navigate, Route, Routes } from 'react-router';
|
import { Navigate, Route, Routes } from 'react-router';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
import {
|
import AuthenticatedRouting from 'AuthenticatedRouting';
|
||||||
LoadingSpinner,
|
import SignIn from 'SignIn';
|
||||||
RequireAuthenticated,
|
import { RequireAuthenticated, RequireUnauthenticated } from 'components';
|
||||||
RequireUnauthenticated
|
|
||||||
} from 'components';
|
|
||||||
import { Authentication, AuthenticationContext } from 'contexts/authentication';
|
import { Authentication, AuthenticationContext } from 'contexts/authentication';
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
|
|
||||||
// Lazy load route components for better code splitting
|
|
||||||
const SignIn = lazy(() => import('SignIn'));
|
|
||||||
const AuthenticatedRouting = lazy(() => import('AuthenticatedRouting'));
|
|
||||||
|
|
||||||
interface SecurityRedirectProps {
|
interface SecurityRedirectProps {
|
||||||
readonly message: string;
|
readonly message: string;
|
||||||
readonly signOut?: boolean;
|
readonly signOut?: boolean;
|
||||||
@@ -45,34 +39,32 @@ const AppRouting: FC = memo(() => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Authentication>
|
<Authentication>
|
||||||
<Suspense fallback={<LoadingSpinner />}>
|
<Routes>
|
||||||
<Routes>
|
<Route
|
||||||
<Route
|
path="/unauthorized"
|
||||||
path="/unauthorized"
|
element={<RootRedirect message={LL.PLEASE_SIGNIN()} signOut />}
|
||||||
element={<RootRedirect message={LL.PLEASE_SIGNIN()} signOut />}
|
/>
|
||||||
/>
|
<Route
|
||||||
<Route
|
path="/fileUpdated"
|
||||||
path="/fileUpdated"
|
element={<RootRedirect message={LL.UPLOAD_SUCCESSFUL()} />}
|
||||||
element={<RootRedirect message={LL.UPLOAD_SUCCESSFUL()} />}
|
/>
|
||||||
/>
|
<Route
|
||||||
<Route
|
path="/"
|
||||||
path="/"
|
element={
|
||||||
element={
|
<RequireUnauthenticated>
|
||||||
<RequireUnauthenticated>
|
<SignIn />
|
||||||
<SignIn />
|
</RequireUnauthenticated>
|
||||||
</RequireUnauthenticated>
|
}
|
||||||
}
|
/>
|
||||||
/>
|
<Route
|
||||||
<Route
|
path="/*"
|
||||||
path="/*"
|
element={
|
||||||
element={
|
<RequireAuthenticated>
|
||||||
<RequireAuthenticated>
|
<AuthenticatedRouting />
|
||||||
<AuthenticatedRouting />
|
</RequireAuthenticated>
|
||||||
</RequireAuthenticated>
|
}
|
||||||
}
|
/>
|
||||||
/>
|
</Routes>
|
||||||
</Routes>
|
|
||||||
</Suspense>
|
|
||||||
</Authentication>
|
</Authentication>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,86 +1,77 @@
|
|||||||
import { Suspense, lazy, memo, useContext } from 'react';
|
import { memo, useContext } from 'react';
|
||||||
import { Navigate, Route, Routes } from 'react-router';
|
import { Navigate, Route, Routes } from 'react-router';
|
||||||
|
|
||||||
import { Layout, LoadingSpinner } from 'components';
|
import CustomEntities from 'app/main/CustomEntities';
|
||||||
|
import Customizations from 'app/main/Customizations';
|
||||||
|
import Dashboard from 'app/main/Dashboard';
|
||||||
|
import Devices from 'app/main/Devices';
|
||||||
|
import Help from 'app/main/Help';
|
||||||
|
import Modules from 'app/main/Modules';
|
||||||
|
import Scheduler from 'app/main/Scheduler';
|
||||||
|
import Sensors from 'app/main/Sensors';
|
||||||
|
import UserProfile from 'app/main/UserProfile';
|
||||||
|
import APSettings from 'app/settings/APSettings';
|
||||||
|
import ApplicationSettings from 'app/settings/ApplicationSettings';
|
||||||
|
import DownloadUpload from 'app/settings/DownloadUpload';
|
||||||
|
import MqttSettings from 'app/settings/MqttSettings';
|
||||||
|
import NTPSettings from 'app/settings/NTPSettings';
|
||||||
|
import Settings from 'app/settings/Settings';
|
||||||
|
import Network from 'app/settings/network/Network';
|
||||||
|
import Security from 'app/settings/security/Security';
|
||||||
|
import APStatus from 'app/status/APStatus';
|
||||||
|
import Activity from 'app/status/Activity';
|
||||||
|
import HardwareStatus from 'app/status/HardwareStatus';
|
||||||
|
import MqttStatus from 'app/status/MqttStatus';
|
||||||
|
import NTPStatus from 'app/status/NTPStatus';
|
||||||
|
import NetworkStatus from 'app/status/NetworkStatus';
|
||||||
|
import Status from 'app/status/Status';
|
||||||
|
import SystemLog from 'app/status/SystemLog';
|
||||||
|
import Version from 'app/status/Version';
|
||||||
|
import { Layout } from 'components';
|
||||||
import { AuthenticatedContext } from 'contexts/authentication';
|
import { AuthenticatedContext } from 'contexts/authentication';
|
||||||
|
|
||||||
// Lazy load all route components for better code splitting
|
|
||||||
const Dashboard = lazy(() => import('app/main/Dashboard'));
|
|
||||||
const Devices = lazy(() => import('app/main/Devices'));
|
|
||||||
const Sensors = lazy(() => import('app/main/Sensors'));
|
|
||||||
const Help = lazy(() => import('app/main/Help'));
|
|
||||||
const Customizations = lazy(() => import('app/main/Customizations'));
|
|
||||||
const Scheduler = lazy(() => import('app/main/Scheduler'));
|
|
||||||
const CustomEntities = lazy(() => import('app/main/CustomEntities'));
|
|
||||||
const Modules = lazy(() => import('app/main/Modules'));
|
|
||||||
const UserProfile = lazy(() => import('app/main/UserProfile'));
|
|
||||||
|
|
||||||
const Status = lazy(() => import('app/status/Status'));
|
|
||||||
const HardwareStatus = lazy(() => import('app/status/HardwareStatus'));
|
|
||||||
const Activity = lazy(() => import('app/status/Activity'));
|
|
||||||
const SystemLog = lazy(() => import('app/status/SystemLog'));
|
|
||||||
const MqttStatus = lazy(() => import('app/status/MqttStatus'));
|
|
||||||
const NTPStatus = lazy(() => import('app/status/NTPStatus'));
|
|
||||||
const APStatus = lazy(() => import('app/status/APStatus'));
|
|
||||||
const NetworkStatus = lazy(() => import('app/status/NetworkStatus'));
|
|
||||||
const Version = lazy(() => import('app/status/Version'));
|
|
||||||
|
|
||||||
const Settings = lazy(() => import('app/settings/Settings'));
|
|
||||||
const ApplicationSettings = lazy(() => import('app/settings/ApplicationSettings'));
|
|
||||||
const MqttSettings = lazy(() => import('app/settings/MqttSettings'));
|
|
||||||
const NTPSettings = lazy(() => import('app/settings/NTPSettings'));
|
|
||||||
const APSettings = lazy(() => import('app/settings/APSettings'));
|
|
||||||
const DownloadUpload = lazy(() => import('app/settings/DownloadUpload'));
|
|
||||||
const Network = lazy(() => import('app/settings/network/Network'));
|
|
||||||
const Security = lazy(() => import('app/settings/security/Security'));
|
|
||||||
|
|
||||||
const AuthenticatedRouting = memo(() => {
|
const AuthenticatedRouting = memo(() => {
|
||||||
const { me } = useContext(AuthenticatedContext);
|
const { me } = useContext(AuthenticatedContext);
|
||||||
return (
|
return (
|
||||||
<Layout>
|
<Layout>
|
||||||
<Suspense fallback={<LoadingSpinner />}>
|
<Routes>
|
||||||
<Routes>
|
<Route path="/dashboard/*" element={<Dashboard />} />
|
||||||
<Route path="/dashboard/*" element={<Dashboard />} />
|
<Route path="/devices/*" element={<Devices />} />
|
||||||
<Route path="/devices/*" element={<Devices />} />
|
<Route path="/sensors/*" element={<Sensors />} />
|
||||||
<Route path="/sensors/*" element={<Sensors />} />
|
<Route path="/help/*" element={<Help />} />
|
||||||
<Route path="/help/*" element={<Help />} />
|
<Route path="/user/*" element={<UserProfile />} />
|
||||||
<Route path="/user/*" element={<UserProfile />} />
|
|
||||||
|
|
||||||
<Route path="/status/*" element={<Status />} />
|
<Route path="/status/*" element={<Status />} />
|
||||||
<Route path="/status/hardwarestatus/*" element={<HardwareStatus />} />
|
<Route path="/status/hardwarestatus/*" element={<HardwareStatus />} />
|
||||||
<Route path="/status/activity" element={<Activity />} />
|
<Route path="/status/activity" element={<Activity />} />
|
||||||
<Route path="/status/log" element={<SystemLog />} />
|
<Route path="/status/log" element={<SystemLog />} />
|
||||||
<Route path="/status/mqtt" element={<MqttStatus />} />
|
<Route path="/status/mqtt" element={<MqttStatus />} />
|
||||||
<Route path="/status/ntp" element={<NTPStatus />} />
|
<Route path="/status/ntp" element={<NTPStatus />} />
|
||||||
<Route path="/status/ap" element={<APStatus />} />
|
<Route path="/status/ap" element={<APStatus />} />
|
||||||
<Route path="/status/network" element={<NetworkStatus />} />
|
<Route path="/status/network" element={<NetworkStatus />} />
|
||||||
<Route path="/status/version" element={<Version />} />
|
<Route path="/status/version" element={<Version />} />
|
||||||
|
|
||||||
{me.admin && (
|
{me.admin && (
|
||||||
<>
|
<>
|
||||||
<Route path="/settings" element={<Settings />} />
|
<Route path="/settings" element={<Settings />} />
|
||||||
<Route
|
<Route path="/settings/application" element={<ApplicationSettings />} />
|
||||||
path="/settings/application"
|
<Route path="/settings/mqtt" element={<MqttSettings />} />
|
||||||
element={<ApplicationSettings />}
|
<Route path="/settings/ntp" element={<NTPSettings />} />
|
||||||
/>
|
<Route path="/settings/ap" element={<APSettings />} />
|
||||||
<Route path="/settings/mqtt" element={<MqttSettings />} />
|
<Route path="/settings/modules" element={<Modules />} />
|
||||||
<Route path="/settings/ntp" element={<NTPSettings />} />
|
<Route path="/settings/downloadUpload" element={<DownloadUpload />} />
|
||||||
<Route path="/settings/ap" element={<APSettings />} />
|
|
||||||
<Route path="/settings/modules" element={<Modules />} />
|
|
||||||
<Route path="/settings/downloadUpload" element={<DownloadUpload />} />
|
|
||||||
|
|
||||||
<Route path="/settings/network/*" element={<Network />} />
|
<Route path="/settings/network/*" element={<Network />} />
|
||||||
<Route path="/settings/security/*" element={<Security />} />
|
<Route path="/settings/security/*" element={<Security />} />
|
||||||
|
|
||||||
<Route path="/customizations" element={<Customizations />} />
|
<Route path="/customizations" element={<Customizations />} />
|
||||||
<Route path="/scheduler" element={<Scheduler />} />
|
<Route path="/scheduler" element={<Scheduler />} />
|
||||||
<Route path="/customentities" element={<CustomEntities />} />
|
<Route path="/customentities" element={<CustomEntities />} />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Route path="/*" element={<Navigate to="/" />} />
|
<Route path="/*" element={<Navigate to="/" />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</Suspense>
|
|
||||||
</Layout>
|
</Layout>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import { PROJECT_NAME } from 'env';
|
|||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
import type { SignInRequest } from 'types';
|
import type { SignInRequest } from 'types';
|
||||||
import { onEnterCallback, updateValue } from 'utils';
|
import { onEnterCallback, updateValue } from 'utils';
|
||||||
import { SIGN_IN_REQUEST_VALIDATOR, validate } from 'validators';
|
import { SIGN_IN_REQUEST_VALIDATOR, ValidationError, validate } from 'validators';
|
||||||
|
|
||||||
const SignIn = memo(() => {
|
const SignIn = memo(() => {
|
||||||
const authenticationContext = useContext(AuthenticationContext);
|
const authenticationContext = useContext(AuthenticationContext);
|
||||||
@@ -74,7 +74,7 @@ const SignIn = memo(() => {
|
|||||||
await validate(SIGN_IN_REQUEST_VALIDATOR, signInRequest);
|
await validate(SIGN_IN_REQUEST_VALIDATOR, signInRequest);
|
||||||
await signIn();
|
await signIn();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setFieldErrors(error as ValidateFieldsError);
|
setFieldErrors((error as ValidationError).fieldErrors);
|
||||||
setProcessing(false);
|
setProcessing(false);
|
||||||
}
|
}
|
||||||
}, [signInRequest, signIn, LL]);
|
}, [signInRequest, signIn, LL]);
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ import type { ValidateFieldsError } from 'async-validator';
|
|||||||
import { BlockFormControlLabel, ValidatedTextField } from 'components';
|
import { BlockFormControlLabel, ValidatedTextField } from 'components';
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
import { numberValue, updateValue } from 'utils';
|
import { numberValue, updateValue } from 'utils';
|
||||||
import { validate } from 'validators';
|
import { ValidationError, validate } from 'validators';
|
||||||
|
|
||||||
import { DeviceValueType, DeviceValueTypeNames, DeviceValueUOM_s } from './types';
|
import { DeviceValueType, DeviceValueTypeNames, DeviceValueUOM_s } from './types';
|
||||||
import type { EntityItem } from './types';
|
import type { EntityItem } from './types';
|
||||||
@@ -136,7 +136,7 @@ const CustomEntitiesDialog = ({
|
|||||||
}
|
}
|
||||||
onSave(processedItem);
|
onSave(processedItem);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setFieldErrors(error as ValidateFieldsError);
|
setFieldErrors((error as ValidationError).fieldErrors);
|
||||||
}
|
}
|
||||||
}, [validator, editItem, onSave]);
|
}, [validator, editItem, onSave]);
|
||||||
|
|
||||||
@@ -215,7 +215,7 @@ const CustomEntitiesDialog = ({
|
|||||||
name="value"
|
name="value"
|
||||||
label={LL.DEFAULT(0) + ' ' + LL.VALUE(0)}
|
label={LL.DEFAULT(0) + ' ' + LL.VALUE(0)}
|
||||||
type="string"
|
type="string"
|
||||||
value={editItem.value as string}
|
value={editItem.value}
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
onChange={updateFormValue}
|
onChange={updateFormValue}
|
||||||
fullWidth
|
fullWidth
|
||||||
@@ -260,7 +260,7 @@ const CustomEntitiesDialog = ({
|
|||||||
margin="normal"
|
margin="normal"
|
||||||
sx={{ width: '11ch' }}
|
sx={{ width: '11ch' }}
|
||||||
type="string"
|
type="string"
|
||||||
value={editItem.device_id as string}
|
value={editItem.device_id}
|
||||||
onChange={updateFormValue}
|
onChange={updateFormValue}
|
||||||
slotProps={{
|
slotProps={{
|
||||||
input: {
|
input: {
|
||||||
@@ -280,7 +280,7 @@ const CustomEntitiesDialog = ({
|
|||||||
margin="normal"
|
margin="normal"
|
||||||
sx={{ width: '11ch' }}
|
sx={{ width: '11ch' }}
|
||||||
type="string"
|
type="string"
|
||||||
value={editItem.type_id as string}
|
value={editItem.type_id}
|
||||||
onChange={updateFormValue}
|
onChange={updateFormValue}
|
||||||
slotProps={{
|
slotProps={{
|
||||||
input: {
|
input: {
|
||||||
@@ -381,7 +381,7 @@ const CustomEntitiesDialog = ({
|
|||||||
fieldErrors={fieldErrors || {}}
|
fieldErrors={fieldErrors || {}}
|
||||||
name="factor"
|
name="factor"
|
||||||
label={LL.BITMASK()}
|
label={LL.BITMASK()}
|
||||||
value={editItem.factor as string}
|
value={editItem.factor}
|
||||||
sx={{ width: '11ch' }}
|
sx={{ width: '11ch' }}
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
onChange={updateFormValue}
|
onChange={updateFormValue}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import type { ValidateFieldsError } from 'async-validator';
|
|||||||
import { ValidatedTextField } from 'components';
|
import { ValidatedTextField } from 'components';
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
import { numberValue, updateValue } from 'utils';
|
import { numberValue, updateValue } from 'utils';
|
||||||
import { validate } from 'validators';
|
import { ValidationError, validate } from 'validators';
|
||||||
|
|
||||||
import { DeviceValueUOM, DeviceValueUOM_s } from './types';
|
import { DeviceValueUOM, DeviceValueUOM_s } from './types';
|
||||||
import type { DeviceValue } from './types';
|
import type { DeviceValue } from './types';
|
||||||
@@ -67,7 +67,7 @@ const DevicesDialog = ({
|
|||||||
await validate(validator, editItem);
|
await validate(validator, editItem);
|
||||||
onSave(editItem);
|
onSave(editItem);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setFieldErrors(error as ValidateFieldsError);
|
setFieldErrors((error as ValidationError).fieldErrors);
|
||||||
}
|
}
|
||||||
}, [validator, editItem, onSave]);
|
}, [validator, editItem, onSave]);
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import type { ValidateFieldsError } from 'async-validator';
|
|||||||
import { BlockFormControlLabel, ValidatedTextField } from 'components';
|
import { BlockFormControlLabel, ValidatedTextField } from 'components';
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
import { updateValue } from 'utils';
|
import { updateValue } from 'utils';
|
||||||
import { validate } from 'validators';
|
import { ValidationError, validate } from 'validators';
|
||||||
|
|
||||||
import { ScheduleFlag } from './types';
|
import { ScheduleFlag } from './types';
|
||||||
import type { ScheduleItem } from './types';
|
import type { ScheduleItem } from './types';
|
||||||
@@ -120,7 +120,7 @@ const SchedulerDialog = ({
|
|||||||
await validate(validator, itemToSave);
|
await validate(validator, itemToSave);
|
||||||
onSave(itemToSave);
|
onSave(itemToSave);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setFieldErrors(error as ValidateFieldsError);
|
setFieldErrors((error as ValidationError).fieldErrors);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[validator, onSave]
|
[validator, onSave]
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import type { ValidateFieldsError } from 'async-validator';
|
|||||||
import { ValidatedTextField } from 'components';
|
import { ValidatedTextField } from 'components';
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
import { numberValue, updateValue } from 'utils';
|
import { numberValue, updateValue } from 'utils';
|
||||||
import { validate } from 'validators';
|
import { ValidationError, validate } from 'validators';
|
||||||
|
|
||||||
import { AnalogType, AnalogTypeNames, DeviceValueUOM_s } from './types';
|
import { AnalogType, AnalogTypeNames, DeviceValueUOM_s } from './types';
|
||||||
import type { AnalogSensor } from './types';
|
import type { AnalogSensor } from './types';
|
||||||
@@ -172,7 +172,7 @@ const SensorsAnalogDialog = ({
|
|||||||
await validate(validator, editItem);
|
await validate(validator, editItem);
|
||||||
onSave(editItem);
|
onSave(editItem);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setFieldErrors(error as ValidateFieldsError);
|
setFieldErrors((error as ValidationError).fieldErrors);
|
||||||
}
|
}
|
||||||
}, [validator, editItem, onSave]);
|
}, [validator, editItem, onSave]);
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import type { ValidateFieldsError } from 'async-validator';
|
|||||||
import { ValidatedTextField } from 'components';
|
import { ValidatedTextField } from 'components';
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
import { numberValue, updateValue } from 'utils';
|
import { numberValue, updateValue } from 'utils';
|
||||||
import { validate } from 'validators';
|
import { ValidationError, validate } from 'validators';
|
||||||
|
|
||||||
import type { TemperatureSensor } from './types';
|
import type { TemperatureSensor } from './types';
|
||||||
|
|
||||||
@@ -84,7 +84,7 @@ const SensorsTemperatureDialog = ({
|
|||||||
await validate(validator, editItem);
|
await validate(validator, editItem);
|
||||||
onSave(editItem);
|
onSave(editItem);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setFieldErrors(error as ValidateFieldsError);
|
setFieldErrors((error as ValidationError).fieldErrors);
|
||||||
}
|
}
|
||||||
}, [validator, editItem, onSave]);
|
}, [validator, editItem, onSave]);
|
||||||
|
|
||||||
|
|||||||
@@ -43,16 +43,6 @@ export interface Settings {
|
|||||||
modbus_port: number;
|
modbus_port: number;
|
||||||
modbus_max_clients: number;
|
modbus_max_clients: number;
|
||||||
modbus_timeout: number;
|
modbus_timeout: number;
|
||||||
email_enabled: boolean;
|
|
||||||
email_ssl?: boolean;
|
|
||||||
email_starttls?: boolean;
|
|
||||||
email_server: string;
|
|
||||||
email_port: number;
|
|
||||||
email_login: string;
|
|
||||||
email_pass: string;
|
|
||||||
email_sender: string;
|
|
||||||
email_recp: string;
|
|
||||||
email_subject: string;
|
|
||||||
developer_mode: boolean;
|
developer_mode: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import { useI18nContext } from 'i18n/i18n-react';
|
|||||||
import type { APSettingsType } from 'types';
|
import type { APSettingsType } from 'types';
|
||||||
import { APProvisionMode } from 'types';
|
import { APProvisionMode } from 'types';
|
||||||
import { numberValue, updateValueDirty, useRest } from 'utils';
|
import { numberValue, updateValueDirty, useRest } from 'utils';
|
||||||
import { createAPSettingsValidator, validate } from 'validators';
|
import { ValidationError, createAPSettingsValidator, validate } from 'validators';
|
||||||
|
|
||||||
export const isAPEnabled = ({ provision_mode }: APSettingsType) =>
|
export const isAPEnabled = ({ provision_mode }: APSettingsType) =>
|
||||||
provision_mode === APProvisionMode.AP_MODE_ALWAYS ||
|
provision_mode === APProvisionMode.AP_MODE_ALWAYS ||
|
||||||
@@ -86,7 +86,7 @@ const APSettings = () => {
|
|||||||
await validate(createAPSettingsValidator(data), data);
|
await validate(createAPSettingsValidator(data), data);
|
||||||
await saveData();
|
await saveData();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setFieldErrors(error as ValidateFieldsError);
|
setFieldErrors((error as ValidationError).fieldErrors);
|
||||||
}
|
}
|
||||||
}, [data, saveData]);
|
}, [data, saveData]);
|
||||||
|
|
||||||
|
|||||||
@@ -28,13 +28,12 @@ import {
|
|||||||
FormLoader,
|
FormLoader,
|
||||||
MessageBox,
|
MessageBox,
|
||||||
SectionContent,
|
SectionContent,
|
||||||
ValidatedPasswordField,
|
|
||||||
ValidatedTextField,
|
ValidatedTextField,
|
||||||
useLayoutTitle
|
useLayoutTitle
|
||||||
} from 'components';
|
} from 'components';
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
import { numberValue, updateValueDirty, useRest } from 'utils';
|
import { numberValue, updateValueDirty, useRest } from 'utils';
|
||||||
import { validate } from 'validators';
|
import { ValidationError, validate } from 'validators';
|
||||||
|
|
||||||
import { API, getBoardProfile, readSettings, writeSettings } from '../../api/app';
|
import { API, getBoardProfile, readSettings, writeSettings } from '../../api/app';
|
||||||
import { BOARD_PROFILES } from '../main/types';
|
import { BOARD_PROFILES } from '../main/types';
|
||||||
@@ -154,7 +153,7 @@ const ApplicationSettings = () => {
|
|||||||
setFieldErrors(undefined);
|
setFieldErrors(undefined);
|
||||||
await validate(createSettingsValidator(data), data);
|
await validate(createSettingsValidator(data), data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setFieldErrors(error as ValidateFieldsError);
|
setFieldErrors((error as ValidationError).fieldErrors);
|
||||||
} finally {
|
} finally {
|
||||||
await saveData();
|
await saveData();
|
||||||
}
|
}
|
||||||
@@ -352,156 +351,6 @@ const ApplicationSettings = () => {
|
|||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
)}
|
)}
|
||||||
<Typography color="secondary">eMail</Typography>
|
|
||||||
<BlockFormControlLabel
|
|
||||||
control={
|
|
||||||
<Checkbox
|
|
||||||
checked={data.email_enabled}
|
|
||||||
onChange={updateFormValue}
|
|
||||||
name="email_enabled"
|
|
||||||
disabled={!hardwareData.psram}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
label={
|
|
||||||
<Typography color={!hardwareData.psram ? 'grey' : 'default'}>
|
|
||||||
Enable eMail notification
|
|
||||||
{!hardwareData.psram && (
|
|
||||||
<Typography variant="caption">
|
|
||||||
({LL.IS_REQUIRED('PSRAM')})
|
|
||||||
</Typography>
|
|
||||||
)}
|
|
||||||
</Typography>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
{data.email_enabled && (
|
|
||||||
<>
|
|
||||||
<Grid
|
|
||||||
container
|
|
||||||
spacing={2}
|
|
||||||
direction="row"
|
|
||||||
justifyContent="flex-start"
|
|
||||||
alignItems="flex-start"
|
|
||||||
>
|
|
||||||
<Grid>
|
|
||||||
<ValidatedTextField
|
|
||||||
fieldErrors={fieldErrors || {}}
|
|
||||||
name="email_server"
|
|
||||||
label="SMTP Server"
|
|
||||||
variant="outlined"
|
|
||||||
value={data.email_server}
|
|
||||||
onChange={updateFormValue}
|
|
||||||
margin="normal"
|
|
||||||
/>
|
|
||||||
</Grid>
|
|
||||||
<Grid>
|
|
||||||
<ValidatedTextField
|
|
||||||
fieldErrors={fieldErrors || {}}
|
|
||||||
sx={{ width: '12ch' }}
|
|
||||||
name="email_port"
|
|
||||||
variant="outlined"
|
|
||||||
label="Port"
|
|
||||||
value={numberValue(data.email_port)}
|
|
||||||
type="number"
|
|
||||||
onChange={updateFormValue}
|
|
||||||
margin="normal"
|
|
||||||
/>
|
|
||||||
</Grid>
|
|
||||||
<Grid size={4} mt={!data.email_ssl && !data.email_starttls ? 0 : 3}>
|
|
||||||
{!data.email_starttls && (
|
|
||||||
<BlockFormControlLabel
|
|
||||||
sx={{ width: '12ch' }}
|
|
||||||
control={
|
|
||||||
<Checkbox
|
|
||||||
checked={data.email_ssl}
|
|
||||||
onChange={updateFormValue}
|
|
||||||
name="email_ssl"
|
|
||||||
disabled={
|
|
||||||
data.email_starttls || data.email_ssl === undefined
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
label="SSL/TLS"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{!data.email_ssl && (
|
|
||||||
<BlockFormControlLabel
|
|
||||||
sx={{ width: '12ch' }}
|
|
||||||
control={
|
|
||||||
<Checkbox
|
|
||||||
checked={data.email_starttls}
|
|
||||||
onChange={updateFormValue}
|
|
||||||
name="email_starttls"
|
|
||||||
disabled={
|
|
||||||
data.email_ssl || data.email_starttls === undefined
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
label="STARTTLS"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
|
||||||
<Grid container spacing={2} rowSpacing={0}>
|
|
||||||
<Grid>
|
|
||||||
<ValidatedTextField
|
|
||||||
fieldErrors={fieldErrors || {}}
|
|
||||||
name="email_login"
|
|
||||||
label="Login"
|
|
||||||
variant="outlined"
|
|
||||||
value={data.email_login}
|
|
||||||
onChange={updateFormValue}
|
|
||||||
margin="normal"
|
|
||||||
/>
|
|
||||||
</Grid>
|
|
||||||
<Grid>
|
|
||||||
<ValidatedPasswordField
|
|
||||||
fieldErrors={fieldErrors || {}}
|
|
||||||
name="email_pass"
|
|
||||||
label="Password"
|
|
||||||
variant="outlined"
|
|
||||||
value={data.email_pass}
|
|
||||||
onChange={updateFormValue}
|
|
||||||
margin="normal"
|
|
||||||
/>
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
|
||||||
<Grid container spacing={2} rowSpacing={0}>
|
|
||||||
<Grid>
|
|
||||||
<ValidatedTextField
|
|
||||||
fieldErrors={fieldErrors || {}}
|
|
||||||
name="email_sender"
|
|
||||||
label="From"
|
|
||||||
variant="outlined"
|
|
||||||
value={data.email_sender}
|
|
||||||
onChange={updateFormValue}
|
|
||||||
margin="normal"
|
|
||||||
/>
|
|
||||||
</Grid>
|
|
||||||
<Grid>
|
|
||||||
<ValidatedTextField
|
|
||||||
fieldErrors={fieldErrors || {}}
|
|
||||||
name="email_recp"
|
|
||||||
label="To"
|
|
||||||
variant="outlined"
|
|
||||||
value={data.email_recp}
|
|
||||||
onChange={updateFormValue}
|
|
||||||
margin="normal"
|
|
||||||
/>
|
|
||||||
</Grid>
|
|
||||||
<Grid>
|
|
||||||
<ValidatedTextField
|
|
||||||
fieldErrors={fieldErrors || {}}
|
|
||||||
name="email_subject"
|
|
||||||
label="Subject"
|
|
||||||
variant="outlined"
|
|
||||||
value={data.email_subject}
|
|
||||||
onChange={updateFormValue}
|
|
||||||
margin="normal"
|
|
||||||
/>
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
<Typography sx={{ pb: 1, pt: 2 }} variant="h6" color="primary">
|
<Typography sx={{ pb: 1, pt: 2 }} variant="h6" color="primary">
|
||||||
{LL.SENSORS()}
|
{LL.SENSORS()}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|||||||
@@ -112,6 +112,7 @@ const DownloadUpload = () => {
|
|||||||
const handleDownload = useCallback(
|
const handleDownload = useCallback(
|
||||||
(type: string) => () => {
|
(type: string) => () => {
|
||||||
void sendExportData(type);
|
void sendExportData(type);
|
||||||
|
setConfirmBackup(false);
|
||||||
},
|
},
|
||||||
[sendExportData]
|
[sendExportData]
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ import {
|
|||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
import type { MqttSettingsType } from 'types';
|
import type { MqttSettingsType } from 'types';
|
||||||
import { numberValue, updateValueDirty, useRest } from 'utils';
|
import { numberValue, updateValueDirty, useRest } from 'utils';
|
||||||
import { createMqttSettingsValidator, validate } from 'validators';
|
import { ValidationError, createMqttSettingsValidator, validate } from 'validators';
|
||||||
|
|
||||||
import { callAction } from '../../api/app';
|
import { callAction } from '../../api/app';
|
||||||
|
|
||||||
@@ -94,7 +94,7 @@ const MqttSettings = () => {
|
|||||||
await validate(createMqttSettingsValidator(data), data);
|
await validate(createMqttSettingsValidator(data), data);
|
||||||
await saveData();
|
await saveData();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setFieldErrors(error as ValidateFieldsError);
|
setFieldErrors((error as ValidationError).fieldErrors);
|
||||||
}
|
}
|
||||||
}, [data, saveData]);
|
}, [data, saveData]);
|
||||||
|
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ import {
|
|||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
import type { NTPSettingsType, Time } from 'types';
|
import type { NTPSettingsType, Time } from 'types';
|
||||||
import { formatLocalDateTime, updateValueDirty, useRest } from 'utils';
|
import { formatLocalDateTime, updateValueDirty, useRest } from 'utils';
|
||||||
import { validate } from 'validators';
|
import { ValidationError, validate } from 'validators';
|
||||||
import { NTP_SETTINGS_VALIDATOR } from 'validators/ntp';
|
import { NTP_SETTINGS_VALIDATOR } from 'validators/ntp';
|
||||||
|
|
||||||
import { TIME_ZONES, selectedTimeZone, useTimeZoneSelectItems } from './TZ';
|
import { TIME_ZONES, selectedTimeZone, useTimeZoneSelectItems } from './TZ';
|
||||||
@@ -133,7 +133,7 @@ const NTPSettings = () => {
|
|||||||
await validate(NTP_SETTINGS_VALIDATOR, data);
|
await validate(NTP_SETTINGS_VALIDATOR, data);
|
||||||
await saveData();
|
await saveData();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setFieldErrors(error as ValidateFieldsError);
|
setFieldErrors((error as ValidationError).fieldErrors);
|
||||||
}
|
}
|
||||||
}, [data, saveData]);
|
}, [data, saveData]);
|
||||||
|
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ import {
|
|||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
import type { NetworkSettingsType } from 'types';
|
import type { NetworkSettingsType } from 'types';
|
||||||
import { updateValueDirty, useRest } from 'utils';
|
import { updateValueDirty, useRest } from 'utils';
|
||||||
import { validate } from 'validators';
|
import { ValidationError, validate } from 'validators';
|
||||||
import { createNetworkSettingsValidator } from 'validators/network';
|
import { createNetworkSettingsValidator } from 'validators/network';
|
||||||
|
|
||||||
import SystemMonitor from '../../status/SystemMonitor';
|
import SystemMonitor from '../../status/SystemMonitor';
|
||||||
@@ -116,7 +116,7 @@ const NetworkSettings = () => {
|
|||||||
await validate(createNetworkSettingsValidator(data), data);
|
await validate(createNetworkSettingsValidator(data), data);
|
||||||
await saveData();
|
await saveData();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setFieldErrors(error as ValidateFieldsError);
|
setFieldErrors((error as ValidationError).fieldErrors);
|
||||||
}
|
}
|
||||||
deselectNetwork();
|
deselectNetwork();
|
||||||
}, [data, saveData, deselectNetwork]);
|
}, [data, saveData, deselectNetwork]);
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import { AuthenticatedContext } from 'contexts/authentication';
|
|||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
import type { SecuritySettingsType } from 'types';
|
import type { SecuritySettingsType } from 'types';
|
||||||
import { updateValueDirty, useRest } from 'utils';
|
import { updateValueDirty, useRest } from 'utils';
|
||||||
import { SECURITY_SETTINGS_VALIDATOR, validate } from 'validators';
|
import { SECURITY_SETTINGS_VALIDATOR, ValidationError, validate } from 'validators';
|
||||||
|
|
||||||
const SecuritySettings = () => {
|
const SecuritySettings = () => {
|
||||||
const { LL } = useI18nContext();
|
const { LL } = useI18nContext();
|
||||||
@@ -58,7 +58,7 @@ const SecuritySettings = () => {
|
|||||||
await saveData();
|
await saveData();
|
||||||
await authenticatedContext.refresh();
|
await authenticatedContext.refresh();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setFieldErrors(error as ValidateFieldsError);
|
setFieldErrors((error as ValidationError).fieldErrors);
|
||||||
}
|
}
|
||||||
}, [data, saveData, authenticatedContext]);
|
}, [data, saveData, authenticatedContext]);
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import {
|
|||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
import type { UserType } from 'types';
|
import type { UserType } from 'types';
|
||||||
import { updateValue } from 'utils';
|
import { updateValue } from 'utils';
|
||||||
import { validate } from 'validators';
|
import { ValidationError, validate } from 'validators';
|
||||||
|
|
||||||
interface UserFormProps {
|
interface UserFormProps {
|
||||||
creating: boolean;
|
creating: boolean;
|
||||||
@@ -69,7 +69,7 @@ const User: FC<UserFormProps> = ({
|
|||||||
await validate(validator, user);
|
await validate(validator, user);
|
||||||
onDoneEditing();
|
onDoneEditing();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setFieldErrors(error as ValidateFieldsError);
|
setFieldErrors((error as ValidationError).fieldErrors);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [user, validator, onDoneEditing]);
|
}, [user, validator, onDoneEditing]);
|
||||||
|
|||||||
@@ -315,13 +315,22 @@ const InstallDialog = memo(
|
|||||||
fetchDevVersion ? latestDevVersion?.name : latestVersion?.name
|
fetchDevVersion ? latestDevVersion?.name : latestVersion?.name
|
||||||
)}
|
)}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
{upgradeImportantMessageType === 1 && LL.UPGRADE_IMPORTANT_MESSAGES_1()}
|
|
||||||
{upgradeImportantMessageType === 2 && LL.UPGRADE_IMPORTANT_MESSAGES_2()}
|
{upgradeImportantMessageType === 2 && LL.UPGRADE_IMPORTANT_MESSAGES_2()}
|
||||||
|
{upgradeImportantMessageType === 1 && (
|
||||||
|
<>
|
||||||
|
{LL.UPGRADE_IMPORTANT_MESSAGES_1()}
|
||||||
|
<Typography sx={{ mt: 2 }}>
|
||||||
|
<Link to="/settings/downloadUpload" style={{ color: 'lightblue' }}>
|
||||||
|
{LL.DOWNLOAD_SYSTEM_BACKUP()}
|
||||||
|
</Link>
|
||||||
|
</Typography>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
<Typography sx={{ mt: 2 }}>
|
<Typography sx={{ mt: 2 }}>
|
||||||
<Link
|
<Link
|
||||||
target="_blank"
|
|
||||||
to="https://docs.emsesp.org/FAQ#upgrading-the-firmware"
|
to="https://docs.emsesp.org/FAQ#upgrading-the-firmware"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
style={{ color: 'lightblue' }}
|
style={{ color: 'lightblue' }}
|
||||||
>
|
>
|
||||||
{LL.ONLINE_HELP()}
|
{LL.ONLINE_HELP()}
|
||||||
|
|||||||
@@ -211,14 +211,15 @@ const DragNdrop = ({ text, onFileSelected }: DragNdropProps) => {
|
|||||||
{LL.UPGRADE_IMPORTANT_MESSAGES()}
|
{LL.UPGRADE_IMPORTANT_MESSAGES()}
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
<DialogContent dividers>
|
<DialogContent dividers>
|
||||||
{upgradeImportantMessageType === 1 &&
|
|
||||||
LL.UPGRADE_IMPORTANT_MESSAGES_1()}
|
|
||||||
{upgradeImportantMessageType === 2 &&
|
{upgradeImportantMessageType === 2 &&
|
||||||
LL.UPGRADE_IMPORTANT_MESSAGES_2()}
|
LL.UPGRADE_IMPORTANT_MESSAGES_2()}
|
||||||
|
{upgradeImportantMessageType === 1 &&
|
||||||
|
LL.UPGRADE_IMPORTANT_MESSAGES_1()}
|
||||||
<Typography sx={{ mt: 2 }}>
|
<Typography sx={{ mt: 2 }}>
|
||||||
<Link
|
<Link
|
||||||
target="_blank"
|
|
||||||
to="https://docs.emsesp.org/FAQ#upgrading-the-firmware"
|
to="https://docs.emsesp.org/FAQ#upgrading-the-firmware"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
style={{ color: 'lightblue' }}
|
style={{ color: 'lightblue' }}
|
||||||
>
|
>
|
||||||
{LL.ONLINE_HELP()}
|
{LL.ONLINE_HELP()}
|
||||||
|
|||||||
@@ -350,19 +350,19 @@ const cz: Translation = {
|
|||||||
NO_DATA_1: 'Nebyly nalezeny žádné oblíbené entity. Použijte modul',
|
NO_DATA_1: 'Nebyly nalezeny žádné oblíbené entity. Použijte modul',
|
||||||
NO_DATA_2: 'pro jejich výběr.',
|
NO_DATA_2: 'pro jejich výběr.',
|
||||||
NO_DATA_3: 'Pro zobrazení všech dostupných entit navštivte stránku',
|
NO_DATA_3: 'Pro zobrazení všech dostupných entit navštivte stránku',
|
||||||
NO_GPIO:'Nebylo nalezeno žádné volné GPIO',
|
NO_GPIO: 'Nebylo nalezeno žádné volné GPIO',
|
||||||
THIS_VERSION: 'Tato verze',
|
THIS_VERSION: 'Tato verze',
|
||||||
PLATFORM: 'Platforma',
|
PLATFORM: 'Platforma',
|
||||||
RELEASE_TYPE: 'Typ sestavení',
|
RELEASE_TYPE: 'Typ sestavení',
|
||||||
INTERNET_CONNECTION_REQUIRED: 'Pro automatickou kontrolu a instalaci aktualizací je třeba internetové připojení',
|
INTERNET_CONNECTION_REQUIRED: 'Pro automatickou kontrolu a instalaci aktualizací je třeba internetové připojení',
|
||||||
SWITCH_RELEASE_TYPE: 'Přepnout na {0} verzi',
|
SWITCH_RELEASE_TYPE: 'Přepnout na {0} verzi',
|
||||||
FIRMWARE_VERSION_INFO: 'Informace o verzi firmwaru',
|
FIRMWARE_VERSION_INFO: 'Informace o verzi firmwaru',
|
||||||
NO_DATA: 'Žádné údaje',
|
NO_DATA: 'žádné údaje',
|
||||||
USER_PROFILE: 'Uživatelský profil',
|
USER_PROFILE: 'Uživatelský profil',
|
||||||
STORED_VERSIONS: 'Uložené verze',
|
STORED_VERSIONS: 'Uložené verze',
|
||||||
ONLINE_HELP: 'online nápověda',
|
ONLINE_HELP: 'online nápověda',
|
||||||
UPGRADE_IMPORTANT_MESSAGES: 'Aktualizovat důležité zprávy',
|
UPGRADE_IMPORTANT_MESSAGES: 'Aktualizovat důležité zprávy',
|
||||||
UPGRADE_IMPORTANT_MESSAGES_1: 'Tato aktualizace vyžaduje obnovení továrního nastavení. Ujistěte se, že jste vytvořili zálohu své konfigurace a nastavení před pokračováním a nahrajte ji po instalaci nové verze.',
|
UPGRADE_IMPORTANT_MESSAGES_1: 'Tato aktualizace vyžaduje obnovení továrního nastavení. Ujistěte se, že nejprve stáhnete systémovou zálohu před pokračováním a poté nahrajte tento soubor po instalaci nové verze.',
|
||||||
UPGRADE_IMPORTANT_MESSAGES_2: 'Aktualizujete se na novou hlavní verzi. Ujistěte se, že jste přečetli ChangeLog pro jakékoliv závažné změny.',
|
UPGRADE_IMPORTANT_MESSAGES_2: 'Aktualizujete se na novou hlavní verzi. Ujistěte se, že jste přečetli ChangeLog pro jakékoliv závažné změny.',
|
||||||
WARNING_SYSTEM_BACKUP: 'Toto vytvoří zálohu vašich celých systémových konfigurací a nastavení. Všechna hesla budou v zálohovém souboru čitelná. Buďte opatrní při sdílení! Opravdu chcete pokračovat?'
|
WARNING_SYSTEM_BACKUP: 'Toto vytvoří zálohu vašich celých systémových konfigurací a nastavení. Všechna hesla budou v zálohovém souboru čitelná. Buďte opatrní při sdílení! Opravdu chcete pokračovat?'
|
||||||
|
|
||||||
|
|||||||
@@ -187,7 +187,7 @@ const de: Translation = {
|
|||||||
COMPACT: 'Kompakte Darstellung',
|
COMPACT: 'Kompakte Darstellung',
|
||||||
DOWNLOAD_SETTINGS_TEXT: 'Erstellen Sie eine Sicherung Ihrer Konfigurationen und Einstellungen',
|
DOWNLOAD_SETTINGS_TEXT: 'Erstellen Sie eine Sicherung Ihrer Konfigurationen und Einstellungen',
|
||||||
DOWNLOAD_SETTINGS_TEXT2: 'Exportiere alle Werte',
|
DOWNLOAD_SETTINGS_TEXT2: 'Exportiere alle Werte',
|
||||||
DOWNLOAD_SYSTEM_BACKUP: 'System Sicherung',
|
DOWNLOAD_SYSTEM_BACKUP: 'Systemsicherung',
|
||||||
UPLOAD_TEXT: 'Laden Sie eine neue Firmware-Datei (.bin) oder eine Sicherungsdatei (.json) hoch',
|
UPLOAD_TEXT: 'Laden Sie eine neue Firmware-Datei (.bin) oder eine Sicherungsdatei (.json) hoch',
|
||||||
UPLOAD_DROP_TEXT: 'Legen Sie eine Firmware-Datei (.bin) ab oder klicken Sie hier',
|
UPLOAD_DROP_TEXT: 'Legen Sie eine Firmware-Datei (.bin) ab oder klicken Sie hier',
|
||||||
ERROR: 'Unerwarteter Fehler, bitte versuchen Sie es erneut.',
|
ERROR: 'Unerwarteter Fehler, bitte versuchen Sie es erneut.',
|
||||||
@@ -350,7 +350,7 @@ const de: Translation = {
|
|||||||
NO_DATA_1: 'Keine favorisierten EMS-Entitäten gefunden! Verwenden Sie das Modul',
|
NO_DATA_1: 'Keine favorisierten EMS-Entitäten gefunden! Verwenden Sie das Modul',
|
||||||
NO_DATA_2: ', um sie zu markieren.',
|
NO_DATA_2: ', um sie zu markieren.',
|
||||||
NO_DATA_3: 'Um alle verfügbaren Entitäten anzuzeigen, gehen Sie zu',
|
NO_DATA_3: 'Um alle verfügbaren Entitäten anzuzeigen, gehen Sie zu',
|
||||||
NO_GPIO:'Keine freien GPIO gefunden',
|
NO_GPIO: 'Keine freien GPIO gefunden',
|
||||||
THIS_VERSION: 'Diese Version',
|
THIS_VERSION: 'Diese Version',
|
||||||
PLATFORM: 'Plattform',
|
PLATFORM: 'Plattform',
|
||||||
RELEASE_TYPE: 'Release Typ',
|
RELEASE_TYPE: 'Release Typ',
|
||||||
@@ -362,9 +362,9 @@ const de: Translation = {
|
|||||||
STORED_VERSIONS: 'Gespeicherte Versionen',
|
STORED_VERSIONS: 'Gespeicherte Versionen',
|
||||||
ONLINE_HELP: 'Online-Hilfe',
|
ONLINE_HELP: 'Online-Hilfe',
|
||||||
UPGRADE_IMPORTANT_MESSAGES: 'Wichtige Nachrichten aktualisieren',
|
UPGRADE_IMPORTANT_MESSAGES: 'Wichtige Nachrichten aktualisieren',
|
||||||
UPGRADE_IMPORTANT_MESSAGES_1: 'Diese Aktualisierung erfordert eine Werkseinstellung. Stellen Sie sicher, dass Sie eine Sicherung Ihrer Konfiguration und Einstellungen vor dem Fortfahren erstellt haben und diese nach der Installation der neuen Version hochladen.',
|
UPGRADE_IMPORTANT_MESSAGES_1: 'Für diese Aktualisierung ist ein Werksreset erforderlich. Stellen Sie sicher, dass Sie zuerst eine Systemsicherung herunterladen, bevor Sie fortfahren, und laden Sie diese Datei dann nach der Installation der neuen Version hoch.',
|
||||||
UPGRADE_IMPORTANT_MESSAGES_2: 'Sie aktualisieren auf eine neue Hauptversion. Stellen Sie sicher, dass Sie den ChangeLog für alle wichtigen Änderungen gelesen haben.',
|
UPGRADE_IMPORTANT_MESSAGES_2: 'Sie aktualisieren auf eine neue Hauptversion. Stellen Sie sicher, dass Sie den ChangeLog für alle wichtigen Änderungen gelesen haben.',
|
||||||
WARNING_SYSTEM_BACKUP: 'Dies wird eine Sicherung Ihrer vollständigen Systemkonfiguration und -einstellungen erstellen. Alle Passwörter werden im Sicherungsdatei lesbar sein. Seien Sie vorsichtig beim Teilen! Möchten Sie fortfahren?'
|
WARNING_SYSTEM_BACKUP: 'Dies wird eine Sicherung Ihrer vollständigen Systemkonfiguration und Einstellungen erstellen. Alle Passwörter werden in dieser Sicherungsdatei lesbar sein. Seien Sie vorsichtig beim Teilen! Möchten Sie fortfahren?'
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -350,7 +350,7 @@ const en: Translation = {
|
|||||||
NO_DATA_1: 'No favorite EMS entities found yet. Use the',
|
NO_DATA_1: 'No favorite EMS entities found yet. Use the',
|
||||||
NO_DATA_2: 'module to mark them.',
|
NO_DATA_2: 'module to mark them.',
|
||||||
NO_DATA_3: 'To see all available entities go to',
|
NO_DATA_3: 'To see all available entities go to',
|
||||||
NO_GPIO:'No available GPIO found',
|
NO_GPIO: 'No available GPIO found',
|
||||||
THIS_VERSION: 'This Version',
|
THIS_VERSION: 'This Version',
|
||||||
PLATFORM: 'Platform',
|
PLATFORM: 'Platform',
|
||||||
RELEASE_TYPE: 'Release Type',
|
RELEASE_TYPE: 'Release Type',
|
||||||
@@ -362,7 +362,7 @@ const en: Translation = {
|
|||||||
STORED_VERSIONS: 'Stored Versions',
|
STORED_VERSIONS: 'Stored Versions',
|
||||||
ONLINE_HELP: 'online help',
|
ONLINE_HELP: 'online help',
|
||||||
UPGRADE_IMPORTANT_MESSAGES: 'Upgrade Important Messages',
|
UPGRADE_IMPORTANT_MESSAGES: 'Upgrade Important Messages',
|
||||||
UPGRADE_IMPORTANT_MESSAGES_1: 'This upgrade requires a factory reset. Make sure you have made a backup of your configuration and settings before continuing, and upload this after the new version is installed.',
|
UPGRADE_IMPORTANT_MESSAGES_1: 'This upgrade requires a factory reset. Make sure you first download a System Backup before continuing, and then upload this file after the new version is installed.',
|
||||||
UPGRADE_IMPORTANT_MESSAGES_2: 'You are upgrading to a new major version. Make sure you have read the ChangeLog for any breaking changes.',
|
UPGRADE_IMPORTANT_MESSAGES_2: 'You are upgrading to a new major version. Make sure you have read the ChangeLog for any breaking changes.',
|
||||||
WARNING_SYSTEM_BACKUP: 'This will create a backup of your full system configuration and settings. All passwords will be readable in the backup file. Be careful with sharing! Do you want to continue?'
|
WARNING_SYSTEM_BACKUP: 'This will create a backup of your full system configuration and settings. All passwords will be readable in the backup file. Be careful with sharing! Do you want to continue?'
|
||||||
|
|
||||||
|
|||||||
@@ -350,7 +350,7 @@ const fr: Translation = {
|
|||||||
NO_DATA_1: 'Aucune entité EMS favorite trouvée. Utilisez le',
|
NO_DATA_1: 'Aucune entité EMS favorite trouvée. Utilisez le',
|
||||||
NO_DATA_2: 'module pour les marquer.',
|
NO_DATA_2: 'module pour les marquer.',
|
||||||
NO_DATA_3: 'Pour voir toutes les entités disponibles, aller à',
|
NO_DATA_3: 'Pour voir toutes les entités disponibles, aller à',
|
||||||
NO_GPIO:"Aucun GPIO disponible n'a été détecté",
|
NO_GPIO: "Aucun GPIO disponible n'a été détecté",
|
||||||
THIS_VERSION: 'Cette version',
|
THIS_VERSION: 'Cette version',
|
||||||
PLATFORM: 'Plateforme',
|
PLATFORM: 'Plateforme',
|
||||||
RELEASE_TYPE: 'Type de version',
|
RELEASE_TYPE: 'Type de version',
|
||||||
@@ -362,7 +362,7 @@ const fr: Translation = {
|
|||||||
STORED_VERSIONS: 'Versions stockées',
|
STORED_VERSIONS: 'Versions stockées',
|
||||||
ONLINE_HELP: 'aide en ligne',
|
ONLINE_HELP: 'aide en ligne',
|
||||||
UPGRADE_IMPORTANT_MESSAGES: 'Mettre à jour les messages importants',
|
UPGRADE_IMPORTANT_MESSAGES: 'Mettre à jour les messages importants',
|
||||||
UPGRADE_IMPORTANT_MESSAGES_1: 'Cette mise à jour nécessite une réinitialisation de fabrique. Assurez-vous d\'avoir créé une sauvegarde de vos configurations et paramètres avant de continuer et de la charger après l\'installation de la nouvelle version.',
|
UPGRADE_IMPORTANT_MESSAGES_1: 'Cette mise à jour nécessite une réinitialisation de fabrique. Assurez-vous de télécharger une sauvegarde système avant de continuer, et de la charger après l\'installation de la nouvelle version.',
|
||||||
UPGRADE_IMPORTANT_MESSAGES_2: 'Vous mettez à jour vers une nouvelle version majeure. Assurez-vous de lire le ChangeLog pour tout changement important.',
|
UPGRADE_IMPORTANT_MESSAGES_2: 'Vous mettez à jour vers une nouvelle version majeure. Assurez-vous de lire le ChangeLog pour tout changement important.',
|
||||||
WARNING_SYSTEM_BACKUP: 'Cela créera une sauvegarde de votre configuration et paramètres complets. Tous les mots de passe seront lisibles dans le fichier de sauvegarde. Soyez prudent avec le partage ! Voulez-vous continuer ?'
|
WARNING_SYSTEM_BACKUP: 'Cela créera une sauvegarde de votre configuration et paramètres complets. Tous les mots de passe seront lisibles dans le fichier de sauvegarde. Soyez prudent avec le partage ! Voulez-vous continuer ?'
|
||||||
|
|
||||||
|
|||||||
@@ -350,7 +350,7 @@ const it: Translation = {
|
|||||||
NO_DATA_1: 'Nessuna entità EMS preferita trovata. Usa il',
|
NO_DATA_1: 'Nessuna entità EMS preferita trovata. Usa il',
|
||||||
NO_DATA_2: 'modulo per marcarle.',
|
NO_DATA_2: 'modulo per marcarle.',
|
||||||
NO_DATA_3: 'Per vedere tutte le entità disponibili vai a',
|
NO_DATA_3: 'Per vedere tutte le entità disponibili vai a',
|
||||||
NO_GPIO:'Non è stato trovato alcun GPIO disponibile',
|
NO_GPIO: 'Non è stato trovato alcun GPIO disponibile',
|
||||||
THIS_VERSION: 'Questa versione',
|
THIS_VERSION: 'Questa versione',
|
||||||
PLATFORM: 'Piattaforma',
|
PLATFORM: 'Piattaforma',
|
||||||
RELEASE_TYPE: 'Tipo di rilascio',
|
RELEASE_TYPE: 'Tipo di rilascio',
|
||||||
@@ -362,7 +362,7 @@ const it: Translation = {
|
|||||||
STORED_VERSIONS: 'Versioni memorizzate',
|
STORED_VERSIONS: 'Versioni memorizzate',
|
||||||
ONLINE_HELP: 'aiuto online',
|
ONLINE_HELP: 'aiuto online',
|
||||||
UPGRADE_IMPORTANT_MESSAGES: 'Aggiorna Messaggi Importanti',
|
UPGRADE_IMPORTANT_MESSAGES: 'Aggiorna Messaggi Importanti',
|
||||||
UPGRADE_IMPORTANT_MESSAGES_1: 'Questa aggiornamento richiede un ripristino di fabbrica. Assicurati di aver creato un backup delle tue configurazioni e impostazioni prima di continuare e di caricarlo dopo l\'installazione della nuova versione.',
|
UPGRADE_IMPORTANT_MESSAGES_1: 'Questa aggiornamento richiede un ripristino di fabbrica. Assicurati di prima scaricare un backup del sistema prima di continuare, e poi caricare questo file dopo l\'installazione della nuova versione.',
|
||||||
UPGRADE_IMPORTANT_MESSAGES_2: 'Stai aggiornando a una nuova versione principale. Assicurati di aver letto il ChangeLog per qualsiasi cambiamento importante.',
|
UPGRADE_IMPORTANT_MESSAGES_2: 'Stai aggiornando a una nuova versione principale. Assicurati di aver letto il ChangeLog per qualsiasi cambiamento importante.',
|
||||||
WARNING_SYSTEM_BACKUP: 'Questo creerà un backup delle tue configurazioni e impostazioni complete. Tutte le password saranno leggibili nel file di backup. Sei sicuro di voler continuare?'
|
WARNING_SYSTEM_BACKUP: 'Questo creerà un backup delle tue configurazioni e impostazioni complete. Tutte le password saranno leggibili nel file di backup. Sei sicuro di voler continuare?'
|
||||||
|
|
||||||
|
|||||||
@@ -350,7 +350,7 @@ const nl: Translation = {
|
|||||||
NO_DATA_1: 'Er zijn nog geen favoriete EMS-entiteiten gevonden. Gebruik de',
|
NO_DATA_1: 'Er zijn nog geen favoriete EMS-entiteiten gevonden. Gebruik de',
|
||||||
NO_DATA_2: 'module om ze te markeren.',
|
NO_DATA_2: 'module om ze te markeren.',
|
||||||
NO_DATA_3: 'Om alle beschikbare entiteiten te zien, ga naar',
|
NO_DATA_3: 'Om alle beschikbare entiteiten te zien, ga naar',
|
||||||
NO_GPIO:'Er is geen beschikbare GPIO gevonden',
|
NO_GPIO: 'Er is geen beschikbare GPIO gevonden',
|
||||||
THIS_VERSION: 'Deze Versie',
|
THIS_VERSION: 'Deze Versie',
|
||||||
PLATFORM: 'Platform',
|
PLATFORM: 'Platform',
|
||||||
RELEASE_TYPE: 'Release Typ',
|
RELEASE_TYPE: 'Release Typ',
|
||||||
@@ -362,7 +362,7 @@ const nl: Translation = {
|
|||||||
STORED_VERSIONS: 'Opgeslagen versies',
|
STORED_VERSIONS: 'Opgeslagen versies',
|
||||||
ONLINE_HELP: 'online help',
|
ONLINE_HELP: 'online help',
|
||||||
UPGRADE_IMPORTANT_MESSAGES: 'Upgrade Belangrijke Berichten',
|
UPGRADE_IMPORTANT_MESSAGES: 'Upgrade Belangrijke Berichten',
|
||||||
UPGRADE_IMPORTANT_MESSAGES_1: 'Deze upgrade vereist een fabrieksinstelling. Zorg ervoor dat u een back-up van uw configuratie en instellingen hebt gemaakt voordat u doorgaat en upload deze na de installatie van de nieuwe versie.',
|
UPGRADE_IMPORTANT_MESSAGES_1: 'Deze upgrade vereist een fabrieksinstelling. Zorg ervoor dat u eerst een Systeem Backup download voordat u doorgaat, en upload deze file na de installatie van de nieuwe versie.',
|
||||||
UPGRADE_IMPORTANT_MESSAGES_2: 'U updatet naar een nieuwe grote versie. Zorg ervoor dat u de ChangeLog hebt gelezen voor alle brekende wijzigingen.',
|
UPGRADE_IMPORTANT_MESSAGES_2: 'U updatet naar een nieuwe grote versie. Zorg ervoor dat u de ChangeLog hebt gelezen voor alle brekende wijzigingen.',
|
||||||
WARNING_SYSTEM_BACKUP: 'Dit zal een back-up van uw volledige systeemconfiguratie en instellingen maken. Alle wachtwoorden zijn leesbaar in het back-upbestand. Wees voorzichtig bij delen! Wilt u doorgaan?'
|
WARNING_SYSTEM_BACKUP: 'Dit zal een back-up van uw volledige systeemconfiguratie en instellingen maken. Alle wachtwoorden zijn leesbaar in het back-upbestand. Wees voorzichtig bij delen! Wilt u doorgaan?'
|
||||||
|
|
||||||
|
|||||||
@@ -350,7 +350,7 @@ const no: Translation = {
|
|||||||
NO_DATA_1: 'Ingen favoritte EMS enheter funnet enda. Bruk',
|
NO_DATA_1: 'Ingen favoritte EMS enheter funnet enda. Bruk',
|
||||||
NO_DATA_2: 'modul for å markere dem.',
|
NO_DATA_2: 'modul for å markere dem.',
|
||||||
NO_DATA_3: 'For å se alle tilgjengelige enheter, gå til',
|
NO_DATA_3: 'For å se alle tilgjengelige enheter, gå til',
|
||||||
NO_GPIO:'Det ble ikke funnet noen tilgjengelige GPIO-porter',
|
NO_GPIO: 'Det ble ikke funnet noen tilgjengelige GPIO-porter',
|
||||||
THIS_VERSION: 'Denne versjonen',
|
THIS_VERSION: 'Denne versjonen',
|
||||||
PLATFORM: 'Plattform',
|
PLATFORM: 'Plattform',
|
||||||
RELEASE_TYPE: 'Utgivelses type',
|
RELEASE_TYPE: 'Utgivelses type',
|
||||||
@@ -362,7 +362,7 @@ const no: Translation = {
|
|||||||
STORED_VERSIONS: 'Lagret versjoner',
|
STORED_VERSIONS: 'Lagret versjoner',
|
||||||
ONLINE_HELP: 'online hjelp',
|
ONLINE_HELP: 'online hjelp',
|
||||||
UPGRADE_IMPORTANT_MESSAGES: 'Oppdater viktige meldinger',
|
UPGRADE_IMPORTANT_MESSAGES: 'Oppdater viktige meldinger',
|
||||||
UPGRADE_IMPORTANT_MESSAGES_1: 'Denne oppdateringen krever en fabriksinstilling. Sørg for at du har laget en sikkerhetskopi av din konfigurasjon og innstillinger før du fortsetter, og last denne opp etter at den nye versjonen er installert.',
|
UPGRADE_IMPORTANT_MESSAGES_1: 'Denne oppdateringen krever en fabriksinstilling. Sørg for at du først lastet ned en System Backup før du fortsetter, og last denne filen etter at den nye versjonen er installert.',
|
||||||
UPGRADE_IMPORTANT_MESSAGES_2: 'Du oppdaterer til en ny hovedversjon. Sørg for at du har lest ChangeLog for eventuelle bruddende endringer.',
|
UPGRADE_IMPORTANT_MESSAGES_2: 'Du oppdaterer til en ny hovedversjon. Sørg for at du har lest ChangeLog for eventuelle bruddende endringer.',
|
||||||
WARNING_SYSTEM_BACKUP: 'Dette vil lage en sikkerhetskopi av din fullstendige systemkonfigurasjon og innstillinger. Alle passord vil være lesbare i sikkerhetskopien. Vær forsiktig med deling! Vil du fortsette?'
|
WARNING_SYSTEM_BACKUP: 'Dette vil lage en sikkerhetskopi av din fullstendige systemkonfigurasjon og innstillinger. Alle passord vil være lesbare i sikkerhetskopien. Vær forsiktig med deling! Vil du fortsette?'
|
||||||
|
|
||||||
|
|||||||
@@ -350,7 +350,7 @@ const pl: BaseTranslation = {
|
|||||||
NO_DATA_1: 'Brak ulubionych encji EMS. Użyj',
|
NO_DATA_1: 'Brak ulubionych encji EMS. Użyj',
|
||||||
NO_DATA_2: 'moduł do ich oznaczenia.',
|
NO_DATA_2: 'moduł do ich oznaczenia.',
|
||||||
NO_DATA_3: 'Aby zobaczyć wszystkie dostępne encje przejdź do',
|
NO_DATA_3: 'Aby zobaczyć wszystkie dostępne encje przejdź do',
|
||||||
NO_GPIO:'Nie znaleziono dostępnych pinów GPIO',
|
NO_GPIO: 'Nie znaleziono dostępnych pinów GPIO',
|
||||||
THIS_VERSION: 'Ta wersja',
|
THIS_VERSION: 'Ta wersja',
|
||||||
PLATFORM: 'Platforma',
|
PLATFORM: 'Platforma',
|
||||||
RELEASE_TYPE: 'Typ wydania',
|
RELEASE_TYPE: 'Typ wydania',
|
||||||
@@ -362,7 +362,7 @@ const pl: BaseTranslation = {
|
|||||||
STORED_VERSIONS: 'Zapisane wersje',
|
STORED_VERSIONS: 'Zapisane wersje',
|
||||||
ONLINE_HELP: 'pomoc online',
|
ONLINE_HELP: 'pomoc online',
|
||||||
UPGRADE_IMPORTANT_MESSAGES: 'Aktualizuj ważne wiadomości',
|
UPGRADE_IMPORTANT_MESSAGES: 'Aktualizuj ważne wiadomości',
|
||||||
UPGRADE_IMPORTANT_MESSAGES_1: 'Ta aktualizacja wymaga resetu fabrycznego. Upewnij się, że masz utworzoną kopię swoich ustawień i konfiguracji przed kontynuowaniem i przesuń ją po zainstalowaniu nowej wersji.',
|
UPGRADE_IMPORTANT_MESSAGES_1: 'Ta aktualizacja wymaga resetu fabrycznego. Upewnij się, że najpierw pobierzesz kopię zapasową systemu przed kontynuowaniem, a następnie przesuń tę plik po zainstalowaniu nowej wersji.',
|
||||||
UPGRADE_IMPORTANT_MESSAGES_2: 'Aktualizujesz się do nowej głównej wersji. Upewnij się, że przeczytałeś ChangeLog dla wszelkich istotnych zmian.',
|
UPGRADE_IMPORTANT_MESSAGES_2: 'Aktualizujesz się do nowej głównej wersji. Upewnij się, że przeczytałeś ChangeLog dla wszelkich istotnych zmian.',
|
||||||
WARNING_SYSTEM_BACKUP: 'To spowoduje utworzenie kopii zapasowej całej konfiguracji i ustawień systemu. Wszystkie hasła będą widoczne w pliku kopii zapasowej. Bądź ostrożny przy udostępnianiu! Chcesz kontynuować?'
|
WARNING_SYSTEM_BACKUP: 'To spowoduje utworzenie kopii zapasowej całej konfiguracji i ustawień systemu. Wszystkie hasła będą widoczne w pliku kopii zapasowej. Bądź ostrożny przy udostępnianiu! Chcesz kontynuować?'
|
||||||
|
|
||||||
|
|||||||
@@ -350,7 +350,7 @@ const sk: Translation = {
|
|||||||
NO_DATA_1: 'Nenašli sa žiadne obľúbené entity EMS. Použite',
|
NO_DATA_1: 'Nenašli sa žiadne obľúbené entity EMS. Použite',
|
||||||
NO_DATA_2: 'modul na ich označenie.',
|
NO_DATA_2: 'modul na ich označenie.',
|
||||||
NO_DATA_3: 'Ak chcete zobraziť všetky dostupné entity, prejdite na',
|
NO_DATA_3: 'Ak chcete zobraziť všetky dostupné entity, prejdite na',
|
||||||
NO_GPIO:'Nebol nájdený žiadny dostupný GPIO',
|
NO_GPIO: 'Nebol nájdený žiadny dostupný GPIO',
|
||||||
THIS_VERSION: 'Táto verzia',
|
THIS_VERSION: 'Táto verzia',
|
||||||
PLATFORM: 'Platforma',
|
PLATFORM: 'Platforma',
|
||||||
RELEASE_TYPE: 'Typ vydania',
|
RELEASE_TYPE: 'Typ vydania',
|
||||||
@@ -362,7 +362,7 @@ const sk: Translation = {
|
|||||||
STORED_VERSIONS: 'Uložené verzie',
|
STORED_VERSIONS: 'Uložené verzie',
|
||||||
ONLINE_HELP: 'online pomoc',
|
ONLINE_HELP: 'online pomoc',
|
||||||
UPGRADE_IMPORTANT_MESSAGES: 'Aktualizovať dôležité správy',
|
UPGRADE_IMPORTANT_MESSAGES: 'Aktualizovať dôležité správy',
|
||||||
UPGRADE_IMPORTANT_MESSAGES_1: 'Táto aktualizácia vyžaduje reštart základných nastavení. Uistite sa, že ste vytvorili zálohu svojich konfigurácií a nastavení pred pokračovaním a nahrajte ju po instalácii novej verzie.',
|
UPGRADE_IMPORTANT_MESSAGES_1: 'Táto aktualizácia vyžaduje reštart základných nastavení. Uistite sa, že najprv stiahnete systémovú zálohu pred pokračovaním, a potom nahrajte tento súbor po instalácii novej verzie.',
|
||||||
UPGRADE_IMPORTANT_MESSAGES_2: 'Aktualizujete sa na novú hlavnú verziu. Uistite sa, že ste prečítali ChangeLog pre akékoľvek dôležité zmeny.',
|
UPGRADE_IMPORTANT_MESSAGES_2: 'Aktualizujete sa na novú hlavnú verziu. Uistite sa, že ste prečítali ChangeLog pre akékoľvek dôležité zmeny.',
|
||||||
WARNING_SYSTEM_BACKUP: 'Toto vytvorí zálohu všetkých vašich celých systémových konfigurácií a nastavení. Všetky hesla budú čitateľné v zálohovom súbore. Buďte opatrní pri zdieľaní! Chcete pokračovať?'
|
WARNING_SYSTEM_BACKUP: 'Toto vytvorí zálohu všetkých vašich celých systémových konfigurácií a nastavení. Všetky hesla budú čitateľné v zálohovom súbore. Buďte opatrní pri zdieľaní! Chcete pokračovať?'
|
||||||
|
|
||||||
|
|||||||
@@ -350,7 +350,7 @@ const sv: Translation = {
|
|||||||
NO_DATA_1: 'Inga favorit EMS enheter hittade än. Använd',
|
NO_DATA_1: 'Inga favorit EMS enheter hittade än. Använd',
|
||||||
NO_DATA_2: 'modul för att markera dem.',
|
NO_DATA_2: 'modul för att markera dem.',
|
||||||
NO_DATA_3: 'För att se alla tillgängliga enheter, gå till',
|
NO_DATA_3: 'För att se alla tillgängliga enheter, gå till',
|
||||||
NO_GPIO:'Inga tillgängliga GPIO-portar hittades',
|
NO_GPIO: 'Inga tillgängliga GPIO-portar hittades',
|
||||||
THIS_VERSION: 'Denna version',
|
THIS_VERSION: 'Denna version',
|
||||||
PLATFORM: 'Plattform',
|
PLATFORM: 'Plattform',
|
||||||
RELEASE_TYPE: 'Utgivelsestyp',
|
RELEASE_TYPE: 'Utgivelsestyp',
|
||||||
@@ -362,7 +362,7 @@ const sv: Translation = {
|
|||||||
STORED_VERSIONS: 'Lagrad versioner',
|
STORED_VERSIONS: 'Lagrad versioner',
|
||||||
ONLINE_HELP: 'online hjälp',
|
ONLINE_HELP: 'online hjälp',
|
||||||
UPGRADE_IMPORTANT_MESSAGES: 'Uppdatera viktiga meddelanden',
|
UPGRADE_IMPORTANT_MESSAGES: 'Uppdatera viktiga meddelanden',
|
||||||
UPGRADE_IMPORTANT_MESSAGES_1: 'Denna uppdatering kräver en fabriksåterställning. Se till att du har gjort en säkerhetskopia av din konfiguration och inställningar innan du fortsätter och ladda upp denna efter att den nya versionen är installerad.',
|
UPGRADE_IMPORTANT_MESSAGES_1: 'Denna uppdatering kräver en fabriksåterställning. Se till att du först laddar ned en System Backup innan du fortsätter, och ladda upp denna fil efter att den nya versionen är installerad.',
|
||||||
UPGRADE_IMPORTANT_MESSAGES_2: 'Du uppdaterar till en ny huvudversion. Se till att du har läst ChangeLog för eventuella brkande ändringar.',
|
UPGRADE_IMPORTANT_MESSAGES_2: 'Du uppdaterar till en ny huvudversion. Se till att du har läst ChangeLog för eventuella brkande ändringar.',
|
||||||
WARNING_SYSTEM_BACKUP: 'Detta kommer att skapa en säkerhetskopia av din fullständiga systemkonfiguration och inställningar. Alla lösenord kommer att vara läsbara i säkerhetskopien. Var försiktig med att dela! Vill du fortsätta?'
|
WARNING_SYSTEM_BACKUP: 'Detta kommer att skapa en säkerhetskopia av din fullständiga systemkonfiguration och inställningar. Alla lösenord kommer att vara läsbara i säkerhetskopien. Var försiktig med att dela! Vill du fortsätta?'
|
||||||
|
|
||||||
|
|||||||
@@ -350,7 +350,7 @@ const tr: Translation = {
|
|||||||
NO_DATA_1: 'Henüz bir favori EMS varlığı bulunamadı. Kullanın',
|
NO_DATA_1: 'Henüz bir favori EMS varlığı bulunamadı. Kullanın',
|
||||||
NO_DATA_2: 'modülünü kullanın.',
|
NO_DATA_2: 'modülünü kullanın.',
|
||||||
NO_DATA_3: 'Tüm kullanılabilir varlıkları görmek için git',
|
NO_DATA_3: 'Tüm kullanılabilir varlıkları görmek için git',
|
||||||
NO_GPIO:'Kullanılabilir GPIO bulunamadı',
|
NO_GPIO: 'Kullanılabilir GPIO bulunamadı',
|
||||||
THIS_VERSION: 'Bu Sürüm',
|
THIS_VERSION: 'Bu Sürüm',
|
||||||
PLATFORM: 'Platforma',
|
PLATFORM: 'Platforma',
|
||||||
RELEASE_TYPE: 'Sürüm Tipi',
|
RELEASE_TYPE: 'Sürüm Tipi',
|
||||||
|
|||||||
@@ -1,6 +1,20 @@
|
|||||||
import type { InternalRuleItem, ValidateOption } from 'async-validator';
|
import type {
|
||||||
|
InternalRuleItem,
|
||||||
|
ValidateFieldsError,
|
||||||
|
ValidateOption
|
||||||
|
} from 'async-validator';
|
||||||
import type Schema from 'async-validator';
|
import type Schema from 'async-validator';
|
||||||
|
|
||||||
|
export class ValidationError extends Error {
|
||||||
|
readonly fieldErrors: ValidateFieldsError;
|
||||||
|
|
||||||
|
constructor(fieldErrors: ValidateFieldsError) {
|
||||||
|
super('Validation failed');
|
||||||
|
this.name = 'ValidationError';
|
||||||
|
this.fieldErrors = fieldErrors;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const validate = <T extends object>(
|
export const validate = <T extends object>(
|
||||||
validator: Schema,
|
validator: Schema,
|
||||||
source: Partial<T>,
|
source: Partial<T>,
|
||||||
@@ -8,7 +22,7 @@ export const validate = <T extends object>(
|
|||||||
): Promise<T> =>
|
): Promise<T> =>
|
||||||
new Promise((resolve, reject) => {
|
new Promise((resolve, reject) => {
|
||||||
void validator.validate(source, options ?? {}, (errors, fieldErrors) => {
|
void validator.validate(source, options ?? {}, (errors, fieldErrors) => {
|
||||||
errors ? reject(fieldErrors as Error) : resolve(source as T);
|
errors ? reject(new ValidationError(fieldErrors)) : resolve(source as T);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ const REPEAT_CHAR = '=';
|
|||||||
const REPEAT_COUNT = 50;
|
const REPEAT_COUNT = 50;
|
||||||
const DEFAULT_OUT_DIR = 'dist';
|
const DEFAULT_OUT_DIR = 'dist';
|
||||||
const ES_TARGET = 'es2020';
|
const ES_TARGET = 'es2020';
|
||||||
const CHUNK_SIZE_WARNING_LIMIT = 512;
|
const CHUNK_SIZE_WARNING_LIMIT = 1024;
|
||||||
const ASSETS_INLINE_LIMIT = 4096;
|
const ASSETS_INLINE_LIMIT = 4096;
|
||||||
|
|
||||||
// Common resolve aliases
|
// Common resolve aliases
|
||||||
@@ -130,40 +130,9 @@ const createBasePlugins = (
|
|||||||
return plugins;
|
return plugins;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Manual chunk splitting strategy
|
const manualChunks = (id: string): string | undefined => {
|
||||||
const createManualChunks = (detailed = false) => {
|
if (id.includes('node_modules')) return 'vendor';
|
||||||
return (id: string): string | undefined => {
|
return undefined;
|
||||||
if (id.includes('node_modules')) {
|
|
||||||
if (id.includes('preact')) return '@preact';
|
|
||||||
if (detailed) {
|
|
||||||
if (id.includes('react-router')) return '@react-router';
|
|
||||||
if (id.includes('@mui/material')) return '@mui-material';
|
|
||||||
if (id.includes('@mui/icons-material')) return '@mui-icons';
|
|
||||||
if (id.includes('alova')) return '@alova';
|
|
||||||
if (id.includes('typesafe-i18n')) return '@i18n';
|
|
||||||
if (id.includes('react-toastify')) return '@toastify';
|
|
||||||
if (id.includes('@table-library')) return '@table-library';
|
|
||||||
if (id.includes('uuid')) return '@uuid';
|
|
||||||
if (id.includes('axios') || id.includes('fetch')) return '@http';
|
|
||||||
if (id.includes('lodash') || id.includes('ramda')) return '@utils';
|
|
||||||
}
|
|
||||||
return 'vendor';
|
|
||||||
}
|
|
||||||
if (detailed) {
|
|
||||||
// Group circularly dependent modules together to avoid circular chunk warnings
|
|
||||||
// components, app, and utils are tightly coupled, so combine them
|
|
||||||
if (
|
|
||||||
id.includes('components/') ||
|
|
||||||
id.includes('app/') ||
|
|
||||||
id.includes('utils/')
|
|
||||||
) {
|
|
||||||
return 'app';
|
|
||||||
}
|
|
||||||
// Keep api separate as it's typically more independent
|
|
||||||
if (id.includes('api/')) return 'api';
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Common build base configuration
|
// Common build base configuration
|
||||||
@@ -290,7 +259,7 @@ export default defineConfig(
|
|||||||
moduleSideEffects: false
|
moduleSideEffects: false
|
||||||
},
|
},
|
||||||
output: {
|
output: {
|
||||||
manualChunks: createManualChunks(false)
|
manualChunks
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -330,7 +299,7 @@ export default defineConfig(
|
|||||||
chunkFileNames: 'assets/[name]-[hash].js',
|
chunkFileNames: 'assets/[name]-[hash].js',
|
||||||
entryFileNames: 'assets/[name]-[hash].js',
|
entryFileNames: 'assets/[name]-[hash].js',
|
||||||
assetFileNames: 'assets/[name]-[hash].[ext]',
|
assetFileNames: 'assets/[name]-[hash].[ext]',
|
||||||
manualChunks: createManualChunks(true),
|
manualChunks,
|
||||||
sourcemap: false
|
sourcemap: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,10 +49,6 @@ the LICENSE file.
|
|||||||
#define EMC_CLIENTID_LENGTH 23 + 1
|
#define EMC_CLIENTID_LENGTH 23 + 1
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef EMSESP_MQTT_STACKSIZE
|
|
||||||
#define EMC_TASK_STACK_SIZE EMSESP_MQTT_STACKSIZE
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef EMC_TASK_STACK_SIZE
|
#ifndef EMC_TASK_STACK_SIZE
|
||||||
#define EMC_TASK_STACK_SIZE 5120
|
#define EMC_TASK_STACK_SIZE 5120
|
||||||
#endif
|
#endif
|
||||||
@@ -70,10 +66,14 @@ the LICENSE file.
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if EMC_USE_MEMPOOL
|
#if EMC_USE_MEMPOOL
|
||||||
#ifndef EMC_NUM_POOL_ELEMENTS
|
#ifndef EMC_NUM_POOL_ELEMENTS
|
||||||
#define EMC_NUM_POOL_ELEMENTS 32
|
#define EMC_NUM_POOL_ELEMENTS 32
|
||||||
#endif
|
#endif
|
||||||
#ifndef EMC_SIZE_POOL_ELEMENTS
|
#ifndef EMC_SIZE_POOL_ELEMENTS
|
||||||
#define EMC_SIZE_POOL_ELEMENTS 128
|
#define EMC_SIZE_POOL_ELEMENTS 128
|
||||||
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifndef TASMOTA_SDK
|
||||||
|
#define EMC_CLIENT_SECURE
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -62,11 +62,7 @@ MqttClient::MqttClient(espMqttClientTypes::UseInternalTask useInternalTask, uint
|
|||||||
_xSemaphore = xSemaphoreCreateMutex();
|
_xSemaphore = xSemaphoreCreateMutex();
|
||||||
EMC_SEMAPHORE_GIVE(); // release before first use
|
EMC_SEMAPHORE_GIVE(); // release before first use
|
||||||
if (_useInternalTask == espMqttClientTypes::UseInternalTask::YES) {
|
if (_useInternalTask == espMqttClientTypes::UseInternalTask::YES) {
|
||||||
if (core > 1) {
|
xTaskCreatePinnedToCore((TaskFunction_t)_loop, "mqttclient", EMC_TASK_STACK_SIZE, this, priority, &_taskHandle, core);
|
||||||
xTaskCreate((TaskFunction_t)_loop, "mqttclient", EMC_TASK_STACK_SIZE, this, priority, &_taskHandle);
|
|
||||||
} else {
|
|
||||||
xTaskCreatePinnedToCore((TaskFunction_t)_loop, "mqttclient", EMC_TASK_STACK_SIZE, this, priority, &_taskHandle, core);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
(void) useInternalTask;
|
(void) useInternalTask;
|
||||||
@@ -74,7 +70,6 @@ MqttClient::MqttClient(espMqttClientTypes::UseInternalTask useInternalTask, uint
|
|||||||
(void) core;
|
(void) core;
|
||||||
#endif
|
#endif
|
||||||
_clientId = _generatedClientId;
|
_clientId = _generatedClientId;
|
||||||
_core = core;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MqttClient::~MqttClient() {
|
MqttClient::~MqttClient() {
|
||||||
|
|||||||
@@ -69,16 +69,6 @@ class MqttClient {
|
|||||||
const char* getClientId() const;
|
const char* getClientId() const;
|
||||||
size_t queueSize(); // No const because of mutex
|
size_t queueSize(); // No const because of mutex
|
||||||
void loop();
|
void loop();
|
||||||
uint32_t stack() {
|
|
||||||
#ifndef EMSESP_STANDALONE
|
|
||||||
return uxTaskGetStackHighWaterMark(_taskHandle);
|
|
||||||
#else
|
|
||||||
return 0;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
uint8_t core() {
|
|
||||||
return _core;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
explicit MqttClient(espMqttClientTypes::UseInternalTask useInternalTask, uint8_t priority = 1, uint8_t core = 1);
|
explicit MqttClient(espMqttClientTypes::UseInternalTask useInternalTask, uint8_t priority = 1, uint8_t core = 1);
|
||||||
@@ -108,7 +98,6 @@ class MqttClient {
|
|||||||
uint8_t _willQos;
|
uint8_t _willQos;
|
||||||
bool _willRetain;
|
bool _willRetain;
|
||||||
uint32_t _timeout;
|
uint32_t _timeout;
|
||||||
uint8_t _core;
|
|
||||||
|
|
||||||
// state is protected to allow state changes by the transport system, defined in child classes
|
// state is protected to allow state changes by the transport system, defined in child classes
|
||||||
// eg. to allow AsyncTCP
|
// eg. to allow AsyncTCP
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ the LICENSE file.
|
|||||||
|
|
||||||
#include "ClientPosix.h"
|
#include "ClientPosix.h"
|
||||||
|
|
||||||
#if defined(__linux__) || defined(__APPLE__)
|
#if defined(__linux__)
|
||||||
|
|
||||||
namespace espMqttClientInternals {
|
namespace espMqttClientInternals {
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ the LICENSE file.
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#if defined(__linux__) || defined(__APPLE__)
|
#if defined(__linux__)
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|||||||
@@ -6,65 +6,66 @@ For a copy, see <https://opensource.org/licenses/MIT> or
|
|||||||
the LICENSE file.
|
the LICENSE file.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef NO_TLS_SUPPORT
|
#if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32)
|
||||||
|
|
||||||
#include "ClientSecureSync.h"
|
#include "ClientSecureSync.h"
|
||||||
#include <lwip/sockets.h>
|
#include <lwip/sockets.h> // socket options
|
||||||
#include "../Config.h"
|
|
||||||
|
|
||||||
namespace espMqttClientInternals {
|
namespace espMqttClientInternals {
|
||||||
|
|
||||||
ClientSecureSync::ClientSecureSync()
|
ClientSecureSync::ClientSecureSync()
|
||||||
: client() {
|
: client() {
|
||||||
client.setClient(&basic_client, true);
|
// empty
|
||||||
client.setBufferSizes(EMC_RX_BUFFER_SIZE, EMC_TX_BUFFER_SIZE);
|
|
||||||
client.setSessionTimeout(120); // Set the timeout in seconds (>=120 seconds)
|
|
||||||
}
|
|
||||||
|
|
||||||
ClientSecureSync::~ClientSecureSync() {
|
|
||||||
stop();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ClientSecureSync::connect(IPAddress ip, uint16_t port) {
|
bool ClientSecureSync::connect(IPAddress ip, uint16_t port) {
|
||||||
bool ret = client.connect(ip, port); // implicit conversion of return code int --> bool
|
bool ret = client.connect(ip, port); // implicit conversion of return code int --> bool
|
||||||
if (ret) {
|
if (ret) {
|
||||||
// Set TCP option directly to bypass lack of working setNoDelay for WiFiClientSecure
|
#if defined(ARDUINO_ARCH_ESP8266)
|
||||||
int val = true;
|
client.setNoDelay(true);
|
||||||
basic_client.setSocketOption(IPPROTO_TCP, TCP_NODELAY, &val, sizeof(int));
|
#elif defined(ARDUINO_ARCH_ESP32)
|
||||||
}
|
// Set TCP option directly to bypass lack of working setNoDelay for WiFiClientSecure
|
||||||
return ret;
|
int val = true;
|
||||||
|
client.setSocketOption(IPPROTO_TCP, TCP_NODELAY, &val, sizeof(int));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ClientSecureSync::connect(const char * host, uint16_t port) {
|
bool ClientSecureSync::connect(const char* host, uint16_t port) {
|
||||||
bool ret = client.connect(host, port); // implicit conversion of return code int --> bool
|
bool ret = client.connect(host, port); // implicit conversion of return code int --> bool
|
||||||
if (ret) {
|
if (ret) {
|
||||||
// Set TCP option directly to bypass lack of working setNoDelay for WiFiClientSecure
|
#if defined(ARDUINO_ARCH_ESP8266)
|
||||||
int val = true;
|
client.setNoDelay(true);
|
||||||
basic_client.setSocketOption(IPPROTO_TCP, TCP_NODELAY, &val, sizeof(int));
|
#elif defined(ARDUINO_ARCH_ESP32)
|
||||||
}
|
// Set TCP option directly to bypass lack of working setNoDelay for WiFiClientSecure
|
||||||
return ret;
|
int val = true;
|
||||||
|
client.setSocketOption(IPPROTO_TCP, TCP_NODELAY, &val, sizeof(int));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t ClientSecureSync::write(const uint8_t * buf, size_t size) {
|
size_t ClientSecureSync::write(const uint8_t* buf, size_t size) {
|
||||||
return client.write(buf, size);
|
return client.write(buf, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
int ClientSecureSync::read(uint8_t * buf, size_t size) {
|
int ClientSecureSync::read(uint8_t* buf, size_t size) {
|
||||||
return client.read(buf, size);
|
return client.read(buf, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ClientSecureSync::stop() {
|
void ClientSecureSync::stop() {
|
||||||
client.stop();
|
client.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ClientSecureSync::connected() {
|
bool ClientSecureSync::connected() {
|
||||||
return client.connected();
|
return client.connected();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ClientSecureSync::disconnected() {
|
bool ClientSecureSync::disconnected() {
|
||||||
return !client.connected();
|
return !client.connected();
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace espMqttClientInternals
|
} // namespace espMqttClientInternals
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
@@ -8,11 +8,15 @@ the LICENSE file.
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#ifndef NO_TLS_SUPPORT
|
#if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32)
|
||||||
|
|
||||||
// #include "esp_tls.h"
|
// Added for EMS-ESP
|
||||||
|
#include "../Config.h"
|
||||||
|
#if defined(EMC_CLIENT_SECURE)
|
||||||
|
#include <WiFiClientSecure.h> // includes IPAddress
|
||||||
|
#else
|
||||||
#include <WiFiClient.h>
|
#include <WiFiClient.h>
|
||||||
#include <ESP_SSLClient.h>
|
#endif
|
||||||
#include "Transport.h"
|
#include "Transport.h"
|
||||||
|
|
||||||
namespace espMqttClientInternals {
|
namespace espMqttClientInternals {
|
||||||
@@ -20,7 +24,6 @@ namespace espMqttClientInternals {
|
|||||||
class ClientSecureSync : public Transport {
|
class ClientSecureSync : public Transport {
|
||||||
public:
|
public:
|
||||||
ClientSecureSync();
|
ClientSecureSync();
|
||||||
~ClientSecureSync();
|
|
||||||
bool connect(IPAddress ip, uint16_t port) override;
|
bool connect(IPAddress ip, uint16_t port) override;
|
||||||
bool connect(const char * host, uint16_t port) override;
|
bool connect(const char * host, uint16_t port) override;
|
||||||
size_t write(const uint8_t * buf, size_t size) override;
|
size_t write(const uint8_t * buf, size_t size) override;
|
||||||
@@ -28,9 +31,12 @@ class ClientSecureSync : public Transport {
|
|||||||
void stop() override;
|
void stop() override;
|
||||||
bool connected() override;
|
bool connected() override;
|
||||||
bool disconnected() override;
|
bool disconnected() override;
|
||||||
|
// added for EMS-ESP
|
||||||
WiFiClient basic_client;
|
#if defined(EMC_CLIENT_SECURE)
|
||||||
ESP_SSLClient client;
|
WiFiClientSecure client;
|
||||||
|
#else
|
||||||
|
WiFiClient client;
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace espMqttClientInternals
|
} // namespace espMqttClientInternals
|
||||||
|
|||||||
@@ -8,6 +8,50 @@ the LICENSE file.
|
|||||||
|
|
||||||
#include "espMqttClient.h"
|
#include "espMqttClient.h"
|
||||||
|
|
||||||
|
#if defined(ARDUINO_ARCH_ESP8266)
|
||||||
|
espMqttClient::espMqttClient()
|
||||||
|
: MqttClientSetup(espMqttClientTypes::UseInternalTask::NO)
|
||||||
|
, _client() {
|
||||||
|
_transport = &_client;
|
||||||
|
}
|
||||||
|
|
||||||
|
espMqttClientSecure::espMqttClientSecure()
|
||||||
|
: MqttClientSetup(espMqttClientTypes::UseInternalTask::NO)
|
||||||
|
, _client() {
|
||||||
|
_transport = &_client;
|
||||||
|
}
|
||||||
|
|
||||||
|
espMqttClientSecure & espMqttClientSecure::setInsecure() {
|
||||||
|
_client.client.setInsecure();
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
espMqttClientSecure & espMqttClientSecure::setFingerprint(const uint8_t fingerprint[20]) {
|
||||||
|
_client.client.setFingerprint(fingerprint);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
espMqttClientSecure & espMqttClientSecure::setTrustAnchors(const X509List * ta) {
|
||||||
|
_client.client.setTrustAnchors(ta);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
espMqttClientSecure & espMqttClientSecure::setClientRSACert(const X509List * cert, const PrivateKey * sk) {
|
||||||
|
_client.client.setClientRSACert(cert, sk);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
espMqttClientSecure & espMqttClientSecure::setClientECCert(const X509List * cert, const PrivateKey * sk, unsigned allowed_usages, unsigned cert_issuer_key_type) {
|
||||||
|
_client.client.setClientECCert(cert, sk, allowed_usages, cert_issuer_key_type);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
espMqttClientSecure & espMqttClientSecure::setCertStore(CertStoreBase * certStore) {
|
||||||
|
_client.client.setCertStore(certStore);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
#if defined(ARDUINO_ARCH_ESP32)
|
#if defined(ARDUINO_ARCH_ESP32)
|
||||||
espMqttClient::espMqttClient(espMqttClientTypes::UseInternalTask useInternalTask)
|
espMqttClient::espMqttClient(espMqttClientTypes::UseInternalTask useInternalTask)
|
||||||
: MqttClientSetup(useInternalTask)
|
: MqttClientSetup(useInternalTask)
|
||||||
@@ -34,45 +78,51 @@ espMqttClientSecure::espMqttClientSecure(uint8_t priority, uint8_t core)
|
|||||||
}
|
}
|
||||||
|
|
||||||
espMqttClientSecure & espMqttClientSecure::setInsecure() {
|
espMqttClientSecure & espMqttClientSecure::setInsecure() {
|
||||||
#ifndef NO_TLS_SUPPORT
|
#if defined(EMC_CLIENT_SECURE)
|
||||||
_client.client.setInsecure();
|
_client.client.setInsecure();
|
||||||
#endif
|
#endif
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
espMqttClientSecure & espMqttClientSecure::setCACert(const char * rootCA) {
|
espMqttClientSecure & espMqttClientSecure::setCACert(const char * rootCA) {
|
||||||
#ifndef NO_TLS_SUPPORT
|
#if defined(EMC_CLIENT_SECURE)
|
||||||
_client.client.setCACert(rootCA);
|
_client.client.setCACert(rootCA);
|
||||||
#endif
|
#endif
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
espMqttClientSecure & espMqttClientSecure::setCertificate(const char * clientCa) {
|
espMqttClientSecure & espMqttClientSecure::setCertificate(const char * clientCa) {
|
||||||
#ifndef NO_TLS_SUPPORT
|
#if defined(EMC_CLIENT_SECURE)
|
||||||
_client.client.setCertificate(clientCa);
|
_client.client.setCertificate(clientCa);
|
||||||
#endif
|
#endif
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
espMqttClientSecure & espMqttClientSecure::setPrivateKey(const char * privateKey) {
|
espMqttClientSecure & espMqttClientSecure::setPrivateKey(const char * privateKey) {
|
||||||
#ifndef NO_TLS_SUPPORT
|
#if defined(EMC_CLIENT_SECURE)
|
||||||
_client.client.setPrivateKey(privateKey);
|
_client.client.setPrivateKey(privateKey);
|
||||||
#endif
|
#endif
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
espMqttClientSecure & espMqttClientSecure::setPreSharedKey(const char * pskIdent, const char * psKey) {
|
espMqttClientSecure & espMqttClientSecure::setPreSharedKey(const char * pskIdent, const char * psKey) {
|
||||||
#ifndef NO_TLS_SUPPORT
|
#if defined(EMC_CLIENT_SECURE)
|
||||||
|
_client.client.setPreSharedKey(pskIdent, psKey);
|
||||||
#endif
|
#endif
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(__linux__) || defined(__APPLE__)
|
#if defined(__linux__)
|
||||||
espMqttClient::espMqttClient()
|
espMqttClient::espMqttClient()
|
||||||
: MqttClientSetup(espMqttClientTypes::UseInternalTask::NO)
|
: MqttClientSetup(espMqttClientTypes::UseInternalTask::NO)
|
||||||
, _client() {
|
, _client() {
|
||||||
_transport = &_client;
|
_transport = &_client;
|
||||||
}
|
}
|
||||||
|
#elif defined(_WIN32) || defined(__APPLE__)
|
||||||
|
// Windows
|
||||||
|
espMqttClient::espMqttClient()
|
||||||
|
: MqttClientSetup(espMqttClientTypes::UseInternalTask::NO) {
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ the LICENSE file.
|
|||||||
#if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32)
|
#if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32)
|
||||||
#include "Transport/ClientSync.h"
|
#include "Transport/ClientSync.h"
|
||||||
#include "Transport/ClientSecureSync.h"
|
#include "Transport/ClientSecureSync.h"
|
||||||
#elif defined(__linux__) || defined(__APPLE__)
|
#elif defined(__linux__)
|
||||||
#include "Transport/ClientPosix.h"
|
#include "Transport/ClientPosix.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -65,16 +65,10 @@ class espMqttClientSecure : public MqttClientSetup<espMqttClientSecure> {
|
|||||||
espMqttClientSecure & setPreSharedKey(const char * pskIdent, const char * psKey);
|
espMqttClientSecure & setPreSharedKey(const char * pskIdent, const char * psKey);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
#ifndef NO_TLS_SUPPORT
|
|
||||||
espMqttClientInternals::ClientSecureSync _client;
|
espMqttClientInternals::ClientSecureSync _client;
|
||||||
#else
|
|
||||||
espMqttClientInternals::ClientSync _client;
|
|
||||||
#endif
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#elif defined(__linux__)
|
||||||
|
|
||||||
#if defined(__linux__) || defined(__APPLE__)
|
|
||||||
class espMqttClient : public MqttClientSetup<espMqttClient> {
|
class espMqttClient : public MqttClientSetup<espMqttClient> {
|
||||||
public:
|
public:
|
||||||
espMqttClient();
|
espMqttClient();
|
||||||
@@ -82,4 +76,10 @@ class espMqttClient : public MqttClientSetup<espMqttClient> {
|
|||||||
protected:
|
protected:
|
||||||
espMqttClientInternals::ClientPosix _client;
|
espMqttClientInternals::ClientPosix _client;
|
||||||
};
|
};
|
||||||
|
#elif defined(_WIN32) || defined(__APPLE__)
|
||||||
|
class espMqttClient : public MqttClientSetup<espMqttClient> {
|
||||||
|
public:
|
||||||
|
espMqttClient();
|
||||||
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
The MIT License (MIT)
|
|
||||||
|
|
||||||
Copyright (c) 2015 Marko Živanović
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
# About
|
|
||||||
|
|
||||||
This project is MIT-licensed, C++14 implementation of [semantic versioning](http://semver.org) parser and comparator with support for modifying parsed version strings. Semantic versioning 2.0.0 specification is supported out-of-the-box and the code should be flexible-enough to support future revisions or other similar versioning schemes.
|
|
||||||
|
|
||||||
Copyright (c) 2015 Marko Zivanovic
|
|
||||||
|
|
||||||
Based on https://github.com/zmarko/semver
|
|
||||||
@@ -1,121 +0,0 @@
|
|||||||
/*
|
|
||||||
The MIT License (MIT)
|
|
||||||
|
|
||||||
Copyright (c) 2015 Marko Zivanovic
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <functional>
|
|
||||||
#include <map>
|
|
||||||
#include "semver200.h"
|
|
||||||
|
|
||||||
using namespace std;
|
|
||||||
|
|
||||||
namespace version {
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
// Compare normal version identifiers.
|
|
||||||
int compare_normal(const Version_data & l, const Version_data & r) {
|
|
||||||
if (l.major > r.major)
|
|
||||||
return 1;
|
|
||||||
if (l.major < r.major)
|
|
||||||
return -1;
|
|
||||||
if (l.minor > r.minor)
|
|
||||||
return 1;
|
|
||||||
if (l.minor < r.minor)
|
|
||||||
return -1;
|
|
||||||
if (l.patch > r.patch)
|
|
||||||
return 1;
|
|
||||||
if (l.patch < r.patch)
|
|
||||||
return -1;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compare alphanumeric prerelease identifiers.
|
|
||||||
inline int cmp_alnum_prerel_ids(const string & l, const string & r) {
|
|
||||||
auto cmp = l.compare(r);
|
|
||||||
if (cmp == 0) {
|
|
||||||
return cmp;
|
|
||||||
} else {
|
|
||||||
return cmp > 0 ? 1 : -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compare numeric prerelease identifiers.
|
|
||||||
inline int cmp_num_prerel_ids(const string & l, const string & r) {
|
|
||||||
long long li = stoll(l);
|
|
||||||
long long ri = stoll(r);
|
|
||||||
if (li == ri)
|
|
||||||
return 0;
|
|
||||||
return li > ri ? 1 : -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
using Prerel_type_pair = pair<Id_type, Id_type>;
|
|
||||||
using Prerel_id_comparator = function<int(const string &, const string &)>;
|
|
||||||
const map<Prerel_type_pair, Prerel_id_comparator> comparators = {{{Id_type::alnum, Id_type::alnum}, cmp_alnum_prerel_ids},
|
|
||||||
{{Id_type::alnum, Id_type::num}, [](const string &, const string &) { return 1; }},
|
|
||||||
{{Id_type::num, Id_type::alnum}, [](const string &, const string &) { return -1; }},
|
|
||||||
{{Id_type::num, Id_type::num}, cmp_num_prerel_ids}};
|
|
||||||
|
|
||||||
// Compare prerelease identifiers based on their types.
|
|
||||||
inline int compare_prerel_identifiers(const Prerelease_identifier & l, const Prerelease_identifier & r) {
|
|
||||||
auto cmp = comparators.at({l.second, r.second});
|
|
||||||
return cmp(l.first, r.first);
|
|
||||||
}
|
|
||||||
|
|
||||||
inline int cmp_rel_prerel(const Prerelease_identifiers & l, const Prerelease_identifiers & r) {
|
|
||||||
if (l.empty() && !r.empty())
|
|
||||||
return 1;
|
|
||||||
if (r.empty() && !l.empty())
|
|
||||||
return -1;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
int Semver200_comparator::compare(const Version_data & l, const Version_data & r) const {
|
|
||||||
// Compare normal version components.
|
|
||||||
int cmp = compare_normal(l, r);
|
|
||||||
if (cmp != 0)
|
|
||||||
return cmp;
|
|
||||||
|
|
||||||
// Compare if one version is release and the other prerelease - release is always higher.
|
|
||||||
cmp = cmp_rel_prerel(l.prerelease_ids, r.prerelease_ids);
|
|
||||||
if (cmp != 0)
|
|
||||||
return cmp;
|
|
||||||
|
|
||||||
// Compare prerelease by looking at each identifier: numeric ones are compared as numbers,
|
|
||||||
// alphanum as ASCII strings.
|
|
||||||
auto shorter = min(l.prerelease_ids.size(), r.prerelease_ids.size());
|
|
||||||
for (size_t i = 0; i < shorter; i++) {
|
|
||||||
cmp = compare_prerel_identifiers(l.prerelease_ids[i], r.prerelease_ids[i]);
|
|
||||||
if (cmp != 0)
|
|
||||||
return cmp;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prerelease identifiers are the same, to the length of the shorter version string;
|
|
||||||
// if they are the same length, then versions are equal, otherwise, longer one wins.
|
|
||||||
if (l.prerelease_ids.size() == r.prerelease_ids.size())
|
|
||||||
return 0;
|
|
||||||
return l.prerelease_ids.size() > r.prerelease_ids.size() ? 1 : -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace version
|
|
||||||
@@ -1,187 +0,0 @@
|
|||||||
/*
|
|
||||||
The MIT License (MIT)
|
|
||||||
|
|
||||||
Copyright (c) 2015 Marko Zivanovic
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <functional>
|
|
||||||
#include <map>
|
|
||||||
#include "semver200.h"
|
|
||||||
|
|
||||||
using namespace std;
|
|
||||||
|
|
||||||
namespace version {
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
enum class Parser_state { major, minor, patch, prerelease, build };
|
|
||||||
|
|
||||||
using Validator = function<void(const string &, const char)>;
|
|
||||||
using State_transition_hook = function<void(string &)>;
|
|
||||||
/// State transition is described by a character that triggers it, a state to transition to and
|
|
||||||
/// optional hook to be invoked on transition.
|
|
||||||
using Transition = tuple<const char, Parser_state, State_transition_hook>;
|
|
||||||
using Transitions = vector<Transition>;
|
|
||||||
using State = tuple<Transitions, string &, Validator>;
|
|
||||||
using State_machine = std::map<Parser_state, State>;
|
|
||||||
|
|
||||||
// Ranges of characters allowed in prerelease and build identifiers.
|
|
||||||
const vector<pair<char, char>> allowed_prerel_id_chars = {{'0', '9'}, {'A', 'Z'}, {'a', 'z'}, {'-', '-'}};
|
|
||||||
|
|
||||||
inline Transition mkx(const char c, Parser_state p, State_transition_hook pth) {
|
|
||||||
return make_tuple(c, p, pth);
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void Parse_error(const std::string & s) {
|
|
||||||
// EMSESP::logger().err("parse error: %s", s.c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Advance parser state machine by a single step.
|
|
||||||
/**
|
|
||||||
Perform single step of parser state machine: if character matches one from transition tables -
|
|
||||||
trigger transition to next state; otherwise, validate if current token is in legal state
|
|
||||||
(throw Parse_error if not) and then add character to current token; State transition includes
|
|
||||||
preparing various vars for next state and invoking state transition hook (if specified) which is
|
|
||||||
where whole tokens are validated.
|
|
||||||
*/
|
|
||||||
inline void process_char(const char c, Parser_state & cstate, Parser_state & pstate, const Transitions & transitions, string & target, Validator validate) {
|
|
||||||
for (const auto & transition : transitions) {
|
|
||||||
if (c == get<0>(transition)) {
|
|
||||||
if (get<2>(transition))
|
|
||||||
get<2>(transition)(target);
|
|
||||||
pstate = cstate;
|
|
||||||
cstate = get<1>(transition);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
validate(target, c);
|
|
||||||
target.push_back(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Validate normal (major, minor, patch) version components.
|
|
||||||
inline void normal_version_validator(const string & tgt, const char c) {
|
|
||||||
if (c < '0' || c > '9')
|
|
||||||
Parse_error("invalid character encountered: " + string(1, c));
|
|
||||||
if (tgt.compare(0, 1, "0") == 0)
|
|
||||||
Parse_error("leading 0 not allowed");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Validate that prerelease and build version identifiers are comprised of allowed chars only.
|
|
||||||
inline void prerelease_version_validator(const string &, const char c) {
|
|
||||||
bool res = false;
|
|
||||||
for (const auto & r : allowed_prerel_id_chars) {
|
|
||||||
res |= (c >= r.first && c <= r.second);
|
|
||||||
}
|
|
||||||
if (!res)
|
|
||||||
Parse_error("invalid character encountered: " + string(1, c));
|
|
||||||
}
|
|
||||||
|
|
||||||
inline bool is_identifier_numeric(const string & id) {
|
|
||||||
return id.find_first_not_of("0123456789") == string::npos;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline bool check_for_leading_0(const string & str) {
|
|
||||||
return str.length() > 1 && str[0] == '0';
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Validate every individual prerelease identifier, determine it's type and add it to collection.
|
|
||||||
void prerelease_hook_impl(string & id, Prerelease_identifiers & prerelease) {
|
|
||||||
if (id.empty())
|
|
||||||
Parse_error("version identifier cannot be empty");
|
|
||||||
Id_type t = Id_type::alnum;
|
|
||||||
if (is_identifier_numeric(id)) {
|
|
||||||
t = Id_type::num;
|
|
||||||
if (check_for_leading_0(id)) {
|
|
||||||
Parse_error("numeric identifiers cannot have leading 0");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
prerelease.push_back(Prerelease_identifier(id, t));
|
|
||||||
id.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Validate every individual build identifier and add it to collection.
|
|
||||||
void build_hook_impl(string & id, Parser_state & pstate, Build_identifiers & build, std::string & prerelease_id, Prerelease_identifiers & prerelease) {
|
|
||||||
// process last token left from parsing prerelease data
|
|
||||||
if (pstate == Parser_state::prerelease)
|
|
||||||
prerelease_hook_impl(prerelease_id, prerelease);
|
|
||||||
if (id.empty())
|
|
||||||
Parse_error("version identifier cannot be empty");
|
|
||||||
build.push_back(id);
|
|
||||||
id.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
/// Parse semver 2.0.0-compatible string to Version_data structure.
|
|
||||||
/**
|
|
||||||
Version text parser is implemented as a state machine. In each step one successive character from version
|
|
||||||
string is consumed and is either added to current token or triggers state transition. Hooks can be
|
|
||||||
injected into state transitions for validation/customization purposes.
|
|
||||||
*/
|
|
||||||
Version_data Semver200_parser::parse(const string & s) const {
|
|
||||||
string major;
|
|
||||||
string minor;
|
|
||||||
string patch;
|
|
||||||
string prerelease_id;
|
|
||||||
string build_id;
|
|
||||||
Prerelease_identifiers prerelease;
|
|
||||||
Build_identifiers build;
|
|
||||||
Parser_state cstate{Parser_state::major};
|
|
||||||
Parser_state pstate;
|
|
||||||
|
|
||||||
auto prerelease_hook = [&](string & id) { prerelease_hook_impl(id, prerelease); };
|
|
||||||
|
|
||||||
auto build_hook = [&](string & id) { build_hook_impl(id, pstate, build, prerelease_id, prerelease); };
|
|
||||||
|
|
||||||
// State transition tables
|
|
||||||
auto major_trans = {mkx('.', Parser_state::minor, {})};
|
|
||||||
auto minor_trans = {mkx('.', Parser_state::patch, {})};
|
|
||||||
auto patch_trans = {mkx('-', Parser_state::prerelease, {}), mkx('+', Parser_state::build, {})};
|
|
||||||
auto prerelease_trans = {// When identifier separator (.) is found, stay in the same state but invoke hook
|
|
||||||
// in order to process each individual identifier separately.
|
|
||||||
mkx('.', Parser_state::prerelease, prerelease_hook),
|
|
||||||
mkx('+', Parser_state::build, {})};
|
|
||||||
auto build_trans = {// Same stay-in-the-same-state-but-invoke-hook trick from above.
|
|
||||||
mkx('.', Parser_state::build, build_hook)};
|
|
||||||
|
|
||||||
State_machine state_machine = {{Parser_state::major, State{major_trans, major, normal_version_validator}},
|
|
||||||
{Parser_state::minor, State{minor_trans, minor, normal_version_validator}},
|
|
||||||
{Parser_state::patch, State{patch_trans, patch, normal_version_validator}},
|
|
||||||
{Parser_state::prerelease, State{prerelease_trans, prerelease_id, prerelease_version_validator}},
|
|
||||||
{Parser_state::build, State{build_trans, build_id, prerelease_version_validator}}};
|
|
||||||
|
|
||||||
// Main loop.
|
|
||||||
for (const auto & c : s) {
|
|
||||||
auto state = state_machine.at(cstate);
|
|
||||||
process_char(c, cstate, pstate, get<0>(state), get<1>(state), get<2>(state));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Trigger appropriate hooks in order to process last token, because no state transition was
|
|
||||||
// triggered for it.
|
|
||||||
if (cstate == Parser_state::prerelease) {
|
|
||||||
prerelease_hook(prerelease_id);
|
|
||||||
} else if (cstate == Parser_state::build) {
|
|
||||||
build_hook(build_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Version_data{stoi(major), stoi(minor), stoi(patch), prerelease, build};
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace version
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
/*
|
|
||||||
The MIT License (MIT)
|
|
||||||
|
|
||||||
Copyright (c) 2015 Marko Zivanovic
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "version.h"
|
|
||||||
|
|
||||||
namespace version {
|
|
||||||
|
|
||||||
/// Parse string into Version_data structure according to semantic versioning 2.0.0 rules.
|
|
||||||
struct Semver200_parser {
|
|
||||||
Version_data parse(const std::string &) const;
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Compare Version_data to another using semantic versioning 2.0.0 rules.
|
|
||||||
struct Semver200_comparator {
|
|
||||||
int compare(const Version_data &, const Version_data &) const;
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Concrete version class that binds all semver 2.0.0 functionality together.
|
|
||||||
class Semver200_version : public Basic_version<Semver200_parser, Semver200_comparator> {
|
|
||||||
public:
|
|
||||||
Semver200_version()
|
|
||||||
: Basic_version{Semver200_parser(), Semver200_comparator()} {
|
|
||||||
}
|
|
||||||
|
|
||||||
Semver200_version(const std::string & v)
|
|
||||||
: Basic_version{v, Semver200_parser(), Semver200_comparator()} {
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace version
|
|
||||||
@@ -1,149 +0,0 @@
|
|||||||
/*
|
|
||||||
The MIT License (MIT)
|
|
||||||
|
|
||||||
Copyright (c) 2015 Marko Zivanovic
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace version {
|
|
||||||
|
|
||||||
/// Type of prerelease identifier: alphanumeric or numeric.
|
|
||||||
/**
|
|
||||||
Type of identifier affects comparison: alphanumeric identifiers are compared as ASCII strings, while
|
|
||||||
numeric identifiers are compared as numbers.
|
|
||||||
*/
|
|
||||||
enum class Id_type {
|
|
||||||
alnum, ///< Identifier is alphanumerical
|
|
||||||
num ///< Identifier is numeric
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Container for prerelease identifier value and it's type.
|
|
||||||
/**
|
|
||||||
Prerelease version string consist of an optional series of dot-separated identifiers.
|
|
||||||
These identifiers can be either numerical or alphanumerical.
|
|
||||||
This structure describes one such identifier.
|
|
||||||
*/
|
|
||||||
using Prerelease_identifier = std::pair<std::string, Id_type>;
|
|
||||||
|
|
||||||
/// Container for all prerelease identifiers for a given version string.
|
|
||||||
using Prerelease_identifiers = std::vector<Prerelease_identifier>;
|
|
||||||
|
|
||||||
/// Build identifier is arbitrary string with no special meaning with regards to version precedence.
|
|
||||||
using Build_identifier = std::string;
|
|
||||||
|
|
||||||
/// Container for all build identifiers of a given version string.
|
|
||||||
using Build_identifiers = std::vector<Build_identifier>;
|
|
||||||
|
|
||||||
/// Description of version broken into parts, as per semantic versioning specification.
|
|
||||||
struct Version_data {
|
|
||||||
Version_data(const int M, const int m, const int p, const Prerelease_identifiers & pr, const Build_identifiers & b)
|
|
||||||
: major{M}
|
|
||||||
, minor{m}
|
|
||||||
, patch{p}
|
|
||||||
, prerelease_ids{pr}
|
|
||||||
, build_ids{b} {
|
|
||||||
}
|
|
||||||
|
|
||||||
int major; ///< Major version, change only on incompatible API modifications.
|
|
||||||
int minor; ///< Minor version, change on backwards-compatible API modifications.
|
|
||||||
int patch; ///< Patch version, change only on bugfixes.
|
|
||||||
|
|
||||||
/// Optional series of prerelease identifiers.
|
|
||||||
Prerelease_identifiers prerelease_ids;
|
|
||||||
|
|
||||||
/// Optional series of build identifiers.
|
|
||||||
Build_identifiers build_ids;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Forward declaration required for operators' template declarations.
|
|
||||||
template <typename Parser, typename Comparator>
|
|
||||||
class Basic_version;
|
|
||||||
|
|
||||||
/// Test if left-hand version operand is of lower precedence than the right-hand version.
|
|
||||||
template <typename Parser, typename Comparator>
|
|
||||||
bool operator<(const Basic_version<Parser, Comparator> &, const Basic_version<Parser, Comparator> &);
|
|
||||||
|
|
||||||
/// Test if left-hand version operand if of equal precedence as the right-hand version.
|
|
||||||
template <typename Parser, typename Comparator>
|
|
||||||
bool operator==(const Basic_version<Parser, Comparator> &, const Basic_version<Parser, Comparator> &);
|
|
||||||
|
|
||||||
/// Test if left-hand version and right-hand version are of different precedence.
|
|
||||||
template <typename Parser, typename Comparator>
|
|
||||||
bool operator!=(const Basic_version<Parser, Comparator> &, const Basic_version<Parser, Comparator> &);
|
|
||||||
|
|
||||||
/// Test if left-hand version operand is of higher precedence than the right-hand version.
|
|
||||||
template <typename Parser, typename Comparator>
|
|
||||||
bool operator>(const Basic_version<Parser, Comparator> &, const Basic_version<Parser, Comparator> &);
|
|
||||||
|
|
||||||
/// Test if left-hand version operand is of higher or equal precedence as the right-hand version.
|
|
||||||
template <typename Parser, typename Comparator>
|
|
||||||
bool operator>=(const Basic_version<Parser, Comparator> &, const Basic_version<Parser, Comparator> &);
|
|
||||||
|
|
||||||
/// Test if left-hand version operand is of lower or equal precedence as the right-hand version.
|
|
||||||
template <typename Parser, typename Comparator>
|
|
||||||
bool operator<=(const Basic_version<Parser, Comparator> &, const Basic_version<Parser, Comparator> &);
|
|
||||||
|
|
||||||
/// Base class for various version parsing, precedence ordering and data manipulation schemes.
|
|
||||||
/**
|
|
||||||
Basic_version class describes general version object without prescribing parsing,
|
|
||||||
validation, comparison and modification rules. These rules are implemented by supplied Parser, Comparator
|
|
||||||
and Modifier objects.
|
|
||||||
*/
|
|
||||||
template <typename Parser, typename Comparator>
|
|
||||||
class Basic_version {
|
|
||||||
public:
|
|
||||||
/// Construct Basic_version object using Parser object to parse default ("0.0.0") version string, Comparator for comparison and Modifier for modification.
|
|
||||||
Basic_version(Parser, Comparator);
|
|
||||||
|
|
||||||
/// Construct Basic_version object using Parser to parse supplied version string, Comparator for comparison and Modifier for modification.
|
|
||||||
Basic_version(const std::string &, Parser, Comparator);
|
|
||||||
|
|
||||||
/// Construct Basic_version object using supplied Version_data, Parser, Comparator and Modifier objects.
|
|
||||||
Basic_version(const Version_data &, Parser, Comparator);
|
|
||||||
|
|
||||||
/// Construct Basic_version by copying data from another one.
|
|
||||||
Basic_version(const Basic_version &);
|
|
||||||
|
|
||||||
/// Copy version data from another Basic_version to this one.
|
|
||||||
Basic_version & operator=(const Basic_version &);
|
|
||||||
|
|
||||||
int major() const; ///< Get major version.
|
|
||||||
int minor() const; ///< Get minor version.
|
|
||||||
int patch() const; ///< Get patch version.
|
|
||||||
const std::string prerelease() const; ///< Get prerelease version string.
|
|
||||||
const std::string build() const; ///< Get build version string.
|
|
||||||
|
|
||||||
friend bool operator< <>(const Basic_version &, const Basic_version &);
|
|
||||||
friend bool operator==<>(const Basic_version &, const Basic_version &);
|
|
||||||
|
|
||||||
private:
|
|
||||||
Parser parser_;
|
|
||||||
Comparator comparator_;
|
|
||||||
Version_data ver_;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace version
|
|
||||||
|
|
||||||
#include "version.inl"
|
|
||||||
@@ -1,132 +0,0 @@
|
|||||||
/*
|
|
||||||
The MIT License (MIT)
|
|
||||||
|
|
||||||
Copyright (c) 2015 Marko Zivanovic
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "version.h"
|
|
||||||
|
|
||||||
namespace version {
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
/// Utility function to splice all vector elements to output stream, using designated separator
|
|
||||||
/// between elements and function object for getting values from vector elements.
|
|
||||||
template <typename T, typename F>
|
|
||||||
std::string & splice(std::string & ss, const std::vector<T> & vec, const std::string & sep, F read) {
|
|
||||||
if (!vec.empty()) {
|
|
||||||
for (auto it = vec.cbegin(); it < vec.cend() - 1; ++it) {
|
|
||||||
ss += read(*it) + sep;
|
|
||||||
}
|
|
||||||
ss += read(*vec.crbegin());
|
|
||||||
}
|
|
||||||
return ss;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
template <typename Parser, typename Comparator>
|
|
||||||
Basic_version<Parser, Comparator>::Basic_version(Parser p, Comparator c)
|
|
||||||
: parser_(p)
|
|
||||||
, comparator_(c)
|
|
||||||
, ver_(parser_.parse("0.0.0")) {
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename Parser, typename Comparator>
|
|
||||||
Basic_version<Parser, Comparator>::Basic_version(const std::string & v, Parser p, Comparator c)
|
|
||||||
: parser_(p)
|
|
||||||
, comparator_(c)
|
|
||||||
, ver_(parser_.parse(v)) {
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename Parser, typename Comparator>
|
|
||||||
Basic_version<Parser, Comparator>::Basic_version(const Version_data & v, Parser p, Comparator c)
|
|
||||||
: parser_(p)
|
|
||||||
, comparator_(c)
|
|
||||||
, ver_(v) {
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename Parser, typename Comparator>
|
|
||||||
Basic_version<Parser, Comparator>::Basic_version(const Basic_version<Parser, Comparator> &) = default;
|
|
||||||
|
|
||||||
template <typename Parser, typename Comparator>
|
|
||||||
Basic_version<Parser, Comparator> & Basic_version<Parser, Comparator>::operator=(const Basic_version<Parser, Comparator> &) = default;
|
|
||||||
|
|
||||||
template <typename Parser, typename Comparator>
|
|
||||||
int Basic_version<Parser, Comparator>::major() const {
|
|
||||||
return ver_.major;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename Parser, typename Comparator>
|
|
||||||
int Basic_version<Parser, Comparator>::minor() const {
|
|
||||||
return ver_.minor;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename Parser, typename Comparator>
|
|
||||||
int Basic_version<Parser, Comparator>::patch() const {
|
|
||||||
return ver_.patch;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename Parser, typename Comparator>
|
|
||||||
const std::string Basic_version<Parser, Comparator>::prerelease() const {
|
|
||||||
std::string ss;
|
|
||||||
return splice(ss, ver_.prerelease_ids, ".", [](const Prerelease_identifier & id) { return id.first; });
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename Parser, typename Comparator>
|
|
||||||
const std::string Basic_version<Parser, Comparator>::build() const {
|
|
||||||
std::string ss;
|
|
||||||
return splice(ss, ver_.build_ids, ".", [](const std::string & id) { return id; });
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename Parser, typename Comparator>
|
|
||||||
bool operator<(const Basic_version<Parser, Comparator> & l, const Basic_version<Parser, Comparator> & r) {
|
|
||||||
return l.comparator_.compare(l.ver_, r.ver_) == -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename Parser, typename Comparator>
|
|
||||||
bool operator==(const Basic_version<Parser, Comparator> & l, const Basic_version<Parser, Comparator> & r) {
|
|
||||||
return l.comparator_.compare(l.ver_, r.ver_) == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename Parser, typename Comparator>
|
|
||||||
inline bool operator!=(const Basic_version<Parser, Comparator> & l, const Basic_version<Parser, Comparator> & r) {
|
|
||||||
return !(l == r);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename Parser, typename Comparator>
|
|
||||||
inline bool operator>(const Basic_version<Parser, Comparator> & l, const Basic_version<Parser, Comparator> & r) {
|
|
||||||
return r < l;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename Parser, typename Comparator>
|
|
||||||
inline bool operator>=(const Basic_version<Parser, Comparator> & l, const Basic_version<Parser, Comparator> & r) {
|
|
||||||
return !(l < r);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename Parser, typename Comparator>
|
|
||||||
inline bool operator<=(const Basic_version<Parser, Comparator> & l, const Basic_version<Parser, Comparator> & r) {
|
|
||||||
return !(l > r);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace version
|
|
||||||
@@ -15,5 +15,5 @@
|
|||||||
"itty-router": "^5.0.23",
|
"itty-router": "^5.0.23",
|
||||||
"prettier": "^3.8.3"
|
"prettier": "^3.8.3"
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@10.33.0+sha512.10568bb4a6afb58c9eb3630da90cc9516417abebd3fabbe6739f0ae795728da1491e9db5a544c76ad8eb7570f5c4bb3d6c637b2cb41bfdcdb47fa823c8649319"
|
"packageManager": "pnpm@10.33.1+sha512.05ba3c1d5d1c18f68df06470d74055e62d41fc110a0c660db1b2dfb2785327f04cf0f68345d4609bc52089e7fa0343c31593b2f9594e2c5d5da426230acc9820"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ my_build_flags =
|
|||||||
|
|
||||||
[platformio]
|
[platformio]
|
||||||
default_envs = s_16M_P ; BBQKees E32V2
|
default_envs = s_16M_P ; BBQKees E32V2
|
||||||
; default_envs = build_webUI ; build the web interface only
|
; default_envs = build-webUI ; build the web interface only
|
||||||
; default_envs = s3_16M_P ; BBQKees S3
|
; default_envs = s3_16M_P ; BBQKees S3
|
||||||
; default_envs = s_4M ; BBQKees older S32, 4MB no psram
|
; default_envs = s_4M ; BBQKees older S32, 4MB no psram
|
||||||
; default_envs = s_16M ; BBQKees newer S32 V2, 16MB no psram
|
; default_envs = s_16M ; BBQKees newer S32 V2, 16MB no psram
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ extra_configs =
|
|||||||
pio_local.ini
|
pio_local.ini
|
||||||
|
|
||||||
[common]
|
[common]
|
||||||
core_build_flags = -std=c++17 -std=gnu++17 -O3 -Wno-type-limits -Wall -Wextra -Wno-unused-parameter -Wno-unused-variable -Wno-format -Wno-missing-field-initializers
|
core_build_flags = -std=gnu++2a -O3 -flto=auto -Wno-type-limits -Wall -Wextra -Wno-unused-parameter -Wno-unused-variable -Wno-format -Wno-missing-field-initializers
|
||||||
core_unbuild_flags = -std=gnu++11
|
core_unbuild_flags = -std=gnu++11 -std=gnu++14 -std=gnu++17 -fno-lto
|
||||||
|
|
||||||
my_build_flags =
|
my_build_flags =
|
||||||
|
|
||||||
@@ -53,21 +53,13 @@ build_flags =
|
|||||||
unbuild_flags =
|
unbuild_flags =
|
||||||
${common.core_unbuild_flags}
|
${common.core_unbuild_flags}
|
||||||
|
|
||||||
; 4MB Flash variants
|
|
||||||
[espressif32_base_4M]
|
|
||||||
framework = arduino
|
|
||||||
board_build.partitions = partitions/esp32_partition_4M.csv
|
|
||||||
board_upload.flash_size = 4MB
|
|
||||||
board_build.app_partition_name = app0
|
|
||||||
platform = https://github.com/tasmota/platform-espressif32/releases/download/2026.04.50/platform-espressif32.zip ; Platform 2026.04.50 Tasmota Arduino Core 3.3.8 based on IDF 5.5.4.260407
|
|
||||||
|
|
||||||
; 16MB Flash variants
|
; 16MB Flash variants
|
||||||
[espressif32_base_16M]
|
[espressif32_base_16M]
|
||||||
framework = arduino
|
framework = arduino
|
||||||
board_build.partitions = partitions/esp32_partition_16M.csv
|
board_build.partitions = partitions/esp32_partition_16M.csv
|
||||||
board_upload.flash_size = 16MB
|
board_upload.flash_size = 16MB
|
||||||
board_build.app_partition_name = app0
|
board_build.app_partition_name = app0
|
||||||
platform = https://github.com/tasmota/platform-espressif32/releases/download/2026.04.50/platform-espressif32.zip ; Platform 2026.04.50 Tasmota Arduino Core 3.3.8 based on IDF 5.5.4.260407
|
platform = espressif32@7.0.0 ; Arduino Core 2.0.17 / IDF 4.4.7
|
||||||
|
|
||||||
; 32MB Flash variants
|
; 32MB Flash variants
|
||||||
[espressif32_base_32M]
|
[espressif32_base_32M]
|
||||||
@@ -75,7 +67,29 @@ framework = arduino
|
|||||||
board_build.partitions = partitions/esp32_partition_32M.csv
|
board_build.partitions = partitions/esp32_partition_32M.csv
|
||||||
board_upload.flash_size = 32MB
|
board_upload.flash_size = 32MB
|
||||||
board_build.app_partition_name = app0
|
board_build.app_partition_name = app0
|
||||||
platform = https://github.com/tasmota/platform-espressif32/releases/download/2026.04.50/platform-espressif32.zip ; Platform 2026.04.50 Tasmota Arduino Core 3.3.8 based on IDF 5.5.4.260407
|
platform = espressif32@7.0.0 ; Arduino Core 2.0.17 / IDF 4.4.7
|
||||||
|
|
||||||
|
; use Tasmota's library for 4MB Flash variants.
|
||||||
|
; Removes libs (like mbedtsl, so no WiFi_secure.h) to increase available heap
|
||||||
|
[espressif32_base_T_4M]
|
||||||
|
framework = arduino
|
||||||
|
board_build.partitions = partitions/esp32_partition_4M.csv
|
||||||
|
board_upload.flash_size = 4MB
|
||||||
|
board_build.app_partition_name = app0
|
||||||
|
platform = https://github.com/tasmota/platform-espressif32/releases/download/2024.06.00/platform-espressif32.zip ; Arduino Core 2.0.18 with IPv6 support, based on IDF 4.4.8
|
||||||
|
; Tasmota Arduino Core 3.1.3.250302 based on IDF 5.3.2.250228
|
||||||
|
; platform = https://github.com/tasmota/platform-espressif32/releases/download/2025.03.30/platform-espressif32.zip
|
||||||
|
|
||||||
|
; use Tasmota's library for 16MB Flash variants.
|
||||||
|
; Removes libs (like mbedtsl, so no WiFi_secure.h) to increase available heap
|
||||||
|
[espressif32_base_T_16M]
|
||||||
|
framework = arduino
|
||||||
|
board_build.partitions = partitions/esp32_partition_16M.csv
|
||||||
|
board_upload.flash_size = 16MB
|
||||||
|
board_build.app_partition_name = app0
|
||||||
|
platform = https://github.com/tasmota/platform-espressif32/releases/download/2024.06.00/platform-espressif32.zip ; Arduino Core 2.0.18 with IPv6 support, based on IDF 4.4.8
|
||||||
|
; Tasmota Arduino Core 3.1.3.250302 based on IDF 5.3.2.250228
|
||||||
|
; platform = https://github.com/tasmota/platform-espressif32/releases/download/2025.03.30/platform-espressif32.zip
|
||||||
|
|
||||||
[env]
|
[env]
|
||||||
build_flags =
|
build_flags =
|
||||||
@@ -91,13 +105,11 @@ board_build.filesystem = littlefs
|
|||||||
lib_deps =
|
lib_deps =
|
||||||
bblanchon/ArduinoJson @ 7.4.3
|
bblanchon/ArduinoJson @ 7.4.3
|
||||||
ESP32Async/AsyncTCP @ 3.4.10
|
ESP32Async/AsyncTCP @ 3.4.10
|
||||||
ESP32Async/ESPAsyncWebServer @ 3.10.3
|
ESP32Async/ESPAsyncWebServer @ 3.11.0
|
||||||
https://github.com/mobizt/ReadyMail.git @ 0.3.8
|
|
||||||
https://github.com/mobizt/ESP_SSLClient.git @ 3.1.3
|
|
||||||
; https://github.com/emsesp/EMS-ESP-Modules.git @ 1.0.8
|
; https://github.com/emsesp/EMS-ESP-Modules.git @ 1.0.8
|
||||||
|
|
||||||
; builds the web interface only, not the firmware
|
; builds the web interface only, not the firmware
|
||||||
[env:build_webUI]
|
[env:build-webUI]
|
||||||
platform = native
|
platform = native
|
||||||
targets = build
|
targets = build
|
||||||
extra_scripts = pre:scripts/build_interface.py
|
extra_scripts = pre:scripts/build_interface.py
|
||||||
@@ -106,17 +118,18 @@ build_src_filter = -<*>
|
|||||||
|
|
||||||
;
|
;
|
||||||
; Builds for different board types
|
; Builds for different board types
|
||||||
|
; We use Tasmota for boards without PSRAM as this framework has mbedtls removed to save memory.
|
||||||
; If you're building for a single target environment, we recommend creating a pio_local.ini (see example file)
|
; If you're building for a single target environment, we recommend creating a pio_local.ini (see example file)
|
||||||
;
|
;
|
||||||
|
|
||||||
[env:s_4M]
|
[env:s_4M]
|
||||||
; 4MB ESP32 - no SSL, no PSRAM - like a BBQKees older S32 and E32 models
|
; 4MB ESP32 - no SSL, no PSRAM - like a BBQKees older S32 and E32 models - uses Tasmota
|
||||||
extends = espressif32_base_4M
|
extends = espressif32_base_T_4M
|
||||||
board = s_4M
|
board = s_4M
|
||||||
|
|
||||||
[env:s_16M]
|
[env:s_16M]
|
||||||
; 16MB ESP32 - no PSRAM - like a BBQKees later S32 V2 models
|
; 16MB ESP32 - no PSRAM - like a BBQKees later S32 V2 models - uses Tasmota
|
||||||
extends = espressif32_base_16M
|
extends = espressif32_base_T_16M
|
||||||
board = s_16M
|
board = s_16M
|
||||||
|
|
||||||
[env:s_16M_P]
|
[env:s_16M_P]
|
||||||
@@ -135,30 +148,24 @@ extends = espressif32_base_32M
|
|||||||
board = s3_32M_P
|
board = s3_32M_P
|
||||||
|
|
||||||
[env:s2_4M_P]
|
[env:s2_4M_P]
|
||||||
; based on lolin_s2_mini 4MB with 2MB PSRAM
|
; based on lolin_s2_mini 4MB with 2MB PSRAM - uses Tasmota
|
||||||
extends = espressif32_base_4M
|
extends = espressif32_base_T_4M
|
||||||
board = s2_4M_P
|
board = s2_4M_P
|
||||||
|
|
||||||
[env:c3_mini_4M]
|
[env:c3_mini_4M]
|
||||||
; based on lolin_c3_mini 4MB, no PSRAM
|
; based on lolin_c3_mini 4MB, no PSRAM - uses Tasmota
|
||||||
extends = espressif32_base_4M
|
extends = espressif32_base_T_4M
|
||||||
board = c3_mini_4M
|
board = c3_mini_4M
|
||||||
|
|
||||||
; lolin C3 mini v1 needs special wifi initialization
|
; lolin C3 mini v1 needs special wifi initialization
|
||||||
; https://www.wemos.cc/en/latest/c3/c3_mini_1_0_0.html#about-wifi
|
; https://www.wemos.cc/en/latest/c3/c3_mini_1_0_0.html#about-wifi
|
||||||
[env:c3_miniv1_4M]
|
[env:c3_miniv1_4M]
|
||||||
extends = espressif32_base_4M
|
extends = espressif32_base_T_4M
|
||||||
board = c3_mini_4M
|
board = c3_mini_4M
|
||||||
build_flags =
|
build_flags =
|
||||||
${common.build_flags}
|
${common.build_flags}
|
||||||
-DBOARD_C3_MINI_V1
|
-DBOARD_C3_MINI_V1
|
||||||
|
|
||||||
; XIAO ESP32C - 512KB SRAM & 4MB Flash - https://wiki.seeedstudio.com/xiao_esp32c6_getting_started/
|
|
||||||
[env:c6_xiao_4M]
|
|
||||||
extends = espressif32_base_4M
|
|
||||||
board_build.app_partition_name = app0
|
|
||||||
board = seeed_xiao_esp32c6
|
|
||||||
|
|
||||||
; foundation for building and testing natively, standalone without an ESP32
|
; foundation for building and testing natively, standalone without an ESP32
|
||||||
; use the `standalone` environment instead of `native` for testing
|
; use the `standalone` environment instead of `native` for testing
|
||||||
[env:native]
|
[env:native]
|
||||||
@@ -168,8 +175,7 @@ build_flags =
|
|||||||
build_src_flags =
|
build_src_flags =
|
||||||
-DEMSESP_STANDALONE -DEMSESP_TEST
|
-DEMSESP_STANDALONE -DEMSESP_TEST
|
||||||
-DARDUINOJSON_ENABLE_ARDUINO_STRING=1
|
-DARDUINOJSON_ENABLE_ARDUINO_STRING=1
|
||||||
-DNO_TLS_SUPPORT
|
-std=gnu++2a -Og -ggdb
|
||||||
-std=gnu++17 -Og -ggdb
|
|
||||||
-Wall -Wextra
|
-Wall -Wextra
|
||||||
-Wno-unused-parameter -Wno-sign-compare -Wno-missing-braces
|
-Wno-unused-parameter -Wno-sign-compare -Wno-missing-braces
|
||||||
-Wno-vla-cxx-extension -Wno-tautological-constant-out-of-range-compare
|
-Wno-vla-cxx-extension -Wno-tautological-constant-out-of-range-compare
|
||||||
@@ -178,7 +184,6 @@ build_src_flags =
|
|||||||
-I./lib/uuid-common/src
|
-I./lib/uuid-common/src
|
||||||
-I./lib/uuid-console/src
|
-I./lib/uuid-console/src
|
||||||
-I./lib/uuid-log/src
|
-I./lib/uuid-log/src
|
||||||
-I./lib/semver
|
|
||||||
-I./lib/PButton
|
-I./lib/PButton
|
||||||
-I./lib/espMqttClient/src
|
-I./lib/espMqttClient/src
|
||||||
-I./lib/espMqttClient/src/Transport
|
-I./lib/espMqttClient/src/Transport
|
||||||
@@ -208,9 +213,8 @@ build_src_flags =
|
|||||||
-DEMSESP_STANDALONE -DEMSESP_TEST
|
-DEMSESP_STANDALONE -DEMSESP_TEST
|
||||||
-DEMSESP_UNITY
|
-DEMSESP_UNITY
|
||||||
-DARDUINOJSON_ENABLE_ARDUINO_STRING=1
|
-DARDUINOJSON_ENABLE_ARDUINO_STRING=1
|
||||||
-DNO_TLS_SUPPORT
|
|
||||||
-DEMSESP_DEFAULT_LOCALE=\"en\" -DEMSESP_DEFAULT_BOARD_PROFILE=\"S32\"
|
-DEMSESP_DEFAULT_LOCALE=\"en\" -DEMSESP_DEFAULT_BOARD_PROFILE=\"S32\"
|
||||||
-std=gnu++17 -Og -ggdb
|
-std=gnu++2a -Og -ggdb
|
||||||
-Wall -Wextra
|
-Wall -Wextra
|
||||||
-Wno-unused-parameter -Wno-sign-compare -Wno-missing-braces
|
-Wno-unused-parameter -Wno-sign-compare -Wno-missing-braces
|
||||||
-Wno-vla-cxx-extension -Wno-tautological-constant-out-of-range-compare
|
-Wno-vla-cxx-extension -Wno-tautological-constant-out-of-range-compare
|
||||||
@@ -219,7 +223,6 @@ build_src_flags =
|
|||||||
-I./lib/uuid-common/src
|
-I./lib/uuid-common/src
|
||||||
-I./lib/uuid-console/src
|
-I./lib/uuid-console/src
|
||||||
-I./lib/uuid-log/src
|
-I./lib/uuid-log/src
|
||||||
-I./lib/semver
|
|
||||||
-I./lib/PButton
|
-I./lib/PButton
|
||||||
-I./lib/espMqttClient/src
|
-I./lib/espMqttClient/src
|
||||||
-I./lib/espMqttClient/src/Transport
|
-I./lib/espMqttClient/src/Transport
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ def buildWeb():
|
|||||||
# Run pnpm commands in the interface directory
|
# Run pnpm commands in the interface directory
|
||||||
commands = [
|
commands = [
|
||||||
f"{pnpm_exe} install",
|
f"{pnpm_exe} install",
|
||||||
f"{pnpm_exe} build_webUI"
|
f"{pnpm_exe} build-webUI"
|
||||||
]
|
]
|
||||||
|
|
||||||
for command in commands:
|
for command in commands:
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ pnpm format
|
|||||||
|
|
||||||
cd ..
|
cd ..
|
||||||
cd interface
|
cd interface
|
||||||
pnpm build_webUI
|
pnpm build-webUI
|
||||||
|
|
||||||
cd ..
|
cd ..
|
||||||
npx cspell "**"
|
npx cspell "**"
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ APSettingsService::APSettingsService(AsyncWebServer * server, FS * fs, SecurityM
|
|||||||
, _reconfigureAp(false)
|
, _reconfigureAp(false)
|
||||||
, _connected(0) {
|
, _connected(0) {
|
||||||
addUpdateHandler([this] { reconfigureAP(); }, false);
|
addUpdateHandler([this] { reconfigureAP(); }, false);
|
||||||
|
WiFi.onEvent([this](WiFiEvent_t event, WiFiEventInfo_t info) { WiFiEvent(event); });
|
||||||
}
|
}
|
||||||
|
|
||||||
void APSettingsService::begin() {
|
void APSettingsService::begin() {
|
||||||
@@ -18,27 +19,39 @@ void APSettingsService::begin() {
|
|||||||
// reconfigureAP();
|
// reconfigureAP();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// wait 10 sec on STA disconnect before starting AP
|
||||||
|
void APSettingsService::WiFiEvent(WiFiEvent_t event) {
|
||||||
|
const uint8_t was_connected = _connected;
|
||||||
|
switch (event) {
|
||||||
|
case ARDUINO_EVENT_WIFI_STA_DISCONNECTED:
|
||||||
|
_connected &= ~1U;
|
||||||
|
break;
|
||||||
|
case ARDUINO_EVENT_ETH_DISCONNECTED:
|
||||||
|
_connected &= ~2U;
|
||||||
|
break;
|
||||||
|
case ARDUINO_EVENT_WIFI_STA_GOT_IP:
|
||||||
|
case ARDUINO_EVENT_WIFI_STA_GOT_IP6:
|
||||||
|
_connected |= 1U;
|
||||||
|
break;
|
||||||
|
case ARDUINO_EVENT_ETH_GOT_IP:
|
||||||
|
case ARDUINO_EVENT_ETH_GOT_IP6:
|
||||||
|
_connected |= 2U;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// wait 10 sec before starting AP
|
||||||
|
if (was_connected && !_connected) {
|
||||||
|
_lastManaged = uuid::get_uptime();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void APSettingsService::reconfigureAP() {
|
void APSettingsService::reconfigureAP() {
|
||||||
_lastManaged = uuid::get_uptime() - MANAGE_NETWORK_DELAY;
|
_lastManaged = uuid::get_uptime() - MANAGE_NETWORK_DELAY;
|
||||||
_reconfigureAp = true;
|
_reconfigureAp = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void APSettingsService::loop() {
|
void APSettingsService::loop() {
|
||||||
const uint8_t was_connected = _connected;
|
|
||||||
if (WiFi.isConnected()) {
|
|
||||||
_connected |= 1U;
|
|
||||||
} else {
|
|
||||||
_connected &= ~1U;
|
|
||||||
}
|
|
||||||
if (ETH.connected()) {
|
|
||||||
_connected |= 2U;
|
|
||||||
} else {
|
|
||||||
_connected &= ~2U;
|
|
||||||
}
|
|
||||||
// wait 10 sec before starting AP
|
|
||||||
if (was_connected && !_connected) {
|
|
||||||
_lastManaged = uuid::get_uptime();
|
|
||||||
}
|
|
||||||
const unsigned long currentMillis = uuid::get_uptime();
|
const unsigned long currentMillis = uuid::get_uptime();
|
||||||
if ((currentMillis - _lastManaged) >= MANAGE_NETWORK_DELAY) {
|
if ((currentMillis - _lastManaged) >= MANAGE_NETWORK_DELAY) {
|
||||||
_lastManaged = currentMillis;
|
_lastManaged = currentMillis;
|
||||||
@@ -63,7 +76,11 @@ void APSettingsService::manageAP() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void APSettingsService::startAP() {
|
void APSettingsService::startAP() {
|
||||||
|
#if ESP_IDF_VERSION_MAJOR < 5
|
||||||
|
WiFi.softAPenableIpV6(); // force IPV6, same as for WiFi - fixes https://github.com/emsesp/EMS-ESP32/issues/1922
|
||||||
|
#else
|
||||||
WiFi.softAPenableIPv6(); // force IPV6, same as for WiFi - fixes https://github.com/emsesp/EMS-ESP32/issues/1922
|
WiFi.softAPenableIPv6(); // force IPV6, same as for WiFi - fixes https://github.com/emsesp/EMS-ESP32/issues/1922
|
||||||
|
#endif
|
||||||
WiFi.softAPConfig(_state.localIP, _state.gatewayIP, _state.subnetMask);
|
WiFi.softAPConfig(_state.localIP, _state.gatewayIP, _state.subnetMask);
|
||||||
esp_wifi_set_bandwidth(static_cast<wifi_interface_t>(ESP_IF_WIFI_AP), WIFI_BW_HT20);
|
esp_wifi_set_bandwidth(static_cast<wifi_interface_t>(ESP_IF_WIFI_AP), WIFI_BW_HT20);
|
||||||
WiFi.softAP(_state.ssid.c_str(), _state.password.c_str(), _state.channel, _state.ssidHidden, _state.maxClients);
|
WiFi.softAP(_state.ssid.c_str(), _state.password.c_str(), _state.channel, _state.ssidHidden, _state.maxClients);
|
||||||
|
|||||||
@@ -104,6 +104,7 @@ class APSettingsService : public StatefulService<APSettings> {
|
|||||||
void startAP();
|
void startAP();
|
||||||
void stopAP();
|
void stopAP();
|
||||||
void handleDNS();
|
void handleDNS();
|
||||||
|
void WiFiEvent(WiFiEvent_t event);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -2,10 +2,75 @@
|
|||||||
|
|
||||||
#include "WWWData.h" // include auto-generated static web resources
|
#include "WWWData.h" // include auto-generated static web resources
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
static constexpr const char CACHE_CONTROL[] = "public,max-age=60";
|
static constexpr const char CACHE_CONTROL[] = "public,max-age=60";
|
||||||
|
|
||||||
|
// Single static-content handler serving all assets embedded in WWWData.h.
|
||||||
|
class StaticContentHandler : public AsyncWebHandler {
|
||||||
|
public:
|
||||||
|
bool canHandle(AsyncWebServerRequest * request) const override {
|
||||||
|
const auto method = request->method();
|
||||||
|
return method == HTTP_GET || method == HTTP_HEAD || method == HTTP_OPTIONS;
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleRequest(AsyncWebServerRequest * request) override {
|
||||||
|
// OPTIONS is handled generically - the server-level CORS headers are
|
||||||
|
// attached via DefaultHeaders in ESP32React::begin().
|
||||||
|
if (request->method() == HTTP_OPTIONS) {
|
||||||
|
request->send(200);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char * url = request->url().c_str();
|
||||||
|
const WWWAsset * found = lookup(url);
|
||||||
|
const WWWAsset * asset = found ? found : index_asset();
|
||||||
|
|
||||||
|
if (asset == nullptr) {
|
||||||
|
request->send(404);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the client already has this exact ETag, respond 304 Not Modified without sending the body.
|
||||||
|
const String & inm = request->header(asyncsrv::T_INM);
|
||||||
|
if (inm.length() != 0 && strcmp(inm.c_str(), asset->etag) == 0) {
|
||||||
|
request->send(304);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AsyncWebServerResponse * response = request->beginResponse(200, asset->contentType, asset->content, asset->len);
|
||||||
|
response->addHeader(asyncsrv::T_Content_Encoding, asyncsrv::T_gzip, false);
|
||||||
|
response->addHeader(asyncsrv::T_ETag, asset->etag, false);
|
||||||
|
response->addHeader(asyncsrv::T_Cache_Control, CACHE_CONTROL, false);
|
||||||
|
request->send(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Exact-match lookup in the asset table
|
||||||
|
static const WWWAsset * lookup(const char * url) {
|
||||||
|
for (size_t i = 0; i < WWW_ASSETS_COUNT; i++) {
|
||||||
|
if (strcmp(WWW_ASSETS[i].uri, url) == 0) {
|
||||||
|
return &WWW_ASSETS[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the /index.html asset, used as the SPA fallback for any GET
|
||||||
|
// that didn't match an embedded asset (React Router handles routing on
|
||||||
|
// the client side).
|
||||||
|
static const WWWAsset * index_asset() {
|
||||||
|
static const WWWAsset * cached = nullptr;
|
||||||
|
if (cached == nullptr) {
|
||||||
|
cached = lookup("/index.html");
|
||||||
|
}
|
||||||
|
return cached;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
ESP32React::ESP32React(AsyncWebServer * server, FS * fs)
|
ESP32React::ESP32React(AsyncWebServer * server, FS * fs)
|
||||||
: _securitySettingsService(server, fs)
|
: _server(server)
|
||||||
|
, _securitySettingsService(server, fs)
|
||||||
, _networkSettingsService(server, fs, &_securitySettingsService)
|
, _networkSettingsService(server, fs, &_securitySettingsService)
|
||||||
, _wifiScanner(server, &_securitySettingsService)
|
, _wifiScanner(server, &_securitySettingsService)
|
||||||
, _networkStatus(server, &_securitySettingsService)
|
, _networkStatus(server, &_securitySettingsService)
|
||||||
@@ -17,50 +82,6 @@ ESP32React::ESP32React(AsyncWebServer * server, FS * fs)
|
|||||||
, _mqttSettingsService(server, fs, &_securitySettingsService)
|
, _mqttSettingsService(server, fs, &_securitySettingsService)
|
||||||
, _mqttStatus(server, &_mqttSettingsService, &_securitySettingsService)
|
, _mqttStatus(server, &_mqttSettingsService, &_securitySettingsService)
|
||||||
, _authenticationService(server, &_securitySettingsService) {
|
, _authenticationService(server, &_securitySettingsService) {
|
||||||
//
|
|
||||||
// Serve static web resources
|
|
||||||
//
|
|
||||||
|
|
||||||
ArRequestHandlerFunction indexHtmlHandler = nullptr;
|
|
||||||
|
|
||||||
WWWData::registerRoutes([server, &indexHtmlHandler](const char * uri, const String & contentType, const uint8_t * content, size_t len, const String & hash) {
|
|
||||||
String etag = "\"" + hash + "\""; // RFC9110: ETag must be enclosed in double quotes
|
|
||||||
|
|
||||||
ArRequestHandlerFunction requestHandler = [contentType, content, len, etag](AsyncWebServerRequest * request) {
|
|
||||||
if (request->header(asyncsrv::T_INM) == etag) {
|
|
||||||
request->send(304);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
AsyncWebServerResponse * response = request->beginResponse(200, contentType, content, len);
|
|
||||||
response->addHeader(asyncsrv::T_Content_Encoding, asyncsrv::T_gzip, false);
|
|
||||||
response->addHeader(asyncsrv::T_ETag, etag, false);
|
|
||||||
response->addHeader(asyncsrv::T_Cache_Control, CACHE_CONTROL, false);
|
|
||||||
request->send(response);
|
|
||||||
};
|
|
||||||
|
|
||||||
server->on(uri, HTTP_GET, requestHandler);
|
|
||||||
|
|
||||||
// Capture index.html handler to set onNotFound once after all routes are registered
|
|
||||||
if (strcmp(uri, "/index.html") == 0) {
|
|
||||||
indexHtmlHandler = requestHandler;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Set onNotFound handler once after all routes are registered
|
|
||||||
// Serving non matching get requests with "/index.html"
|
|
||||||
// OPTIONS get a straight up 200 response
|
|
||||||
if (indexHtmlHandler != nullptr) {
|
|
||||||
server->onNotFound([indexHtmlHandler](AsyncWebServerRequest * request) {
|
|
||||||
if (request->method() == HTTP_GET) {
|
|
||||||
indexHtmlHandler(request);
|
|
||||||
} else if (request->method() == HTTP_OPTIONS) {
|
|
||||||
request->send(200);
|
|
||||||
} else {
|
|
||||||
request->send(404); // not found
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ESP32React::begin() {
|
void ESP32React::begin() {
|
||||||
@@ -78,11 +99,11 @@ void ESP32React::begin() {
|
|||||||
_ntpSettingsService.begin();
|
_ntpSettingsService.begin();
|
||||||
_mqttSettingsService.begin();
|
_mqttSettingsService.begin();
|
||||||
_securitySettingsService.begin();
|
_securitySettingsService.begin();
|
||||||
|
_server->addHandler(new StaticContentHandler());
|
||||||
}
|
}
|
||||||
|
|
||||||
void ESP32React::loop() {
|
void ESP32React::loop() {
|
||||||
_networkSettingsService.loop();
|
_networkSettingsService.loop();
|
||||||
_apSettingsService.loop();
|
_apSettingsService.loop();
|
||||||
_mqttSettingsService.loop();
|
_mqttSettingsService.loop();
|
||||||
_ntpSettingsService.loop();
|
|
||||||
}
|
}
|
||||||
@@ -68,6 +68,7 @@ class ESP32React {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
AsyncWebServer * _server;
|
||||||
SecuritySettingsService _securitySettingsService;
|
SecuritySettingsService _securitySettingsService;
|
||||||
NetworkSettingsService _networkSettingsService;
|
NetworkSettingsService _networkSettingsService;
|
||||||
WiFiScanner _wifiScanner;
|
WiFiScanner _wifiScanner;
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ MqttSettingsService::MqttSettingsService(AsyncWebServer * server, FS * fs, Secur
|
|||||||
, _disconnectedAt(0)
|
, _disconnectedAt(0)
|
||||||
, _disconnectReason(espMqttClientTypes::DisconnectReason::TCP_DISCONNECTED)
|
, _disconnectReason(espMqttClientTypes::DisconnectReason::TCP_DISCONNECTED)
|
||||||
, _mqttClient(nullptr) {
|
, _mqttClient(nullptr) {
|
||||||
|
WiFi.onEvent([this](WiFiEvent_t event, WiFiEventInfo_t info) { WiFiEvent(event); });
|
||||||
addUpdateHandler([this] { onConfigUpdated(); }, false);
|
addUpdateHandler([this] { onConfigUpdated(); }, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,7 +29,6 @@ MqttSettingsService::~MqttSettingsService() {
|
|||||||
void MqttSettingsService::begin() {
|
void MqttSettingsService::begin() {
|
||||||
_fsPersistence.readFromFS();
|
_fsPersistence.readFromFS();
|
||||||
startClient();
|
startClient();
|
||||||
_reconfigureMqtt = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MqttSettingsService::startClient() {
|
void MqttSettingsService::startClient() {
|
||||||
@@ -41,7 +41,7 @@ void MqttSettingsService::startClient() {
|
|||||||
delete _mqttClient;
|
delete _mqttClient;
|
||||||
_mqttClient = nullptr;
|
_mqttClient = nullptr;
|
||||||
}
|
}
|
||||||
#ifndef NO_TLS_SUPPORT
|
#ifndef TASMOTA_SDK
|
||||||
if (_state.enableTLS) {
|
if (_state.enableTLS) {
|
||||||
isSecure = true;
|
isSecure = true;
|
||||||
if (emsesp::EMSESP::system_.PSram() == 0) {
|
if (emsesp::EMSESP::system_.PSram() == 0) {
|
||||||
@@ -79,10 +79,6 @@ void MqttSettingsService::startClient() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void MqttSettingsService::loop() {
|
void MqttSettingsService::loop() {
|
||||||
if (_state.enabled && _mqttClient && _mqttClient->connected() && !emsesp::EMSESP::system_.network_connected()) {
|
|
||||||
// emsesp::EMSESP::logger().info("Network connection dropped, stopping MQTT client");
|
|
||||||
_mqttClient->disconnect(true);
|
|
||||||
}
|
|
||||||
if (_reconfigureMqtt || (_disconnectedAt && static_cast<uint32_t>(uuid::get_uptime() - _disconnectedAt) >= MQTT_RECONNECTION_DELAY)) {
|
if (_reconfigureMqtt || (_disconnectedAt && static_cast<uint32_t>(uuid::get_uptime() - _disconnectedAt) >= MQTT_RECONNECTION_DELAY)) {
|
||||||
// reconfigure MQTT client
|
// reconfigure MQTT client
|
||||||
_disconnectedAt = configureMqtt() ? 0 : uuid::get_uptime();
|
_disconnectedAt = configureMqtt() ? 0 : uuid::get_uptime();
|
||||||
@@ -146,6 +142,28 @@ void MqttSettingsService::onConfigUpdated() {
|
|||||||
emsesp::EMSESP::mqtt_.start(); // reload EMS-ESP MQTT settings
|
emsesp::EMSESP::mqtt_.start(); // reload EMS-ESP MQTT settings
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MqttSettingsService::WiFiEvent(WiFiEvent_t event) {
|
||||||
|
switch (event) {
|
||||||
|
case ARDUINO_EVENT_WIFI_STA_GOT_IP:
|
||||||
|
case ARDUINO_EVENT_ETH_GOT_IP:
|
||||||
|
case ARDUINO_EVENT_ETH_GOT_IP6:
|
||||||
|
case ARDUINO_EVENT_WIFI_STA_GOT_IP6:
|
||||||
|
if (_state.enabled && !_mqttClient->connected()) {
|
||||||
|
onConfigUpdated();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ARDUINO_EVENT_WIFI_STA_DISCONNECTED:
|
||||||
|
case ARDUINO_EVENT_ETH_DISCONNECTED:
|
||||||
|
if (_state.enabled) {
|
||||||
|
_mqttClient->disconnect(true);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool MqttSettingsService::configureMqtt() {
|
bool MqttSettingsService::configureMqtt() {
|
||||||
// disconnect if already connected
|
// disconnect if already connected
|
||||||
if (_mqttClient->connected()) {
|
if (_mqttClient->connected()) {
|
||||||
@@ -164,13 +182,17 @@ bool MqttSettingsService::configureMqtt() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_reconfigureMqtt = false;
|
_reconfigureMqtt = false;
|
||||||
#ifndef NO_TLS_SUPPORT
|
#ifndef TASMOTA_SDK
|
||||||
if (_state.enableTLS) {
|
if (_state.enableTLS) {
|
||||||
if (_state.rootCA == "insecure") {
|
if (_state.rootCA == "insecure") {
|
||||||
|
#if defined(EMSESP_DEBUG)
|
||||||
emsesp::EMSESP::logger().debug("Start insecure MQTT");
|
emsesp::EMSESP::logger().debug("Start insecure MQTT");
|
||||||
|
#endif
|
||||||
static_cast<espMqttClientSecure *>(_mqttClient)->setInsecure();
|
static_cast<espMqttClientSecure *>(_mqttClient)->setInsecure();
|
||||||
} else {
|
} else {
|
||||||
|
#if defined(EMSESP_DEBUG)
|
||||||
emsesp::EMSESP::logger().debug("Start secure MQTT with rootCA");
|
emsesp::EMSESP::logger().debug("Start secure MQTT with rootCA");
|
||||||
|
#endif
|
||||||
String certificate = "-----BEGIN CERTIFICATE-----\n" + _state.rootCA + "\n-----END CERTIFICATE-----\n";
|
String certificate = "-----BEGIN CERTIFICATE-----\n" + _state.rootCA + "\n-----END CERTIFICATE-----\n";
|
||||||
static_cast<espMqttClientSecure *>(_mqttClient)->setCACert(certificate.c_str());
|
static_cast<espMqttClientSecure *>(_mqttClient)->setCACert(certificate.c_str());
|
||||||
}
|
}
|
||||||
@@ -201,7 +223,7 @@ bool MqttSettingsService::configureMqtt() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void MqttSettings::read(MqttSettings & settings, JsonObject root) {
|
void MqttSettings::read(MqttSettings & settings, JsonObject root) {
|
||||||
#ifndef NO_TLS_SUPPORT
|
#ifndef TASMOTA_SDK
|
||||||
root["enableTLS"] = settings.enableTLS;
|
root["enableTLS"] = settings.enableTLS;
|
||||||
root["rootCA"] = settings.rootCA;
|
root["rootCA"] = settings.rootCA;
|
||||||
#endif
|
#endif
|
||||||
@@ -240,7 +262,7 @@ StateUpdateResult MqttSettings::update(JsonObject root, MqttSettings & settings)
|
|||||||
MqttSettings newSettings;
|
MqttSettings newSettings;
|
||||||
bool changed = false;
|
bool changed = false;
|
||||||
|
|
||||||
#ifndef NO_TLS_SUPPORT
|
#ifndef TASMOTA_SDK
|
||||||
newSettings.enableTLS = root["enableTLS"];
|
newSettings.enableTLS = root["enableTLS"];
|
||||||
newSettings.rootCA = root["rootCA"] | "";
|
newSettings.rootCA = root["rootCA"] | "";
|
||||||
#else
|
#else
|
||||||
@@ -371,7 +393,7 @@ StateUpdateResult MqttSettings::update(JsonObject root, MqttSettings & settings)
|
|||||||
emsesp::EMSESP::mqtt_.set_publish_time_heartbeat(newSettings.publish_time_heartbeat);
|
emsesp::EMSESP::mqtt_.set_publish_time_heartbeat(newSettings.publish_time_heartbeat);
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifndef NO_TLS_SUPPORT
|
#ifndef TASMOTA_SDK
|
||||||
// strip down to certificate only
|
// strip down to certificate only
|
||||||
newSettings.rootCA.replace("\r", "");
|
newSettings.rootCA.replace("\r", "");
|
||||||
newSettings.rootCA.replace("\n", "");
|
newSettings.rootCA.replace("\n", "");
|
||||||
|
|||||||
@@ -134,6 +134,7 @@ class MqttSettingsService : public StatefulService<MqttSettings> {
|
|||||||
// the MQTT client instance
|
// the MQTT client instance
|
||||||
MqttClient * _mqttClient;
|
MqttClient * _mqttClient;
|
||||||
|
|
||||||
|
void WiFiEvent(WiFiEvent_t event);
|
||||||
void onMqttConnect(bool sessionPresent);
|
void onMqttConnect(bool sessionPresent);
|
||||||
void onMqttDisconnect(espMqttClientTypes::DisconnectReason reason);
|
void onMqttDisconnect(espMqttClientTypes::DisconnectReason reason);
|
||||||
void
|
void
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ NTPSettingsService::NTPSettingsService(AsyncWebServer * server, FS * fs, Securit
|
|||||||
configureTime(request, json);
|
configureTime(request, json);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
WiFi.onEvent([this](WiFiEvent_t event, WiFiEventInfo_t info) { WiFiEvent(event); });
|
||||||
addUpdateHandler([this] { configureNTP(); }, false);
|
addUpdateHandler([this] { configureNTP(); }, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -19,10 +20,27 @@ void NTPSettingsService::begin() {
|
|||||||
configureNTP();
|
configureNTP();
|
||||||
}
|
}
|
||||||
|
|
||||||
void NTPSettingsService::loop() {
|
// handles both WiFI and Ethernet
|
||||||
if (_connected != emsesp::EMSESP::system_.network_connected()) {
|
void NTPSettingsService::WiFiEvent(WiFiEvent_t event) {
|
||||||
_connected = emsesp::EMSESP::system_.network_connected();
|
switch (event) {
|
||||||
|
case ARDUINO_EVENT_WIFI_STA_DISCONNECTED:
|
||||||
|
case ARDUINO_EVENT_ETH_DISCONNECTED:
|
||||||
|
if (_connected && emsesp::EMSESP::system_.ntp_connected()) {
|
||||||
|
emsesp::EMSESP::logger().info("WiFi connection dropped, stopping NTP");
|
||||||
|
_connected = false;
|
||||||
|
configureNTP();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ARDUINO_EVENT_WIFI_STA_GOT_IP:
|
||||||
|
case ARDUINO_EVENT_ETH_GOT_IP:
|
||||||
|
// emsesp::EMSESP::logger().info("Got IP address, starting NTP synchronization");
|
||||||
|
_connected = true;
|
||||||
configureNTP();
|
configureNTP();
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,9 +55,7 @@ void NTPSettingsService::configureNTP() {
|
|||||||
} else {
|
} else {
|
||||||
setenv("TZ", _state.tzFormat.c_str(), 1);
|
setenv("TZ", _state.tzFormat.c_str(), 1);
|
||||||
tzset();
|
tzset();
|
||||||
if (esp_sntp_enabled()) {
|
esp_sntp_stop();
|
||||||
esp_sntp_stop();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,12 +68,7 @@ void NTPSettingsService::configureTime(AsyncWebServerRequest * request, JsonVari
|
|||||||
tm.tm_isdst = -1; // not set by strptime, tells mktime to determine daylightsaving
|
tm.tm_isdst = -1; // not set by strptime, tells mktime to determine daylightsaving
|
||||||
time_t time = mktime(&tm);
|
time_t time = mktime(&tm);
|
||||||
struct timeval now = {.tv_sec = time, .tv_usec = {}};
|
struct timeval now = {.tv_sec = time, .tv_usec = {}};
|
||||||
#if CONFIG_IDF_TARGET_ESP32C3
|
|
||||||
// settimeofday and adjtime() does not work, unknown how to set time
|
|
||||||
emsesp::EMSESP::logger().warning("manual clock setting not possible");
|
|
||||||
#else
|
|
||||||
settimeofday(&now, nullptr);
|
settimeofday(&now, nullptr);
|
||||||
#endif
|
|
||||||
AsyncWebServerResponse * response = request->beginResponse(200);
|
AsyncWebServerResponse * response = request->beginResponse(200);
|
||||||
request->send(response);
|
request->send(response);
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -44,7 +44,6 @@ class NTPSettingsService : public StatefulService<NTPSettings> {
|
|||||||
NTPSettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager);
|
NTPSettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager);
|
||||||
|
|
||||||
void begin();
|
void begin();
|
||||||
void loop();
|
|
||||||
static void ntp_received(struct timeval * tv);
|
static void ntp_received(struct timeval * tv);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -52,6 +51,7 @@ class NTPSettingsService : public StatefulService<NTPSettings> {
|
|||||||
FSPersistence<NTPSettings> _fsPersistence;
|
FSPersistence<NTPSettings> _fsPersistence;
|
||||||
volatile bool _connected;
|
volatile bool _connected;
|
||||||
|
|
||||||
|
void WiFiEvent(WiFiEvent_t event);
|
||||||
void configureNTP();
|
void configureNTP();
|
||||||
void configureTime(AsyncWebServerRequest * request, JsonVariant json);
|
void configureTime(AsyncWebServerRequest * request, JsonVariant json);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ NetworkSettingsService::NetworkSettingsService(AsyncWebServer * server, FS * fs,
|
|||||||
, _stopping(false) {
|
, _stopping(false) {
|
||||||
addUpdateHandler([this] { reconfigureWiFiConnection(); }, false);
|
addUpdateHandler([this] { reconfigureWiFiConnection(); }, false);
|
||||||
// Eth is also bound to the WifiGeneric event handler
|
// Eth is also bound to the WifiGeneric event handler
|
||||||
// Network.onEvent([this](arduino_event_id_t event, arduino_event_info_t info) { WiFiEvent(event, info); });
|
WiFi.onEvent([this](WiFiEvent_t event, WiFiEventInfo_t info) { WiFiEvent(event, info); });
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool formatBssid(const String & bssid, uint8_t (&mac)[6]) {
|
static bool formatBssid(const String & bssid, uint8_t (&mac)[6]) {
|
||||||
@@ -34,8 +34,8 @@ void NetworkSettingsService::begin() {
|
|||||||
WiFi.persistent(false);
|
WiFi.persistent(false);
|
||||||
WiFi.setAutoReconnect(false);
|
WiFi.setAutoReconnect(false);
|
||||||
|
|
||||||
WiFi.mode(WIFI_STA);
|
WiFi.mode(WIFI_MODE_MAX);
|
||||||
WiFi.mode(WIFI_OFF);
|
WiFi.mode(WIFI_MODE_NULL);
|
||||||
|
|
||||||
// scan settings give connect issues since arduino 2.0.14 and arduino 3.x.x with some wifi systems
|
// scan settings give connect issues since arduino 2.0.14 and arduino 3.x.x with some wifi systems
|
||||||
// WiFi.setScanMethod(WIFI_ALL_CHANNEL_SCAN); // default is FAST_SCAN
|
// WiFi.setScanMethod(WIFI_ALL_CHANNEL_SCAN); // default is FAST_SCAN
|
||||||
@@ -62,118 +62,24 @@ void NetworkSettingsService::loop() {
|
|||||||
_lastConnectionAttempt = currentMillis;
|
_lastConnectionAttempt = currentMillis;
|
||||||
manageSTA();
|
manageSTA();
|
||||||
}
|
}
|
||||||
static uint8_t connect = 0;
|
|
||||||
enum uint8_t {
|
|
||||||
CONNECT_IDLE = 0,
|
|
||||||
CONNECT_WAIT_ETH,
|
|
||||||
CONNECT_WAIT_IP4,
|
|
||||||
CONNECT_WAIT_ETH_IP4,
|
|
||||||
CONNECT_WAIT_IP6,
|
|
||||||
CONNECT_WAIT_ETH_IP6,
|
|
||||||
CONNECT_ETH_ACTIVE,
|
|
||||||
CONNECT_WIFI_ACTIVE
|
|
||||||
};
|
|
||||||
switch (connect) {
|
|
||||||
default:
|
|
||||||
connect = CONNECT_IDLE;
|
|
||||||
break;
|
|
||||||
case CONNECT_IDLE:
|
|
||||||
if (ETH.started() && _state.ssid.length() == 0) {
|
|
||||||
emsesp::EMSESP::logger().info("ETH started");
|
|
||||||
ETH.enableIPv6(true);
|
|
||||||
if (_state.staticIPConfig) {
|
|
||||||
ETH.config(_state.localIP, _state.gatewayIP, _state.subnetMask, _state.dnsIP1, _state.dnsIP2);
|
|
||||||
}
|
|
||||||
ETH.setHostname(emsesp::EMSESP::system_.hostname().c_str());
|
|
||||||
connect = CONNECT_WAIT_ETH;
|
|
||||||
}
|
|
||||||
if (WiFi.isConnected()) {
|
|
||||||
emsesp::EMSESP::logger().info("Wifi connected");
|
|
||||||
if (_state.tx_power == 0) {
|
|
||||||
setWiFiPowerOnRSSI();
|
|
||||||
}
|
|
||||||
mDNS_start();
|
|
||||||
emsesp::EMSESP::system_.has_ipv6(true);
|
|
||||||
connect = CONNECT_WAIT_IP4;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case CONNECT_WAIT_ETH:
|
|
||||||
if (ETH.connected()) {
|
|
||||||
emsesp::EMSESP::logger().info("ETH connected");
|
|
||||||
emsesp::EMSESP::system_.ethernet_connected(true);
|
|
||||||
mDNS_start();
|
|
||||||
emsesp::EMSESP::system_.has_ipv6(true);
|
|
||||||
connect = CONNECT_WAIT_ETH_IP4;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case CONNECT_WAIT_ETH_IP4:
|
|
||||||
if (ETH.hasIP()) {
|
|
||||||
emsesp::EMSESP::logger().info("ETH IPv4: %s", ETH.localIP().toString().c_str());
|
|
||||||
connect = CONNECT_WAIT_ETH_IP6;
|
|
||||||
}
|
|
||||||
if (!ETH.connected()) {
|
|
||||||
connect = CONNECT_ETH_ACTIVE;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case CONNECT_WAIT_ETH_IP6:
|
|
||||||
if (ETH.hasLinkLocalIPv6() && ETH.hasGlobalIPv6()) {
|
|
||||||
emsesp::EMSESP::system_.has_ipv6(true);
|
|
||||||
connect = CONNECT_ETH_ACTIVE;
|
|
||||||
}
|
|
||||||
if (!ETH.connected()) {
|
|
||||||
connect = CONNECT_ETH_ACTIVE;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case CONNECT_ETH_ACTIVE:
|
|
||||||
if (!ETH.connected()) {
|
|
||||||
emsesp::EMSESP::logger().info("ETH disconnected");
|
|
||||||
emsesp::EMSESP::system_.ethernet_connected(false);
|
|
||||||
emsesp::EMSESP::system_.has_ipv6(false);
|
|
||||||
connect = CONNECT_IDLE;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case CONNECT_WAIT_IP4:
|
|
||||||
if (!WiFi.localIP().toString().isEmpty()) {
|
|
||||||
emsesp::EMSESP::logger().info("Wifi IPv4: %s", WiFi.localIP().toString().c_str());
|
|
||||||
connect = CONNECT_WAIT_IP6;
|
|
||||||
}
|
|
||||||
if (!WiFi.isConnected()) {
|
|
||||||
connect = CONNECT_ETH_ACTIVE;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case CONNECT_WAIT_IP6:
|
|
||||||
if (WiFi.linkLocalIPv6().toString() != "::" && WiFi.globalIPv6().toString() != "::") {
|
|
||||||
emsesp::EMSESP::logger().info("Wifi IPv6: %s, %s", WiFi.linkLocalIPv6().toString().c_str(), WiFi.globalIPv6().toString().c_str());
|
|
||||||
emsesp::EMSESP::system_.has_ipv6(true);
|
|
||||||
connect = CONNECT_WIFI_ACTIVE;
|
|
||||||
}
|
|
||||||
if (!WiFi.isConnected()) {
|
|
||||||
connect = CONNECT_WIFI_ACTIVE;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case CONNECT_WIFI_ACTIVE:
|
|
||||||
if (!WiFi.isConnected()) {
|
|
||||||
emsesp::EMSESP::logger().info("WiFi disconnected");
|
|
||||||
if (_stopping) {
|
|
||||||
_lastConnectionAttempt = 0;
|
|
||||||
_stopping = false;
|
|
||||||
}
|
|
||||||
emsesp::EMSESP::system_.has_ipv6(false);
|
|
||||||
connect = CONNECT_IDLE;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void NetworkSettingsService::manageSTA() {
|
void NetworkSettingsService::manageSTA() {
|
||||||
// Abort if already connected, or if we have no SSID
|
// Abort if already connected, or if we have no SSID
|
||||||
if (WiFi.isConnected() || _state.ssid.length() == 0) {
|
if (WiFi.isConnected() || _state.ssid.length() == 0) {
|
||||||
|
#if ESP_IDF_VERSION_MAJOR >= 5
|
||||||
|
if (_state.ssid.length() == 0) {
|
||||||
|
ETH.enableIPv6(true);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Connect or reconnect as required
|
// Connect or reconnect as required
|
||||||
if ((WiFi.getMode() & WIFI_STA) == 0) {
|
if ((WiFi.getMode() & WIFI_STA) == 0) {
|
||||||
|
#if ESP_IDF_VERSION_MAJOR >= 5
|
||||||
WiFi.enableIPv6(true);
|
WiFi.enableIPv6(true);
|
||||||
|
#endif
|
||||||
if (_state.staticIPConfig) {
|
if (_state.staticIPConfig) {
|
||||||
WiFi.config(_state.localIP, _state.gatewayIP, _state.subnetMask, _state.dnsIP1, _state.dnsIP2); // configure for static IP
|
WiFi.config(_state.localIP, _state.gatewayIP, _state.subnetMask, _state.dnsIP1, _state.dnsIP2); // configure for static IP
|
||||||
}
|
}
|
||||||
@@ -387,7 +293,7 @@ const char * NetworkSettingsService::disconnectReason(uint8_t code) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// handles both WiFI and Ethernet
|
// handles both WiFI and Ethernet
|
||||||
void NetworkSettingsService::WiFiEvent(arduino_event_id_t event, arduino_event_info_t info) {
|
void NetworkSettingsService::WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info) {
|
||||||
#ifndef EMSESP_STANDALONE
|
#ifndef EMSESP_STANDALONE
|
||||||
|
|
||||||
switch (event) {
|
switch (event) {
|
||||||
@@ -399,7 +305,7 @@ void NetworkSettingsService::WiFiEvent(arduino_event_id_t event, arduino_event_i
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case ARDUINO_EVENT_WIFI_STA_DISCONNECTED:
|
case ARDUINO_EVENT_WIFI_STA_DISCONNECTED:
|
||||||
connectcount_ = connectcount_ + 1; // count the number of WiFi reconnects
|
connectcount_++; // count the number of WiFi reconnects
|
||||||
emsesp::EMSESP::logger().warning("WiFi disconnected (#%d). Reason: %s (%d)",
|
emsesp::EMSESP::logger().warning("WiFi disconnected (#%d). Reason: %s (%d)",
|
||||||
connectcount_,
|
connectcount_,
|
||||||
disconnectReason(info.wifi_sta_disconnected.reason),
|
disconnectReason(info.wifi_sta_disconnected.reason),
|
||||||
@@ -454,15 +360,25 @@ void NetworkSettingsService::WiFiEvent(arduino_event_id_t event, arduino_event_i
|
|||||||
if (_state.tx_power == 0) {
|
if (_state.tx_power == 0) {
|
||||||
setWiFiPowerOnRSSI();
|
setWiFiPowerOnRSSI();
|
||||||
}
|
}
|
||||||
|
#if ESP_IDF_VERSION_MAJOR < 5
|
||||||
|
WiFi.enableIpV6(); // force ipv6
|
||||||
|
#endif
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ARDUINO_EVENT_ETH_CONNECTED:
|
case ARDUINO_EVENT_ETH_CONNECTED:
|
||||||
|
#if ESP_IDF_VERSION_MAJOR < 5
|
||||||
|
ETH.enableIpV6(); // force ipv6
|
||||||
|
#endif
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// IPv6 specific - WiFi/Eth
|
// IPv6 specific - WiFi/Eth
|
||||||
case ARDUINO_EVENT_WIFI_STA_GOT_IP6:
|
case ARDUINO_EVENT_WIFI_STA_GOT_IP6:
|
||||||
case ARDUINO_EVENT_ETH_GOT_IP6: {
|
case ARDUINO_EVENT_ETH_GOT_IP6: {
|
||||||
auto ip6 = IPAddress(IPv6, (uint8_t *)info.got_ip6.ip6_info.ip.addr, 0).toString();
|
#if !TASMOTA_SDK && ESP_IDF_VERSION_MAJOR < 5
|
||||||
|
auto ip6 = IPv6Address((uint8_t *)info.got_ip6.ip6_info.ip.addr).toString();
|
||||||
|
#else
|
||||||
|
auto ip6 = IPAddress(IPv6, (uint8_t *)info.got_ip6.ip6_info.ip.addr, 0).toString();
|
||||||
|
#endif
|
||||||
const char * link = event == ARDUINO_EVENT_ETH_GOT_IP6 ? "Eth" : "WiFi";
|
const char * link = event == ARDUINO_EVENT_ETH_GOT_IP6 ? "Eth" : "WiFi";
|
||||||
if (ip6.startsWith("fe80")) {
|
if (ip6.startsWith("fe80")) {
|
||||||
emsesp::EMSESP::logger().info("IPv6 (%s) local: %s", link, ip6.c_str());
|
emsesp::EMSESP::logger().info("IPv6 (%s) local: %s", link, ip6.c_str());
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ class NetworkSettingsService : public StatefulService<NetworkSettings> {
|
|||||||
|
|
||||||
volatile uint16_t connectcount_ = 0; // number of wifi reconnects
|
volatile uint16_t connectcount_ = 0; // number of wifi reconnects
|
||||||
|
|
||||||
void WiFiEvent(arduino_event_id_t event, arduino_event_info_t info);
|
void WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info);
|
||||||
void mDNS_start() const;
|
void mDNS_start() const;
|
||||||
const char * disconnectReason(uint8_t code);
|
const char * disconnectReason(uint8_t code);
|
||||||
void reconfigureWiFiConnection();
|
void reconfigureWiFiConnection();
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
#include <emsesp.h>
|
#include <emsesp.h>
|
||||||
|
|
||||||
#ifdef NO_TLS_SUPPORT
|
#ifdef TASMOTA_SDK
|
||||||
#include "lwip/dns.h"
|
#include "lwip/dns.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -31,13 +31,22 @@ void NetworkStatus::networkStatus(AsyncWebServerRequest * request) {
|
|||||||
// for both connections show ethernet
|
// for both connections show ethernet
|
||||||
if (ethernet_connected) {
|
if (ethernet_connected) {
|
||||||
// Ethernet
|
// Ethernet
|
||||||
root["local_ip"] = ETH.localIP().toString();
|
root["local_ip"] = ETH.localIP().toString();
|
||||||
root["local_ipv6"] = ETH.linkLocalIPv6().toString();
|
#if ESP_IDF_VERSION_MAJOR < 5
|
||||||
|
root["local_ipv6"] = ETH.localIPv6().toString();
|
||||||
|
#else
|
||||||
|
root["local_ipv6"] = ETH.linkLocalIPv6().toString();
|
||||||
|
#endif
|
||||||
root["mac_address"] = ETH.macAddress();
|
root["mac_address"] = ETH.macAddress();
|
||||||
root["subnet_mask"] = ETH.subnetMask().toString();
|
root["subnet_mask"] = ETH.subnetMask().toString();
|
||||||
root["gateway_ip"] = ETH.gatewayIP().toString();
|
root["gateway_ip"] = ETH.gatewayIP().toString();
|
||||||
IPAddress dnsIP1 = ETH.dnsIP(0);
|
#ifdef TASMOTA_SDK
|
||||||
IPAddress dnsIP2 = ETH.dnsIP(1);
|
IPAddress dnsIP1 = IPAddress(dns_getserver(0));
|
||||||
|
IPAddress dnsIP2 = IPAddress(dns_getserver(1));
|
||||||
|
#else
|
||||||
|
IPAddress dnsIP1 = ETH.dnsIP(0);
|
||||||
|
IPAddress dnsIP2 = ETH.dnsIP(1);
|
||||||
|
#endif
|
||||||
if (IPUtils::isSet(dnsIP1)) {
|
if (IPUtils::isSet(dnsIP1)) {
|
||||||
root["dns_ip_1"] = dnsIP1.toString();
|
root["dns_ip_1"] = dnsIP1.toString();
|
||||||
}
|
}
|
||||||
@@ -45,8 +54,12 @@ void NetworkStatus::networkStatus(AsyncWebServerRequest * request) {
|
|||||||
root["dns_ip_2"] = dnsIP2.toString();
|
root["dns_ip_2"] = dnsIP2.toString();
|
||||||
}
|
}
|
||||||
} else if (wifi_status == WL_CONNECTED) {
|
} else if (wifi_status == WL_CONNECTED) {
|
||||||
root["local_ip"] = WiFi.localIP().toString();
|
root["local_ip"] = WiFi.localIP().toString();
|
||||||
root["local_ipv6"] = WiFi.linkLocalIPv6().toString();
|
#if ESP_IDF_VERSION_MAJOR < 5
|
||||||
|
root["local_ipv6"] = WiFi.localIPv6().toString();
|
||||||
|
#else
|
||||||
|
root["local_ipv6"] = WiFi.linkLocalIPv6().toString();
|
||||||
|
#endif
|
||||||
root["mac_address"] = WiFi.macAddress();
|
root["mac_address"] = WiFi.macAddress();
|
||||||
root["rssi"] = WiFi.RSSI();
|
root["rssi"] = WiFi.RSSI();
|
||||||
root["ssid"] = WiFi.SSID();
|
root["ssid"] = WiFi.SSID();
|
||||||
@@ -58,8 +71,14 @@ void NetworkStatus::networkStatus(AsyncWebServerRequest * request) {
|
|||||||
if (WiFi.gatewayIP() != INADDR_NONE) {
|
if (WiFi.gatewayIP() != INADDR_NONE) {
|
||||||
root["gateway_ip"] = WiFi.gatewayIP().toString();
|
root["gateway_ip"] = WiFi.gatewayIP().toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef TASMOTA_SDK
|
||||||
|
IPAddress dnsIP1 = IPAddress(dns_getserver(0));
|
||||||
|
IPAddress dnsIP2 = IPAddress(dns_getserver(1));
|
||||||
|
#else
|
||||||
IPAddress dnsIP1 = WiFi.dnsIP(0);
|
IPAddress dnsIP1 = WiFi.dnsIP(0);
|
||||||
IPAddress dnsIP2 = WiFi.dnsIP(1);
|
IPAddress dnsIP2 = WiFi.dnsIP(1);
|
||||||
|
#endif
|
||||||
if (dnsIP1 != INADDR_NONE) {
|
if (dnsIP1 != INADDR_NONE) {
|
||||||
root["dns_ip_1"] = dnsIP1.toString();
|
root["dns_ip_1"] = dnsIP1.toString();
|
||||||
}
|
}
|
||||||
|
|||||||
126
src/core/EMSESP_Version.h
Normal file
126
src/core/EMSESP_Version.h
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
/*
|
||||||
|
* EMS-ESP - https://github.com/emsesp/EMS-ESP
|
||||||
|
* Copyright 2020-2026 emsesp.org
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef EMSESP_Version_H
|
||||||
|
#define EMSESP_Version_H
|
||||||
|
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstring>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
// Drop-in lightweight replacement for the subset of the semver library actually used by EMS-ESP.
|
||||||
|
// The previous semver library (lib/semver) builds a std::map + std::function-based state machine on
|
||||||
|
// every parse, which fragments the internal heap on the ESP32. This replacement does no heap
|
||||||
|
// allocation beyond the std::string member for the prerelease tag, and matches the API surface
|
||||||
|
// we consume: construction from string, major()/minor()/patch()/prerelease(), and operator>/</==.
|
||||||
|
//
|
||||||
|
// Only strict numeric precedence (major.minor.patch) is used for comparison in EMS-ESP, so we
|
||||||
|
// intentionally ignore prerelease tags during comparison rather than implement the full semver
|
||||||
|
// ordering rules. This is consistent with how the old code was used (callers only check major/
|
||||||
|
// minor/patch numerically; prerelease() is only read for logging).
|
||||||
|
|
||||||
|
namespace version {
|
||||||
|
|
||||||
|
class EMSESP_Version {
|
||||||
|
public:
|
||||||
|
EMSESP_Version() = default;
|
||||||
|
|
||||||
|
// Construct from a version string like "3.9.0-dev.14" or "3.9.0".
|
||||||
|
// Anything past a '-' or '+' is kept as the prerelease string and not interpreted.
|
||||||
|
explicit EMSESP_Version(const std::string & s) {
|
||||||
|
parse(s.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
explicit EMSESP_Version(const char * s) {
|
||||||
|
parse(s ? s : "");
|
||||||
|
}
|
||||||
|
|
||||||
|
int major() const {
|
||||||
|
return major_;
|
||||||
|
}
|
||||||
|
int minor() const {
|
||||||
|
return minor_;
|
||||||
|
}
|
||||||
|
int patch() const {
|
||||||
|
return patch_;
|
||||||
|
}
|
||||||
|
const std::string & prerelease() const {
|
||||||
|
return prerelease_;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Numeric-only comparison (major.minor.patch). Prerelease tags are ignored on purpose.
|
||||||
|
friend bool operator<(const EMSESP_Version & a, const EMSESP_Version & b) {
|
||||||
|
if (a.major_ != b.major_)
|
||||||
|
return a.major_ < b.major_;
|
||||||
|
if (a.minor_ != b.minor_)
|
||||||
|
return a.minor_ < b.minor_;
|
||||||
|
if (a.patch_ != b.patch_)
|
||||||
|
return a.patch_ < b.patch_;
|
||||||
|
return a.prerelease_ < b.prerelease_;
|
||||||
|
}
|
||||||
|
|
||||||
|
friend bool operator>(const EMSESP_Version & a, const EMSESP_Version & b) {
|
||||||
|
return b < a;
|
||||||
|
}
|
||||||
|
|
||||||
|
friend bool operator==(const EMSESP_Version & a, const EMSESP_Version & b) {
|
||||||
|
return a.major_ == b.major_ && a.minor_ == b.minor_ && a.patch_ == b.patch_ && a.prerelease_ == b.prerelease_;
|
||||||
|
}
|
||||||
|
|
||||||
|
friend bool operator!=(const EMSESP_Version & a, const EMSESP_Version & b) {
|
||||||
|
return !(a == b);
|
||||||
|
}
|
||||||
|
|
||||||
|
friend bool operator>=(const EMSESP_Version & a, const EMSESP_Version & b) {
|
||||||
|
return !(a < b);
|
||||||
|
}
|
||||||
|
|
||||||
|
friend bool operator<=(const EMSESP_Version & a, const EMSESP_Version & b) {
|
||||||
|
return !(b < a);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
int major_ = 0;
|
||||||
|
int minor_ = 0;
|
||||||
|
int patch_ = 0;
|
||||||
|
std::string prerelease_;
|
||||||
|
|
||||||
|
void parse(const char * s) {
|
||||||
|
major_ = minor_ = patch_ = 0;
|
||||||
|
prerelease_.clear();
|
||||||
|
if (s == nullptr || *s == '\0') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// parse numeric major.minor.patch; accept partial ("3", "3.9", "3.9.0")
|
||||||
|
sscanf(s, "%d.%d.%d", &major_, &minor_, &patch_);
|
||||||
|
// capture prerelease tag after '-' if present (stop at '+' which is build metadata)
|
||||||
|
const char * dash = strchr(s, '-');
|
||||||
|
if (dash != nullptr) {
|
||||||
|
const char * plus = strchr(dash, '+');
|
||||||
|
if (plus != nullptr) {
|
||||||
|
prerelease_.assign(dash + 1, plus - dash - 1);
|
||||||
|
} else {
|
||||||
|
prerelease_.assign(dash + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace version
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -347,13 +347,23 @@ void AnalogSensor::reload(bool get_nvs) {
|
|||||||
sensor.polltime_ = sensor.value() != 0 ? uuid::get_uptime() + (sensor.factor() * 1000) : 0;
|
sensor.polltime_ = sensor.value() != 0 ? uuid::get_uptime() + (sensor.factor() * 1000) : 0;
|
||||||
} else if (sensor.type() >= AnalogType::PWM_0 && sensor.type() <= AnalogType::PWM_2) {
|
} else if (sensor.type() >= AnalogType::PWM_0 && sensor.type() <= AnalogType::PWM_2) {
|
||||||
LOG_DEBUG("PWM output on GPIO %02d", sensor.gpio());
|
LOG_DEBUG("PWM output on GPIO %02d", sensor.gpio());
|
||||||
|
#if ESP_IDF_VERSION_MAJOR >= 5
|
||||||
ledcAttach(sensor.gpio(), sensor.factor(), 13);
|
ledcAttach(sensor.gpio(), sensor.factor(), 13);
|
||||||
|
#else
|
||||||
|
uint8_t channel = sensor.type() - AnalogType::PWM_0;
|
||||||
|
ledcSetup(channel, sensor.factor(), 13);
|
||||||
|
ledcAttachPin(sensor.gpio(), channel);
|
||||||
|
#endif
|
||||||
if (sensor.offset() > 100) {
|
if (sensor.offset() > 100) {
|
||||||
sensor.set_offset(100);
|
sensor.set_offset(100);
|
||||||
} else if (sensor.offset() < 0) {
|
} else if (sensor.offset() < 0) {
|
||||||
sensor.set_offset(0);
|
sensor.set_offset(0);
|
||||||
}
|
}
|
||||||
|
#if ESP_IDF_VERSION_MAJOR >= 5
|
||||||
ledcWrite(sensor.gpio(), (uint32_t)(sensor.offset() * 8191 / 100));
|
ledcWrite(sensor.gpio(), (uint32_t)(sensor.offset() * 8191 / 100));
|
||||||
|
#else
|
||||||
|
ledcWrite(channel, (uint32_t)(sensor.offset() * 8191 / 100));
|
||||||
|
#endif
|
||||||
sensor.set_value(sensor.offset());
|
sensor.set_value(sensor.offset());
|
||||||
sensor.set_uom(DeviceValueUOM::PERCENT);
|
sensor.set_uom(DeviceValueUOM::PERCENT);
|
||||||
publish_sensor(sensor);
|
publish_sensor(sensor);
|
||||||
@@ -1031,7 +1041,12 @@ bool AnalogSensor::command_setvalue(const char * value, const int8_t gpio) {
|
|||||||
}
|
}
|
||||||
sensor.set_offset(val);
|
sensor.set_offset(val);
|
||||||
sensor.set_value(val);
|
sensor.set_value(val);
|
||||||
|
#if ESP_IDF_VERSION_MAJOR >= 5
|
||||||
ledcWrite(sensor.gpio(), (uint32_t)(sensor.offset() * 8191 / 100));
|
ledcWrite(sensor.gpio(), (uint32_t)(sensor.offset() * 8191 / 100));
|
||||||
|
#else
|
||||||
|
uint8_t channel = sensor.type() - AnalogType::PWM_0;
|
||||||
|
ledcWrite(channel, (uint32_t)(val * 8191 / 100));
|
||||||
|
#endif
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -285,8 +285,6 @@ enum {
|
|||||||
#define EMSESP_PLATFORM "ESP32S3"
|
#define EMSESP_PLATFORM "ESP32S3"
|
||||||
#elif CONFIG_IDF_TARGET_ESP32 || EMSESP_STANDALONE
|
#elif CONFIG_IDF_TARGET_ESP32 || EMSESP_STANDALONE
|
||||||
#define EMSESP_PLATFORM "ESP32"
|
#define EMSESP_PLATFORM "ESP32"
|
||||||
#elif CONFIG_IDF_TARGET_ESP32C6
|
|
||||||
#define EMSESP_PLATFORM "ESP32C6"
|
|
||||||
#else
|
#else
|
||||||
#error Target CONFIG_IDF_TARGET is not supported
|
#error Target CONFIG_IDF_TARGET is not supported
|
||||||
#endif
|
#endif
|
||||||
@@ -295,9 +293,12 @@ enum {
|
|||||||
#ifndef STRINGIZE
|
#ifndef STRINGIZE
|
||||||
#define STRINGIZE(s) #s
|
#define STRINGIZE(s) #s
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef TASMOTA_SDK
|
||||||
#define ARDUINO_VERSION_STR(major, minor, patch) "Tasmota Arduino v" STRINGIZE(major) "." STRINGIZE(minor) "." STRINGIZE(patch)
|
#define ARDUINO_VERSION_STR(major, minor, patch) "Tasmota Arduino v" STRINGIZE(major) "." STRINGIZE(minor) "." STRINGIZE(patch)
|
||||||
|
#else
|
||||||
|
#define ARDUINO_VERSION_STR(major, minor, patch) "ESP32 Arduino v" STRINGIZE(major) "." STRINGIZE(minor) "." STRINGIZE(patch)
|
||||||
|
#endif
|
||||||
#define ARDUINO_VERSION ARDUINO_VERSION_STR(ESP_ARDUINO_VERSION_MAJOR, ESP_ARDUINO_VERSION_MINOR, ESP_ARDUINO_VERSION_PATCH)
|
#define ARDUINO_VERSION ARDUINO_VERSION_STR(ESP_ARDUINO_VERSION_MAJOR, ESP_ARDUINO_VERSION_MINOR, ESP_ARDUINO_VERSION_PATCH)
|
||||||
|
#endif
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
#endif
|
|
||||||
|
|||||||
@@ -387,10 +387,10 @@ void EMSdevice::toggle_fetch(uint16_t telegram_id, bool toggle) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// get status of automatic fetch for a telegramID
|
// get status of automatic fetch for a telegramID
|
||||||
bool EMSdevice::is_fetch(uint16_t telegram_id) const {
|
bool EMSdevice::is_fetch(uint16_t telegram_id, uint8_t len) const {
|
||||||
for (const auto & tf : telegram_functions_) {
|
for (const auto & tf : telegram_functions_) {
|
||||||
if (tf.telegram_type_id_ == telegram_id) {
|
if (tf.telegram_type_id_ == telegram_id) {
|
||||||
return tf.fetch_;
|
return tf.fetch_ && tf.length_ >= len;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -362,7 +362,7 @@ class EMSdevice {
|
|||||||
const char * telegram_type_name(std::shared_ptr<const Telegram> telegram);
|
const char * telegram_type_name(std::shared_ptr<const Telegram> telegram);
|
||||||
void fetch_values();
|
void fetch_values();
|
||||||
void toggle_fetch(uint16_t telegram_id, bool toggle);
|
void toggle_fetch(uint16_t telegram_id, bool toggle);
|
||||||
bool is_fetch(uint16_t telegram_id) const;
|
bool is_fetch(uint16_t telegram_id, uint8_t len = 0) const;
|
||||||
bool is_received(uint16_t telegram_id) const;
|
bool is_received(uint16_t telegram_id) const;
|
||||||
bool has_telegram_id(uint16_t id) const;
|
bool has_telegram_id(uint16_t id) const;
|
||||||
void ha_config_clear();
|
void ha_config_clear();
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
#ifndef EMSESP_EMSFACTORY_H_
|
#ifndef EMSESP_EMSFACTORY_H_
|
||||||
#define EMSESP_EMSFACTORY_H_
|
#define EMSESP_EMSFACTORY_H_
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
#include <memory> // for unique_ptr
|
#include <memory> // for unique_ptr
|
||||||
#include <map>
|
#include <map>
|
||||||
|
|
||||||
|
|||||||
@@ -380,15 +380,11 @@ std::string commands(std::string & expr, bool quotes) {
|
|||||||
if (return_code != CommandRet::OK && return_code != CommandRet::NO_VALUE) {
|
if (return_code != CommandRet::OK && return_code != CommandRet::NO_VALUE) {
|
||||||
return expr = "";
|
return expr = "";
|
||||||
}
|
}
|
||||||
std::string data;
|
|
||||||
if (output["api_data"].is<std::string>()) {
|
std::string data = output["api_data"] | "";
|
||||||
data = output["api_data"].as<std::string>();
|
if (!isnum(data) && quotes) {
|
||||||
if (!isnum(data) && quotes) {
|
data.insert(data.begin(), '"');
|
||||||
data.insert(data.begin(), '"');
|
data.insert(data.end(), '"');
|
||||||
data.insert(data.end(), '"');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
serializeJson(output, data);
|
|
||||||
}
|
}
|
||||||
expr.replace(f, l, data);
|
expr.replace(f, l, data);
|
||||||
e = f + data.length();
|
e = f + data.length();
|
||||||
@@ -704,7 +700,8 @@ std::string compute(const std::string & expr) {
|
|||||||
std::string cmd = expr_new.substr(f, e - f).c_str();
|
std::string cmd = expr_new.substr(f, e - f).c_str();
|
||||||
JsonDocument doc;
|
JsonDocument doc;
|
||||||
if (DeserializationError::Ok == deserializeJson(doc, cmd)) {
|
if (DeserializationError::Ok == deserializeJson(doc, cmd)) {
|
||||||
std::string url, header_s, value_s, method_s, key_s, keys_s;
|
HTTPClient * http = new HTTPClient;
|
||||||
|
std::string url, header_s, value_s, method_s, key_s, keys_s;
|
||||||
// search keys lower case
|
// search keys lower case
|
||||||
for (JsonPair p : doc.as<JsonObject>()) {
|
for (JsonPair p : doc.as<JsonObject>()) {
|
||||||
if (Helpers::toLower(p.key().c_str()) == "url") {
|
if (Helpers::toLower(p.key().c_str()) == "url") {
|
||||||
@@ -723,150 +720,58 @@ std::string compute(const std::string & expr) {
|
|||||||
keys_s = p.key().c_str();
|
keys_s = p.key().c_str();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
bool content_set = false;
|
if (http->begin(url.c_str())) {
|
||||||
std::string value = doc[value_s] | "";
|
int httpResult = 0;
|
||||||
std::string method = doc[method_s] | "GET";
|
for (JsonPair p : doc[header_s].as<JsonObject>()) {
|
||||||
if (value.length()) {
|
http->addHeader(p.key().c_str(), p.value().as<std::string>().c_str());
|
||||||
method = "POST";
|
|
||||||
}
|
|
||||||
std::string result;
|
|
||||||
int httpResult = 0;
|
|
||||||
#ifndef NO_TLS_SUPPORT
|
|
||||||
if (Helpers::toLower(url.c_str()).starts_with("https://")) {
|
|
||||||
WiFiClient * basic_client = new WiFiClient;
|
|
||||||
ESP_SSLClient * ssl_client = new ESP_SSLClient;
|
|
||||||
ssl_client->setInsecure(); // with root CA we should set here: ssl_client->setCACert(rootCACert);
|
|
||||||
ssl_client->setBufferSizes(1024, 1024);
|
|
||||||
ssl_client->setSessionTimeout(120); // Set the timeout in seconds (>=120 seconds)
|
|
||||||
url.replace(0, 8, "");
|
|
||||||
std::string host = url;
|
|
||||||
auto index = url.find_first_of('/');
|
|
||||||
if (index != std::string::npos) {
|
|
||||||
host = url.substr(0, index);
|
|
||||||
url.replace(0, index, "");
|
|
||||||
}
|
}
|
||||||
/*
|
std::string value = doc[value_s] | "";
|
||||||
index = host.find_first_of('@');
|
std::string method = doc[method_s] | "get";
|
||||||
std::string auth;
|
|
||||||
if (index != std::string::npos) {
|
// if there is data, force a POST
|
||||||
auth = base64::encode(host.substr(0, index));
|
if (value.length() || Helpers::toLower(method) == "post") {
|
||||||
host.replace(0, index, "");
|
if (value.find_first_of('{') != std::string::npos) {
|
||||||
|
http->addHeader(asyncsrv::T_Content_Type, asyncsrv::T_application_json, false); // auto-set to JSON
|
||||||
|
}
|
||||||
|
httpResult = http->POST(value.c_str());
|
||||||
|
} else {
|
||||||
|
httpResult = http->GET(); // normal GET
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
ssl_client->setClient(basic_client);
|
if (httpResult > 0) {
|
||||||
if (ssl_client->connect(host.c_str(), 443)) {
|
std::string result = http->getString().c_str();
|
||||||
if (value.length() || Helpers::toLower(method) == "post") {
|
std::string key = doc[key_s] | "";
|
||||||
ssl_client->print("POST ");
|
JsonDocument keys_doc; // JsonDocument to hold "keys" after doc is parsed with HTTP body
|
||||||
ssl_client->print(url.c_str());
|
if (doc[keys_s].is<JsonArray>()) {
|
||||||
ssl_client->println(" HTTP/1.1");
|
keys_doc.set(doc[keys_s].as<JsonArray>());
|
||||||
ssl_client->print("Host: ");
|
}
|
||||||
ssl_client->println(host.c_str());
|
JsonArray keys = keys_doc.as<JsonArray>();
|
||||||
for (JsonPair p : doc[header_s].as<JsonObject>()) {
|
|
||||||
content_set |= (emsesp::Helpers::toLower(p.key().c_str()) == "content-type");
|
if (key.length() || !keys.isNull()) {
|
||||||
ssl_client->print(p.key().c_str());
|
doc.clear();
|
||||||
ssl_client->print(": ");
|
if (DeserializationError::Ok == deserializeJson(doc, result)) {
|
||||||
ssl_client->println(p.value().as<std::string>().c_str());
|
if (key.length()) {
|
||||||
}
|
result = doc[key.c_str()].as<std::string>();
|
||||||
if (!content_set) {
|
|
||||||
ssl_client->print("Content-Type: ");
|
|
||||||
if (value.starts_with('{')) {
|
|
||||||
ssl_client->println(asyncsrv::T_application_json);
|
|
||||||
} else {
|
} else {
|
||||||
ssl_client->println(asyncsrv::T_text_plain);
|
JsonVariant json = doc.as<JsonVariant>();
|
||||||
|
for (JsonVariant keys_key : keys) {
|
||||||
|
if (keys_key.is<std::string>() && json.is<JsonObject>()) {
|
||||||
|
json = json[keys_key.as<std::string>()].as<JsonVariant>();
|
||||||
|
} else if (keys_key.is<int>() && json.is<JsonArray>()) {
|
||||||
|
json = json[keys_key.as<int>()].as<JsonVariant>();
|
||||||
|
} else {
|
||||||
|
break; // type mismatch
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result = json.as<std::string>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ssl_client->print("Content-Length: ");
|
|
||||||
ssl_client->println(value.length());
|
|
||||||
ssl_client->println("Connection: close");
|
|
||||||
ssl_client->print("\r\n");
|
|
||||||
ssl_client->print(value.c_str());
|
|
||||||
} else {
|
|
||||||
ssl_client->print("GET ");
|
|
||||||
ssl_client->print(url.c_str());
|
|
||||||
ssl_client->println(" HTTP/1.1");
|
|
||||||
ssl_client->print("Host: ");
|
|
||||||
ssl_client->println(host.c_str());
|
|
||||||
for (JsonPair p : doc[header_s].as<JsonObject>()) {
|
|
||||||
ssl_client->print(p.key().c_str());
|
|
||||||
ssl_client->print(": ");
|
|
||||||
ssl_client->println(p.value().as<std::string>().c_str());
|
|
||||||
}
|
|
||||||
ssl_client->println("Connection: close");
|
|
||||||
}
|
|
||||||
auto ms = millis();
|
|
||||||
while (!ssl_client->available() && millis() - ms < 3000) {
|
|
||||||
delay(0);
|
|
||||||
}
|
|
||||||
while (ssl_client->available()) {
|
|
||||||
result += (char)ssl_client->read();
|
|
||||||
}
|
|
||||||
ssl_client->stop();
|
|
||||||
index = result.find_first_of(' ');
|
|
||||||
if (index != std::string::npos) {
|
|
||||||
httpResult = stoi(result.substr(index + 1, 3));
|
|
||||||
}
|
|
||||||
index = result.find("\r\n\r\n");
|
|
||||||
if (index != std::string::npos) {
|
|
||||||
result.replace(0, index + 4, "");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
delete ssl_client;
|
|
||||||
delete basic_client;
|
|
||||||
} else
|
|
||||||
#endif
|
|
||||||
if (Helpers::toLower(url.c_str()).starts_with("http://")) {
|
|
||||||
HTTPClient * http = new HTTPClient;
|
|
||||||
if (http->begin(url.c_str())) {
|
|
||||||
for (JsonPair p : doc[header_s].as<JsonObject>()) {
|
|
||||||
http->addHeader(p.key().c_str(), p.value().as<std::string>().c_str());
|
|
||||||
content_set |= (emsesp::Helpers::toLower(p.key().c_str()) == "content-type");
|
|
||||||
}
|
|
||||||
if (value.length() || Helpers::toLower(method) == "post") {
|
|
||||||
if (!content_set) {
|
|
||||||
http->addHeader("Content-Type", value.starts_with('{') ? asyncsrv::T_application_json : asyncsrv::T_text_plain);
|
|
||||||
}
|
|
||||||
httpResult = http->POST(value.c_str());
|
|
||||||
} else {
|
|
||||||
httpResult = http->GET(); // normal GET
|
|
||||||
}
|
|
||||||
if (httpResult > 0) {
|
|
||||||
result = http->getString().c_str();
|
|
||||||
}
|
}
|
||||||
|
expr_new.replace(f, e - f, result.c_str());
|
||||||
}
|
}
|
||||||
http->end();
|
http->end();
|
||||||
delete http;
|
delete http;
|
||||||
}
|
}
|
||||||
if (httpResult == 200) {
|
|
||||||
std::string key = doc[key_s] | "";
|
|
||||||
JsonDocument keys_doc; // JsonDocument to hold "keys" after doc is parsed with HTTP body
|
|
||||||
if (doc[keys_s].is<JsonArray>()) {
|
|
||||||
keys_doc.set(doc[keys_s].as<JsonArray>());
|
|
||||||
}
|
|
||||||
JsonArray keys = keys_doc.as<JsonArray>();
|
|
||||||
if (key.length() || !keys.isNull()) {
|
|
||||||
doc.clear();
|
|
||||||
if (DeserializationError::Ok == deserializeJson(doc, result)) {
|
|
||||||
if (key.length()) {
|
|
||||||
result = doc[key.c_str()].as<std::string>();
|
|
||||||
} else {
|
|
||||||
JsonVariant json = doc.as<JsonVariant>();
|
|
||||||
for (JsonVariant keys_key : keys) {
|
|
||||||
if (keys_key.is<std::string>() && json.is<JsonObject>()) {
|
|
||||||
json = json[keys_key.as<std::string>()].as<JsonVariant>();
|
|
||||||
} else if (keys_key.is<int>() && json.is<JsonArray>()) {
|
|
||||||
json = json[keys_key.as<int>()].as<JsonVariant>();
|
|
||||||
} else {
|
|
||||||
break; // type mismatch
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result = json.as<std::string>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
expr_new.replace(f, e - f, result);
|
|
||||||
} else if (httpResult != 0) {
|
|
||||||
EMSESP::logger().warning("URL command failed with https code: %d, response: %s", httpResult, result.c_str());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
f = expr_new.find_first_of('{', e);
|
f = expr_new.find_first_of('{', e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,27 +26,17 @@
|
|||||||
#include <esp_mac.h>
|
#include <esp_mac.h>
|
||||||
#include "esp_efuse.h"
|
#include "esp_efuse.h"
|
||||||
#include <nvs.h>
|
#include <nvs.h>
|
||||||
#include <mbedtls/base64.h>
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <HTTPClient.h>
|
#include <HTTPClient.h>
|
||||||
#include <map>
|
#include <map>
|
||||||
|
|
||||||
#include <semver200.h>
|
#include "EMSESP_Version.h"
|
||||||
|
|
||||||
#if defined(EMSESP_TEST)
|
#if defined(EMSESP_TEST)
|
||||||
#include "../test/test.h"
|
#include "../test/test.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef NO_TLS_SUPPORT
|
|
||||||
#define ENABLE_SMTP
|
|
||||||
#define USE_ESP_SSLCLIENT
|
|
||||||
#define READYCLIENT_SSL_CLIENT ESP_SSLClient
|
|
||||||
#define READYCLIENT_TYPE_1 // TYPE 1 when using ESP_SSLClient
|
|
||||||
#include <ESP_SSLClient.h>
|
|
||||||
#include <ReadyMail.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace emsesp {
|
namespace emsesp {
|
||||||
|
|
||||||
// Languages supported. Note: the order is important
|
// Languages supported. Note: the order is important
|
||||||
@@ -115,115 +105,6 @@ bool System::command_send(const char * value, const int8_t id) {
|
|||||||
return EMSESP::txservice_.send_raw(value); // ignore id
|
return EMSESP::txservice_.send_raw(value); // ignore id
|
||||||
}
|
}
|
||||||
|
|
||||||
bool System::command_sendmail(const char * value, const int8_t id) {
|
|
||||||
bool enabled = false;
|
|
||||||
bool ssl, starttls;
|
|
||||||
uint16_t port;
|
|
||||||
String server, login, pass, sender, recp, subject;
|
|
||||||
EMSESP::webSettingsService.read([&](WebSettings & settings) {
|
|
||||||
enabled = settings.email_enabled;
|
|
||||||
ssl = settings.email_ssl;
|
|
||||||
starttls = settings.email_starttls;
|
|
||||||
server = settings.email_server;
|
|
||||||
port = settings.email_port;
|
|
||||||
login = settings.email_login;
|
|
||||||
pass = settings.email_pass;
|
|
||||||
sender = settings.email_sender;
|
|
||||||
recp = settings.email_recp;
|
|
||||||
subject = settings.email_subject;
|
|
||||||
});
|
|
||||||
if (!enabled) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
LOG_DEBUG("Command sendmail port %d%s called with '%s'", port, ssl ? " (SSL)" : starttls ? " (STARTTLS)" : "", value);
|
|
||||||
// LOG_DEBUG("Command sendmail port %d called with '%s'", port, value);
|
|
||||||
bool success = false;
|
|
||||||
|
|
||||||
#ifndef NO_TLS_SUPPORT
|
|
||||||
WiFiClient * basic_client = new WiFiClient;
|
|
||||||
ESP_SSLClient * ssl_client = new ESP_SSLClient;
|
|
||||||
ReadyClient * r_client = new ReadyClient(*ssl_client);
|
|
||||||
SMTPClient * smtp = new SMTPClient(*r_client);
|
|
||||||
|
|
||||||
ssl_client->setClient(basic_client);
|
|
||||||
ssl_client->setInsecure();
|
|
||||||
ssl_client->setBufferSizes(1024, 1024);
|
|
||||||
r_client->addPort(port, starttls ? readymail_protocol_tls : ssl ? readymail_protocol_ssl : readymail_protocol_plain_text);
|
|
||||||
|
|
||||||
// smtp->connect(server, port, sendmailCallback);
|
|
||||||
smtp->connect(server, port);
|
|
||||||
if (!smtp->isConnected()) {
|
|
||||||
LOG_ERROR("Sendmail connection error");
|
|
||||||
delete smtp;
|
|
||||||
delete r_client;
|
|
||||||
delete ssl_client;
|
|
||||||
delete basic_client;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// LOG_INFO("authenticate %s:%s", login.c_str(), pass.c_str());
|
|
||||||
smtp->authenticate(login, pass, readymail_auth_password);
|
|
||||||
if (!smtp->isAuthenticated()) {
|
|
||||||
LOG_ERROR("Sendmail authenticate error");
|
|
||||||
delete smtp;
|
|
||||||
delete r_client;
|
|
||||||
delete ssl_client;
|
|
||||||
delete basic_client;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
JsonDocument doc;
|
|
||||||
String body = value;
|
|
||||||
if (body.length()) {
|
|
||||||
auto error = deserializeJson(doc, (const char *)value);
|
|
||||||
if (!error && doc.as<JsonObject>().size() >= 0) {
|
|
||||||
subject = doc["subject"] | subject;
|
|
||||||
recp = doc["to"] | recp;
|
|
||||||
sender = doc["from"] | sender;
|
|
||||||
body = doc["body"] | body;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SMTPMessage & msg = smtp->getMessage();
|
|
||||||
msg.headers.add(rfc822_subject, subject);
|
|
||||||
msg.headers.add(rfc822_from, sender);
|
|
||||||
msg.headers.add(rfc822_to, recp);
|
|
||||||
|
|
||||||
// Use addCustom to add custom header e.g. Importance and Priority.
|
|
||||||
// msg.headers.addCustom("Importance", PRIORITY);
|
|
||||||
// msg.headers.addCustom("X-MSMail-Priority", PRIORITY);
|
|
||||||
// msg.headers.addCustom("X-Priority", PRIORITY_NUM);
|
|
||||||
EMSESP::webSchedulerService.computed_value.clear();
|
|
||||||
EMSESP::webSchedulerService.raw_value = body.c_str();
|
|
||||||
for (uint16_t wait = 0; wait < 2000 && !EMSESP::webSchedulerService.raw_value.empty(); wait++) {
|
|
||||||
delay(1);
|
|
||||||
}
|
|
||||||
if (!EMSESP::webSchedulerService.computed_value.empty()) {
|
|
||||||
body = EMSESP::webSchedulerService.computed_value.c_str();
|
|
||||||
EMSESP::webSchedulerService.computed_value.clear();
|
|
||||||
EMSESP::webSchedulerService.computed_value.shrink_to_fit(); // free allocated memory
|
|
||||||
}
|
|
||||||
msg.text.body(body);
|
|
||||||
|
|
||||||
// bodyText.replace("\r\n", "<br>\r\n");
|
|
||||||
// msg.html.body("<html><body><div style=\"color:#cc0066;\">" + bodyText + "</div></body></html>");
|
|
||||||
// msg.html.transferEncoding("base64");
|
|
||||||
|
|
||||||
// With embedFile function, the html message will send as attachment.
|
|
||||||
// if (EMBED_MESSAGE)
|
|
||||||
// msg.html.embedFile(true, "msg.html", embed_message_type_attachment);
|
|
||||||
|
|
||||||
msg.timestamp = time(nullptr);
|
|
||||||
|
|
||||||
success = smtp->send(msg);
|
|
||||||
|
|
||||||
delete smtp;
|
|
||||||
delete r_client;
|
|
||||||
delete ssl_client;
|
|
||||||
delete basic_client;
|
|
||||||
#endif
|
|
||||||
return success;
|
|
||||||
}
|
|
||||||
|
|
||||||
// return string of languages and count
|
// return string of languages and count
|
||||||
std::string System::languages_string() {
|
std::string System::languages_string() {
|
||||||
std::string languages_string = std::to_string(NUM_LANGUAGES) + " languages (";
|
std::string languages_string = std::to_string(NUM_LANGUAGES) + " languages (";
|
||||||
@@ -356,8 +237,7 @@ bool System::command_message(const char * value, const int8_t id, JsonObject out
|
|||||||
LOG_INFO("Message: %s", EMSESP::webSchedulerService.computed_value.c_str()); // send to log
|
LOG_INFO("Message: %s", EMSESP::webSchedulerService.computed_value.c_str()); // send to log
|
||||||
Mqtt::queue_publish(F_(message), EMSESP::webSchedulerService.computed_value); // send to MQTT if enabled
|
Mqtt::queue_publish(F_(message), EMSESP::webSchedulerService.computed_value); // send to MQTT if enabled
|
||||||
output["api_data"] = EMSESP::webSchedulerService.computed_value; // send to API
|
output["api_data"] = EMSESP::webSchedulerService.computed_value; // send to API
|
||||||
EMSESP::webSchedulerService.computed_value.clear();
|
|
||||||
EMSESP::webSchedulerService.computed_value.shrink_to_fit();
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -408,11 +288,11 @@ void System::get_partition_info() {
|
|||||||
partition_info_.clear(); // clear existing data
|
partition_info_.clear(); // clear existing data
|
||||||
|
|
||||||
#ifdef EMSESP_STANDALONE
|
#ifdef EMSESP_STANDALONE
|
||||||
// dummy data for standalone mode - version, size, install_date
|
// dummy data for standalone mode - version, size, install_date in UTC epoch
|
||||||
partition_info_["app0"] = {EMSESP_APP_VERSION, 0, ""};
|
partition_info_["app0"] = {EMSESP_APP_VERSION, 0, 0};
|
||||||
partition_info_["app1"] = {"", 0, ""};
|
partition_info_["app1"] = {"", 0, 0};
|
||||||
partition_info_["factory"] = {"", 0, ""};
|
partition_info_["factory"] = {"", 0, 0};
|
||||||
partition_info_["boot"] = {"", 0, ""};
|
partition_info_["boot"] = {"", 0, 0};
|
||||||
#else
|
#else
|
||||||
|
|
||||||
auto current_partition = (const char *)esp_ota_get_running_partition()->label;
|
auto current_partition = (const char *)esp_ota_get_running_partition()->label;
|
||||||
@@ -452,20 +332,19 @@ void System::get_partition_info() {
|
|||||||
p_info.version = EMSESP::nvs_.getString(part->label, "").c_str();
|
p_info.version = EMSESP::nvs_.getString(part->label, "").c_str();
|
||||||
char c[20];
|
char c[20];
|
||||||
snprintf(c, sizeof(c), "d_%s", (const char *)part->label);
|
snprintf(c, sizeof(c), "d_%s", (const char *)part->label);
|
||||||
time_t d = EMSESP::nvs_.getULong(c, 0);
|
time_t d = EMSESP::nvs_.getULong(c, 0);
|
||||||
char time_string[25];
|
p_info.install_date = d > 1500000000L ? d : 0; // store UTC epoch; formatted to local time at render
|
||||||
strftime(time_string, sizeof(time_string), "%FT%T", localtime(&d));
|
|
||||||
p_info.install_date = d > 1500000000L ? time_string : "";
|
|
||||||
|
|
||||||
esp_image_metadata_t meta = {};
|
if (!p_info.version.empty()) {
|
||||||
esp_partition_pos_t part_pos = {.offset = part->address, .size = part->size};
|
esp_image_metadata_t meta = {};
|
||||||
if (esp_image_verify(ESP_IMAGE_VERIFY_SILENT, &part_pos, &meta) == ESP_OK) {
|
esp_partition_pos_t part_pos = {.offset = part->address, .size = part->size};
|
||||||
p_info.size = meta.image_len / 1024; // actual firmware size in KB
|
if (esp_image_verify(ESP_IMAGE_VERIFY_SILENT, &part_pos, &meta) == ESP_OK) {
|
||||||
} else {
|
p_info.size = meta.image_len / 1024; // actual firmware size in KB
|
||||||
p_info.size = 0;
|
} else {
|
||||||
|
p_info.size = 0;
|
||||||
|
}
|
||||||
|
partition_info_[part->label] = p_info;
|
||||||
}
|
}
|
||||||
|
|
||||||
partition_info_[part->label] = p_info;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
it = esp_partition_next(it); // loop to next partition
|
it = esp_partition_next(it); // loop to next partition
|
||||||
@@ -474,7 +353,7 @@ void System::get_partition_info() {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
// set NTP install time/date for the current partition
|
// set install time/date for the current partition, in UTC
|
||||||
// assumes NTP is connected and working
|
// assumes NTP is connected and working
|
||||||
void System::set_partition_install_date() {
|
void System::set_partition_install_date() {
|
||||||
#ifndef EMSESP_STANDALONE
|
#ifndef EMSESP_STANDALONE
|
||||||
@@ -582,6 +461,7 @@ void System::system_restart(const char * partitionname) {
|
|||||||
|
|
||||||
Mqtt::disconnect(); // gracefully disconnect MQTT, needed for QOS1
|
Mqtt::disconnect(); // gracefully disconnect MQTT, needed for QOS1
|
||||||
EMSuart::stop(); // stop UART so there is no interference
|
EMSuart::stop(); // stop UART so there is no interference
|
||||||
|
|
||||||
#ifndef EMSESP_STANDALONE
|
#ifndef EMSESP_STANDALONE
|
||||||
delay(1000); // wait 1 second
|
delay(1000); // wait 1 second
|
||||||
ESP.restart(); // ka-boom! - this is the only place where the ESP32 restart is called
|
ESP.restart(); // ka-boom! - this is the only place where the ESP32 restart is called
|
||||||
@@ -646,6 +526,29 @@ void System::syslog_init() {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// start or reconfigure modbus
|
||||||
|
void System::modbus_init() {
|
||||||
|
EMSESP::webSettingsService.read([&](WebSettings & settings) {
|
||||||
|
if (settings.modbus_enabled) {
|
||||||
|
if (EMSESP::modbus_ == nullptr) {
|
||||||
|
EMSESP::modbus_ = new Modbus;
|
||||||
|
EMSESP::modbus_->start(1, settings.modbus_port, settings.modbus_max_clients, settings.modbus_timeout * 1000);
|
||||||
|
} else if (settings.modbus_port != modbus_port_ || settings.modbus_max_clients != modbus_max_clients_ || settings.modbus_timeout != modbus_timeout_) {
|
||||||
|
EMSESP::modbus_->stop();
|
||||||
|
EMSESP::modbus_->start(1, settings.modbus_port, settings.modbus_max_clients, settings.modbus_timeout * 1000);
|
||||||
|
}
|
||||||
|
} else if (EMSESP::modbus_ != nullptr) {
|
||||||
|
EMSESP::modbus_->stop();
|
||||||
|
delete EMSESP::modbus_;
|
||||||
|
EMSESP::modbus_ = nullptr;
|
||||||
|
}
|
||||||
|
modbus_enabled_ = settings.modbus_enabled;
|
||||||
|
modbus_port_ = settings.modbus_port;
|
||||||
|
modbus_max_clients_ = settings.modbus_max_clients;
|
||||||
|
modbus_timeout_ = settings.modbus_timeout;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// read specific major system settings to store locally for faster access
|
// read specific major system settings to store locally for faster access
|
||||||
void System::store_settings(WebSettings & settings) {
|
void System::store_settings(WebSettings & settings) {
|
||||||
version_ = settings.version;
|
version_ = settings.version;
|
||||||
@@ -683,24 +586,6 @@ void System::store_settings(WebSettings & settings) {
|
|||||||
|
|
||||||
locale_ = settings.locale;
|
locale_ = settings.locale;
|
||||||
developer_mode_ = settings.developer_mode;
|
developer_mode_ = settings.developer_mode;
|
||||||
// start services
|
|
||||||
if (settings.modbus_enabled) {
|
|
||||||
if (EMSESP::modbus_ == nullptr) {
|
|
||||||
EMSESP::modbus_ = new Modbus;
|
|
||||||
EMSESP::modbus_->start(1, settings.modbus_port, settings.modbus_max_clients, settings.modbus_timeout * 1000);
|
|
||||||
} else if (settings.modbus_port != modbus_port_ || settings.modbus_max_clients != modbus_max_clients_ || settings.modbus_timeout != modbus_timeout_) {
|
|
||||||
EMSESP::modbus_->stop();
|
|
||||||
EMSESP::modbus_->start(1, settings.modbus_port, settings.modbus_max_clients, settings.modbus_timeout * 1000);
|
|
||||||
}
|
|
||||||
} else if (EMSESP::modbus_ != nullptr) {
|
|
||||||
EMSESP::modbus_->stop();
|
|
||||||
delete EMSESP::modbus_;
|
|
||||||
EMSESP::modbus_ = nullptr;
|
|
||||||
}
|
|
||||||
modbus_enabled_ = settings.modbus_enabled;
|
|
||||||
modbus_port_ = settings.modbus_port;
|
|
||||||
modbus_max_clients_ = settings.modbus_max_clients;
|
|
||||||
modbus_timeout_ = settings.modbus_timeout;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Starts up core services
|
// Starts up core services
|
||||||
@@ -724,11 +609,20 @@ void System::start() {
|
|||||||
appfree_ = esp_ota_get_running_partition()->size / 1024 - appused_;
|
appfree_ = esp_ota_get_running_partition()->size / 1024 - appused_;
|
||||||
refreshHeapMem(); // refresh free heap and max alloc heap
|
refreshHeapMem(); // refresh free heap and max alloc heap
|
||||||
#if CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2
|
#if CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2
|
||||||
|
#if ESP_IDF_VERSION_MAJOR < 5
|
||||||
|
temp_sensor_config_t temp_sensor = TSENS_CONFIG_DEFAULT();
|
||||||
|
temp_sensor_get_config(&temp_sensor);
|
||||||
|
temp_sensor.dac_offset = TSENS_DAC_DEFAULT; // DEFAULT: range:-10℃ ~ 80℃, error < 1℃.
|
||||||
|
temp_sensor_set_config(temp_sensor);
|
||||||
|
temp_sensor_start();
|
||||||
|
temp_sensor_read_celsius(&temperature_);
|
||||||
|
#else
|
||||||
temperature_sensor_config_t temp_sensor_config = TEMPERATURE_SENSOR_CONFIG_DEFAULT(-10, 80);
|
temperature_sensor_config_t temp_sensor_config = TEMPERATURE_SENSOR_CONFIG_DEFAULT(-10, 80);
|
||||||
temperature_sensor_install(&temp_sensor_config, &temperature_handle_);
|
temperature_sensor_install(&temp_sensor_config, &temperature_handle_);
|
||||||
temperature_sensor_enable(temperature_handle_);
|
temperature_sensor_enable(temperature_handle_);
|
||||||
temperature_sensor_get_celsius(temperature_handle_, &temperature_);
|
temperature_sensor_get_celsius(temperature_handle_, &temperature_);
|
||||||
#endif
|
#endif
|
||||||
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
EMSESP::esp32React.getNetworkSettingsService()->read([&](NetworkSettings & networkSettings) {
|
EMSESP::esp32React.getNetworkSettingsService()->read([&](NetworkSettings & networkSettings) {
|
||||||
@@ -741,6 +635,7 @@ void System::start() {
|
|||||||
network_init(); // network
|
network_init(); // network
|
||||||
uart_init(); // start UART
|
uart_init(); // start UART
|
||||||
syslog_init(); // start syslog
|
syslog_init(); // start syslog
|
||||||
|
modbus_init(); // start modbus
|
||||||
}
|
}
|
||||||
|
|
||||||
// button single click
|
// button single click
|
||||||
@@ -949,9 +844,16 @@ void System::send_info_mqtt() {
|
|||||||
doc["IPv4 gateway"] = uuid::printable_to_string(WiFi.gatewayIP());
|
doc["IPv4 gateway"] = uuid::printable_to_string(WiFi.gatewayIP());
|
||||||
doc["IPv4 nameserver"] = uuid::printable_to_string(WiFi.dnsIP());
|
doc["IPv4 nameserver"] = uuid::printable_to_string(WiFi.dnsIP());
|
||||||
|
|
||||||
|
#if ESP_IDF_VERSION_MAJOR < 5
|
||||||
|
if (WiFi.localIPv6().toString() != "0000:0000:0000:0000:0000:0000:0000:0000" && WiFi.localIPv6().toString() != "::") {
|
||||||
|
doc["IPv6 address"] = uuid::printable_to_string(WiFi.localIPv6());
|
||||||
|
}
|
||||||
|
#else
|
||||||
if (WiFi.linkLocalIPv6().toString() != "0000:0000:0000:0000:0000:0000:0000:0000" && WiFi.linkLocalIPv6().toString() != "::") {
|
if (WiFi.linkLocalIPv6().toString() != "0000:0000:0000:0000:0000:0000:0000:0000" && WiFi.linkLocalIPv6().toString() != "::") {
|
||||||
doc["IPv6 address"] = uuid::printable_to_string(WiFi.linkLocalIPv6());
|
doc["IPv6 address"] = uuid::printable_to_string(WiFi.linkLocalIPv6());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
Mqtt::queue_publish_retain(F_(info), doc.as<JsonObject>()); // topic called "info" and it's Retained
|
Mqtt::queue_publish_retain(F_(info), doc.as<JsonObject>()); // topic called "info" and it's Retained
|
||||||
@@ -1060,8 +962,13 @@ void System::network_init() {
|
|||||||
delay(500);
|
delay(500);
|
||||||
digitalWrite(eth_power_, HIGH);
|
digitalWrite(eth_power_, HIGH);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if ESP_IDF_VERSION_MAJOR < 5
|
||||||
|
eth_present_ = ETH.begin(phy_addr, power, mdc, mdio, type, clock_mode);
|
||||||
|
#else
|
||||||
eth_present_ = ETH.begin(type, phy_addr, mdc, mdio, power, clock_mode);
|
eth_present_ = ETH.begin(type, phy_addr, mdc, mdio, power, clock_mode);
|
||||||
#endif
|
#endif
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
// check health of system, done every 5 seconds
|
// check health of system, done every 5 seconds
|
||||||
@@ -1072,9 +979,13 @@ void System::system_check() {
|
|||||||
|
|
||||||
#ifndef EMSESP_STANDALONE
|
#ifndef EMSESP_STANDALONE
|
||||||
#if CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2
|
#if CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2
|
||||||
|
#if ESP_IDF_VERSION_MAJOR < 5
|
||||||
|
temp_sensor_read_celsius(&temperature_);
|
||||||
|
#else
|
||||||
temperature_sensor_get_celsius(temperature_handle_, &temperature_);
|
temperature_sensor_get_celsius(temperature_handle_, &temperature_);
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef EMSESP_PINGTEST
|
#ifdef EMSESP_PINGTEST
|
||||||
static uint64_t ping_count = 0;
|
static uint64_t ping_count = 0;
|
||||||
@@ -1126,7 +1037,6 @@ void System::commands_init() {
|
|||||||
Command::add(EMSdevice::DeviceType::SYSTEM, F_(read), System::command_read, FL_(read_cmd), CommandFlag::ADMIN_ONLY);
|
Command::add(EMSdevice::DeviceType::SYSTEM, F_(read), System::command_read, FL_(read_cmd), CommandFlag::ADMIN_ONLY);
|
||||||
Command::add(EMSdevice::DeviceType::SYSTEM, F_(send), System::command_send, FL_(send_cmd), CommandFlag::ADMIN_ONLY);
|
Command::add(EMSdevice::DeviceType::SYSTEM, F_(send), System::command_send, FL_(send_cmd), CommandFlag::ADMIN_ONLY);
|
||||||
Command::add(EMSdevice::DeviceType::SYSTEM, F_(fetch), System::command_fetch, FL_(fetch_cmd), CommandFlag::ADMIN_ONLY);
|
Command::add(EMSdevice::DeviceType::SYSTEM, F_(fetch), System::command_fetch, FL_(fetch_cmd), CommandFlag::ADMIN_ONLY);
|
||||||
Command::add(EMSdevice::DeviceType::SYSTEM, F_(sendmail), System::command_sendmail, FL_(sendmail_cmd), CommandFlag::ADMIN_ONLY);
|
|
||||||
Command::add(EMSdevice::DeviceType::SYSTEM, F_(restart), System::command_restart, FL_(restart_cmd), CommandFlag::ADMIN_ONLY);
|
Command::add(EMSdevice::DeviceType::SYSTEM, F_(restart), System::command_restart, FL_(restart_cmd), CommandFlag::ADMIN_ONLY);
|
||||||
Command::add(EMSdevice::DeviceType::SYSTEM, F_(format), System::command_format, FL_(format_cmd), CommandFlag::ADMIN_ONLY);
|
Command::add(EMSdevice::DeviceType::SYSTEM, F_(format), System::command_format, FL_(format_cmd), CommandFlag::ADMIN_ONLY);
|
||||||
Command::add(EMSdevice::DeviceType::SYSTEM, F_(txpause), System::command_txpause, FL_(txpause_cmd), CommandFlag::ADMIN_ONLY);
|
Command::add(EMSdevice::DeviceType::SYSTEM, F_(txpause), System::command_txpause, FL_(txpause_cmd), CommandFlag::ADMIN_ONLY);
|
||||||
@@ -1333,11 +1243,18 @@ void System::show_system(uuid::console::Shell & shell) {
|
|||||||
if (partition.second.version.empty()) {
|
if (partition.second.version.empty()) {
|
||||||
continue; // no version, empty string
|
continue; // no version, empty string
|
||||||
}
|
}
|
||||||
|
std::string installed;
|
||||||
|
if (partition.second.install_date > 0) {
|
||||||
|
char time_string[25];
|
||||||
|
time_t d = partition.second.install_date;
|
||||||
|
strftime(time_string, sizeof(time_string), "%FT%T", localtime(&d));
|
||||||
|
installed = std::string(", installed on ") + time_string;
|
||||||
|
}
|
||||||
shell.printfln(" %s: v%s (%d KB%s) %s",
|
shell.printfln(" %s: v%s (%d KB%s) %s",
|
||||||
partition.first.c_str(),
|
partition.first.c_str(),
|
||||||
partition.second.version.c_str(),
|
partition.second.version.c_str(),
|
||||||
partition.second.size,
|
partition.second.size,
|
||||||
partition.second.install_date.empty() ? "" : (std::string(", installed on ") + partition.second.install_date).c_str(),
|
installed.c_str(),
|
||||||
(strcmp(esp_ota_get_running_partition()->label, partition.first.c_str()) == 0) ? "** active **" : "");
|
(strcmp(esp_ota_get_running_partition()->label, partition.first.c_str()) == 0) ? "** active **" : "");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1368,9 +1285,16 @@ void System::show_system(uuid::console::Shell & shell) {
|
|||||||
shell.printfln(" IPv4 address: %s/%s", uuid::printable_to_string(WiFi.localIP()).c_str(), uuid::printable_to_string(WiFi.subnetMask()).c_str());
|
shell.printfln(" IPv4 address: %s/%s", uuid::printable_to_string(WiFi.localIP()).c_str(), uuid::printable_to_string(WiFi.subnetMask()).c_str());
|
||||||
shell.printfln(" IPv4 gateway: %s", uuid::printable_to_string(WiFi.gatewayIP()).c_str());
|
shell.printfln(" IPv4 gateway: %s", uuid::printable_to_string(WiFi.gatewayIP()).c_str());
|
||||||
shell.printfln(" IPv4 nameserver: %s", uuid::printable_to_string(WiFi.dnsIP()).c_str());
|
shell.printfln(" IPv4 nameserver: %s", uuid::printable_to_string(WiFi.dnsIP()).c_str());
|
||||||
|
#if ESP_IDF_VERSION_MAJOR < 5
|
||||||
|
if (WiFi.localIPv6().toString() != "0000:0000:0000:0000:0000:0000:0000:0000" && WiFi.localIPv6().toString() != "::") {
|
||||||
|
shell.printfln(" IPv6 address: %s", uuid::printable_to_string(WiFi.localIPv6()).c_str());
|
||||||
|
}
|
||||||
|
#else
|
||||||
if (WiFi.linkLocalIPv6().toString() != "0000:0000:0000:0000:0000:0000:0000:0000" && WiFi.linkLocalIPv6().toString() != "::") {
|
if (WiFi.linkLocalIPv6().toString() != "0000:0000:0000:0000:0000:0000:0000:0000" && WiFi.linkLocalIPv6().toString() != "::") {
|
||||||
shell.printfln(" IPv6 address: %s", uuid::printable_to_string(WiFi.linkLocalIPv6()).c_str());
|
shell.printfln(" IPv6 address: %s", uuid::printable_to_string(WiFi.linkLocalIPv6()).c_str());
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case WL_CONNECT_FAILED:
|
case WL_CONNECT_FAILED:
|
||||||
@@ -1401,9 +1325,15 @@ void System::show_system(uuid::console::Shell & shell) {
|
|||||||
shell.printfln(" IPv4 address: %s/%s", uuid::printable_to_string(ETH.localIP()).c_str(), uuid::printable_to_string(ETH.subnetMask()).c_str());
|
shell.printfln(" IPv4 address: %s/%s", uuid::printable_to_string(ETH.localIP()).c_str(), uuid::printable_to_string(ETH.subnetMask()).c_str());
|
||||||
shell.printfln(" IPv4 gateway: %s", uuid::printable_to_string(ETH.gatewayIP()).c_str());
|
shell.printfln(" IPv4 gateway: %s", uuid::printable_to_string(ETH.gatewayIP()).c_str());
|
||||||
shell.printfln(" IPv4 nameserver: %s", uuid::printable_to_string(ETH.dnsIP()).c_str());
|
shell.printfln(" IPv4 nameserver: %s", uuid::printable_to_string(ETH.dnsIP()).c_str());
|
||||||
|
#if ESP_IDF_VERSION_MAJOR < 5
|
||||||
|
if (ETH.localIPv6().toString() != "0000:0000:0000:0000:0000:0000:0000:0000" && ETH.localIPv6().toString() != "::") {
|
||||||
|
shell.printfln(" IPv6 address: %s", uuid::printable_to_string(ETH.localIPv6()).c_str());
|
||||||
|
}
|
||||||
|
#else
|
||||||
if (ETH.linkLocalIPv6().toString() != "0000:0000:0000:0000:0000:0000:0000:0000" && ETH.linkLocalIPv6().toString() != "::") {
|
if (ETH.linkLocalIPv6().toString() != "0000:0000:0000:0000:0000:0000:0000:0000" && ETH.linkLocalIPv6().toString() != "::") {
|
||||||
shell.printfln(" IPv6 address: %s", uuid::printable_to_string(ETH.linkLocalIPv6()).c_str());
|
shell.printfln(" IPv6 address: %s", uuid::printable_to_string(ETH.linkLocalIPv6()).c_str());
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
shell.println();
|
shell.println();
|
||||||
|
|
||||||
@@ -1425,10 +1355,10 @@ void System::show_system(uuid::console::Shell & shell) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
shell.println();
|
shell.println();
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// see if there is a restore of an older settings file that needs to be applied
|
// see if there is a restore of an older settings file that needs to be applied
|
||||||
// note there can be only one file at a time
|
// note there can be only one file at a time
|
||||||
bool System::check_restore() {
|
bool System::check_restore() {
|
||||||
@@ -1613,8 +1543,8 @@ bool System::check_upgrade() {
|
|||||||
settingsVersion = "3.5.0"; // this was the last stable version without version info
|
settingsVersion = "3.5.0"; // this was the last stable version without version info
|
||||||
}
|
}
|
||||||
|
|
||||||
version::Semver200_version settings_version(settingsVersion);
|
version::EMSESP_Version settings_version(settingsVersion);
|
||||||
version::Semver200_version this_version(EMSESP_APP_VERSION);
|
version::EMSESP_Version this_version(EMSESP_APP_VERSION);
|
||||||
|
|
||||||
std::string settings_version_type = settings_version.prerelease().empty() ? "" : ("-" + settings_version.prerelease());
|
std::string settings_version_type = settings_version.prerelease().empty() ? "" : ("-" + settings_version.prerelease());
|
||||||
std::string this_version_type = this_version.prerelease().empty() ? "" : ("-" + this_version.prerelease());
|
std::string this_version_type = this_version.prerelease().empty() ? "" : ("-" + this_version.prerelease());
|
||||||
@@ -1728,8 +1658,8 @@ bool System::check_upgrade() {
|
|||||||
return false; // no reboot required
|
return false; // no reboot required
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifndef EMSESP_STANDALONE
|
|
||||||
// map each config filename to its human-readable section key
|
// map each config filename to its human-readable section key
|
||||||
|
#ifndef EMSESP_STANDALONE
|
||||||
static const std::pair<const char *, const char *> SECTION_MAP[] = {
|
static const std::pair<const char *, const char *> SECTION_MAP[] = {
|
||||||
{NETWORK_SETTINGS_FILE, "Network"},
|
{NETWORK_SETTINGS_FILE, "Network"},
|
||||||
{AP_SETTINGS_FILE, "AP"},
|
{AP_SETTINGS_FILE, "AP"},
|
||||||
@@ -1765,18 +1695,17 @@ void System::exportSettings(const std::string & type, const char * filename, Jso
|
|||||||
|
|
||||||
File settingsFile = LittleFS.open(filename);
|
File settingsFile = LittleFS.open(filename);
|
||||||
if (settingsFile) {
|
if (settingsFile) {
|
||||||
JsonDocument jsonDocument;
|
{
|
||||||
DeserializationError error = deserializeJson(jsonDocument, settingsFile);
|
JsonDocument jsonDocument;
|
||||||
if (error == DeserializationError::Ok && jsonDocument.is<JsonObject>()) {
|
DeserializationError error = deserializeJson(jsonDocument, settingsFile);
|
||||||
JsonObject node = output[section].to<JsonObject>();
|
settingsFile.close(); // close early, we no longer need the file
|
||||||
for (JsonPair kvp : jsonDocument.as<JsonObject>()) {
|
if (error || !jsonDocument.is<JsonObject>()) {
|
||||||
node[kvp.key()] = kvp.value();
|
LOG_ERROR("Failed to deserialize settings file %s", filename);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
output[section].set(jsonDocument.as<JsonObject>());
|
||||||
LOG_ERROR("Failed to deserialize settings file %s", filename);
|
|
||||||
}
|
}
|
||||||
LOG_DEBUG("Exported %s settings from file %s", section, filename);
|
LOG_DEBUG("Exported %s settings from file %s", section, filename);
|
||||||
settingsFile.close();
|
|
||||||
} else {
|
} else {
|
||||||
LOG_ERROR("No settings file for %s found", filename);
|
LOG_ERROR("No settings file for %s found", filename);
|
||||||
}
|
}
|
||||||
@@ -1828,13 +1757,15 @@ void System::exportSystemBackup(JsonObject output) {
|
|||||||
if (file) {
|
if (file) {
|
||||||
JsonDocument jsonDocument;
|
JsonDocument jsonDocument;
|
||||||
DeserializationError error = deserializeJson(jsonDocument, file);
|
DeserializationError error = deserializeJson(jsonDocument, file);
|
||||||
if (error == DeserializationError::Ok && jsonDocument.is<JsonObject>()) {
|
file.close(); // close early, we no longer need the file
|
||||||
JsonObject node = nodes.add<JsonObject>();
|
if (!error && jsonDocument.is<JsonObject>()) {
|
||||||
node["type"] = "customSupport";
|
JsonObject support_node = nodes.add<JsonObject>();
|
||||||
node["data"] = jsonDocument.as<JsonObject>();
|
support_node["type"] = "customSupport";
|
||||||
|
support_node["data"].set(jsonDocument.as<JsonObject>());
|
||||||
|
LOG_DEBUG("Exported custom support file %s", EMSESP_CUSTOMSUPPORT_FILE);
|
||||||
|
} else {
|
||||||
|
LOG_ERROR("Failed to deserialize custom support file");
|
||||||
}
|
}
|
||||||
file.close();
|
|
||||||
LOG_DEBUG("Exported custom support file %s", EMSESP_CUSTOMSUPPORT_FILE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Backup NVS values
|
// Backup NVS values
|
||||||
@@ -1843,16 +1774,27 @@ void System::exportSystemBackup(JsonObject output) {
|
|||||||
|
|
||||||
const char * nvs_part = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_NVS, "nvs1") ? "nvs1" : "nvs"; // nvs1 is on 16MBs
|
const char * nvs_part = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_NVS, "nvs1") ? "nvs1" : "nvs"; // nvs1 is on 16MBs
|
||||||
nvs_iterator_t it = nullptr;
|
nvs_iterator_t it = nullptr;
|
||||||
esp_err_t err = nvs_entry_find(nvs_part, "ems-esp", NVS_TYPE_ANY, &it);
|
#if ESP_IDF_VERSION_MAJOR < 5
|
||||||
|
it = nvs_entry_find(nvs_part, "ems-esp", NVS_TYPE_ANY);
|
||||||
|
if (it == nullptr) {
|
||||||
|
#else
|
||||||
|
esp_err_t err = nvs_entry_find(nvs_part, "ems-esp", NVS_TYPE_ANY, &it);
|
||||||
if (err != ESP_OK) {
|
if (err != ESP_OK) {
|
||||||
|
#endif
|
||||||
LOG_ERROR("Failed to find NVS entry for %s", nvs_part);
|
LOG_ERROR("Failed to find NVS entry for %s", nvs_part);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
JsonArray entries = node["nvs"].to<JsonArray>();
|
JsonArray entries = node["nvs"].to<JsonArray>();
|
||||||
|
#if ESP_IDF_VERSION_MAJOR < 5
|
||||||
|
while (it != nullptr) {
|
||||||
|
nvs_entry_info_t info;
|
||||||
|
nvs_entry_info(it, &info);
|
||||||
|
#else
|
||||||
while (err == ESP_OK) {
|
while (err == ESP_OK) {
|
||||||
nvs_entry_info_t info;
|
nvs_entry_info_t info;
|
||||||
nvs_entry_info(it, &info);
|
nvs_entry_info(it, &info);
|
||||||
|
#endif
|
||||||
JsonObject entry = entries.add<JsonObject>();
|
JsonObject entry = entries.add<JsonObject>();
|
||||||
entry["type"] = info.type;
|
entry["type"] = info.type;
|
||||||
entry["key"] = info.key;
|
entry["key"] = info.key;
|
||||||
@@ -1887,8 +1829,14 @@ void System::exportSystemBackup(JsonObject output) {
|
|||||||
entry["value"] = EMSESP::nvs_.getString(info.key);
|
entry["value"] = EMSESP::nvs_.getString(info.key);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if ESP_IDF_VERSION_MAJOR < 5
|
||||||
|
it = nvs_entry_next(it);
|
||||||
|
}
|
||||||
|
#else
|
||||||
err = nvs_entry_next(&it);
|
err = nvs_entry_next(&it);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
if (it != nullptr) {
|
if (it != nullptr) {
|
||||||
nvs_release_iterator(it);
|
nvs_release_iterator(it);
|
||||||
@@ -2690,12 +2638,12 @@ bool System::command_info(const char * value, const int8_t id, JsonObject output
|
|||||||
node["maxWebLogBuffer"] = settings.weblog_buffer;
|
node["maxWebLogBuffer"] = settings.weblog_buffer;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
#if defined(EMSESP_UNITY)
|
#if defined(EMSESP_UNITY)
|
||||||
node["webLogBuffer"] = 0;
|
node["webLogBuffer"] = 0;
|
||||||
#else
|
#else
|
||||||
node["webLogBuffer"] = EMSESP::webLogService.num_log_messages();
|
node["webLogBuffer"] = EMSESP::webLogService.num_log_messages();
|
||||||
#endif
|
#endif
|
||||||
*/
|
*/
|
||||||
node["modbusEnabled"] = settings.modbus_enabled;
|
node["modbusEnabled"] = settings.modbus_enabled;
|
||||||
node["forceHeatingOff"] = settings.boiler_heatingoff;
|
node["forceHeatingOff"] = settings.boiler_heatingoff;
|
||||||
node["developerMode"] = settings.developer_mode;
|
node["developerMode"] = settings.developer_mode;
|
||||||
@@ -3386,10 +3334,6 @@ void System::set_valid_system_gpios() {
|
|||||||
} else {
|
} else {
|
||||||
valid_system_gpios_ = string_range_to_vector("0-39", "6-11, 20, 24, 28-31");
|
valid_system_gpios_ = string_range_to_vector("0-39", "6-11, 20, 24, 28-31");
|
||||||
}
|
}
|
||||||
#elif CONFIG_IDF_TARGET_ESP32C6
|
|
||||||
// https://docs.espressif.com/projects/esp-idf/en/v5.5.3/esp32c6/api-reference/peripherals/gpio.html
|
|
||||||
// 24-30 used for flash, 12-13 USB, 16-17 uart0
|
|
||||||
valid_system_gpios_ = string_range_to_vector("0-30", "12-13, 16-17, 24-30");
|
|
||||||
#elif defined(EMSESP_STANDALONE)
|
#elif defined(EMSESP_STANDALONE)
|
||||||
valid_system_gpios_ = string_range_to_vector("0-39");
|
valid_system_gpios_ = string_range_to_vector("0-39");
|
||||||
#endif
|
#endif
|
||||||
@@ -3439,6 +3383,24 @@ void System::remove_gpio(uint8_t pin, bool also_system) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// remove a gpio that has 0 for disable
|
||||||
|
void System::remove_optional_gpio(uint8_t pin) {
|
||||||
|
if (pin) {
|
||||||
|
remove_gpio(pin, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// set unused gpios to default state input high-Z
|
||||||
|
void System::reset_unused_gpios() {
|
||||||
|
for (const auto & pin : valid_system_gpios_) {
|
||||||
|
auto it = std::find_if(used_gpios_.begin(), used_gpios_.end(), [pin](const GpioUsage & usage) { return usage.pin == pin; });
|
||||||
|
if (it == used_gpios_.end()) {
|
||||||
|
LOG_DEBUG("reset pin %d", pin);
|
||||||
|
pinMode(pin, INPUT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// return a list of GPIO's available for use
|
// return a list of GPIO's available for use
|
||||||
std::vector<uint8_t> System::available_gpios() {
|
std::vector<uint8_t> System::available_gpios() {
|
||||||
std::vector<uint8_t> gpios;
|
std::vector<uint8_t> gpios;
|
||||||
|
|||||||
@@ -37,7 +37,11 @@
|
|||||||
#include <uuid/log.h>
|
#include <uuid/log.h>
|
||||||
#include <PButton.h>
|
#include <PButton.h>
|
||||||
|
|
||||||
|
#if ESP_ARDUINO_VERSION_MAJOR < 3
|
||||||
|
#define EMSESP_RGB_WRITE neopixelWrite
|
||||||
|
#else
|
||||||
#define EMSESP_RGB_WRITE rgbLedWrite
|
#define EMSESP_RGB_WRITE rgbLedWrite
|
||||||
|
#endif
|
||||||
|
|
||||||
#if CONFIG_IDF_TARGET_ESP32
|
#if CONFIG_IDF_TARGET_ESP32
|
||||||
// there is no official API available on the original ESP32
|
// there is no official API available on the original ESP32
|
||||||
@@ -45,8 +49,12 @@ extern "C" {
|
|||||||
uint8_t temprature_sens_read();
|
uint8_t temprature_sens_read();
|
||||||
}
|
}
|
||||||
#elif CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2
|
#elif CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2
|
||||||
|
#if ESP_IDF_VERSION_MAJOR < 5
|
||||||
|
#include "driver/temp_sensor.h"
|
||||||
|
#else
|
||||||
#include "driver/temperature_sensor.h"
|
#include "driver/temperature_sensor.h"
|
||||||
#endif
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
using uuid::console::Shell;
|
using uuid::console::Shell;
|
||||||
|
|
||||||
@@ -75,7 +83,7 @@ enum FUSE_VALUE : uint8_t { ALL = 0, MFG = 1, MODEL = 2, BOARD = 3, REV = 4, BAT
|
|||||||
struct PartitionInfo {
|
struct PartitionInfo {
|
||||||
std::string version;
|
std::string version;
|
||||||
size_t size;
|
size_t size;
|
||||||
std::string install_date; // optional, only available if NTP is connected
|
time_t install_date; // UTC epoch seconds; 0 if unknown. Format with localtime() at render time so it honors the current TZ.
|
||||||
};
|
};
|
||||||
|
|
||||||
class System {
|
class System {
|
||||||
@@ -95,7 +103,6 @@ class System {
|
|||||||
static bool command_info(const char * value, const int8_t id, JsonObject output);
|
static bool command_info(const char * value, const int8_t id, JsonObject output);
|
||||||
static bool command_response(const char * value, const int8_t id, JsonObject output);
|
static bool command_response(const char * value, const int8_t id, JsonObject output);
|
||||||
static bool command_service(const char * cmd, const char * value);
|
static bool command_service(const char * cmd, const char * value);
|
||||||
static bool command_sendmail(const char * value, const int8_t id);
|
|
||||||
static bool command_txpause(const char * value, const int8_t id);
|
static bool command_txpause(const char * value, const int8_t id);
|
||||||
|
|
||||||
static bool get_value_info(JsonObject root, const char * cmd);
|
static bool get_value_info(JsonObject root, const char * cmd);
|
||||||
@@ -115,6 +122,7 @@ class System {
|
|||||||
void show_mem(const char * note);
|
void show_mem(const char * note);
|
||||||
void store_settings(class WebSettings & settings);
|
void store_settings(class WebSettings & settings);
|
||||||
void syslog_init();
|
void syslog_init();
|
||||||
|
void modbus_init();
|
||||||
bool check_upgrade();
|
bool check_upgrade();
|
||||||
bool check_restore();
|
bool check_restore();
|
||||||
void heartbeat_json(JsonObject output);
|
void heartbeat_json(JsonObject output);
|
||||||
@@ -369,6 +377,8 @@ class System {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
static void remove_gpio(uint8_t pin, bool also_system = false); // remove a gpio from both valid (optional) and used lists
|
static void remove_gpio(uint8_t pin, bool also_system = false); // remove a gpio from both valid (optional) and used lists
|
||||||
|
static void remove_optional_gpio(uint8_t pin);
|
||||||
|
static void reset_unused_gpios();
|
||||||
|
|
||||||
// Partition info map: partition name -> {version, size, install_date}
|
// Partition info map: partition name -> {version, size, install_date}
|
||||||
std::map<std::string, PartitionInfo, std::less<>, AllocatorPSRAM<std::pair<const std::string, PartitionInfo>>> partition_info_;
|
std::map<std::string, PartitionInfo, std::less<>, AllocatorPSRAM<std::pair<const std::string, PartitionInfo>>> partition_info_;
|
||||||
@@ -495,7 +505,9 @@ class System {
|
|||||||
uint32_t appfree_;
|
uint32_t appfree_;
|
||||||
|
|
||||||
#if CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2
|
#if CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2
|
||||||
|
#if ESP_IDF_VERSION_MAJOR >= 5
|
||||||
temperature_sensor_handle_t temperature_handle_ = NULL;
|
temperature_sensor_handle_t temperature_handle_ = NULL;
|
||||||
|
#endif
|
||||||
#endif
|
#endif
|
||||||
float temperature_ = 0;
|
float temperature_ = 0;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -159,11 +159,10 @@ class EMSbus {
|
|||||||
public:
|
public:
|
||||||
static uuid::log::Logger logger_;
|
static uuid::log::Logger logger_;
|
||||||
|
|
||||||
static constexpr uint8_t EMS_MASK_UNSET = 0xFF; // EMS bus type (budrus/junkers) hasn't been detected yet
|
static constexpr uint8_t EMS_MASK_UNSET = 0xFF; // EMS bus type (budrus/junkers) hasn't been detected yet
|
||||||
static constexpr uint8_t EMS_MASK_HT3 = 0x80; // EMS bus type Junkers/HT3
|
static constexpr uint8_t EMS_MASK_HT3 = 0x80; // EMS bus type Junkers/HT3
|
||||||
static constexpr uint8_t EMS_MASK_BUDERUS = 0xFF; // EMS bus type Buderus
|
static constexpr uint8_t EMS_MASK_BUDERUS = 0xFF; // EMS bus type Buderus
|
||||||
static constexpr uint8_t EMS_TX_ERROR_LIMIT = 10; // % limit of failed Tx read/write attempts before showing a warning
|
static constexpr uint8_t EMS_TX_ERROR_LIMIT = 10; // % limit of failed Tx read/write attempts before showing a warning
|
||||||
static constexpr uint8_t EMS_POLL_MATCH_LIMIT = 3; // consecutive poll matches needed before declaring bus connected
|
|
||||||
|
|
||||||
static bool is_ht3() {
|
static bool is_ht3() {
|
||||||
return (ems_mask_ == EMS_MASK_HT3);
|
return (ems_mask_ == EMS_MASK_HT3);
|
||||||
@@ -211,6 +210,7 @@ class EMSbus {
|
|||||||
if (!last_bus_activity_) {
|
if (!last_bus_activity_) {
|
||||||
bus_uptime_start_ = timestamp;
|
bus_uptime_start_ = timestamp;
|
||||||
}
|
}
|
||||||
|
|
||||||
last_bus_activity_ = timestamp;
|
last_bus_activity_ = timestamp;
|
||||||
bus_connected_ = true;
|
bus_connected_ = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,12 +21,6 @@
|
|||||||
#include "temperaturesensor.h"
|
#include "temperaturesensor.h"
|
||||||
#include "emsesp.h"
|
#include "emsesp.h"
|
||||||
|
|
||||||
#ifdef ESP32
|
|
||||||
#define YIELD
|
|
||||||
#else
|
|
||||||
#define YIELD yield()
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace emsesp {
|
namespace emsesp {
|
||||||
|
|
||||||
uuid::log::Logger TemperatureSensor::logger_{F_(temperaturesensor), uuid::log::Facility::DAEMON};
|
uuid::log::Logger TemperatureSensor::logger_{F_(temperaturesensor), uuid::log::Facility::DAEMON};
|
||||||
@@ -81,7 +75,6 @@ void TemperatureSensor::loop() {
|
|||||||
LOG_DEBUG("Read sensor temperature");
|
LOG_DEBUG("Read sensor temperature");
|
||||||
#endif
|
#endif
|
||||||
if (bus_.reset() || parasite_) {
|
if (bus_.reset() || parasite_) {
|
||||||
YIELD;
|
|
||||||
bus_.skip();
|
bus_.skip();
|
||||||
bus_.write(CMD_CONVERT_TEMP, parasite_ ? 1 : 0);
|
bus_.write(CMD_CONVERT_TEMP, parasite_ ? 1 : 0);
|
||||||
state_ = State::READING;
|
state_ = State::READING;
|
||||||
@@ -260,19 +253,16 @@ int16_t TemperatureSensor::get_temperature_c(const uint8_t addr[]) {
|
|||||||
LOG_ERROR("Bus reset failed before reading scratchpad from %s", Sensor(addr).id());
|
LOG_ERROR("Bus reset failed before reading scratchpad from %s", Sensor(addr).id());
|
||||||
return EMS_VALUE_INT16_NOTSET;
|
return EMS_VALUE_INT16_NOTSET;
|
||||||
}
|
}
|
||||||
YIELD;
|
|
||||||
|
|
||||||
uint8_t scratchpad[SCRATCHPAD_LEN] = {0};
|
uint8_t scratchpad[SCRATCHPAD_LEN] = {0};
|
||||||
bus_.select(addr);
|
bus_.select(addr);
|
||||||
bus_.write(CMD_READ_SCRATCHPAD);
|
bus_.write(CMD_READ_SCRATCHPAD);
|
||||||
bus_.read_bytes(scratchpad, SCRATCHPAD_LEN);
|
bus_.read_bytes(scratchpad, SCRATCHPAD_LEN);
|
||||||
YIELD;
|
|
||||||
|
|
||||||
if (!bus_.reset()) {
|
if (!bus_.reset()) {
|
||||||
LOG_ERROR("Bus reset failed after reading scratchpad from %s", Sensor(addr).id());
|
LOG_ERROR("Bus reset failed after reading scratchpad from %s", Sensor(addr).id());
|
||||||
return EMS_VALUE_INT16_NOTSET;
|
return EMS_VALUE_INT16_NOTSET;
|
||||||
}
|
}
|
||||||
YIELD;
|
|
||||||
|
|
||||||
if (bus_.crc8(scratchpad, SCRATCHPAD_LEN - 1) != scratchpad[SCRATCHPAD_LEN - 1]) {
|
if (bus_.crc8(scratchpad, SCRATCHPAD_LEN - 1) != scratchpad[SCRATCHPAD_LEN - 1]) {
|
||||||
LOG_WARNING("Invalid scratchpad CRC: %02X%02X%02X%02X%02X%02X%02X%02X%02X from sensor %s",
|
LOG_WARNING("Invalid scratchpad CRC: %02X%02X%02X%02X%02X%02X%02X%02X%02X from sensor %s",
|
||||||
|
|||||||
@@ -39,11 +39,11 @@ Connect::Connect(uint8_t device_type, uint8_t device_id, uint8_t product_id, con
|
|||||||
DeviceValueUOM::DEGREES);
|
DeviceValueUOM::DEGREES);
|
||||||
// Roomthermostats
|
// Roomthermostats
|
||||||
for (uint8_t i = 0; i < 16; i++) {
|
for (uint8_t i = 0; i < 16; i++) {
|
||||||
register_telegram_type(0x0BDD + i, "Room", false, MAKE_PF_CB(process_roomThermostat)); // broadcasted
|
register_telegram_type(0x0BDD + i, "Room", false, MAKE_PF_CB(process_roomThermostat)); // broadcasted
|
||||||
register_telegram_type(0x0B3D + i, "Roomname", false, MAKE_PF_CB(process_roomThermostatName), 100); // fetch for active circuits
|
register_telegram_type(0x0B3D + i, "Roomname", false, MAKE_PF_CB(process_roomThermostatName), 100); // fetch for active circuits
|
||||||
register_telegram_type(0x0BB5 + i, "Roomsettings", false, MAKE_PF_CB(process_roomThermostatSettings), 8); // fetch for active circuits
|
register_telegram_type(0x0BB5 + i, "Roomsettings", false, MAKE_PF_CB(process_roomThermostatSettings), 8); // fetch for active circuits
|
||||||
register_telegram_type(0x1230 + i, "Roomparams", false, MAKE_PF_CB(process_roomThermostatParam)); // fetch for active circuits
|
register_telegram_type(0x1230 + i, "Roomparams", false, MAKE_PF_CB(process_roomThermostatParam)); // fetch for active circuits
|
||||||
register_telegram_type(0x1244 + i, "Roomdata", false, MAKE_PF_CB(process_roomThermostatData)); // broadcasted
|
register_telegram_type(0x1244 + i, "Roomdata", false, MAKE_PF_CB(process_roomThermostatData)); // broadcasted
|
||||||
}
|
}
|
||||||
register_telegram_type(0xDB65, "Roomschedule", true, MAKE_PF_CB(process_roomSchedule));
|
register_telegram_type(0xDB65, "Roomschedule", true, MAKE_PF_CB(process_roomSchedule));
|
||||||
// 0x2040, broadcast 36 bytes:
|
// 0x2040, broadcast 36 bytes:
|
||||||
|
|||||||
@@ -173,7 +173,7 @@ Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_i
|
|||||||
for (uint8_t i = 0; i < monitor_size; i++) {
|
for (uint8_t i = 0; i < monitor_size; i++) {
|
||||||
register_telegram_type(monitor_typeids[i], "RC300Monitor", false, MAKE_PF_CB(process_RC300Monitor), 33);
|
register_telegram_type(monitor_typeids[i], "RC300Monitor", false, MAKE_PF_CB(process_RC300Monitor), 33);
|
||||||
register_telegram_type(set_typeids[i], "RC300Set", false, MAKE_PF_CB(process_RC300Set), 29);
|
register_telegram_type(set_typeids[i], "RC300Set", false, MAKE_PF_CB(process_RC300Set), 29);
|
||||||
register_telegram_type(summer_typeids[i], "RC300Summer", false, MAKE_PF_CB(process_RC300Summer), 13);
|
register_telegram_type(summer_typeids[i], "RC300Summer", false, MAKE_PF_CB(process_RC300Summer), 14);
|
||||||
register_telegram_type(curve_typeids[i], "RC300Curves", false, MAKE_PF_CB(process_RC300Curve), 9);
|
register_telegram_type(curve_typeids[i], "RC300Curves", false, MAKE_PF_CB(process_RC300Curve), 9);
|
||||||
register_telegram_type(summer2_typeids[i], "RC300Summer2", false, MAKE_PF_CB(process_RC300Summer2), 8);
|
register_telegram_type(summer2_typeids[i], "RC300Summer2", false, MAKE_PF_CB(process_RC300Summer2), 8);
|
||||||
}
|
}
|
||||||
@@ -202,11 +202,12 @@ Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_i
|
|||||||
if (model == EMSdevice::EMS_DEVICE_FLAG_RC100) {
|
if (model == EMSdevice::EMS_DEVICE_FLAG_RC100) {
|
||||||
register_telegram_type(0x43F, "CRHolidays", true, MAKE_PF_CB(process_RC300Holiday), 6);
|
register_telegram_type(0x43F, "CRHolidays", true, MAKE_PF_CB(process_RC300Holiday), 6);
|
||||||
} else {
|
} else {
|
||||||
register_telegram_type(0x269, "RC300Holiday", true, MAKE_PF_CB(process_RC300Holiday), 6);
|
register_telegram_type(0x269, "RC300Holiday", true, MAKE_PF_CB(process_RC300Holiday), 18);
|
||||||
}
|
}
|
||||||
register_telegram_type(0x16E, "Absent", true, MAKE_PF_CB(process_Absent), 1);
|
register_telegram_type(0x16E, "Absent", true, MAKE_PF_CB(process_Absent), 1);
|
||||||
register_telegram_type(0xBF, "ErrorMessage", false, MAKE_PF_CB(process_ErrorMessageBF));
|
register_telegram_type(0xBF, "ErrorMessage", false, MAKE_PF_CB(process_ErrorMessageBF));
|
||||||
register_telegram_type(0xC0, "RCErrorMessage", false, MAKE_PF_CB(process_RCErrorMessage2));
|
register_telegram_type(0xC0, "RCErrorMessage", false, MAKE_PF_CB(process_RCErrorMessage2));
|
||||||
|
register_telegram_type(0x470, "RC300Summer2", true, MAKE_PF_CB(process_RC300Summer2), 8);
|
||||||
EMSESP::send_read_request(0xC0, device_id, 0, 20); // read last errorcode on start (only published on errors)
|
EMSESP::send_read_request(0xC0, device_id, 0, 20); // read last errorcode on start (only published on errors)
|
||||||
|
|
||||||
// JUNKERS/HT3
|
// JUNKERS/HT3
|
||||||
@@ -1268,7 +1269,13 @@ void Thermostat::process_RC300Summer(std::shared_ptr<const Telegram> telegram) {
|
|||||||
void Thermostat::process_RC300Summer2(std::shared_ptr<const Telegram> telegram) {
|
void Thermostat::process_RC300Summer2(std::shared_ptr<const Telegram> telegram) {
|
||||||
auto hc = heating_circuit(telegram);
|
auto hc = heating_circuit(telegram);
|
||||||
if (hc == nullptr) {
|
if (hc == nullptr) {
|
||||||
return;
|
// telegram 0x470 see https://github.com/emsesp/EMS-ESP32/issues/2686
|
||||||
|
if (telegram->type_id == 0x470 && telegram->message_length > 2) {
|
||||||
|
hc = heating_circuit(1);
|
||||||
|
summer2_typeids[0] = 0x470;
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (hc->statusbyte & 1) {
|
if (hc->statusbyte & 1) {
|
||||||
has_update(telegram, hc->summersetmode, 0);
|
has_update(telegram, hc->summersetmode, 0);
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
#define EMSESP_APP_VERSION "3.9.0-dev.0"
|
#define EMSESP_APP_VERSION "3.8.2-dev.21"
|
||||||
|
|||||||
@@ -66,23 +66,18 @@ void EMSuart::uart_event_task(void * pvParameters) {
|
|||||||
// initialize UART driver
|
// initialize UART driver
|
||||||
void EMSuart::start(const uint8_t tx_mode, const uint8_t rx_gpio, const uint8_t tx_gpio) {
|
void EMSuart::start(const uint8_t tx_mode, const uint8_t rx_gpio, const uint8_t tx_gpio) {
|
||||||
if (tx_mode_ == EMS_TXMODE_INIT) {
|
if (tx_mode_ == EMS_TXMODE_INIT) {
|
||||||
#if CONFIG_IDF_TARGET_ESP32C6
|
|
||||||
uart_config_t uart_config = {.baud_rate = EMSUART_BAUD,
|
|
||||||
.data_bits = UART_DATA_8_BITS,
|
|
||||||
.parity = UART_PARITY_DISABLE,
|
|
||||||
.stop_bits = UART_STOP_BITS_1,
|
|
||||||
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
|
|
||||||
.rx_flow_ctrl_thresh = 0};
|
|
||||||
#else
|
|
||||||
uart_config_t uart_config = {.baud_rate = EMSUART_BAUD,
|
uart_config_t uart_config = {.baud_rate = EMSUART_BAUD,
|
||||||
.data_bits = UART_DATA_8_BITS,
|
.data_bits = UART_DATA_8_BITS,
|
||||||
.parity = UART_PARITY_DISABLE,
|
.parity = UART_PARITY_DISABLE,
|
||||||
.stop_bits = UART_STOP_BITS_1,
|
.stop_bits = UART_STOP_BITS_1,
|
||||||
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
|
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
|
||||||
.rx_flow_ctrl_thresh = 0,
|
.rx_flow_ctrl_thresh = 0,
|
||||||
.source_clk = UART_SCLK_APB,
|
.source_clk = UART_SCLK_APB
|
||||||
.flags = {0}};
|
#if ESP_ARDUINO_VERSION_MAJOR >= 3
|
||||||
|
,
|
||||||
|
.flags = {0}
|
||||||
#endif
|
#endif
|
||||||
|
};
|
||||||
#if defined(EMSUART_RX_INVERT)
|
#if defined(EMSUART_RX_INVERT)
|
||||||
inverse_mask |= UART_SIGNAL_RXD_INV;
|
inverse_mask |= UART_SIGNAL_RXD_INV;
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -166,7 +166,9 @@ StateUpdateResult WebCustomEntity::update(JsonObject root, WebCustomEntity & web
|
|||||||
bool WebCustomEntityService::command_setvalue(const char * value, const int8_t id, const char * name) {
|
bool WebCustomEntityService::command_setvalue(const char * value, const int8_t id, const char * name) {
|
||||||
// don't write if there is no value, to prevent setting an empty value by mistake when parsing attributes
|
// don't write if there is no value, to prevent setting an empty value by mistake when parsing attributes
|
||||||
if (!strlen(value)) {
|
if (!strlen(value)) {
|
||||||
|
#if defined(EMSESP_DEBUG)
|
||||||
EMSESP::logger().debug("can't set empty value!");
|
EMSESP::logger().debug("can't set empty value!");
|
||||||
|
#endif
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -686,7 +688,7 @@ void WebCustomEntityService::fetch() {
|
|||||||
uint8_t stop = (entity.offset + len[entity.value_type]) % fetchblock;
|
uint8_t stop = (entity.offset + len[entity.value_type]) % fetchblock;
|
||||||
bool is_fetched = start < fetchblock && stop < fetchblock; // make sure the complete value is a a fetched block
|
bool is_fetched = start < fetchblock && stop < fetchblock; // make sure the complete value is a a fetched block
|
||||||
for (const auto & emsdevice : EMSESP::emsdevices) {
|
for (const auto & emsdevice : EMSESP::emsdevices) {
|
||||||
if (emsdevice->is_device_id(entity.device_id) && emsdevice->is_fetch(entity.type_id)
|
if (emsdevice->is_device_id(entity.device_id) && emsdevice->is_fetch(entity.type_id, entity.offset + len[entity.value_type])
|
||||||
&& (is_fetched || entity.value_type == DeviceValueType::STRING)) {
|
&& (is_fetched || entity.value_type == DeviceValueType::STRING)) {
|
||||||
needFetch = false;
|
needFetch = false;
|
||||||
break;
|
break;
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user