134 Commits

Author SHA1 Message Date
Yurii
b7825111bb refactor: active + passive BLE scanning 2025-10-22 20:37:36 +03:00
Yurii
d5691ef8f7 refactor: decreased interval and window for scanning BLE 2025-10-22 17:22:02 +03:00
Yurii
0213582464 fix: build for nodemcu32, c3, c6 boards; formatting `platformio.ini` 2025-10-21 13:43:41 +03:00
Yurii
396dc7f7e3 refactor: increased disconnected timeout for sensors 2025-10-20 18:37:35 +03:00
Yurii
9d38525251 refactor: passive scan instead of connecting to BLE devices 2025-10-20 17:59:52 +03:00
Yurii
5667199988 refactor: reset BLE/DALLAS address if not valid 2025-10-14 07:38:30 +03:00
Yurii
cf81ff5856 refactor: improved work with BLE sensors 2025-10-10 05:35:57 +03:00
Yurii
78b5a12e90 feat: setting the cooling setpoint if cooling support is enabled 2025-10-09 22:57:04 +03:00
Yurii
cc5bbb7a87 refactor: refactoring after #184 2025-10-09 08:22:07 +03:00
Douwe
dda79151f3 feat: added Dutch locale #184
* Create nl.json

* Update nl.json
2025-10-09 08:19:55 +03:00
Yurii
1375d8c7a5 chore: bump pioarduino/platform-espressif32 from 3.3.1 to 3.3.2 2025-10-09 08:16:04 +03:00
Yurii
d4addf887f refactor: improved work with BLE sensors 2025-10-09 08:13:33 +03:00
Yurii
9d77256051 refactor: not initialize Bluetooth if BLE sensors are not used 2025-10-03 20:56:04 +03:00
Yurii
d3e7a13e1f fix: rollback NimBLE-Arduino from 2.3.6 to 2.3.3 2025-10-03 20:54:42 +03:00
Yurii
14826c10cd chore: `byte to uint8_t` 2025-09-23 04:03:36 +03:00
Yurii
28a5218b7c fix: various fixes
Found by PVS-Studio
2025-09-23 04:02:40 +03:00
Yurii
f3ba43adbd docs: fix typo 2025-09-19 21:14:38 +03:00
Yurii
062c6714d1 chore: bump dependencies 2025-09-19 21:09:55 +03:00
Yurii
502f73db0e chore: bump pioarduino/platform-espressif32 from 3.3.0 to 3.3.1 2025-09-19 21:06:17 +03:00
zedward
c2ee30d1ab fix: added `nodemcu_32_160mhz` env, deadband usage in PID fixed (#176)
* Small fixes: CPU limit for odemcu_32,  deadbamd usage in PID

* PR review fixes

* Fix: remove spaces

---------

Co-authored-by: Edward Zhuravlov <Edward.Zhuravlev@topsoft.by>
2025-09-19 21:00:11 +03:00
dependabot[bot]
9217bb7448 chore: bump actions/stale from 9 to 10 (#172)
Bumps [actions/stale](https://github.com/actions/stale) from 9 to 10.
- [Release notes](https://github.com/actions/stale/releases)
- [Changelog](https://github.com/actions/stale/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/stale/compare/v9...v10)

---
updated-dependencies:
- dependency-name: actions/stale
  dependency-version: '10'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-05 06:33:43 +03:00
Yurii
d694ae18c0 chore: bump version to 1.5.6 2025-08-21 19:34:07 +03:00
dependabot[bot]
0f4fa2e406 chore(deps): bump actions/checkout from 4 to 5 (#169)
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-12 13:24:55 +03:00
Yurii
df2af928a5 chore: bump pioarduino/platform-espressif32 from 3.2.1 to 3.3.0 2025-08-04 07:08:37 +03:00
Yurii
3e6a2d93d4 chore: updated esp32.py
Source: https://raw.githubusercontent.com/letscontrolit/ESPEasy/mega/tools/pio/post_esp32.py
2025-08-04 07:07:31 +03:00
Yurii
062468c6b1 chore: updated esp32.py to be compatible with new versions of esptool
Source: https://raw.githubusercontent.com/letscontrolit/ESPEasy/mega/tools/pio/post_esp32.py
2025-07-04 04:12:06 +03:00
Yurii
530892dc09 chore: bump pioarduino/platform-espressif32 from 3.2.0 to 3.2.1 2025-07-04 04:10:06 +03:00
Yurii
c73f74ad45 chore: upd platformio.ini 2025-07-04 03:08:51 +03:00
Yurii
a67ff046b0 refactor: refactoring after #163 2025-07-04 03:00:41 +03:00
menyuguren
0f496fee42 feat: added Chinese locale by @menyuguren #163
* Add files via upload

支持中文

* fix typo

---------

Co-authored-by: Yurii <34578544+Laxilef@users.noreply.github.com>
2025-07-04 02:55:09 +03:00
Yurii
e63a0dc7eb refactor: improved freeze protection & overheat protection; added desc for OT options 2025-06-30 02:41:44 +03:00
Yurii
792c6a6668 chore: upd gitignore 2025-06-30 02:38:25 +03:00
Yurii
94f6f14795 refactor: fixed logo size on mobile devices 2025-06-30 02:38:00 +03:00
Yurii
47696a0721 refactor: added print to log channel 2 activity state #155 2025-06-28 20:16:19 +03:00
Yurii
5c4ad8cd07 chore: upd readme 2025-06-27 14:53:17 +03:00
Yurii
f6cfdf3263 feat: added freeze protection parameter for heating, removed forced start of heating in emergency mode #157 2025-06-27 00:28:38 +03:00
Yurii
58b0c18448 fix: added C/F temperature conversion in overheating protection 2025-06-20 02:46:25 +03:00
Yurii
b985275309 feat: added software overheating protection 2025-06-17 17:50:15 +03:00
Yurii
1eee184887 feat: added OT option dhwStateAsDhwBlocking
For Baxi Slim with Bertelli
2025-06-16 01:04:07 +03:00
Yurii
ba03c9cda3 fix: last sensor was ignored 2025-06-08 03:10:10 +03:00
Yurii
14b1eac732 chore: bump version to 1.5.5 2025-06-05 05:40:09 +03:00
Yurii
d16f56d280 fix: build for ESP32 C6 2025-05-23 00:42:29 +03:00
Yurii
8b50ed48c1 fix: revert default value of slave max modulation; always set max modulation level
Baxi Ampera resets the maximum modulation level to 0 when the heating is turned off
2025-05-21 01:17:43 +03:00
Yurii
f212d9d9a8 fix: changed default value of slave max modulation to 0 2025-05-20 00:05:29 +03:00
Yurii
0e78e71493 refactor: more logs 2025-05-19 23:39:15 +03:00
Yurii
655313562d fix: typo 2025-05-19 23:28:55 +03:00
Yurii
c8e7724da8 refactor: cosmetic changes 2025-05-19 23:26:44 +03:00
Yurii
f986129c72 fix: set month of date to OT fixed 2025-05-19 23:16:34 +03:00
Yurii
7b31315242 feat: added OT option to set date and time on the boiler 2025-05-19 22:38:35 +03:00
Yurii
6872cad8ce feat: added new purpose (number) for sensors and added polling for OpenTherm statistical IDs
New sensor types:
* Number of burner starts
* Number of burner starts (DHW)
* Number of pump starts (heating)
* Number of pump starts (DHW)
* Number of burner operating hours
* Number of burner operating hours (DHW)
* Number of pump operating hours (heating)
* Number of pump operating hours (DHW)
2025-05-19 21:09:39 +03:00
Yurii
4b1b7f5857 feat: added OT options: ignore diag state, auto fault reset, auto diag reset 2025-05-18 16:47:28 +03:00
Yurii
a667317412 refactor: increased max. target indoor temp. from 30 to 40 degrees (for control by return temperature) 2025-05-18 16:01:04 +03:00
Yurii
612b17b86f refactor: reworked the setting of the maximum modulation level, added the parameter of the maximum modulation level for DHW 2025-05-18 15:31:49 +03:00
Yurii
7c032ddc2f fix: removed `device_class` for signal quality entities for compatibility with HA 2025.7.0 2025-05-18 15:29:32 +03:00
Yurii
9dd5ee8da5 chore: bump pioarduino/platform-espressif32 from 3.1.3 to 3.2.0 2025-05-08 09:39:30 +03:00
Yurii
7dbd503e1e fix: restarting on critically low heap #151 2025-05-07 06:12:39 +03:00
Yurii
85932fdc1d refactor: rounding `heating.setpointTemp` 2025-03-26 16:14:32 +03:00
Yurii
77d80225ad refactor: small changes 2025-03-18 09:26:53 +03:00
Yurii
dc68315166 refactor: added `Msg type` output to opentherm log 2025-03-18 09:21:00 +03:00
Yurii
5d0ca68dc0 fix: `OpenThermTask::setMaxHeatingTemp(const uint8_t temperature)` changed to OpenThermTask::setMaxHeatingTemp(const float temperature) 2025-03-18 09:19:24 +03:00
Yurii
d50b70c211 refactor: improved work with opentherm on esp32 2025-03-14 05:12:20 +03:00
Yurii
dd53d1ef3e chore: bump version to 1.5.4 2025-03-06 19:15:56 +03:00
Yurii
3bd8010b74 refactor: BLE device support for ESP32 C6 (#147)
Building on ``Arduino``+``ESP-IDF`` with ``h2zero/esp-nimble-cpp`` for ESP32 C6
2025-03-06 04:45:13 +03:00
Yurii
6a26e27d39 refactor: heating temperature step changed
* step changed to 0.1
* added processing of long presses on thermostats in the dashboard
2025-03-06 04:29:01 +03:00
Yurii
8fa440810c refactor: status BLE sensors 2025-03-05 02:28:17 +03:00
Yurii
95b18385ba chore: gitignore update 2025-03-04 17:50:59 +03:00
Yurii
4457e16a8f refactor: increased opentherm disconnect timeout 2025-02-27 12:48:27 +03:00
Yurii
1965ca671e chore: bump pioarduino/platform-espressif32 from 3.1.2 to 3.1.3 2025-02-20 16:50:06 +03:00
Yurii
0d1873ec77 fix: set SSID on click in table of available networks fixed 2025-02-17 19:37:51 +03:00
Yurii
38ec56fb33 fix: working with `Sensors::Type::MANUAL` sensors fixed 2025-02-17 18:58:01 +03:00
Yurii
bb7c3eeba3 feat: added mDNS settings 2025-02-15 00:05:10 +03:00
Yurii
0c778d4c7f refactor: added `robots.txt` to disallow indexing 2025-02-14 23:43:25 +03:00
Yurii
2e5e5e59a8 feat: added mDNS 2025-02-14 07:43:52 +03:00
Yurii
e1623e7b63 chore: bump pioarduino/platform-espressif32 from 3.1.1 to 3.1.2 2025-02-14 06:34:46 +03:00
Yurii
80b91d9a01 feat: generate `network.hostname and settings.mqtt.prefix` if they are empty 2025-02-03 06:38:36 +03:00
Roman Andriadi
25b70e4db5 refactor: allow up to 100x correction of sensor values (#137) 2025-02-03 04:56:00 +03:00
Yurii
1903ee2bc7 chore: bump version to 1.5.3 2025-02-02 08:09:20 +03:00
Yurii
916a710064 refactor: improved connection to wifi 2025-01-30 21:46:06 +03:00
Yurii
38acae417d refactor: reworked BLE sensors
* add clean unused ble instances
* moved subscribe to notify to another method
* set date/time on BLE sensors
2025-01-30 01:46:55 +03:00
Yurii
3bc9fa81a8 feat: added ntp server and timezone settings 2025-01-30 01:25:05 +03:00
Yurii
cc2d6ef385 fix: increase keep alive timeout for mqtt #115 2025-01-24 21:07:08 +03:00
Yurii
fe93c00204 chore: locale correction for pid deadband 2025-01-24 06:42:22 +03:00
Yurii
05a2d080be refactor: default pid coefficients changed 2025-01-24 06:38:10 +03:00
Yurii
664bd7938c fix: sensors pos from 1 on Sensors settings page 2025-01-24 04:48:59 +03:00
Yurii
a78f35328f refactor: increased timeout for nimble 2025-01-24 04:48:09 +03:00
Yurii
eab47af0e1 refactor: added `CONFIG_BT_NIMBLE_EXT_ADV=1 to build_flags` for esp32 s3, esp32 c3 2025-01-24 04:47:29 +03:00
Yurii
c524abd959 refactor: trying to improve connection to BLE devices 2025-01-24 03:17:14 +03:00
Yurii
666786fd65 refactor: small fixes 2025-01-24 03:15:15 +03:00
Yurii
8475833dce feat: added deadband for pid 2025-01-24 01:43:52 +03:00
Yurii
afe710abd3 fix: `inputmode` attribute fixed for pid min temp on settings page 2025-01-20 03:48:54 +03:00
Yurii
1982843624 chore: bump version to 1.5.2 2025-01-19 22:44:52 +03:00
Yurii
bf161c1200 feat: added Italian locale by @bredy73 #132 2025-01-19 22:20:16 +03:00
Yurii
57f1129cee style: formatting 2025-01-14 06:22:04 +03:00
Yurii
0425cdc499 refactor: prohibition of enabling portal auth with an empty login or password 2025-01-14 06:21:32 +03:00
Yurii
53ff69f03a fix: removed `required` attribute for optional parameters on settings page #128 2025-01-14 06:16:15 +03:00
Yurii
e7cae4b950 refactor: improved OT bus reset 2025-01-13 10:56:19 +03:00
Yurii
3ff8f40654 refactor: sensors pos from 1 on `Sensors settings` page 2025-01-13 01:26:22 +03:00
Yurii
d2499a2727 docs: update readme 2025-01-13 01:02:07 +03:00
Yurii
5b7da4ed2a fix: typo 2025-01-09 20:44:12 +03:00
Yurii
8d516c7f95 refactor: optimized work with etag 2025-01-09 19:35:56 +03:00
Yurii
d756716497 chore: bump pioarduino/platform-espressif32 from 3.1.0 to 3.1.1 2025-01-09 19:35:24 +03:00
Stefan S
9a2f9d64ec feat: add support board "OT Thing" (#123)
* Add board "OT Thing"

* style: formatting

---------

Co-authored-by: Yurii <34578544+Laxilef@users.noreply.github.com>
2025-01-08 23:41:06 +03:00
Yurii
0d0926cdac chore: update README 2025-01-07 07:14:19 +03:00
Yurii
3ce3ce5016 chore: move web flasher to `gh-pages` branch 2025-01-07 06:53:08 +03:00
Yurii
6ca6d3cab7 chore: added web flasher 2025-01-07 06:22:41 +03:00
Yurii
e7f3c66e05 chore: bump version to 1.5.1 2025-01-05 21:45:28 +03:00
Yurii
17bd31b2a2 feat: added OT option `Sync max heating temp with target temp` 2025-01-05 17:32:10 +03:00
Yurii
8662b9dc8f chore: update pcb for smt 2025-01-05 01:55:34 +03:00
Yurii
6efa3a52fe refactor: added OT bus reset; increased timings for changing OT status 2025-01-05 01:39:36 +03:00
Yurii
7e31de6c71 fix: typo 2025-01-02 23:21:54 +03:00
Yurii
b53dae6a43 fix: `inputmode` attribute fixed for float settings #117 2024-12-31 01:48:44 +03:00
Yurii
de2318bc6a refactor: compatibility with ArduinoJson 7.3.0 2024-12-30 23:12:31 +03:00
Yurii
081209420a chore: bump bblanchon/ArduinoJson from 7.1.0 to 7.3.0 2024-12-30 23:11:14 +03:00
Yurii
75bc4d5c4a feat: ability to reset fault on the dashboard 2024-12-26 19:37:39 +03:00
Yurii
527e9cc1d6 feat: added OT option `Heating state as summer/winter mode` 2024-12-25 19:30:09 +03:00
Yurii
60b7caf4bc fix: typo 2024-12-25 19:25:22 +03:00
Yurii
e8d0ad0a4e refactor: improved work with clones (not original) of DS18B20 sensors 2024-12-23 11:37:54 +03:00
Yurii
afe269aeff fix: `Content-Length` for pretty json for chunked response 2024-12-23 11:08:37 +03:00
Yurii
4702909043 fix: BLE power 9 dbm with NimBLE-Arduino 2.1.x 2024-12-18 21:58:45 +03:00
Yurii
4c32ccc450 fix: `Content-Length` on esp32 for chunked response 2024-12-18 20:53:33 +03:00
Yurii
2e3b38e14f refactor: compatibility with NimBLE-Arduino 2.1.x 2024-12-17 11:24:49 +03:00
Yurii
b6c80f355f Merge branch 'master' of https://github.com/Laxilef/OTGateway 2024-12-17 11:22:13 +03:00
github-actions[bot]
65b2a3c2bd chore: bump NimBLE-Arduino from 1.4.3 to 2.1.0
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-12-17 11:21:04 +03:00
Yurii
0cb361d243 refactor: OT option `heatingCh2Enabled has been renamed to ch2AlwaysEnabled` 2024-12-17 10:30:06 +03:00
Yurii
c7f54ca4fb chore: bump pioarduino/platform-espressif32 from 3.1.0 rc3 to 3.1.0 2024-12-17 09:45:10 +03:00
Yurii
1d46176b5e chore: update pcb 2024-12-17 04:00:29 +03:00
Yurii
5e2f6c9cea refactor: optimization of the web portal layout 2024-12-16 00:43:32 +03:00
Yurii
f439f8c5ba feat: added OT cooling support flag
* refactoring OT settings struct
* renamed some OT settings
2024-12-15 14:24:05 +03:00
Yurii
bae7770371 chore: update pcb 2024-12-07 23:47:21 +03:00
Yurii
4e5a3e9da5 refactor: little changes 2024-12-07 23:10:50 +03:00
Yurii
412e1594e9 chore: update readme & assets 2024-12-07 23:10:27 +03:00
Yurii
9701e8c97b chore: update readme 2024-12-07 22:59:29 +03:00
Yurii
2fe546812c chore: update readme 2024-12-07 22:47:24 +03:00
Yurii
7efcbaa57e chore: bump version to 1.5.0 2024-12-05 00:04:58 +03:00
52 changed files with 20885 additions and 8979 deletions

View File

@@ -15,7 +15,7 @@ jobs:
name: run PlatformIO Dependabot
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: run PlatformIO Dependabot
uses: peterus/platformio_dependabot@v1.2.0
with:

View File

@@ -7,7 +7,7 @@ jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v9
- uses: actions/stale@v10
with:
stale-issue-message: >
This issue is stale because it has been open 15 days with no activity. Remove stale label or comment or this will be closed in 5 days.

10
.gitignore vendored
View File

@@ -1,8 +1,14 @@
.pio
.vscode
build/*.bin
.PVS-Studio
build/*
data/*
managed_components/*
node_modules/*
secrets.ini
node_modules
package-lock.json
*.lock
sdkconfig.*
CMakeLists.txt
!sdkconfig.defaults
!.gitkeep

View File

@@ -1,49 +1,55 @@
<div align="center">
![logo](/assets/logo.svg)
<br>
[![GitHub version](https://img.shields.io/github/release/Laxilef/OTGateway.svg?include_prereleases)](https://github.com/Laxilef/OTGateway/releases)
[![GitHub download](https://img.shields.io/github/downloads/Laxilef/OTGateway/total.svg)](https://github.com/Laxilef/OTGateway/releases/latest)
[![License](https://img.shields.io/github/license/Laxilef/OTGateway.svg)](LICENSE.txt)
[![Telegram](https://img.shields.io/badge/Telegram-Channel-33A8E3)](https://t.me/otgateway)
</div>
<hr />
![Dashboard](/assets/poster-1.png)
![Configuration](/assets/poster-2.png)
![Integration with HomeAssistant](/assets/poster-3.png)
## Features
- Hot water temperature control
- DHW temperature control
- Heating temperature control
- Smart heating temperature control modes:
- PID
- Equithermic curves - adjusts the temperature based on indoor and outdoor temperatures
- Hysteresis setting (for accurate maintenance of room temperature)
- Ability to connect an external sensors to monitor outdoor and indoor temperature ([compatible sensors](https://github.com/Laxilef/OTGateway/wiki/Compatibility#temperature-sensors))
- Emergency mode. If the Wi-Fi connection is lost or the gateway cannot connect to the MQTT server, the mode will turn on. This mode will automatically maintain the set temperature and prevent your home from freezing. In this mode it is also possible to use equithermal curves (weather-compensated control).
- Automatic error reset (not with all boilers)
- Hysteresis setting _(for accurate maintenance of room temperature)_
- Ability to connect [additional (external) sensors](https://github.com/Laxilef/OTGateway/wiki/Compatibility#temperature-sensors): Dallas (1-wire), NTC 10k, Bluetooth (BLE). Makes it possible to monitor indoor and outdoor temperatures, temperatures on pipes/heat exchangers/etc.
- Emergency mode. In any dangerous situation _(loss of connection with Wifi, MQTT, sensors, etc)_ it will not let you and your home freeze.
- Ability of remote fault reset _(not with all boilers)_
- Diagnostics:
- The process of heating: works/does not work
- The process of heating water for hot water: working/not working
- Display of boiler errors
- Burner status (flame): on/off
- Burner modulation level in percent
- Pressure in the heating system
- Gateway status (depending on errors and connection status)
- Boiler connection status via OpenTherm interface
- The current temperature of the heat carrier (usually the return heat carrier)
- Set heat carrier temperature (depending on the selected mode)
- Current hot water temperature
- Displaying gateway status
- Displaying the connection status to the boiler via OpenTherm
- Displaying the fault status and fault code
- Displaying the diagnostic status & diagnostic code
- Display of the process of heating: works/does not work
- Display of burner (flame) status: on/off
- Display of burner modulation level in percent
- Display of pressure in the heating system
- Display of current temperature of the heat carrier
- Display of return temperature of the heat carrier
- Display of setpoint heat carrier temperature (useful when using PID or Equitherm)
- Display of the process of DHW: working/not working
- Display of current DHW temperature
- _And other information..._
- [Home Assistant](https://www.home-assistant.io/) integration via MQTT. The ability to create any automation for the boiler!
![logo](/assets/ha.png)
## Documentation
All available information and instructions can be found in the wiki:
* [Home](https://github.com/Laxilef/OTGateway/wiki)
* [Quick Start](https://github.com/Laxilef/OTGateway/wiki#quick-start)
* [Build firmware](https://github.com/Laxilef/OTGateway/wiki#build-firmware)
* [Flash firmware via ESP Flash Download Tool](https://github.com/Laxilef/OTGateway/wiki#flash-firmware-via-esp-flash-download-tool)
* [Flashing via Web Flasher](https://github.com/Laxilef/OTGateway/wiki#flashing-via-web-flasher)
* [Flashing via ESP Flash Download Tool](https://github.com/Laxilef/OTGateway/wiki#flashing-via-esp-flash-download-tool)
* [Settings](https://github.com/Laxilef/OTGateway/wiki#settings)
* [External temperature sensors](https://github.com/Laxilef/OTGateway/wiki#external-temperature-sensors)
* [Other external sensors](https://github.com/Laxilef/OTGateway/wiki#other-external-sensors)
* [Reporting indoor/outdoor temperature from any Home Assistant sensor](https://github.com/Laxilef/OTGateway/wiki#reporting-indooroutdoor-temperature-from-any-home-assistant-sensor)
* [Reporting outdoor temperature from Home Assistant weather integration](https://github.com/Laxilef/OTGateway/wiki#reporting-outdoor-temperature-from-home-assistant-weather-integration)
* [DHW meter](https://github.com/Laxilef/OTGateway/wiki#dhw-meter)
@@ -52,6 +58,7 @@ All available information and instructions can be found in the wiki:
* [Ratios](https://github.com/Laxilef/OTGateway/wiki#ratios)
* [Fit coefficients](https://github.com/Laxilef/OTGateway/wiki#fit-coefficients)
* [PID mode](https://github.com/Laxilef/OTGateway/wiki#pid-mode)
* [Logs and debug](https://github.com/Laxilef/OTGateway/wiki#logs-and-debug)
* [Compatibility](https://github.com/Laxilef/OTGateway/wiki/Compatibility)
* [Boilers](https://github.com/Laxilef/OTGateway/wiki/Compatibility#boilers)
* [Boards](https://github.com/Laxilef/OTGateway/wiki/Compatibility#boards)
@@ -64,24 +71,10 @@ All available information and instructions can be found in the wiki:
* [Connection](https://github.com/Laxilef/OTGateway/wiki/OT-adapters#connection)
* [Leds on board](https://github.com/Laxilef/OTGateway/wiki/OT-adapters#leds-on-board)
## Dependencies
- [ESP8266Scheduler](https://github.com/nrwiersma/ESP8266Scheduler) (for ESP8266)
- [ESP32Scheduler](https://github.com/laxilef/ESP32Scheduler) (for ESP32)
- [ArduinoJson](https://github.com/bblanchon/ArduinoJson)
- [OpenTherm Library](https://github.com/ihormelnyk/opentherm_library)
- [ArduinoMqttClient](https://github.com/arduino-libraries/ArduinoMqttClient)
- [ESPTelnet](https://github.com/LennartHennigs/ESPTelnet)
- [FileData](https://github.com/GyverLibs/FileData)
- [GyverPID](https://github.com/GyverLibs/GyverPID)
- [GyverBlinker](https://github.com/GyverLibs/GyverBlinker)
- [DallasTemperature](https://github.com/milesburton/Arduino-Temperature-Control-Library)
- [TinyLogger](https://github.com/laxilef/TinyLogger)
## Debug
To display DEBUG messages you must enable debug in settings (switch is disabled by default).
You can connect via Telnet to read messages. IP: ESP8266 ip, port: 23
___
This project is tested with BrowserStack.
## Gratitude
* To the developers of the libraries used: [OpenTherm Library](https://github.com/ihormelnyk/opentherm_library), [ESP8266Scheduler](https://github.com/nrwiersma/ESP8266Scheduler), [ArduinoJson](https://github.com/bblanchon/ArduinoJson), [NimBLE-Arduino](https://github.com/h2zero/NimBLE-Arduino), [ArduinoMqttClient](https://github.com/arduino-libraries/ArduinoMqttClient), [ESPTelnet](https://github.com/LennartHennigs/ESPTelnet), [FileData](https://github.com/GyverLibs/FileData), [GyverPID](https://github.com/GyverLibs/GyverPID), [GyverBlinker](https://github.com/GyverLibs/GyverBlinker), [OneWireNg](https://github.com/pstolarz/OneWireNg) & [OneWire](https://github.com/PaulStoffregen/OneWire)
* To the [PlatformIO](https://platformio.org/) Team
* To the team and contributors of the [pioarduino](https://github.com/pioarduino/platform-espressif32) project
* To the [BrowserStack](https://www.browserstack.com/) team. This project is tested with BrowserStack.
* To the [PVS-Studio](https://pvs-studio.com/pvs-studio/?utm_source=website&utm_medium=github&utm_campaign=open_source) - static analyzer for C, C++, C#, and Java code.
* And of course to the contributors for their contribution to the development of the project!

Binary file not shown.

Before

Width:  |  Height:  |  Size: 261 KiB

After

Width:  |  Height:  |  Size: 283 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 326 KiB

After

Width:  |  Height:  |  Size: 386 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 684 KiB

After

Width:  |  Height:  |  Size: 849 KiB

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 190 KiB

BIN
assets/poster-1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

BIN
assets/poster-2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

BIN
assets/poster-3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 973 KiB

View File

@@ -34,6 +34,10 @@ let paths = {
{
src: 'src_data/locales/*.json',
dest: 'data/static/locales/'
},
{
src: 'src_data/*.json',
dest: 'data/static/'
}
],
static: [
@@ -44,6 +48,10 @@ let paths = {
{
src: 'src_data/images/*.*',
dest: 'data/static/images/'
},
{
src: 'src_data/*.txt',
dest: 'data/static/'
}
],
pages: {

View File

@@ -12,16 +12,17 @@ public:
template <class T>
void send(int code, T contentType, const JsonVariantConst content, bool pretty = false) {
auto contentLength = pretty ? measureJsonPretty(content) : measureJson(content);
#ifdef ARDUINO_ARCH_ESP8266
if (!this->webServer->chunkedResponseModeStart(code, contentType)) {
this->webServer->send(505, F("text/html"), F("HTTP1.1 required"));
return;
}
this->webServer->setContentLength(measureJson(content));
this->webServer->setContentLength(contentLength);
#else
this->webServer->setContentLength(CONTENT_LENGTH_UNKNOWN);
this->webServer->sendHeader(F("Content-Length"), String(measureJson(content)));
this->webServer->setContentLength(contentLength);
this->webServer->send(code, contentType, emptyString);
#endif

View File

@@ -3,15 +3,15 @@
class CustomOpenTherm : public OpenTherm {
public:
typedef std::function<void()> YieldCallback;
typedef std::function<void(unsigned long, byte)> BeforeSendRequestCallback;
typedef std::function<void(unsigned long, unsigned long, OpenThermResponseStatus, byte)> AfterSendRequestCallback;
typedef std::function<void(unsigned int)> DelayCallback;
typedef std::function<void(unsigned long, uint8_t)> BeforeSendRequestCallback;
typedef std::function<void(unsigned long, unsigned long, OpenThermResponseStatus, uint8_t)> AfterSendRequestCallback;
CustomOpenTherm(int inPin = 4, int outPin = 5, bool isSlave = false) : OpenTherm(inPin, outPin, isSlave) {}
CustomOpenTherm(int inPin = 4, int outPin = 5, bool isSlave = false, bool alwaysReceive = false) : OpenTherm(inPin, outPin, isSlave, alwaysReceive) {}
~CustomOpenTherm() {}
CustomOpenTherm* setYieldCallback(YieldCallback callback = nullptr) {
this->yieldCallback = callback;
CustomOpenTherm* setDelayCallback(DelayCallback callback = nullptr) {
this->delayCallback = callback;
return this;
}
@@ -28,76 +28,48 @@ public:
return this;
}
unsigned long sendRequest(unsigned long request, byte attempts = 5, byte _attempt = 0) {
_attempt++;
unsigned long sendRequest(unsigned long request) override {
this->sendRequestAttempt++;
while (!this->isReady()) {
if (this->yieldCallback) {
this->yieldCallback();
} else {
::yield();
if (this->delayCallback) {
this->delayCallback(150);
}
this->process();
}
if (this->beforeSendRequestCallback) {
this->beforeSendRequestCallback(request, _attempt);
this->beforeSendRequestCallback(request, this->sendRequestAttempt);
}
unsigned long _response;
OpenThermResponseStatus _responseStatus = OpenThermResponseStatus::NONE;
if (!this->sendRequestAsync(request)) {
_response = 0;
} else {
while (true) {
this->process();
if (this->status == OpenThermStatus::READY || this->status == OpenThermStatus::DELAY) {
break;
} else if (this->yieldCallback) {
this->yieldCallback();
} else {
::yield();
if (this->sendRequestAsync(request)) {
do {
if (this->delayCallback) {
this->delayCallback(150);
}
}
_response = this->getLastResponse();
_responseStatus = this->getLastResponseStatus();
this->process();
} while (this->status != OpenThermStatus::READY && this->status != OpenThermStatus::DELAY);
}
if (this->afterSendRequestCallback) {
this->afterSendRequestCallback(request, _response, _responseStatus, _attempt);
this->afterSendRequestCallback(request, this->response, this->responseStatus, this->sendRequestAttempt);
}
if (_responseStatus == OpenThermResponseStatus::SUCCESS || _responseStatus == OpenThermResponseStatus::INVALID || _attempt >= attempts) {
return _response;
if (this->responseStatus == OpenThermResponseStatus::SUCCESS || this->responseStatus == OpenThermResponseStatus::INVALID) {
this->sendRequestAttempt = 0;
return this->response;
} else if (this->sendRequestAttempt >= this->sendRequestMaxAttempts) {
this->sendRequestAttempt = 0;
return this->response;
} else {
return this->sendRequest(request, attempts, _attempt);
return this->sendRequest(request);
}
}
unsigned long setBoilerStatus(bool enableCentralHeating, bool enableHotWater, bool enableCooling, bool enableOutsideTemperatureCompensation, bool enableCentralHeating2, bool summerWinterMode, bool dhwBlocking, uint8_t lb = 0) {
unsigned int data = enableCentralHeating
| (enableHotWater << 1)
| (enableCooling << 2)
| (enableOutsideTemperatureCompensation << 3)
| (enableCentralHeating2 << 4)
| (summerWinterMode << 5)
| (dhwBlocking << 6);
data <<= 8;
data |= lb;
return this->sendRequest(buildRequest(
OpenThermMessageType::READ_DATA,
OpenThermMessageID::Status,
data
));
}
bool sendBoilerReset() {
unsigned int data = 1;
data <<= 8;
@@ -134,10 +106,35 @@ public:
return isValidResponse(response) && isValidResponseId(response, OpenThermMessageID::RemoteRequest);
}
static bool isCh2Active(unsigned long response) {
return response & 0x20;
}
static bool isValidResponseId(unsigned long response, OpenThermMessageID id) {
byte responseId = (response >> 16) & 0xFF;
return (byte)id == responseId;
uint8_t responseId = (response >> 16) & 0xFF;
return (uint8_t)id == responseId;
}
static uint8_t getResponseMessageTypeId(unsigned long response) {
return (response << 1) >> 29;
}
static const char* getResponseMessageTypeString(unsigned long response) {
uint8_t msgType = getResponseMessageTypeId(response);
switch (msgType) {
case (uint8_t) OpenThermMessageType::READ_ACK:
case (uint8_t) OpenThermMessageType::WRITE_ACK:
case (uint8_t) OpenThermMessageType::DATA_INVALID:
case (uint8_t) OpenThermMessageType::UNKNOWN_DATA_ID:
return CustomOpenTherm::messageTypeToString(
static_cast<OpenThermMessageType>(msgType)
);
default:
return "UNKNOWN";
}
}
// converters
@@ -151,7 +148,9 @@ public:
}
protected:
YieldCallback yieldCallback;
const uint8_t sendRequestMaxAttempts = 5;
uint8_t sendRequestAttempt = 0;
DelayCallback delayCallback;
BeforeSendRequestCallback beforeSendRequestCallback;
AfterSendRequestCallback afterSendRequestCallback;
};

View File

@@ -35,7 +35,7 @@ namespace NetworkUtils {
return this;
}
NetworkMgr* setApCredentials(const char* ssid, const char* password = nullptr, byte channel = 0) {
NetworkMgr* setApCredentials(const char* ssid, const char* password = nullptr, uint8_t channel = 0) {
this->apName = ssid;
this->apPassword = password;
this->apChannel = channel;
@@ -43,7 +43,7 @@ namespace NetworkUtils {
return this;
}
NetworkMgr* setStaCredentials(const char* ssid = nullptr, const char* password = nullptr, byte channel = 0) {
NetworkMgr* setStaCredentials(const char* ssid = nullptr, const char* password = nullptr, uint8_t channel = 0) {
this->staSsid = ssid;
this->staPassword = password;
this->staChannel = channel;
@@ -140,7 +140,7 @@ namespace NetworkUtils {
return this->staPassword;
}
byte getStaChannel() {
uint8_t getStaChannel() {
return this->staChannel;
}
@@ -148,10 +148,10 @@ namespace NetworkUtils {
// set policy manual for work 13 ch
{
#ifdef ARDUINO_ARCH_ESP8266
wifi_country_t country = {"CN", 1, 13, WIFI_COUNTRY_POLICY_AUTO};
wifi_country_t country = {"JP", 1, 14, WIFI_COUNTRY_POLICY_MANUAL};
wifi_set_country(&country);
#elif defined(ARDUINO_ARCH_ESP32)
const wifi_country_t country = {"CN", 1, 13, CONFIG_ESP32_PHY_MAX_WIFI_TX_POWER, WIFI_COUNTRY_POLICY_AUTO};
const wifi_country_t country = {"JP", 1, 14, CONFIG_ESP32_PHY_MAX_WIFI_TX_POWER, WIFI_COUNTRY_POLICY_MANUAL};
esp_wifi_set_country(&country);
#endif
}
@@ -377,14 +377,14 @@ namespace NetworkUtils {
}
}
static byte rssiToSignalQuality(short int rssi) {
static uint8_t rssiToSignalQuality(short int rssi) {
return constrain(map(rssi, -100, -50, 0, 100), 0, 100);
}
protected:
const unsigned int reconnectInterval = 15000;
const unsigned int failedConnectTimeout = 185000;
const unsigned int connectionTimeout = 5000;
const unsigned int connectionTimeout = 10000;
const unsigned int resetConnectionTimeout = 90000;
YieldCallback yieldCallback = []() {
@@ -397,11 +397,11 @@ namespace NetworkUtils {
const char* hostname = "esp";
const char* apName = "ESP";
const char* apPassword = nullptr;
byte apChannel = 1;
uint8_t apChannel = 1;
const char* staSsid = nullptr;
const char* staPassword = nullptr;
byte staChannel = 0;
uint8_t staChannel = 0;
bool useDhcp = true;
IPAddress staticIp;

View File

@@ -1,8 +1,5 @@
#include <FS.h>
#include <detail/mimetable.h>
#if defined(ARDUINO_ARCH_ESP32)
#include <detail/RequestHandlersImpl.h>
#endif
using namespace mime;
@@ -54,13 +51,6 @@ public:
if (this->eTag.isEmpty()) {
if (server._eTagFunction) {
this->eTag = (server._eTagFunction)(*this->fs, this->path);
} else {
#if defined(ARDUINO_ARCH_ESP8266)
this->eTag = esp8266webserver::calcETag(*this->fs, this->path);
#elif defined(ARDUINO_ARCH_ESP32)
this->eTag = StaticRequestHandler::calcETag(*this->fs, this->path);
#endif
}
}

View File

@@ -1,303 +1,270 @@
; PlatformIO Project Configuration File
;
; Build options: build flags, source filter
; Upload options: custom upload port, speed and extra flags
; Library options: dependencies, extra library storages
; Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html
[platformio]
;extra_configs = secrets.ini
extra_configs = secrets.default.ini
core_dir = .pio
;extra_configs = secrets.ini
extra_configs = secrets.default.ini
core_dir = .pio
[env]
version = 1.5.0
framework = arduino
lib_deps =
bblanchon/ArduinoJson@^7.1.0
;ihormelnyk/OpenTherm Library@^1.1.5
https://github.com/ihormelnyk/opentherm_library#master
;arduino-libraries/ArduinoMqttClient@^0.1.8
https://github.com/Laxilef/ArduinoMqttClient.git#esp32_core_310
lennarthennigs/ESP Telnet@^2.2
gyverlibs/FileData@^1.0.2
gyverlibs/GyverPID@^3.3.2
gyverlibs/GyverBlinker@^1.1.1
https://github.com/pstolarz/Arduino-Temperature-Control-Library.git#OneWireNg
laxilef/TinyLogger@^1.1.1
build_type = ${secrets.build_type}
build_flags =
-mtext-section-literals
-D MQTT_CLIENT_STD_FUNCTION_CALLBACK=1
;-D DEBUG_ESP_CORE -D DEBUG_ESP_WIFI -D DEBUG_ESP_HTTP_SERVER -D DEBUG_ESP_PORT=Serial
-D BUILD_VERSION='"${this.version}"'
-D BUILD_ENV='"$PIOENV"'
-D DEFAULT_SERIAL_ENABLED=${secrets.serial_enabled}
-D DEFAULT_SERIAL_BAUD=${secrets.serial_baud}
-D DEFAULT_TELNET_ENABLED=${secrets.telnet_enabled}
-D DEFAULT_TELNET_PORT=${secrets.telnet_port}
-D DEFAULT_LOG_LEVEL=${secrets.log_level}
-D DEFAULT_HOSTNAME='"${secrets.hostname}"'
-D DEFAULT_AP_SSID='"${secrets.ap_ssid}"'
-D DEFAULT_AP_PASSWORD='"${secrets.ap_password}"'
-D DEFAULT_STA_SSID='"${secrets.sta_ssid}"'
-D DEFAULT_STA_PASSWORD='"${secrets.sta_password}"'
-D DEFAULT_PORTAL_LOGIN='"${secrets.portal_login}"'
-D DEFAULT_PORTAL_PASSWORD='"${secrets.portal_password}"'
-D DEFAULT_MQTT_ENABLED=${secrets.mqtt_enabled}
-D DEFAULT_MQTT_SERVER='"${secrets.mqtt_server}"'
-D DEFAULT_MQTT_PORT=${secrets.mqtt_port}
-D DEFAULT_MQTT_USER='"${secrets.mqtt_user}"'
-D DEFAULT_MQTT_PASSWORD='"${secrets.mqtt_password}"'
-D DEFAULT_MQTT_PREFIX='"${secrets.mqtt_prefix}"'
upload_speed = 921600
monitor_speed = 115200
;monitor_filters = direct
monitor_filters =
esp32_exception_decoder
esp8266_exception_decoder
board_build.flash_mode = dio
board_build.filesystem = littlefs
version = 1.5.7-passiveble
framework = arduino
lib_deps = bblanchon/ArduinoJson@^7.4.2
;ihormelnyk/OpenTherm Library@^1.1.5
https://github.com/Laxilef/opentherm_library#esp32_timer
arduino-libraries/ArduinoMqttClient@^0.1.8
lennarthennigs/ESP Telnet@^2.2.3
gyverlibs/FileData@^1.0.3
gyverlibs/GyverPID@^3.3.2
gyverlibs/GyverBlinker@^1.1.1
https://github.com/pstolarz/Arduino-Temperature-Control-Library.git#OneWireNg
laxilef/TinyLogger@^1.1.1
build_type = ${secrets.build_type}
build_flags = -mtext-section-literals
-Wno-deprecated-declarations
-D MQTT_CLIENT_STD_FUNCTION_CALLBACK=1
;-D DEBUG_ESP_CORE -D DEBUG_ESP_WIFI -D DEBUG_ESP_HTTP_SERVER -D DEBUG_ESP_PORT=Serial
-D BUILD_VERSION='"${this.version}"'
-D BUILD_ENV='"$PIOENV"'
-D DEFAULT_SERIAL_ENABLED=${secrets.serial_enabled}
-D DEFAULT_SERIAL_BAUD=${secrets.serial_baud}
-D DEFAULT_TELNET_ENABLED=${secrets.telnet_enabled}
-D DEFAULT_TELNET_PORT=${secrets.telnet_port}
-D DEFAULT_LOG_LEVEL=${secrets.log_level}
-D DEFAULT_HOSTNAME='"${secrets.hostname}"'
-D DEFAULT_AP_SSID='"${secrets.ap_ssid}"'
-D DEFAULT_AP_PASSWORD='"${secrets.ap_password}"'
-D DEFAULT_STA_SSID='"${secrets.sta_ssid}"'
-D DEFAULT_STA_PASSWORD='"${secrets.sta_password}"'
-D DEFAULT_PORTAL_LOGIN='"${secrets.portal_login}"'
-D DEFAULT_PORTAL_PASSWORD='"${secrets.portal_password}"'
-D DEFAULT_MQTT_ENABLED=${secrets.mqtt_enabled}
-D DEFAULT_MQTT_SERVER='"${secrets.mqtt_server}"'
-D DEFAULT_MQTT_PORT=${secrets.mqtt_port}
-D DEFAULT_MQTT_USER='"${secrets.mqtt_user}"'
-D DEFAULT_MQTT_PASSWORD='"${secrets.mqtt_password}"'
-D DEFAULT_MQTT_PREFIX='"${secrets.mqtt_prefix}"'
upload_speed = 921600
monitor_speed = 115200
;monitor_filters = direct
monitor_filters = esp32_exception_decoder
esp8266_exception_decoder
board_build.flash_mode = dio
board_build.filesystem = littlefs
check_tool = ;pvs-studio
check_flags = ;pvs-studio: --analysis-mode=4 --exclude-path=./.pio/libdeps
; Defaults
[esp8266_defaults]
platform = espressif8266@^4.2.1
lib_deps =
${env.lib_deps}
nrwiersma/ESP8266Scheduler@^1.2
lib_ignore =
extra_scripts =
post:tools/build.py
build_type = ${env.build_type}
build_flags =
${env.build_flags}
-D PIO_FRAMEWORK_ARDUINO_LWIP2_LOW_MEMORY
;-D PIO_FRAMEWORK_ARDUINO_LWIP2_HIGHER_BANDWIDTH_LOW_FLASH
-D PIO_FRAMEWORK_ARDUINO_ESPRESSIF_SDK305
board_build.ldscript = eagle.flash.4m1m.ld
platform = espressif8266@^4.2.1
platform_packages = ${env.platform_packages}
lib_deps = ${env.lib_deps}
nrwiersma/ESP8266Scheduler@^1.2
lib_ignore =
extra_scripts = post:tools/build.py
build_type = ${env.build_type}
build_flags = ${env.build_flags}
-D PIO_FRAMEWORK_ARDUINO_LWIP2_LOW_MEMORY
;-D PIO_FRAMEWORK_ARDUINO_LWIP2_HIGHER_BANDWIDTH_LOW_FLASH
-D PIO_FRAMEWORK_ARDUINO_ESPRESSIF_SDK305
board_build.ldscript = eagle.flash.4m1m.ld
check_tool = ${env.check_tool}
check_flags = ${env.check_flags}
[esp32_defaults]
;platform = espressif32@^6.7
;platform = https://github.com/platformio/platform-espressif32.git
;platform_packages =
; framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git#3.0.5
; framework-arduinoespressif32-libs @ https://github.com/espressif/esp32-arduino-lib-builder/releases/download/idf-release_v5.1/esp32-arduino-libs-idf-release_v5.1-33fbade6.zip
platform = https://github.com/pioarduino/platform-espressif32/releases/download/53.03.10-rc3/platform-espressif32.zip
platform_packages =
board_build.partitions = esp32_partitions.csv
lib_deps =
${env.lib_deps}
laxilef/ESP32Scheduler@^1.0.1
nimble_lib = h2zero/NimBLE-Arduino@^1.4.2
lib_ignore =
extra_scripts =
post:tools/esp32.py
post:tools/build.py
build_type = ${env.build_type}
build_flags =
${env.build_flags}
-D CORE_DEBUG_LEVEL=0
-Wl,--wrap=esp_panic_handler
platform = https://github.com/pioarduino/platform-espressif32/releases/download/55.03.32/platform-espressif32.zip
platform_packages = ${env.platform_packages}
board_build.partitions = esp32_partitions.csv
lib_deps = ${env.lib_deps}
laxilef/ESP32Scheduler@^1.0.1
nimble_lib = h2zero/NimBLE-Arduino@2.3.6
lib_ignore = BluetoothSerial
SimpleBLE
ESP RainMaker
RainMaker
ESP Insights
Insights
Zigbee
Matter
OpenThread
dsp
custom_component_remove = espressif/esp_hosted
espressif/esp_wifi_remote
espressif/esp-dsp
espressif/esp_modem
espressif/esp_rainmaker
espressif/rmaker_common
espressif/esp_insights
espressif/esp_diag_data_store
espressif/esp_diagnostics
espressif/libsodium
espressif/esp-modbus
espressif/esp-cbor
espressif/esp-sr
espressif/esp32-camera
espressif/qrcode
espressif/esp-zboss-lib
espressif/esp-zigbee-lib
chmorgan/esp-libhelix-mp3
extra_scripts = post:tools/esp32.py
post:tools/build.py
build_type = ${env.build_type}
build_flags = ${env.build_flags}
-D CORE_DEBUG_LEVEL=0
-Wl,--wrap=esp_panic_handler
check_tool = ${env.check_tool}
check_flags = ${env.check_flags}
; Boards
[env:d1_mini]
platform = ${esp8266_defaults.platform}
board = d1_mini
lib_deps = ${esp8266_defaults.lib_deps}
lib_ignore = ${esp8266_defaults.lib_ignore}
extra_scripts = ${esp8266_defaults.extra_scripts}
board_build.ldscript = ${esp8266_defaults.board_build.ldscript}
build_type = ${esp8266_defaults.build_type}
build_flags =
${esp8266_defaults.build_flags}
-D DEFAULT_OT_IN_GPIO=4
-D DEFAULT_OT_OUT_GPIO=5
-D DEFAULT_SENSOR_OUTDOOR_GPIO=12
-D DEFAULT_SENSOR_INDOOR_GPIO=14
-D DEFAULT_STATUS_LED_GPIO=13
-D DEFAULT_OT_RX_LED_GPIO=15
extends = esp8266_defaults
board = d1_mini
build_flags = ${esp8266_defaults.build_flags}
-D DEFAULT_OT_IN_GPIO=4
-D DEFAULT_OT_OUT_GPIO=5
-D DEFAULT_SENSOR_OUTDOOR_GPIO=12
-D DEFAULT_SENSOR_INDOOR_GPIO=14
-D DEFAULT_STATUS_LED_GPIO=13
-D DEFAULT_OT_RX_LED_GPIO=15
[env:d1_mini_lite]
platform = ${esp8266_defaults.platform}
board = d1_mini_lite
lib_deps = ${esp8266_defaults.lib_deps}
lib_ignore = ${esp8266_defaults.lib_ignore}
extra_scripts = ${esp8266_defaults.extra_scripts}
board_build.ldscript = ${esp8266_defaults.board_build.ldscript}
build_type = ${esp8266_defaults.build_type}
build_flags =
${esp8266_defaults.build_flags}
-D DEFAULT_OT_IN_GPIO=4
-D DEFAULT_OT_OUT_GPIO=5
-D DEFAULT_SENSOR_OUTDOOR_GPIO=12
-D DEFAULT_SENSOR_INDOOR_GPIO=14
-D DEFAULT_STATUS_LED_GPIO=13
-D DEFAULT_OT_RX_LED_GPIO=15
extends = esp8266_defaults
board = d1_mini_lite
build_flags = ${esp8266_defaults.build_flags}
-D DEFAULT_OT_IN_GPIO=4
-D DEFAULT_OT_OUT_GPIO=5
-D DEFAULT_SENSOR_OUTDOOR_GPIO=12
-D DEFAULT_SENSOR_INDOOR_GPIO=14
-D DEFAULT_STATUS_LED_GPIO=13
-D DEFAULT_OT_RX_LED_GPIO=15
[env:d1_mini_pro]
platform = ${esp8266_defaults.platform}
board = d1_mini_pro
lib_deps = ${esp8266_defaults.lib_deps}
lib_ignore = ${esp8266_defaults.lib_ignore}
extra_scripts = ${esp8266_defaults.extra_scripts}
board_build.ldscript = ${esp8266_defaults.board_build.ldscript}
build_type = ${esp8266_defaults.build_type}
build_flags =
${esp8266_defaults.build_flags}
-D DEFAULT_OT_IN_GPIO=4
-D DEFAULT_OT_OUT_GPIO=5
-D DEFAULT_SENSOR_OUTDOOR_GPIO=12
-D DEFAULT_SENSOR_INDOOR_GPIO=14
-D DEFAULT_STATUS_LED_GPIO=13
-D DEFAULT_OT_RX_LED_GPIO=15
extends = esp8266_defaults
board = d1_mini_pro
build_flags = ${esp8266_defaults.build_flags}
-D DEFAULT_OT_IN_GPIO=4
-D DEFAULT_OT_OUT_GPIO=5
-D DEFAULT_SENSOR_OUTDOOR_GPIO=12
-D DEFAULT_SENSOR_INDOOR_GPIO=14
-D DEFAULT_STATUS_LED_GPIO=13
-D DEFAULT_OT_RX_LED_GPIO=15
[env:nodemcu_8266]
platform = ${esp8266_defaults.platform}
board = nodemcuv2
lib_deps = ${esp8266_defaults.lib_deps}
lib_ignore = ${esp8266_defaults.lib_ignore}
extra_scripts = ${esp8266_defaults.extra_scripts}
board_build.ldscript = ${esp8266_defaults.board_build.ldscript}
build_type = ${esp8266_defaults.build_type}
build_flags =
${esp8266_defaults.build_flags}
-D DEFAULT_OT_IN_GPIO=13
-D DEFAULT_OT_OUT_GPIO=15
-D DEFAULT_SENSOR_OUTDOOR_GPIO=12
-D DEFAULT_SENSOR_INDOOR_GPIO=4
-D DEFAULT_STATUS_LED_GPIO=2
-D DEFAULT_OT_RX_LED_GPIO=16
extends = esp8266_defaults
board = nodemcuv2
build_flags = ${esp8266_defaults.build_flags}
-D DEFAULT_OT_IN_GPIO=13
-D DEFAULT_OT_OUT_GPIO=15
-D DEFAULT_SENSOR_OUTDOOR_GPIO=12
-D DEFAULT_SENSOR_INDOOR_GPIO=4
-D DEFAULT_STATUS_LED_GPIO=2
-D DEFAULT_OT_RX_LED_GPIO=16
[env:s2_mini]
platform = ${esp32_defaults.platform}
platform_packages = ${esp32_defaults.platform_packages}
board = lolin_s2_mini
board_build.partitions = ${esp32_defaults.board_build.partitions}
lib_deps = ${esp32_defaults.lib_deps}
lib_ignore = ${esp32_defaults.lib_ignore}
extra_scripts = ${esp32_defaults.extra_scripts}
build_unflags =
-DARDUINO_USB_MODE=1
build_type = ${esp32_defaults.build_type}
build_flags =
${esp32_defaults.build_flags}
-D ARDUINO_USB_MODE=0
-D ARDUINO_USB_CDC_ON_BOOT=1
-D DEFAULT_OT_IN_GPIO=33
-D DEFAULT_OT_OUT_GPIO=35
-D DEFAULT_SENSOR_OUTDOOR_GPIO=9
-D DEFAULT_SENSOR_INDOOR_GPIO=7
-D DEFAULT_STATUS_LED_GPIO=11
-D DEFAULT_OT_RX_LED_GPIO=12
extends = esp32_defaults
board = lolin_s2_mini
build_unflags = -DARDUINO_USB_MODE=1
build_flags = ${esp32_defaults.build_flags}
-D ARDUINO_USB_MODE=0
-D ARDUINO_USB_CDC_ON_BOOT=1
-D DEFAULT_OT_IN_GPIO=33
-D DEFAULT_OT_OUT_GPIO=35
-D DEFAULT_SENSOR_OUTDOOR_GPIO=9
-D DEFAULT_SENSOR_INDOOR_GPIO=7
-D DEFAULT_STATUS_LED_GPIO=11
-D DEFAULT_OT_RX_LED_GPIO=12
[env:s3_mini]
platform = ${esp32_defaults.platform}
platform_packages = ${esp32_defaults.platform_packages}
board = lolin_s3_mini
board_build.partitions = ${esp32_defaults.board_build.partitions}
lib_deps =
${esp32_defaults.lib_deps}
${esp32_defaults.nimble_lib}
lib_ignore = ${esp32_defaults.lib_ignore}
extra_scripts = ${esp32_defaults.extra_scripts}
build_unflags =
-DARDUINO_USB_MODE=1
build_type = ${esp32_defaults.build_type}
build_flags =
${esp32_defaults.build_flags}
-D ARDUINO_USB_MODE=0
-D ARDUINO_USB_CDC_ON_BOOT=1
-D USE_BLE=1
-D DEFAULT_OT_IN_GPIO=35
-D DEFAULT_OT_OUT_GPIO=36
-D DEFAULT_SENSOR_OUTDOOR_GPIO=13
-D DEFAULT_SENSOR_INDOOR_GPIO=12
-D DEFAULT_STATUS_LED_GPIO=11
-D DEFAULT_OT_RX_LED_GPIO=10
[env:s3_mini]
extends = esp32_defaults
board = lolin_s3_mini
lib_deps = ${esp32_defaults.lib_deps}
;${esp32_defaults.nimble_lib}
https://github.com/h2zero/NimBLE-Arduino
build_unflags = -DARDUINO_USB_MODE=1
build_flags = ${esp32_defaults.build_flags}
-D ARDUINO_USB_MODE=0
-D ARDUINO_USB_CDC_ON_BOOT=1
-D USE_BLE=1
-D MYNEWT_VAL_BLE_EXT_ADV=1
-D DEFAULT_OT_IN_GPIO=35
-D DEFAULT_OT_OUT_GPIO=36
-D DEFAULT_SENSOR_OUTDOOR_GPIO=13
-D DEFAULT_SENSOR_INDOOR_GPIO=12
-D DEFAULT_STATUS_LED_GPIO=11
-D DEFAULT_OT_RX_LED_GPIO=10
[env:c3_mini]
platform = ${esp32_defaults.platform}
platform_packages = ${esp32_defaults.platform_packages}
board = lolin_c3_mini
board_build.partitions = ${esp32_defaults.board_build.partitions}
lib_deps =
${esp32_defaults.lib_deps}
${esp32_defaults.nimble_lib}
lib_ignore = ${esp32_defaults.lib_ignore}
extra_scripts = ${esp32_defaults.extra_scripts}
build_unflags =
-mtext-section-literals
build_type = ${esp32_defaults.build_type}
build_flags =
${esp32_defaults.build_flags}
-D USE_BLE=1
-D DEFAULT_OT_IN_GPIO=8
-D DEFAULT_OT_OUT_GPIO=10
-D DEFAULT_SENSOR_OUTDOOR_GPIO=0
-D DEFAULT_SENSOR_INDOOR_GPIO=1
-D DEFAULT_STATUS_LED_GPIO=4
-D DEFAULT_OT_RX_LED_GPIO=5
[env:c3_mini]
extends = esp32_defaults
board = lolin_c3_mini
lib_deps = ${esp32_defaults.lib_deps}
${esp32_defaults.nimble_lib}
build_unflags = -mtext-section-literals
build_flags = ${esp32_defaults.build_flags}
-D USE_BLE=1
-D DEFAULT_OT_IN_GPIO=8
-D DEFAULT_OT_OUT_GPIO=10
-D DEFAULT_SENSOR_OUTDOOR_GPIO=0
-D DEFAULT_SENSOR_INDOOR_GPIO=1
-D DEFAULT_STATUS_LED_GPIO=4
-D DEFAULT_OT_RX_LED_GPIO=5
[env:nodemcu_32]
platform = ${esp32_defaults.platform}
platform_packages = ${esp32_defaults.platform_packages}
board = nodemcu-32s
board_build.partitions = ${esp32_defaults.board_build.partitions}
lib_deps =
${esp32_defaults.lib_deps}
${esp32_defaults.nimble_lib}
lib_ignore = ${esp32_defaults.lib_ignore}
extra_scripts = ${esp32_defaults.extra_scripts}
build_type = ${esp32_defaults.build_type}
build_flags =
${esp32_defaults.build_flags}
-D USE_BLE=1
-D DEFAULT_OT_IN_GPIO=16
-D DEFAULT_OT_OUT_GPIO=4
-D DEFAULT_SENSOR_OUTDOOR_GPIO=15
-D DEFAULT_SENSOR_INDOOR_GPIO=26
-D DEFAULT_STATUS_LED_GPIO=2
-D DEFAULT_OT_RX_LED_GPIO=19
extends = esp32_defaults
board = nodemcu-32s
lib_deps = ${esp32_defaults.lib_deps}
${esp32_defaults.nimble_lib}
build_flags = ${esp32_defaults.build_flags}
-D USE_BLE=1
-D DEFAULT_OT_IN_GPIO=16
-D DEFAULT_OT_OUT_GPIO=4
-D DEFAULT_SENSOR_OUTDOOR_GPIO=15
-D DEFAULT_SENSOR_INDOOR_GPIO=26
-D DEFAULT_STATUS_LED_GPIO=2
-D DEFAULT_OT_RX_LED_GPIO=19
[env:nodemcu_32_160mhz]
extends = env:nodemcu_32
board_build.f_cpu = 160000000L ; set frequency to 160MHz
[env:d1_mini32]
platform = ${esp32_defaults.platform}
platform_packages = ${esp32_defaults.platform_packages}
board = wemos_d1_mini32
board_build.partitions = ${esp32_defaults.board_build.partitions}
lib_deps =
${esp32_defaults.lib_deps}
${esp32_defaults.nimble_lib}
lib_ignore = ${esp32_defaults.lib_ignore}
extra_scripts = ${esp32_defaults.extra_scripts}
build_type = ${esp32_defaults.build_type}
build_flags =
${esp32_defaults.build_flags}
-D USE_BLE=1
-D DEFAULT_OT_IN_GPIO=21
-D DEFAULT_OT_OUT_GPIO=22
-D DEFAULT_SENSOR_OUTDOOR_GPIO=12
-D DEFAULT_SENSOR_INDOOR_GPIO=18
-D DEFAULT_STATUS_LED_GPIO=2
-D DEFAULT_OT_RX_LED_GPIO=19
extends = esp32_defaults
board = wemos_d1_mini32
lib_deps = ${esp32_defaults.lib_deps}
${esp32_defaults.nimble_lib}
build_flags = ${esp32_defaults.build_flags}
-D USE_BLE=1
-D DEFAULT_OT_IN_GPIO=21
-D DEFAULT_OT_OUT_GPIO=22
-D DEFAULT_SENSOR_OUTDOOR_GPIO=12
-D DEFAULT_SENSOR_INDOOR_GPIO=18
-D DEFAULT_STATUS_LED_GPIO=2
-D DEFAULT_OT_RX_LED_GPIO=19
[env:esp32_c6]
platform = ${esp32_defaults.platform}
platform_packages = ${esp32_defaults.platform_packages}
board = esp32-c6-devkitm-1
board_build.partitions = ${esp32_defaults.board_build.partitions}
lib_deps =
${esp32_defaults.lib_deps}
;${esp32_defaults.nimble_lib}
lib_ignore = ${esp32_defaults.lib_ignore}
extra_scripts = ${esp32_defaults.extra_scripts}
build_unflags =
-mtext-section-literals
build_type = ${esp32_defaults.build_type}
build_flags =
${esp32_defaults.build_flags}
; Currently the NimBLE library is incompatible with ESP32 C6
;-D USE_BLE=1
extends = esp32_defaults
board = esp32-c6-devkitc-1
lib_deps = ${esp32_defaults.lib_deps}
${esp32_defaults.nimble_lib}
build_unflags = -mtext-section-literals
build_flags = ${esp32_defaults.build_flags}
-D USE_BLE=1
-D DEFAULT_OT_IN_GPIO=15
-D DEFAULT_OT_OUT_GPIO=23
-D DEFAULT_SENSOR_OUTDOOR_GPIO=0
-D DEFAULT_SENSOR_INDOOR_GPIO=0
-D DEFAULT_STATUS_LED_GPIO=11
-D DEFAULT_OT_RX_LED_GPIO=10
[env:otthing]
extends = esp32_defaults
board = lolin_c3_mini
lib_deps = ${esp32_defaults.lib_deps}
${esp32_defaults.nimble_lib}
build_unflags = -mtext-section-literals
build_flags = ${esp32_defaults.build_flags}
-D USE_BLE=1
-D DEFAULT_OT_IN_GPIO=3
-D DEFAULT_OT_OUT_GPIO=1
;-D DEFAULT_SENSOR_OUTDOOR_GPIO=0
;-D DEFAULT_SENSOR_INDOOR_GPIO=1
-D DEFAULT_STATUS_LED_GPIO=8
-D DEFAULT_OT_RX_LED_GPIO=2
-D OT_BYPASS_RELAY_GPIO=20

33
sdkconfig.defaults Normal file
View File

@@ -0,0 +1,33 @@
# Source:
# https://github.com/pioarduino/platform-espressif32/tree/main/examples/espidf-arduino-h2zero-BLE_scan
CONFIG_FREERTOS_HZ=1000
CONFIG_MBEDTLS_PSK_MODES=y
CONFIG_MBEDTLS_KEY_EXCHANGE_PSK=y
CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_SIZE=y
CONFIG_COMPILER_OPTIMIZATION_SIZE=y
CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y
CONFIG_ESPTOOLPY_HEADER_FLASHSIZE_UPDATE=y
#
# BT config
CONFIG_BT_ENABLED=y
CONFIG_BTDM_CTRL_MODE_BLE_ONLY=y
CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=n
CONFIG_BTDM_CTRL_MODE_BTDM=n
CONFIG_BT_BLUEDROID_ENABLED=n
CONFIG_BT_NIMBLE_ENABLED=y
#
# Arduino Configuration
CONFIG_AUTOSTART_ARDUINO=y
CONFIG_ARDUINO_SELECTIVE_COMPILATION=y
CONFIG_ARDUINO_SELECTIVE_Zigbee=n
CONFIG_ARDUINO_SELECTIVE_Matter=n
CONFIG_ARDUINO_SELECTIVE_WiFiProv=n
CONFIG_ARDUINO_SELECTIVE_BLE=n
CONFIG_ARDUINO_SELECTIVE_BluetoothSerial=n
CONFIG_ARDUINO_SELECTIVE_SimpleBLE=n
CONFIG_ARDUINO_SELECTIVE_RainMaker=n
CONFIG_ARDUINO_SELECTIVE_OpenThread=n
CONFIG_ARDUINO_SELECTIVE_Insights=n

View File

@@ -3,8 +3,8 @@
class HaHelper : public HomeAssistantHelper {
public:
static const byte TEMP_SOURCE_HEATING = 0;
static const byte TEMP_SOURCE_INDOOR = 1;
static const uint8_t TEMP_SOURCE_HEATING = 0;
static const uint8_t TEMP_SOURCE_INDOOR = 1;
static const char AVAILABILITY_OT_CONN[];
static const char AVAILABILITY_SENSOR_CONN[];
@@ -181,7 +181,7 @@ public:
if (sSensor.type == Sensors::Type::BLUETOOTH) {
// available state topic
doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_TOPIC)] = doc[FPSTR(HA_STATE_TOPIC)];
doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_VALUE_TEMPLATE)] = AVAILABILITY_SENSOR_CONN;
doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_VALUE_TEMPLATE)] = JsonString(AVAILABILITY_SENSOR_CONN, true);
String sName = sSensor.name;
switch (vType) {
@@ -254,7 +254,7 @@ public:
} else {
// available state topic
doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_TOPIC)] = doc[FPSTR(HA_STATE_TOPIC)];
doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_VALUE_TEMPLATE)] = AVAILABILITY_SENSOR_CONN;
doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_VALUE_TEMPLATE)] = JsonString(AVAILABILITY_SENSOR_CONN, true);
doc[FPSTR(HA_NAME)] = sSensor.name;
doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.value|float(0)|round(2) }}");
@@ -395,7 +395,7 @@ public:
doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->statusTopic.c_str();
doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault;
doc[FPSTR(HA_ENTITY_CATEGORY)] = FPSTR(HA_ENTITY_CATEGORY_DIAGNOSTIC);
doc[FPSTR(HA_DEVICE_CLASS)] = F("signal_strength");
//doc[FPSTR(HA_DEVICE_CLASS)] = F("signal_strength");
doc[FPSTR(HA_STATE_CLASS)] = FPSTR(HA_STATE_CLASS_MEASUREMENT);
doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR(HA_UNIT_OF_MEASUREMENT_PERCENT);
doc[FPSTR(HA_ICON)] = F("mdi:signal");
@@ -970,7 +970,7 @@ public:
JsonDocument doc;
doc[FPSTR(HA_AVAILABILITY)][0][FPSTR(HA_TOPIC)] = this->statusTopic.c_str();
doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_TOPIC)] = this->stateTopic.c_str();
doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_VALUE_TEMPLATE)] = AVAILABILITY_OT_CONN;
doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_VALUE_TEMPLATE)] = JsonString(AVAILABILITY_OT_CONN, true);
doc[FPSTR(HA_AVAILABILITY_MODE)] = F("all");
doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault;
doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectIdWithPrefix(F("heating"));
@@ -991,7 +991,7 @@ public:
JsonDocument doc;
doc[FPSTR(HA_AVAILABILITY)][0][FPSTR(HA_TOPIC)] = this->statusTopic.c_str();
doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_TOPIC)] = this->stateTopic.c_str();
doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_VALUE_TEMPLATE)] = AVAILABILITY_OT_CONN;
doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_VALUE_TEMPLATE)] = JsonString(AVAILABILITY_OT_CONN, true);
doc[FPSTR(HA_AVAILABILITY_MODE)] = F("all");
doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault;
doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectIdWithPrefix(F("dhw"));
@@ -1012,7 +1012,7 @@ public:
JsonDocument doc;
doc[FPSTR(HA_AVAILABILITY)][0][FPSTR(HA_TOPIC)] = this->statusTopic.c_str();
doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_TOPIC)] = this->stateTopic.c_str();
doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_VALUE_TEMPLATE)] = AVAILABILITY_OT_CONN;
doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_VALUE_TEMPLATE)] = JsonString(AVAILABILITY_OT_CONN, true);
doc[FPSTR(HA_AVAILABILITY_MODE)] = F("all");
doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault;
doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectIdWithPrefix(F("flame"));
@@ -1033,7 +1033,7 @@ public:
JsonDocument doc;
doc[FPSTR(HA_AVAILABILITY)][0][FPSTR(HA_TOPIC)] = this->statusTopic.c_str();
doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_TOPIC)] = this->stateTopic.c_str();
doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_VALUE_TEMPLATE)] = AVAILABILITY_OT_CONN;
doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_VALUE_TEMPLATE)] = JsonString(AVAILABILITY_OT_CONN, true);
doc[FPSTR(HA_AVAILABILITY_MODE)] = F("all");
doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault;
doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectIdWithPrefix(F("fault"));
@@ -1054,7 +1054,7 @@ public:
JsonDocument doc;
doc[FPSTR(HA_AVAILABILITY)][0][FPSTR(HA_TOPIC)] = this->statusTopic.c_str();
doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_TOPIC)] = this->stateTopic.c_str();
doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_VALUE_TEMPLATE)] = AVAILABILITY_OT_CONN;
doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_VALUE_TEMPLATE)] = JsonString(AVAILABILITY_OT_CONN, true);
doc[FPSTR(HA_AVAILABILITY_MODE)] = F("all");
doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault;
doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectIdWithPrefix(FPSTR(HA_ENTITY_CATEGORY_DIAGNOSTIC));
@@ -1170,7 +1170,7 @@ public:
}
bool publishClimateHeating(UnitSystem unit = UnitSystem::METRIC, byte minTemp = 20, byte maxTemp = 90, bool enabledByDefault = true) {
bool publishClimateHeating(UnitSystem unit = UnitSystem::METRIC, uint8_t minTemp = 20, uint8_t maxTemp = 90, bool enabledByDefault = true) {
JsonDocument doc;
doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->statusTopic.c_str();
doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault;
@@ -1215,14 +1215,14 @@ public:
doc[FPSTR(HA_MIN_TEMP)] = minTemp;
doc[FPSTR(HA_MAX_TEMP)] = maxTemp;
doc[FPSTR(HA_TEMP_STEP)] = 0.5f;
doc[FPSTR(HA_TEMP_STEP)] = 0.1f;
doc[FPSTR(HA_EXPIRE_AFTER)] = this->expireAfter;
doc.shrinkToFit();
return this->publish(this->makeConfigTopic(FPSTR(HA_ENTITY_CLIMATE), F("heating"), '_').c_str(), doc);
}
bool publishClimateDhw(UnitSystem unit = UnitSystem::METRIC, byte minTemp = 40, byte maxTemp = 60, bool enabledByDefault = true) {
bool publishClimateDhw(UnitSystem unit = UnitSystem::METRIC, uint8_t minTemp = 40, uint8_t maxTemp = 60, bool enabledByDefault = true) {
JsonDocument doc;
doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->statusTopic.c_str();
doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault;

View File

@@ -29,6 +29,7 @@ protected:
enum class PumpStartReason {NONE, HEATING, ANTISTUCK};
Blinker* blinker = nullptr;
unsigned long miscRunned = 0;
unsigned long lastHeapInfo = 0;
unsigned int minFreeHeap = 0;
unsigned int minMaxFreeBlockHeap = 0;
@@ -38,9 +39,12 @@ protected:
unsigned long heatingDisabledTime = 0;
PumpStartReason extPumpStartReason = PumpStartReason::NONE;
unsigned long externalPumpStartTime = 0;
bool ntpStarted = false;
bool telnetStarted = false;
bool emergencyDetected = false;
unsigned long emergencyFlipTime = 0;
bool freezeDetected = false;
unsigned long freezeDetectedTime = 0;
#if defined(ARDUINO_ARCH_ESP32)
const char* getTaskName() override {
@@ -109,6 +113,16 @@ protected:
}
if (network->isConnected()) {
if (!this->ntpStarted) {
if (strlen(settings.system.ntp.server)) {
configTime(0, 0, settings.system.ntp.server);
setenv("TZ", settings.system.ntp.timezone, 1);
tzset();
this->ntpStarted = true;
}
}
if (!this->telnetStarted && telnetStream != nullptr) {
telnetStream->begin(23, false);
this->telnetStarted = true;
@@ -124,6 +138,10 @@ protected:
Sensors::setConnectionStatusByType(Sensors::Type::MANUAL, !settings.mqtt.enabled || vars.mqtt.connected, false);
} else {
if (this->ntpStarted) {
this->ntpStarted = false;
}
if (this->telnetStarted) {
telnetStream->stop();
this->telnetStarted = false;
@@ -135,17 +153,16 @@ protected:
Sensors::setConnectionStatusByType(Sensors::Type::MANUAL, false, false);
}
this->yield();
this->emergency();
this->yield();
if (this->misc()) {
this->yield();
}
this->ledStatus();
this->cascadeControl();
this->externalPump();
this->yield();
// telnet
if (this->telnetStarted) {
this->yield();
telnetStream->loop();
this->yield();
}
@@ -164,14 +181,27 @@ protected:
// heap info
this->heap();
}
bool misc() {
if (millis() - this->miscRunned < 1000) {
return false;
}
// restart
// restart if required
if (this->restartSignalReceived && millis() - this->restartSignalReceivedTime > 15000) {
this->restartSignalReceived = false;
ESP.restart();
}
this->heating();
this->emergency();
this->cascadeControl();
this->externalPump();
this->miscRunned = millis();
return true;
}
void heap() {
@@ -181,6 +211,7 @@ protected:
// critical heap
if (!vars.states.restarting && (freeHeap < 2048 || maxFreeBlockHeap < 2048)) {
this->restartSignalReceivedTime = millis();
this->restartSignalReceived = true;
vars.states.restarting = true;
}
@@ -212,6 +243,65 @@ protected:
}
}
void heating() {
// freeze protection
if (!settings.heating.enabled) {
float lowTemp = 255.0f;
uint8_t availableSensors = 0;
if (Sensors::existsConnectedSensorsByPurpose(Sensors::Purpose::INDOOR_TEMP)) {
auto value = Sensors::getMeanValueByPurpose(Sensors::Purpose::INDOOR_TEMP, Sensors::ValueType::PRIMARY);
if (value < lowTemp) {
lowTemp = value;
}
availableSensors++;
}
if (Sensors::existsConnectedSensorsByPurpose(Sensors::Purpose::HEATING_TEMP)) {
auto value = Sensors::getMeanValueByPurpose(Sensors::Purpose::HEATING_TEMP, Sensors::ValueType::PRIMARY);
if (value < lowTemp) {
lowTemp = value;
}
availableSensors++;
}
if (Sensors::existsConnectedSensorsByPurpose(Sensors::Purpose::HEATING_RETURN_TEMP)) {
auto value = Sensors::getMeanValueByPurpose(Sensors::Purpose::HEATING_RETURN_TEMP, Sensors::ValueType::PRIMARY);
if (value < lowTemp) {
lowTemp = value;
}
availableSensors++;
}
if (availableSensors && lowTemp <= settings.heating.freezeProtection.lowTemp) {
if (!this->freezeDetected) {
this->freezeDetected = true;
this->freezeDetectedTime = millis();
} else if (millis() - this->freezeDetectedTime > (settings.heating.freezeProtection.thresholdTime * 1000)) {
this->freezeDetected = false;
settings.heating.enabled = true;
fsSettings.update();
Log.sinfoln(
FPSTR(L_MAIN),
F("Heating turned on by freeze protection, current low temp: %.2f, threshold: %hhu"),
lowTemp, settings.heating.freezeProtection.lowTemp
);
}
} else if (this->freezeDetected) {
this->freezeDetected = false;
}
} else if (this->freezeDetected) {
this->freezeDetected = false;
}
}
void emergency() {
// flags
uint8_t emergencyFlags = 0b00000000;
@@ -229,7 +319,7 @@ protected:
emergencyFlags |= 0b00000010;
}
if (settings.opentherm.nativeHeatingControl) {
if (settings.opentherm.options.nativeHeatingControl) {
emergencyFlags |= 0b00000100;
}
}

View File

@@ -70,8 +70,8 @@ public:
this->prevPubVarsTime = 0;
}
inline void rebuildHaEntity(uint8_t sensorId, Sensors::Settings& prevSettings) {
this->queueRebuildingHaEntities[sensorId] = prevSettings;
inline void reconfigureSensor(uint8_t sensorId, Sensors::Settings& prevSettings) {
this->queueReconfigureSensors[sensorId] = prevSettings;
}
protected:
@@ -81,7 +81,7 @@ protected:
MqttWriter* writer = nullptr;
UnitSystem currentUnitSystem = UnitSystem::METRIC;
bool currentHomeAssistantDiscovery = false;
std::unordered_map<uint8_t, Sensors::Settings> queueRebuildingHaEntities;
std::unordered_map<uint8_t, Sensors::Settings> queueReconfigureSensors;
unsigned short readyForSendTime = 30000;
unsigned long lastReconnectTime = 0;
unsigned long connectedTime = 0;
@@ -120,7 +120,6 @@ protected:
#endif
// client settings
this->client->setKeepAliveInterval(15000);
this->client->setTxPayloadSize(256);
#ifdef ARDUINO_ARCH_ESP8266
this->client->setConnectionTimeout(1000);
@@ -197,14 +196,14 @@ protected:
this->haHelper->setDevicePrefix(settings.mqtt.prefix);
this->haHelper->updateCachedTopics();
this->client->stop();
this->client->setKeepAliveInterval(settings.mqtt.interval * 10000);
this->client->setId(networkSettings.hostname);
this->client->setUsernamePassword(settings.mqtt.user, settings.mqtt.password);
this->client->beginWill(this->haHelper->getDeviceTopic(F("status")).c_str(), 7, true, 1);
this->client->print(F("offline"));
this->client->endWill();
this->client->connect(settings.mqtt.server, settings.mqtt.port);
this->lastReconnectTime = millis();
this->yield();
@@ -277,8 +276,8 @@ protected:
this->publishNonStaticHaEntities();
}
for (auto& [sensorId, prevSettings] : this->queueRebuildingHaEntities) {
// rebuilding ha configs
for (auto& [sensorId, prevSettings] : this->queueReconfigureSensors) {
Log.sinfoln(FPSTR(L_MQTT_HA), F("Rebuilding config for sensor #%hhu '%s'"), sensorId, prevSettings.name);
// delete old config
@@ -298,15 +297,6 @@ protected:
this->haHelper->deleteSignalQualityDynamicSensor(prevSettings);
this->haHelper->deleteDynamicSensor(prevSettings, Sensors::ValueType::TEMPERATURE);
break;
case Sensors::Type::MANUAL:
this->client->unsubscribe(
this->haHelper->getDeviceTopic(
F("sensors"),
Sensors::makeObjectId(prevSettings.name).c_str(),
F("set")
).c_str()
);
default:
this->haHelper->deleteDynamicSensor(prevSettings, Sensors::ValueType::PRIMARY);
@@ -334,26 +324,51 @@ protected:
this->haHelper->publishSignalQualityDynamicSensor(sSettings, false);
this->haHelper->publishDynamicSensor(sSettings, Sensors::ValueType::TEMPERATURE, settings.system.unitSystem);
break;
case Sensors::Type::MANUAL:
this->client->subscribe(
this->haHelper->getDeviceTopic(
F("sensors"),
Sensors::makeObjectId(prevSettings.name).c_str(),
F("set")
).c_str()
);
default:
this->haHelper->publishDynamicSensor(sSettings, Sensors::ValueType::PRIMARY, settings.system.unitSystem);
}
}
this->queueRebuildingHaEntities.clear();
} else if (this->currentHomeAssistantDiscovery) {
this->currentHomeAssistantDiscovery = false;
}
// reconfigure manual sensors
for (auto& [sensorId, prevSettings] : this->queueReconfigureSensors) {
// unsubscribe from old topic
if (strlen(prevSettings.name) && prevSettings.enabled) {
if (prevSettings.type == Sensors::Type::MANUAL) {
this->client->unsubscribe(
this->haHelper->getDeviceTopic(
F("sensors"),
Sensors::makeObjectId(prevSettings.name).c_str(),
F("set")
).c_str()
);
}
}
if (!Sensors::hasEnabledAndValid(sensorId)) {
continue;
}
// subscribe to new topic
auto& sSettings = Sensors::settings[sensorId];
if (sSettings.type == Sensors::Type::MANUAL) {
this->client->subscribe(
this->haHelper->getDeviceTopic(
F("sensors"),
Sensors::makeObjectId(sSettings.name).c_str(),
F("set")
).c_str()
);
}
}
// clear queue
this->queueReconfigureSensors.clear();
if (this->newConnection) {
this->newConnection = false;
}
@@ -367,6 +382,26 @@ protected:
this->client->subscribe(this->haHelper->getDeviceTopic(F("settings/set")).c_str());
this->client->subscribe(this->haHelper->getDeviceTopic(F("state/set")).c_str());
// subscribe to manual sensors
for (uint8_t sensorId = 0; sensorId <= Sensors::getMaxSensorId(); sensorId++) {
if (!Sensors::hasEnabledAndValid(sensorId)) {
continue;
}
auto& sSettings = Sensors::settings[sensorId];
if (sSettings.type != Sensors::Type::MANUAL) {
continue;
}
this->client->subscribe(
this->haHelper->getDeviceTopic(
F("sensors"),
Sensors::makeObjectId(sSettings.name).c_str(),
F("set")
).c_str()
);
}
}
void onDisconnect() {
@@ -514,15 +549,6 @@ protected:
this->haHelper->publishSignalQualityDynamicSensor(sSettings, false);
this->haHelper->publishDynamicSensor(sSettings, Sensors::ValueType::TEMPERATURE, settings.system.unitSystem);
break;
case Sensors::Type::MANUAL:
this->client->subscribe(
this->haHelper->getDeviceTopic(
F("sensors"),
Sensors::makeObjectId(sSettings.name).c_str(),
F("set")
).c_str()
);
default:
this->haHelper->publishDynamicSensor(sSettings, Sensors::ValueType::PRIMARY, settings.system.unitSystem);
@@ -531,14 +557,14 @@ protected:
}
bool publishNonStaticHaEntities(bool force = false) {
static byte _heatingMinTemp, _heatingMaxTemp, _dhwMinTemp, _dhwMaxTemp = 0;
static bool _indoorTempControl, _dhwPresent = false;
static uint8_t _heatingMinTemp, _heatingMaxTemp, _dhwMinTemp, _dhwMaxTemp = 0;
static bool _indoorTempControl, _dhwSupport = false;
bool published = false;
if (force || _dhwPresent != settings.opentherm.dhwPresent) {
_dhwPresent = settings.opentherm.dhwPresent;
if (force || _dhwSupport != settings.opentherm.options.dhwSupport) {
_dhwSupport = settings.opentherm.options.dhwSupport;
if (_dhwPresent) {
if (_dhwSupport) {
this->haHelper->publishInputDhwMinTemp(settings.system.unitSystem);
this->haHelper->publishInputDhwMaxTemp(settings.system.unitSystem);
this->haHelper->publishDhwState();
@@ -569,7 +595,7 @@ protected:
published = true;
}
if (_dhwPresent && (force || _dhwMinTemp != settings.dhw.minTemp || _dhwMaxTemp != settings.dhw.maxTemp)) {
if (_dhwSupport && (force || _dhwMinTemp != settings.dhw.minTemp || _dhwMaxTemp != settings.dhw.maxTemp)) {
_dhwMinTemp = settings.dhw.minTemp;
_dhwMaxTemp = settings.dhw.maxTemp;

File diff suppressed because it is too large Load Diff

View File

@@ -1,10 +1,12 @@
//#define PORTAL_CACHE "max-age=86400"
#define PORTAL_CACHE nullptr
#ifdef ARDUINO_ARCH_ESP8266
#include <ESP8266mDNS.h>
#include <ESP8266WebServer.h>
#include <Updater.h>
using WebServer = ESP8266WebServer;
#else
#include <ESPmDNS.h>
#include <WebServer.h>
#include <Update.h>
#endif
@@ -53,7 +55,7 @@ protected:
bool webServerEnabled = false;
bool dnsServerEnabled = false;
unsigned long webServerChangeState = 0;
unsigned long dnsServerChangeState = 0;
bool mDnsState = false;
#if defined(ARDUINO_ARCH_ESP32)
const char* getTaskName() override {
@@ -616,7 +618,7 @@ protected:
}
if (changed) {
tMqtt->rebuildHaEntity(sensorId, prevSettings);
tMqtt->reconfigureSensor(sensorId, prevSettings);
fsSensorsSettings.update();
}
});
@@ -858,6 +860,7 @@ protected:
}
});
this->webServer->serveStatic("/robots.txt", LittleFS, "/static/robots.txt", PORTAL_CACHE);
this->webServer->serveStatic("/favicon.ico", LittleFS, "/static/images/favicon.ico", PORTAL_CACHE);
this->webServer->serveStatic("/static", LittleFS, "/static", PORTAL_CACHE);
}
@@ -872,6 +875,16 @@ protected:
this->startWebServer();
Log.straceln(FPSTR(L_PORTAL_WEBSERVER), F("Started: AP up or STA connected"));
// Enabling mDNS
if (!this->mDnsState && settings.portal.mdns) {
if (MDNS.begin(networkSettings.hostname)) {
MDNS.addService("http", "tcp", 80);
this->mDnsState = true;
Log.straceln(FPSTR(L_PORTAL_WEBSERVER), F("mDNS enabled and service added"));
}
}
#ifdef ARDUINO_ARCH_ESP8266
::optimistic_yield(1000);
#endif
@@ -880,13 +893,29 @@ protected:
this->stopWebServer();
Log.straceln(FPSTR(L_PORTAL_WEBSERVER), F("Stopped: AP and STA down"));
// Disabling mDNS
if (this->mDnsState) {
MDNS.end();
this->mDnsState = false;
Log.straceln(FPSTR(L_PORTAL_WEBSERVER), F("mDNS disabled"));
}
#ifdef ARDUINO_ARCH_ESP8266
::optimistic_yield(1000);
#endif
}
// Disabling mDNS if disabled in settings
if (this->mDnsState && !settings.portal.mdns) {
MDNS.end();
this->mDnsState = false;
Log.straceln(FPSTR(L_PORTAL_WEBSERVER), F("mDNS disabled"));
}
// dns server
if (!this->stateDnsServer() && this->stateWebServer() && network->isApEnabled() && network->hasApClients() && millis() - this->dnsServerChangeState >= this->changeStateInterval) {
if (!this->stateDnsServer() && !network->isConnected() && network->isApEnabled() && this->stateWebServer()) {
this->startDnsServer();
Log.straceln(FPSTR(L_PORTAL_DNSSERVER), F("Started: AP up"));
@@ -894,9 +923,9 @@ protected:
::optimistic_yield(1000);
#endif
} else if (this->stateDnsServer() && (!network->isApEnabled() || !this->stateWebServer())) {
} else if (this->stateDnsServer() && (network->isConnected() || !network->isApEnabled() || !this->stateWebServer())) {
this->stopDnsServer();
Log.straceln(FPSTR(L_PORTAL_DNSSERVER), F("Stopped: AP down"));
Log.straceln(FPSTR(L_PORTAL_DNSSERVER), F("Stopped: AP down/STA connected"));
#ifdef ARDUINO_ARCH_ESP8266
::optimistic_yield(1000);
@@ -1006,7 +1035,6 @@ protected:
this->dnsServer->start(53, "*", network->getApIp());
this->dnsServerEnabled = true;
this->dnsServerChangeState = millis();
}
void stopDnsServer() {
@@ -1017,6 +1045,5 @@ protected:
//this->dnsServer->processNextRequest();
this->dnsServer->stop();
this->dnsServerEnabled = false;
this->dnsServerChangeState = millis();
}
};

View File

@@ -39,7 +39,7 @@ protected:
this->indoorSensorsConnected = Sensors::existsConnectedSensorsByPurpose(Sensors::Purpose::INDOOR_TEMP);
//this->outdoorSensorsConnected = Sensors::existsConnectedSensorsByPurpose(Sensors::Purpose::OUTDOOR_TEMP);
if (settings.equitherm.enabled || settings.pid.enabled || settings.opentherm.nativeHeatingControl) {
if (settings.equitherm.enabled || settings.pid.enabled || settings.opentherm.options.nativeHeatingControl) {
vars.master.heating.indoorTempControl = true;
vars.master.heating.minTemp = THERMOSTAT_INDOOR_MIN_TEMP;
vars.master.heating.maxTemp = THERMOSTAT_INDOOR_MAX_TEMP;
@@ -60,11 +60,11 @@ protected:
this->hysteresis();
vars.master.heating.targetTemp = settings.heating.target;
vars.master.heating.setpointTemp = constrain(
vars.master.heating.setpointTemp = roundf(constrain(
this->getHeatingSetpointTemp(),
this->getHeatingMinSetpointTemp(),
this->getHeatingMaxSetpointTemp()
);
), 0);
Sensors::setValueByType(
Sensors::Type::HEATING_SETPOINT_TEMP, vars.master.heating.setpointTemp,
@@ -93,7 +93,7 @@ protected:
void hysteresis() {
bool useHyst = false;
if (settings.heating.hysteresis > 0.01f && this->indoorSensorsConnected) {
useHyst = settings.equitherm.enabled || settings.pid.enabled || settings.opentherm.nativeHeatingControl;
useHyst = settings.equitherm.enabled || settings.pid.enabled || settings.opentherm.options.nativeHeatingControl;
}
if (useHyst) {
@@ -110,13 +110,13 @@ protected:
}
inline float getHeatingMinSetpointTemp() {
return settings.opentherm.nativeHeatingControl
return settings.opentherm.options.nativeHeatingControl
? vars.master.heating.minTemp
: settings.heating.minTemp;
}
inline float getHeatingMaxSetpointTemp() {
return settings.opentherm.nativeHeatingControl
return settings.opentherm.options.nativeHeatingControl
? vars.master.heating.maxTemp
: settings.heating.maxTemp;
}
@@ -137,7 +137,7 @@ protected:
if (vars.emergency.state) {
return settings.emergency.target;
} else if (settings.opentherm.nativeHeatingControl) {
} else if (settings.opentherm.options.nativeHeatingControl) {
return settings.heating.target;
} else if (!settings.equitherm.enabled && !settings.pid.enabled) {
@@ -196,6 +196,7 @@ protected:
//if (vars.parameters.heatingEnabled) {
if (settings.heating.enabled && this->indoorSensorsConnected) {
pidRegulator.Kp = settings.heating.turbo ? 0.0f : settings.pid.p_factor;
pidRegulator.Ki = settings.pid.i_factor;
pidRegulator.Kd = settings.pid.d_factor;
pidRegulator.setLimits(settings.pid.minTemp, settings.pid.maxTemp);
@@ -203,12 +204,23 @@ protected:
pidRegulator.input = vars.master.heating.indoorTemp;
pidRegulator.setpoint = settings.heating.target;
if (fabsf(pidRegulator.Ki - settings.pid.i_factor) >= 0.0001f) {
/*if (fabsf(pidRegulator.Ki - settings.pid.i_factor) >= 0.0001f) {
pidRegulator.Ki = settings.pid.i_factor;
pidRegulator.integral = 0.0f;
pidRegulator.getResultNow();
Log.sinfoln(FPSTR(L_REGULATOR_PID), F("Integral sum has been reset"));
}*/
float error = pidRegulator.setpoint - pidRegulator.input;
bool hasDeadband = settings.pid.deadband.enabled
&& (error > -(settings.pid.deadband.thresholdHigh))
&& (error < settings.pid.deadband.thresholdLow);
if (hasDeadband) {
pidRegulator.Kp *= settings.pid.deadband.p_multiplier;
pidRegulator.Ki *= settings.pid.deadband.i_multiplier;
pidRegulator.Kd *= settings.pid.deadband.d_multiplier;
}
float pidResult = pidRegulator.getResultTimer();

View File

@@ -25,6 +25,15 @@ public:
OT_SOLAR_COLLECTOR_TEMP = 16,
OT_FAN_SPEED_SETPOINT = 17,
OT_FAN_SPEED_CURRENT = 18,
OT_BURNER_STARTS = 19,
OT_DHW_BURNER_STARTS = 20,
OT_HEATING_PUMP_STARTS = 21,
OT_DHW_PUMP_STARTS = 22,
OT_BURNER_HOURS = 23,
OT_DHW_BURNER_HOURS = 24,
OT_HEATING_PUMP_HOURS = 25,
OT_DHW_PUMP_HOURS = 26,
NTC_10K_TEMP = 50,
DALLAS_TEMP = 51,
@@ -46,6 +55,7 @@ public:
EXHAUST_TEMP = 7,
MODULATION_LEVEL = 8,
NUMBER = 247,
POWER_FACTOR = 248,
POWER = 249,
FAN_SPEED = 250,
@@ -128,7 +138,7 @@ public:
}
uint8_t amount = 0;
for (uint8_t id = 0; id < getMaxSensorId(); id++) {
for (uint8_t id = 0; id <= getMaxSensorId(); id++) {
if (settings[id].type == type && (!onlyEnabled || settings[id].enabled)) {
amount++;
}
@@ -139,10 +149,10 @@ public:
static int16_t getIdByName(const char* name) {
if (settings == nullptr) {
return 0;
return -1;
}
for (uint8_t id = 0; id < getMaxSensorId(); id++) {
for (uint8_t id = 0; id <= getMaxSensorId(); id++) {
if (strcmp(settings[id].name, name) == 0) {
return id;
}
@@ -153,11 +163,11 @@ public:
static int16_t getIdByObjectId(const char* objectId) {
if (settings == nullptr) {
return 0;
return -1;
}
String refObjectId;
for (uint8_t id = 0; id < getMaxSensorId(); id++) {
for (uint8_t id = 0; id <= getMaxSensorId(); id++) {
Sensors::makeObjectId(refObjectId, settings[id].name);
if (refObjectId.equals(objectId)) {
return id;
@@ -237,7 +247,7 @@ public:
uint8_t updated = 0;
// read sensors data for current instance
for (uint8_t sensorId = 0; sensorId < getMaxSensorId(); sensorId++) {
for (uint8_t sensorId = 0; sensorId <= getMaxSensorId(); sensorId++) {
auto& sSensor = settings[sensorId];
// only target & valid sensors
@@ -301,7 +311,7 @@ public:
uint8_t updated = 0;
// read sensors data for current instance
for (uint8_t sensorId = 0; sensorId < getMaxSensorId(); sensorId++) {
for (uint8_t sensorId = 0; sensorId <= getMaxSensorId(); sensorId++) {
auto& sSensor = settings[sensorId];
// only target & valid sensors
@@ -324,13 +334,13 @@ public:
uint8_t valueId = (uint8_t) valueType;
if (!isValidValueId(valueId)) {
return false;
return 0;
}
float value = 0.0f;
uint8_t amount = 0;
for (uint8_t id = 0; id < getMaxSensorId(); id++) {
for (uint8_t id = 0; id <= getMaxSensorId(); id++) {
auto& sSensor = settings[id];
auto& rSensor = results[id];
@@ -356,7 +366,7 @@ public:
return 0;
}
for (uint8_t id = 0; id < getMaxSensorId(); id++) {
for (uint8_t id = 0; id <= getMaxSensorId(); id++) {
if (settings[id].purpose == purpose && results[id].connected) {
return true;
}

View File

@@ -8,6 +8,140 @@
extern FileData fsSensorsSettings;
#if USE_BLE
class BluetoothScanCallbacks : public NimBLEScanCallbacks {
public:
void onDiscovered(const NimBLEAdvertisedDevice* device) override {
auto& deviceAddress = device->getAddress();
bool found = false;
uint8_t sensorId;
for (sensorId = 0; sensorId <= Sensors::getMaxSensorId(); sensorId++) {
auto& sSensor = Sensors::settings[sensorId];
if (!sSensor.enabled || sSensor.type != Sensors::Type::BLUETOOTH || sSensor.purpose == Sensors::Purpose::NOT_CONFIGURED) {
continue;
}
const auto sensorAddress = NimBLEAddress(sSensor.address, deviceAddress.getType());
if (sensorAddress.isNull() || sensorAddress != deviceAddress) {
continue;
}
found = true;
break;
}
if (!found) {
return;
}
auto& sSensor = Sensors::settings[sensorId];
auto& rSensor = Sensors::results[sensorId];
auto deviceName = device->getName();
auto deviceRssi = device->getRSSI();
Log.straceln(
FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': discovered device %s, name: %s, RSSI: %hhd"),
sensorId, sSensor.name,
deviceAddress.toString().c_str(), deviceName.c_str(), deviceRssi
);
if (!device->haveServiceData()) {
Log.straceln(
FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': not found service data"),
sensorId, sSensor.name
);
return;
}
auto serviceDataCount = device->getServiceDataCount();
Log.straceln(
FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': found %hhu service data"),
sensorId, sSensor.name, serviceDataCount
);
NimBLEUUID serviceUuid((uint16_t) 0x181A);
auto serviceData = device->getServiceData(serviceUuid);
if (!serviceData.size()) {
Log.straceln(
FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': NOT found %s env service data"),
sensorId, sSensor.name, serviceUuid.toString().c_str()
);
return;
}
Log.straceln(
FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': found %s env service data"),
sensorId, sSensor.name, serviceUuid.toString().c_str()
);
float temperature, humidity;
uint16_t batteryMv;
uint8_t batteryLevel;
if (serviceData.size() == 13) {
// atc1441 format
// Temperature (2 bytes, big-endian)
temperature = (
(static_cast<uint8_t>(serviceData[6]) << 8) | static_cast<uint8_t>(serviceData[7])
) * 0.1f;
// Humidity (1 byte)
humidity = static_cast<uint8_t>(serviceData[8]);
// Battery mV (2 bytes, big-endian)
batteryMv = (static_cast<uint8_t>(serviceData[10]) << 8) | static_cast<uint8_t>(serviceData[11]);
// Battery level (1 byte)
batteryLevel = static_cast<uint8_t>(serviceData[9]);
} else if (serviceData.size() == 15) {
// custom pvvx format
// Temperature (2 bytes, little-endian)
temperature = (
(static_cast<uint8_t>(serviceData[7]) << 8) | static_cast<uint8_t>(serviceData[6])
) * 0.01f;
// Humidity (2 bytes, little-endian)
humidity = (
(static_cast<uint8_t>(serviceData[9]) << 8) | static_cast<uint8_t>(serviceData[8])
) * 0.01f;
// Battery mV (2 bytes, little-endian)
batteryMv = (static_cast<uint8_t>(serviceData[11]) << 8) | static_cast<uint8_t>(serviceData[10]);
// Battery level (1 byte)
batteryLevel = static_cast<uint8_t>(serviceData[12]);
} else {
// unknown format
Log.straceln(
FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': unknown data format (size: %i)"),
sensorId, sSensor.name, serviceData.size()
);
return;
}
Log.straceln(
FPSTR(L_SENSORS_BLE),
F("Sensor #%hhu '%s', received temp: %.2f; humidity: %.2f, battery voltage: %hu, battery level: %hhu"),
sensorId, sSensor.name,
temperature, humidity, batteryMv, batteryLevel
);
// update data
Sensors::setValueById(sensorId, temperature, Sensors::ValueType::TEMPERATURE, true, true);
Sensors::setValueById(sensorId, humidity, Sensors::ValueType::HUMIDITY, true, true);
Sensors::setValueById(sensorId, batteryLevel, Sensors::ValueType::BATTERY, true, true);
// update rssi
Sensors::setValueById(sensorId, deviceRssi, Sensors::ValueType::RSSI, false, false);
}
};
#endif
class SensorsTask : public LeanTask {
public:
SensorsTask(bool _enabled = false, unsigned long _interval = 0) : LeanTask(_enabled, _interval) {
@@ -16,6 +150,10 @@ public:
this->dallasSearchTime.reserve(2);
this->dallasPolling.reserve(2);
this->dallasLastPollingTime.reserve(2);
#if USE_BLE
this->pBLEScanCallbacks = new BluetoothScanCallbacks();
#endif
}
~SensorsTask() {
@@ -24,19 +162,28 @@ public:
this->dallasSearchTime.clear();
this->dallasPolling.clear();
this->dallasLastPollingTime.clear();
#if USE_BLE
delete this->pBLEScanCallbacks;
#endif
}
protected:
const unsigned int disconnectedTimeout = 120000;
const unsigned short dallasSearchInterval = 60000;
const unsigned short dallasPollingInterval = 10000;
const unsigned short globalPollingInterval = 15000;
const unsigned int disconnectedTimeout = 180000u;
const unsigned short dallasSearchInterval = 60000u;
const unsigned short dallasPollingInterval = 10000u;
const unsigned short globalPollingInterval = 15000u;
std::unordered_map<uint8_t, OneWire> owInstances;
std::unordered_map<uint8_t, DallasTemperature> dallasInstances;
std::unordered_map<uint8_t, unsigned long> dallasSearchTime;
std::unordered_map<uint8_t, bool> dallasPolling;
std::unordered_map<uint8_t, unsigned long> dallasLastPollingTime;
#if USE_BLE
NimBLEScan* pBLEScan = nullptr;
BluetoothScanCallbacks* pBLEScanCallbacks = nullptr;
bool activeScanBle = false;
#endif
unsigned long globalLastPollingTime = 0;
#if defined(ARDUINO_ARCH_ESP32)
@@ -83,8 +230,10 @@ protected:
pollingNtcSensors();
this->yield();
pollingBleSensors();
#if USE_BLE
scanBleSensors();
this->yield();
#endif
this->globalLastPollingTime = millis();
}
@@ -175,16 +324,19 @@ protected:
continue;
}
if (millis() - this->dallasSearchTime[gpio] > this->dallasSearchInterval) {
this->dallasSearchTime[gpio] = millis();
instance.begin();
Log.straceln(
FPSTR(L_SENSORS_DALLAS),
F("GPIO %hhu, devices on bus: %hhu, DS18* devices: %hhu"),
gpio, instance.getDeviceCount(), instance.getDS18Count()
);
if (millis() - this->dallasSearchTime[gpio] < this->dallasSearchInterval) {
continue;
}
this->dallasSearchTime[gpio] = millis();
this->owInstances[gpio].reset();
instance.begin();
Log.straceln(
FPSTR(L_SENSORS_DALLAS),
F("GPIO %hhu, devices on bus: %hhu, DS18* devices: %hhu"),
gpio, instance.getDeviceCount(), instance.getDS18Count()
);
}
}
@@ -274,25 +426,14 @@ protected:
unsigned long ts = millis();
if (this->dallasPolling[gpio]) {
auto minPollingTime = instance.millisToWaitForConversion(12);
unsigned long minPollingTime = instance.millisToWaitForConversion(12) * 2;
unsigned long estimatePollingTime = ts - this->dallasLastPollingTime[gpio];
// check conversion time
// isConversionComplete does not work with chinese clones!
if (estimatePollingTime < minPollingTime) {
continue;
}
// check conversion
bool conversionComplete = instance.isConversionComplete();
if (!conversionComplete) {
if (estimatePollingTime > (minPollingTime * 2)) {
this->dallasPolling[gpio] = false;
Log.swarningln(FPSTR(L_SENSORS_DALLAS), F("GPIO %hhu, timeout receiving data"), gpio);
}
continue;
}
// read sensors data for current instance
for (uint8_t sensorId = 0; sensorId <= Sensors::getMaxSensorId(); sensorId++) {
@@ -345,28 +486,6 @@ protected:
continue;
}
// check sensors on bus
if (!instance.getDeviceCount()) {
for (uint8_t sensorId = 0; sensorId <= Sensors::getMaxSensorId(); sensorId++) {
auto& sSensor = Sensors::settings[sensorId];
// only target & valid sensors
if (!sSensor.enabled || sSensor.type != Sensors::Type::DALLAS_TEMP || sSensor.purpose == Sensors::Purpose::NOT_CONFIGURED) {
continue;
} else if (sSensor.gpio != gpio || isEmptyAddress(sSensor.address)) {
continue;
}
auto& rSensor = Sensors::results[sensorId];
if (rSensor.signalQuality > 0) {
rSensor.signalQuality--;
}
}
continue;
}
// start polling
instance.setResolution(12);
instance.requestTemperatures();
@@ -378,26 +497,6 @@ protected:
}
}
void pollingBleSensors() {
#if USE_BLE
if (!NimBLEDevice::getInitialized() && millis() > 5000) {
Log.sinfoln(FPSTR(L_SENSORS_BLE), F("Initialized"));
BLEDevice::init("");
NimBLEDevice::setPower(ESP_PWR_LVL_P9);
}
for (uint8_t sensorId = 0; sensorId <= Sensors::getMaxSensorId(); sensorId++) {
auto& sSensor = Sensors::settings[sensorId];
if (!sSensor.enabled || sSensor.type != Sensors::Type::BLUETOOTH || sSensor.purpose == Sensors::Purpose::NOT_CONFIGURED) {
continue;
}
connectToBleDevice(sensorId);
}
#endif
}
void pollingNtcSensors() {
for (uint8_t sensorId = 0; sensorId <= Sensors::getMaxSensorId(); sensorId++) {
auto& sSensor = Sensors::settings[sensorId];
@@ -425,7 +524,7 @@ protected:
continue;
}
const float sensorResistance = value > 0.001f
const float sensorResistance = value > 1
? DEFAULT_NTC_REF_RESISTANCE / (DEFAULT_NTC_VREF / (float) value - 1.0f)
: 0.0f;
const float rawTemp = 1.0f / (
@@ -443,426 +542,74 @@ protected:
}
}
bool connectToBleDevice(const uint8_t sensorId) {
#if USE_BLE
if (!NimBLEDevice::getInitialized()) {
return false;
}
auto& sSensor = Sensors::settings[sensorId];
auto& rSensor = Sensors::results[sensorId];
if (!sSensor.enabled || sSensor.type != Sensors::Type::BLUETOOTH || sSensor.purpose == Sensors::Purpose::NOT_CONFIGURED) {
return false;
}
uint8_t addr[6] = {
sSensor.address[0], sSensor.address[1], sSensor.address[2],
sSensor.address[3], sSensor.address[4], sSensor.address[5]
};
const NimBLEAddress address = NimBLEAddress(addr);
NimBLEClient* pClient = nullptr;
pClient = NimBLEDevice::getClientByPeerAddress(address);
if (pClient == nullptr) {
pClient = NimBLEDevice::getDisconnectedClient();
}
if (pClient == nullptr) {
if (NimBLEDevice::getClientListSize() >= NIMBLE_MAX_CONNECTIONS) {
return false;
}
pClient = NimBLEDevice::createClient();
pClient->setConnectTimeout(5);
}
if(pClient->isConnected()) {
if (!rSensor.connected) {
rSensor.connected = true;
}
return true;
}
if (!pClient->connect(address)) {
Log.swarningln(
FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': failed connecting to %s"),
sensorId, sSensor.name, address.toString().c_str()
);
NimBLEDevice::deleteClient(pClient);
return false;
}
Log.sinfoln(
FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': connected to %s"),
sensorId, sSensor.name, address.toString().c_str()
);
NimBLERemoteService* pService = nullptr;
NimBLERemoteCharacteristic* pChar = nullptr;
// ENV Service (0x181A)
NimBLEUUID serviceUuid((uint16_t) 0x181AU);
pService = pClient->getService(serviceUuid);
if (!pService) {
Log.straceln(
FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': failed to find env service (%s) on device %s"),
sensorId, sSensor.name, serviceUuid.toString().c_str(), address.toString().c_str()
);
} else {
Log.straceln(
FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': found env service (%s) on device %s"),
sensorId, sSensor.name, serviceUuid.toString().c_str(), address.toString().c_str()
);
// 0x2A6E - Notify temperature x0.01C (pvvx)
bool tempNotifyCreated = false;
if (!tempNotifyCreated) {
NimBLEUUID charUuid((uint16_t) 0x2A6E);
pChar = pService->getCharacteristic(charUuid);
if (pChar && pChar->canNotify()) {
Log.straceln(
FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': found temp char (%s) in env service on device %s"),
sensorId, sSensor.name, charUuid.toString().c_str(), address.toString().c_str()
);
tempNotifyCreated = pChar->subscribe(true, [sensorId](NimBLERemoteCharacteristic* pChar, uint8_t* pData, size_t length, bool isNotify) {
if (pChar == nullptr) {
return;
}
NimBLERemoteService* pService = pChar->getRemoteService();
if (pService == nullptr) {
return;
}
NimBLEClient* pClient = pService->getClient();
if (pClient == nullptr) {
return;
}
auto& sSensor = Sensors::settings[sensorId];
if (length != 2) {
Log.swarningln(
FPSTR(L_SENSORS_BLE),
F("Sensor #%hhu '%s': invalid notification data at temp char (%s) on device %s"),
sensorId,
sSensor.name,
pChar->getUUID().toString().c_str(),
pClient->getPeerAddress().toString().c_str()
);
return;
}
float rawTemp = (pChar->getValue<int16_t>() * 0.01f);
Log.straceln(
FPSTR(L_SENSORS_BLE),
F("Sensor #%hhu '%s': received temp: %.2f"),
sensorId, sSensor.name, rawTemp
);
// set temp
Sensors::setValueById(sensorId, rawTemp, Sensors::ValueType::TEMPERATURE, true, true);
// update rssi
Sensors::setValueById(sensorId, pClient->getRssi(), Sensors::ValueType::RSSI, false, false);
});
if (tempNotifyCreated) {
Log.straceln(
FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': subscribed to temp char (%s) in env service on device %s"),
sensorId, sSensor.name,
charUuid.toString().c_str(), address.toString().c_str()
);
#if USE_BLE
void scanBleSensors() {
if (!Sensors::getAmountByType(Sensors::Type::BLUETOOTH, true)) {
if (NimBLEDevice::isInitialized()) {
if (this->pBLEScan != nullptr) {
if (this->pBLEScan->isScanning()) {
this->pBLEScan->stop();
} else {
Log.swarningln(
FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': failed to subscribe to temp char (%s) in env service on device %s"),
sensorId, sSensor.name,
charUuid.toString().c_str(), address.toString().c_str()
);
this->pBLEScan = nullptr;
}
}
}
// 0x2A1F - Notify temperature x0.1C (atc1441/pvvx)
if (!tempNotifyCreated) {
NimBLEUUID charUuid((uint16_t) 0x2A1F);
pChar = pService->getCharacteristic(charUuid);
if (pChar && pChar->canNotify()) {
Log.straceln(
FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': found temp char (%s) in env service on device %s"),
sensorId, sSensor.name, charUuid.toString().c_str(), address.toString().c_str()
);
tempNotifyCreated = pChar->subscribe(true, [sensorId](NimBLERemoteCharacteristic* pChar, uint8_t* pData, size_t length, bool isNotify) {
if (pChar == nullptr) {
return;
}
NimBLERemoteService* pService = pChar->getRemoteService();
if (pService == nullptr) {
return;
}
NimBLEClient* pClient = pService->getClient();
if (pClient == nullptr) {
return;
}
auto& sSensor = Sensors::settings[sensorId];
if (length != 2) {
Log.swarningln(
FPSTR(L_SENSORS_BLE),
F("Sensor #%hhu '%s': invalid notification data at temp char (%s) on device %s"),
sensorId,
sSensor.name,
pChar->getUUID().toString().c_str(),
pClient->getPeerAddress().toString().c_str()
);
return;
}
float rawTemp = (pChar->getValue<int16_t>() * 0.1f);
Log.straceln(
FPSTR(L_SENSORS_BLE),
F("Sensor #%hhu '%s': received temp: %.2f"),
sensorId, sSensor.name, rawTemp
);
// set temp
Sensors::setValueById(sensorId, rawTemp, Sensors::ValueType::TEMPERATURE, true, true);
// update rssi
Sensors::setValueById(sensorId, pClient->getRssi(), Sensors::ValueType::RSSI, false, false);
});
if (tempNotifyCreated) {
Log.straceln(
FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': subscribed to temp char (%s) in env service on device %s"),
sensorId, sSensor.name,
charUuid.toString().c_str(), address.toString().c_str()
);
if (this->pBLEScan == nullptr) {
if (NimBLEDevice::deinit(true)) {
Log.sinfoln(FPSTR(L_SENSORS_BLE), F("Deinitialized"));
} else {
Log.swarningln(
FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': failed to subscribe to temp char (%s) in env service on device %s"),
sensorId, sSensor.name,
charUuid.toString().c_str(), address.toString().c_str()
);
Log.swarningln(FPSTR(L_SENSORS_BLE), F("Unable to deinitialize!"));
}
}
}
if (!tempNotifyCreated) {
Log.swarningln(
FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': not found supported temp chars in env service on device %s"),
sensorId, sSensor.name, address.toString().c_str()
);
pClient->disconnect();
return false;
}
// 0x2A6F - Notify about humidity x0.01% (pvvx)
{
bool humidityNotifyCreated = false;
if (!humidityNotifyCreated) {
NimBLEUUID charUuid((uint16_t) 0x2A6F);
pChar = pService->getCharacteristic(charUuid);
if (pChar && pChar->canNotify()) {
Log.straceln(
FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': found humidity char (%s) in env service on device %s"),
sensorId, sSensor.name, charUuid.toString().c_str(), address.toString().c_str()
);
humidityNotifyCreated = pChar->subscribe(true, [sensorId](NimBLERemoteCharacteristic* pChar, uint8_t* pData, size_t length, bool isNotify) {
if (pChar == nullptr) {
return;
}
NimBLERemoteService* pService = pChar->getRemoteService();
if (pService == nullptr) {
return;
}
NimBLEClient* pClient = pService->getClient();
if (pClient == nullptr) {
return;
}
auto& sSensor = Sensors::settings[sensorId];
if (length != 2) {
Log.swarningln(
FPSTR(L_SENSORS_BLE),
F("Sensor #%hhu '%s': invalid notification data at humidity char (%s) on device %s"),
sensorId,
sSensor.name,
pChar->getUUID().toString().c_str(),
pClient->getPeerAddress().toString().c_str()
);
return;
}
float rawHumidity = (pChar->getValue<uint16_t>() * 0.01f);
Log.straceln(
FPSTR(L_SENSORS_BLE),
F("Sensor #%hhu '%s': received humidity: %.2f"),
sensorId, sSensor.name, rawHumidity
);
// set humidity
Sensors::setValueById(sensorId, rawHumidity, Sensors::ValueType::HUMIDITY, true, true);
// update rssi
Sensors::setValueById(sensorId, pClient->getRssi(), Sensors::ValueType::RSSI, false, false);
});
if (humidityNotifyCreated) {
Log.straceln(
FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': subscribed to humidity char (%s) in env service on device %s"),
sensorId, sSensor.name,
charUuid.toString().c_str(), address.toString().c_str()
);
} else {
Log.swarningln(
FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': failed to subscribe to humidity char (%s) in env service on device %s"),
sensorId, sSensor.name,
charUuid.toString().c_str(), address.toString().c_str()
);
}
}
}
if (!humidityNotifyCreated) {
Log.swarningln(
FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': not found supported humidity chars in env service on device %s"),
sensorId, sSensor.name, address.toString().c_str()
);
}
}
return;
}
if (!NimBLEDevice::isInitialized() && millis() > 5000) {
Log.sinfoln(FPSTR(L_SENSORS_BLE), F("Initialized"));
NimBLEDevice::init("");
#if defined(ESP_PWR_LVL_P20)
NimBLEDevice::setPower(ESP_PWR_LVL_P20);
#elif defined(ESP_PWR_LVL_P9)
NimBLEDevice::setPower(ESP_PWR_LVL_P9);
#endif
}
// Battery Service (0x180F)
{
NimBLEUUID serviceUuid((uint16_t) 0x180F);
pService = pClient->getService(serviceUuid);
if (!pService) {
Log.straceln(
FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': failed to find battery service (%s) on device %s"),
sensorId, sSensor.name, serviceUuid.toString().c_str(), address.toString().c_str()
if (this->pBLEScan == nullptr) {
this->pBLEScan = NimBLEDevice::getScan();
this->pBLEScan->setScanCallbacks(this->pBLEScanCallbacks);
#if MYNEWT_VAL(BLE_EXT_ADV)
this->pBLEScan->setPhy(NimBLEScan::Phy::SCAN_ALL);
#endif
this->pBLEScan->setDuplicateFilter(false);
this->pBLEScan->setMaxResults(0);
this->pBLEScan->setInterval(10000);
this->pBLEScan->setWindow(10000);
Log.sinfoln(FPSTR(L_SENSORS_BLE), F("Scanning initialized"));
}
if (!this->pBLEScan->isScanning()) {
this->activeScanBle = !this->activeScanBle;
this->pBLEScan->setActiveScan(this->activeScanBle);
if (this->pBLEScan->start(30000, false, false)) {
Log.sinfoln(
FPSTR(L_SENSORS_BLE),
F("%s scanning started"),
this->activeScanBle ? "Active" : "Passive"
);
} else {
Log.straceln(
FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': found battery service (%s) on device %s"),
sensorId, sSensor.name, serviceUuid.toString().c_str(), address.toString().c_str()
);
// 0x2A19 - Notify the battery charge level 0..99% (pvvx)
bool batteryNotifyCreated = false;
if (!batteryNotifyCreated) {
NimBLEUUID charUuid((uint16_t) 0x2A19);
pChar = pService->getCharacteristic(charUuid);
if (pChar && pChar->canNotify()) {
Log.straceln(
FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': found battery char (%s) in battery service on device %s"),
sensorId, sSensor.name, charUuid.toString().c_str(), address.toString().c_str()
);
batteryNotifyCreated = pChar->subscribe(true, [sensorId](NimBLERemoteCharacteristic* pChar, uint8_t* pData, size_t length, bool isNotify) {
if (pChar == nullptr) {
return;
}
NimBLERemoteService* pService = pChar->getRemoteService();
if (pService == nullptr) {
return;
}
NimBLEClient* pClient = pService->getClient();
if (pClient == nullptr) {
return;
}
auto& sSensor = Sensors::settings[sensorId];
if (length != 1) {
Log.swarningln(
FPSTR(L_SENSORS_BLE),
F("Sensor #%hhu '%s': invalid notification data at battery char (%s) on device %s"),
sensorId,
sSensor.name,
pChar->getUUID().toString().c_str(),
pClient->getPeerAddress().toString().c_str()
);
return;
}
auto rawBattery = pChar->getValue<uint8_t>();
Log.straceln(
FPSTR(L_SENSORS_BLE),
F("Sensor #%hhu '%s': received battery: %.2f"),
sensorId, sSensor.name, rawBattery
);
// set battery
Sensors::setValueById(sensorId, rawBattery, Sensors::ValueType::BATTERY, true, true);
// update rssi
Sensors::setValueById(sensorId, pClient->getRssi(), Sensors::ValueType::RSSI, false, false);
});
if (batteryNotifyCreated) {
Log.straceln(
FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': subscribed to battery char (%s) in battery service on device %s"),
sensorId, sSensor.name,
charUuid.toString().c_str(), address.toString().c_str()
);
} else {
Log.swarningln(
FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': failed to subscribe to battery char (%s) in battery service on device %s"),
sensorId, sSensor.name,
charUuid.toString().c_str(), address.toString().c_str()
);
}
}
}
if (!batteryNotifyCreated) {
Log.swarningln(
FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': not found supported battery chars in battery service on device %s"),
sensorId, sSensor.name, address.toString().c_str()
);
}
Log.sinfoln(FPSTR(L_SENSORS_BLE), F("Unable to start scanning"));
}
}
return true;
#else
return false;
#endif
}
#endif
void updateConnectionStatus() {
for (uint8_t sensorId = 0; sensorId <= Sensors::getMaxSensorId(); sensorId++) {
@@ -870,16 +617,16 @@ protected:
auto& rSensor = Sensors::results[sensorId];
if (rSensor.connected && !sSensor.enabled) {
rSensor.connected = false;
Sensors::setConnectionStatusById(sensorId, false, false);
} else if (rSensor.connected && sSensor.type == Sensors::Type::NOT_CONFIGURED) {
rSensor.connected = false;
Sensors::setConnectionStatusById(sensorId, false, false);
} else if (rSensor.connected && sSensor.purpose == Sensors::Purpose::NOT_CONFIGURED) {
rSensor.connected = false;
Sensors::setConnectionStatusById(sensorId, false, false);
} else if (sSensor.type != Sensors::Type::MANUAL && rSensor.connected && (millis() - rSensor.activityTime) > this->disconnectedTimeout) {
rSensor.connected = false;
Sensors::setConnectionStatusById(sensorId, false, false);
}/* else if (!rSensor.connected) {
rSensor.connected = true;
@@ -888,7 +635,8 @@ protected:
}
static bool isEqualAddress(const uint8_t *addr1, const uint8_t *addr2, const uint8_t length = 8) {
bool result = true;
return memcmp(addr1, addr2, length) == 0;
/*bool result = true;
for (uint8_t i = 0; i < length; i++) {
if (addr1[i] != addr2[i]) {
@@ -897,7 +645,7 @@ protected:
}
}
return result;
return result;*/
}
static bool isEmptyAddress(const uint8_t *addr, const uint8_t length = 8) {

View File

@@ -12,13 +12,13 @@ struct NetworkSettings {
struct {
char ssid[33] = DEFAULT_AP_SSID;
char password[65] = DEFAULT_AP_PASSWORD;
byte channel = 6;
uint8_t channel = 6;
} ap;
struct {
char ssid[33] = DEFAULT_STA_SSID;
char password[65] = DEFAULT_STA_PASSWORD;
byte channel = 0;
uint8_t channel = 0;
} sta;
} networkSettings;
@@ -36,37 +36,51 @@ struct Settings {
unsigned short port = DEFAULT_TELNET_PORT;
} telnet;
struct {
char server[49] = "pool.ntp.org";
char timezone[49] = "UTC0";
} ntp;
UnitSystem unitSystem = UnitSystem::METRIC;
byte statusLedGpio = DEFAULT_STATUS_LED_GPIO;
uint8_t statusLedGpio = DEFAULT_STATUS_LED_GPIO;
} system;
struct {
bool auth = false;
char login[13] = DEFAULT_PORTAL_LOGIN;
char password[33] = DEFAULT_PORTAL_PASSWORD;
bool mdns = true;
} portal;
struct {
UnitSystem unitSystem = UnitSystem::METRIC;
byte inGpio = DEFAULT_OT_IN_GPIO;
byte outGpio = DEFAULT_OT_OUT_GPIO;
byte rxLedGpio = DEFAULT_OT_RX_LED_GPIO;
uint8_t inGpio = DEFAULT_OT_IN_GPIO;
uint8_t outGpio = DEFAULT_OT_OUT_GPIO;
uint8_t rxLedGpio = DEFAULT_OT_RX_LED_GPIO;
uint8_t memberId = 0;
uint8_t flags = 0;
uint8_t maxModulation = 100;
float minPower = 0.0f;
float maxPower = 0.0f;
bool dhwPresent = true;
bool summerWinterMode = false;
bool heatingCh2Enabled = true;
bool heatingCh1ToCh2 = false;
bool dhwToCh2 = false;
bool dhwBlocking = false;
bool modulationSyncWithHeating = false;
bool getMinMaxTemp = true;
bool nativeHeatingControl = false;
bool immergasFix = false;
struct {
bool dhwSupport = true;
bool coolingSupport = false;
bool summerWinterMode = false;
bool heatingStateToSummerWinterMode = false;
bool ch2AlwaysEnabled = true;
bool heatingToCh2 = false;
bool dhwToCh2 = false;
bool dhwBlocking = false;
bool dhwStateAsDhwBlocking = false;
bool maxTempSyncWithTargetTemp = true;
bool getMinMaxTemp = true;
bool ignoreDiagState = false;
bool autoFaultReset = false;
bool autoDiagReset = false;
bool setDateAndTime = false;
bool nativeHeatingControl = false;
bool immergasFix = false;
} options;
} opentherm;
struct {
@@ -91,25 +105,51 @@ struct Settings {
float target = DEFAULT_HEATING_TARGET_TEMP;
float hysteresis = 0.5f;
float turboFactor = 7.5f;
byte minTemp = DEFAULT_HEATING_MIN_TEMP;
byte maxTemp = DEFAULT_HEATING_MAX_TEMP;
uint8_t minTemp = DEFAULT_HEATING_MIN_TEMP;
uint8_t maxTemp = DEFAULT_HEATING_MAX_TEMP;
uint8_t maxModulation = 100;
struct {
uint8_t highTemp = 95;
uint8_t lowTemp = 90;
} overheatProtection;
struct {
uint8_t lowTemp = 10;
unsigned short thresholdTime = 600;
} freezeProtection;
} heating;
struct {
bool enabled = true;
float target = DEFAULT_DHW_TARGET_TEMP;
byte minTemp = DEFAULT_DHW_MIN_TEMP;
byte maxTemp = DEFAULT_DHW_MAX_TEMP;
uint8_t minTemp = DEFAULT_DHW_MIN_TEMP;
uint8_t maxTemp = DEFAULT_DHW_MAX_TEMP;
uint8_t maxModulation = 100;
struct {
uint8_t highTemp = 95;
uint8_t lowTemp = 90;
} overheatProtection;
} dhw;
struct {
bool enabled = false;
float p_factor = 2.0f;
float i_factor = 0.0055f;
float i_factor = 0.002f;
float d_factor = 0.0f;
unsigned short dt = 180;
unsigned short dt = 300;
short minTemp = 0;
short maxTemp = DEFAULT_HEATING_MAX_TEMP;
struct {
bool enabled = true;
float p_multiplier = 1.0f;
float i_multiplier = 0.05f;
float d_multiplier = 1.0f;
float thresholdHigh = 0.5f;
float thresholdLow = 1.0f;
} deadband;
} pid;
struct {
@@ -121,7 +161,7 @@ struct Settings {
struct {
bool use = false;
byte gpio = DEFAULT_EXT_PUMP_GPIO;
uint8_t gpio = DEFAULT_EXT_PUMP_GPIO;
unsigned short postCirculationTime = 600;
unsigned int antiStuckInterval = 2592000;
unsigned short antiStuckTime = 300;
@@ -130,15 +170,15 @@ struct Settings {
struct {
struct {
bool enabled = false;
byte gpio = GPIO_IS_NOT_CONFIGURED;
byte invertState = false;
uint8_t gpio = GPIO_IS_NOT_CONFIGURED;
bool invertState = false;
unsigned short thresholdTime = 60;
} input;
struct {
bool enabled = false;
byte gpio = GPIO_IS_NOT_CONFIGURED;
byte invertState = false;
uint8_t gpio = GPIO_IS_NOT_CONFIGURED;
bool invertState = false;
unsigned short thresholdTime = 60;
bool onFault = true;
bool onLossConnection = true;
@@ -255,6 +295,7 @@ struct Variables {
bool blocking = false;
bool enabled = false;
bool indoorTempControl = false;
bool overheat = false;
float setpointTemp = 0.0f;
float targetTemp = 0.0f;
float currentTemp = 0.0f;
@@ -267,6 +308,7 @@ struct Variables {
struct {
bool enabled = false;
bool overheat = false;
float targetTemp = 0.0f;
float currentTemp = 0.0f;
float returnTemp = 0.0f;
@@ -290,6 +332,11 @@ struct Variables {
float pressure = 0.0f;
float heatExchangerTemp = 0.0f;
struct {
bool active = false;
uint8_t setpoint = 0;
} cooling;
struct {
bool active = false;
uint8_t code = 0;
@@ -329,6 +376,17 @@ struct Variables {
uint16_t supply = 0;
} fanSpeed;
struct {
uint16_t burnerStarts = 0;
uint16_t dhwBurnerStarts = 0;
uint16_t heatingPumpStarts = 0;
uint16_t dhwPumpStarts = 0;
uint16_t burnerHours = 0;
uint16_t dhwBurnerHours = 0;
uint16_t heatingPumpHours = 0;
uint16_t dhwPumpHours = 0;
} stats;
struct {
bool active = false;
bool enabled = false;
@@ -354,6 +412,7 @@ struct Variables {
} dhw;
struct {
bool active = false;
bool enabled = false;
float targetTemp = 0.0f;
float currentTemp = 0.0f;

View File

@@ -16,7 +16,7 @@
#define THERMOSTAT_INDOOR_DEFAULT_TEMP 20
#define THERMOSTAT_INDOOR_MIN_TEMP 5
#define THERMOSTAT_INDOOR_MAX_TEMP 30
#define THERMOSTAT_INDOOR_MAX_TEMP 40
#define DEFAULT_NTC_NOMINAL_RESISTANCE 10000.0f
#define DEFAULT_NTC_NOMINAL_TEMP 25.0f
@@ -55,7 +55,7 @@
#endif
#ifndef DEFAULT_HOSTNAME
#define DEFAULT_HOSTNAME "opentherm"
#define DEFAULT_HOSTNAME ""
#endif
#ifndef DEFAULT_AP_SSID
@@ -111,7 +111,7 @@
#endif
#ifndef DEFAULT_MQTT_PREFIX
#define DEFAULT_MQTT_PREFIX "opentherm"
#define DEFAULT_MQTT_PREFIX ""
#endif
#ifndef DEFAULT_OT_IN_GPIO

3
src/idf_component.yml Normal file
View File

@@ -0,0 +1,3 @@
dependencies:
idf: ">=5.3.2"
h2zero/esp-nimble-cpp: ">=2.2.1"

View File

@@ -102,6 +102,12 @@ void setup() {
break;
}
// generate hostname if it is empty
if (!strlen(networkSettings.hostname)) {
strcpy(networkSettings.hostname, getChipId("otgateway-").c_str());
fsNetworkSettings.update();
}
network = (new NetworkMgr)
->setHostname(networkSettings.hostname)
->setStaCredentials(
@@ -148,6 +154,12 @@ void setup() {
break;
}
// generate mqtt prefix if it is empty
if (!strlen(settings.mqtt.prefix)) {
strcpy(settings.mqtt.prefix, getChipId("otgateway_").c_str());
fsSettings.update();
}
// Logs settings
if (!settings.system.serial.enabled) {
Serial.end();

View File

@@ -3,192 +3,216 @@
#define PROGMEM
#endif
const char L_SETTINGS[] PROGMEM = "SETTINGS";
const char L_SETTINGS_OT[] PROGMEM = "SETTINGS.OT";
const char L_SETTINGS_DHW[] PROGMEM = "SETTINGS.DHW";
const char L_SETTINGS_HEATING[] PROGMEM = "SETTINGS.HEATING";
const char L_NETWORK[] PROGMEM = "NETWORK";
const char L_NETWORK_SETTINGS[] PROGMEM = "NETWORK.SETTINGS";
const char L_PORTAL_WEBSERVER[] PROGMEM = "PORTAL.WEBSERVER";
const char L_PORTAL_DNSSERVER[] PROGMEM = "PORTAL.DNSSERVER";
const char L_PORTAL_CAPTIVE[] PROGMEM = "PORTAL.CAPTIVE";
const char L_PORTAL_OTA[] PROGMEM = "PORTAL.OTA";
const char L_MAIN[] PROGMEM = "MAIN";
const char L_MQTT[] PROGMEM = "MQTT";
const char L_MQTT_HA[] PROGMEM = "MQTT.HA";
const char L_MQTT_MSG[] PROGMEM = "MQTT.MSG";
const char L_OT[] PROGMEM = "OT";
const char L_OT_DHW[] PROGMEM = "OT.DHW";
const char L_OT_HEATING[] PROGMEM = "OT.HEATING";
const char L_OT_CH2[] PROGMEM = "OT.CH2";
const char L_SENSORS[] PROGMEM = "SENSORS";
const char L_SENSORS_SETTINGS[] PROGMEM = "SENSORS.SETTINGS";
const char L_SENSORS_DALLAS[] PROGMEM = "SENSORS.DALLAS";
const char L_SENSORS_NTC[] PROGMEM = "SENSORS.NTC";
const char L_SENSORS_BLE[] PROGMEM = "SENSORS.BLE";
const char L_REGULATOR[] PROGMEM = "REGULATOR";
const char L_REGULATOR_PID[] PROGMEM = "REGULATOR.PID";
const char L_REGULATOR_EQUITHERM[] PROGMEM = "REGULATOR.EQUITHERM";
const char L_CASCADE_INPUT[] PROGMEM = "CASCADE.INPUT";
const char L_CASCADE_OUTPUT[] PROGMEM = "CASCADE.OUTPUT";
const char L_EXTPUMP[] PROGMEM = "EXTPUMP";
const char L_SETTINGS[] PROGMEM = "SETTINGS";
const char L_SETTINGS_OT[] PROGMEM = "SETTINGS.OT";
const char L_SETTINGS_DHW[] PROGMEM = "SETTINGS.DHW";
const char L_SETTINGS_HEATING[] PROGMEM = "SETTINGS.HEATING";
const char L_NETWORK[] PROGMEM = "NETWORK";
const char L_NETWORK_SETTINGS[] PROGMEM = "NETWORK.SETTINGS";
const char L_PORTAL_WEBSERVER[] PROGMEM = "PORTAL.WEBSERVER";
const char L_PORTAL_DNSSERVER[] PROGMEM = "PORTAL.DNSSERVER";
const char L_PORTAL_CAPTIVE[] PROGMEM = "PORTAL.CAPTIVE";
const char L_PORTAL_OTA[] PROGMEM = "PORTAL.OTA";
const char L_MAIN[] PROGMEM = "MAIN";
const char L_MQTT[] PROGMEM = "MQTT";
const char L_MQTT_HA[] PROGMEM = "MQTT.HA";
const char L_MQTT_MSG[] PROGMEM = "MQTT.MSG";
const char L_OT[] PROGMEM = "OT";
const char L_OT_DHW[] PROGMEM = "OT.DHW";
const char L_OT_HEATING[] PROGMEM = "OT.HEATING";
const char L_OT_CH2[] PROGMEM = "OT.CH2";
const char L_SENSORS[] PROGMEM = "SENSORS";
const char L_SENSORS_SETTINGS[] PROGMEM = "SENSORS.SETTINGS";
const char L_SENSORS_DALLAS[] PROGMEM = "SENSORS.DALLAS";
const char L_SENSORS_NTC[] PROGMEM = "SENSORS.NTC";
const char L_SENSORS_BLE[] PROGMEM = "SENSORS.BLE";
const char L_REGULATOR[] PROGMEM = "REGULATOR";
const char L_REGULATOR_PID[] PROGMEM = "REGULATOR.PID";
const char L_REGULATOR_EQUITHERM[] PROGMEM = "REGULATOR.EQUITHERM";
const char L_CASCADE_INPUT[] PROGMEM = "CASCADE.INPUT";
const char L_CASCADE_OUTPUT[] PROGMEM = "CASCADE.OUTPUT";
const char L_EXTPUMP[] PROGMEM = "EXTPUMP";
const char S_ACTIONS[] PROGMEM = "actions";
const char S_ACTIVE[] PROGMEM = "active";
const char S_ADDRESS[] PROGMEM = "address";
const char S_ANTI_STUCK_INTERVAL[] PROGMEM = "antiStuckInterval";
const char S_ANTI_STUCK_TIME[] PROGMEM = "antiStuckTime";
const char S_AP[] PROGMEM = "ap";
const char S_APP_VERSION[] PROGMEM = "appVersion";
const char S_AUTH[] PROGMEM = "auth";
const char S_BACKTRACE[] PROGMEM = "backtrace";
const char S_BATTERY[] PROGMEM = "battery";
const char S_BAUDRATE[] PROGMEM = "baudrate";
const char S_BLOCKING[] PROGMEM = "blocking";
const char S_BSSID[] PROGMEM = "bssid";
const char S_BUILD[] PROGMEM = "build";
const char S_CASCADE_CONTROL[] PROGMEM = "cascadeControl";
const char S_CHANNEL[] PROGMEM = "channel";
const char S_CHIP[] PROGMEM = "chip";
const char S_CODE[] PROGMEM = "code";
const char S_CONNECTED[] PROGMEM = "connected";
const char S_CONTINUES[] PROGMEM = "continues";
const char S_CORE[] PROGMEM = "core";
const char S_CORES[] PROGMEM = "cores";
const char S_CRASH[] PROGMEM = "crash";
const char S_CURRENT_TEMP[] PROGMEM = "currentTemp";
const char S_DATA[] PROGMEM = "data";
const char S_DATE[] PROGMEM = "date";
const char S_DHW[] PROGMEM = "dhw";
const char S_DHW_BLOCKING[] PROGMEM = "dhwBlocking";
const char S_DHW_PRESENT[] PROGMEM = "dhwPresent";
const char S_DHW_TO_CH2[] PROGMEM = "dhwToCh2";
const char S_DIAG[] PROGMEM = "diag";
const char S_DNS[] PROGMEM = "dns";
const char S_DT[] PROGMEM = "dt";
const char S_D_FACTOR[] PROGMEM = "d_factor";
const char S_EMERGENCY[] PROGMEM = "emergency";
const char S_ENABLED[] PROGMEM = "enabled";
const char S_ENV[] PROGMEM = "env";
const char S_EPC[] PROGMEM = "epc";
const char S_EQUITHERM[] PROGMEM = "equitherm";
const char S_EXTERNAL_PUMP[] PROGMEM = "externalPump";
const char S_FACTOR[] PROGMEM = "factor";
const char S_FAULT[] PROGMEM = "fault";
const char S_FILTERING[] PROGMEM = "filtering";
const char S_FILTERING_FACTOR[] PROGMEM = "filteringFactor";
const char S_FLAGS[] PROGMEM = "flags";
const char S_FLAME[] PROGMEM = "flame";
const char S_FLASH[] PROGMEM = "flash";
const char S_FREE[] PROGMEM = "free";
const char S_FREQ[] PROGMEM = "freq";
const char S_GATEWAY[] PROGMEM = "gateway";
const char S_GET_MIN_MAX_TEMP[] PROGMEM = "getMinMaxTemp";
const char S_GPIO[] PROGMEM = "gpio";
const char S_HEAP[] PROGMEM = "heap";
const char S_HEATING[] PROGMEM = "heating";
const char S_HEATING_CH1_TO_CH2[] PROGMEM = "heatingCh1ToCh2";
const char S_HEATING_CH2_ENABLED[] PROGMEM = "heatingCh2Enabled";
const char S_HIDDEN[] PROGMEM = "hidden";
const char S_HOME_ASSISTANT_DISCOVERY[] PROGMEM = "homeAssistantDiscovery";
const char S_HOSTNAME[] PROGMEM = "hostname";
const char S_HUMIDITY[] PROGMEM = "humidity";
const char S_HYSTERESIS[] PROGMEM = "hysteresis";
const char S_ID[] PROGMEM = "id";
const char S_IMMERGAS_FIX[] PROGMEM = "immergasFix";
const char S_INDOOR_TEMP[] PROGMEM = "indoorTemp";
const char S_INDOOR_TEMP_CONTROL[] PROGMEM = "indoorTempControl";
const char S_IN_GPIO[] PROGMEM = "inGpio";
const char S_INPUT[] PROGMEM = "input";
const char S_INTERVAL[] PROGMEM = "interval";
const char S_INVERT_STATE[] PROGMEM = "invertState";
const char S_IP[] PROGMEM = "ip";
const char S_I_FACTOR[] PROGMEM = "i_factor";
const char S_K_FACTOR[] PROGMEM = "k_factor";
const char S_LOGIN[] PROGMEM = "login";
const char S_LOG_LEVEL[] PROGMEM = "logLevel";
const char S_MAC[] PROGMEM = "mac";
const char S_MASTER[] PROGMEM = "master";
const char S_MAX[] PROGMEM = "max";
const char S_MAX_FREE_BLOCK[] PROGMEM = "maxFreeBlock";
const char S_MAX_MODULATION[] PROGMEM = "maxModulation";
const char S_MAX_POWER[] PROGMEM = "maxPower";
const char S_MAX_TEMP[] PROGMEM = "maxTemp";
const char S_MEMBER_ID[] PROGMEM = "memberId";
const char S_MIN[] PROGMEM = "min";
const char S_MIN_FREE[] PROGMEM = "minFree";
const char S_MIN_MAX_FREE_BLOCK[] PROGMEM = "minMaxFreeBlock";
const char S_MIN_POWER[] PROGMEM = "minPower";
const char S_MIN_TEMP[] PROGMEM = "minTemp";
const char S_MODEL[] PROGMEM = "model";
const char S_MODULATION[] PROGMEM = "modulation";
const char S_MODULATION_SYNC_WITH_HEATING[] PROGMEM = "modulationSyncWithHeating";
const char S_MQTT[] PROGMEM = "mqtt";
const char S_NAME[] PROGMEM = "name";
const char S_NATIVE_HEATING_CONTROL[] PROGMEM = "nativeHeatingControl";
const char S_NETWORK[] PROGMEM = "network";
const char S_N_FACTOR[] PROGMEM = "n_factor";
const char S_OFFSET[] PROGMEM = "offset";
const char S_ON_ENABLED_HEATING[] PROGMEM = "onEnabledHeating";
const char S_ON_FAULT[] PROGMEM = "onFault";
const char S_ON_LOSS_CONNECTION[] PROGMEM = "onLossConnection";
const char S_OPENTHERM[] PROGMEM = "opentherm";
const char S_OUTDOOR_TEMP[] PROGMEM = "outdoorTemp";
const char S_OUT_GPIO[] PROGMEM = "outGpio";
const char S_OUTPUT[] PROGMEM = "output";
const char S_PASSWORD[] PROGMEM = "password";
const char S_PID[] PROGMEM = "pid";
const char S_PORT[] PROGMEM = "port";
const char S_PORTAL[] PROGMEM = "portal";
const char S_POST_CIRCULATION_TIME[] PROGMEM = "postCirculationTime";
const char S_POWER[] PROGMEM = "power";
const char S_PREFIX[] PROGMEM = "prefix";
const char S_PROTOCOL_VERSION[] PROGMEM = "protocolVersion";
const char S_PURPOSE[] PROGMEM = "purpose";
const char S_P_FACTOR[] PROGMEM = "p_factor";
const char S_REAL_SIZE[] PROGMEM = "realSize";
const char S_REASON[] PROGMEM = "reason";
const char S_RESET_DIAGNOSTIC[] PROGMEM = "resetDiagnostic";
const char S_RESET_FAULT[] PROGMEM = "resetFault";
const char S_RESET_REASON[] PROGMEM = "resetReason";
const char S_RESTART[] PROGMEM = "restart";
const char S_RETURN_TEMP[] PROGMEM = "returnTemp";
const char S_REV[] PROGMEM = "rev";
const char S_RSSI[] PROGMEM = "rssi";
const char S_RX_LED_GPIO[] PROGMEM = "rxLedGpio";
const char S_SDK[] PROGMEM = "sdk";
const char S_SENSORS[] PROGMEM = "sensors";
const char S_SERIAL[] PROGMEM = "serial";
const char S_SERVER[] PROGMEM = "server";
const char S_SETTINGS[] PROGMEM = "settings";
const char S_SIGNAL_QUALITY[] PROGMEM = "signalQuality";
const char S_SIZE[] PROGMEM = "size";
const char S_SLAVE[] PROGMEM = "slave";
const char S_SSID[] PROGMEM = "ssid";
const char S_STA[] PROGMEM = "sta";
const char S_STATE[] PROGMEM = "state";
const char S_STATIC_CONFIG[] PROGMEM = "staticConfig";
const char S_STATUS_LED_GPIO[] PROGMEM = "statusLedGpio";
const char S_SETPOINT_TEMP[] PROGMEM = "setpointTemp";
const char S_SUBNET[] PROGMEM = "subnet";
const char S_SUMMER_WINTER_MODE[] PROGMEM = "summerWinterMode";
const char S_SYSTEM[] PROGMEM = "system";
const char S_TARGET[] PROGMEM = "target";
const char S_TARGET_TEMP[] PROGMEM = "targetTemp";
const char S_TELNET[] PROGMEM = "telnet";
const char S_TEMPERATURE[] PROGMEM = "temperature";
const char S_THRESHOLD_TIME[] PROGMEM = "thresholdTime";
const char S_TOTAL[] PROGMEM = "total";
const char S_TRESHOLD_TIME[] PROGMEM = "tresholdTime";
const char S_TURBO[] PROGMEM = "turbo";
const char S_TURBO_FACTOR[] PROGMEM = "turboFactor";
const char S_TYPE[] PROGMEM = "type";
const char S_T_FACTOR[] PROGMEM = "t_factor";
const char S_UNIT_SYSTEM[] PROGMEM = "unitSystem";
const char S_UPTIME[] PROGMEM = "uptime";
const char S_USE[] PROGMEM = "use";
const char S_USE_DHCP[] PROGMEM = "useDhcp";
const char S_USER[] PROGMEM = "user";
const char S_VALUE[] PROGMEM = "value";
const char S_VERSION[] PROGMEM = "version";
const char S_ACTIONS[] PROGMEM = "actions";
const char S_ACTIVE[] PROGMEM = "active";
const char S_ADDRESS[] PROGMEM = "address";
const char S_ANTI_STUCK_INTERVAL[] PROGMEM = "antiStuckInterval";
const char S_ANTI_STUCK_TIME[] PROGMEM = "antiStuckTime";
const char S_AP[] PROGMEM = "ap";
const char S_APP_VERSION[] PROGMEM = "appVersion";
const char S_AUTH[] PROGMEM = "auth";
const char S_AUTO_DIAG_RESET[] PROGMEM = "autoDiagReset";
const char S_AUTO_FAULT_RESET[] PROGMEM = "autoFaultReset";
const char S_BACKTRACE[] PROGMEM = "backtrace";
const char S_BATTERY[] PROGMEM = "battery";
const char S_BAUDRATE[] PROGMEM = "baudrate";
const char S_BLOCKING[] PROGMEM = "blocking";
const char S_BSSID[] PROGMEM = "bssid";
const char S_BUILD[] PROGMEM = "build";
const char S_CASCADE_CONTROL[] PROGMEM = "cascadeControl";
const char S_CHANNEL[] PROGMEM = "channel";
const char S_CH2_ALWAYS_ENABLED[] PROGMEM = "ch2AlwaysEnabled";
const char S_CHIP[] PROGMEM = "chip";
const char S_CODE[] PROGMEM = "code";
const char S_CONNECTED[] PROGMEM = "connected";
const char S_CONTINUES[] PROGMEM = "continues";
const char S_COOLING[] PROGMEM = "cooling";
const char S_COOLING_SUPPORT[] PROGMEM = "coolingSupport";
const char S_CORE[] PROGMEM = "core";
const char S_CORES[] PROGMEM = "cores";
const char S_CRASH[] PROGMEM = "crash";
const char S_CURRENT_TEMP[] PROGMEM = "currentTemp";
const char S_DATA[] PROGMEM = "data";
const char S_DATE[] PROGMEM = "date";
const char S_DEADBAND[] PROGMEM = "deadband";
const char S_DHW[] PROGMEM = "dhw";
const char S_DHW_BLOCKING[] PROGMEM = "dhwBlocking";
const char S_DHW_STATE_AS_DHW_BLOCKING[] PROGMEM = "dhwStateAsDhwBlocking";
const char S_DHW_SUPPORT[] PROGMEM = "dhwSupport";
const char S_DHW_TO_CH2[] PROGMEM = "dhwToCh2";
const char S_DIAG[] PROGMEM = "diag";
const char S_DNS[] PROGMEM = "dns";
const char S_DT[] PROGMEM = "dt";
const char S_D_FACTOR[] PROGMEM = "d_factor";
const char S_D_MULTIPLIER[] PROGMEM = "d_multiplier";
const char S_EMERGENCY[] PROGMEM = "emergency";
const char S_ENABLED[] PROGMEM = "enabled";
const char S_ENV[] PROGMEM = "env";
const char S_EPC[] PROGMEM = "epc";
const char S_EQUITHERM[] PROGMEM = "equitherm";
const char S_EXTERNAL_PUMP[] PROGMEM = "externalPump";
const char S_FACTOR[] PROGMEM = "factor";
const char S_FAULT[] PROGMEM = "fault";
const char S_FREEZE_PROTECTION[] PROGMEM = "freezeProtection";
const char S_FILTERING[] PROGMEM = "filtering";
const char S_FILTERING_FACTOR[] PROGMEM = "filteringFactor";
const char S_FLAGS[] PROGMEM = "flags";
const char S_FLAME[] PROGMEM = "flame";
const char S_FLASH[] PROGMEM = "flash";
const char S_FREE[] PROGMEM = "free";
const char S_FREQ[] PROGMEM = "freq";
const char S_GATEWAY[] PROGMEM = "gateway";
const char S_GET_MIN_MAX_TEMP[] PROGMEM = "getMinMaxTemp";
const char S_GPIO[] PROGMEM = "gpio";
const char S_HEAP[] PROGMEM = "heap";
const char S_HEATING[] PROGMEM = "heating";
const char S_HEATING_TO_CH2[] PROGMEM = "heatingToCh2";
const char S_HEATING_STATE_TO_SUMMER_WINTER_MODE[] PROGMEM = "heatingStateToSummerWinterMode";
const char S_HIDDEN[] PROGMEM = "hidden";
const char S_HIGH_TEMP[] PROGMEM = "highTemp";
const char S_HOME_ASSISTANT_DISCOVERY[] PROGMEM = "homeAssistantDiscovery";
const char S_HOSTNAME[] PROGMEM = "hostname";
const char S_HUMIDITY[] PROGMEM = "humidity";
const char S_HYSTERESIS[] PROGMEM = "hysteresis";
const char S_ID[] PROGMEM = "id";
const char S_IGNORE_DIAG_STATE[] PROGMEM = "ignoreDiagState";
const char S_IMMERGAS_FIX[] PROGMEM = "immergasFix";
const char S_INDOOR_TEMP[] PROGMEM = "indoorTemp";
const char S_INDOOR_TEMP_CONTROL[] PROGMEM = "indoorTempControl";
const char S_IN_GPIO[] PROGMEM = "inGpio";
const char S_INPUT[] PROGMEM = "input";
const char S_INTERVAL[] PROGMEM = "interval";
const char S_INVERT_STATE[] PROGMEM = "invertState";
const char S_IP[] PROGMEM = "ip";
const char S_I_FACTOR[] PROGMEM = "i_factor";
const char S_I_MULTIPLIER[] PROGMEM = "i_multiplier";
const char S_K_FACTOR[] PROGMEM = "k_factor";
const char S_LOGIN[] PROGMEM = "login";
const char S_LOG_LEVEL[] PROGMEM = "logLevel";
const char S_LOW_TEMP[] PROGMEM = "lowTemp";
const char S_MAC[] PROGMEM = "mac";
const char S_MASTER[] PROGMEM = "master";
const char S_MAX[] PROGMEM = "max";
const char S_MAX_FREE_BLOCK[] PROGMEM = "maxFreeBlock";
const char S_MAX_MODULATION[] PROGMEM = "maxModulation";
const char S_MAX_POWER[] PROGMEM = "maxPower";
const char S_MAX_TEMP[] PROGMEM = "maxTemp";
const char S_MAX_TEMP_SYNC_WITH_TARGET_TEMP[] PROGMEM = "maxTempSyncWithTargetTemp";
const char S_MDNS[] PROGMEM = "mdns";
const char S_MEMBER_ID[] PROGMEM = "memberId";
const char S_MIN[] PROGMEM = "min";
const char S_MIN_FREE[] PROGMEM = "minFree";
const char S_MIN_MAX_FREE_BLOCK[] PROGMEM = "minMaxFreeBlock";
const char S_MIN_POWER[] PROGMEM = "minPower";
const char S_MIN_TEMP[] PROGMEM = "minTemp";
const char S_MODEL[] PROGMEM = "model";
const char S_MODULATION[] PROGMEM = "modulation";
const char S_MQTT[] PROGMEM = "mqtt";
const char S_NAME[] PROGMEM = "name";
const char S_NATIVE_HEATING_CONTROL[] PROGMEM = "nativeHeatingControl";
const char S_NETWORK[] PROGMEM = "network";
const char S_NTP[] PROGMEM = "ntp";
const char S_N_FACTOR[] PROGMEM = "n_factor";
const char S_OFFSET[] PROGMEM = "offset";
const char S_ON_ENABLED_HEATING[] PROGMEM = "onEnabledHeating";
const char S_ON_FAULT[] PROGMEM = "onFault";
const char S_ON_LOSS_CONNECTION[] PROGMEM = "onLossConnection";
const char S_OPENTHERM[] PROGMEM = "opentherm";
const char S_OPTIONS[] PROGMEM = "options";
const char S_OUTDOOR_TEMP[] PROGMEM = "outdoorTemp";
const char S_OUT_GPIO[] PROGMEM = "outGpio";
const char S_OUTPUT[] PROGMEM = "output";
const char S_OVERHEAT[] PROGMEM = "overheat";
const char S_OVERHEAT_PROTECTION[] PROGMEM = "overheatProtection";
const char S_PASSWORD[] PROGMEM = "password";
const char S_PID[] PROGMEM = "pid";
const char S_PORT[] PROGMEM = "port";
const char S_PORTAL[] PROGMEM = "portal";
const char S_POST_CIRCULATION_TIME[] PROGMEM = "postCirculationTime";
const char S_POWER[] PROGMEM = "power";
const char S_PREFIX[] PROGMEM = "prefix";
const char S_PROTOCOL_VERSION[] PROGMEM = "protocolVersion";
const char S_PURPOSE[] PROGMEM = "purpose";
const char S_P_FACTOR[] PROGMEM = "p_factor";
const char S_P_MULTIPLIER[] PROGMEM = "p_multiplier";
const char S_REAL_SIZE[] PROGMEM = "realSize";
const char S_REASON[] PROGMEM = "reason";
const char S_RESET_DIAGNOSTIC[] PROGMEM = "resetDiagnostic";
const char S_RESET_FAULT[] PROGMEM = "resetFault";
const char S_RESET_REASON[] PROGMEM = "resetReason";
const char S_RESTART[] PROGMEM = "restart";
const char S_RETURN_TEMP[] PROGMEM = "returnTemp";
const char S_REV[] PROGMEM = "rev";
const char S_RSSI[] PROGMEM = "rssi";
const char S_RX_LED_GPIO[] PROGMEM = "rxLedGpio";
const char S_SDK[] PROGMEM = "sdk";
const char S_SENSORS[] PROGMEM = "sensors";
const char S_SERIAL[] PROGMEM = "serial";
const char S_SERVER[] PROGMEM = "server";
const char S_SETTINGS[] PROGMEM = "settings";
const char S_SET_DATE_AND_TIME[] PROGMEM = "setDateAndTime";
const char S_SIGNAL_QUALITY[] PROGMEM = "signalQuality";
const char S_SIZE[] PROGMEM = "size";
const char S_SLAVE[] PROGMEM = "slave";
const char S_SSID[] PROGMEM = "ssid";
const char S_STA[] PROGMEM = "sta";
const char S_STATE[] PROGMEM = "state";
const char S_STATIC_CONFIG[] PROGMEM = "staticConfig";
const char S_STATUS_LED_GPIO[] PROGMEM = "statusLedGpio";
const char S_SETPOINT[] PROGMEM = "setpoint";
const char S_SETPOINT_TEMP[] PROGMEM = "setpointTemp";
const char S_SUBNET[] PROGMEM = "subnet";
const char S_SUMMER_WINTER_MODE[] PROGMEM = "summerWinterMode";
const char S_SYSTEM[] PROGMEM = "system";
const char S_TARGET[] PROGMEM = "target";
const char S_TARGET_TEMP[] PROGMEM = "targetTemp";
const char S_TELNET[] PROGMEM = "telnet";
const char S_TEMPERATURE[] PROGMEM = "temperature";
const char S_THRESHOLD_HIGH[] PROGMEM = "thresholdHigh";
const char S_THRESHOLD_LOW[] PROGMEM = "thresholdLow";
const char S_THRESHOLD_TIME[] PROGMEM = "thresholdTime";
const char S_TIMEZONE[] PROGMEM = "timezone";
const char S_TOTAL[] PROGMEM = "total";
const char S_TRESHOLD_TIME[] PROGMEM = "tresholdTime";
const char S_TURBO[] PROGMEM = "turbo";
const char S_TURBO_FACTOR[] PROGMEM = "turboFactor";
const char S_TYPE[] PROGMEM = "type";
const char S_T_FACTOR[] PROGMEM = "t_factor";
const char S_UNIT_SYSTEM[] PROGMEM = "unitSystem";
const char S_UPTIME[] PROGMEM = "uptime";
const char S_USE[] PROGMEM = "use";
const char S_USE_DHCP[] PROGMEM = "useDhcp";
const char S_USER[] PROGMEM = "user";
const char S_VALUE[] PROGMEM = "value";
const char S_VERSION[] PROGMEM = "version";

View File

@@ -1,8 +1,78 @@
#include <Arduino.h>
String getChipId(const char* prefix = nullptr, const char* suffix = nullptr) {
String chipId;
chipId.reserve(
6
+ (prefix != nullptr ? strlen(prefix) : 0)
+ (suffix != nullptr ? strlen(suffix) : 0)
);
if (prefix != nullptr) {
chipId.concat(prefix);
}
uint32_t cid = 0;
#if defined(ARDUINO_ARCH_ESP8266)
cid = ESP.getChipId();
#elif defined(ARDUINO_ARCH_ESP32)
// https://github.com/espressif/arduino-esp32/blob/master/libraries/ESP32/examples/ChipID/GetChipID/GetChipID.ino
for (uint8_t i = 0; i < 17; i = i + 8) {
cid |= ((ESP.getEfuseMac() >> (40 - i)) & 0xff) << i;
}
#endif
chipId += String(cid, HEX);
if (suffix != nullptr) {
chipId.concat(suffix);
}
chipId.trim();
return chipId;
}
bool isLeapYear(short year) {
if (year % 4 != 0) {
return false;
}
if (year % 100 != 0) {
return true;
}
return (year % 400) == 0;
}
// convert UTC tm time to time_t epoch time
// source: https://github.com/cyberman54/ESP32-Paxcounter/blob/master/src/timekeeper.cpp
time_t mkgmtime(const struct tm *ptm) {
const int SecondsPerMinute = 60;
const int SecondsPerHour = 3600;
const int SecondsPerDay = 86400;
const int DaysOfMonth[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
time_t secs = 0;
// tm_year is years since 1900
int year = ptm->tm_year + 1900;
for (int y = 1970; y < year; ++y) {
secs += (isLeapYear(y) ? 366 : 365) * SecondsPerDay;
}
// tm_mon is month from 0..11
for (int m = 0; m < ptm->tm_mon; ++m) {
secs += DaysOfMonth[m] * SecondsPerDay;
if (m == 1 && isLeapYear(year))
secs += SecondsPerDay;
}
secs += (ptm->tm_mday - 1) * SecondsPerDay;
secs += ptm->tm_hour * SecondsPerHour;
secs += ptm->tm_min * SecondsPerMinute;
secs += ptm->tm_sec;
return secs;
}
inline bool isDigit(const char* ptr) {
char* endPtr;
strtol(ptr, &endPtr, 10);
auto tmp = strtol(ptr, &endPtr, 10);
return *endPtr == 0;
}
@@ -359,6 +429,10 @@ void settingsToJson(const Settings& src, JsonVariant dst, bool safe = false) {
telnet[FPSTR(S_ENABLED)] = src.system.telnet.enabled;
telnet[FPSTR(S_PORT)] = src.system.telnet.port;
auto ntp = system[FPSTR(S_NTP)].to<JsonObject>();
ntp[FPSTR(S_SERVER)] = src.system.ntp.server;
ntp[FPSTR(S_TIMEZONE)] = src.system.ntp.timezone;
system[FPSTR(S_UNIT_SYSTEM)] = static_cast<uint8_t>(src.system.unitSystem);
system[FPSTR(S_STATUS_LED_GPIO)] = src.system.statusLedGpio;
@@ -366,6 +440,7 @@ void settingsToJson(const Settings& src, JsonVariant dst, bool safe = false) {
portal[FPSTR(S_AUTH)] = src.portal.auth;
portal[FPSTR(S_LOGIN)] = src.portal.login;
portal[FPSTR(S_PASSWORD)] = src.portal.password;
portal[FPSTR(S_MDNS)] = src.portal.mdns;
auto opentherm = dst[FPSTR(S_OPENTHERM)].to<JsonObject>();
opentherm[FPSTR(S_UNIT_SYSTEM)] = static_cast<uint8_t>(src.opentherm.unitSystem);
@@ -374,19 +449,27 @@ void settingsToJson(const Settings& src, JsonVariant dst, bool safe = false) {
opentherm[FPSTR(S_RX_LED_GPIO)] = src.opentherm.rxLedGpio;
opentherm[FPSTR(S_MEMBER_ID)] = src.opentherm.memberId;
opentherm[FPSTR(S_FLAGS)] = src.opentherm.flags;
opentherm[FPSTR(S_MAX_MODULATION)] = src.opentherm.maxModulation;
opentherm[FPSTR(S_MIN_POWER)] = roundf(src.opentherm.minPower, 2);
opentherm[FPSTR(S_MAX_POWER)] = roundf(src.opentherm.maxPower, 2);
opentherm[FPSTR(S_DHW_PRESENT)] = src.opentherm.dhwPresent;
opentherm[FPSTR(S_SUMMER_WINTER_MODE)] = src.opentherm.summerWinterMode;
opentherm[FPSTR(S_HEATING_CH2_ENABLED)] = src.opentherm.heatingCh2Enabled;
opentherm[FPSTR(S_HEATING_CH1_TO_CH2)] = src.opentherm.heatingCh1ToCh2;
opentherm[FPSTR(S_DHW_TO_CH2)] = src.opentherm.dhwToCh2;
opentherm[FPSTR(S_DHW_BLOCKING)] = src.opentherm.dhwBlocking;
opentherm[FPSTR(S_MODULATION_SYNC_WITH_HEATING)] = src.opentherm.modulationSyncWithHeating;
opentherm[FPSTR(S_GET_MIN_MAX_TEMP)] = src.opentherm.getMinMaxTemp;
opentherm[FPSTR(S_NATIVE_HEATING_CONTROL)] = src.opentherm.nativeHeatingControl;
opentherm[FPSTR(S_IMMERGAS_FIX)] = src.opentherm.immergasFix;
auto otOptions = opentherm[FPSTR(S_OPTIONS)].to<JsonObject>();
otOptions[FPSTR(S_DHW_SUPPORT)] = src.opentherm.options.dhwSupport;
otOptions[FPSTR(S_COOLING_SUPPORT)] = src.opentherm.options.coolingSupport;
otOptions[FPSTR(S_SUMMER_WINTER_MODE)] = src.opentherm.options.summerWinterMode;
otOptions[FPSTR(S_HEATING_STATE_TO_SUMMER_WINTER_MODE)] = src.opentherm.options.heatingStateToSummerWinterMode;
otOptions[FPSTR(S_CH2_ALWAYS_ENABLED)] = src.opentherm.options.ch2AlwaysEnabled;
otOptions[FPSTR(S_HEATING_TO_CH2)] = src.opentherm.options.heatingToCh2;
otOptions[FPSTR(S_DHW_TO_CH2)] = src.opentherm.options.dhwToCh2;
otOptions[FPSTR(S_DHW_BLOCKING)] = src.opentherm.options.dhwBlocking;
otOptions[FPSTR(S_DHW_STATE_AS_DHW_BLOCKING)] = src.opentherm.options.dhwStateAsDhwBlocking;
otOptions[FPSTR(S_MAX_TEMP_SYNC_WITH_TARGET_TEMP)] = src.opentherm.options.maxTempSyncWithTargetTemp;
otOptions[FPSTR(S_GET_MIN_MAX_TEMP)] = src.opentherm.options.getMinMaxTemp;
otOptions[FPSTR(S_IGNORE_DIAG_STATE)] = src.opentherm.options.ignoreDiagState;
otOptions[FPSTR(S_AUTO_FAULT_RESET)] = src.opentherm.options.autoFaultReset;
otOptions[FPSTR(S_AUTO_DIAG_RESET)] = src.opentherm.options.autoDiagReset;
otOptions[FPSTR(S_SET_DATE_AND_TIME)] = src.opentherm.options.setDateAndTime;
otOptions[FPSTR(S_NATIVE_HEATING_CONTROL)] = src.opentherm.options.nativeHeatingControl;
otOptions[FPSTR(S_IMMERGAS_FIX)] = src.opentherm.options.immergasFix;
auto mqtt = dst[FPSTR(S_MQTT)].to<JsonObject>();
mqtt[FPSTR(S_ENABLED)] = src.mqtt.enabled;
@@ -411,12 +494,26 @@ void settingsToJson(const Settings& src, JsonVariant dst, bool safe = false) {
heating[FPSTR(S_TURBO_FACTOR)] = roundf(src.heating.turboFactor, 3);
heating[FPSTR(S_MIN_TEMP)] = src.heating.minTemp;
heating[FPSTR(S_MAX_TEMP)] = src.heating.maxTemp;
heating[FPSTR(S_MAX_MODULATION)] = src.heating.maxModulation;
auto heatingOverheatProtection = heating[FPSTR(S_OVERHEAT_PROTECTION)].to<JsonObject>();
heatingOverheatProtection[FPSTR(S_HIGH_TEMP)] = src.heating.overheatProtection.highTemp;
heatingOverheatProtection[FPSTR(S_LOW_TEMP)] = src.heating.overheatProtection.lowTemp;
auto freezeProtection = heating[FPSTR(S_FREEZE_PROTECTION)].to<JsonObject>();
freezeProtection[FPSTR(S_LOW_TEMP)] = src.heating.freezeProtection.lowTemp;
freezeProtection[FPSTR(S_THRESHOLD_TIME)] = src.heating.freezeProtection.thresholdTime;
auto dhw = dst[FPSTR(S_DHW)].to<JsonObject>();
dhw[FPSTR(S_ENABLED)] = src.dhw.enabled;
dhw[FPSTR(S_TARGET)] = roundf(src.dhw.target, 1);
dhw[FPSTR(S_MIN_TEMP)] = src.dhw.minTemp;
dhw[FPSTR(S_MAX_TEMP)] = src.dhw.maxTemp;
dhw[FPSTR(S_MAX_MODULATION)] = src.dhw.maxModulation;
auto dhwOverheatProtection = dhw[FPSTR(S_OVERHEAT_PROTECTION)].to<JsonObject>();
dhwOverheatProtection[FPSTR(S_HIGH_TEMP)] = src.dhw.overheatProtection.highTemp;
dhwOverheatProtection[FPSTR(S_LOW_TEMP)] = src.dhw.overheatProtection.lowTemp;
auto equitherm = dst[FPSTR(S_EQUITHERM)].to<JsonObject>();
equitherm[FPSTR(S_ENABLED)] = src.equitherm.enabled;
@@ -433,6 +530,14 @@ void settingsToJson(const Settings& src, JsonVariant dst, bool safe = false) {
pid[FPSTR(S_MIN_TEMP)] = src.pid.minTemp;
pid[FPSTR(S_MAX_TEMP)] = src.pid.maxTemp;
auto pidDeadband = pid[FPSTR(S_DEADBAND)].to<JsonObject>();
pidDeadband[FPSTR(S_ENABLED)] = src.pid.deadband.enabled;
pidDeadband[FPSTR(S_P_MULTIPLIER)] = src.pid.deadband.p_multiplier;
pidDeadband[FPSTR(S_I_MULTIPLIER)] = src.pid.deadband.i_multiplier;
pidDeadband[FPSTR(S_D_MULTIPLIER)] = src.pid.deadband.d_multiplier;
pidDeadband[FPSTR(S_THRESHOLD_HIGH)] = src.pid.deadband.thresholdHigh;
pidDeadband[FPSTR(S_THRESHOLD_LOW)] = src.pid.deadband.thresholdLow;
if (!safe) {
auto externalPump = dst[FPSTR(S_EXTERNAL_PUMP)].to<JsonObject>();
externalPump[FPSTR(S_USE)] = src.externalPump.use;
@@ -514,6 +619,24 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false
}
}
if (!src[FPSTR(S_SYSTEM)][FPSTR(S_NTP)][FPSTR(S_SERVER)].isNull()) {
String value = src[FPSTR(S_SYSTEM)][FPSTR(S_NTP)][FPSTR(S_SERVER)].as<String>();
if (value.length() < sizeof(dst.system.ntp.server) && !value.equals(dst.system.ntp.server)) {
strcpy(dst.system.ntp.server, value.c_str());
changed = true;
}
}
if (!src[FPSTR(S_SYSTEM)][FPSTR(S_NTP)][FPSTR(S_TIMEZONE)].isNull()) {
String value = src[FPSTR(S_SYSTEM)][FPSTR(S_NTP)][FPSTR(S_TIMEZONE)].as<String>();
if (value.length() < sizeof(dst.system.ntp.timezone) && !value.equals(dst.system.ntp.timezone)) {
strcpy(dst.system.ntp.timezone, value.c_str());
changed = true;
}
}
if (!src[FPSTR(S_SYSTEM)][FPSTR(S_UNIT_SYSTEM)].isNull()) {
uint8_t value = src[FPSTR(S_SYSTEM)][FPSTR(S_UNIT_SYSTEM)].as<unsigned char>();
UnitSystem prevUnitSystem = dst.system.unitSystem;
@@ -597,6 +720,20 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false
}
}
if (dst.portal.auth && (!strlen(dst.portal.login) || !strlen(dst.portal.password))) {
dst.portal.auth = false;
changed = true;
}
if (src[FPSTR(S_PORTAL)][FPSTR(S_MDNS)].is<bool>()) {
bool value = src[FPSTR(S_PORTAL)][FPSTR(S_MDNS)].as<bool>();
if (value != dst.portal.mdns) {
dst.portal.mdns = value;
changed = true;
}
}
// opentherm
if (!src[FPSTR(S_OPENTHERM)][FPSTR(S_UNIT_SYSTEM)].isNull()) {
@@ -691,15 +828,6 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false
}
}
if (!src[FPSTR(S_OPENTHERM)][FPSTR(S_MAX_MODULATION)].isNull()) {
unsigned char value = src[FPSTR(S_OPENTHERM)][FPSTR(S_MAX_MODULATION)].as<unsigned char>();
if (value > 0 && value <= 100 && value != dst.opentherm.maxModulation) {
dst.opentherm.maxModulation = value;
changed = true;
}
}
if (!src[FPSTR(S_OPENTHERM)][FPSTR(S_MIN_POWER)].isNull()) {
float value = src[FPSTR(S_OPENTHERM)][FPSTR(S_MIN_POWER)].as<float>();
@@ -718,101 +846,164 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false
}
}
if (src[FPSTR(S_OPENTHERM)][FPSTR(S_DHW_PRESENT)].is<bool>()) {
bool value = src[FPSTR(S_OPENTHERM)][FPSTR(S_DHW_PRESENT)].as<bool>();
if (src[FPSTR(S_OPENTHERM)][FPSTR(S_OPTIONS)][FPSTR(S_DHW_SUPPORT)].is<bool>()) {
bool value = src[FPSTR(S_OPENTHERM)][FPSTR(S_OPTIONS)][FPSTR(S_DHW_SUPPORT)].as<bool>();
if (value != dst.opentherm.dhwPresent) {
dst.opentherm.dhwPresent = value;
if (value != dst.opentherm.options.dhwSupport) {
dst.opentherm.options.dhwSupport = value;
changed = true;
}
}
if (src[FPSTR(S_OPENTHERM)][FPSTR(S_SUMMER_WINTER_MODE)].is<bool>()) {
bool value = src[FPSTR(S_OPENTHERM)][FPSTR(S_SUMMER_WINTER_MODE)].as<bool>();
if (src[FPSTR(S_OPENTHERM)][FPSTR(S_OPTIONS)][FPSTR(S_COOLING_SUPPORT)].is<bool>()) {
bool value = src[FPSTR(S_OPENTHERM)][FPSTR(S_OPTIONS)][FPSTR(S_COOLING_SUPPORT)].as<bool>();
if (value != dst.opentherm.summerWinterMode) {
dst.opentherm.summerWinterMode = value;
if (value != dst.opentherm.options.coolingSupport) {
dst.opentherm.options.coolingSupport = value;
changed = true;
}
}
if (src[FPSTR(S_OPENTHERM)][FPSTR(S_HEATING_CH2_ENABLED)].is<bool>()) {
bool value = src[FPSTR(S_OPENTHERM)][FPSTR(S_HEATING_CH2_ENABLED)].as<bool>();
if (src[FPSTR(S_OPENTHERM)][FPSTR(S_OPTIONS)][FPSTR(S_SUMMER_WINTER_MODE)].is<bool>()) {
bool value = src[FPSTR(S_OPENTHERM)][FPSTR(S_OPTIONS)][FPSTR(S_SUMMER_WINTER_MODE)].as<bool>();
if (value != dst.opentherm.heatingCh2Enabled) {
dst.opentherm.heatingCh2Enabled = value;
if (value != dst.opentherm.options.summerWinterMode) {
dst.opentherm.options.summerWinterMode = value;
changed = true;
}
}
if (dst.opentherm.heatingCh2Enabled) {
dst.opentherm.heatingCh1ToCh2 = false;
dst.opentherm.dhwToCh2 = false;
if (src[FPSTR(S_OPENTHERM)][FPSTR(S_OPTIONS)][FPSTR(S_HEATING_STATE_TO_SUMMER_WINTER_MODE)].is<bool>()) {
bool value = src[FPSTR(S_OPENTHERM)][FPSTR(S_OPTIONS)][FPSTR(S_HEATING_STATE_TO_SUMMER_WINTER_MODE)].as<bool>();
if (value != dst.opentherm.options.heatingStateToSummerWinterMode) {
dst.opentherm.options.heatingStateToSummerWinterMode = value;
changed = true;
}
}
if (src[FPSTR(S_OPENTHERM)][FPSTR(S_OPTIONS)][FPSTR(S_CH2_ALWAYS_ENABLED)].is<bool>()) {
bool value = src[FPSTR(S_OPENTHERM)][FPSTR(S_OPTIONS)][FPSTR(S_CH2_ALWAYS_ENABLED)].as<bool>();
if (value != dst.opentherm.options.ch2AlwaysEnabled) {
dst.opentherm.options.ch2AlwaysEnabled = value;
if (dst.opentherm.options.ch2AlwaysEnabled) {
dst.opentherm.options.heatingToCh2 = false;
dst.opentherm.options.dhwToCh2 = false;
}
changed = true;
}
}
if (src[FPSTR(S_OPENTHERM)][FPSTR(S_HEATING_CH1_TO_CH2)].is<bool>()) {
bool value = src[FPSTR(S_OPENTHERM)][FPSTR(S_HEATING_CH1_TO_CH2)].as<bool>();
if (src[FPSTR(S_OPENTHERM)][FPSTR(S_OPTIONS)][FPSTR(S_HEATING_TO_CH2)].is<bool>()) {
bool value = src[FPSTR(S_OPENTHERM)][FPSTR(S_OPTIONS)][FPSTR(S_HEATING_TO_CH2)].as<bool>();
if (value != dst.opentherm.heatingCh1ToCh2) {
dst.opentherm.heatingCh1ToCh2 = value;
if (value != dst.opentherm.options.heatingToCh2) {
dst.opentherm.options.heatingToCh2 = value;
if (dst.opentherm.heatingCh1ToCh2) {
dst.opentherm.heatingCh2Enabled = false;
dst.opentherm.dhwToCh2 = false;
if (dst.opentherm.options.heatingToCh2) {
dst.opentherm.options.ch2AlwaysEnabled = false;
dst.opentherm.options.dhwToCh2 = false;
}
changed = true;
}
}
if (src[FPSTR(S_OPENTHERM)][FPSTR(S_DHW_TO_CH2)].is<bool>()) {
bool value = src[FPSTR(S_OPENTHERM)][FPSTR(S_DHW_TO_CH2)].as<bool>();
if (src[FPSTR(S_OPENTHERM)][FPSTR(S_OPTIONS)][FPSTR(S_DHW_TO_CH2)].is<bool>()) {
bool value = src[FPSTR(S_OPENTHERM)][FPSTR(S_OPTIONS)][FPSTR(S_DHW_TO_CH2)].as<bool>();
if (value != dst.opentherm.dhwToCh2) {
dst.opentherm.dhwToCh2 = value;
if (value != dst.opentherm.options.dhwToCh2) {
dst.opentherm.options.dhwToCh2 = value;
if (dst.opentherm.dhwToCh2) {
dst.opentherm.heatingCh2Enabled = false;
dst.opentherm.heatingCh1ToCh2 = false;
if (dst.opentherm.options.dhwToCh2) {
dst.opentherm.options.ch2AlwaysEnabled = false;
dst.opentherm.options.heatingToCh2 = false;
}
changed = true;
}
}
if (src[FPSTR(S_OPENTHERM)][FPSTR(S_DHW_BLOCKING)].is<bool>()) {
bool value = src[FPSTR(S_OPENTHERM)][FPSTR(S_DHW_BLOCKING)].as<bool>();
if (src[FPSTR(S_OPENTHERM)][FPSTR(S_OPTIONS)][FPSTR(S_DHW_BLOCKING)].is<bool>()) {
bool value = src[FPSTR(S_OPENTHERM)][FPSTR(S_OPTIONS)][FPSTR(S_DHW_BLOCKING)].as<bool>();
if (value != dst.opentherm.dhwBlocking) {
dst.opentherm.dhwBlocking = value;
if (value != dst.opentherm.options.dhwBlocking) {
dst.opentherm.options.dhwBlocking = value;
changed = true;
}
}
if (src[FPSTR(S_OPENTHERM)][FPSTR(S_MODULATION_SYNC_WITH_HEATING)].is<bool>()) {
bool value = src[FPSTR(S_OPENTHERM)][FPSTR(S_MODULATION_SYNC_WITH_HEATING)].as<bool>();
if (src[FPSTR(S_OPENTHERM)][FPSTR(S_OPTIONS)][FPSTR(S_DHW_STATE_AS_DHW_BLOCKING)].is<bool>()) {
bool value = src[FPSTR(S_OPENTHERM)][FPSTR(S_OPTIONS)][FPSTR(S_DHW_STATE_AS_DHW_BLOCKING)].as<bool>();
if (value != dst.opentherm.modulationSyncWithHeating) {
dst.opentherm.modulationSyncWithHeating = value;
if (value != dst.opentherm.options.dhwStateAsDhwBlocking) {
dst.opentherm.options.dhwStateAsDhwBlocking = value;
changed = true;
}
}
if (src[FPSTR(S_OPENTHERM)][FPSTR(S_GET_MIN_MAX_TEMP)].is<bool>()) {
bool value = src[FPSTR(S_OPENTHERM)][FPSTR(S_GET_MIN_MAX_TEMP)].as<bool>();
if (src[FPSTR(S_OPENTHERM)][FPSTR(S_OPTIONS)][FPSTR(S_MAX_TEMP_SYNC_WITH_TARGET_TEMP)].is<bool>()) {
bool value = src[FPSTR(S_OPENTHERM)][FPSTR(S_OPTIONS)][FPSTR(S_MAX_TEMP_SYNC_WITH_TARGET_TEMP)].as<bool>();
if (value != dst.opentherm.getMinMaxTemp) {
dst.opentherm.getMinMaxTemp = value;
if (value != dst.opentherm.options.maxTempSyncWithTargetTemp) {
dst.opentherm.options.maxTempSyncWithTargetTemp = value;
changed = true;
}
}
if (src[FPSTR(S_OPENTHERM)][FPSTR(S_NATIVE_HEATING_CONTROL)].is<bool>()) {
bool value = src[FPSTR(S_OPENTHERM)][FPSTR(S_NATIVE_HEATING_CONTROL)].as<bool>();
if (src[FPSTR(S_OPENTHERM)][FPSTR(S_OPTIONS)][FPSTR(S_GET_MIN_MAX_TEMP)].is<bool>()) {
bool value = src[FPSTR(S_OPENTHERM)][FPSTR(S_OPTIONS)][FPSTR(S_GET_MIN_MAX_TEMP)].as<bool>();
if (value != dst.opentherm.nativeHeatingControl) {
dst.opentherm.nativeHeatingControl = value;
if (value != dst.opentherm.options.getMinMaxTemp) {
dst.opentherm.options.getMinMaxTemp = value;
changed = true;
}
}
if (src[FPSTR(S_OPENTHERM)][FPSTR(S_OPTIONS)][FPSTR(S_IGNORE_DIAG_STATE)].is<bool>()) {
bool value = src[FPSTR(S_OPENTHERM)][FPSTR(S_OPTIONS)][FPSTR(S_IGNORE_DIAG_STATE)].as<bool>();
if (value != dst.opentherm.options.ignoreDiagState) {
dst.opentherm.options.ignoreDiagState = value;
changed = true;
}
}
if (src[FPSTR(S_OPENTHERM)][FPSTR(S_OPTIONS)][FPSTR(S_AUTO_FAULT_RESET)].is<bool>()) {
bool value = src[FPSTR(S_OPENTHERM)][FPSTR(S_OPTIONS)][FPSTR(S_AUTO_FAULT_RESET)].as<bool>();
if (value != dst.opentherm.options.autoFaultReset) {
dst.opentherm.options.autoFaultReset = value;
changed = true;
}
}
if (src[FPSTR(S_OPENTHERM)][FPSTR(S_OPTIONS)][FPSTR(S_AUTO_DIAG_RESET)].is<bool>()) {
bool value = src[FPSTR(S_OPENTHERM)][FPSTR(S_OPTIONS)][FPSTR(S_AUTO_DIAG_RESET)].as<bool>();
if (value != dst.opentherm.options.autoDiagReset) {
dst.opentherm.options.autoDiagReset = value;
changed = true;
}
}
if (src[FPSTR(S_OPENTHERM)][FPSTR(S_OPTIONS)][FPSTR(S_SET_DATE_AND_TIME)].is<bool>()) {
bool value = src[FPSTR(S_OPENTHERM)][FPSTR(S_OPTIONS)][FPSTR(S_SET_DATE_AND_TIME)].as<bool>();
if (value != dst.opentherm.options.setDateAndTime) {
dst.opentherm.options.setDateAndTime = value;
changed = true;
}
}
if (src[FPSTR(S_OPENTHERM)][FPSTR(S_OPTIONS)][FPSTR(S_NATIVE_HEATING_CONTROL)].is<bool>()) {
bool value = src[FPSTR(S_OPENTHERM)][FPSTR(S_OPTIONS)][FPSTR(S_NATIVE_HEATING_CONTROL)].as<bool>();
if (value != dst.opentherm.options.nativeHeatingControl) {
dst.opentherm.options.nativeHeatingControl = value;
if (value) {
dst.equitherm.enabled = false;
@@ -823,11 +1014,11 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false
}
}
if (src[FPSTR(S_OPENTHERM)][FPSTR(S_IMMERGAS_FIX)].is<bool>()) {
bool value = src[FPSTR(S_OPENTHERM)][FPSTR(S_IMMERGAS_FIX)].as<bool>();
if (src[FPSTR(S_OPENTHERM)][FPSTR(S_OPTIONS)][FPSTR(S_IMMERGAS_FIX)].is<bool>()) {
bool value = src[FPSTR(S_OPENTHERM)][FPSTR(S_OPTIONS)][FPSTR(S_IMMERGAS_FIX)].as<bool>();
if (value != dst.opentherm.immergasFix) {
dst.opentherm.immergasFix = value;
if (value != dst.opentherm.options.immergasFix) {
dst.opentherm.options.immergasFix = value;
changed = true;
}
}
@@ -923,7 +1114,7 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false
if (src[FPSTR(S_EQUITHERM)][FPSTR(S_ENABLED)].is<bool>()) {
bool value = src[FPSTR(S_EQUITHERM)][FPSTR(S_ENABLED)].as<bool>();
if (!dst.opentherm.nativeHeatingControl) {
if (!dst.opentherm.options.nativeHeatingControl) {
if (value != dst.equitherm.enabled) {
dst.equitherm.enabled = value;
changed = true;
@@ -967,7 +1158,7 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false
if (src[FPSTR(S_PID)][FPSTR(S_ENABLED)].is<bool>()) {
bool value = src[FPSTR(S_PID)][FPSTR(S_ENABLED)].as<bool>();
if (!dst.opentherm.nativeHeatingControl) {
if (!dst.opentherm.options.nativeHeatingControl) {
if (value != dst.pid.enabled) {
dst.pid.enabled = value;
changed = true;
@@ -1038,6 +1229,60 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false
changed = true;
}
if (src[FPSTR(S_PID)][FPSTR(S_DEADBAND)][FPSTR(S_ENABLED)].is<bool>()) {
bool value = src[FPSTR(S_PID)][FPSTR(S_DEADBAND)][FPSTR(S_ENABLED)].as<bool>();
if (value != dst.pid.deadband.enabled) {
dst.pid.deadband.enabled = value;
changed = true;
}
}
if (!src[FPSTR(S_PID)][FPSTR(S_DEADBAND)][FPSTR(S_P_MULTIPLIER)].isNull()) {
float value = src[FPSTR(S_PID)][FPSTR(S_DEADBAND)][FPSTR(S_P_MULTIPLIER)].as<float>();
if (value >= 0 && value <= 1 && fabsf(value - dst.pid.deadband.p_multiplier) > 0.0001f) {
dst.pid.deadband.p_multiplier = roundf(value, 3);
changed = true;
}
}
if (!src[FPSTR(S_PID)][FPSTR(S_DEADBAND)][FPSTR(S_I_MULTIPLIER)].isNull()) {
float value = src[FPSTR(S_PID)][FPSTR(S_DEADBAND)][FPSTR(S_I_MULTIPLIER)].as<float>();
if (value >= 0 && value <= 1 && fabsf(value - dst.pid.deadband.i_multiplier) > 0.0001f) {
dst.pid.deadband.i_multiplier = roundf(value, 3);
changed = true;
}
}
if (!src[FPSTR(S_PID)][FPSTR(S_DEADBAND)][FPSTR(S_D_MULTIPLIER)].isNull()) {
float value = src[FPSTR(S_PID)][FPSTR(S_DEADBAND)][FPSTR(S_D_MULTIPLIER)].as<float>();
if (value >= 0 && value <= 1 && fabsf(value - dst.pid.deadband.d_multiplier) > 0.0001f) {
dst.pid.deadband.d_multiplier = roundf(value, 3);
changed = true;
}
}
if (!src[FPSTR(S_PID)][FPSTR(S_DEADBAND)][FPSTR(S_THRESHOLD_HIGH)].isNull()) {
float value = src[FPSTR(S_PID)][FPSTR(S_DEADBAND)][FPSTR(S_THRESHOLD_HIGH)].as<float>();
if (value >= 0.0f && value <= 5.0f && fabsf(value - dst.pid.deadband.thresholdHigh) > 0.0001f) {
dst.pid.deadband.thresholdHigh = roundf(value, 2);
changed = true;
}
}
if (!src[FPSTR(S_PID)][FPSTR(S_DEADBAND)][FPSTR(S_THRESHOLD_LOW)].isNull()) {
float value = src[FPSTR(S_PID)][FPSTR(S_DEADBAND)][FPSTR(S_THRESHOLD_LOW)].as<float>();
if (value >= 0.0f && value <= 5.0f && fabsf(value - dst.pid.deadband.thresholdLow) > 0.0001f) {
dst.pid.deadband.thresholdLow = roundf(value, 2);
changed = true;
}
}
// heating
if (src[FPSTR(S_HEATING)][FPSTR(S_ENABLED)].is<bool>()) {
@@ -1079,7 +1324,7 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false
if (!src[FPSTR(S_HEATING)][FPSTR(S_MIN_TEMP)].isNull()) {
unsigned char value = src[FPSTR(S_HEATING)][FPSTR(S_MIN_TEMP)].as<unsigned char>();
if (value != dst.heating.minTemp && value >= vars.slave.heating.minTemp && value < vars.slave.heating.maxTemp && value != dst.heating.minTemp) {
if (value != dst.heating.minTemp && value >= vars.slave.heating.minTemp && value < vars.slave.heating.maxTemp && value != dst.heating.maxTemp) {
dst.heating.minTemp = value;
changed = true;
}
@@ -1088,7 +1333,7 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false
if (!src[FPSTR(S_HEATING)][FPSTR(S_MAX_TEMP)].isNull()) {
unsigned char value = src[FPSTR(S_HEATING)][FPSTR(S_MAX_TEMP)].as<unsigned char>();
if (value != dst.heating.maxTemp && value > vars.slave.heating.minTemp && value <= vars.slave.heating.maxTemp && value != dst.heating.maxTemp) {
if (value != dst.heating.maxTemp && value > vars.slave.heating.minTemp && value <= vars.slave.heating.maxTemp && value != dst.heating.minTemp) {
dst.heating.maxTemp = value;
changed = true;
}
@@ -1100,6 +1345,59 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false
}
if (!src[FPSTR(S_HEATING)][FPSTR(S_MAX_MODULATION)].isNull()) {
unsigned char value = src[FPSTR(S_HEATING)][FPSTR(S_MAX_MODULATION)].as<unsigned char>();
if (value > 0 && value <= 100 && value != dst.heating.maxModulation) {
dst.heating.maxModulation = value;
changed = true;
}
}
if (!src[FPSTR(S_HEATING)][FPSTR(S_OVERHEAT_PROTECTION)][FPSTR(S_HIGH_TEMP)].isNull()) {
unsigned char value = src[FPSTR(S_HEATING)][FPSTR(S_OVERHEAT_PROTECTION)][FPSTR(S_HIGH_TEMP)].as<unsigned char>();
if (isValidTemp(value, dst.system.unitSystem, 0.0f, 100.0f) && value != dst.heating.overheatProtection.highTemp) {
dst.heating.overheatProtection.highTemp = value;
changed = true;
}
}
if (!src[FPSTR(S_HEATING)][FPSTR(S_OVERHEAT_PROTECTION)][FPSTR(S_LOW_TEMP)].isNull()) {
unsigned char value = src[FPSTR(S_HEATING)][FPSTR(S_OVERHEAT_PROTECTION)][FPSTR(S_LOW_TEMP)].as<unsigned char>();
if (isValidTemp(value, dst.system.unitSystem, 0.0f, 99.0f) && value != dst.heating.overheatProtection.lowTemp) {
dst.heating.overheatProtection.lowTemp = value;
changed = true;
}
}
if (dst.heating.overheatProtection.highTemp < dst.heating.overheatProtection.lowTemp) {
dst.heating.overheatProtection.highTemp = dst.heating.overheatProtection.lowTemp;
changed = true;
}
if (!src[FPSTR(S_HEATING)][FPSTR(S_FREEZE_PROTECTION)][FPSTR(S_LOW_TEMP)].isNull()) {
unsigned short value = src[FPSTR(S_HEATING)][FPSTR(S_FREEZE_PROTECTION)][FPSTR(S_LOW_TEMP)].as<uint8_t>();
if (isValidTemp(value, dst.system.unitSystem, 1, 30) && value != dst.heating.freezeProtection.lowTemp) {
dst.heating.freezeProtection.lowTemp = value;
changed = true;
}
}
if (!src[FPSTR(S_HEATING)][FPSTR(S_FREEZE_PROTECTION)][FPSTR(S_THRESHOLD_TIME)].isNull()) {
unsigned short value = src[FPSTR(S_HEATING)][FPSTR(S_FREEZE_PROTECTION)][FPSTR(S_THRESHOLD_TIME)].as<unsigned short>();
if (value >= 30 && value <= 1800) {
if (value != dst.heating.freezeProtection.thresholdTime) {
dst.heating.freezeProtection.thresholdTime = value;
changed = true;
}
}
}
// dhw
if (src[FPSTR(S_DHW)][FPSTR(S_ENABLED)].is<bool>()) {
bool value = src[FPSTR(S_DHW)][FPSTR(S_ENABLED)].as<bool>();
@@ -1133,6 +1431,38 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false
changed = true;
}
if (!src[FPSTR(S_DHW)][FPSTR(S_MAX_MODULATION)].isNull()) {
unsigned char value = src[FPSTR(S_DHW)][FPSTR(S_MAX_MODULATION)].as<unsigned char>();
if (value > 0 && value <= 100 && value != dst.dhw.maxModulation) {
dst.dhw.maxModulation = value;
changed = true;
}
}
if (!src[FPSTR(S_DHW)][FPSTR(S_OVERHEAT_PROTECTION)][FPSTR(S_HIGH_TEMP)].isNull()) {
unsigned char value = src[FPSTR(S_DHW)][FPSTR(S_OVERHEAT_PROTECTION)][FPSTR(S_HIGH_TEMP)].as<unsigned char>();
if (isValidTemp(value, dst.system.unitSystem, 0.0f, 100.0f) && value != dst.dhw.overheatProtection.highTemp) {
dst.dhw.overheatProtection.highTemp = value;
changed = true;
}
}
if (!src[FPSTR(S_DHW)][FPSTR(S_OVERHEAT_PROTECTION)][FPSTR(S_LOW_TEMP)].isNull()) {
unsigned char value = src[FPSTR(S_DHW)][FPSTR(S_OVERHEAT_PROTECTION)][FPSTR(S_LOW_TEMP)].as<unsigned char>();
if (isValidTemp(value, dst.system.unitSystem, 0.0f, 99.0f) && value != dst.dhw.overheatProtection.lowTemp) {
dst.dhw.overheatProtection.lowTemp = value;
changed = true;
}
}
if (dst.dhw.overheatProtection.highTemp < dst.dhw.overheatProtection.lowTemp) {
dst.dhw.overheatProtection.highTemp = dst.dhw.overheatProtection.lowTemp;
changed = true;
}
if (!safe) {
// external pump
@@ -1326,7 +1656,7 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false
// force check emergency target
{
float value = !src[FPSTR(S_EMERGENCY)][FPSTR(S_TARGET)].isNull() ? src[FPSTR(S_EMERGENCY)][FPSTR(S_TARGET)].as<float>() : dst.emergency.target;
bool noRegulators = !dst.opentherm.nativeHeatingControl;
bool noRegulators = !dst.opentherm.options.nativeHeatingControl;
bool valid = isValidTemp(
value,
dst.system.unitSystem,
@@ -1351,7 +1681,7 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false
// force check heating target
{
bool indoorTempControl = dst.equitherm.enabled || dst.pid.enabled || dst.opentherm.nativeHeatingControl;
bool indoorTempControl = dst.equitherm.enabled || dst.pid.enabled || dst.opentherm.options.nativeHeatingControl;
float minTemp = indoorTempControl ? THERMOSTAT_INDOOR_MIN_TEMP : dst.heating.minTemp;
float maxTemp = indoorTempControl ? THERMOSTAT_INDOOR_MAX_TEMP : dst.heating.maxTemp;
@@ -1491,6 +1821,7 @@ bool jsonToSensorSettings(const uint8_t sensorId, const JsonVariantConst src, Se
case static_cast<uint8_t>(Sensors::Purpose::EXHAUST_TEMP):
case static_cast<uint8_t>(Sensors::Purpose::MODULATION_LEVEL):
case static_cast<uint8_t>(Sensors::Purpose::NUMBER):
case static_cast<uint8_t>(Sensors::Purpose::POWER_FACTOR):
case static_cast<uint8_t>(Sensors::Purpose::POWER):
case static_cast<uint8_t>(Sensors::Purpose::FAN_SPEED):
@@ -1535,6 +1866,15 @@ bool jsonToSensorSettings(const uint8_t sensorId, const JsonVariantConst src, Se
case static_cast<uint8_t>(Sensors::Type::OT_FAN_SPEED_SETPOINT):
case static_cast<uint8_t>(Sensors::Type::OT_FAN_SPEED_CURRENT):
case static_cast<uint8_t>(Sensors::Type::OT_BURNER_STARTS):
case static_cast<uint8_t>(Sensors::Type::OT_DHW_BURNER_STARTS):
case static_cast<uint8_t>(Sensors::Type::OT_HEATING_PUMP_STARTS):
case static_cast<uint8_t>(Sensors::Type::OT_DHW_PUMP_STARTS):
case static_cast<uint8_t>(Sensors::Type::OT_BURNER_HOURS):
case static_cast<uint8_t>(Sensors::Type::OT_DHW_BURNER_HOURS):
case static_cast<uint8_t>(Sensors::Type::OT_HEATING_PUMP_HOURS):
case static_cast<uint8_t>(Sensors::Type::OT_DHW_PUMP_HOURS):
case static_cast<uint8_t>(Sensors::Type::NTC_10K_TEMP):
case static_cast<uint8_t>(Sensors::Type::DALLAS_TEMP):
case static_cast<uint8_t>(Sensors::Type::BLUETOOTH):
@@ -1554,7 +1894,7 @@ bool jsonToSensorSettings(const uint8_t sensorId, const JsonVariantConst src, Se
// gpio
if (!src[FPSTR(S_GPIO)].isNull()) {
if (dst.type != Sensors::Type::DALLAS_TEMP && dst.type == Sensors::Type::BLUETOOTH && dst.type == Sensors::Type::NTC_10K_TEMP) {
if (dst.type != Sensors::Type::DALLAS_TEMP && dst.type != Sensors::Type::NTC_10K_TEMP) {
if (dst.gpio != GPIO_IS_NOT_CONFIGURED) {
dst.gpio = GPIO_IS_NOT_CONFIGURED;
changed = true;
@@ -1590,12 +1930,20 @@ bool jsonToSensorSettings(const uint8_t sensorId, const JsonVariantConst src, Se
);
if (parsed == 8) {
for (uint8_t i = 0; i < 8; i++) {
for (uint8_t i = 0; i < parsed; i++) {
if (dst.address[i] != tmp[i]) {
dst.address[i] = tmp[i];
changed = true;
}
}
} else {
// reset
for (uint8_t i = 0; i < sizeof(dst.address); i++) {
dst.address[i] = 0x00;
}
changed = true;
}
} else if (dst.type == Sensors::Type::BLUETOOTH) {
@@ -1608,12 +1956,20 @@ bool jsonToSensorSettings(const uint8_t sensorId, const JsonVariantConst src, Se
);
if (parsed == 6) {
for (uint8_t i = 0; i < 6; i++) {
for (uint8_t i = 0; i < parsed; i++) {
if (dst.address[i] != tmp[i]) {
dst.address[i] = tmp[i];
changed = true;
}
}
} else {
// reset
for (uint8_t i = 0; i < sizeof(dst.address); i++) {
dst.address[i] = 0x00;
}
changed = true;
}
}
}
@@ -1632,7 +1988,7 @@ bool jsonToSensorSettings(const uint8_t sensorId, const JsonVariantConst src, Se
if (!src[FPSTR(S_FACTOR)].isNull()) {
float value = src[FPSTR(S_FACTOR)].as<float>();
if (value > 0.09f && value <= 10.0f && fabsf(value - dst.factor) > 0.0001f) {
if (value > 0.09f && value <= 100.0f && fabsf(value - dst.factor) > 0.0001f) {
dst.factor = roundf(value, 3);
changed = true;
}
@@ -1721,6 +2077,10 @@ void varsToJson(const Variables& src, JsonVariant dst) {
slave[FPSTR(S_CONNECTED)] = src.slave.connected;
slave[FPSTR(S_FLAME)] = src.slave.flame;
auto sCooling = slave[FPSTR(S_COOLING)].to<JsonObject>();
sCooling[FPSTR(S_ACTIVE)] = src.slave.cooling.active;
sCooling[FPSTR(S_SETPOINT)] = src.slave.cooling.setpoint;
auto sModulation = slave[FPSTR(S_MODULATION)].to<JsonObject>();
sModulation[FPSTR(S_MIN)] = src.slave.modulation.min;
sModulation[FPSTR(S_MAX)] = src.slave.modulation.max;
@@ -1753,6 +2113,7 @@ void varsToJson(const Variables& src, JsonVariant dst) {
mHeating[FPSTR(S_ENABLED)] = src.master.heating.enabled;
mHeating[FPSTR(S_BLOCKING)] = src.master.heating.blocking;
mHeating[FPSTR(S_INDOOR_TEMP_CONTROL)] = src.master.heating.indoorTempControl;
mHeating[FPSTR(S_OVERHEAT)] = src.master.heating.overheat;
mHeating[FPSTR(S_SETPOINT_TEMP)] = roundf(src.master.heating.setpointTemp, 2);
mHeating[FPSTR(S_TARGET_TEMP)] = roundf(src.master.heating.targetTemp, 2);
mHeating[FPSTR(S_CURRENT_TEMP)] = roundf(src.master.heating.currentTemp, 2);
@@ -1764,6 +2125,7 @@ void varsToJson(const Variables& src, JsonVariant dst) {
auto mDhw = master[FPSTR(S_DHW)].to<JsonObject>();
mDhw[FPSTR(S_ENABLED)] = src.master.dhw.enabled;
mDhw[FPSTR(S_OVERHEAT)] = src.master.dhw.overheat;
mDhw[FPSTR(S_TARGET_TEMP)] = roundf(src.master.dhw.targetTemp, 2);
mDhw[FPSTR(S_CURRENT_TEMP)] = roundf(src.master.dhw.currentTemp, 2);
mDhw[FPSTR(S_RETURN_TEMP)] = roundf(src.master.dhw.returnTemp, 2);

508
src_data/locales/cn.json Normal file
View File

@@ -0,0 +1,508 @@
{
"values": {
"logo": "OpenTherm Gateway",
"nav": {
"license": "授权许可",
"source": "源代码",
"help": "帮助",
"issues": "问题与反馈",
"releases": "发行版"
},
"dbm": "dBm",
"kw": "kW",
"time": {
"days": "天",
"hours": "小时",
"min": "分",
"sec": "秒"
},
"button": {
"upgrade": "固件升级",
"restart": "重启",
"save": "保存",
"saved": "已保存",
"refresh": "刷新",
"restore": "恢复",
"restored": "已恢复",
"backup": "备份",
"wait": "请等待...",
"uploading": "上传中...",
"success": "成功",
"error": "错误"
},
"index": {
"title": "OpenTherm Gateway",
"section": {
"network": "网络",
"system": "系统"
},
"system": {
"build": {
"title": "Build",
"version": "固件版本",
"date": "日期",
"core": "内核版本",
"sdk": "SDK"
},
"uptime": "运行时间",
"memory": {
"title": "可用内存",
"maxFreeBlock": "max free block",
"min": "min"
},
"board": "开发板",
"chip": {
"model": "芯片",
"cores": "核心数",
"freq": "频率"
},
"flash": {
"size": "闪存容量",
"realSize": "实际容量"
},
"lastResetReason": "上次重置原因"
}
},
"dashboard": {
"name": "仪表盘",
"title": "仪表盘 - OpenTherm Gateway",
"section": {
"control": "调节",
"states": "状态",
"sensors": "传感器",
"diag": "OpenTherm 诊断"
},
"thermostat": {
"heating": "供暖",
"dhw": "生活热水",
"temp.current": "当前温度",
"enable": "启用",
"turbo": "Turbo 模式"
},
"notify": {
"fault": {
"title": "锅炉报警状态已激活!",
"note": "建议检查锅炉并查看说明书对应的报警代码:"
},
"diag": {
"title": "锅炉诊断状态已激活!",
"note": "锅炉可能需要检查,建议查看说明书对应的诊断代码:"
},
"reset": "点击复位"
},
"states": {
"mNetworkConnected": "网络连接状态",
"mMqttConnected": "MQTT服务器连接状态",
"mEmergencyState": "应急模式",
"mExtPumpState": "外置循环泵",
"mCascadeControlInput": "Cascade 控制 (input)",
"mCascadeControlOutput": "Cascade 控制 (output)",
"sConnected": "OpenTherm 通讯状态",
"sFlame": "火焰",
"sCoolingActive": "制冷",
"sCoolingSetpoint": "冷却设定点",
"sFaultActive": "报警状态",
"sFaultCode": "报警代码",
"sDiagActive": "诊断状态",
"sDiagCode": "诊断代码",
"mHeatEnabled": "供暖功能已启用",
"mHeatBlocking": "供暖",
"mHeatOverheat": "供暖超热保护",
"sHeatActive": "供暖激活状态",
"mHeatSetpointTemp": "供暖供水设定温度",
"mHeatTargetTemp": "供暖供水目标温度",
"mHeatCurrTemp": "供暖当前供水温度",
"mHeatRetTemp": "供暖回水温度",
"mHeatIndoorTemp": "供暖,室内温度",
"mHeatOutdoorTemp": "供暖,室外温度",
"mDhwEnabled": "生活热水功能已启用",
"mDhwOverheat": "生活热水超热保护",
"sDhwActive": "生活热水激活",
"mDhwTargetTemp": "生活热水目标温度",
"mDhwCurrTemp": "生活热水当前出水温度",
"mDhwRetTemp": "生活热水回水温度"
},
"sensors": {
"values": {
"temp": "温度",
"humidity": "湿度",
"battery": "电量",
"rssi": "RSSI"
}
}
},
"network": {
"title": "网络 - OpenTherm Gateway",
"name": "网络设置",
"section": {
"static": "静态设置",
"availableNetworks": "可用网络",
"staSettings": "WiFi 设置",
"apSettings": "AP 设置"
},
"scan": {
"pos": "#",
"info": "Info"
},
"wifi": {
"ssid": "SSID",
"password": "密码",
"channel": "频道",
"signal": "信号强度",
"connected": "已连接"
},
"params": {
"hostname": "Hostname",
"dhcp": "自动 (DHCP)",
"mac": "物理地址 (MAC)",
"ip": "IP",
"subnet": "子网掩码",
"gateway": "网关",
"dns": "DNS 服务器"
},
"sta": {
"channel.note": "自动选择设置为0"
}
},
"sensors": {
"title": "传感器设置 - OpenTherm Gateway",
"name": "传感器设置",
"enabled": "启用",
"sensorName": {
"title": "传感器名称",
"note": "只能包含a-z、A-Z、0-9、下划线和空格"
},
"purpose": "用途",
"purposes": {
"outdoorTemp": "室外温度",
"indoorTemp": "室内温度",
"heatTemp": "供暖,温度",
"heatRetTemp": "供暖回水温度",
"dhwTemp": "生活热水温度",
"dhwRetTemp": "生活热水回水温度",
"dhwFlowRate": "生活热水水流量",
"exhaustTemp": "烟气温度",
"modLevel": "Modulation level (%)",
"number": "Number (raw)",
"powerFactor": "功率 (%)",
"power": "功率(kW)",
"fanSpeed": "风机转速",
"co2": "CO2",
"pressure": "压力",
"humidity": "湿度",
"temperature": "温度",
"notConfigured": "未配置"
},
"type": "类型/来源",
"types": {
"otOutdoorTemp": "OpenTherm, outdoor temp",
"otHeatTemp": "OpenTherm, heating, temp",
"otHeatRetTemp": "OpenTherm, heating, return temp",
"otDhwTemp": "OpenTherm, DHW, temperature",
"otDhwTemp2": "OpenTherm, DHW, temperature 2",
"otDhwFlowRate": "OpenTherm, DHW, flow rate",
"otCh2Temp": "OpenTherm, channel 2, temp",
"otExhaustTemp": "OpenTherm, exhaust temp",
"otHeatExchangerTemp": "OpenTherm, heat exchanger temp",
"otPressure": "OpenTherm, pressure",
"otModLevel": "OpenTherm, modulation level",
"otCurrentPower": "OpenTherm, current power",
"otExhaustCo2": "OpenTherm, exhaust CO2",
"otExhaustFanSpeed": "OpenTherm, exhaust fan speed",
"otSupplyFanSpeed": "OpenTherm, supply fan speed",
"otSolarStorageTemp": "OpenTherm, solar storage temp",
"otSolarCollectorTemp": "OpenTherm, solar collector temp",
"otFanSpeedSetpoint": "OpenTherm, setpoint fan speed",
"otFanSpeedCurrent": "OpenTherm, current fan speed",
"otBurnerStarts": "OpenTherm, number of burner starts",
"otDhwBurnerStarts": "OpenTherm, number of burner starts (DHW)",
"otHeatingPumpStarts": "OpenTherm, number of pump starts (heating)",
"otDhwPumpStarts": "OpenTherm, number of pump starts (DHW)",
"otBurnerHours": "OpenTherm, number of burner operating hours",
"otDhwBurnerHours": "OpenTherm, number of burner operating hours (DHW)",
"otHeatingPumpHours": "OpenTherm, number of pump operating hours (heating)",
"otDhwPumpHours": "OpenTherm, number of pump operating hours (DHW)",
"ntcTemp": "NTC 传感器",
"dallasTemp": "DALLAS 传感器",
"bluetooth": "BLE 传感器",
"heatSetpointTemp": "Heating, setpoint temp",
"manual": "通过 MQTT/API 手动配置",
"notConfigured": "未配置"
},
"gpio": "GPIO",
"address": {
"title": "传感器地址",
"note": "如需自动检测DALLAS传感器请保持默认设置如需连接BLE设备则需提供MAC地址"
},
"correction": {
"desc": "数值校正",
"offset": "补偿值(偏移量)",
"factor": "Multiplier"
},
"filtering": {
"desc": "数值滤波",
"enabled": {
"title": "启用滤波",
"note": "如果图表中有大量尖锐的噪声,此功能会很有用。使用的滤波器是 \"滑动平均滤波器\"."
},
"factor": {
"title": "滤波系数",
"note": "数值越低,数值变化越平滑且响应越滞后"
}
}
},
"settings": {
"title": "设置 - OpenTherm Gateway",
"name": "设置",
"section": {
"portal": "Portal 设置",
"system": "系统设置",
"diag": "诊断",
"heating": "供热设置",
"dhw": "生活热水设置",
"emergency": "应急模式设置",
"equitherm": "气候补偿设置",
"pid": "PID 参数设置",
"ot": "OpenTherm协议设置",
"mqtt": "MQTT 服务器设置",
"extPump": "外置循环泵设置",
"cascadeControl": "Cascade 级联控制设置"
},
"enable": "启用",
"note": {
"restart": "更改这些设置后,必须重启设备以使变更生效",
"blankNotUse": "空白 - 未使用",
"bleDevice": "BLE设备仅支持搭载BLE功能的特定ESP32开发板使用!"
},
"temp": {
"min": "最低温度",
"max": "最高温度"
},
"maxModulation": "最大调制范围",
"ohProtection": {
"title": "超温保护",
"desc": "<b>注意:</b> 当锅炉内置过热保护失效或工作异常导致系统超温时,此功能可提供额外保护。如需禁用,请将<b>最高温度</b>和<b>最低温度</b>均设为0。",
"highTemp": {
"title": "高温阈值",
"note": "触发燃烧器强制关闭的阈值温度"
},
"lowTemp": {
"title": "低温阈值",
"note": "燃烧器重新启动的阈值温度"
}
},
"freezeProtection": {
"title": "防冻保护",
"desc": "当热媒或室内温度在<b>等待时间</b> 内降至<b>低温阈值</b>以下时,系统将强制启动加热功能。",
"lowTemp": "低温阈值",
"thresholdTime": "等待时间<small>(秒)</small>"
},
"portal": {
"login": "登录",
"password": "密码",
"auth": "需身份验证",
"mdns": "使用 mDNS"
},
"system": {
"unit": "单位",
"metric": "公制 <small>(摄氏度、升、巴)</small>",
"imperial": "英制 <small>(华氏度、加仑、psi)</small>",
"statusLedGpio": "状态指示灯GPIO引脚",
"logLevel": "日志级别",
"serial": {
"enable": "启用串口",
"baud": "串口波特率"
},
"telnet": {
"enable": "启用 Telnet",
"port": {
"title": "Telnet 端口",
"note": "默认值23"
}
},
"ntp": {
"server": "NTP服务器",
"timezone": "时区",
"timezonePresets": "选择预设配置..."
}
},
"heating": {
"hyst": "滞后值<small>(单位:度)</small>",
"turboFactor": "Turbo 模式系数"
},
"emergency": {
"desc": "紧急模式会在以下情况自动激活当PID或气候补偿无法计算热媒设定值时<br />启用气候补偿但室外温度传感器断开连接;<br />启用PID或 OpenTherm 选项中启用<i>原生供暖控制</i>但室内温度传感器断开连接。<br /><b>注意:</b> 网络故障或MQTT 服务器连接故障时,类型为<i>通过MQTT/API手动控制<i>的传感器将显示为断开连接状态。",
"target": {
"title": "目标温度",
"note": "<b>重要提示:</b> 若启用OpenTherm选项 <i>«原生供暖控制»</i>,此处设定值为<u>目标室内温度</u><br />其他所有情况下,此处设定值为 <u>目标热媒出水温度</u>."
},
"treshold": "阈值时间 <small>(秒)</small>"
},
"equitherm": {
"n": "N 系数",
"k": "K 系数",
"t": {
"title": "T 系数",
"note": "启用PID时此参数无效"
}
},
"pid": {
"p": "P 系数",
"i": "I 系数",
"d": "D 系数",
"dt": "DT <small>以秒为单位</small>",
"limits": {
"title": "Limits",
"note": "<b>重要提示:</b> When using «Equitherm» and «PID» at the same time, the min and max temperatures limit the influence on the «Equitherm» result temperature.<br />Thus, if the min temperature is set to -15 and the max temperature is set to 15, then the final heat carrier setpoint will be from <code>equitherm_result - 15</code> to <code>equitherm_result + 15</code>."
},
"deadband": {
"title": "Deadband",
"note": "Deadband is a range around the target temperature where PID regulation becomes less active. Within this range, the algorithm can reduce intensity or pause adjustments to avoid overreacting to small fluctuations.<br /><br />For instance, with a target temperature of 22°, a lower threshold of 1.0, and an upper threshold of 0.5, the deadband operates between 21° and 22.5°. If the I coefficient is 0.0005 and the I multiplier is 0.05, then within the deadband, the I coefficient becomes: <code>0.0005 * 0.05 = 0.000025</code>",
"p_multiplier": "Multiplier for P factor",
"i_multiplier": "Multiplier for I factor",
"d_multiplier": "Multiplier for D factor",
"thresholdHigh": "Threshold high",
"thresholdLow": "Threshold low"
}
},
"ot": {
"advanced": "高级设置",
"inGpio": "In GPIO",
"outGpio": "Out GPIO",
"ledGpio": "RX LED GPIO",
"memberId": "主设备成员 ID",
"flags": "主设备标志",
"minPower": {
"title": "最小锅炉功率 <small>(kW)</small>",
"note": "该值对应锅炉0-1%的调制水平,通常在锅炉参数设置中的\"最小有效热输出\"。"
},
"maxPower": {
"title": "最大锅炉功率 <small>(kW)</small>",
"note": "<b>0</b> - 自动检测,通常在锅炉参数设置中的\"最大有效热输出\"。 "
},
"options": {
"title": "选项(附加设置)",
"desc": "附加设置选项可调整锅炉的运行逻辑。由于协议未完整记录所有选项,同一选项在不同锅炉上可能产生不同效果。<br /><b>注意:</b>若系统运行正常,无需修改设置。",
"dhwSupport": "生活热水支持",
"coolingSupport": "制冷支持",
"summerWinterMode": "夏季/冬季模式",
"heatingStateToSummerWinterMode": "以供暖状态作为夏季/冬季模式",
"ch2AlwaysEnabled": "CH2 始终启用",
"heatingToCh2": "将供暖同步至 CH2",
"dhwToCh2": "将生活热水同步至 CH2",
"dhwBlocking": "生活热水锁定",
"dhwStateAsDhwBlocking": "以生活热水状态作为生活热水锁定依据",
"maxTempSyncWithTargetTemp": "将最高供暖温度与目标温度同步",
"getMinMaxTemp": "从锅炉获取最小/最大温度参数",
"ignoreDiagState": "忽略诊断状态",
"autoFaultReset": "自动报警复位 <small>(不推荐!)</small>",
"autoDiagReset": "自动诊断复位 <small>(不推荐!)</small>",
"setDateAndTime": "同步设置锅炉日期与时间",
"immergasFix": "针对Immergas锅炉的兼容性修复"
},
"nativeHeating": {
"title": "原生锅炉供暖控制",
"note": "<u>注意:</u> 仅适用于锅炉需接收目标室温并自主调节载热介质温度的场景与固件中的PID及Equithermq气候补偿功能不兼容。"
}
},
"mqtt": {
"homeAssistantDiscovery": "Home Assistant 自动发现",
"server": "服务器地址",
"port": "端口",
"user": "用户名",
"password": "密码",
"prefix": "Prefix 前缀",
"interval": "发布间隔 <small>(秒)</small>"
},
"extPump": {
"use": "使用外置循环泵",
"gpio": "继电器 GPIO引脚",
"postCirculationTime": "后循环时间 <small>(分钟)</small>",
"antiStuckInterval": "防卡死间隔时间<small>(天)</small>",
"antiStuckTime": "防卡死运行时长<small>(分钟)</small>"
},
"cascadeControl": {
"input": {
"desc": "仅当另一台锅炉发生故障时启用本锅炉加热。另一台锅炉的控制器需在故障发生时切换GPIO输入状态以触发本功能。",
"enable": "启用 input",
"gpio": "GPIO",
"invertState": "切换 GPIO 状态",
"thresholdTime": "状态变化阈值时间<small>(秒)</small>"
},
"output": {
"desc": "可通过<u>继电器</u>控制另一台锅炉的启停。",
"enable": "启用 output",
"gpio": "GPIO",
"invertState": "切换 GPIO 状态",
"thresholdTime": "状态变化阈值时间 <small>(秒)</small>",
"events": {
"desc": "事件",
"onFault": "当故障状态激活时",
"onLossConnection": "当OpenTherm连接中断时",
"onEnabledHeating": "当供暖功能启用时"
}
}
}
},
"upgrade": {
"title": "固件升级 - OpenTherm Gateway",
"name": "固件升级",
"section": {
"backupAndRestore": "备份与恢复",
"backupAndRestore.desc": "本功能支持备份和恢复全部设置",
"upgrade": "升级",
"upgrade.desc": "本模块支持升级设备的固件与系统文件。<br />可从以下地址下载最新版本 <a href=\"https://github.com/Laxilef/OTGateway/releases\" target=\"_blank\">Releases page</a> 。"
},
"note": {
"disclaimer1": "升级系统文件成功后,所有设置将恢复为默认值!升级前请务必备份配置。",
"disclaimer2": "升级成功后设备将在15秒后自动重启。"
},
"settingsFile": "设置文件",
"fw": "Firmware",
"fs": "Filesystem"
}
}
}

View File

@@ -87,6 +87,18 @@
"turbo": "Turbo mode"
},
"notify": {
"fault": {
"title": "Boiler Fault state is active!",
"note": "It is recommended to inspect the boiler and study the documentation to interpret the fault code:"
},
"diag": {
"title": "Boiler Diagnostic state is active!",
"note": "Perhaps your boiler needs inspection? It is recommended study the documentation to interpret the diag code:"
},
"reset": "Try reset"
},
"states": {
"mNetworkConnected": "Network connection",
"mMqttConnected": "MQTT connection",
@@ -97,13 +109,16 @@
"sConnected": "OpenTherm connection",
"sFlame": "Flame",
"sCoolingActive": "Cooling",
"sCoolingSetpoint": "Cooling setpoint",
"sFaultActive": "Fault",
"sFaultCode": "Faul code",
"sFaultCode": "Fault code",
"sDiagActive": "Diagnostic",
"sDiagCode": "Diagnostic code",
"mHeatEnabled": "Heating enabled",
"mHeatBlocking": "Heating blocked",
"mHeatOverheat": "Heating overheat",
"sHeatActive": "Heating active",
"mHeatSetpointTemp": "Heating setpoint temp",
"mHeatTargetTemp": "Heating target temp",
@@ -113,6 +128,7 @@
"mHeatOutdoorTemp": "Heating, outdoor temp",
"mDhwEnabled": "DHW enabled",
"mDhwOverheat": "DHW overheat",
"sDhwActive": "DHW active",
"mDhwTargetTemp": "DHW target temp",
"mDhwCurrTemp": "DHW current temp",
@@ -188,6 +204,7 @@
"dhwFlowRate": "DHW, flow rate",
"exhaustTemp": "Exhaust temperature",
"modLevel": "Modulation level (in percents)",
"number": "Number (raw)",
"powerFactor": "Power (in percent)",
"power": "Power (in kWt)",
"fanSpeed": "Fan speed",
@@ -218,6 +235,14 @@
"otSolarCollectorTemp": "OpenTherm, solar collector temp",
"otFanSpeedSetpoint": "OpenTherm, setpoint fan speed",
"otFanSpeedCurrent": "OpenTherm, current fan speed",
"otBurnerStarts": "OpenTherm, number of burner starts",
"otDhwBurnerStarts": "OpenTherm, number of burner starts (DHW)",
"otHeatingPumpStarts": "OpenTherm, number of pump starts (heating)",
"otDhwPumpStarts": "OpenTherm, number of pump starts (DHW)",
"otBurnerHours": "OpenTherm, number of burner operating hours",
"otDhwBurnerHours": "OpenTherm, number of burner operating hours (DHW)",
"otHeatingPumpHours": "OpenTherm, number of pump operating hours (heating)",
"otDhwPumpHours": "OpenTherm, number of pump operating hours (DHW)",
"ntcTemp": "NTC sensor",
"dallasTemp": "DALLAS sensor",
@@ -279,11 +304,31 @@
"min": "Minimum temperature",
"max": "Maximum temperature"
},
"maxModulation": "Max modulation level",
"ohProtection": {
"title": "Overheating protection",
"desc": "<b>Note:</b> This feature can be useful if the built-in boiler overheating protection does not work or does not work correctly and the heat carrier boils. To disable, set 0 as <b>high</b> and <b>low</b> temperature.",
"highTemp": {
"title": "High temperature threshold",
"note": "Threshold at which the burner will be forcibly switched off"
},
"lowTemp": {
"title": "Low temperature threshold",
"note": "Threshold at which the burner can be turned on again"
}
},
"freezeProtection": {
"title": "Freeze protection",
"desc": "Heating will be forced to turn on if the heat carrier or indoor temperature drops below <b>Low temperature</b> during <b>Waiting time</b>.",
"lowTemp": "Low temperature threshold",
"thresholdTime": "Waiting time <small>(sec)</small>"
},
"portal": {
"login": "Login",
"password": "Password",
"auth": "Require authentication"
"auth": "Require authentication",
"mdns": "Use mDNS"
},
"system": {
@@ -302,6 +347,11 @@
"title": "Telnet port",
"note": "Default: 23"
}
},
"ntp": {
"server": "NTP server",
"timezone": "Timezone",
"timezonePresets": "Select preset..."
}
},
@@ -334,7 +384,19 @@
"i": "I factor",
"d": "D factor",
"dt": "DT <small>in seconds</small>",
"noteMinMaxTemp": "<b>Important:</b> When using «Equitherm» and «PID» at the same time, the min and max temperatures limit the influence on the «Equitherm» result temperature.<br />Thus, if the min temperature is set to -15 and the max temperature is set to 15, then the final heat carrier setpoint will be from <code>equitherm_result - 15</code> to <code>equitherm_result + 15</code>."
"limits": {
"title": "Limits",
"note": "<b>Important:</b> When using «Equitherm» and «PID» at the same time, the min and max temperatures limit the influence on the «Equitherm» result temperature.<br />Thus, if the min temperature is set to -15 and the max temperature is set to 15, then the final heat carrier setpoint will be from <code>equitherm_result - 15</code> to <code>equitherm_result + 15</code>."
},
"deadband": {
"title": "Deadband",
"note": "Deadband is a range around the target temperature where PID regulation becomes less active. Within this range, the algorithm can reduce intensity or pause adjustments to avoid overreacting to small fluctuations.<br /><br />For instance, with a target temperature of 22°, a lower threshold of 1.0, and an upper threshold of 0.5, the deadband operates between 21° and 22.5°. If the I coefficient is 0.0005 and the I multiplier is 0.05, then within the deadband, the I coefficient becomes: <code>0.0005 * 0.05 = 0.000025</code>",
"p_multiplier": "Multiplier for P factor",
"i_multiplier": "Multiplier for I factor",
"d_multiplier": "Multiplier for D factor",
"thresholdHigh": "Threshold high",
"thresholdLow": "Threshold low"
}
},
"ot": {
@@ -344,7 +406,6 @@
"ledGpio": "RX LED GPIO",
"memberId": "Master member ID",
"flags": "Master flags",
"maxMod": "Max modulation level",
"minPower": {
"title": "Min boiler power <small>(kW)</small>",
"note": "This value is at 0-1% boiler modulation level. Typically found in the boiler specification as \"minimum useful heat output\"."
@@ -355,15 +416,23 @@
},
"options": {
"desc": "Options",
"dhwPresent": "DHW present",
"title": "Options (additional settings)",
"desc": "Options can change the logic of the boiler. Not all options are documented in the protocol, so the same option can have different effects on different boilers.<br /><b>Note:</b> There is no need to change anything if everything works well.",
"dhwSupport": "DHW support",
"coolingSupport": "Cooling support",
"summerWinterMode": "Summer/winter mode",
"heatingCh2Enabled": "Heating CH2 always enabled",
"heatingCh1ToCh2": "Duplicate heating CH1 to CH2",
"heatingStateToSummerWinterMode": "Heating state as summer/winter mode",
"ch2AlwaysEnabled": "CH2 always enabled",
"heatingToCh2": "Duplicate heating to CH2",
"dhwToCh2": "Duplicate DHW to CH2",
"dhwBlocking": "DHW blocking",
"modulationSyncWithHeating": "Sync modulation with heating",
"dhwStateAsDhwBlocking": "DHW state as DHW blocking",
"maxTempSyncWithTargetTemp": "Sync max heating temp with target temp",
"getMinMaxTemp": "Get min/max temp from boiler",
"ignoreDiagState": "Ignore diag state",
"autoFaultReset": "Auto fault reset <small>(not recommended!)</small>",
"autoDiagReset": "Auto diag reset <small>(not recommended!)</small>",
"setDateAndTime": "Set date & time on boiler",
"immergasFix": "Fix for Immergas boilers"
},

508
src_data/locales/it.json Normal file
View File

@@ -0,0 +1,508 @@
{
"values": {
"logo": "OpenTherm Gateway",
"nav": {
"license": "Licenza",
"source": "Codice",
"help": "Aiuto",
"issues": "Problemi e domande",
"releases": "Versione"
},
"dbm": "dBm",
"kw": "kW",
"time": {
"days": "d.",
"hours": "h.",
"min": "min.",
"sec": "sec."
},
"button": {
"upgrade": "Aggiorna",
"restart": "Riavvia",
"save": "Salva",
"saved": "Salvato",
"refresh": "Ricarica",
"restore": "Recupera",
"restored": "Recuperato",
"backup": "Backup",
"wait": "Attendi...",
"uploading": "caricamento...",
"success": "Riuscito",
"error": "Errore"
},
"index": {
"title": "OpenTherm Gateway",
"section": {
"network": "Rete",
"system": "Sistema"
},
"system": {
"build": {
"title": "Build",
"version": "Versione",
"date": "Data",
"core": "Core",
"sdk": "SDK"
},
"uptime": "Tempo di attività",
"memory": {
"title": "Memoria libera",
"maxFreeBlock": "max free block",
"min": "min"
},
"board": "Scheda",
"chip": {
"model": "Chip",
"cores": "Cores",
"freq": "frequenza"
},
"flash": {
"size": "Dimensioni del flash",
"realSize": "dimensione reale"
},
"lastResetReason": "Motivo ultimo Reset"
}
},
"dashboard": {
"name": "Pannello",
"title": "Pannello - OpenTherm Gateway",
"section": {
"control": "Controlli",
"states": "Stato",
"sensors": "Sensori",
"diag": "Diagnostica OpenTherm"
},
"thermostat": {
"heating": "Riscaldamento",
"dhw": "ACS",
"temp.current": "Attuale",
"enable": "Attiva",
"turbo": "Turbo"
},
"notify": {
"fault": {
"title": "Rilevamento guasti caldiaia attivo!",
"note": "Si consiglia di ispezionare la caldaia e studiare la documentazione per interpretare il codice di errore:"
},
"diag": {
"title": "Stato diagnostica Caldaia attivo!",
"note": "Forse la tua caldaia ha bisogno di un'ispezione? Si consiglia di studiare la documentazione per interpretare il codice diagnostico:"
},
"reset": "Prova a resettare"
},
"states": {
"mNetworkConnected": "Connessione Rete",
"mMqttConnected": "Connessione MQTT",
"mEmergencyState": "Modo Emergenza",
"mExtPumpState": "Pompa esterna",
"mCascadeControlInput": "Controllo a cascata (input)",
"mCascadeControlOutput": "Controllo a cascata (output)",
"sConnected": "Connessione OpenTherm",
"sFlame": "Fiamma",
"sCoolingActive": "Raffrescamento",
"sCoolingSetpoint": "Raffrescamento setpoint",
"sFaultActive": "Anomalia",
"sFaultCode": "Codice anomalia",
"sDiagActive": "Diagnostica",
"sDiagCode": "Codice Diagnostica",
"mHeatEnabled": "Riscaldamento attivato",
"mHeatBlocking": "Riscaldamento bloccato",
"mHeatOverheat": "Riscaldamento surriscaldamento",
"sHeatActive": "Riscaldamento attivo",
"mHeatSetpointTemp": "Temp riscaldamento impostato",
"mHeatTargetTemp": "Target Temp caldaia",
"mHeatCurrTemp": "Temp attuale riscaldamento",
"mHeatRetTemp": "Temp ritorno riscaldamento",
"mHeatIndoorTemp": "Riscaldamento, temp interna",
"mHeatOutdoorTemp": "Riscaldamento, temp esterna",
"mDhwEnabled": "ACS attivata",
"mDhwOverheat": "ACS surriscaldamento",
"sDhwActive": "ACS attiva",
"mDhwTargetTemp": "ACS temp impostata",
"mDhwCurrTemp": "ACS temp attuale",
"mDhwRetTemp": "ACS temp ricircolo"
},
"sensors": {
"values": {
"temp": "Temperatura",
"humidity": "Umidità",
"battery": "Batteria",
"rssi": "RSSI"
}
}
},
"network": {
"title": "Rete - OpenTherm Gateway",
"name": "Impostazioni rete",
"section": {
"static": "Impostazioni statico",
"availableNetworks": "Reti disponibili",
"staSettings": "Impostazioni WiFi",
"apSettings": "Impostazioni AP"
},
"scan": {
"pos": "#",
"info": "Info"
},
"wifi": {
"ssid": "SSID",
"password": "Password",
"channel": "Canale",
"signal": "Segnale",
"connected": "Connesso"
},
"params": {
"hostname": "Hostname",
"dhcp": "Usa DHCP",
"mac": "MAC",
"ip": "IP",
"subnet": "Subnet",
"gateway": "Gateway",
"dns": "DNS"
},
"sta": {
"channel.note": "Metti 0 per auto selezione"
}
},
"sensors": {
"title": "Impostazione sensori - OpenTherm Gateway",
"name": "Impostazione sensori",
"enabled": "Attivato",
"sensorName": {
"title": "Nome sensore",
"note": "Può contenere solo: a-z, A-Z, 0-9, _ e spazi"
},
"purpose": "Funzione",
"purposes": {
"outdoorTemp": "Temperatura esterna",
"indoorTemp": "Temperatura interna",
"heatTemp": "Riscaldamento, temperatura mandata",
"heatRetTemp": "Riscaldamento, temperatura ritorno",
"dhwTemp": "ACS, temperatura",
"dhwRetTemp": "ACS, temperatura ritorno",
"dhwFlowRate": "ACS, prelievo",
"exhaustTemp": "Temperatura fumi",
"modLevel": "Livello Modulazione (%)",
"number": "Numero (raw)",
"powerFactor": "Potenza (%)",
"power": "Potenza (in kW)",
"fanSpeed": "Velocità ventilatore",
"co2": "CO2",
"pressure": "Pressione",
"humidity": "Umidità",
"temperature": "Temperatura",
"notConfigured": "Non configurato"
},
"type": "Tipo/sorgente",
"types": {
"otOutdoorTemp": "OpenTherm, temp esterna",
"otHeatTemp": "OpenTherm, riscaldamento, temp",
"otHeatRetTemp": "OpenTherm, riscaldamento, temp ritorno",
"otDhwTemp": "OpenTherm, ACS, temperatura",
"otDhwTemp2": "OpenTherm, ACS, temperatura 2",
"otDhwFlowRate": "OpenTherm, ACS, prelievo",
"otCh2Temp": "OpenTherm, canale 2, temp",
"otExhaustTemp": "OpenTherm, temp fumi",
"otHeatExchangerTemp": "OpenTherm, temp scambiatore",
"otPressure": "OpenTherm, pressione",
"otModLevel": "OpenTherm, livello modulazione",
"otCurrentPower": "OpenTherm, potenza attuale",
"otExhaustCo2": "OpenTherm, CO2 fumi",
"otExhaustFanSpeed": "OpenTherm, velocità ventola fumi",
"otSupplyFanSpeed": "OpenTherm, velocità ventola supporto",
"otSolarStorageTemp": "OpenTherm, temp accumulo solare",
"otSolarCollectorTemp": "OpenTherm, temp collettore solare",
"otFanSpeedSetpoint": "OpenTherm, velocità ventola impostata",
"otFanSpeedCurrent": "OpenTherm, velocità ventola attuale",
"otBurnerStarts": "OpenTherm, numero di avviamenti del bruciatore",
"otDhwBurnerStarts": "OpenTherm, numero di avviamenti del bruciatore (ACS)",
"otHeatingPumpStarts": "OpenTherm, numero di avviamenti della pompa (riscaldamento)",
"otDhwPumpStarts": "OpenTherm, numero di avviamenti della pompa (ACS)",
"otBurnerHours": "OpenTherm, numero di ore di funzionamento del bruciatore",
"otDhwBurnerHours": "OpenTherm, numero di ore di funzionamento del bruciatore (ACS)",
"otHeatingPumpHours": "OpenTherm, numero di ore di funzionamento della pompa (riscaldamento)",
"otDhwPumpHours": "OpenTherm, numero di ore di funzionamento della pompa (ACS)",
"ntcTemp": "Sensore NTC",
"dallasTemp": "Sensore DALLAS",
"bluetooth": "Sensore BLE",
"heatSetpointTemp": "Riscaldamento, temp impostata",
"manual": "Manuale via MQTT/API",
"notConfigured": "Non configurato"
},
"gpio": "GPIO",
"address": {
"title": "Indirizzo sensore",
"note": "Per l'autoriconoscimento del sensore DALLAS lasciare quello di default, per sensore BLE richiede indirizzo MAC"
},
"correction": {
"desc": "Correzione del valore",
"offset": "Compensazione (offset)",
"factor": "Moltiplicatore"
},
"filtering": {
"desc": "Filtraggio valore",
"enabled": {
"title": "Filtraggio attivo",
"note": "Può servire in caso vi siano molti sbalzi nel grafico. Il filtro usato è \"Running Average\"."
},
"factor": {
"title": "Fattore di filtrazione",
"note": "Quanto più basso è il valore, tanto più graduale e prolungata sarà la variazione dei valori numerici."
}
}
},
"settings": {
"title": "Impostazioni - OpenTherm Gateway",
"name": "Impostazioni",
"section": {
"portal": "Impostazioni Accesso",
"system": "Impostazioni sistema",
"diag": "Diagnostica",
"heating": "Impostazioni riscaldamento",
"dhw": "Impostazioni ACS",
"emergency": "Impostazioni modo Emergenza",
"equitherm": "Impostazioni Equitherm",
"pid": "Impostazioni PID",
"ot": "Impostazioni OpenTherm",
"mqtt": "Impostazioni MQTT",
"extPump": "Impostazioni pompa esterna",
"cascadeControl": "Impostazioni controllo a cascata"
},
"enable": "Attiva",
"note": {
"restart": "Dopo aver cambiato queste impostazioni, il sistema sarà riavviato perchè i cambiamenti abbiano effetto.",
"blankNotUse": "vuoto - non usare",
"bleDevice": "Dispositivi BLE possono essere usati <u>solo</u> con alcune schede ESP32 che supportano il bluetooth!"
},
"temp": {
"min": "Temperatura minima",
"max": "Temperatura massima"
},
"maxModulation": "Max livello modulazione",
"ohProtection": {
"title": "Protezione contro il surriscaldamento",
"desc": "<b>Nota:</b> questa funzione può essere utile se la protezione contro il surriscaldamento integrata nella caldaia non funziona o non funziona correttamente e il fluido termovettore bolle. Per disattivarla, impostare 0 come temperatura <b>alta</b> e <b>bassa</b>.",
"highTemp": {
"title": "Soglia di temperatura alta",
"note": "Soglia alla quale il bruciatore verrà spento forzatamente"
},
"lowTemp": {
"title": "Soglia di temperatura bassa",
"note": "Soglia alla quale il bruciatore può essere riacceso"
}
},
"freezeProtection": {
"title": "Protezione antigelo",
"desc": "Il riscaldamento verrà attivato forzatamente se la temperatura del vettore di calore o interna scende al di sotto della <b>temperatura minima</b> durante il <b>tempo di attesa</b>.",
"lowTemp": "Soglia di temperatura minima",
"thresholdTime": "Tempo di attesa <small>(sec)</small>"
},
"portal": {
"login": "Login",
"password": "Password",
"auth": "Richiede autenticazione",
"mdns": "Usa mDNS"
},
"system": {
"unit": "Unità di misura",
"metric": "Metrico <small>(celsius, litri, bar)</small>",
"imperial": "Imperiale <small>(fahrenheit, galloni, psi)</small>",
"statusLedGpio": "LED di stato GPIO",
"logLevel": "Log livello",
"serial": {
"enable": "Porta seriale attivata",
"baud": "Porta seriale baud rate"
},
"telnet": {
"enable": "Telnet attivato",
"port": {
"title": "Porta Telnet",
"note": "Default: 23"
}
},
"ntp": {
"server": "NTP server",
"timezone": "Zona oraria",
"timezonePresets": "Seleziona preimpostato..."
}
},
"heating": {
"hyst": "Isteresi <small>(in gradi)</small>",
"turboFactor": "Turbo mode coeff."
},
"emergency": {
"desc": "Il modo emergenza è attivato automaticamente quando «PID» o «Equitherm» non possono calcolare il setpoint:<br />- se «Equitherm» è attivato e il sensore della temperatura esternare è disconnesso;<br />- se «PID» o l'opzione OT <i>«Impostazioni riscaldamento native»</i> è attiva e il sensore di temperatura interno è disconnesso.<br /><b>Nota:</b> In mancanza di rete o MQTT, sensore di tipo <i>«Manuale via MQTT/API»</i> è in stato Disconnesso.",
"target": {
"title": "Temperatura impostata",
"note": "<b>Importante:</b> <u>Temperatura interna impostata</u> se l'opzione OT <i>«Controllo riscaldamento interno»</i> è attivato.<br />In tutti gli altri casi, la <u>target heat carrier temperature</u>."
},
"treshold": "Tempo di soglia <small>(sec)</small>"
},
"equitherm": {
"n": "Fattore N",
"k": "Fattore K",
"t": {
"title": "Fattore T",
"note": "Non usato se PID è attivato"
}
},
"pid": {
"p": "Fattore P",
"i": "Fattore I",
"d": "Fattore D",
"dt": "DT <small>in secondi</small>",
"limits": {
"title": "Limiti",
"note": "<b>Importante:</b> Quando usi «Equitherm» e «PID» allo stesso tempo, i limiti della temperatura min e max influenzano il risultato della temperatura «Equitherm».<br />Thus, se la temperatura minima è impostata a -15 e la massima a 15, il riscaldamento finale sarà impostato fra <code>equitherm_result - 15</code> a <code>equitherm_result + 15</code>."
},
"deadband": {
"title": "Zona morta (Deadband)",
"note": "La zona morta è un intervallo intorno alla temperatura target in cui la regolazione PID diventa meno attiva. In questo intervallo, l'algoritmo può ridurre l'intensità o interrompere gli aggiustamenti per evitare di reagire eccessivamente a piccole fluttuazioni.<br /><br />Ad esempio, con una temperatura target di 22°, una soglia inferiore di 1.0 e una soglia superiore di 0.5, la zona morta opera tra 21° e 22.5°. Se il coefficiente I è 0.0005 e il moltiplicatore I è 0.05, allora nella zona morta, il coefficiente I diventa: <code>0.0005 * 0.05 = 0.000025</code>",
"p_multiplier": "Moltiplicatore P",
"i_multiplier": "Moltiplicatore I",
"d_multiplier": "Moltiplicatore D",
"thresholdHigh": "Soglia superiore",
"thresholdLow": "Soglia inferiore"
}
},
"ot": {
"advanced": "Impostazioni avanzate",
"inGpio": "In GPIO",
"outGpio": "Out GPIO",
"ledGpio": "RX LED GPIO",
"memberId": "Master member ID",
"flags": "Master flags",
"minPower": {
"title": "Potenza minima caldaia <small>(kW)</small>",
"note": "Questo valore corrisponde allo livello 0-1% di modulazione della caldaia. Di solito si trova nelle specifiche delle caldaia come \"potenza minima disponibile\"."
},
"maxPower": {
"title": "Potenza massima caldaia <small>(kW)</small>",
"note": "<b>0</b> - prova a rilevarla automaticamente. Di solito si trova nelle specifiche delle caldaia come \"potenza massima disponibile\"."
},
"options": {
"title": "Opzioni (impostazioni aggiuntive)",
"desc": "Le opzioni possono modificare la logica della caldaia. Non tutte le opzioni sono documentate nel protocollo, quindi la stessa opzione può avere effetti diversi su caldaie diverse.<br /><b>Nota:</b> Non è necessario modificare nulla se tutto funziona correttamente.",
"dhwSupport": "Supporto ACS",
"coolingSupport": "Supporto rafferscamento",
"summerWinterMode": "Modalità Estate/inverno",
"heatingStateToSummerWinterMode": "Stato di riscaldamento come modalità estate/inverno",
"ch2AlwaysEnabled": "CH2 sempre abilitato",
"heatingToCh2": "Riproduci riscaldamento su CH2",
"dhwToCh2": "Riproduci ACS su CH2",
"dhwBlocking": "Bloccare ACS",
"dhwStateAsDhwBlocking": "Stato ACS come bloccare ACS",
"maxTempSyncWithTargetTemp": "Sincronizza la temperatura massima di riscaldamento con la temperatura target",
"getMinMaxTemp": "Prendi temp min/max dalla caldaia",
"ignoreDiagState": "Ignora lo stato diagnostico",
"autoFaultReset": "Ripristino automatico degli errori <small>(sconsigliato!)</small>",
"autoDiagReset": "Ripristino diagnostico automatica <small>(sconsigliato!)</small>",
"setDateAndTime": "Imposta data e ora sulla caldaia",
"immergasFix": "Fix per caldiaie Immergas"
},
"nativeHeating": {
"title": "Controllo del riscaldamento nativo (caldaia)",
"note": "Lavora <u>SOLO</u> se la caldaia richiede la temperatura ambiente desiderata e regola autonomamente la temperatura del fluido. Non compatiblile con regolazioni PID e Equitherm del sistema."
}
},
"mqtt": {
"homeAssistantDiscovery": "Home Assistant Discovery",
"server": "Server",
"port": "Porta",
"user": "Utente",
"password": "Password",
"prefix": "Prefisso",
"interval": "Intervallo invio <small>(sec)</small>"
},
"extPump": {
"use": "Usa pompa/circolatore esterno",
"gpio": "GPIO relè",
"postCirculationTime": "Tempo di post circolazione <small>(min)</small>",
"antiStuckInterval": "Intervallo antiblocco <small>(days)</small>",
"antiStuckTime": "Tempo antiblocco <small>(min)</small>"
},
"cascadeControl": {
"input": {
"desc": "Può essere attivata la caldaia se un'altra ha fallito. Il controllo dell'altra caldaia cambia lo stato dell'ingresso del GPIO in caso di errore.",
"enable": "Ingresso abilitato",
"gpio": "GPIO",
"invertState": "Inverti stato GPIO",
"thresholdTime": "Tempo soglia di modifica dello stato <small>(sec)</small>"
},
"output": {
"desc": "Può essere usato per passare ad un'altra caldaia tramite <u>relè</u>.",
"enable": "Uscita abilitata",
"gpio": "GPIO",
"invertState": "Invert GPIO state",
"thresholdTime": "Tempo soglia di modifica dello stato <small>(sec)</small>",
"events": {
"desc": "Eventi",
"onFault": "Se lo stato di errore è attivo",
"onLossConnection": "Se non c'è la connessione via Opentherm",
"onEnabledHeating": "Se il riscaldamento è attivato"
}
}
}
},
"upgrade": {
"title": "Aggiornamenti - OpenTherm Gateway",
"name": "Aggiornamenti",
"section": {
"backupAndRestore": "Backup & restore",
"backupAndRestore.desc": "In questa sezione puoi salvare e recuperare un backup di tutte le impostazioni.",
"upgrade": "Aggiorna",
"upgrade.desc": "In questa sezione puoi aggiornare il firmware il filesystem del tuo dispositivo.<br />L'ultimo aggiornamento può essere scaricato da <a href=\"https://github.com/Laxilef/OTGateway/releases\" target=\"_blank\">Releases page</a> del progetto."
},
"note": {
"disclaimer1": "Dopo un aggiornamento riuscito del filesystem, tutte le impostazioni sono impostate di default! Salva un backup prima di aggiornare.",
"disclaimer2": "Dopo un aggiornamento riuscito, il sistema viene automaticamente riavviato dopo 15 secondi."
},
"settingsFile": "Settings file",
"fw": "Firmware",
"fs": "Filesystem"
}
}
}

465
src_data/locales/nl.json Normal file
View File

@@ -0,0 +1,465 @@
{
"values": {
"logo": "OpenTherm Gateway",
"nav": {
"license": "Licentie",
"source": "Broncode",
"help": "Help",
"issues": "Problemen & vragen",
"releases": "Releases"
},
"dbm": "dBm",
"kw": "kW",
"time": {
"days": "d.",
"hours": "u.",
"min": "min.",
"sec": "sec."
},
"button": {
"upgrade": "Upgraden",
"restart": "Herstarten",
"save": "Opslaan",
"saved": "Opgeslagen",
"refresh": "Vernieuwen",
"restore": "Herstellen",
"restored": "Hersteld",
"backup": "Back-up",
"wait": "Even wachten...",
"uploading": "Uploaden...",
"success": "Succes",
"error": "Fout"
},
"index": {
"title": "OpenTherm Gateway",
"section": {
"network": "Netwerk",
"system": "Systeem"
},
"system": {
"build": {
"title": "Build",
"version": "Versie",
"date": "Datum",
"core": "Core",
"sdk": "SDK"
},
"uptime": "Uptime",
"memory": {
"title": "Vrij geheugen",
"maxFreeBlock": "max. vrij blok",
"min": "min"
},
"board": "Board",
"chip": {
"model": "Chip",
"cores": "Kernen",
"freq": "frequentie"
},
"flash": {
"size": "Flash-grootte",
"realSize": "werkelijke grootte"
},
"lastResetReason": "Reden laatste herstart"
}
},
"dashboard": {
"name": "Dashboard",
"title": "Dashboard - OpenTherm Gateway",
"section": {
"control": "Bediening",
"states": "Statussen",
"sensors": "Sensoren",
"diag": "OpenTherm diagnose"
},
"thermostat": {
"heating": "Verwarming",
"dhw": "Warm water",
"temp.current": "Huidig",
"enable": "Inschakelen",
"turbo": "Turbomodus"
},
"notify": {
"fault": {
"title": "Ketelstoring is actief!",
"note": "Het wordt aanbevolen de ketel te inspecteren en de documentatie te raadplegen om de storingscode te interpreteren:"
},
"diag": {
"title": "Keteldiagnose is actief!",
"note": "Heeft uw ketel misschien een inspectie nodig? Het wordt aanbevolen de documentatie te raadplegen om de diagnosecode te interpreteren:"
},
"reset": "Probeer te resetten"
},
"states": {
"mNetworkConnected": "Netwerkverbinding",
"mMqttConnected": "MQTT-verbinding",
"mEmergencyState": "Noodmodus",
"mExtPumpState": "Externe pomp",
"mCascadeControlInput": "Cascaderegeling (ingang)",
"mCascadeControlOutput": "Cascaderegeling (uitgang)",
"sConnected": "OpenTherm-verbinding",
"sFlame": "Vlam",
"sCoolingActive": "Koeling",
"sCoolingSetpoint": "Koelinstelpunt",
"sFaultActive": "Storing",
"sFaultCode": "Storingscode",
"sDiagActive": "Diagnose",
"sDiagCode": "Diagnosecode",
"mHeatEnabled": "Verwarming ingeschakeld",
"mHeatBlocking": "Verwarming geblokkeerd",
"mHeatOverheat": "Verwarming oververhit",
"sHeatActive": "Verwarming actief",
"mHeatSetpointTemp": "Insteltemperatuur verwarming",
"mHeatTargetTemp": "Doeltemperatuur verwarming",
"mHeatCurrTemp": "Huidige temperatuur verwarming",
"mHeatRetTemp": "Retourtemperatuur verwarming",
"mHeatIndoorTemp": "Verwarming, binnentemperatuur",
"mHeatOutdoorTemp": "Verwarming, buitentemperatuur",
"mDhwEnabled": "Warm water ingeschakeld",
"mDhwOverheat": "Warm water oververhit",
"sDhwActive": "Warm water actief",
"mDhwTargetTemp": "Doeltemperatuur warm water",
"mDhwCurrTemp": "Huidige temperatuur warm water",
"mDhwRetTemp": "Retourtemperatuur warm water"
},
"sensors": {
"values": {
"temp": "Temperatuur",
"humidity": "Luchtvochtigheid",
"battery": "Batterij",
"rssi": "RSSI"
}
}
},
"network": {
"title": "Netwerk - OpenTherm Gateway",
"name": "Netwerkinstellingen",
"section": {
"static": "Statische instellingen",
"availableNetworks": "Beschikbare netwerken",
"staSettings": "WiFi-instellingen",
"apSettings": "AP-instellingen"
},
"scan": {
"pos": "#",
"info": "Info"
},
"wifi": {
"ssid": "SSID",
"password": "Wachtwoord",
"channel": "Kanaal",
"signal": "Signaal",
"connected": "Verbonden"
},
"params": {
"hostname": "Hostnaam",
"dhcp": "Gebruik DHCP",
"mac": "MAC",
"ip": "IP",
"subnet": "Subnet",
"gateway": "Gateway",
"dns": "DNS"
},
"sta": {
"channel.note": "zet op 0 voor automatische selectie"
}
},
"sensors": {
"title": "Sensorinstellingen - OpenTherm Gateway",
"name": "Sensorinstellingen",
"enabled": "Ingeschakeld",
"sensorName": {
"title": "Sensornaam",
"note": "Mag alleen bevatten: a-z, A-Z, 0-9, _ en spatie"
},
"purpose": "Doel",
"purposes": {
"outdoorTemp": "Buitentemperatuur",
"indoorTemp": "Binnentemperatuur",
"heatTemp": "Verwarming, temperatuur",
"heatRetTemp": "Verwarming, retourtemperatuur",
"dhwTemp": "Warm water, temperatuur",
"dhwRetTemp": "Warm water, retourtemperatuur",
"dhwFlowRate": "Warm water, doorstroomsnelheid",
"exhaustTemp": "Rookgastemperatuur",
"modLevel": "Modulatieniveau (in procenten)",
"number": "Getal (ruw)",
"powerFactor": "Vermogen (in procent)",
"power": "Vermogen (in kWt)",
"fanSpeed": "Ventilatorsnelheid",
"co2": "CO2",
"pressure": "Druk",
"humidity": "Luchtvochtigheid",
"temperature": "Temperatuur",
"notConfigured": "Niet geconfigureerd"
},
"type": "Type/bron",
"types": {
"otOutdoorTemp": "OpenTherm, buitentemp.",
"otHeatTemp": "OpenTherm, verwarming, temp.",
"otHeatRetTemp": "OpenTherm, verwarming, retourtemp.",
"otDhwTemp": "OpenTherm, warm water, temperatuur",
"otDhwTemp2": "OpenTherm, warm water, temperatuur 2",
"otDhwFlowRate": "OpenTherm, warm water, doorstroomsnelheid",
"otCh2Temp": "OpenTherm, kanaal 2, temp.",
"otExhaustTemp": "OpenTherm, rookgastemp.",
"otHeatExchangerTemp": "OpenTherm, warmtewisselaar temp.",
"otPressure": "OpenTherm, druk",
"otModLevel": "OpenTherm, modulatieniveau",
"otCurrentPower": "OpenTherm, huidig vermogen",
"otExhaustCo2": "OpenTherm, rookgas CO2",
"otExhaustFanSpeed": "OpenTherm, rookgasventilator snelheid",
"otSupplyFanSpeed": "OpenTherm, toevoerventilator snelheid",
"otSolarStorageTemp": "OpenTherm, zonneboiler opslagtemp.",
"otSolarCollectorTemp": "OpenTherm, zonnecollector temp.",
"otFanSpeedSetpoint": "OpenTherm, instelpunt ventilatorsnelheid",
"otFanSpeedCurrent": "OpenTherm, huidige ventilatorsnelheid",
"otBurnerStarts": "OpenTherm, aantal branderstarts",
"otDhwBurnerStarts": "OpenTherm, aantal branderstarts (warm water)",
"otHeatingPumpStarts": "OpenTherm, aantal pompstarts (verwarming)",
"otDhwPumpStarts": "OpenTherm, aantal pompstarts (warm water)",
"otBurnerHours": "OpenTherm, aantal branderuren",
"otDhwBurnerHours": "OpenTherm, aantal branderuren (warm water)",
"otHeatingPumpHours": "OpenTherm, aantal pompuren (verwarming)",
"otDhwPumpHours": "OpenTherm, aantal pompuren (warm water)",
"ntcTemp": "NTC-sensor",
"dallasTemp": "DALLAS-sensor",
"bluetooth": "BLE-sensor",
"heatSetpointTemp": "Verwarming, insteltemperatuur",
"manual": "Handmatig via MQTT/API",
"notConfigured": "Niet geconfigureerd"
},
"gpio": "GPIO",
"address": {
"title": "Sensoradres",
"note": "Laat leeg voor automatische detectie van DALLAS-sensoren. Voor BLE-apparaten is een MAC-adres vereist."
},
"correction": {
"desc": "Correctie van waarden",
"offset": "Compensatie (offset)",
"factor": "Vermenigvuldiger"
},
"filtering": {
"desc": "Filteren van waarden",
"enabled": {
"title": "Filteren ingeschakeld",
"note": "Kan handig zijn bij veel scherpe ruis in de grafieken. Het gebruikte filter is \"Voortschrijdend gemiddelde\"."
},
"factor": {
"title": "Filterfactor",
"note": "Hoe lager de waarde, hoe vloeiender en <u>langer</u> de verandering in numerieke waarden."
}
}
},
"settings": {
"title": "Instellingen - OpenTherm Gateway",
"name": "Instellingen",
"section": {
"portal": "Portaalinstellingen",
"system": "Systeeminstellingen",
"diag": "Diagnose",
"heating": "Verwarmingsinstellingen",
"dhw": "Warmwaterinstellingen",
"emergency": "Instellingen noodmodus",
"equitherm": "Equitherm-instellingen",
"pid": "PID-instellingen",
"ot": "OpenTherm-instellingen",
"mqtt": "MQTT-instellingen",
"extPump": "Instellingen externe pomp",
"cascadeControl": "Instellingen cascaderegeling"
},
"enable": "Inschakelen",
"note": {
"restart": "Na het wijzigen van deze instellingen moet het apparaat opnieuw worden opgestart om de wijzigingen door te voeren.",
"blankNotUse": "leeg - niet gebruiken",
"bleDevice": "BLE-apparaat kan <u>alleen</u> worden gebruikt met sommige ESP32-boards met BLE-ondersteuning!"
},
"temp": {
"min": "Minimumtemperatuur",
"max": "Maximumtemperatuur"
},
"maxModulation": "Max. modulatieniveau",
"ohProtection": {
"title": "Oververhittingsbeveiliging",
"desc": "<b>Let op:</b> Deze functie kan handig zijn als de ingebouwde oververhittingsbeveiliging van de ketel niet of niet correct werkt en de warmtedrager kookt. Om uit te schakelen, stel 0 in als <b>hoge</b> en <b>lage</b> temperatuur.",
"highTemp": {
"title": "Drempelwaarde hoge temperatuur",
"note": "Drempelwaarde waarbij de brander geforceerd wordt uitgeschakeld"
},
"lowTemp": {
"title": "Drempelwaarde lage temperatuur",
"note": "Drempelwaarde waarbij de brander weer ingeschakeld kan worden"
}
},
"freezeProtection": {
"title": "Vorstbeveiliging",
"desc": "De verwarming wordt geforceerd ingeschakeld als de temperatuur van de warmtedrager of de binnentemperatuur onder de <b>Lage temperatuur</b> daalt gedurende de <b>Wachttijd</b>.",
"lowTemp": "Drempelwaarde lage temperatuur",
"thresholdTime": "Wachttijd <small>(sec)</small>"
},
"portal": {
"login": "Gebruikersnaam",
"password": "Wachtwoord",
"auth": "Authenticatie vereisen",
"mdns": "Gebruik mDNS"
},
"system": {
"unit": "Eenheidssysteem",
"metric": "Metrisch <small>(celsius, liters, bar)</small>",
"imperial": "Imperiaal <small>(fahrenheit, gallons, psi)</small>",
"statusLedGpio": "Status LED GPIO",
"logLevel": "Logniveau",
"serial": {
"enable": "Seriële poort ingeschakeld",
"baud": "Baudrate seriële poort"
},
"telnet": {
"enable": "Telnet ingeschakeld",
"port": {
"title": "Telnet-poort",
"note": "Standaard: 23"
}
},
"ntp": {
"server": "NTP-server",
"timezone": "Tijdzone",
"timezonePresets": "Selecteer voorinstelling..."
}
},
"heating": {
"hyst": "Hysterese <small>(in graden)</small>",
"turboFactor": "Turbomodus coëff."
},
"emergency": {
"desc": "Noodmodus wordt automatisch geactiveerd wanneer «PID» of «Equitherm» het instelpunt van de warmtedrager niet kan berekenen:<br />- als «Equitherm» is ingeschakeld en de buitentemperatuursensor is losgekoppeld;<br />- als «PID» of OT-optie <i>«Natuurlijke verwarmingsregeling»</i> is ingeschakeld en de binnentemperatuursensor is losgekoppeld.<br /><b>Let op:</b> Bij een netwerk- of MQTT-storing krijgen sensoren van het type <i>«Handmatig via MQTT/API»</i> de status ONVERBONDEN.",
"target": {
"title": "Doeltemperatuur",
"note": "<b>Belangrijk:</b> <u>Doel binnentemperatuur</u> als OT-optie <i>«Natuurlijke verwarmingsregeling»</i> is ingeschakeld.<br />In alle andere gevallen, de <u>doel warmtedragertemperatuur</u>."
},
"treshold": "Drempeltijd <small>(sec)</small>"
},
"equitherm": {
"n": "N-factor",
"k": "K-factor",
"t": {
"title": "T-factor",
"note": "Niet gebruikt als PID is ingeschakeld"
}
},
"pid": {
"p": "P-factor",
"i": "I-factor",
"d": "D-factor",
"dt": "DT <small>in seconden</small>",
"limits": {
"title": "Limieten",
"note": "<b>Belangrijk:</b> Bij gelijktijdig gebruik van «Equitherm» en «PID» beperken de min- en maxtemperaturen de invloed op de resulterende «Equitherm»-temperatuur.<br />Dus, als de min-temperatuur is ingesteld op -15 en de max-temperatuur op 15, zal het uiteindelijke instelpunt van de warmtedrager liggen tussen <code>equitherm_resultaat - 15</code> en <code>equitherm_resultaat + 15</code>."
},
"deadband": {
"title": "Deadband",
"note": "Deadband is een bereik rond de doeltemperatuur waarbinnen de PID-regeling minder actief wordt. Binnen dit bereik kan het algoritme de intensiteit verminderen of aanpassingen pauzeren om overreactie op kleine schommelingen te voorkomen.<br /><br />Bijvoorbeeld, met een doeltemperatuur van 22°, een onderdrempel van 1.0 en een bovendrempel van 0.5, werkt de deadband tussen 21° en 22.5°. Als de I-coëfficiënt 0.0005 is en de I-vermenigvuldiger 0.05, dan wordt de I-coëfficiënt binnen de deadband: <code>0.0005 * 0.05 = 0.000025</code>",
"p_multiplier": "Vermenigvuldiger voor P-factor",
"i_multiplier": "Vermenigvuldiger voor I-factor",
"d_multiplier": "Vermenigvuldiger voor D-factor",
"thresholdHigh": "Bovendrempel",
"thresholdLow": "Onderdrempel"
}
},
"ot": {
"advanced": "Geavanceerde instellingen",
"inGpio": "In GPIO",
"outGpio": "Uit GPIO",
"ledGpio": "RX LED GPIO",
"memberId": "Master member ID",
"flags": "Master flags",
"minPower": {
"title": "Min. ketelvermogen <small>(kW)</small>",
"note": "Deze waarde is bij 0-1% modulatieniveau van de ketel. Typisch te vinden in de ketelspecificatie als \"minimum nuttig warmtevermogen\"."
},
"maxPower": {
"title": "Max. ketelvermogen <small>(kW)</small>",
"note": "<b>0</b> - probeer automatisch te detecteren. Typisch te vinden in de ketelspecificatie als \"maximum nuttig warmtevermogen\"."
},
"options": {
"title": "Opties (aanvullende instellingen)",
"desc": "Opties kunnen de logica van de ketel veranderen. Niet alle opties zijn gedocumenteerd in het protocol, dus dezelfde optie kan verschillende effecten hebben op verschillende ketels.<br /><b>Let op:</b> Het is niet nodig om iets te veranderen als alles goed werkt.",
"dhwSupport": "Warm water ondersteuning",
"coolingSupport": "Koeling ondersteuning",
"summerWinterMode": "Zomer/wintermodus",
"heatingStateToSummerWinterMode": "Verwarmingsstatus als zomer/wintermodus",
"ch2AlwaysEnabled": "CH2 altijd ingeschakeld",
"heatingToCh2": "Dupliceer verwarming naar CH2",
"dhwToCh2": "Dupliceer warm water naar CH2",
"dhwBlocking": "Blokkering warm water",
"dhwStateAsDhwBlocking": "Status warm water als blokkering warm water",
"maxTempSyncWithTargetTemp": "Synchroniseer max. verwarmingstemp. met doeltemp.",
"getMinMaxTemp": "Haal min/max temp. op van ketel",
"ignoreDiagState": "Negeer diagnosestatus",
"autoFaultReset": "Automatische storingsreset <small>(niet aanbevolen!)</small>",
"autoDiagReset": "Automatische diagnosereset <small>(niet aanbevolen!)</small>",
"setDateAndTime": "Stel datum & tijd in op ketel",
"immergasFix": "Fix voor Immergas-ketels"
},
"nativeHeating": {
"title": "Natuurlijke verwarmingsregeling (ketel)",
"note": "Werkt <u>ALLEEN</u> als de ketel de gewenste kamertemperatuur vereist en zelf de temperatuur van de warmtedrager regelt. Niet compatibel met PID- en Equitherm-regelaars in de firmware."
}
},
"mqtt": {
"homeAssistantDiscovery": "Home Assistant Discovery",
"server": "Server",
"port": "Poort",
"user": "Gebruiker",
"password": "Wachtwoord",
"prefix": "Prefix",
"interval": "Publicatie-interval <small>(sec)</small>"
},
"extPump": {
"use": "Gebruik externe pomp",
"gpio": "Relais GPIO",
"postCirculationTime": "Nacirculatietijd <small>(min)</small>",
"antiStuckInterval": "Anti-vastloopinterval <small>(dagen)</small>",
"antiStuckTime": "Anti-vastlooptijd <small>(min)</small>"
},
"cascadeControl": {
"input": {
"desc": "Kan worden gebruikt om de verwarming alleen in te schakelen als een andere ketel defect is. De besturing van de andere ketel moet de status van de GPIO-ingang wijzigen in geval van een storing.",
"enable": "Ingang ingeschakeld",
"gpio": "GPIO",
"invertState": "Inverteer GPIO-status",
"thresholdTime": "Drempeltijd statuswijziging <small>(sec)</small>"
},
"output": {
"desc": "Kan worden gebruikt om een andere ketel in te schakelen <u>via een relais</u>.",
"enable": "Uitgang ingeschakeld",
"gpio": "GPIO",
"invertState": "Inverteer GPIO-status",
"thresholdTime": "Drempeltijd statuswijziging <small>(sec)</small>",
"events": {
"desc": "Gebeurtenissen",
"onFault": "Als de storingsstatus actief is",
"onLossConnection": "Als de verbinding via Opentherm is verbroken",
"onEnabledHeating": "Als de verwarming is ingeschakeld"
}
}
}
},
"upgrade": {
"title": "Upgrade - OpenTherm Gateway",
"name": "Upgrade",
"section": {
"backupAndRestore": "Back-up & herstel",
"backupAndRestore.desc": "In deze sectie kunt u een back-up van ALLE instellingen opslaan en herstellen.",
"upgrade": "Upgrade",
"upgrade.desc": "In deze sectie kunt u de firmware en het bestandssysteem van uw apparaat upgraden.<br />De nieuwste releases kunnen worden gedownload van de <a href=\"https://github.com/Laxilef/OTGateway/releases\" target=\"_blank\">Releases-pagina</a> van de projectrepository."
},
"note": {
"disclaimer1": "Na een succesvolle upgrade van het bestandssysteem worden ALLE instellingen teruggezet naar de standaardwaarden! Sla een back-up op voordat u gaat upgraden.",
"disclaimer2": "Na een succesvolle upgrade zal het apparaat automatisch herstarten na 15 seconden."
},
"settingsFile": "Instellingenbestand",
"fw": "Firmware",
"fs": "Bestandssysteem"
}
}
}

View File

@@ -87,6 +87,18 @@
"turbo": "Турбо"
},
"notify": {
"fault": {
"title": "Состояние неисправности котла активно!",
"note": "Рекомендуется осмотреть котел и изучить документацию котла для расшифровки кода неисправности:"
},
"diag": {
"title": "Состояние диагностики котла активно!",
"note": "Возможно, вашему котлу требуется проверка? Рекомендуется изучить документацию котла для расшифровки кода диагностики:"
},
"reset": "Сбросить"
},
"states": {
"mNetworkConnected": "Подключение к сети",
"mMqttConnected": "Подключение к MQTT",
@@ -97,6 +109,8 @@
"sConnected": "Подключение к OpenTherm",
"sFlame": "Пламя",
"sCoolingActive": "Охлаждение",
"sCoolingSetpoint": "Охлаждение, уставка",
"sFaultActive": "Ошибка",
"sFaultCode": "Код ошибки",
"sDiagActive": "Диагностика",
@@ -104,6 +118,7 @@
"mHeatEnabled": "Отопление",
"mHeatBlocking": "Блокировка отопления",
"mHeatOverheat": "Отопление, перегрев",
"sHeatActive": "Активность отопления",
"mHeatSetpointTemp": "Отопление, уставка",
"mHeatTargetTemp": "Отопление, целевая температура",
@@ -113,6 +128,7 @@
"mHeatOutdoorTemp": "Отопление, наружная темп.",
"mDhwEnabled": "ГВС",
"mDhwOverheat": "ГВС, перегрев",
"sDhwActive": "Активность ГВС",
"mDhwTargetTemp": "ГВС, целевая температура",
"mDhwCurrTemp": "ГВС, текущая температура",
@@ -180,7 +196,7 @@
"purpose": "Назначение",
"purposes": {
"outdoorTemp": "Внешняя температура",
"indoorTemp": "Внутреняя температура",
"indoorTemp": "Внутренняя температура",
"heatTemp": "Отопление, температура",
"heatRetTemp": "Отопление, температура обратки",
"dhwTemp": "ГВС, температура",
@@ -188,6 +204,7 @@
"dhwFlowRate": "ГВС, расход/скорость потока",
"exhaustTemp": "Температура выхлопных газов",
"modLevel": "Уровень модуляции (в процентах)",
"number": "Число (raw)",
"powerFactor": "Мощность (в процентах)",
"power": "Мощность (в кВт)",
"fanSpeed": "Скорость вентилятора",
@@ -218,6 +235,14 @@
"otSolarCollectorTemp": "OpenTherm, темп. солн. коллектора",
"otFanSpeedSetpoint": "OpenTherm, установленная мощн. вентилятора",
"otFanSpeedCurrent": "OpenTherm, текущая мощн. вентилятора",
"otBurnerStarts": "OpenTherm, кол-во запусков горелки",
"otDhwBurnerStarts": "OpenTherm, кол-во запусков горелки (ГВС)",
"otHeatingPumpStarts": "OpenTherm, кол-во запусков насоса (отопление)",
"otDhwPumpStarts": "OpenTherm, кол-во запусков насоса (ГВС)",
"otBurnerHours": "OpenTherm, кол-во часов работы горелки",
"otDhwBurnerHours": "OpenTherm, кол-во часов работы горелки (ГВС)",
"otHeatingPumpHours": "OpenTherm, кол-во часов работы насоса (отопление)",
"otDhwPumpHours": "OpenTherm, кол-во часов работы насоса (ГВС)",
"ntcTemp": "NTC датчик",
"dallasTemp": "DALLAS датчик",
@@ -279,16 +304,36 @@
"min": "Мин. температура",
"max": "Макс. температура"
},
"maxModulation": "Макс. уровень модуляции",
"ohProtection": {
"title": "Защита от перегрева",
"desc": "<b>Примечание:</b> Эта функция может быть полезна, если встроенная защита от перегрева котла не срабатывает или срабатывает некорректно и теплоноситель закипает. Для отключения установите 0 в качестве <b>верхнего</b> и <b>нижнего</b> порога температуры.",
"highTemp": {
"title": "Верхний порог температуры",
"note": "Порог, при котором горелка будет принудительно отключена"
},
"lowTemp": {
"title": "Нижний порог температуры",
"note": "Порог, при котором горелка может быть включена снова"
}
},
"freezeProtection": {
"title": "Защита от замерзания",
"desc": "Отопление будет принудительно включено, если темп. теплоносителя или внутренняя темп. опустится ниже <b>нижнего порога</b> в течение <b>времени ожидания</b>.",
"lowTemp": "Нижний порог температуры",
"thresholdTime": "Время ожидания <small>(сек)</small>"
},
"portal": {
"login": "Логин",
"password": "Пароль",
"auth": "Требовать аутентификацию"
"auth": "Требовать аутентификацию",
"mdns": "Использовать mDNS"
},
"system": {
"unit": "Система единиц",
"metric": "Метрическая <small>(цильсии, литры, бары)</small>",
"metric": "Метрическая <small>(цельсии, литры, бары)</small>",
"imperial": "Imperial <small>(фаренгейты, галлоны, psi)</small>",
"statusLedGpio": "Статус LED GPIO",
"logLevel": "Уровень логирования",
@@ -302,6 +347,11 @@
"title": "Telnet порт",
"note": "По умолчанию: 23"
}
},
"ntp": {
"server": "NTP сервер",
"timezone": "Часовой пояс",
"timezonePresets": "Выберите пресет..."
}
},
@@ -334,7 +384,19 @@
"i": "Коэффициент I",
"d": "Коэффициент D",
"dt": "DT <small>(сек)</small>",
"noteMinMaxTemp": "<b>Важно:</b> При использовании «ПЗА» и «ПИД» одновременно, мин. и макс. температура ограничивает влияние на расчётную температуру «ПЗА».<br />Таким образом, если мин. температура задана как -15, а макс. как 15, то конечная температура теплоносителя будет от <code>equitherm_result - 15</code> до <code>equitherm_result + 15</code>."
"limits": {
"title": "Лимиты",
"note": "<b>Важно:</b> При использовании «ПЗА» и «ПИД» одновременно, мин. и макс. температура ограничивает влияние на расчётную температуру «ПЗА».<br />Таким образом, если мин. температура задана как -15, а макс. как 15, то конечная температура теплоносителя будет от <code>equitherm_result - 15</code> до <code>equitherm_result + 15</code>."
},
"deadband": {
"title": "Зона нечувствительности (Deadband)",
"note": "Deadband - это зона нечувствительности вокруг целевой температуры, в которой PID-регулирование становится менее активным. В этом диапазоне алгоритм может снижать интенсивность или полностью прекращать корректировку температуры, чтобы избежать излишней чувствительности к небольшим колебаниям.<br /><br />Например, при целевой температуре 22°, нижнем пороге 1.0 и верхнем 0.5, deadband активен в диапазоне от 21° до 22.5°. Если коэфф. I=0.0005, а множитель I=0.05, то при включении зоны нечувствительности коэфф. I будет равен: <code>0.0005 * 0.05 = 0.000025</code>",
"p_multiplier": "Множитель для коэф. P",
"i_multiplier": "Множитель для коэф. I",
"d_multiplier": "Множитель для коэф. D",
"thresholdHigh": "Верхний порог",
"thresholdLow": "Нижний порог"
}
},
"ot": {
@@ -344,7 +406,6 @@
"ledGpio": "RX LED GPIO",
"memberId": "Master member ID",
"flags": "Master flags",
"maxMod": "Макс. уровень модуляции",
"minPower": {
"title": "Мин. мощность котла <small>(кВт)</small>",
"note": "Это значение соответствует уровню модуляции котла 01%. Обычно можно найти в спецификации котла как \"минимальная полезная тепловая мощность\"."
@@ -355,15 +416,23 @@
},
"options": {
"desc": "Опции",
"dhwPresent": "Контур ГВС",
"title": "Опции (дополнительные настройки)",
"desc": "Опции могут менять логику работы котла. Не все опции задокументированы в протоколе, поэтому одна и та же опция может иметь разный эффект на разных котлах.<br /><b>Примечание:</b> Нет необходимости что-то менять, если всё работает хорошо.",
"dhwSupport": "Поддержка ГВС",
"coolingSupport": "Поддержка охлаждения",
"summerWinterMode": "Летний/зимний режим",
"heatingCh2Enabled": "Канал 2 отопления всегда вкл.",
"heatingCh1ToCh2": "Дублировать параметры отопления канала 1 в канал 2",
"heatingStateToSummerWinterMode": "Летний/зимний режим в качестве состояния отопления",
"ch2AlwaysEnabled": "Канал 2 всегда вкл.",
"heatingToCh2": "Дублировать параметры отопления в канал 2",
"dhwToCh2": "Дублировать параметры ГВС в канал 2",
"dhwBlocking": "DHW blocking",
"modulationSyncWithHeating": "Синхронизировать модуляцию с отоплением",
"dhwStateAsDhwBlocking": "DHW blocking в качестве состояния ГВС",
"maxTempSyncWithTargetTemp": "Синхронизировать макс. темп. отопления с целевой темп.",
"getMinMaxTemp": "Получать мин. и макс. температуру от котла",
"ignoreDiagState": "Игнорировать состояние диагностики",
"autoFaultReset": "Автоматический сброс ошибок <small>(не рекомендуется!)</small>",
"autoDiagReset": "Автоматический сброс диагностики <small>(не рекомендуется!)</small>",
"setDateAndTime": "Устанавливать время и дату на котле",
"immergasFix": "Фикс для котлов Immergas"
},

View File

@@ -21,6 +21,9 @@
<li>
<select id="lang" aria-label="Lang">
<option value="en" selected>EN</option>
<option value="cn">CN</option>
<option value="it">IT</option>
<option value="nl">NL</option>
<option value="ru">RU</option>
</select>
</li>
@@ -40,14 +43,18 @@
<details open>
<summary><b data-i18n>dashboard.section.control</b></summary>
<div class="grid">
<div class="thermostat" id="thermostat-heating">
<div class="thermostat tHeat" data-purpose="heating" data-min="0" data-max="100" data-step="0.1" data-big-step="1">
<div class="thermostat-header" data-i18n>dashboard.thermostat.heating</div>
<div class="thermostat-temp">
<div class="thermostat-temp-target"><span id="tHeatTargetTemp"></span> <span class="tempUnit"></span></div>
<div class="thermostat-temp-target"><span class="targetTemp"></span> <span class="tempUnit"></span></div>
<div class="thermostat-temp-current"><span data-i18n>dashboard.thermostat.temp.current</span>: <span id="tHeatCurrentTemp"></span> <span class="tempUnit"></span></div>
</div>
<div class="thermostat-minus"><button id="tHeatActionMinus" class="outline"><i class="icons-down"></i></button></div>
<div class="thermostat-plus"><button id="tHeatActionPlus" class="outline"><i class="icons-up"></i></button></div>
<div class="thermostat-minus">
<button class="tAction outline" data-action="decrement"><i class="icons-down"></i></button>
</div>
<div class="thermostat-plus">
<button class="tAction outline" data-action="increment"><i class="icons-up"></i></button>
</div>
<div class="thermostat-control">
<input type="checkbox" role="switch" id="tHeatEnabled" value="true">
<label htmlFor="tHeatEnabled" data-i18n>dashboard.thermostat.enable</label>
@@ -57,20 +64,54 @@
</div>
</div>
<div class="thermostat" id="thermostat-dhw">
<div class="thermostat tDhw" data-purpose="dhw" data-min="0" data-max="100" data-step="1" data-big-step="5">
<div class="thermostat-header" data-i18n>dashboard.thermostat.dhw</div>
<div class="thermostat-temp">
<div class="thermostat-temp-target"><span id="tDhwTargetTemp"></span> <span class="tempUnit"></span></div>
<div class="thermostat-temp-target"><span class="targetTemp"></span> <span class="tempUnit"></span></div>
<div class="thermostat-temp-current"><span data-i18n>dashboard.thermostat.temp.current</span>: <span id="tDhwCurrentTemp"></span> <span class="tempUnit"></span></div>
</div>
<div class="thermostat-minus"><button class="outline" id="tDhwActionMinus"><i class="icons-down"></i></button></div>
<div class="thermostat-plus"><button class="outline" id="tDhwActionPlus"><i class="icons-up"></i></button></div>
<div class="thermostat-minus">
<button class="tAction outline" data-action="decrement"><i class="icons-down"></i></button>
</div>
<div class="thermostat-plus">
<button class="tAction outline" data-action="increment"><i class="icons-up"></i></button>
</div>
<div class="thermostat-control">
<input type="checkbox" role="switch" id="tDhwEnabled" value="true">
<label htmlFor="tDhwEnabled" data-i18n>dashboard.thermostat.enable</label>
</div>
</div>
</div>
<div class="notify notify-error notify-fault hidden">
<div class="notify-icon">
<i class="icons-error"></i>
</div>
<div class="notify-content">
<b data-i18n>dashboard.notify.fault.title</b><br />
<small>
<span data-i18n>dashboard.notify.fault.note</span> <b class="sFaultCode"></b>
</small>
</div>
<div class="notify-actions">
<button class="reset" data-i18n>dashboard.notify.reset</button>
</div>
</div>
<div class="notify notify-alarm notify-diag hidden">
<div class="notify-icon">
<i class="icons-alarm"></i>
</div>
<div class="notify-content">
<b data-i18n>dashboard.notify.diag.title</b><br />
<small>
<span data-i18n>dashboard.notify.diag.note</span> <b class="sDiagCode"></b>
</small>
</div>
<div class="notify-actions">
<button class="reset" data-i18n>dashboard.notify.reset</button>
</div>
</div>
</details>
<hr />
@@ -114,6 +155,15 @@
<td><i class="sFlame"></i></td>
</tr>
<tr>
<th scope="row" data-i18n>dashboard.states.sCoolingActive</th>
<td><i class="sCoolingActive"></i></td>
</tr>
<tr>
<th scope="row" data-i18n>dashboard.states.sCoolingSetpoint</th>
<td><b class="sCoolingSetpoint"></b> %</td>
</tr>
<tr>
<th scope="row" data-i18n>dashboard.states.sFaultActive</th>
@@ -141,6 +191,10 @@
<th scope="row" data-i18n>dashboard.states.mHeatBlocking</th>
<td><i class="mHeatBlocking"></i></td>
</tr>
<tr>
<th scope="row" data-i18n>dashboard.states.mHeatOverheat</th>
<td><i class="mHeatOverheat"></i></td>
</tr>
<tr>
<th scope="row" data-i18n>dashboard.states.sHeatActive</th>
<td><i class="sHeatActive"></i></td>
@@ -175,6 +229,10 @@
<th scope="row" data-i18n>dashboard.states.mDhwEnabled</th>
<td><i class="mDhwEnabled"></i></td>
</tr>
<tr>
<th scope="row" data-i18n>dashboard.states.mDhwOverheat</th>
<td><i class="mDhwOverheat"></i></td>
</tr>
<tr>
<th scope="row" data-i18n>dashboard.states.sDhwActive</th>
<td><i class="sDhwActive"></i></td>
@@ -218,16 +276,16 @@
<details>
<summary><b data-i18n>dashboard.section.diag</b></summary>
<pre><b>Vendor:</b> <span class="sVendor"></span>
<b>Member ID:</b> <span class="sMemberId"></span>
<b>Flags:</b> <span class="sFlags"></span>
<b>Type:</b> <span class="sType"></span>
<b>AppVersion:</b> <span class="sAppVersion"></span>
<b>OT version:</b> <span class="sProtocolVersion"></span>
<b>Modulation limits:</b> <span class="sModMin"></span>...<span class="sModMax"></span> %
<b>Power limits:</b> <span class="sPowerMin"></span>...<span class="sPowerMax"></span> kW
<b>Heating limits:</b> <span class="sHeatMinTemp"></span>...<span class="sHeatMaxTemp"></span> <span class="tempUnit"></span>
<b>DHW limits:</b> <span class="sDhwMinTemp"></span>...<span class="sDhwMaxTemp"></span> <span class="tempUnit"></span></pre>
<pre><b>Vendor:</b> <span class="sVendor"></span>
<b>Member ID:</b> <span class="sMemberId"></span>
<b>Flags:</b> <span class="sFlags"></span>
<b>Type:</b> <span class="sType"></span>
<b>AppVersion:</b> <span class="sAppVersion"></span>
<b>OT version:</b> <span class="sProtocolVersion"></span>
<b>Modulation:</b> min: <span class="sModMin"></span> %, curr. max: <span class="sModMax"></span> %
<b>Power limits:</b> <span class="sPowerMin"></span>...<span class="sPowerMax"></span> kW
<b>Heating limits:</b> <span class="sHeatMinTemp"></span>...<span class="sHeatMaxTemp"></span> <span class="tempUnit"></span>
<b>DHW limits:</b> <span class="sDhwMinTemp"></span>...<span class="sDhwMaxTemp"></span> <span class="tempUnit"></span></pre>
</details>
</div>
</article>
@@ -247,7 +305,6 @@
<script src="/static/app.js?{BUILD_TIME}"></script>
<script>
let modifiedTime = null;
let noRegulators;
let prevSettings;
let newSettings = {
heating: {
@@ -265,78 +322,69 @@
const lang = new Lang(document.getElementById('lang'));
lang.build();
document.querySelector('#tHeatActionMinus').addEventListener('click', (event) => {
if (!prevSettings) {
return;
let actionTimer = null;
let actionLongPress = false;
document.querySelectorAll('.tAction').forEach((item) => {
const action = item.dataset.action;
const tContainer = item.parentNode.parentNode;
for (const eName of ['pointerup', 'pointercancel']) {
item.addEventListener(eName, (event) => {
clearInterval(actionTimer);
const purpose = tContainer.dataset.purpose;
const minTemp = parseFloat(tContainer.dataset.min);
const maxTemp = parseFloat(tContainer.dataset.max);
const step = parseFloat(tContainer.dataset.step);
const bigStep = parseFloat(tContainer.dataset.bigStep);
if (!actionLongPress && prevSettings) {
let value = 0;
if (action == 'increment') {
value = step;
} else if (action == 'decrement') {
value = -(step);
}
newSettings[purpose].target = parseFloat(constrain(newSettings[purpose].target + value, minTemp, maxTemp).toFixed(2));
modifiedTime = Date.now();
setValue('.targetTemp', newSettings[purpose].target, tContainer);
}
});
}
newSettings.heating.target -= 0.5;
modifiedTime = Date.now();
item.addEventListener('pointerdown', (event) => {
if (!prevSettings) {
return;
}
let minTemp;
if (noRegulators) {
minTemp = prevSettings.heating.minTemp;
} else {
minTemp = prevSettings.system.unitSystem == 0 ? 5 : 41;
}
const purpose = tContainer.dataset.purpose;
const minTemp = parseFloat(tContainer.dataset.min);
const maxTemp = parseFloat(tContainer.dataset.max);
const step = parseFloat(tContainer.dataset.step);
const bigStep = parseFloat(tContainer.dataset.bigStep);
if (prevSettings && newSettings.heating.target < minTemp) {
newSettings.heating.target = minTemp;
}
actionLongPress = false;
actionTimer = setInterval(() => {
if (!actionLongPress) {
actionLongPress = true;
}
setValue('#tHeatTargetTemp', newSettings.heating.target);
});
let value = 0;
if (action == 'increment') {
value = bigStep;
document.querySelector('#tHeatActionPlus').addEventListener('click', (event) => {
if (!prevSettings) {
return;
}
} else if (action == 'decrement') {
value = -(bigStep);
}
newSettings.heating.target += 0.5;
modifiedTime = Date.now();
newSettings[purpose].target = parseFloat(constrain(newSettings[purpose].target + value, minTemp, maxTemp).toFixed(2));
modifiedTime = Date.now();
let maxTemp;
if (noRegulators) {
maxTemp = prevSettings.heating.maxTemp;
} else {
maxTemp = prevSettings.system.unitSystem == 0 ? 30 : 86;
}
if (prevSettings && newSettings.heating.target > maxTemp) {
newSettings.heating.target = maxTemp;
}
setValue('#tHeatTargetTemp', newSettings.heating.target);
});
document.querySelector('#tDhwActionMinus').addEventListener('click', (event) => {
if (!prevSettings) {
return;
}
newSettings.dhw.target -= 1.0;
modifiedTime = Date.now();
if (newSettings.dhw.target < prevSettings.dhw.minTemp) {
newSettings.dhw.target = prevSettings.dhw.minTemp;
}
setValue('#tDhwTargetTemp', newSettings.dhw.target);
});
document.querySelector('#tDhwActionPlus').addEventListener('click', (event) => {
if (!prevSettings) {
return;
}
newSettings.dhw.target += 1.0;
modifiedTime = Date.now();
if (newSettings.dhw.target > prevSettings.dhw.maxTemp) {
newSettings.dhw.target = prevSettings.dhw.maxTemp;
}
setValue('#tDhwTargetTemp', newSettings.dhw.target);
setValue('.targetTemp', newSettings[purpose].target, tContainer);
}, 500);
});
});
document.querySelector('#tHeatEnabled').addEventListener('change', (event) => {
@@ -354,6 +402,60 @@
newSettings.dhw.enabled = event.currentTarget.checked;
});
document.querySelector('.notify-fault .reset').addEventListener('click', async (event) => {
const resetBtn = document.querySelector(".notify-fault .reset");
if (!resetBtn.disabled) {
resetBtn.disabled = true;
}
let response = await fetch("/api/vars", {
method: "POST",
cache: "no-cache",
credentials: "include",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
"actions": {
"resetFault": true
}
})
});
setTimeout(() => {
if (resetBtn.disabled) {
resetBtn.disabled = false;
}
}, 10000);
});
document.querySelector('.notify-diag .reset').addEventListener('click', async (event) => {
const resetBtn = document.querySelector(".notify-diag .reset");
if (!resetBtn.disabled) {
resetBtn.disabled = true;
}
let response = await fetch("/api/vars", {
method: "POST",
cache: "no-cache",
credentials: "include",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
"actions": {
"resetDiagnostic": true
}
})
});
setTimeout(() => {
if (resetBtn.disabled) {
resetBtn.disabled = false;
}
}, 10000);
});
setTimeout(async function onLoadPage() {
let unitSystem = null;
@@ -372,8 +474,8 @@
(prevSettings.heating.enabled != newSettings.heating.enabled)
|| (prevSettings.heating.turbo != newSettings.heating.turbo)
|| (prevSettings.heating.target != newSettings.heating.target)
|| (prevSettings.opentherm.dhwPresent && prevSettings.dhw.enabled != newSettings.dhw.enabled)
|| (prevSettings.opentherm.dhwPresent && prevSettings.dhw.target != newSettings.dhw.target)
|| (prevSettings.opentherm.options.dhwSupport && prevSettings.dhw.enabled != newSettings.dhw.enabled)
|| (prevSettings.opentherm.options.dhwSupport && prevSettings.dhw.target != newSettings.dhw.target)
);
if (modified) {
@@ -397,7 +499,6 @@
}
const result = await response.json();
noRegulators = !result.opentherm.nativeHeatingControl && !result.equitherm.enabled && !result.pid.enabled;
prevSettings = result;
unitSystem = result.system.unitSystem;
newSettings.heating.enabled = result.heating.enabled;
@@ -406,18 +507,18 @@
newSettings.dhw.enabled = result.dhw.enabled;
newSettings.dhw.target = result.dhw.target;
if (result.opentherm.dhwPresent) {
show('#thermostat-dhw');
if (result.opentherm.options.dhwSupport) {
show('.tDhw');
} else {
hide('#thermostat-dhw');
hide('.tDhw');
}
setCheckboxValue('#tHeatEnabled', result.heating.enabled);
setCheckboxValue('#tHeatTurbo', result.heating.turbo);
setValue('#tHeatTargetTemp', result.heating.target);
setValue('.tHeat .targetTemp', result.heating.target);
setCheckboxValue('#tDhwEnabled', result.dhw.enabled);
setValue('#tDhwTargetTemp', result.dhw.target);
setValue('.tDhw .targetTemp', result.dhw.target);
setValue('.tempUnit', temperatureUnit(unitSystem));
setValue('.pressureUnit', pressureUnit(unitSystem));
@@ -433,20 +534,20 @@
cache: "no-cache",
credentials: "include"
});
if (!response.ok) {
throw new Error('Response not valid');
}
const result = await response.json();
// Graph
setValue('#tHeatCurrentTemp', result.master.heating.indoorTempControl
? result.master.heating.indoorTemp
setValue('#tHeatCurrentTemp', result.master.heating.indoorTempControl
? result.master.heating.indoorTemp
: result.master.heating.currentTemp
);
setValue('#tDhwCurrentTemp', result.master.dhw.currentTemp);
// SLAVE
setValue('.sMemberId', result.slave.memberId);
@@ -463,6 +564,9 @@
);
setState('.sFlame', result.slave.flame);
setState('.sCoolingActive', result.slave.cooling.active);
setValue('.sCoolingSetpoint', result.slave.cooling.setpoint);
setValue('.sModMin', result.slave.modulation.min);
setValue('.sModMax', result.slave.modulation.max);
@@ -489,6 +593,13 @@
: "-"
);
if (result.slave.fault.active) {
show(".notify-fault");
} else {
hide('.notify-fault');
}
setStatus(
'.sDiagActive',
result.slave.diag.active ? "success" : "error",
@@ -501,6 +612,13 @@
: "-"
);
if (result.slave.diag.active) {
show(".notify-diag");
} else {
hide('.notify-diag');
}
// MASTER
setState('.mHeatEnabled', result.master.heating.enabled);
@@ -510,6 +628,11 @@
result.master.heating.blocking ? "red" : "green"
);
setState('.mHeatIndoorTempControl', result.master.heating.indoorTempControl);
setStatus(
'.mHeatOverheat',
result.master.heating.overheat ? "success" : "error",
result.master.heating.overheat ? "red" : "green"
);
setValue('.mHeatSetpointTemp', result.master.heating.setpointTemp);
setValue('.mHeatTargetTemp', result.master.heating.targetTemp);
setValue('.mHeatCurrTemp', result.master.heating.currentTemp);
@@ -520,6 +643,11 @@
setValue('.mHeatMaxTemp', result.master.heating.maxTemp);
setState('.mDhwEnabled', result.master.dhw.enabled);
setStatus(
'.mDhwOverheat',
result.master.dhw.overheat ? "success" : "error",
result.master.dhw.overheat ? "red" : "green"
);
setValue('.mDhwTargetTemp', result.master.dhw.targetTemp);
setValue('.mDhwCurrTemp', result.master.dhw.currentTemp);
setValue('.mDhwRetTemp', result.master.dhw.returnTemp);
@@ -541,6 +669,14 @@
setState('.mCascadeControlInput', result.master.cascadeControl.input);
setState('.mCascadeControlOutput', result.master.cascadeControl.output);
const tHeat = document.querySelector('.tHeat');
tHeat.dataset.min = result.master.heating.minTemp;
tHeat.dataset.max = result.master.heating.maxTemp;
const tDhw = document.querySelector('.tDhw');
tDhw.dataset.min = result.master.dhw.minTemp;
tDhw.dataset.max = result.master.dhw.maxTemp;
setBusy('#dashboard-busy', '#dashboard-container', false);
} catch (error) {
@@ -553,7 +689,7 @@
cache: "no-cache",
credentials: "include"
});
if (!response.ok) {
throw new Error("Response not valid");
}
@@ -579,7 +715,7 @@
if (!sensorNode) {
continue;
}
const sData = result[sensorId];
if (!sData.enabled || sData.purpose == 255) {
sensorNode.classList.toggle("hidden", true);

View File

@@ -21,6 +21,9 @@
<li>
<select id="lang" aria-label="Lang">
<option value="en" selected>EN</option>
<option value="cn">CN</option>
<option value="it">IT</option>
<option value="nl">NL</option>
<option value="ru">RU</option>
</select>
</li>

View File

@@ -21,6 +21,9 @@
<li>
<select id="lang" aria-label="Lang">
<option value="en" selected>EN</option>
<option value="cn">CN</option>
<option value="it">IT</option>
<option value="nl">NL</option>
<option value="ru">RU</option>
</select>
</li>
@@ -38,37 +41,37 @@
<div id="network-settings-busy" aria-busy="true"></div>
<form action="/api/network/settings" id="network-settings" class="hidden">
<label for="network-hostname">
<label>
<span data-i18n>network.params.hostname</span>
<input type="text" id="network-hostname" name="hostname" maxlength="24" pattern="[A-Za-z0-9]+[A-Za-z0-9\-]+[A-Za-z0-9]+" required>
<input type="text" name="hostname" maxlength="24" pattern="[A-Za-z0-9]+[A-Za-z0-9\-]+[A-Za-z0-9]+" required>
</label>
<label for="network-use-dhcp">
<input type="checkbox" id="network-use-dhcp" name="useDhcp" value="true">
<label>
<input type="checkbox" name="useDhcp" value="true">
<span data-i18n>network.params.dhcp</span>
</label>
<br />
<hr />
<h4 data-i18n>network.section.static</h4>
<label for="network-static-ip">
<label>
<span data-i18n>network.params.ip</span>
<input type="text" id="network-static-ip" name="staticConfig[ip]" value="true" maxlength="16" pattern="\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}" required>
<input type="text" name="staticConfig[ip]" value="true" maxlength="16" pattern="\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}" required>
</label>
<label for="network-static-gateway">
<label>
<span data-i18n>network.params.gateway</span>
<input type="text" id="network-static-gateway" name="staticConfig[gateway]" maxlength="16" pattern="\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}" required>
<input type="text" name="staticConfig[gateway]" maxlength="16" pattern="\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}" required>
</label>
<label for="network-static-subnet">
<label>
<span data-i18n>network.params.subnet</span>
<input type="text" id="network-static-subnet" name="staticConfig[subnet]" maxlength="16" pattern="\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}" required>
<input type="text" name="staticConfig[subnet]" maxlength="16" pattern="\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}" required>
</label>
<label for="network-static-dns">
<label>
<span data-i18n>network.params.dns</span>
<input type="text" id="network-static-dns" name="staticConfig[dns]" maxlength="16" pattern="\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}" required>
<input type="text" name="staticConfig[dns]" maxlength="16" pattern="\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}" required>
</label>
<button type="submit" data-i18n>button.save</button>
@@ -109,19 +112,19 @@
<div id="sta-settings-busy" aria-busy="true"></div>
<form action="/api/network/settings" id="sta-settings" class="hidden">
<label for="sta-ssid">
<label>
<span data-i18n>network.wifi.ssid</span>
<input type="text" id="sta-ssid" name="sta[ssid]" maxlength="32" required>
<input type="text" name="sta[ssid]" maxlength="32" required>
</label>
<label for="sta-password">
<label>
<span data-i18n>network.wifi.password</span>
<input type="password" id="sta-password" name="sta[password]" maxlength="64" required>
<input type="password" name="sta[password]" maxlength="64" required>
</label>
<label for="sta-channel">
<label>
<span data-i18n>network.wifi.channel</span>
<input type="number" inputmode="numeric" id="sta-channel" name="sta[channel]" min="0" max="12" step="1" required>
<input type="number" inputmode="numeric" name="sta[channel]" min="0" max="12" step="1" required>
<small data-i18n>network.sta.channel.note</small>
</label>
@@ -140,19 +143,19 @@
<div id="ap-settings-busy" aria-busy="true"></div>
<form action="/api/network/settings" id="ap-settings" class="hidden">
<label for="ap-ssid">
<label>
<span data-i18n>network.wifi.ssid</span>
<input type="text" id="ap-ssid" name="ap[ssid]" maxlength="32" required>
<input type="text" name="ap[ssid]" maxlength="32" required>
</label>
<label for="ap-password">
<label>
<span data-i18n>network.wifi.password</span>
<input type="text" id="ap-password" name="ap[password]" maxlength="64" required>
<input type="text" name="ap[password]" maxlength="64" required>
</label>
<label for="ap-channel">
<label>
<span data-i18n>network.wifi.channel</span>
<input type="number" inputmode="numeric" id="ap-channel" name="ap[channel]" min="1" max="12" step="1" required>
<input type="number" inputmode="numeric" name="ap[channel]" min="1" max="12" step="1" required>
</label>
<button type="submit" data-i18n>button.save</button>
@@ -179,22 +182,22 @@
lang.build();
const fillData = (data) => {
setInputValue('#network-hostname', data.hostname);
setCheckboxValue('#network-use-dhcp', data.useDhcp);
setInputValue('#network-static-ip', data.staticConfig.ip);
setInputValue('#network-static-gateway', data.staticConfig.gateway);
setInputValue('#network-static-subnet', data.staticConfig.subnet);
setInputValue('#network-static-dns', data.staticConfig.dns);
setInputValue("[name='hostname']", data.hostname);
setCheckboxValue("[name='useDhcp']", data.useDhcp);
setInputValue("[name='staticConfig[ip]']", data.staticConfig.ip);
setInputValue("[name='staticConfig[gateway]']", data.staticConfig.gateway);
setInputValue("[name='staticConfig[subnet]']", data.staticConfig.subnet);
setInputValue("[name='staticConfig[dns]']", data.staticConfig.dns);
setBusy('#network-settings-busy', '#network-settings', false);
setInputValue('#sta-ssid', data.sta.ssid);
setInputValue('#sta-password', data.sta.password);
setInputValue('#sta-channel', data.sta.channel);
setInputValue("[name='sta[ssid]']", data.sta.ssid);
setInputValue("[name='sta[password]']", data.sta.password);
setInputValue("[name='sta[channel]']", data.sta.channel);
setBusy('#sta-settings-busy', '#sta-settings', false);
setInputValue('#ap-ssid', data.ap.ssid);
setInputValue('#ap-password', data.ap.password);
setInputValue('#ap-channel', data.ap.channel);
setInputValue("[name='ap[ssid]']", data.ap.ssid);
setInputValue("[name='ap[password]']", data.ap.password);
setInputValue("[name='ap[channel]']", data.ap.channel);
setBusy('#ap-settings-busy', '#ap-settings', false);
};

View File

@@ -21,6 +21,9 @@
<li>
<select id="lang" aria-label="Lang">
<option value="en" selected>EN</option>
<option value="cn">CN</option>
<option value="it">IT</option>
<option value="nl">NL</option>
<option value="ru">RU</option>
</select>
</li>
@@ -36,7 +39,7 @@
</hgroup>
<details id="template" class="sensor hidden" data-id="" data-preloaded="0">
<summary><b>#<span class="id"></span>: <span class="name"></span></b></summary>
<summary><b>#<span class="pos"></span>: <span class="name"></span></b></summary>
<div>
<div class="form-busy" aria-busy="true"></div>
@@ -68,6 +71,7 @@
<option value="6" data-i18n>sensors.purposes.dhwFlowRate</option>
<option value="7" data-i18n>sensors.purposes.exhaustTemp</option>
<option value="8" data-i18n>sensors.purposes.modLevel</option>
<option value="247" data-i18n>sensors.purposes.number</option>
<option value="248" data-i18n>sensors.purposes.powerFactor</option>
<option value="249" data-i18n>sensors.purposes.power</option>
<option value="250" data-i18n>sensors.purposes.fanSpeed</option>
@@ -101,6 +105,14 @@
<option value="16" data-i18n>sensors.types.otSolarCollectorTemp</option>
<option value="17" data-i18n>sensors.types.otFanSpeedSetpoint</option>
<option value="18" data-i18n>sensors.types.otFanSpeedCurrent</option>
<option value="19" data-i18n>sensors.types.otBurnerStarts</option>
<option value="20" data-i18n>sensors.types.otDhwBurnerStarts</option>
<option value="21" data-i18n>sensors.types.otHeatingPumpStarts</option>
<option value="22" data-i18n>sensors.types.otDhwPumpStarts</option>
<option value="23" data-i18n>sensors.types.otBurnerHours</option>
<option value="24" data-i18n>sensors.types.otDhwBurnerHours</option>
<option value="25" data-i18n>sensors.types.otHeatingPumpHours</option>
<option value="26" data-i18n>sensors.types.otDhwPumpHours</option>
<option value="50" data-i18n>sensors.types.ntcTemp</option>
<option value="51" data-i18n>sensors.types.dallasTemp</option>
@@ -134,12 +146,12 @@
<div class="grid">
<label>
<span data-i18n>sensors.correction.offset</span>
<input type="number" inputmode="numeric" name="offset" min="-20" max="20" step="0.01" required>
<input type="number" inputmode="decimal" name="offset" min="-20" max="20" step="0.01" required>
</label>
<label>
<span data-i18n>sensors.correction.factor</span>
<input type="number" inputmode="numeric" name="factor" min="0.01" max="10" step="0.01" required>
<input type="number" inputmode="decimal" name="factor" min="0.01" max="100" step="0.01" required>
</label>
</div>
</details>
@@ -159,7 +171,7 @@
<label>
<span data-i18n>sensors.filtering.factor.title</span>
<input type="number" inputmode="numeric" name="filteringFactor" min="0.01" max="1" step="0.01">
<input type="number" inputmode="decimal" name="filteringFactor" min="0.01" max="1" step="0.01">
<small data-i18n>sensors.filtering.factor.note</small>
</label>
</div>
@@ -210,6 +222,7 @@
sensorNode.classList.remove("hidden");
sensorNode.dataset.id = sensorId;
setValue(".id", sensorId, sensorNode);
setValue(".pos", parseInt(sensorId) + 1, sensorNode);
setValue(".name", result[sensorId], sensorNode);
container.appendChild(sensorNode);

File diff suppressed because it is too large Load Diff

View File

@@ -21,6 +21,9 @@
<li>
<select id="lang" aria-label="Lang">
<option value="en" selected>EN</option>
<option value="cn">CN</option>
<option value="it">IT</option>
<option value="nl">NL</option>
<option value="ru">RU</option>
</select>
</li>
@@ -42,7 +45,7 @@
<input type="file" name="settings" id="restore-file" accept=".json">
</label>
<div class="grid">
<div role="group">
<button type="submit" data-i18n>button.restore</button>
<button type="button" class="secondary" onclick="window.location='/api/backup/save';" data-i18n>button.backup</button>
</div>

2
src_data/robots.txt Normal file
View File

@@ -0,0 +1,2 @@
User-agent: *
Disallow: /

View File

@@ -132,10 +132,14 @@ const setupNetworkScanForm = (formSelector, tableSelector) => {
for (let i = 0; i < result.length; i++) {
let row = tbody.insertRow(-1);
row.classList.add("network");
row.setAttribute('data-ssid', result[i].hidden ? '' : result[i].ssid);
row.onclick = () => {
const input = document.querySelector('input#sta-ssid');
const ssid = this.getAttribute('data-ssid');
row.dataset.ssid = result[i].hidden ? '' : result[i].ssid;
row.insertCell().textContent = `#${i + 1}`;
const nameCell = row.insertCell();
nameCell.innerHTML = result[i].hidden ? `<i>${result[i].bssid}</i>` : result[i].ssid;
nameCell.onclick = (event) => {
const input = document.querySelector("[name='sta[ssid]']");
const ssid = event.target.parentNode.dataset.ssid;
if (!input || !ssid) {
return;
}
@@ -144,9 +148,6 @@ const setupNetworkScanForm = (formSelector, tableSelector) => {
input.focus();
};
row.insertCell().textContent = `#${i + 1}`;
row.insertCell().innerHTML = result[i].hidden ? `<i>${result[i].bssid}</i>` : result[i].ssid;
// info cell
let infoCell = row.insertCell();
@@ -165,7 +166,7 @@ const setupNetworkScanForm = (formSelector, tableSelector) => {
}
let signalQualityContainer = document.createElement("span");
signalQualityContainer.setAttribute('data-tooltip', `${result[i].signalQuality}%`);
signalQualityContainer.dataset.tooltip = `${result[i].signalQuality}%`;
signalQualityContainer.appendChild(signalQualityIcon);
infoCell.appendChild(signalQualityContainer);
@@ -192,7 +193,7 @@ const setupNetworkScanForm = (formSelector, tableSelector) => {
}
let authContainer = document.createElement("span");
authContainer.setAttribute('data-tooltip', (result[i].auth in authList) ? authList[result[i].auth] : "unknown");
authContainer.dataset.tooltip = (result[i].auth in authList) ? authList[result[i].auth] : "unknown";
authContainer.appendChild(authIcon);
infoCell.appendChild(authContainer);
}
@@ -848,4 +849,8 @@ function dec2hex(i) {
}
return hex.toUpperCase();
}
function constrain(amt, low, high) {
return ((amt) < (low) ? (low) : ((amt) > (high) ? (high) : (amt)));
}

View File

@@ -3,6 +3,10 @@
--pico-block-spacing-vertical: calc(var(--pico-spacing) * 0.75);
--pico-block-spacing-horizontal: calc(var(--pico-spacing) * 0.75);
}
.logo {
font-size: 1.2rem;
}
}
@media (min-width: 768px) {
@@ -10,6 +14,10 @@
--pico-block-spacing-vertical: var(--pico-spacing);
--pico-block-spacing-horizontal: var(--pico-spacing);
}
.logo {
font-size: 1.25rem;
}
}
@media (min-width: 1024px) {
@@ -17,6 +25,10 @@
--pico-block-spacing-vertical: calc(var(--pico-spacing) * 1.25);
--pico-block-spacing-horizontal: calc(var(--pico-spacing) * 1.25);
}
.logo {
font-size: 1.25rem;
}
}
@media (min-width: 1280px) {
@@ -25,6 +37,10 @@
--pico-block-spacing-horizontal: calc(var(--pico-spacing) * 1.5);
}
.logo {
font-size: 1.3rem;
}
.container {
max-width: 1000px;
}
@@ -36,6 +52,10 @@
--pico-block-spacing-horizontal: calc(var(--pico-spacing) * 1.75);
}
.logo {
font-size: 1.3rem;
}
.container {
max-width: 1000px;
}
@@ -111,10 +131,11 @@ tr.network:hover {
border-radius: var(--pico-border-radius);
color: var(--pico-code-kbd-color);
font-weight: bolder;
font-size: 1.3rem;
/*font-size: 1.3rem;*/
font-family: var(--pico-font-family-monospace);
}
.thermostat {
display: grid;
grid-template-columns: 0.5fr 2fr 0.5fr;
@@ -193,6 +214,92 @@ tr.network:hover {
margin: 1.25rem 0;
}
@media (max-width: 575px) {
.notify {
display: grid;
grid-template-columns: 1fr;
grid-template-rows: 1fr 0.5fr;
gap: 0.5rem 0.5rem;
grid-template-areas:
"notify-content"
"notify-actions";
}
.notify-icon {
display: none;
}
.notify-content {
justify-self: center;
align-self: center;
grid-area: notify-content;
}
.notify-actions {
justify-self: center;
align-self: center;
grid-area: notify-actions;
}
}
@media (min-width: 576px) {
.notify {
display: grid;
grid-template-columns: 5rem 1fr 10rem;
grid-template-rows: 1fr;
gap: 0rem 0.5rem;
grid-auto-flow: row;
grid-template-areas:
"notify-icon notify-content notify-actions";
}
.notify-icon {
justify-self: center;
align-self: center;
grid-area: notify-icon;
}
.notify-content {
justify-self: center;
align-self: center;
grid-area: notify-content;
}
.notify-actions {
justify-self: center;
align-self: center;
grid-area: notify-actions;
}
}
.notify {
margin: 1rem;
padding: 0.5rem;
border: .25rem solid var(--pico-blockquote-border-color);
}
.notify-error {
border-color: var(--pico-form-element-invalid-border-color);
}
.notify-error .notify-icon {
color: var(--pico-form-element-invalid-border-color);
}
.notify-alarm {
border-color: #c89048;
}
.notify-alarm .notify-icon {
color: #c89048;
}
.notify-icon i {
font-size: 2.5rem;
}
[class*=" icons-"],
[class=icons],
[class^=icons-] {

458
src_data/timezones.json Normal file
View File

@@ -0,0 +1,458 @@
{
"Africa/Abidjan":"GMT0",
"Africa/Accra":"GMT0",
"Africa/Addis_Ababa":"EAT-3",
"Africa/Algiers":"CET-1",
"Africa/Asmara":"EAT-3",
"Africa/Bamako":"GMT0",
"Africa/Bangui":"WAT-1",
"Africa/Banjul":"GMT0",
"Africa/Bissau":"GMT0",
"Africa/Blantyre":"CAT-2",
"Africa/Brazzaville":"WAT-1",
"Africa/Bujumbura":"CAT-2",
"Africa/Cairo":"EET-2EEST,M4.5.5/0,M10.5.4/24",
"Africa/Casablanca":"<+01>-1",
"Africa/Ceuta":"CET-1CEST,M3.5.0,M10.5.0/3",
"Africa/Conakry":"GMT0",
"Africa/Dakar":"GMT0",
"Africa/Dar_es_Salaam":"EAT-3",
"Africa/Djibouti":"EAT-3",
"Africa/Douala":"WAT-1",
"Africa/El_Aaiun":"<+01>-1",
"Africa/Freetown":"GMT0",
"Africa/Gaborone":"CAT-2",
"Africa/Harare":"CAT-2",
"Africa/Johannesburg":"SAST-2",
"Africa/Juba":"CAT-2",
"Africa/Kampala":"EAT-3",
"Africa/Khartoum":"CAT-2",
"Africa/Kigali":"CAT-2",
"Africa/Kinshasa":"WAT-1",
"Africa/Lagos":"WAT-1",
"Africa/Libreville":"WAT-1",
"Africa/Lome":"GMT0",
"Africa/Luanda":"WAT-1",
"Africa/Lubumbashi":"CAT-2",
"Africa/Lusaka":"CAT-2",
"Africa/Malabo":"WAT-1",
"Africa/Maputo":"CAT-2",
"Africa/Maseru":"SAST-2",
"Africa/Mbabane":"SAST-2",
"Africa/Mogadishu":"EAT-3",
"Africa/Monrovia":"GMT0",
"Africa/Nairobi":"EAT-3",
"Africa/Ndjamena":"WAT-1",
"Africa/Niamey":"WAT-1",
"Africa/Nouakchott":"GMT0",
"Africa/Ouagadougou":"GMT0",
"Africa/Porto-Novo":"WAT-1",
"Africa/Sao_Tome":"GMT0",
"Africa/Tripoli":"EET-2",
"Africa/Tunis":"CET-1",
"Africa/Windhoek":"CAT-2",
"America/Adak":"HST10HDT,M3.2.0,M11.1.0",
"America/Anchorage":"AKST9AKDT,M3.2.0,M11.1.0",
"America/Anguilla":"AST4",
"America/Antigua":"AST4",
"America/Araguaina":"<-03>3",
"America/Argentina/Buenos_Aires":"<-03>3",
"America/Argentina/Catamarca":"<-03>3",
"America/Argentina/Cordoba":"<-03>3",
"America/Argentina/Jujuy":"<-03>3",
"America/Argentina/La_Rioja":"<-03>3",
"America/Argentina/Mendoza":"<-03>3",
"America/Argentina/Rio_Gallegos":"<-03>3",
"America/Argentina/Salta":"<-03>3",
"America/Argentina/San_Juan":"<-03>3",
"America/Argentina/San_Luis":"<-03>3",
"America/Argentina/Tucuman":"<-03>3",
"America/Argentina/Ushuaia":"<-03>3",
"America/Aruba":"AST4",
"America/Asuncion":"<-04>4<-03>,M10.1.0/0,M3.4.0/0",
"America/Atikokan":"EST5",
"America/Bahia":"<-03>3",
"America/Bahia_Banderas":"CST6",
"America/Barbados":"AST4",
"America/Belem":"<-03>3",
"America/Belize":"CST6",
"America/Blanc-Sablon":"AST4",
"America/Boa_Vista":"<-04>4",
"America/Bogota":"<-05>5",
"America/Boise":"MST7MDT,M3.2.0,M11.1.0",
"America/Cambridge_Bay":"MST7MDT,M3.2.0,M11.1.0",
"America/Campo_Grande":"<-04>4",
"America/Cancun":"EST5",
"America/Caracas":"<-04>4",
"America/Cayenne":"<-03>3",
"America/Cayman":"EST5",
"America/Chicago":"CST6CDT,M3.2.0,M11.1.0",
"America/Chihuahua":"CST6",
"America/Costa_Rica":"CST6",
"America/Creston":"MST7",
"America/Cuiaba":"<-04>4",
"America/Curacao":"AST4",
"America/Danmarkshavn":"GMT0",
"America/Dawson":"MST7",
"America/Dawson_Creek":"MST7",
"America/Denver":"MST7MDT,M3.2.0,M11.1.0",
"America/Detroit":"EST5EDT,M3.2.0,M11.1.0",
"America/Dominica":"AST4",
"America/Edmonton":"MST7MDT,M3.2.0,M11.1.0",
"America/Eirunepe":"<-05>5",
"America/El_Salvador":"CST6",
"America/Fort_Nelson":"MST7",
"America/Fortaleza":"<-03>3",
"America/Glace_Bay":"AST4ADT,M3.2.0,M11.1.0",
"America/Godthab":"<-02>2<-01>,M3.5.0/-1,M10.5.0/0",
"America/Goose_Bay":"AST4ADT,M3.2.0,M11.1.0",
"America/Grand_Turk":"EST5EDT,M3.2.0,M11.1.0",
"America/Grenada":"AST4",
"America/Guadeloupe":"AST4",
"America/Guatemala":"CST6",
"America/Guayaquil":"<-05>5",
"America/Guyana":"<-04>4",
"America/Halifax":"AST4ADT,M3.2.0,M11.1.0",
"America/Havana":"CST5CDT,M3.2.0/0,M11.1.0/1",
"America/Hermosillo":"MST7",
"America/Indiana/Indianapolis":"EST5EDT,M3.2.0,M11.1.0",
"America/Indiana/Knox":"CST6CDT,M3.2.0,M11.1.0",
"America/Indiana/Marengo":"EST5EDT,M3.2.0,M11.1.0",
"America/Indiana/Petersburg":"EST5EDT,M3.2.0,M11.1.0",
"America/Indiana/Tell_City":"CST6CDT,M3.2.0,M11.1.0",
"America/Indiana/Vevay":"EST5EDT,M3.2.0,M11.1.0",
"America/Indiana/Vincennes":"EST5EDT,M3.2.0,M11.1.0",
"America/Indiana/Winamac":"EST5EDT,M3.2.0,M11.1.0",
"America/Inuvik":"MST7MDT,M3.2.0,M11.1.0",
"America/Iqaluit":"EST5EDT,M3.2.0,M11.1.0",
"America/Jamaica":"EST5",
"America/Juneau":"AKST9AKDT,M3.2.0,M11.1.0",
"America/Kentucky/Louisville":"EST5EDT,M3.2.0,M11.1.0",
"America/Kentucky/Monticello":"EST5EDT,M3.2.0,M11.1.0",
"America/Kralendijk":"AST4",
"America/La_Paz":"<-04>4",
"America/Lima":"<-05>5",
"America/Los_Angeles":"PST8PDT,M3.2.0,M11.1.0",
"America/Lower_Princes":"AST4",
"America/Maceio":"<-03>3",
"America/Managua":"CST6",
"America/Manaus":"<-04>4",
"America/Marigot":"AST4",
"America/Martinique":"AST4",
"America/Matamoros":"CST6CDT,M3.2.0,M11.1.0",
"America/Mazatlan":"MST7",
"America/Menominee":"CST6CDT,M3.2.0,M11.1.0",
"America/Merida":"CST6",
"America/Metlakatla":"AKST9AKDT,M3.2.0,M11.1.0",
"America/Mexico_City":"CST6",
"America/Miquelon":"<-03>3<-02>,M3.2.0,M11.1.0",
"America/Moncton":"AST4ADT,M3.2.0,M11.1.0",
"America/Monterrey":"CST6",
"America/Montevideo":"<-03>3",
"America/Montreal":"EST5EDT,M3.2.0,M11.1.0",
"America/Montserrat":"AST4",
"America/Nassau":"EST5EDT,M3.2.0,M11.1.0",
"America/New_York":"EST5EDT,M3.2.0,M11.1.0",
"America/Nipigon":"EST5EDT,M3.2.0,M11.1.0",
"America/Nome":"AKST9AKDT,M3.2.0,M11.1.0",
"America/Noronha":"<-02>2",
"America/North_Dakota/Beulah":"CST6CDT,M3.2.0,M11.1.0",
"America/North_Dakota/Center":"CST6CDT,M3.2.0,M11.1.0",
"America/North_Dakota/New_Salem":"CST6CDT,M3.2.0,M11.1.0",
"America/Nuuk":"<-02>2<-01>,M3.5.0/-1,M10.5.0/0",
"America/Ojinaga":"CST6CDT,M3.2.0,M11.1.0",
"America/Panama":"EST5",
"America/Pangnirtung":"EST5EDT,M3.2.0,M11.1.0",
"America/Paramaribo":"<-03>3",
"America/Phoenix":"MST7",
"America/Port-au-Prince":"EST5EDT,M3.2.0,M11.1.0",
"America/Port_of_Spain":"AST4",
"America/Porto_Velho":"<-04>4",
"America/Puerto_Rico":"AST4",
"America/Punta_Arenas":"<-03>3",
"America/Rainy_River":"CST6CDT,M3.2.0,M11.1.0",
"America/Rankin_Inlet":"CST6CDT,M3.2.0,M11.1.0",
"America/Recife":"<-03>3",
"America/Regina":"CST6",
"America/Resolute":"CST6CDT,M3.2.0,M11.1.0",
"America/Rio_Branco":"<-05>5",
"America/Santarem":"<-03>3",
"America/Santiago":"<-04>4<-03>,M9.1.6/24,M4.1.6/24",
"America/Santo_Domingo":"AST4",
"America/Sao_Paulo":"<-03>3",
"America/Scoresbysund":"<-02>2<-01>,M3.5.0/-1,M10.5.0/0",
"America/Sitka":"AKST9AKDT,M3.2.0,M11.1.0",
"America/St_Barthelemy":"AST4",
"America/St_Johns":"NST3:30NDT,M3.2.0,M11.1.0",
"America/St_Kitts":"AST4",
"America/St_Lucia":"AST4",
"America/St_Thomas":"AST4",
"America/St_Vincent":"AST4",
"America/Swift_Current":"CST6",
"America/Tegucigalpa":"CST6",
"America/Thule":"AST4ADT,M3.2.0,M11.1.0",
"America/Thunder_Bay":"EST5EDT,M3.2.0,M11.1.0",
"America/Tijuana":"PST8PDT,M3.2.0,M11.1.0",
"America/Toronto":"EST5EDT,M3.2.0,M11.1.0",
"America/Tortola":"AST4",
"America/Vancouver":"PST8PDT,M3.2.0,M11.1.0",
"America/Whitehorse":"MST7",
"America/Winnipeg":"CST6CDT,M3.2.0,M11.1.0",
"America/Yakutat":"AKST9AKDT,M3.2.0,M11.1.0",
"America/Yellowknife":"MST7MDT,M3.2.0,M11.1.0",
"Antarctica/Casey":"<+08>-8",
"Antarctica/Davis":"<+07>-7",
"Antarctica/DumontDUrville":"<+10>-10",
"Antarctica/Macquarie":"AEST-10AEDT,M10.1.0,M4.1.0/3",
"Antarctica/Mawson":"<+05>-5",
"Antarctica/McMurdo":"NZST-12NZDT,M9.5.0,M4.1.0/3",
"Antarctica/Palmer":"<-03>3",
"Antarctica/Rothera":"<-03>3",
"Antarctica/Syowa":"<+03>-3",
"Antarctica/Troll":"<+00>0<+02>-2,M3.5.0/1,M10.5.0/3",
"Antarctica/Vostok":"<+05>-5",
"Arctic/Longyearbyen":"CET-1CEST,M3.5.0,M10.5.0/3",
"Asia/Aden":"<+03>-3",
"Asia/Almaty":"<+05>-5",
"Asia/Amman":"<+03>-3",
"Asia/Anadyr":"<+12>-12",
"Asia/Aqtau":"<+05>-5",
"Asia/Aqtobe":"<+05>-5",
"Asia/Ashgabat":"<+05>-5",
"Asia/Atyrau":"<+05>-5",
"Asia/Baghdad":"<+03>-3",
"Asia/Bahrain":"<+03>-3",
"Asia/Baku":"<+04>-4",
"Asia/Bangkok":"<+07>-7",
"Asia/Barnaul":"<+07>-7",
"Asia/Beirut":"EET-2EEST,M3.5.0/0,M10.5.0/0",
"Asia/Bishkek":"<+06>-6",
"Asia/Brunei":"<+08>-8",
"Asia/Chita":"<+09>-9",
"Asia/Choibalsan":"<+08>-8",
"Asia/Colombo":"<+0530>-5:30",
"Asia/Damascus":"<+03>-3",
"Asia/Dhaka":"<+06>-6",
"Asia/Dili":"<+09>-9",
"Asia/Dubai":"<+04>-4",
"Asia/Dushanbe":"<+05>-5",
"Asia/Famagusta":"EET-2EEST,M3.5.0/3,M10.5.0/4",
"Asia/Gaza":"EET-2EEST,M3.4.4/50,M10.4.4/50",
"Asia/Hebron":"EET-2EEST,M3.4.4/50,M10.4.4/50",
"Asia/Ho_Chi_Minh":"<+07>-7",
"Asia/Hong_Kong":"HKT-8",
"Asia/Hovd":"<+07>-7",
"Asia/Irkutsk":"<+08>-8",
"Asia/Jakarta":"WIB-7",
"Asia/Jayapura":"WIT-9",
"Asia/Jerusalem":"IST-2IDT,M3.4.4/26,M10.5.0",
"Asia/Kabul":"<+0430>-4:30",
"Asia/Kamchatka":"<+12>-12",
"Asia/Karachi":"PKT-5",
"Asia/Kathmandu":"<+0545>-5:45",
"Asia/Khandyga":"<+09>-9",
"Asia/Kolkata":"IST-5:30",
"Asia/Krasnoyarsk":"<+07>-7",
"Asia/Kuala_Lumpur":"<+08>-8",
"Asia/Kuching":"<+08>-8",
"Asia/Kuwait":"<+03>-3",
"Asia/Macau":"CST-8",
"Asia/Magadan":"<+11>-11",
"Asia/Makassar":"WITA-8",
"Asia/Manila":"PST-8",
"Asia/Muscat":"<+04>-4",
"Asia/Nicosia":"EET-2EEST,M3.5.0/3,M10.5.0/4",
"Asia/Novokuznetsk":"<+07>-7",
"Asia/Novosibirsk":"<+07>-7",
"Asia/Omsk":"<+06>-6",
"Asia/Oral":"<+05>-5",
"Asia/Phnom_Penh":"<+07>-7",
"Asia/Pontianak":"WIB-7",
"Asia/Pyongyang":"KST-9",
"Asia/Qatar":"<+03>-3",
"Asia/Qyzylorda":"<+05>-5",
"Asia/Riyadh":"<+03>-3",
"Asia/Sakhalin":"<+11>-11",
"Asia/Samarkand":"<+05>-5",
"Asia/Seoul":"KST-9",
"Asia/Shanghai":"CST-8",
"Asia/Singapore":"<+08>-8",
"Asia/Srednekolymsk":"<+11>-11",
"Asia/Taipei":"CST-8",
"Asia/Tashkent":"<+05>-5",
"Asia/Tbilisi":"<+04>-4",
"Asia/Tehran":"<+0330>-3:30",
"Asia/Thimphu":"<+06>-6",
"Asia/Tokyo":"JST-9",
"Asia/Tomsk":"<+07>-7",
"Asia/Ulaanbaatar":"<+08>-8",
"Asia/Urumqi":"<+06>-6",
"Asia/Ust-Nera":"<+10>-10",
"Asia/Vientiane":"<+07>-7",
"Asia/Vladivostok":"<+10>-10",
"Asia/Yakutsk":"<+09>-9",
"Asia/Yangon":"<+0630>-6:30",
"Asia/Yekaterinburg":"<+05>-5",
"Asia/Yerevan":"<+04>-4",
"Atlantic/Azores":"<-01>1<+00>,M3.5.0/0,M10.5.0/1",
"Atlantic/Bermuda":"AST4ADT,M3.2.0,M11.1.0",
"Atlantic/Canary":"WET0WEST,M3.5.0/1,M10.5.0",
"Atlantic/Cape_Verde":"<-01>1",
"Atlantic/Faroe":"WET0WEST,M3.5.0/1,M10.5.0",
"Atlantic/Madeira":"WET0WEST,M3.5.0/1,M10.5.0",
"Atlantic/Reykjavik":"GMT0",
"Atlantic/South_Georgia":"<-02>2",
"Atlantic/St_Helena":"GMT0",
"Atlantic/Stanley":"<-03>3",
"Australia/Adelaide":"ACST-9:30ACDT,M10.1.0,M4.1.0/3",
"Australia/Brisbane":"AEST-10",
"Australia/Broken_Hill":"ACST-9:30ACDT,M10.1.0,M4.1.0/3",
"Australia/Currie":"AEST-10AEDT,M10.1.0,M4.1.0/3",
"Australia/Darwin":"ACST-9:30",
"Australia/Eucla":"<+0845>-8:45",
"Australia/Hobart":"AEST-10AEDT,M10.1.0,M4.1.0/3",
"Australia/Lindeman":"AEST-10",
"Australia/Lord_Howe":"<+1030>-10:30<+11>-11,M10.1.0,M4.1.0",
"Australia/Melbourne":"AEST-10AEDT,M10.1.0,M4.1.0/3",
"Australia/Perth":"AWST-8",
"Australia/Sydney":"AEST-10AEDT,M10.1.0,M4.1.0/3",
"Europe/Amsterdam":"CET-1CEST,M3.5.0,M10.5.0/3",
"Europe/Andorra":"CET-1CEST,M3.5.0,M10.5.0/3",
"Europe/Astrakhan":"<+04>-4",
"Europe/Athens":"EET-2EEST,M3.5.0/3,M10.5.0/4",
"Europe/Belgrade":"CET-1CEST,M3.5.0,M10.5.0/3",
"Europe/Berlin":"CET-1CEST,M3.5.0,M10.5.0/3",
"Europe/Bratislava":"CET-1CEST,M3.5.0,M10.5.0/3",
"Europe/Brussels":"CET-1CEST,M3.5.0,M10.5.0/3",
"Europe/Bucharest":"EET-2EEST,M3.5.0/3,M10.5.0/4",
"Europe/Budapest":"CET-1CEST,M3.5.0,M10.5.0/3",
"Europe/Busingen":"CET-1CEST,M3.5.0,M10.5.0/3",
"Europe/Chisinau":"EET-2EEST,M3.5.0,M10.5.0/3",
"Europe/Copenhagen":"CET-1CEST,M3.5.0,M10.5.0/3",
"Europe/Dublin":"IST-1GMT0,M10.5.0,M3.5.0/1",
"Europe/Gibraltar":"CET-1CEST,M3.5.0,M10.5.0/3",
"Europe/Guernsey":"GMT0BST,M3.5.0/1,M10.5.0",
"Europe/Helsinki":"EET-2EEST,M3.5.0/3,M10.5.0/4",
"Europe/Isle_of_Man":"GMT0BST,M3.5.0/1,M10.5.0",
"Europe/Istanbul":"<+03>-3",
"Europe/Jersey":"GMT0BST,M3.5.0/1,M10.5.0",
"Europe/Kaliningrad":"EET-2",
"Europe/Kiev":"EET-2EEST,M3.5.0/3,M10.5.0/4",
"Europe/Kirov":"MSK-3",
"Europe/Lisbon":"WET0WEST,M3.5.0/1,M10.5.0",
"Europe/Ljubljana":"CET-1CEST,M3.5.0,M10.5.0/3",
"Europe/London":"GMT0BST,M3.5.0/1,M10.5.0",
"Europe/Luxembourg":"CET-1CEST,M3.5.0,M10.5.0/3",
"Europe/Madrid":"CET-1CEST,M3.5.0,M10.5.0/3",
"Europe/Malta":"CET-1CEST,M3.5.0,M10.5.0/3",
"Europe/Mariehamn":"EET-2EEST,M3.5.0/3,M10.5.0/4",
"Europe/Minsk":"<+03>-3",
"Europe/Monaco":"CET-1CEST,M3.5.0,M10.5.0/3",
"Europe/Moscow":"MSK-3",
"Europe/Oslo":"CET-1CEST,M3.5.0,M10.5.0/3",
"Europe/Paris":"CET-1CEST,M3.5.0,M10.5.0/3",
"Europe/Podgorica":"CET-1CEST,M3.5.0,M10.5.0/3",
"Europe/Prague":"CET-1CEST,M3.5.0,M10.5.0/3",
"Europe/Riga":"EET-2EEST,M3.5.0/3,M10.5.0/4",
"Europe/Rome":"CET-1CEST,M3.5.0,M10.5.0/3",
"Europe/Samara":"<+04>-4",
"Europe/San_Marino":"CET-1CEST,M3.5.0,M10.5.0/3",
"Europe/Sarajevo":"CET-1CEST,M3.5.0,M10.5.0/3",
"Europe/Saratov":"<+04>-4",
"Europe/Simferopol":"MSK-3",
"Europe/Skopje":"CET-1CEST,M3.5.0,M10.5.0/3",
"Europe/Sofia":"EET-2EEST,M3.5.0/3,M10.5.0/4",
"Europe/Stockholm":"CET-1CEST,M3.5.0,M10.5.0/3",
"Europe/Tallinn":"EET-2EEST,M3.5.0/3,M10.5.0/4",
"Europe/Tirane":"CET-1CEST,M3.5.0,M10.5.0/3",
"Europe/Ulyanovsk":"<+04>-4",
"Europe/Uzhgorod":"EET-2EEST,M3.5.0/3,M10.5.0/4",
"Europe/Vaduz":"CET-1CEST,M3.5.0,M10.5.0/3",
"Europe/Vatican":"CET-1CEST,M3.5.0,M10.5.0/3",
"Europe/Vienna":"CET-1CEST,M3.5.0,M10.5.0/3",
"Europe/Vilnius":"EET-2EEST,M3.5.0/3,M10.5.0/4",
"Europe/Volgograd":"MSK-3",
"Europe/Warsaw":"CET-1CEST,M3.5.0,M10.5.0/3",
"Europe/Zagreb":"CET-1CEST,M3.5.0,M10.5.0/3",
"Europe/Zaporozhye":"EET-2EEST,M3.5.0/3,M10.5.0/4",
"Europe/Zurich":"CET-1CEST,M3.5.0,M10.5.0/3",
"Indian/Antananarivo":"EAT-3",
"Indian/Chagos":"<+06>-6",
"Indian/Christmas":"<+07>-7",
"Indian/Cocos":"<+0630>-6:30",
"Indian/Comoro":"EAT-3",
"Indian/Kerguelen":"<+05>-5",
"Indian/Mahe":"<+04>-4",
"Indian/Maldives":"<+05>-5",
"Indian/Mauritius":"<+04>-4",
"Indian/Mayotte":"EAT-3",
"Indian/Reunion":"<+04>-4",
"Pacific/Apia":"<+13>-13",
"Pacific/Auckland":"NZST-12NZDT,M9.5.0,M4.1.0/3",
"Pacific/Bougainville":"<+11>-11",
"Pacific/Chatham":"<+1245>-12:45<+1345>,M9.5.0/2:45,M4.1.0/3:45",
"Pacific/Chuuk":"<+10>-10",
"Pacific/Easter":"<-06>6<-05>,M9.1.6/22,M4.1.6/22",
"Pacific/Efate":"<+11>-11",
"Pacific/Enderbury":"<+13>-13",
"Pacific/Fakaofo":"<+13>-13",
"Pacific/Fiji":"<+12>-12",
"Pacific/Funafuti":"<+12>-12",
"Pacific/Galapagos":"<-06>6",
"Pacific/Gambier":"<-09>9",
"Pacific/Guadalcanal":"<+11>-11",
"Pacific/Guam":"ChST-10",
"Pacific/Honolulu":"HST10",
"Pacific/Kiritimati":"<+14>-14",
"Pacific/Kosrae":"<+11>-11",
"Pacific/Kwajalein":"<+12>-12",
"Pacific/Majuro":"<+12>-12",
"Pacific/Marquesas":"<-0930>9:30",
"Pacific/Midway":"SST11",
"Pacific/Nauru":"<+12>-12",
"Pacific/Niue":"<-11>11",
"Pacific/Norfolk":"<+11>-11<+12>,M10.1.0,M4.1.0/3",
"Pacific/Noumea":"<+11>-11",
"Pacific/Pago_Pago":"SST11",
"Pacific/Palau":"<+09>-9",
"Pacific/Pitcairn":"<-08>8",
"Pacific/Pohnpei":"<+11>-11",
"Pacific/Port_Moresby":"<+10>-10",
"Pacific/Rarotonga":"<-10>10",
"Pacific/Saipan":"ChST-10",
"Pacific/Tahiti":"<-10>10",
"Pacific/Tarawa":"<+12>-12",
"Pacific/Tongatapu":"<+13>-13",
"Pacific/Wake":"<+12>-12",
"Pacific/Wallis":"<+12>-12",
"Etc/UTC":"UTC0",
"Etc/GMT":"GMT0",
"Etc/GMT+1":"<-01>1",
"Etc/GMT+2":"<-02>2",
"Etc/GMT+3":"<-03>3",
"Etc/GMT+4":"<-04>4",
"Etc/GMT+5":"<-05>5",
"Etc/GMT+6":"<-06>6",
"Etc/GMT+7":"<-07>7",
"Etc/GMT+8":"<-08>8",
"Etc/GMT+9":"<-09>9",
"Etc/GMT+10":"<-10>10",
"Etc/GMT+11":"<-11>11",
"Etc/GMT+12":"<-12>12",
"Etc/GMT-1":"<+01>-1",
"Etc/GMT-2":"<+02>-2",
"Etc/GMT-3":"<+03>-3",
"Etc/GMT-4":"<+04>-4",
"Etc/GMT-5":"<+05>-5",
"Etc/GMT-6":"<+06>-6",
"Etc/GMT-7":"<+07>-7",
"Etc/GMT-8":"<+08>-8",
"Etc/GMT-9":"<+09>-9",
"Etc/GMT-10":"<+10>-10",
"Etc/GMT-11":"<+11>-11",
"Etc/GMT-12":"<+12>-12",
"Etc/GMT-13":"<+13>-13",
"Etc/GMT-14":"<+14>-14",
"Etc/Zulu":"UTC0"
}

View File

@@ -23,10 +23,15 @@ Import("env")
platform = env.PioPlatform()
import sys
import os
import subprocess
from os.path import join
sys.path.append(join(platform.get_package_dir("tool-esptoolpy")))
import esptool
def normalize_paths(cmd):
for i, arg in enumerate(cmd):
if isinstance(arg, str) and '/' in arg:
cmd[i] = os.path.normpath(arg)
return cmd
def esp32_create_combined_bin(source, target, env):
print("Generating combined binary for serial flashing")
@@ -39,26 +44,21 @@ def esp32_create_combined_bin(source, target, env):
sections = env.subst(env.get("FLASH_EXTRA_IMAGES"))
firmware_name = env.subst("$BUILD_DIR/${PROGNAME}.bin")
chip = env.get("BOARD_MCU")
flash_size = env.BoardConfig().get("upload.flash_size")
flash_freq = env.BoardConfig().get("build.f_flash", '40m')
flash_freq = flash_freq.replace('000000L', 'm')
flash_mode = env.BoardConfig().get("build.flash_mode", "dio")
memory_type = env.BoardConfig().get("build.arduino.memory_type", "qio_qspi")
if flash_mode == "qio" or flash_mode == "qout":
flash_mode = "dio"
if memory_type == "opi_opi" or memory_type == "opi_qspi":
flash_mode = "dout"
flash_size = env.BoardConfig().get("upload.flash_size", "4MB")
flash_mode = env["__get_board_flash_mode"](env)
flash_freq = env["__get_board_f_flash"](env)
cmd = [
"--chip",
chip,
"merge_bin",
"merge-bin",
"-o",
new_file_name,
"--flash_mode",
"--flash-mode",
flash_mode,
"--flash_freq",
"--flash-freq",
flash_freq,
"--flash_size",
"--flash-size",
flash_size,
]
@@ -71,9 +71,12 @@ def esp32_create_combined_bin(source, target, env):
print(f" - {hex(app_offset)} | {firmware_name}")
cmd += [hex(app_offset), firmware_name]
print('Using esptool.py arguments: %s' % ' '.join(cmd))
esptool.main(cmd)
# print('Using esptool.py arguments: %s' % ' '.join(cmd))
cmdline = [env.subst("$OBJCOPY")] + normalize_paths(cmd)
print('Command Line: %s' % cmdline)
result = subprocess.run(cmdline, text=True, check=False, stdout=subprocess.DEVNULL)
if result.returncode != 0:
print(f"esptool create firmware failed with exit code: {result.returncode}")
env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", esp32_create_combined_bin)