80 Commits

Author SHA1 Message Date
Yurii
6a9bd9673a fix: `Sensors::cleanDallasInstances()` fixed 2024-11-10 01:32:42 +03:00
Yurii
5aea80e630 fix: fix typos 2024-11-10 01:30:28 +03:00
Yurii
fcf7d61ca5 fix: polling ntc on esp8266 fixed 2024-11-09 17:41:39 +03:00
Yurii
e71f3868fd refactor: dynamic sensors 2024-11-09 17:10:26 +03:00
Yurii
c3d0d94806 fix; fix typo 2024-11-01 12:49:35 +03:00
Yurii
e4211c872c fix: hysteresis with native heating control has been fixed 2024-11-01 12:47:32 +03:00
Yurii
467cfea449 feat: ability to use return heat carrier temp as indoor temp 2024-11-01 04:16:50 +03:00
Yurii
0e3473e065 fix: fix typo 2024-11-01 04:10:56 +03:00
Yurii
8780e5245a fix: for some HA entities added missing parameter enabled_by_default 2024-11-01 03:33:35 +03:00
Yurii
94e8288d76 feat: added entities to HA: connected, rssi, battery, humidity for indoor and outdoor sensors; some entities are disabled by default 2024-11-01 02:36:45 +03:00
Yurii
261a53207c refactor: improved turbo mode 2024-10-31 22:35:23 +03:00
Yurii
1dbc895cdb refactor: increased delay before sending data to MQTT after connection 2024-10-31 22:33:11 +03:00
Yurii
747d8841bc refactor: removed resetting modulation and power if response not valid 2024-10-31 22:30:37 +03:00
Yurii
7ed47a4eca chore: updated equitherm calculator 2024-10-31 22:29:04 +03:00
Yurii
0ccea290cb fix: try to auto determine the min boiler power 2024-10-31 05:29:24 +03:00
Yurii
c03df67900 fix: fixed crash if BLE service not found #88 2024-10-31 05:25:34 +03:00
Yurii
f86857c279 feat: improved turbo mode
- added turbo factor parameter
- implemented turbo mode for PID
2024-10-31 05:22:41 +03:00
Yurii
acd8348a5b feat: try to auto determine the min boiler power 2024-10-31 03:49:00 +03:00
Yurii
cdde3c30af chore: fix typos 2024-10-31 02:42:46 +03:00
Yurii
392242ef3e refactor: vendor list updated 2024-10-31 01:36:50 +03:00
Yurii
a6e8953807 refactor: reworked emergency mode; reworked hysteresis algorithm; improved detection of connection state for MANUAL & BOILER type sensors 2024-10-31 01:36:21 +03:00
Yurii
11b1277d79 refactor: added ID validation for opentherm response 2024-10-27 04:33:46 +03:00
Yurii
f62e687d3f fix: fixed pressure and flow validation when using correction 2024-10-27 04:31:35 +03:00
Yurii
42fa95969f fix: compilation on windows fixed for C3, C6 2024-10-27 03:36:18 +03:00
Yurii
56a0d1322f fix: display of fault code and diag code in logs fixed 2024-10-26 21:37:23 +03:00
Yurii
45762967ee refactor: fix wifi scan on esp32, connection timeouts changed 2024-10-26 20:18:23 +03:00
Yurii
10f9cde17a chore: bump pioarduino/platform-espressif32 from 3.0.6 to 3.1.0 rc2 2024-10-26 20:16:54 +03:00
Yurii
351a884685 refactor: increased max request size for /api/settings 2024-10-24 05:13:29 +03:00
Yurii
98db62cc9e fix: fix typo 2024-10-24 05:00:52 +03:00
Yurii
355d983437 chore: copy elf to build dir 2024-10-24 04:26:46 +03:00
Yurii
3d11d13631 feat: added crash recorder and ability to save dump 2024-10-24 04:01:14 +03:00
Yurii
6c4f8a78a0 refactor: added defaults for serial & telnet 2024-10-24 04:00:06 +03:00
Yurii
3fb5eb32c3 refactor: increased max request size for /api/vars and /api/backup/restore 2024-10-24 03:54:26 +03:00
Yurii
c1447098da chore: bump framework-arduinoespressif32 from 3.0.5 to 3.0.6 2024-10-24 03:52:01 +03:00
Yurii
87b222e7bc refactor: increased the max value of dt for pid to 1800 sec 2024-10-21 21:34:53 +03:00
Yurii
0eea1b8121 fix: change of log level when wifi is not connected 2024-10-18 06:45:16 +03:00
Yurii
7f701a74e7 feat: fault state gpio setting replaced with cascade control 2024-10-18 06:14:09 +03:00
Yurii
57cf98ca19 refactor: cosmetic changes; move maxModulation setting to opentherm section 2024-10-15 05:09:20 +03:00
Yurii
c32c643442 refactor: more logs 2024-10-15 04:21:20 +03:00
Yurii
5553a13cc0 feat: added log level setting 2024-10-15 04:07:00 +03:00
Yurii
a9e97c15ad refactor: more logs; improved sensor of current boiler power: added settings min & max boiler power 2024-10-15 02:10:46 +03:00
Yurii
dc62f99b7d feat: added polling of min modulation and max boiler power; added sensor for current boiler power 2024-10-14 19:54:26 +03:00
Yurii
05ff426b28 chore: bump version to 1.4.5 2024-10-13 22:39:20 +03:00
Yurii
45af7a30d8 refactor: cosmetic changes; added coeff. setting for filtering numeric values 2024-10-13 21:42:33 +03:00
Yurii
8aab541afa fix: added min DHW flow rate of 0.1 l/min 2024-10-11 01:38:05 +03:00
Yurii
7672c4b927 fix: fix types 2024-10-11 01:33:49 +03:00
Yurii
b0a9460257 feat: added correction coeff. settings for pressure and dhw flow rate 2024-10-11 01:29:50 +03:00
Yurii
3c69f1295e feat: added opentherm option for filtering numeric values 2024-10-10 21:08:13 +03:00
Yurii
282a02ecdb fix: added request for set opentherm version 2024-10-09 12:33:58 +03:00
Yurii
8503ef966f fix: small fix 2024-10-05 10:27:16 +03:00
Yurii
a4ee4c5224 feat: added diagnostic code polling via opentherm, added hex value for fault code and diag code 2024-10-05 10:03:14 +03:00
Yurii
4478e8f204 chore: updated platformio.ini for ESP32 C6 2024-10-01 01:45:57 +03:00
Yurii
a50c13fd8a Merge branch 'master' of https://github.com/Laxilef/OTGateway 2024-10-01 01:39:27 +03:00
Yurii
ee1e7f92b2 fix: added delay before start web server 2024-10-01 01:39:23 +03:00
Yurii
5704075682 chore: switch from PaulStoffregen/OneWire to pstolarz/OneWireNg 2024-10-01 01:38:57 +03:00
Yurii
52e4933923 fix: compatibility with framework-arduinoespressif32 version 3.0.5 2024-10-01 01:36:44 +03:00
Yurii
00a82ca3e5 chore: switch from espressif/arduino-esp32 to pioarduino/platform-espressif32 for new core 2024-10-01 01:35:36 +03:00
Yurii
7658aeaa8c feat: added opentherm option 'immergasFix' 2024-10-01 01:32:35 +03:00
Yurii
790ff5a011 feat: added Radiant to vendor list 2024-09-30 23:56:52 +03:00
dependabot[bot]
935cf27139 chore: bump peterus/platformio_dependabot from 1.1.1 to 1.2.0 (#80)
Bumps [peterus/platformio_dependabot](https://github.com/peterus/platformio_dependabot) from 1.1.1 to 1.2.0.
- [Release notes](https://github.com/peterus/platformio_dependabot/releases)
- [Commits](https://github.com/peterus/platformio_dependabot/compare/v1.1.1...v1.2.0)

---
updated-dependencies:
- dependency-name: peterus/platformio_dependabot
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-14 00:36:24 +03:00
Yurii
503068f6e7 chore: update readme 2024-09-06 20:29:50 +03:00
Yurii
5c868d589d chore: update files for production 2024-09-06 20:25:29 +03:00
Yurii
1cca8ffd5d chore: update assets 2024-09-06 19:40:30 +03:00
Yurii
f291eb33ac chore: added assets 2024-09-03 18:00:12 +03:00
Yurii
f0c505c332 chore: added blueprints 2024-09-03 17:38:59 +03:00
Yurii
7eafe4a90b fix: compatibility with framework-arduinoespressif32 version 3.0.4 2024-08-22 04:57:13 +03:00
Yurii
d23527a48b chore: bump framework-arduinoespressif32 from 3.0.1 to 3.0.4 2024-08-22 03:34:17 +03:00
Yurii
c341f86e5c refactor: cosmetic changes 2024-08-22 03:33:12 +03:00
Yurii
939ed6cdab chore: bump bblanchon/ArduinoJson from 7.0.4 to 7.1.0 2024-08-22 00:46:13 +03:00
Yurii
2da41707a9 chore: bump version to 1.4.4 2024-08-20 23:39:50 +03:00
Yurii
460cb01146 chore: removed wowki support 2024-08-20 23:28:09 +03:00
Yurii
1b2bc8e200 feat: added feat use of BLE external sensor; added events onIndoorSensorDisconnect and onOutdoorSensorDisconnect for emergency mode; added polling of rssi, humidity, battery for BLE sensors 2024-08-20 19:06:18 +03:00
Yurii
d5acb44648 fix: text of action buttons fixed 2024-08-20 19:01:08 +03:00
Yurii
c64cf41757 Merge branch 'master' of https://github.com/Laxilef/OTGateway 2024-08-20 02:13:42 +03:00
Yurii
9250bb26f2 fix: locale detection error fixed 2024-08-19 14:45:06 +03:00
dependabot[bot]
b2c6eca2d5 chore: bump peterus/platformio_dependabot from 1.1.0 to 1.1.1 (#77)
Bumps [peterus/platformio_dependabot](https://github.com/peterus/platformio_dependabot) from 1.1.0 to 1.1.1.
- [Release notes](https://github.com/peterus/platformio_dependabot/releases)
- [Commits](https://github.com/peterus/platformio_dependabot/compare/v1.1.0...v1.1.1)

---
updated-dependencies:
- dependency-name: peterus/platformio_dependabot
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-02 17:27:41 +03:00
Yurii
5cec043015 chore: bump platformio_dependabot to 1.1.0 2024-06-22 20:28:44 +03:00
github-actions[bot]
7c9a483677 chore: Bump NimBLE-Arduino to 1.4.2 (#75) 2024-06-21 04:18:17 +03:00
github-actions[bot]
1bfe7a688a chore: Bump TinyLogger to 1.1.1 (#74)
Co-authored-by: root <root@5e361a757fb5>
2024-06-21 04:17:22 +03:00
Yurii
05104aa8eb chore: create dependabot.yml, pio-dependabot.yaml 2024-06-21 03:44:25 +03:00
56 changed files with 16624 additions and 9141 deletions

11
.github/dependabot.yaml vendored Normal file
View File

@@ -0,0 +1,11 @@
version: 2
updates:
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "daily"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "daily"

22
.github/workflows/pio-dependabot.yaml vendored Normal file
View File

@@ -0,0 +1,22 @@
name: PlatformIO Dependabot
on:
workflow_dispatch: # option to manually trigger the workflow
schedule:
# Runs every day at 00:00
- cron: "0 0 * * *"
permissions:
contents: write
pull-requests: write
jobs:
dependabot:
runs-on: ubuntu-latest
name: run PlatformIO Dependabot
steps:
- name: Checkout
uses: actions/checkout@v4
- name: run PlatformIO Dependabot
uses: peterus/platformio_dependabot@v1.2.0
with:
github_token: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -42,7 +42,7 @@ All available information and instructions can be found in the wiki:
* [Quick Start](https://github.com/Laxilef/OTGateway/wiki#quick-start) * [Quick Start](https://github.com/Laxilef/OTGateway/wiki#quick-start)
* [Build firmware](https://github.com/Laxilef/OTGateway/wiki#build-firmware) * [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) * [Flash firmware via ESP Flash Download Tool](https://github.com/Laxilef/OTGateway/wiki#flash-firmware-via-esp-flash-download-tool)
* [HomeAsssistant settings](https://github.com/Laxilef/OTGateway/wiki#homeasssistant-settings) * [Settings](https://github.com/Laxilef/OTGateway/wiki#settings)
* [External temperature sensors](https://github.com/Laxilef/OTGateway/wiki#external-temperature-sensors) * [External temperature sensors](https://github.com/Laxilef/OTGateway/wiki#external-temperature-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 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) * [Reporting outdoor temperature from Home Assistant weather integration](https://github.com/Laxilef/OTGateway/wiki#reporting-outdoor-temperature-from-home-assistant-weather-integration)

BIN
assets/2D_PCB_bottom.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 261 KiB

BIN
assets/2D_PCB_top.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 326 KiB

BIN
assets/3D_PCB.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 684 KiB

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,12 @@
<?xml version="1.0"?>
<svg xmlns="http://www.w3.org/2000/svg" height="40" aria-label="Import blueprint to My Home Assistant" style="border-radius:24px;width:auto" viewBox="0 0 592 96">
<rect width="592" height="96" fill="#18BCF2" rx="48"/>
<path fill="#fff" d="M42.55 60.2h4.59V36.75h-4.59Zm10.35 0h4.59V43.99l7.23 10.58 7.24-10.62V60.2h4.56V36.75h-4.56v.03l-.4-.03-6.84 10.12-6.83-10.12-.4.03v-.03H52.9Zm29.38 0h4.59v-7.77h4.49c4.72 0 8.07-3.29 8.07-7.87 0-4.59-3.48-7.88-8.44-7.84l-4.66.03h-4.05Zm8.61-19.26c2.27-.04 3.88 1.47 3.88 3.62 0 2.14-1.47 3.65-3.51 3.65h-4.39v-7.27Zm22.98 19.66c6.97 0 11.92-5.02 11.92-12.09 0-7.1-4.95-12.13-12.02-12.13-7.04 0-12 4.99-12 12.13 0 7.07 5 12.09 12.1 12.09m0-4.19c-4.36 0-7.41-3.28-7.41-7.9 0-4.66 3.02-7.94 7.31-7.94 4.32 0 7.33 3.28 7.33 7.94 0 4.62-2.98 7.9-7.23 7.9m16.31 3.79h4.59v-8.31h3.52l4.79 8.31h5.19l-5.42-9.11c2.71-1.21 4.48-3.69 4.48-6.77 0-4.45-3.48-7.64-8.44-7.6l-4.65.03h-4.06Zm8.51-19.26c2.28 0 3.89 1.37 3.89 3.38 0 1.98-1.61 3.38-3.65 3.38h-4.16v-6.76Zm18.33 19.26h4.59V40.94h7v-4.19h-18.63v4.19h7.04Zm23.91 0h9.02c4.69 0 7.77-2.41 7.77-6.7 0-2.65-1.24-4.42-3.42-5.53 1.64-1.17 2.61-2.78 2.61-4.55 0-4.43-3.21-6.7-8.04-6.7l-3.45.03h-4.49Zm7.68-19.8c2.21-.03 3.61 1.07 3.61 2.95s-1.27 2.95-3.25 2.95h-3.55v-5.9Zm.53 9.65c2.41-.03 3.89 1.17 3.89 3.08 0 1.81-1.34 3.02-3.52 3.02h-4.09v-6.1h.24Zm12.97 10.15h14.9v-4.19H206.7V36.75h-4.59Zm18.32-8.74c0 5.62 3.69 9.21 9.51 9.21 5.9 0 9.65-3.59 9.65-9.21V36.75H235v14.71c0 3.01-1.97 4.95-4.99 4.95-3.01 0-4.99-1.94-4.99-4.95V36.75h-4.59Zm24.59 8.74h14.97v-4.19h-10.38v-5.66h8.84v-4.09h-8.84v-5.32h10.25v-4.19h-14.84Zm20.4 0h4.59v-7.77h4.49c4.72 0 8.07-3.29 8.07-7.87 0-4.59-3.48-7.88-8.44-7.84l-4.66.03h-4.05Zm8.61-19.26c2.28-.04 3.89 1.47 3.89 3.62 0 2.14-1.48 3.65-3.52 3.65h-4.39v-7.27Zm12.26 19.26h4.59v-8.31h3.52l4.79 8.31h5.19l-5.43-9.11c2.72-1.21 4.49-3.69 4.49-6.77 0-4.45-3.48-7.64-8.44-7.6l-4.65.03h-4.06Zm8.51-19.26c2.28 0 3.89 1.37 3.89 3.38 0 1.98-1.61 3.38-3.65 3.38h-4.16v-6.76Zm13.77 19.26h4.59V36.75h-4.59Zm10.35 0 4.59.03V44.12l11.66 16.08h4.55V36.75h-4.55v15.81l-11.46-15.81h-4.79Zm31.52 0h4.59V40.94h7.01v-4.19h-18.63v4.19h7.03Zm28.88 0h4.59V40.94h7v-4.19h-18.62v4.19h7.03Zm26 .4c6.97 0 11.92-5.02 11.92-12.09 0-7.1-4.95-12.13-12.02-12.13-7.04 0-12 4.99-12 12.13 0 7.07 5 12.09 12.1 12.09m0-4.19c-4.36 0-7.41-3.28-7.41-7.9 0-4.66 3.02-7.94 7.31-7.94 4.32 0 7.33 3.28 7.33 7.94 0 4.62-2.98 7.9-7.23 7.9"/>
<g style="transform:translate(95px,0)">
<rect width="137" height="64" x="344" y="16" fill="#F2F4F9" rx="32"/>
<path fill="#18BCF2" d="M394.419 37.047V60.5h-4.297V46.797L384.716 60.5h-4.157l-5.343-13.594V60.5h-4.188V37.047h4.188l7.422 18.36 7.484-18.36zm9.365 0 5.344 9.89 5.344-9.89h4.766l-7.969 14.14V60.5h-4.391v-9.312l-8.031-14.141zM457 60c0 1.65-1.35 3-3 3h-24c-1.65 0-3-1.35-3-3v-9c0-1.65.95-3.95 2.12-5.12l10.76-10.76a3 3 0 0 1 4.24 0l10.76 10.76c1.17 1.17 2.12 3.47 2.12 5.12z"/>
<path fill="#F2F4F9" stroke="#F2F4F9" d="M442 45.5a2 2 0 1 0 0-4 2 2 0 0 0 0 4Z"/>
<path fill="#F2F4F9" stroke="#F2F4F9" stroke-miterlimit="10" d="M449.5 53.5a2 2 0 1 0 0-4 2 2 0 0 0 0 4ZM434.5 57.5a2 2 0 1 0 0-4 2 2 0 0 0 0 4Z"/>
<path fill="none" stroke="#F2F4F9" stroke-miterlimit="10" stroke-width="2.25" d="M442 43.48V63l-7.5-7.5M449.5 51.46l-7.41 7.41"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Binary file not shown.

View File

@@ -1,3 +1,6 @@
# Package for Home Assistant Packages
# More info: https://www.home-assistant.io/docs/configuration/packages/
dhw_meter: dhw_meter:
sensor: sensor:
- platform: integration - platform: integration

View File

@@ -1,29 +0,0 @@
# Script for reporting outdoor temperature to the controller from home assistant weather integration
# Updated: 07.12.2023
alias: Report outdoor temp to controller from weather
description: ""
variables:
# The source weather from which we take the temperature
source_entity: "weather.home"
# Target entity number where we set the temperature
# If the prefix has not changed, then you do not need to change it
target_entity: "number.opentherm_outdoor_temp"
trigger:
- platform: time_pattern
seconds: /30
condition:
- condition: template
value_template: "{{ states(source_entity) != 'unavailable' and states(target_entity) != 'unavailable' }}"
action:
- if:
- condition: template
value_template: "{{ (state_attr(source_entity, 'temperature')|float(0) - states(target_entity)|float(0)) | abs | round(2) >= 0.1 }}"
then:
- service: number.set_value
data:
value: "{{ state_attr(source_entity, 'temperature')|float(0)|round(2) }}"
target:
entity_id: "{{ target_entity }}"
mode: single

View File

@@ -1,30 +0,0 @@
# Script for reporting indoor/outdoor temperature to the controller from any home assistant sensor
# Updated: 07.12.2023
alias: Report temp to controller
description: ""
variables:
# The source sensor from which we take the temperature
source_entity: "sensor.livingroom_temperature"
# Target entity number where we set the temperature
# To report indoor temperature: number.opentherm_indoor_temp
# To report outdoor temperature: number.opentherm_outdoor_temp
target_entity: "number.opentherm_indoor_temp"
trigger:
- platform: time_pattern
seconds: /30
condition:
- condition: template
value_template: "{{ states(source_entity) != 'unavailable' and states(target_entity) != 'unavailable' }}"
action:
- if:
- condition: template
value_template: "{{ (states(source_entity)|float(0) - states(target_entity)|float(0)) | abs | round(2) >= 0.01 }}"
then:
- service: number.set_value
data:
value: "{{ states(source_entity)|float(0)|round(2) }}"
target:
entity_id: "{{ target_entity }}"
mode: single

View File

@@ -0,0 +1,48 @@
# Blueprint for reporting indoor/outdoor temperature to OpenTherm Gateway from any home assistant sensor
# Updated: 03.09.2024
blueprint:
name: Report temp to OpenTherm Gateway
domain: automation
author: "Laxilef"
input:
source_entity:
name: Source entity
description: "Temperature data source"
selector:
entity:
multiple: false
filter:
- domain: sensor
device_class: temperature
target_entity:
name: Target entity
description: "Usually ``number.opentherm_indoor_temp`` or ``number.opentherm_outdoor_temp``"
default: "number.opentherm_indoor_temp"
selector:
entity:
multiple: false
filter:
- domain: number
mode: single
variables:
source_entity: !input source_entity
target_entity: !input target_entity
trigger:
- platform: time_pattern
seconds: /30
condition:
- condition: template
value_template: "{{ states(source_entity) != 'unavailable' and states(target_entity) != 'unavailable' }}"
action:
- if:
- condition: template
value_template: "{{ (states(source_entity)|float(0) - states(target_entity)|float(0)) | abs | round(2) >= 0.01 }}"
then:
- service: number.set_value
data:
value: "{{ states(source_entity)|float(0)|round(2) }}"
target:
entity_id: "{{ target_entity }}"

View File

@@ -0,0 +1,47 @@
# Blueprint for reporting temperature to OpenTherm Gateway from home assistant weather integration
# Updated: 03.09.2024
blueprint:
name: Report temp to OpenTherm Gateway from Weather
domain: automation
author: "Laxilef"
input:
source_entity:
name: Source entity
description: "Temperature data source"
selector:
entity:
multiple: false
filter:
- domain: weather
target_entity:
name: Target entity
description: "Usually ``number.opentherm_outdoor_temp``"
default: "number.opentherm_outdoor_temp"
selector:
entity:
multiple: false
filter:
- domain: number
mode: single
variables:
source_entity: !input source_entity
target_entity: !input target_entity
trigger:
- platform: time_pattern
seconds: /30
condition:
- condition: template
value_template: "{{ states(source_entity) != 'unavailable' and states(target_entity) != 'unavailable' }}"
action:
- if:
- condition: template
value_template: "{{ (state_attr(source_entity, 'temperature')|float(0) - states(target_entity)|float(0)) | abs | round(2) >= 0.1 }}"
then:
- service: number.set_value
data:
value: "{{ state_attr(source_entity, 'temperature')|float(0)|round(2) }}"
target:
entity_id: "{{ target_entity }}"

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 675 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 154 KiB

View File

@@ -10,7 +10,7 @@ public:
free(this->buffer); free(this->buffer);
} }
void send(int code, const char* contentType, JsonDocument& content) { void send(int code, const char* contentType, const JsonVariantConst content, bool pretty = false) {
#ifdef ARDUINO_ARCH_ESP8266 #ifdef ARDUINO_ARCH_ESP8266
if (!this->webServer->chunkedResponseModeStart(code, contentType)) { if (!this->webServer->chunkedResponseModeStart(code, contentType)) {
this->webServer->send(505, F("text/html"), F("HTTP1.1 required")); this->webServer->send(505, F("text/html"), F("HTTP1.1 required"));
@@ -24,7 +24,13 @@ public:
this->webServer->send(code, contentType, emptyString); this->webServer->send(code, contentType, emptyString);
#endif #endif
serializeJson(content, *this); if (pretty) {
serializeJsonPretty(content, *this);
} else {
serializeJson(content, *this);
}
this->flush(); this->flush();
#ifdef ARDUINO_ARCH_ESP8266 #ifdef ARDUINO_ARCH_ESP8266

View File

@@ -79,7 +79,7 @@ public:
} }
} }
unsigned long setBoilerStatus(bool enableCentralHeating, bool enableHotWater, bool enableCooling, bool enableOutsideTemperatureCompensation, bool enableCentralHeating2, bool summerWinterMode, bool dhwBlocking) { 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 unsigned int data = enableCentralHeating
| (enableHotWater << 1) | (enableHotWater << 1)
| (enableCooling << 2) | (enableCooling << 2)
@@ -87,7 +87,9 @@ public:
| (enableCentralHeating2 << 4) | (enableCentralHeating2 << 4)
| (summerWinterMode << 5) | (summerWinterMode << 5)
| (dhwBlocking << 6); | (dhwBlocking << 6);
data <<= 8; data <<= 8;
data |= lb;
return this->sendRequest(buildRequest( return this->sendRequest(buildRequest(
OpenThermMessageType::READ_DATA, OpenThermMessageType::READ_DATA,
@@ -96,66 +98,6 @@ public:
)); ));
} }
bool setHeatingCh1Temp(float temperature) {
unsigned long response = this->sendRequest(buildRequest(
OpenThermMessageType::WRITE_DATA,
OpenThermMessageID::TSet,
temperatureToData(temperature)
));
return isValidResponse(response);
}
bool setHeatingCh2Temp(float temperature) {
unsigned long response = this->sendRequest(buildRequest(
OpenThermMessageType::WRITE_DATA,
OpenThermMessageID::TsetCH2,
temperatureToData(temperature)
));
return isValidResponse(response);
}
bool setDhwTemp(float temperature) {
unsigned long response = this->sendRequest(buildRequest(
OpenThermMessageType::WRITE_DATA,
OpenThermMessageID::TdhwSet,
temperatureToData(temperature)
));
return isValidResponse(response);
}
bool setRoomSetpoint(float temperature) {
unsigned long response = this->sendRequest(buildRequest(
OpenThermMessageType::WRITE_DATA,
OpenThermMessageID::TrSet,
temperatureToData(temperature)
));
return isValidResponse(response);
}
bool setRoomSetpointCh2(float temperature) {
unsigned long response = this->sendRequest(buildRequest(
OpenThermMessageType::WRITE_DATA,
OpenThermMessageID::TrSetCH2,
temperatureToData(temperature)
));
return isValidResponse(response);
}
bool setRoomTemp(float temperature) {
unsigned long response = this->sendRequest(buildRequest(
OpenThermMessageType::WRITE_DATA,
OpenThermMessageID::Tr,
temperatureToData(temperature)
));
return isValidResponse(response);
}
bool sendBoilerReset() { bool sendBoilerReset() {
unsigned int data = 1; unsigned int data = 1;
data <<= 8; data <<= 8;
@@ -165,7 +107,7 @@ public:
data data
)); ));
return isValidResponse(response); return isValidResponse(response) && isValidResponseId(response, OpenThermMessageID::RemoteRequest);
} }
bool sendServiceReset() { bool sendServiceReset() {
@@ -177,7 +119,7 @@ public:
data data
)); ));
return isValidResponse(response); return isValidResponse(response) && isValidResponseId(response, OpenThermMessageID::RemoteRequest);
} }
bool sendWaterFilling() { bool sendWaterFilling() {
@@ -189,7 +131,13 @@ public:
data data
)); ));
return isValidResponse(response); return isValidResponse(response) && isValidResponseId(response, OpenThermMessageID::RemoteRequest);
}
static bool isValidResponseId(unsigned long response, OpenThermMessageID id) {
byte responseId = (response >> 16) & 0xFF;
return (byte)id == responseId;
} }
// converters // converters

View File

@@ -27,7 +27,7 @@ public:
} }
// лимит выходной величины // лимит выходной величины
void setLimits(int min_output, int max_output) { void setLimits(unsigned short min_output, unsigned short max_output) {
_minOut = min_output; _minOut = min_output;
_maxOut = max_output; _maxOut = max_output;
} }
@@ -40,7 +40,7 @@ public:
} }
private: private:
int _minOut = 20, _maxOut = 90; unsigned short _minOut = 20, _maxOut = 90;
// температура контура отопления в зависимости от наружной температуры // температура контура отопления в зависимости от наружной температуры
datatype getResultN() { datatype getResultN() {
@@ -58,6 +58,6 @@ private:
// Расчет поправки (ошибки) термостата // Расчет поправки (ошибки) термостата
datatype getResultT() { datatype getResultT() {
return constrain((targetTemp - indoorTemp), -2, 2) * Kt; return constrain((targetTemp - indoorTemp), -3, 3) * Kt;
} }
}; };

View File

@@ -100,8 +100,8 @@ public:
return result; return result;
} }
template <class T> template <class CT, class NT>
String getTopic(T category, T name, char nameSeparator = '/') { String makeConfigTopic(CT category, NT name, char nameSeparator = '/') {
String topic = ""; String topic = "";
topic.concat(this->prefix); topic.concat(this->prefix);
topic.concat('/'); topic.concat('/');
@@ -115,16 +115,40 @@ public:
} }
template <class T> template <class T>
String getDeviceTopic(T value, char separator = '/') { String getDeviceTopic(T value, char dpvSeparator = '/') {
String topic = ""; String topic = "";
topic.concat(this->devicePrefix); topic.concat(this->devicePrefix);
topic.concat(separator); topic.concat(dpvSeparator);
topic.concat(value); topic.concat(value);
return topic; return topic;
} }
template <class CT, class NT>
String getDeviceTopic(CT category, NT name, char dpcSeparator = '/', char cnSeparator = '/') {
String topic = "";
topic.concat(this->devicePrefix);
topic.concat(dpcSeparator);
topic.concat(category);
topic.concat(cnSeparator);
topic.concat(name);
return topic;
}
template <class CT, class NT, class ST>
String getDeviceTopic(CT category, NT name, ST suffix, char dpcSeparator = '/', char cnSeparator = '/', char nsSeparator = '/') {
String topic = "";
topic.concat(this->devicePrefix);
topic.concat(dpcSeparator);
topic.concat(category);
topic.concat(cnSeparator);
topic.concat(name);
topic.concat(nsSeparator);
topic.concat(suffix);
return topic;
}
template <class T> template <class T>
String getObjectId(T value, char separator = '_') { String getObjectIdWithPrefix(T value, char separator = '_') {
String topic = ""; String topic = "";
topic.concat(this->devicePrefix); topic.concat(this->devicePrefix);
topic.concat(separator); topic.concat(separator);

View File

@@ -25,6 +25,8 @@ const char HA_ENABLED_BY_DEFAULT[] PROGMEM = "enabled_by_default";
const char HA_UNIQUE_ID[] PROGMEM = "unique_id"; const char HA_UNIQUE_ID[] PROGMEM = "unique_id";
const char HA_OBJECT_ID[] PROGMEM = "object_id"; const char HA_OBJECT_ID[] PROGMEM = "object_id";
const char HA_ENTITY_CATEGORY[] PROGMEM = "entity_category"; const char HA_ENTITY_CATEGORY[] PROGMEM = "entity_category";
const char HA_ENTITY_CATEGORY_DIAGNOSTIC[] PROGMEM = "diagnostic";
const char HA_ENTITY_CATEGORY_CONFIG[] PROGMEM = "config";
const char HA_STATE_TOPIC[] PROGMEM = "state_topic"; const char HA_STATE_TOPIC[] PROGMEM = "state_topic";
const char HA_VALUE_TEMPLATE[] PROGMEM = "value_template"; const char HA_VALUE_TEMPLATE[] PROGMEM = "value_template";
const char HA_OPTIONS[] PROGMEM = "options"; const char HA_OPTIONS[] PROGMEM = "options";
@@ -45,6 +47,7 @@ const char HA_STATE_OFF[] PROGMEM = "state_off";
const char HA_PAYLOAD_ON[] PROGMEM = "payload_on"; const char HA_PAYLOAD_ON[] PROGMEM = "payload_on";
const char HA_PAYLOAD_OFF[] PROGMEM = "payload_off"; const char HA_PAYLOAD_OFF[] PROGMEM = "payload_off";
const char HA_STATE_CLASS[] PROGMEM = "state_class"; const char HA_STATE_CLASS[] PROGMEM = "state_class";
const char HA_STATE_CLASS_MEASUREMENT[] PROGMEM = "measurement";
const char HA_EXPIRE_AFTER[] PROGMEM = "expire_after"; const char HA_EXPIRE_AFTER[] PROGMEM = "expire_after";
const char HA_CURRENT_TEMPERATURE_TOPIC[] PROGMEM = "current_temperature_topic"; const char HA_CURRENT_TEMPERATURE_TOPIC[] PROGMEM = "current_temperature_topic";
const char HA_CURRENT_TEMPERATURE_TEMPLATE[] PROGMEM = "current_temperature_template"; const char HA_CURRENT_TEMPERATURE_TEMPLATE[] PROGMEM = "current_temperature_template";

View File

@@ -77,7 +77,7 @@ public:
#endif #endif
} }
bool publish(const char* topic, JsonDocument& doc, bool retained = false) { bool publish(const char* topic, const JsonVariantConst doc, bool retained = false) {
if (!this->client->connected()) { if (!this->client->connected()) {
this->bufferPos = 0; this->bufferPos = 0;
return false; return false;

View File

@@ -247,11 +247,19 @@ namespace NetworkUtils {
this->delayCallback(250); this->delayCallback(250);
#endif #endif
if (!this->useDhcp) { #ifdef ARDUINO_ARCH_ESP32
WiFi.config(this->staticIp, this->staticGateway, this->staticSubnet, this->staticDns); WiFi.setScanMethod(WIFI_ALL_CHANNEL_SCAN);
} WiFi.setSortMethod(WIFI_CONNECT_AP_BY_SIGNAL);
#endif
WiFi.begin(this->staSsid, this->staPassword, this->staChannel); if (!this->useDhcp) {
WiFi.begin(this->staSsid, this->staPassword, this->staChannel, nullptr, false);
WiFi.config(this->staticIp, this->staticGateway, this->staticSubnet, this->staticDns);
WiFi.reconnect();
} else {
WiFi.begin(this->staSsid, this->staPassword, this->staChannel, nullptr, true);
}
unsigned long beginConnectionTime = millis(); unsigned long beginConnectionTime = millis();
while (millis() - beginConnectionTime < timeout) { while (millis() - beginConnectionTime < timeout) {
@@ -267,7 +275,16 @@ namespace NetworkUtils {
} }
void disconnect() { void disconnect() {
#ifdef ARDUINO_ARCH_ESP32
WiFi.disconnectAsync(false, true);
const unsigned long start = millis();
while (WiFi.isConnected() && (millis() - start) < 5000) {
this->delayCallback(100);
}
#else
WiFi.disconnect(false, true); WiFi.disconnect(false, true);
#endif
} }
void loop() { void loop() {
@@ -365,10 +382,10 @@ namespace NetworkUtils {
} }
protected: protected:
const unsigned int reconnectInterval = 5000; const unsigned int reconnectInterval = 15000;
const unsigned int failedConnectTimeout = 120000; const unsigned int failedConnectTimeout = 185000;
const unsigned int connectionTimeout = 15000; const unsigned int connectionTimeout = 5000;
const unsigned int resetConnectionTimeout = 30000; const unsigned int resetConnectionTimeout = 90000;
YieldCallback yieldCallback = []() { YieldCallback yieldCallback = []() {
::yield(); ::yield();

View File

@@ -33,18 +33,16 @@ public:
} }
#if defined(ARDUINO_ARCH_ESP32) #if defined(ARDUINO_ARCH_ESP32)
bool canHandle(HTTPMethod method, const String uri) override { bool canHandle(WebServer &server, HTTPMethod method, const String &uri) override {
#else return this->canHandle(method, uri);
bool canHandle(HTTPMethod method, const String& uri) override { }
#endif #endif
bool canHandle(HTTPMethod method, const String& uri) override {
return uri.equals(this->uri) && (!this->canHandleCallback || this->canHandleCallback(method, uri)); return uri.equals(this->uri) && (!this->canHandleCallback || this->canHandleCallback(method, uri));
} }
#if defined(ARDUINO_ARCH_ESP32)
bool handle(WebServer& server, HTTPMethod method, const String uri) override {
#else
bool handle(WebServer& server, HTTPMethod method, const String& uri) override { bool handle(WebServer& server, HTTPMethod method, const String& uri) override {
#endif
if (!this->canHandle(method, uri)) { if (!this->canHandle(method, uri)) {
return false; return false;
} }

View File

@@ -28,18 +28,16 @@ public:
} }
#if defined(ARDUINO_ARCH_ESP32) #if defined(ARDUINO_ARCH_ESP32)
bool canHandle(HTTPMethod method, const String uri) override { bool canHandle(WebServer &server, HTTPMethod method, const String &uri) override {
#else return this->canHandle(method, uri);
bool canHandle(HTTPMethod method, const String& uri) override { }
#endif #endif
bool canHandle(HTTPMethod method, const String& uri) override {
return method == HTTP_GET && uri.equals(this->uri) && (!this->canHandleCallback || this->canHandleCallback(method, uri)); return method == HTTP_GET && uri.equals(this->uri) && (!this->canHandleCallback || this->canHandleCallback(method, uri));
} }
#if defined(ARDUINO_ARCH_ESP32)
bool handle(WebServer& server, HTTPMethod method, const String uri) override {
#else
bool handle(WebServer& server, HTTPMethod method, const String& uri) override { bool handle(WebServer& server, HTTPMethod method, const String& uri) override {
#endif
if (!this->canHandle(method, uri)) { if (!this->canHandle(method, uri)) {
return false; return false;
} }

View File

@@ -58,26 +58,26 @@ public:
} }
#if defined(ARDUINO_ARCH_ESP32) #if defined(ARDUINO_ARCH_ESP32)
bool canHandle(HTTPMethod method, const String uri) override { bool canHandle(WebServer &server, HTTPMethod method, const String &uri) override {
#else return this->canHandle(method, uri);
bool canHandle(HTTPMethod method, const String& uri) override { }
#endif #endif
bool canHandle(HTTPMethod method, const String& uri) override {
return method == HTTP_POST && uri.equals(this->uri) && (!this->canHandleCallback || this->canHandleCallback(method, uri)); return method == HTTP_POST && uri.equals(this->uri) && (!this->canHandleCallback || this->canHandleCallback(method, uri));
} }
#if defined(ARDUINO_ARCH_ESP32) #if defined(ARDUINO_ARCH_ESP32)
bool canUpload(const String uri) override { bool canUpload(WebServer &server, const String &uri) override {
#else return this->canUpload(uri);
bool canUpload(const String& uri) override { }
#endif #endif
bool canUpload(const String& uri) override {
return uri.equals(this->uri) && (!this->canUploadCallback || this->canUploadCallback(uri)); return uri.equals(this->uri) && (!this->canUploadCallback || this->canUploadCallback(uri));
} }
#if defined(ARDUINO_ARCH_ESP32)
bool handle(WebServer& server, HTTPMethod method, const String uri) override {
#else
bool handle(WebServer& server, HTTPMethod method, const String& uri) override { bool handle(WebServer& server, HTTPMethod method, const String& uri) override {
#endif
if (this->afterUpgradeCallback) { if (this->afterUpgradeCallback) {
this->afterUpgradeCallback(this->firmwareResult, this->filesystemResult); this->afterUpgradeCallback(this->firmwareResult, this->filesystemResult);
} }
@@ -91,11 +91,7 @@ public:
return true; return true;
} }
#if defined(ARDUINO_ARCH_ESP32)
void upload(WebServer& server, const String uri, HTTPUpload& upload) override {
#else
void upload(WebServer& server, const String& uri, HTTPUpload& upload) override { void upload(WebServer& server, const String& uri, HTTPUpload& upload) override {
#endif
UpgradeResult* result; UpgradeResult* result;
if (upload.name.equals("firmware")) { if (upload.name.equals("firmware")) {
result = &this->firmwareResult; result = &this->firmwareResult;

View File

@@ -11,22 +11,24 @@
[platformio] [platformio]
;extra_configs = secrets.ini ;extra_configs = secrets.ini
extra_configs = secrets.default.ini extra_configs = secrets.default.ini
core_dir = .pio
[env] [env]
version = 1.4.3 version = 1.5.0-alpha
framework = arduino framework = arduino
lib_deps = lib_deps =
bblanchon/ArduinoJson@^7.0.4 bblanchon/ArduinoJson@^7.1.0
;ihormelnyk/OpenTherm Library@^1.1.5 ;ihormelnyk/OpenTherm Library@^1.1.5
https://github.com/ihormelnyk/opentherm_library#master https://github.com/ihormelnyk/opentherm_library#master
arduino-libraries/ArduinoMqttClient@^0.1.8 ;arduino-libraries/ArduinoMqttClient@^0.1.8
https://github.com/Laxilef/ArduinoMqttClient.git#esp32_core_310
lennarthennigs/ESP Telnet@^2.2 lennarthennigs/ESP Telnet@^2.2
gyverlibs/FileData@^1.0.2 gyverlibs/FileData@^1.0.2
gyverlibs/GyverPID@^3.3.2 gyverlibs/GyverPID@^3.3.2
gyverlibs/GyverBlinker@^1.0 gyverlibs/GyverBlinker@^1.1.1
https://github.com/PaulStoffregen/OneWire#master https://github.com/pstolarz/Arduino-Temperature-Control-Library.git#OneWireNg
milesburton/DallasTemperature@^3.11.0 laxilef/TinyLogger@^1.1.1
laxilef/TinyLogger@^1.1.0 build_type = ${secrets.build_type}
build_flags = build_flags =
-D PIO_FRAMEWORK_ARDUINO_LWIP2_LOW_MEMORY -D PIO_FRAMEWORK_ARDUINO_LWIP2_LOW_MEMORY
;-D PIO_FRAMEWORK_ARDUINO_LWIP2_HIGHER_BANDWIDTH_LOW_FLASH ;-D PIO_FRAMEWORK_ARDUINO_LWIP2_HIGHER_BANDWIDTH_LOW_FLASH
@@ -36,9 +38,11 @@ build_flags =
;-D DEBUG_ESP_CORE -D DEBUG_ESP_WIFI -D DEBUG_ESP_HTTP_SERVER -D DEBUG_ESP_PORT=Serial ;-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_VERSION='"${this.version}"'
-D BUILD_ENV='"$PIOENV"' -D BUILD_ENV='"$PIOENV"'
-D USE_SERIAL=${secrets.use_serial} -D DEFAULT_SERIAL_ENABLED=${secrets.serial_enabled}
-D USE_TELNET=${secrets.use_telnet} -D DEFAULT_SERIAL_BAUD=${secrets.serial_baud}
-D DEBUG_BY_DEFAULT=${secrets.debug} -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_HOSTNAME='"${secrets.hostname}"'
-D DEFAULT_AP_SSID='"${secrets.ap_ssid}"' -D DEFAULT_AP_SSID='"${secrets.ap_ssid}"'
-D DEFAULT_AP_PASSWORD='"${secrets.ap_password}"' -D DEFAULT_AP_PASSWORD='"${secrets.ap_password}"'
@@ -46,6 +50,7 @@ build_flags =
-D DEFAULT_STA_PASSWORD='"${secrets.sta_password}"' -D DEFAULT_STA_PASSWORD='"${secrets.sta_password}"'
-D DEFAULT_PORTAL_LOGIN='"${secrets.portal_login}"' -D DEFAULT_PORTAL_LOGIN='"${secrets.portal_login}"'
-D DEFAULT_PORTAL_PASSWORD='"${secrets.portal_password}"' -D DEFAULT_PORTAL_PASSWORD='"${secrets.portal_password}"'
-D DEFAULT_MQTT_ENABLED=${secrets.mqtt_enabled}
-D DEFAULT_MQTT_SERVER='"${secrets.mqtt_server}"' -D DEFAULT_MQTT_SERVER='"${secrets.mqtt_server}"'
-D DEFAULT_MQTT_PORT=${secrets.mqtt_port} -D DEFAULT_MQTT_PORT=${secrets.mqtt_port}
-D DEFAULT_MQTT_USER='"${secrets.mqtt_user}"' -D DEFAULT_MQTT_USER='"${secrets.mqtt_user}"'
@@ -53,7 +58,8 @@ build_flags =
-D DEFAULT_MQTT_PREFIX='"${secrets.mqtt_prefix}"' -D DEFAULT_MQTT_PREFIX='"${secrets.mqtt_prefix}"'
upload_speed = 921600 upload_speed = 921600
monitor_speed = 115200 monitor_speed = 115200
monitor_filters = direct ;monitor_filters = direct
monitor_filters = esp32_exception_decoder
board_build.flash_mode = dio board_build.flash_mode = dio
board_build.filesystem = littlefs board_build.filesystem = littlefs
@@ -66,27 +72,32 @@ lib_deps =
lib_ignore = lib_ignore =
extra_scripts = extra_scripts =
post:tools/build.py post:tools/build.py
build_type = ${env.build_type}
build_flags = ${env.build_flags} build_flags = ${env.build_flags}
board_build.ldscript = eagle.flash.4m1m.ld board_build.ldscript = eagle.flash.4m1m.ld
[esp32_defaults] [esp32_defaults]
;platform = espressif32@^6.7 ;platform = espressif32@^6.7
platform = https://github.com/platformio/platform-espressif32.git ;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-rc2/platform-espressif32.zip
platform_packages = platform_packages =
;platformio/framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32/archive/refs/tags/2.0.17.zip
framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git#3.0.1
framework-arduinoespressif32-libs @ https://github.com/espressif/arduino-esp32/releases/download/3.0.1/esp32-arduino-libs-3.0.1.zip
board_build.partitions = esp32_partitions.csv board_build.partitions = esp32_partitions.csv
lib_deps = lib_deps =
${env.lib_deps} ${env.lib_deps}
laxilef/ESP32Scheduler@^1.0.1 laxilef/ESP32Scheduler@^1.0.1
nimble_lib = h2zero/NimBLE-Arduino@^1.4.2
lib_ignore = lib_ignore =
extra_scripts = extra_scripts =
post:tools/esp32.py post:tools/esp32.py
post:tools/build.py post:tools/build.py
build_type = ${env.build_type}
build_flags = build_flags =
${env.build_flags} ${env.build_flags}
-D CORE_DEBUG_LEVEL=0 -D CORE_DEBUG_LEVEL=0
-Wl,--wrap=esp_panic_handler
; Boards ; Boards
@@ -97,6 +108,7 @@ lib_deps = ${esp8266_defaults.lib_deps}
lib_ignore = ${esp8266_defaults.lib_ignore} lib_ignore = ${esp8266_defaults.lib_ignore}
extra_scripts = ${esp8266_defaults.extra_scripts} extra_scripts = ${esp8266_defaults.extra_scripts}
board_build.ldscript = ${esp8266_defaults.board_build.ldscript} board_build.ldscript = ${esp8266_defaults.board_build.ldscript}
build_type = ${esp8266_defaults.build_type}
build_flags = build_flags =
${esp8266_defaults.build_flags} ${esp8266_defaults.build_flags}
-D DEFAULT_OT_IN_GPIO=4 -D DEFAULT_OT_IN_GPIO=4
@@ -113,6 +125,7 @@ lib_deps = ${esp8266_defaults.lib_deps}
lib_ignore = ${esp8266_defaults.lib_ignore} lib_ignore = ${esp8266_defaults.lib_ignore}
extra_scripts = ${esp8266_defaults.extra_scripts} extra_scripts = ${esp8266_defaults.extra_scripts}
board_build.ldscript = ${esp8266_defaults.board_build.ldscript} board_build.ldscript = ${esp8266_defaults.board_build.ldscript}
build_type = ${esp8266_defaults.build_type}
build_flags = build_flags =
${esp8266_defaults.build_flags} ${esp8266_defaults.build_flags}
-D DEFAULT_OT_IN_GPIO=4 -D DEFAULT_OT_IN_GPIO=4
@@ -129,6 +142,7 @@ lib_deps = ${esp8266_defaults.lib_deps}
lib_ignore = ${esp8266_defaults.lib_ignore} lib_ignore = ${esp8266_defaults.lib_ignore}
extra_scripts = ${esp8266_defaults.extra_scripts} extra_scripts = ${esp8266_defaults.extra_scripts}
board_build.ldscript = ${esp8266_defaults.board_build.ldscript} board_build.ldscript = ${esp8266_defaults.board_build.ldscript}
build_type = ${esp8266_defaults.build_type}
build_flags = build_flags =
${esp8266_defaults.build_flags} ${esp8266_defaults.build_flags}
-D DEFAULT_OT_IN_GPIO=4 -D DEFAULT_OT_IN_GPIO=4
@@ -145,6 +159,7 @@ lib_deps = ${esp8266_defaults.lib_deps}
lib_ignore = ${esp8266_defaults.lib_ignore} lib_ignore = ${esp8266_defaults.lib_ignore}
extra_scripts = ${esp8266_defaults.extra_scripts} extra_scripts = ${esp8266_defaults.extra_scripts}
board_build.ldscript = ${esp8266_defaults.board_build.ldscript} board_build.ldscript = ${esp8266_defaults.board_build.ldscript}
build_type = ${esp8266_defaults.build_type}
build_flags = build_flags =
${esp8266_defaults.build_flags} ${esp8266_defaults.build_flags}
-D DEFAULT_OT_IN_GPIO=13 -D DEFAULT_OT_IN_GPIO=13
@@ -164,6 +179,7 @@ lib_ignore = ${esp32_defaults.lib_ignore}
extra_scripts = ${esp32_defaults.extra_scripts} extra_scripts = ${esp32_defaults.extra_scripts}
build_unflags = build_unflags =
-DARDUINO_USB_MODE=1 -DARDUINO_USB_MODE=1
build_type = ${esp32_defaults.build_type}
build_flags = build_flags =
${esp32_defaults.build_flags} ${esp32_defaults.build_flags}
-D ARDUINO_USB_MODE=0 -D ARDUINO_USB_MODE=0
@@ -182,11 +198,12 @@ board = lolin_s3_mini
board_build.partitions = ${esp32_defaults.board_build.partitions} board_build.partitions = ${esp32_defaults.board_build.partitions}
lib_deps = lib_deps =
${esp32_defaults.lib_deps} ${esp32_defaults.lib_deps}
h2zero/NimBLE-Arduino@^1.4.1 ${esp32_defaults.nimble_lib}
lib_ignore = ${esp32_defaults.lib_ignore} lib_ignore = ${esp32_defaults.lib_ignore}
extra_scripts = ${esp32_defaults.extra_scripts} extra_scripts = ${esp32_defaults.extra_scripts}
build_unflags = build_unflags =
-DARDUINO_USB_MODE=1 -DARDUINO_USB_MODE=1
build_type = ${esp32_defaults.build_type}
build_flags = build_flags =
${esp32_defaults.build_flags} ${esp32_defaults.build_flags}
-D ARDUINO_USB_MODE=0 -D ARDUINO_USB_MODE=0
@@ -206,11 +223,12 @@ board = lolin_c3_mini
board_build.partitions = ${esp32_defaults.board_build.partitions} board_build.partitions = ${esp32_defaults.board_build.partitions}
lib_deps = lib_deps =
${esp32_defaults.lib_deps} ${esp32_defaults.lib_deps}
h2zero/NimBLE-Arduino@^1.4.1 ${esp32_defaults.nimble_lib}
lib_ignore = ${esp32_defaults.lib_ignore} lib_ignore = ${esp32_defaults.lib_ignore}
extra_scripts = ${esp32_defaults.extra_scripts} extra_scripts = ${esp32_defaults.extra_scripts}
build_unflags = build_unflags =
-mtext-section-literals -mtext-section-literals
build_type = ${esp32_defaults.build_type}
build_flags = build_flags =
${esp32_defaults.build_flags} ${esp32_defaults.build_flags}
-D USE_BLE=1 -D USE_BLE=1
@@ -228,9 +246,10 @@ board = nodemcu-32s
board_build.partitions = ${esp32_defaults.board_build.partitions} board_build.partitions = ${esp32_defaults.board_build.partitions}
lib_deps = lib_deps =
${esp32_defaults.lib_deps} ${esp32_defaults.lib_deps}
h2zero/NimBLE-Arduino@^1.4.1 ${esp32_defaults.nimble_lib}
lib_ignore = ${esp32_defaults.lib_ignore} lib_ignore = ${esp32_defaults.lib_ignore}
extra_scripts = ${esp32_defaults.extra_scripts} extra_scripts = ${esp32_defaults.extra_scripts}
build_type = ${esp32_defaults.build_type}
build_flags = build_flags =
${esp32_defaults.build_flags} ${esp32_defaults.build_flags}
-D USE_BLE=1 -D USE_BLE=1
@@ -240,7 +259,6 @@ build_flags =
-D DEFAULT_SENSOR_INDOOR_GPIO=26 -D DEFAULT_SENSOR_INDOOR_GPIO=26
-D DEFAULT_STATUS_LED_GPIO=2 -D DEFAULT_STATUS_LED_GPIO=2
-D DEFAULT_OT_RX_LED_GPIO=19 -D DEFAULT_OT_RX_LED_GPIO=19
;-D WOKWI=1
[env:d1_mini32] [env:d1_mini32]
platform = ${esp32_defaults.platform} platform = ${esp32_defaults.platform}
@@ -249,9 +267,10 @@ board = wemos_d1_mini32
board_build.partitions = ${esp32_defaults.board_build.partitions} board_build.partitions = ${esp32_defaults.board_build.partitions}
lib_deps = lib_deps =
${esp32_defaults.lib_deps} ${esp32_defaults.lib_deps}
h2zero/NimBLE-Arduino@^1.4.1 ${esp32_defaults.nimble_lib}
lib_ignore = ${esp32_defaults.lib_ignore} lib_ignore = ${esp32_defaults.lib_ignore}
extra_scripts = ${esp32_defaults.extra_scripts} extra_scripts = ${esp32_defaults.extra_scripts}
build_type = ${esp32_defaults.build_type}
build_flags = build_flags =
${esp32_defaults.build_flags} ${esp32_defaults.build_flags}
-D USE_BLE=1 -D USE_BLE=1
@@ -261,3 +280,21 @@ build_flags =
-D DEFAULT_SENSOR_INDOOR_GPIO=18 -D DEFAULT_SENSOR_INDOOR_GPIO=18
-D DEFAULT_STATUS_LED_GPIO=2 -D DEFAULT_STATUS_LED_GPIO=2
-D DEFAULT_OT_RX_LED_GPIO=19 -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

View File

@@ -1,7 +1,11 @@
[secrets] [secrets]
use_serial = true build_type = release
use_telnet = true
debug = true serial_enabled = true
serial_baud = 115200
telnet_enabled = true
telnet_port = 23
log_level = 5
hostname = opentherm hostname = opentherm
ap_ssid = OpenTherm Gateway ap_ssid = OpenTherm Gateway
@@ -13,6 +17,7 @@ sta_password =
portal_login = admin portal_login = admin
portal_password = admin portal_password = admin
mqtt_enabled = false
mqtt_server = mqtt_server =
mqtt_port = 1883 mqtt_port = 1883
mqtt_user = mqtt_user =

132
src/CrashRecorder.h Normal file
View File

@@ -0,0 +1,132 @@
#pragma once
#include <Arduino.h>
#ifdef ARDUINO_ARCH_ESP32
#include "esp_err.h"
#endif
#ifdef ARDUINO_ARCH_ESP8266
extern "C" {
#include <user_interface.h>
}
// https://github.com/espressif/ESP8266_RTOS_SDK/blob/master/components/esp8266/include/esp_attr.h
#define _COUNTER_STRINGIFY(COUNTER) #COUNTER
#define _SECTION_ATTR_IMPL(SECTION, COUNTER) __attribute__((section(SECTION "." _COUNTER_STRINGIFY(COUNTER))))
#define __NOINIT_ATTR _SECTION_ATTR_IMPL(".noinit", __COUNTER__)
#endif
namespace CrashRecorder {
typedef struct {
unsigned int data[32];
uint8_t length;
bool continues;
} backtrace_t;
typedef struct {
unsigned int data[4];
uint8_t length;
} epc_t;
typedef struct {
uint8_t core;
size_t heap;
unsigned long uptime;
} ext_t;
__NOINIT_ATTR volatile static backtrace_t backtrace;
__NOINIT_ATTR volatile static epc_t epc;
__NOINIT_ATTR volatile static ext_t ext;
uint8_t backtraceMaxLength = sizeof(backtrace.data) / sizeof(*backtrace.data);
uint8_t epcMaxLength = sizeof(epc.data) / sizeof(*epc.data);
#ifdef ARDUINO_ARCH_ESP32
void IRAM_ATTR panicHandler(arduino_panic_info_t *info, void *arg) {;
ext.core = info->core;
ext.heap = ESP.getFreeHeap();
ext.uptime = millis() / 1000u;
// Backtrace
backtrace.length = info->backtrace_len < backtraceMaxLength ? info->backtrace_len : backtraceMaxLength;
backtrace.continues = false;
for (unsigned int i = 0; i < info->backtrace_len; i++) {
if (i >= backtraceMaxLength) {
backtrace.continues = true;
break;
}
backtrace.data[i] = info->backtrace[i];
}
// EPC
if (info->pc) {
epc.data[0] = (unsigned int) info->pc;
epc.length = 1;
} else {
epc.length = 0;
}
}
#endif
void init() {
if (backtrace.length > backtraceMaxLength) {
backtrace.length = 0;
}
if (epc.length > epcMaxLength) {
epc.length = 0;
}
#ifdef ARDUINO_ARCH_ESP32
set_arduino_panic_handler(panicHandler, nullptr);
#endif
}
}
#ifdef ARDUINO_ARCH_ESP8266
extern "C" void custom_crash_callback(struct rst_info *info, uint32_t stack, uint32_t stack_end) {
uint8_t _length = 0;
CrashRecorder::ext.core = 0;
CrashRecorder::ext.heap = ESP.getFreeHeap();
CrashRecorder::ext.uptime = millis() / 1000u;
// Backtrace
CrashRecorder::backtrace.continues = false;
uint32_t value;
for (uint32_t i = stack; i < stack_end; i += 4) {
value = *((uint32_t*) i);
// keep only addresses in code area
if ((value >= 0x40000000) && (value < 0x40300000)) {
if (_length >= CrashRecorder::backtraceMaxLength) {
CrashRecorder::backtrace.continues = true;
break;
}
CrashRecorder::backtrace.data[_length++] = value;
}
}
CrashRecorder::backtrace.length = _length;
// EPC
_length = 0;
if (info->epc1 > 0) {
CrashRecorder::epc.data[_length++] = info->epc1;
}
if (info->epc2 > 0) {
CrashRecorder::epc.data[_length++] = info->epc2;
}
if (info->epc3 > 0) {
CrashRecorder::epc.data[_length++] = info->epc3;
}
CrashRecorder::epc.length = _length;
}
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -5,7 +5,7 @@ using namespace NetworkUtils;
extern NetworkMgr* network; extern NetworkMgr* network;
extern MqttTask* tMqtt; extern MqttTask* tMqtt;
extern OpenThermTask* tOt; extern OpenThermTask* tOt;
extern FileData fsSettings, fsNetworkSettings; extern FileData fsNetworkSettings, fsSettings, fsSensorsSettings;
extern ESPTelnetStream* telnetStream; extern ESPTelnetStream* telnetStream;
@@ -29,7 +29,6 @@ protected:
enum class PumpStartReason {NONE, HEATING, ANTISTUCK}; enum class PumpStartReason {NONE, HEATING, ANTISTUCK};
Blinker* blinker = nullptr; Blinker* blinker = nullptr;
unsigned long firstFailConnect = 0;
unsigned long lastHeapInfo = 0; unsigned long lastHeapInfo = 0;
unsigned int minFreeHeap = 0; unsigned int minFreeHeap = 0;
unsigned int minMaxFreeBlockHeap = 0; unsigned int minMaxFreeBlockHeap = 0;
@@ -39,6 +38,8 @@ protected:
PumpStartReason extPumpStartReason = PumpStartReason::NONE; PumpStartReason extPumpStartReason = PumpStartReason::NONE;
unsigned long externalPumpStartTime = 0; unsigned long externalPumpStartTime = 0;
bool telnetStarted = false; bool telnetStarted = false;
bool emergencyDetected = false;
unsigned long emergencyFlipTime = 0;
#if defined(ARDUINO_ARCH_ESP32) #if defined(ARDUINO_ARCH_ESP32)
const char* getTaskName() override { const char* getTaskName() override {
@@ -59,12 +60,16 @@ protected:
void loop() { void loop() {
network->loop(); network->loop();
if (fsNetworkSettings.tick() == FD_WRITE) {
Log.sinfoln(FPSTR(L_NETWORK_SETTINGS), F("Updated"));
}
if (fsSettings.tick() == FD_WRITE) { if (fsSettings.tick() == FD_WRITE) {
Log.sinfoln(FPSTR(L_SETTINGS), F("Updated")); Log.sinfoln(FPSTR(L_SETTINGS), F("Updated"));
} }
if (fsNetworkSettings.tick() == FD_WRITE) { if (fsSensorsSettings.tick() == FD_WRITE) {
Log.sinfoln(FPSTR(L_NETWORK_SETTINGS), F("Updated")); Log.sinfoln(FPSTR(L_SENSORS_SETTINGS), F("Updated"));
} }
if (vars.actions.restart) { if (vars.actions.restart) {
@@ -74,6 +79,9 @@ protected:
// save settings // save settings
fsSettings.updateNow(); fsSettings.updateNow();
// save sensors settings
fsSensorsSettings.updateNow();
// force save network settings // force save network settings
if (fsNetworkSettings.updateNow() == FD_FILE_ERR && LittleFS.begin()) { if (fsNetworkSettings.updateNow() == FD_FILE_ERR && LittleFS.begin()) {
fsNetworkSettings.write(); fsNetworkSettings.write();
@@ -82,11 +90,14 @@ protected:
Log.sinfoln(FPSTR(L_MAIN), F("Restart signal received. Restart after 10 sec.")); Log.sinfoln(FPSTR(L_MAIN), F("Restart signal received. Restart after 10 sec."));
} }
vars.states.mqtt = tMqtt->isConnected(); vars.mqtt.connected = tMqtt->isConnected();
vars.sensors.rssi = network->isConnected() ? WiFi.RSSI() : 0; vars.network.connected = network->isConnected();
vars.network.rssi = network->isConnected() ? WiFi.RSSI() : 0;
if (vars.states.emergency && !settings.emergency.enable) { if (settings.system.logLevel >= TinyLogger::Level::SILENT && settings.system.logLevel <= TinyLogger::Level::VERBOSE) {
vars.states.emergency = false; if (Log.getLevel() != settings.system.logLevel) {
Log.setLevel(static_cast<TinyLogger::Level>(settings.system.logLevel));
}
} }
if (network->isConnected()) { if (network->isConnected()) {
@@ -95,30 +106,14 @@ protected:
this->telnetStarted = true; this->telnetStarted = true;
} }
if (settings.mqtt.enable && !tMqtt->isEnabled()) { if (settings.mqtt.enabled && !tMqtt->isEnabled()) {
tMqtt->enable(); tMqtt->enable();
} else if (!settings.mqtt.enable && tMqtt->isEnabled()) { } else if (!settings.mqtt.enabled && tMqtt->isEnabled()) {
tMqtt->disable(); tMqtt->disable();
} }
if (!vars.states.emergency && settings.emergency.enable && settings.emergency.onMqttFault && !tMqtt->isEnabled()) { Sensors::setConnectionStatusByType(Sensors::Type::MANUAL, !settings.mqtt.enabled || vars.mqtt.connected, false);
vars.states.emergency = true;
} else if (vars.states.emergency && !settings.emergency.onMqttFault) {
vars.states.emergency = false;
}
if (this->firstFailConnect != 0) {
this->firstFailConnect = 0;
}
if ( Log.getLevel() != TinyLogger::Level::INFO && !settings.system.debug ) {
Log.setLevel(TinyLogger::Level::INFO);
} else if ( Log.getLevel() != TinyLogger::Level::VERBOSE && settings.system.debug ) {
Log.setLevel(TinyLogger::Level::VERBOSE);
}
} else { } else {
if (this->telnetStarted) { if (this->telnetStarted) {
@@ -130,21 +125,13 @@ protected:
tMqtt->disable(); tMqtt->disable();
} }
if (!vars.states.emergency && settings.emergency.enable && settings.emergency.onNetworkFault) { Sensors::setConnectionStatusByType(Sensors::Type::MANUAL, false, false);
if (this->firstFailConnect == 0) {
this->firstFailConnect = millis();
}
if (millis() - this->firstFailConnect > (settings.emergency.tresholdTime * 1000)) {
vars.states.emergency = true;
Log.sinfoln(FPSTR(L_MAIN), F("Emergency mode enabled"));
}
}
} }
this->yield(); this->yield();
this->emergency();
this->ledStatus(); this->ledStatus();
this->cascadeControl();
this->externalPump(); this->externalPump();
this->yield(); this->yield();
@@ -185,7 +172,7 @@ protected:
this->restartSignalTime = millis(); this->restartSignalTime = millis();
} }
if (!settings.system.debug) { if (settings.system.logLevel < TinyLogger::Level::VERBOSE) {
return; return;
} }
@@ -213,6 +200,59 @@ protected:
} }
} }
void emergency() {
// flags
uint8_t emergencyFlags = 0b00000000;
// set outdoor sensor flag
if (settings.equitherm.enabled) {
if (!Sensors::existsConnectedSensorsByPurpose(Sensors::Purpose::INDOOR_TEMP)) {
emergencyFlags |= 0b00000001;
}
}
// set indoor sensor flags
if (!Sensors::existsConnectedSensorsByPurpose(Sensors::Purpose::OUTDOOR_TEMP)) {
if (!settings.equitherm.enabled && settings.pid.enabled) {
emergencyFlags |= 0b00000010;
}
if (settings.opentherm.nativeHeatingControl) {
emergencyFlags |= 0b00000100;
}
}
// if any flags is true
if ((emergencyFlags & 0b00001111) != 0) {
if (!this->emergencyDetected) {
// flip flag
this->emergencyDetected = true;
this->emergencyFlipTime = millis();
} else if (this->emergencyDetected && !vars.emergency.state) {
// enable emergency
if (millis() - this->emergencyFlipTime > (settings.emergency.tresholdTime * 1000)) {
vars.emergency.state = true;
Log.sinfoln(FPSTR(L_MAIN), F("Emergency mode enabled (%hhu)"), emergencyFlags);
}
}
} else {
if (this->emergencyDetected) {
// flip flag
this->emergencyDetected = false;
this->emergencyFlipTime = millis();
} else if (!this->emergencyDetected && vars.emergency.state) {
// disable emergency
if (millis() - this->emergencyFlipTime > (settings.emergency.tresholdTime * 1000)) {
vars.emergency.state = false;
Log.sinfoln(FPSTR(L_MAIN), F("Emergency mode disabled"));
}
}
}
}
void ledStatus() { void ledStatus() {
uint8_t errors[4]; uint8_t errors[4];
uint8_t errCount = 0; uint8_t errCount = 0;
@@ -245,15 +285,15 @@ protected:
errors[errCount++] = 2; errors[errCount++] = 2;
} }
if (!vars.states.otStatus) { if (!vars.slave.connected) {
errors[errCount++] = 3; errors[errCount++] = 3;
} }
if (vars.states.fault) { if (vars.slave.fault.active) {
errors[errCount++] = 4; errors[errCount++] = 4;
} }
if (vars.states.emergency) { if (vars.emergency.state) {
errors[errCount++] = 5; errors[errCount++] = 5;
} }
@@ -292,6 +332,170 @@ protected:
this->blinker->tick(); this->blinker->tick();
} }
void cascadeControl() {
static uint8_t configuredInputGpio = GPIO_IS_NOT_CONFIGURED;
static uint8_t configuredOutputGpio = GPIO_IS_NOT_CONFIGURED;
static bool inputTempValue = false;
static unsigned long inputChangedTs = 0;
static bool outputTempValue = false;
static unsigned long outputChangedTs = 0;
// input
if (settings.cascadeControl.input.enabled) {
if (settings.cascadeControl.input.gpio != configuredInputGpio) {
if (configuredInputGpio != GPIO_IS_NOT_CONFIGURED) {
pinMode(configuredInputGpio, OUTPUT);
digitalWrite(configuredInputGpio, LOW);
Log.sinfoln(FPSTR(L_CASCADE_INPUT), F("Deinitialized on GPIO %hhu"), configuredInputGpio);
}
if (GPIO_IS_VALID(settings.cascadeControl.input.gpio)) {
configuredInputGpio = settings.cascadeControl.input.gpio;
pinMode(configuredInputGpio, INPUT);
Log.sinfoln(FPSTR(L_CASCADE_INPUT), F("Initialized on GPIO %hhu"), configuredInputGpio);
} else if (configuredInputGpio != GPIO_IS_NOT_CONFIGURED) {
configuredInputGpio = GPIO_IS_NOT_CONFIGURED;
Log.swarningln(FPSTR(L_CASCADE_INPUT), F("Failed initialize: GPIO %hhu is not valid!"), configuredInputGpio);
}
}
if (configuredInputGpio != GPIO_IS_NOT_CONFIGURED) {
bool value;
if (digitalRead(configuredInputGpio) == HIGH) {
value = true ^ settings.cascadeControl.input.invertState;
} else {
value = false ^ settings.cascadeControl.input.invertState;
}
if (value != vars.cascadeControl.input) {
if (value != inputTempValue) {
inputTempValue = value;
inputChangedTs = millis();
} else if (millis() - inputChangedTs >= settings.cascadeControl.input.thresholdTime * 1000u) {
vars.cascadeControl.input = value;
Log.sinfoln(
FPSTR(L_CASCADE_INPUT),
F("State changed to %s"),
value ? F("TRUE") : F("FALSE")
);
}
} else if (value != inputTempValue) {
inputTempValue = value;
}
}
}
if (!settings.cascadeControl.input.enabled || configuredInputGpio == GPIO_IS_NOT_CONFIGURED) {
if (!vars.cascadeControl.input) {
vars.cascadeControl.input = true;
Log.sinfoln(
FPSTR(L_CASCADE_INPUT),
F("Disabled, state changed to %s"),
vars.cascadeControl.input ? F("TRUE") : F("FALSE")
);
}
}
// output
if (settings.cascadeControl.output.enabled) {
if (settings.cascadeControl.output.gpio != configuredOutputGpio) {
if (configuredOutputGpio != GPIO_IS_NOT_CONFIGURED) {
pinMode(configuredOutputGpio, OUTPUT);
digitalWrite(configuredOutputGpio, LOW);
Log.sinfoln(FPSTR(L_CASCADE_OUTPUT), F("Deinitialized on GPIO %hhu"), configuredOutputGpio);
}
if (GPIO_IS_VALID(settings.cascadeControl.output.gpio)) {
configuredOutputGpio = settings.cascadeControl.output.gpio;
pinMode(configuredOutputGpio, OUTPUT);
digitalWrite(
configuredOutputGpio,
settings.cascadeControl.output.invertState
? HIGH
: LOW
);
Log.sinfoln(FPSTR(L_CASCADE_OUTPUT), F("Initialized on GPIO %hhu"), configuredOutputGpio);
} else if (configuredOutputGpio != GPIO_IS_NOT_CONFIGURED) {
configuredOutputGpio = GPIO_IS_NOT_CONFIGURED;
Log.swarningln(FPSTR(L_CASCADE_OUTPUT), F("Failed initialize: GPIO %hhu is not valid!"), configuredOutputGpio);
}
}
if (configuredOutputGpio != GPIO_IS_NOT_CONFIGURED) {
bool value = false;
if (settings.cascadeControl.output.onFault && vars.slave.fault.active) {
value = true;
} else if (settings.cascadeControl.output.onLossConnection && !vars.slave.connected) {
value = true;
} else if (settings.cascadeControl.output.onEnabledHeating && settings.heating.enabled && vars.cascadeControl.input) {
value = true;
}
if (value != vars.cascadeControl.output) {
if (value != outputTempValue) {
outputTempValue = value;
outputChangedTs = millis();
} else if (millis() - outputChangedTs >= settings.cascadeControl.output.thresholdTime * 1000u) {
vars.cascadeControl.output = value;
digitalWrite(
configuredOutputGpio,
vars.cascadeControl.output ^ settings.cascadeControl.output.invertState
? HIGH
: LOW
);
Log.sinfoln(
FPSTR(L_CASCADE_OUTPUT),
F("State changed to %s"),
value ? F("TRUE") : F("FALSE")
);
}
} else if (value != outputTempValue) {
outputTempValue = value;
}
}
}
if (!settings.cascadeControl.output.enabled || configuredOutputGpio == GPIO_IS_NOT_CONFIGURED) {
if (vars.cascadeControl.output) {
vars.cascadeControl.output = false;
if (configuredOutputGpio != GPIO_IS_NOT_CONFIGURED) {
digitalWrite(
configuredOutputGpio,
vars.cascadeControl.output ^ settings.cascadeControl.output.invertState
? HIGH
: LOW
);
}
Log.sinfoln(
FPSTR(L_CASCADE_OUTPUT),
F("Disabled, state changed to %s"),
vars.cascadeControl.output ? F("TRUE") : F("FALSE")
);
}
}
}
void externalPump() { void externalPump() {
static uint8_t configuredGpio = GPIO_IS_NOT_CONFIGURED; static uint8_t configuredGpio = GPIO_IS_NOT_CONFIGURED;
@@ -311,75 +515,75 @@ protected:
} }
if (configuredGpio == GPIO_IS_NOT_CONFIGURED) { if (configuredGpio == GPIO_IS_NOT_CONFIGURED) {
if (vars.states.externalPump) { if (vars.externalPump.state) {
vars.states.externalPump = false; vars.externalPump.state = false;
vars.parameters.extPumpLastEnableTime = millis(); vars.externalPump.lastEnableTime = millis();
Log.sinfoln("EXTPUMP", F("Disabled: use = off")); Log.sinfoln(FPSTR(L_EXTPUMP), F("Disabled: use = off"));
} }
return; return;
} }
if (!vars.states.heating && this->heatingEnabled) { if (!vars.master.heating.enabled && this->heatingEnabled) {
this->heatingEnabled = false; this->heatingEnabled = false;
this->heatingDisabledTime = millis(); this->heatingDisabledTime = millis();
} else if (vars.states.heating && !this->heatingEnabled) { } else if (vars.master.heating.enabled && !this->heatingEnabled) {
this->heatingEnabled = true; this->heatingEnabled = true;
} }
if (!settings.externalPump.use) { if (!settings.externalPump.use) {
if (vars.states.externalPump) { if (vars.externalPump.state) {
digitalWrite(configuredGpio, LOW); digitalWrite(configuredGpio, LOW);
vars.states.externalPump = false; vars.externalPump.state = false;
vars.parameters.extPumpLastEnableTime = millis(); vars.externalPump.lastEnableTime = millis();
Log.sinfoln("EXTPUMP", F("Disabled: use = off")); Log.sinfoln(FPSTR(L_EXTPUMP), F("Disabled: use = off"));
} }
return; return;
} }
if (vars.states.externalPump && !this->heatingEnabled) { if (vars.externalPump.state && !this->heatingEnabled) {
if (this->extPumpStartReason == MainTask::PumpStartReason::HEATING && millis() - this->heatingDisabledTime > (settings.externalPump.postCirculationTime * 1000u)) { if (this->extPumpStartReason == MainTask::PumpStartReason::HEATING && millis() - this->heatingDisabledTime > (settings.externalPump.postCirculationTime * 1000u)) {
digitalWrite(configuredGpio, LOW); digitalWrite(configuredGpio, LOW);
vars.states.externalPump = false; vars.externalPump.state = false;
vars.parameters.extPumpLastEnableTime = millis(); vars.externalPump.lastEnableTime = millis();
Log.sinfoln("EXTPUMP", F("Disabled: expired post circulation time")); Log.sinfoln(FPSTR(L_EXTPUMP), F("Disabled: expired post circulation time"));
} else if (this->extPumpStartReason == MainTask::PumpStartReason::ANTISTUCK && millis() - this->externalPumpStartTime >= (settings.externalPump.antiStuckTime * 1000u)) { } else if (this->extPumpStartReason == MainTask::PumpStartReason::ANTISTUCK && millis() - this->externalPumpStartTime >= (settings.externalPump.antiStuckTime * 1000u)) {
digitalWrite(configuredGpio, LOW); digitalWrite(configuredGpio, LOW);
vars.states.externalPump = false; vars.externalPump.state = false;
vars.parameters.extPumpLastEnableTime = millis(); vars.externalPump.lastEnableTime = millis();
Log.sinfoln("EXTPUMP", F("Disabled: expired anti stuck time")); Log.sinfoln(FPSTR(L_EXTPUMP), F("Disabled: expired anti stuck time"));
} }
} else if (vars.states.externalPump && this->heatingEnabled && this->extPumpStartReason == MainTask::PumpStartReason::ANTISTUCK) { } else if (vars.externalPump.state && this->heatingEnabled && this->extPumpStartReason == MainTask::PumpStartReason::ANTISTUCK) {
this->extPumpStartReason = MainTask::PumpStartReason::HEATING; this->extPumpStartReason = MainTask::PumpStartReason::HEATING;
} else if (!vars.states.externalPump && this->heatingEnabled) { } else if (!vars.externalPump.state && this->heatingEnabled) {
vars.states.externalPump = true; vars.externalPump.state = true;
this->externalPumpStartTime = millis(); this->externalPumpStartTime = millis();
this->extPumpStartReason = MainTask::PumpStartReason::HEATING; this->extPumpStartReason = MainTask::PumpStartReason::HEATING;
digitalWrite(configuredGpio, HIGH); digitalWrite(configuredGpio, HIGH);
Log.sinfoln("EXTPUMP", F("Enabled: heating on")); Log.sinfoln(FPSTR(L_EXTPUMP), F("Enabled: heating on"));
} else if (!vars.states.externalPump && (vars.parameters.extPumpLastEnableTime == 0 || millis() - vars.parameters.extPumpLastEnableTime >= (settings.externalPump.antiStuckInterval * 1000ul))) { } else if (!vars.externalPump.state && (vars.externalPump.lastEnableTime == 0 || millis() - vars.externalPump.lastEnableTime >= (settings.externalPump.antiStuckInterval * 1000ul))) {
vars.states.externalPump = true; vars.externalPump.state = true;
this->externalPumpStartTime = millis(); this->externalPumpStartTime = millis();
this->extPumpStartReason = MainTask::PumpStartReason::ANTISTUCK; this->extPumpStartReason = MainTask::PumpStartReason::ANTISTUCK;
digitalWrite(configuredGpio, HIGH); digitalWrite(configuredGpio, HIGH);
Log.sinfoln("EXTPUMP", F("Enabled: anti stuck")); Log.sinfoln(FPSTR(L_EXTPUMP), F("Enabled: anti stuck"));
} }
} }
}; };

View File

@@ -1,3 +1,4 @@
#include <unordered_map>
#include <MqttClient.h> #include <MqttClient.h>
#include <MqttWiFiClient.h> #include <MqttWiFiClient.h>
#include <MqttWriter.h> #include <MqttWriter.h>
@@ -61,10 +62,18 @@ public:
this->prevPubSettingsTime = 0; this->prevPubSettingsTime = 0;
} }
inline void resetPublishedSensorTime(uint8_t sensorId) {
this->prevPubSensorTime[sensorId] = 0;
}
inline void resetPublishedVarsTime() { inline void resetPublishedVarsTime() {
this->prevPubVarsTime = 0; this->prevPubVarsTime = 0;
} }
inline void rebuildHaEntity(uint8_t sensorId, Sensors::Settings& prevSettings) {
this->queueRebuildingHaEntities[sensorId] = prevSettings;
}
protected: protected:
MqttWiFiClient* wifiClient = nullptr; MqttWiFiClient* wifiClient = nullptr;
MqttClient* client = nullptr; MqttClient* client = nullptr;
@@ -72,12 +81,14 @@ protected:
MqttWriter* writer = nullptr; MqttWriter* writer = nullptr;
UnitSystem currentUnitSystem = UnitSystem::METRIC; UnitSystem currentUnitSystem = UnitSystem::METRIC;
bool currentHomeAssistantDiscovery = false; bool currentHomeAssistantDiscovery = false;
unsigned short readyForSendTime = 15000; std::unordered_map<uint8_t, Sensors::Settings> queueRebuildingHaEntities;
unsigned short readyForSendTime = 30000;
unsigned long lastReconnectTime = 0; unsigned long lastReconnectTime = 0;
unsigned long connectedTime = 0; unsigned long connectedTime = 0;
unsigned long disconnectedTime = 0; unsigned long disconnectedTime = 0;
unsigned long prevPubVarsTime = 0; unsigned long prevPubVarsTime = 0;
unsigned long prevPubSettingsTime = 0; unsigned long prevPubSettingsTime = 0;
std::unordered_map<uint8_t, unsigned long> prevPubSensorTime;
bool connected = false; bool connected = false;
bool newConnection = false; bool newConnection = false;
@@ -173,11 +184,6 @@ protected:
} }
void loop() { void loop() {
if (settings.mqtt.interval > 120) {
settings.mqtt.interval = 5;
fsSettings.update();
}
if (this->connected && !this->client->connected()) { if (this->connected && !this->client->connected()) {
this->connected = false; this->connected = false;
this->onDisconnect(); this->onDisconnect();
@@ -198,17 +204,6 @@ protected:
this->onConnect(); this->onConnect();
} }
if (settings.emergency.enable && settings.emergency.onMqttFault) {
if (!this->connected && !vars.states.emergency && millis() - this->disconnectedTime > (settings.emergency.tresholdTime * 1000)) {
vars.states.emergency = true;
Log.sinfoln(FPSTR(L_MQTT), F("Emergency mode enabled"));
} else if (this->connected && vars.states.emergency && millis() - this->connectedTime > 10000) {
vars.states.emergency = false;
Log.sinfoln(FPSTR(L_MQTT), F("Emergency mode disabled"));
}
}
if (!this->connected) { if (!this->connected) {
return; return;
} }
@@ -237,6 +232,28 @@ protected:
this->prevPubSettingsTime = millis(); this->prevPubSettingsTime = millis();
} }
// publish sensors
for (uint8_t sensorId = 0; sensorId <= Sensors::getMaxSensorId(); sensorId++) {
if (!Sensors::hasEnabledAndValid(sensorId)) {
continue;
}
auto& rSensor = Sensors::results[sensorId];
bool needUpdate = false;
if (millis() - this->prevPubSensorTime[sensorId] > ((this->haHelper->getExpireAfter() - 10) * 1000u)) {
needUpdate = true;
} else if (rSensor.activityTime >= this->prevPubSensorTime[sensorId]) {
auto estimated = rSensor.activityTime - this->prevPubSensorTime[sensorId];
needUpdate = estimated > 1000u;
}
if (this->newConnection || needUpdate) {
this->publishSensor(sensorId);
this->prevPubSensorTime[sensorId] = millis();
}
}
// publish ha entities if not published // publish ha entities if not published
if (settings.mqtt.homeAssistantDiscovery) { if (settings.mqtt.homeAssistantDiscovery) {
if (this->newConnection || !this->currentHomeAssistantDiscovery || this->currentUnitSystem != settings.system.unitSystem) { if (this->newConnection || !this->currentHomeAssistantDiscovery || this->currentUnitSystem != settings.system.unitSystem) {
@@ -250,6 +267,79 @@ protected:
this->publishNonStaticHaEntities(); this->publishNonStaticHaEntities();
} }
for (auto& [sensorId, prevSettings] : this->queueRebuildingHaEntities) {
Log.sinfoln(FPSTR(L_MQTT_HA), F("Rebuilding config for sensor #%hhu '%s'"), sensorId, prevSettings.name);
// delete old config
if (strlen(prevSettings.name) && prevSettings.enabled) {
switch (prevSettings.type) {
case Sensors::Type::BLUETOOTH:
this->haHelper->deleteConnectionDynamicSensor(prevSettings);
this->haHelper->deleteSignalQualityDynamicSensor(prevSettings);
this->haHelper->deleteDynamicSensor(prevSettings, Sensors::ValueType::TEMPERATURE);
this->haHelper->deleteDynamicSensor(prevSettings, Sensors::ValueType::HUMIDITY);
this->haHelper->deleteDynamicSensor(prevSettings, Sensors::ValueType::BATTERY);
this->haHelper->deleteDynamicSensor(prevSettings, Sensors::ValueType::RSSI);
break;
case Sensors::Type::DALLAS_TEMP:
this->haHelper->deleteConnectionDynamicSensor(prevSettings);
this->haHelper->deleteSignalQualityDynamicSensor(prevSettings);
this->haHelper->deleteDynamicSensor(prevSettings, Sensors::ValueType::TEMPERATURE);
break;
case Sensors::Type::MANUAL: {
String topic = this->haHelper->getDeviceTopic(
F("sensors"),
Sensors::makeObjectId(prevSettings.name),
F("set")
);
this->client->unsubscribe(topic.c_str());
}
default:
this->haHelper->deleteDynamicSensor(prevSettings, Sensors::ValueType::PRIMARY);
}
}
if (!Sensors::hasEnabledAndValid(sensorId)) {
continue;
}
// make new config
auto& sSettings = Sensors::settings[sensorId];
switch (sSettings.type) {
case Sensors::Type::BLUETOOTH:
this->haHelper->publishConnectionDynamicSensor(sSettings);
this->haHelper->publishSignalQualityDynamicSensor(sSettings, false);
this->haHelper->publishDynamicSensor(sSettings, Sensors::ValueType::TEMPERATURE, settings.system.unitSystem);
this->haHelper->publishDynamicSensor(sSettings, Sensors::ValueType::HUMIDITY, settings.system.unitSystem);
this->haHelper->publishDynamicSensor(sSettings, Sensors::ValueType::BATTERY, settings.system.unitSystem);
this->haHelper->publishDynamicSensor(sSettings, Sensors::ValueType::RSSI, settings.system.unitSystem, false);
break;
case Sensors::Type::DALLAS_TEMP:
this->haHelper->publishConnectionDynamicSensor(sSettings);
this->haHelper->publishSignalQualityDynamicSensor(sSettings, false);
this->haHelper->publishDynamicSensor(sSettings, Sensors::ValueType::TEMPERATURE, settings.system.unitSystem);
break;
case Sensors::Type::MANUAL: {
String topic = this->haHelper->getDeviceTopic(
F("sensors"),
Sensors::makeObjectId(prevSettings.name),
F("set")
);
this->client->subscribe(topic.c_str());
}
default:
this->haHelper->publishDynamicSensor(sSettings, Sensors::ValueType::PRIMARY, settings.system.unitSystem);
}
}
this->queueRebuildingHaEntities.clear();
} else if (this->currentHomeAssistantDiscovery) { } else if (this->currentHomeAssistantDiscovery) {
this->currentHomeAssistantDiscovery = false; this->currentHomeAssistantDiscovery = false;
} }
@@ -281,7 +371,7 @@ protected:
return; return;
} }
if (settings.system.debug) { if (settings.system.logLevel >= TinyLogger::Level::TRACE) {
Log.strace(FPSTR(L_MQTT_MSG), F("Topic: %s\r\n> "), topic); Log.strace(FPSTR(L_MQTT_MSG), F("Topic: %s\r\n> "), topic);
if (Log.lock()) { if (Log.lock()) {
for (size_t i = 0; i < length; i++) { for (size_t i = 0; i < length; i++) {
@@ -314,136 +404,160 @@ protected:
doc.shrinkToFit(); doc.shrinkToFit();
if (this->haHelper->getDeviceTopic("state/set").equals(topic)) { if (this->haHelper->getDeviceTopic("state/set").equals(topic)) {
this->writer->publish(this->haHelper->getDeviceTopic("state/set").c_str(), nullptr, 0, true); this->writer->publish(topic, nullptr, 0, true);
if (jsonToVars(doc, vars)) { if (jsonToVars(doc, vars)) {
this->resetPublishedVarsTime(); this->resetPublishedVarsTime();
} }
} else if (this->haHelper->getDeviceTopic("settings/set").equals(topic)) { } else if (this->haHelper->getDeviceTopic("settings/set").equals(topic)) {
this->writer->publish(this->haHelper->getDeviceTopic("settings/set").c_str(), nullptr, 0, true); this->writer->publish(topic, nullptr, 0, true);
if (safeJsonToSettings(doc, settings)) { if (safeJsonToSettings(doc, settings)) {
this->resetPublishedSettingsTime(); this->resetPublishedSettingsTime();
fsSettings.update(); fsSettings.update();
} }
} else {
this->writer->publish(topic, nullptr, 0, true);
String _topic = topic;
String sensorsTopic = this->haHelper->getDeviceTopic("sensors/");
unsigned short stLength = sensorsTopic.length();
if (_topic.startsWith(sensorsTopic) && _topic.endsWith("/set")) {
if (_topic.length() > stLength + 4) {
String name = _topic.substring(stLength, _topic.indexOf('/', stLength));
int16_t id = Sensors::getIdByObjectId(name.c_str());
if (id == -1) {
return;
}
if (jsonToSensorResult(id, doc)) {
this->resetPublishedSensorTime(id);
}
}
}
} }
} }
void publishHaEntities() { void publishHaEntities() {
// heating // heating
this->haHelper->publishSwitchHeating(false); this->haHelper->publishSwitchHeatingTurbo(false);
this->haHelper->publishSwitchHeatingTurbo(); this->haHelper->publishInputHeatingHysteresis(settings.system.unitSystem);
this->haHelper->publishNumberHeatingHysteresis(settings.system.unitSystem); this->haHelper->publishInputHeatingTurboFactor(false);
this->haHelper->publishSensorHeatingSetpoint(settings.system.unitSystem, false); this->haHelper->publishInputHeatingMinTemp(settings.system.unitSystem);
this->haHelper->publishSensorBoilerHeatingMinTemp(settings.system.unitSystem, false); this->haHelper->publishInputHeatingMaxTemp(settings.system.unitSystem);
this->haHelper->publishSensorBoilerHeatingMaxTemp(settings.system.unitSystem, false);
this->haHelper->publishNumberHeatingMinTemp(settings.system.unitSystem, false);
this->haHelper->publishNumberHeatingMaxTemp(settings.system.unitSystem, false);
this->haHelper->publishNumberHeatingMaxModulation(false);
// pid // pid
this->haHelper->publishSwitchPid(); this->haHelper->publishSwitchPid();
this->haHelper->publishNumberPidFactorP(); this->haHelper->publishInputPidFactorP(false);
this->haHelper->publishNumberPidFactorI(); this->haHelper->publishInputPidFactorI(false);
this->haHelper->publishNumberPidFactorD(); this->haHelper->publishInputPidFactorD(false);
this->haHelper->publishNumberPidDt(false); this->haHelper->publishInputPidDt(false);
this->haHelper->publishNumberPidMinTemp(settings.system.unitSystem, false); this->haHelper->publishInputPidMinTemp(settings.system.unitSystem, false);
this->haHelper->publishNumberPidMaxTemp(settings.system.unitSystem, false); this->haHelper->publishInputPidMaxTemp(settings.system.unitSystem, false);
// equitherm // equitherm
this->haHelper->publishSwitchEquitherm(); this->haHelper->publishSwitchEquitherm();
this->haHelper->publishNumberEquithermFactorN(); this->haHelper->publishInputEquithermFactorN(false);
this->haHelper->publishNumberEquithermFactorK(); this->haHelper->publishInputEquithermFactorK(false);
this->haHelper->publishNumberEquithermFactorT(); this->haHelper->publishInputEquithermFactorT(false);
// states // states
this->haHelper->publishBinSensorStatus(); this->haHelper->publishStatusState();
this->haHelper->publishBinSensorOtStatus(); this->haHelper->publishEmergencyState();
this->haHelper->publishBinSensorHeating(); this->haHelper->publishOpenthermConnectedState();
this->haHelper->publishBinSensorFlame(); this->haHelper->publishHeatingState();
this->haHelper->publishBinSensorFault(); this->haHelper->publishFlameState();
this->haHelper->publishBinSensorDiagnostic(); this->haHelper->publishFaultState();
this->haHelper->publishDiagState();
this->haHelper->publishExternalPumpState(false);
// sensors // sensors
this->haHelper->publishSensorModulation(false); this->haHelper->publishFaultCode();
this->haHelper->publishSensorPressure(settings.system.unitSystem, false); this->haHelper->publishDiagCode();
this->haHelper->publishSensorFaultCode(); this->haHelper->publishNetworkRssi(false);
this->haHelper->publishSensorRssi(false); this->haHelper->publishUptime(false);
this->haHelper->publishSensorUptime(false);
// temperatures
this->haHelper->publishNumberIndoorTemp(settings.system.unitSystem);
this->haHelper->publishSensorHeatingTemp(settings.system.unitSystem);
this->haHelper->publishSensorHeatingReturnTemp(settings.system.unitSystem, false);
this->haHelper->publishSensorExhaustTemp(settings.system.unitSystem, false);
// buttons // buttons
this->haHelper->publishButtonRestart(false); this->haHelper->publishRestartButton(false);
this->haHelper->publishButtonResetFault(); this->haHelper->publishResetFaultButton();
this->haHelper->publishButtonResetDiagnostic(); this->haHelper->publishResetDiagButton();
// dynamic sensors
for (uint8_t sensorId = 0; sensorId <= Sensors::getMaxSensorId(); sensorId++) {
if (!Sensors::hasEnabledAndValid(sensorId)) {
continue;
}
auto& sSettings = Sensors::settings[sensorId];
switch (sSettings.type) {
case Sensors::Type::BLUETOOTH:
this->haHelper->publishConnectionDynamicSensor(sSettings);
this->haHelper->publishSignalQualityDynamicSensor(sSettings, false);
this->haHelper->publishDynamicSensor(sSettings, Sensors::ValueType::TEMPERATURE, settings.system.unitSystem);
this->haHelper->publishDynamicSensor(sSettings, Sensors::ValueType::HUMIDITY, settings.system.unitSystem);
this->haHelper->publishDynamicSensor(sSettings, Sensors::ValueType::BATTERY, settings.system.unitSystem);
this->haHelper->publishDynamicSensor(sSettings, Sensors::ValueType::RSSI, settings.system.unitSystem, false);
break;
case Sensors::Type::DALLAS_TEMP:
this->haHelper->publishConnectionDynamicSensor(sSettings);
this->haHelper->publishSignalQualityDynamicSensor(sSettings, false);
this->haHelper->publishDynamicSensor(sSettings, Sensors::ValueType::TEMPERATURE, settings.system.unitSystem);
break;
case Sensors::Type::MANUAL: {
String topic = this->haHelper->getDeviceTopic(
F("sensors"),
Sensors::makeObjectId(sSettings.name),
F("set")
);
this->client->subscribe(topic.c_str());
}
default:
this->haHelper->publishDynamicSensor(sSettings, Sensors::ValueType::PRIMARY, settings.system.unitSystem);
}
}
} }
bool publishNonStaticHaEntities(bool force = false) { bool publishNonStaticHaEntities(bool force = false) {
static byte _heatingMinTemp, _heatingMaxTemp, _dhwMinTemp, _dhwMaxTemp = 0; static byte _heatingMinTemp, _heatingMaxTemp, _dhwMinTemp, _dhwMaxTemp = 0;
static bool _noRegulators, _editableOutdoorTemp, _editableIndoorTemp, _dhwPresent = false; static bool _indoorTempControl, _dhwPresent = false;
bool published = false; bool published = false;
bool noRegulators = !settings.opentherm.nativeHeatingControl && !settings.pid.enable && !settings.equitherm.enable;
byte heatingMinTemp = 0;
byte heatingMaxTemp = 0;
bool editableOutdoorTemp = settings.sensors.outdoor.type == SensorType::MANUAL;
bool editableIndoorTemp = settings.sensors.indoor.type == SensorType::MANUAL;
if (noRegulators) {
heatingMinTemp = settings.heating.minTemp;
heatingMaxTemp = settings.heating.maxTemp;
} else {
heatingMinTemp = convertTemp(THERMOSTAT_INDOOR_MIN_TEMP, UnitSystem::METRIC, settings.system.unitSystem);
heatingMaxTemp = convertTemp(THERMOSTAT_INDOOR_MAX_TEMP, UnitSystem::METRIC, settings.system.unitSystem);
}
if (force || _dhwPresent != settings.opentherm.dhwPresent) { if (force || _dhwPresent != settings.opentherm.dhwPresent) {
_dhwPresent = settings.opentherm.dhwPresent; _dhwPresent = settings.opentherm.dhwPresent;
if (_dhwPresent) { if (_dhwPresent) {
this->haHelper->publishSwitchDhw(false); this->haHelper->publishInputDhwMinTemp(settings.system.unitSystem);
this->haHelper->publishSensorBoilerDhwMinTemp(settings.system.unitSystem, false); this->haHelper->publishInputDhwMaxTemp(settings.system.unitSystem);
this->haHelper->publishSensorBoilerDhwMaxTemp(settings.system.unitSystem, false); this->haHelper->publishDhwState();
this->haHelper->publishNumberDhwMinTemp(settings.system.unitSystem, false);
this->haHelper->publishNumberDhwMaxTemp(settings.system.unitSystem, false);
this->haHelper->publishBinSensorDhw();
this->haHelper->publishSensorDhwTemp(settings.system.unitSystem);
this->haHelper->publishSensorDhwFlowRate(settings.system.unitSystem, false);
} else { } else {
this->haHelper->deleteSwitchDhw(); this->haHelper->deleteSwitchDhw();
this->haHelper->deleteSensorBoilerDhwMinTemp(); this->haHelper->deleteInputDhwMinTemp();
this->haHelper->deleteSensorBoilerDhwMaxTemp(); this->haHelper->deleteInputDhwMaxTemp();
this->haHelper->deleteNumberDhwMinTemp(); this->haHelper->deleteDhwState();
this->haHelper->deleteNumberDhwMaxTemp(); this->haHelper->deleteInputDhwTarget();
this->haHelper->deleteBinSensorDhw();
this->haHelper->deleteSensorDhwTemp();
this->haHelper->deleteNumberDhwTarget();
this->haHelper->deleteClimateDhw(); this->haHelper->deleteClimateDhw();
this->haHelper->deleteSensorDhwFlowRate();
} }
published = true; published = true;
} }
if (force || _noRegulators != noRegulators || _heatingMinTemp != heatingMinTemp || _heatingMaxTemp != heatingMaxTemp) { if (force || _indoorTempControl != vars.master.heating.indoorTempControl || _heatingMinTemp != vars.master.heating.minTemp || _heatingMaxTemp != vars.master.heating.maxTemp) {
_heatingMinTemp = heatingMinTemp; _heatingMinTemp = vars.master.heating.minTemp;
_heatingMaxTemp = heatingMaxTemp; _heatingMaxTemp = vars.master.heating.maxTemp;
_noRegulators = noRegulators; _indoorTempControl = vars.master.heating.indoorTempControl;
this->haHelper->publishNumberHeatingTarget(settings.system.unitSystem, heatingMinTemp, heatingMaxTemp, false);
this->haHelper->publishClimateHeating( this->haHelper->publishClimateHeating(
settings.system.unitSystem, settings.system.unitSystem,
heatingMinTemp, vars.master.heating.minTemp,
heatingMaxTemp, vars.master.heating.maxTemp
noRegulators ? HaHelper::TEMP_SOURCE_HEATING : HaHelper::TEMP_SOURCE_INDOOR
); );
published = true; published = true;
@@ -453,40 +567,11 @@ protected:
_dhwMinTemp = settings.dhw.minTemp; _dhwMinTemp = settings.dhw.minTemp;
_dhwMaxTemp = settings.dhw.maxTemp; _dhwMaxTemp = settings.dhw.maxTemp;
this->haHelper->publishNumberDhwTarget(settings.system.unitSystem, settings.dhw.minTemp, settings.dhw.maxTemp, false);
this->haHelper->publishClimateDhw(settings.system.unitSystem, settings.dhw.minTemp, settings.dhw.maxTemp); this->haHelper->publishClimateDhw(settings.system.unitSystem, settings.dhw.minTemp, settings.dhw.maxTemp);
published = true; published = true;
} }
if (force || _editableOutdoorTemp != editableOutdoorTemp) {
_editableOutdoorTemp = editableOutdoorTemp;
if (editableOutdoorTemp) {
this->haHelper->deleteSensorOutdoorTemp();
this->haHelper->publishNumberOutdoorTemp(settings.system.unitSystem);
} else {
this->haHelper->deleteNumberOutdoorTemp();
this->haHelper->publishSensorOutdoorTemp(settings.system.unitSystem);
}
published = true;
}
if (force || _editableIndoorTemp != editableIndoorTemp) {
_editableIndoorTemp = editableIndoorTemp;
if (editableIndoorTemp) {
this->haHelper->deleteSensorIndoorTemp();
this->haHelper->publishNumberIndoorTemp(settings.system.unitSystem);
} else {
this->haHelper->deleteNumberIndoorTemp();
this->haHelper->publishSensorIndoorTemp(settings.system.unitSystem);
}
published = true;
}
return published; return published;
} }
@@ -498,6 +583,25 @@ protected:
return this->writer->publish(topic, doc, true); return this->writer->publish(topic, doc, true);
} }
bool publishSensor(uint8_t sensorId) {
auto& sSettings = Sensors::settings[sensorId];
if (!Sensors::isValidSensorId(sensorId)) {
return false;
} else if (!strlen(sSettings.name)) {
return false;
}
JsonDocument doc;
sensorResultToJson(sensorId, doc);
doc.shrinkToFit();
String objId = Sensors::makeObjectId(sSettings.name);
String topic = this->haHelper->getDeviceTopic(F("sensors"), objId);
return this->writer->publish(topic.c_str(), doc, true);
}
bool publishVariables(const char* topic) { bool publishVariables(const char* topic) {
JsonDocument doc; JsonDocument doc;
varsToJson(vars, doc); varsToJson(vars, doc);

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
#define PORTAL_CACHE_TIME "max-age=86400" #define PORTAL_CACHE_TIME "max-age=86400"
#define PORTAL_CACHE settings.system.debug ? nullptr : PORTAL_CACHE_TIME #define PORTAL_CACHE (settings.system.logLevel >= TinyLogger::Level::TRACE ? nullptr : PORTAL_CACHE_TIME)
#ifdef ARDUINO_ARCH_ESP8266 #ifdef ARDUINO_ARCH_ESP8266
#include <ESP8266WebServer.h> #include <ESP8266WebServer.h>
#include <Updater.h> #include <Updater.h>
@@ -17,7 +17,7 @@ using WebServer = ESP8266WebServer;
using namespace NetworkUtils; using namespace NetworkUtils;
extern NetworkMgr* network; extern NetworkMgr* network;
extern FileData fsSettings, fsNetworkSettings; extern FileData fsNetworkSettings, fsSettings, fsSensorsSettings;
extern MqttTask* tMqtt; extern MqttTask* tMqtt;
@@ -140,6 +140,18 @@ protected:
}); });
this->webServer->addHandler(settingsPage); this->webServer->addHandler(settingsPage);
// sensors page
auto sensorsPage = (new StaticPage("/sensors.html", &LittleFS, "/pages/sensors.html", PORTAL_CACHE))
->setBeforeSendCallback([this]() {
if (this->isAuthRequired() && !this->webServer->authenticate(settings.portal.login, settings.portal.password)) {
this->webServer->requestAuthentication(DIGEST_AUTH);
return false;
}
return true;
});
this->webServer->addHandler(sensorsPage);
// upgrade page // upgrade page
auto upgradePage = (new StaticPage("/upgrade.html", &LittleFS, "/pages/upgrade.html", PORTAL_CACHE)) auto upgradePage = (new StaticPage("/upgrade.html", &LittleFS, "/pages/upgrade.html", PORTAL_CACHE))
->setBeforeSendCallback([this]() { ->setBeforeSendCallback([this]() {
@@ -194,17 +206,19 @@ protected:
} }
} }
JsonDocument networkSettingsDoc;
networkSettingsToJson(networkSettings, networkSettingsDoc);
networkSettingsDoc.shrinkToFit();
JsonDocument settingsDoc;
settingsToJson(settings, settingsDoc);
settingsDoc.shrinkToFit();
JsonDocument doc; JsonDocument doc;
doc["network"] = networkSettingsDoc;
doc["settings"] = settingsDoc; auto networkDoc = doc["network"].to<JsonObject>();
networkSettingsToJson(networkSettings, networkDoc);
auto settingskDoc = doc["settings"].to<JsonObject>();
settingsToJson(settings, settingskDoc);
for (uint8_t sensorId = 0; sensorId <= Sensors::getMaxSensorId(); sensorId++) {
auto sensorSettingskDoc = doc["sensors"][sensorId].to<JsonObject>();
sensorSettingsToJson(sensorId, Sensors::settings[sensorId], sensorSettingskDoc);
}
doc.shrinkToFit(); doc.shrinkToFit();
this->webServer->sendHeader(F("Content-Disposition"), F("attachment; filename=\"backup.json\"")); this->webServer->sendHeader(F("Content-Disposition"), F("attachment; filename=\"backup.json\""));
@@ -225,7 +239,7 @@ protected:
this->webServer->send(406); this->webServer->send(406);
return; return;
} else if (plain.length() > 2048) { } else if (plain.length() > 2536) {
this->webServer->send(413); this->webServer->send(413);
return; return;
} }
@@ -240,13 +254,13 @@ protected:
} }
bool changed = false; bool changed = false;
if (doc["settings"] && jsonToSettings(doc["settings"], settings)) { if (!doc["settings"].isNull() && jsonToSettings(doc["settings"], settings)) {
vars.actions.restart = true; vars.actions.restart = true;
fsSettings.update(); fsSettings.update();
changed = true; changed = true;
} }
if (doc["network"] && jsonToNetworkSettings(doc["network"], networkSettings)) { if (!doc["network"].isNull() && jsonToNetworkSettings(doc["network"], networkSettings)) {
fsNetworkSettings.update(); fsNetworkSettings.update();
network->setHostname(networkSettings.hostname) network->setHostname(networkSettings.hostname)
->setStaCredentials(networkSettings.sta.ssid, networkSettings.sta.password, networkSettings.sta.channel) ->setStaCredentials(networkSettings.sta.ssid, networkSettings.sta.password, networkSettings.sta.channel)
@@ -262,6 +276,19 @@ protected:
changed = true; changed = true;
} }
if (!doc["sensors"].isNull()) {
for (uint8_t sensorId = 0; sensorId <= Sensors::getMaxSensorId(); sensorId++) {
if (doc["sensors"][sensorId].isNull()) {
continue;
}
auto sensorSettingsDoc = doc["sensors"][sensorId].to<JsonObject>();
if (jsonToSensorSettings(sensorId, sensorSettingsDoc, Sensors::settings[sensorId])){
changed = true;
}
}
}
doc.clear(); doc.clear();
doc.shrinkToFit(); doc.shrinkToFit();
@@ -413,7 +440,7 @@ protected:
this->webServer->send(406); this->webServer->send(406);
return; return;
} else if (plain.length() > 2048) { } else if (plain.length() > 2536) {
this->webServer->send(413); this->webServer->send(413);
return; return;
} }
@@ -446,6 +473,135 @@ protected:
}); });
// sensors list
this->webServer->on("/api/sensors", HTTP_GET, [this]() {
if (this->isAuthRequired()) {
if (!this->webServer->authenticate(settings.portal.login, settings.portal.password)) {
return this->webServer->send(401);
}
}
bool detailed = false;
if (this->webServer->hasArg("detailed")) {
detailed = this->webServer->arg("detailed").toInt() > 0;
}
JsonDocument doc;
for (uint8_t sensorId = 0; sensorId <= Sensors::getMaxSensorId(); sensorId++) {
if (detailed) {
auto& sSensor = Sensors::settings[sensorId];
doc[sensorId]["name"] = sSensor.name;
doc[sensorId]["purpose"] = static_cast<uint8_t>(sSensor.purpose);
sensorResultToJson(sensorId, doc[sensorId]);
} else {
doc[sensorId] = Sensors::settings[sensorId].name;
}
}
doc.shrinkToFit();
this->bufferedWebServer->send(200, "application/json", doc);
});
// sensor settings
this->webServer->on("/api/sensor", HTTP_GET, [this]() {
if (this->isAuthRequired()) {
if (!this->webServer->authenticate(settings.portal.login, settings.portal.password)) {
return this->webServer->send(401);
}
}
if (!this->webServer->hasArg("id")) {
return this->webServer->send(400);
}
auto id = this->webServer->arg("id");
if (!isDigit(id.c_str())) {
return this->webServer->send(400);
}
uint8_t sensorId = id.toInt();
id.clear();
if (!Sensors::isValidSensorId(sensorId)) {
return this->webServer->send(404);
}
JsonDocument doc;
sensorSettingsToJson(sensorId, Sensors::settings[sensorId], doc);
doc.shrinkToFit();
this->bufferedWebServer->send(200, "application/json", doc);
});
this->webServer->on("/api/sensor", HTTP_POST, [this]() {
if (this->isAuthRequired()) {
if (!this->webServer->authenticate(settings.portal.login, settings.portal.password)) {
return this->webServer->send(401);
}
}
#ifdef ARDUINO_ARCH_ESP8266
if (!this->webServer->hasArg("id") || this->webServer->args() != 1) {
return this->webServer->send(400);
}
#else
if (!this->webServer->hasArg("id") || this->webServer->args() != 2) {
return this->webServer->send(400);
}
#endif
auto id = this->webServer->arg("id");
if (!isDigit(id.c_str())) {
return this->webServer->send(400);
}
uint8_t sensorId = id.toInt();
id.clear();
if (!Sensors::isValidSensorId(sensorId)) {
return this->webServer->send(404);
}
auto plain = this->webServer->arg(1);
Log.straceln(FPSTR(L_PORTAL_WEBSERVER), F("Request /api/sensor/?id=%hhu %d bytes: %s"), sensorId, plain.length(), plain.c_str());
if (plain.length() < 5) {
return this->webServer->send(406);
} else if (plain.length() > 1024) {
return this->webServer->send(413);
}
bool changed = false;
auto prevSettings = Sensors::settings[sensorId];
{
JsonDocument doc;
DeserializationError dErr = deserializeJson(doc, plain);
plain.clear();
if (dErr != DeserializationError::Ok || doc.isNull() || !doc.size()) {
return this->webServer->send(400);
}
if (jsonToSensorSettings(sensorId, doc, Sensors::settings[sensorId])) {
changed = true;
}
}
{
JsonDocument doc;
auto& sSettings = Sensors::settings[sensorId];
sensorSettingsToJson(sensorId, sSettings, doc);
doc.shrinkToFit();
this->bufferedWebServer->send(changed ? 201 : 200, "application/json", doc);
}
if (changed) {
tMqtt->rebuildHaEntity(sensorId, prevSettings);
fsSensorsSettings.update();
}
});
// vars // vars
this->webServer->on("/api/vars", HTTP_GET, [this]() { this->webServer->on("/api/vars", HTTP_GET, [this]() {
JsonDocument doc; JsonDocument doc;
@@ -469,7 +625,7 @@ protected:
this->webServer->send(406); this->webServer->send(406);
return; return;
} else if (plain.length() > 1024) { } else if (plain.length() > 1536) {
this->webServer->send(413); this->webServer->send(413);
return; return;
} }
@@ -504,6 +660,9 @@ protected:
bool isConnected = network->isConnected(); bool isConnected = network->isConnected();
JsonDocument doc; JsonDocument doc;
doc["system"]["resetReason"] = getResetReason();
doc["system"]["uptime"] = millis() / 1000ul;
doc["network"]["hostname"] = networkSettings.hostname; doc["network"]["hostname"] = networkSettings.hostname;
doc["network"]["mac"] = network->getStaMac(); doc["network"]["mac"] = network->getStaMac();
doc["network"]["connected"] = isConnected; doc["network"]["connected"] = isConnected;
@@ -515,49 +674,124 @@ protected:
doc["network"]["gateway"] = isConnected ? network->getStaGateway().toString() : ""; doc["network"]["gateway"] = isConnected ? network->getStaGateway().toString() : "";
doc["network"]["dns"] = isConnected ? network->getStaDns().toString() : ""; doc["network"]["dns"] = isConnected ? network->getStaDns().toString() : "";
doc["system"]["buildVersion"] = BUILD_VERSION; doc["build"]["version"] = BUILD_VERSION;
doc["system"]["buildDate"] = __DATE__ " " __TIME__; doc["build"]["date"] = __DATE__ " " __TIME__;
doc["system"]["buildEnv"] = BUILD_ENV; doc["build"]["env"] = BUILD_ENV;
doc["system"]["uptime"] = millis() / 1000ul;
doc["system"]["totalHeap"] = getTotalHeap(); doc["heap"]["total"] = getTotalHeap();
doc["system"]["freeHeap"] = getFreeHeap(); doc["heap"]["free"] = getFreeHeap();
doc["system"]["minFreeHeap"] = getFreeHeap(true); doc["heap"]["minFree"] = getFreeHeap(true);
doc["system"]["maxFreeBlockHeap"] = getMaxFreeBlockHeap(); doc["heap"]["maxFreeBlock"] = getMaxFreeBlockHeap();
doc["system"]["minMaxFreeBlockHeap"] = getMaxFreeBlockHeap(true); doc["heap"]["minMaxFreeBlock"] = getMaxFreeBlockHeap(true);
doc["system"]["resetReason"] = getResetReason();
#ifdef ARDUINO_ARCH_ESP8266 #ifdef ARDUINO_ARCH_ESP8266
doc["system"]["chipModel"] = esp_is_8285() ? "ESP8285" : "ESP8266"; doc["build"]["core"] = ESP.getCoreVersion();
doc["system"]["chipRevision"] = 0; doc["build"]["sdk"] = ESP.getSdkVersion();
doc["system"]["chipCores"] = 1; doc["chip"]["model"] = esp_is_8285() ? "ESP8285" : "ESP8266";
doc["system"]["cpuFreq"] = ESP.getCpuFreqMHz(); doc["chip"]["rev"] = 0;
doc["system"]["coreVersion"] = ESP.getCoreVersion(); doc["chip"]["cores"] = 1;
doc["system"]["flashSize"] = ESP.getFlashChipSize(); doc["chip"]["freq"] = ESP.getCpuFreqMHz();
doc["system"]["flashRealSize"] = ESP.getFlashChipRealSize(); doc["flash"]["size"] = ESP.getFlashChipSize();
doc["flash"]["realSize"] = ESP.getFlashChipRealSize();
#elif ARDUINO_ARCH_ESP32 #elif ARDUINO_ARCH_ESP32
doc["system"]["chipModel"] = ESP.getChipModel(); doc["build"]["core"] = ESP.getCoreVersion();
doc["system"]["chipRevision"] = ESP.getChipRevision(); doc["build"]["sdk"] = ESP.getSdkVersion();
doc["system"]["chipCores"] = ESP.getChipCores(); doc["chip"]["model"] = ESP.getChipModel();
doc["system"]["cpuFreq"] = ESP.getCpuFreqMHz(); doc["chip"]["rev"] = ESP.getChipRevision();
doc["system"]["coreVersion"] = ESP.getSdkVersion(); doc["chip"]["cores"] = ESP.getChipCores();
doc["system"]["flashSize"] = ESP.getFlashChipSize(); doc["chip"]["freq"] = ESP.getCpuFreqMHz();
doc["system"]["flashRealSize"] = doc["system"]["flashSize"]; doc["flash"]["size"] = ESP.getFlashChipSize();
doc["flash"]["realSize"] = doc["flash"]["size"];
#else #else
doc["system"]["chipModel"] = 0; doc["build"]["core"] = 0;
doc["system"]["chipRevision"] = 0; doc["build"]["sdk"] = 0;
doc["system"]["chipCores"] = 0; doc["chip"]["model"] = 0;
doc["system"]["cpuFreq"] = 0; doc["chip"]["rev"] = 0;
doc["system"]["coreVersion"] = 0; doc["chip"]["cores"] = 0;
doc["system"]["flashSize"] = 0; doc["chip"]["freq"] = 0;
doc["system"]["flashRealSize"] = 0; doc["flash"]["size"] = 0;
doc["flash"]["realSize"] = 0;
#endif #endif
doc.shrinkToFit(); doc.shrinkToFit();
this->bufferedWebServer->send(200, "application/json", doc); this->bufferedWebServer->send(200, "application/json", doc);
}); });
this->webServer->on("/api/debug", HTTP_GET, [this]() {
JsonDocument doc;
doc["build"]["version"] = BUILD_VERSION;
doc["build"]["date"] = __DATE__ " " __TIME__;
doc["build"]["env"] = BUILD_ENV;
doc["heap"]["total"] = getTotalHeap();
doc["heap"]["free"] = getFreeHeap();
doc["heap"]["minFree"] = getFreeHeap(true);
doc["heap"]["maxFreeBlock"] = getMaxFreeBlockHeap();
doc["heap"]["minMaxFreeBlock"] = getMaxFreeBlockHeap(true);
#if defined(ARDUINO_ARCH_ESP32)
auto reason = esp_reset_reason();
if (reason != ESP_RST_UNKNOWN && reason != ESP_RST_POWERON && reason != ESP_RST_SW) {
#elif defined(ARDUINO_ARCH_ESP8266)
auto reason = ESP.getResetInfoPtr()->reason;
if (reason != REASON_DEFAULT_RST && reason != REASON_SOFT_RESTART && reason != REASON_EXT_SYS_RST) {
#else
if (false) {
#endif
doc["crash"]["reason"] = getResetReason();
doc["crash"]["core"] = CrashRecorder::ext.core;
doc["crash"]["heap"] = CrashRecorder::ext.heap;
doc["crash"]["uptime"] = CrashRecorder::ext.uptime;
if (CrashRecorder::backtrace.length > 0 && CrashRecorder::backtrace.length <= CrashRecorder::backtraceMaxLength) {
String backtraceStr;
arr2str(backtraceStr, CrashRecorder::backtrace.data, CrashRecorder::backtrace.length);
doc["crash"]["backtrace"]["data"] = backtraceStr;
doc["crash"]["backtrace"]["continues"] = CrashRecorder::backtrace.continues;
}
if (CrashRecorder::epc.length > 0 && CrashRecorder::epc.length <= CrashRecorder::epcMaxLength) {
String epcStr;
arr2str(epcStr, CrashRecorder::epc.data, CrashRecorder::epc.length);
doc["crash"]["epc"] = epcStr;
}
}
#ifdef ARDUINO_ARCH_ESP8266
doc["build"]["core"] = ESP.getCoreVersion();
doc["build"]["sdk"] = ESP.getSdkVersion();
doc["chip"]["model"] = esp_is_8285() ? "ESP8285" : "ESP8266";
doc["chip"]["rev"] = 0;
doc["chip"]["cores"] = 1;
doc["chip"]["freq"] = ESP.getCpuFreqMHz();
doc["flash"]["size"] = ESP.getFlashChipSize();
doc["flash"]["realSize"] = ESP.getFlashChipRealSize();
#elif ARDUINO_ARCH_ESP32
doc["build"]["core"] = ESP.getCoreVersion();
doc["build"]["sdk"] = ESP.getSdkVersion();
doc["chip"]["model"] = ESP.getChipModel();
doc["chip"]["rev"] = ESP.getChipRevision();
doc["chip"]["cores"] = ESP.getChipCores();
doc["chip"]["freq"] = ESP.getCpuFreqMHz();
doc["flash"]["size"] = ESP.getFlashChipSize();
doc["flash"]["realSize"] = doc["flash"]["size"];
#else
doc["build"]["core"] = 0;
doc["build"]["sdk"] = 0;
doc["chip"]["model"] = 0;
doc["chip"]["rev"] = 0;
doc["chip"]["cores"] = 0;
doc["chip"]["freq"] = 0;
doc["flash"]["size"] = 0;
doc["flash"]["realSize"] = 0;
#endif
doc.shrinkToFit();
this->webServer->sendHeader(F("Content-Disposition"), F("attachment; filename=\"debug.json\""));
this->bufferedWebServer->send(200, "application/json", doc, true);
});
// not found // not found
this->webServer->onNotFound([this]() { this->webServer->onNotFound([this]() {
@@ -582,6 +816,10 @@ protected:
void loop() { void loop() {
// web server // web server
if (!this->stateWebServer() && (network->isApEnabled() || network->isConnected()) && millis() - this->webServerChangeState >= this->changeStateInterval) { if (!this->stateWebServer() && (network->isApEnabled() || network->isConnected()) && millis() - this->webServerChangeState >= this->changeStateInterval) {
#ifdef ARDUINO_ARCH_ESP32
this->delay(250);
#endif
this->startWebServer(); this->startWebServer();
Log.straceln(FPSTR(L_PORTAL_WEBSERVER), F("Started: AP up or STA connected")); Log.straceln(FPSTR(L_PORTAL_WEBSERVER), F("Started: AP up or STA connected"));
@@ -687,7 +925,7 @@ protected:
return; return;
} }
this->webServer->handleClient(); //this->webServer->handleClient();
this->webServer->stop(); this->webServer->stop();
this->webServerEnabled = false; this->webServerEnabled = false;
this->webServerChangeState = millis(); this->webServerChangeState = millis();
@@ -712,7 +950,7 @@ protected:
return; return;
} }
this->dnsServer->processNextRequest(); //this->dnsServer->processNextRequest();
this->dnsServer->stop(); this->dnsServer->stop();
this->dnsServerEnabled = false; this->dnsServerEnabled = false;
this->dnsServerChangeState = millis(); this->dnsServerChangeState = millis();

View File

@@ -10,9 +10,12 @@ public:
RegulatorTask(bool _enabled = false, unsigned long _interval = 0) : LeanTask(_enabled, _interval) {} RegulatorTask(bool _enabled = false, unsigned long _interval = 0) : LeanTask(_enabled, _interval) {}
protected: protected:
float prevHeatingTarget = 0; float prevHeatingTarget = 0.0f;
float prevEtResult = 0; float prevEtResult = 0.0f;
float prevPidResult = 0; float prevPidResult = 0.0f;
bool indoorSensorsConnected = false;
//bool outdoorSensorsConnected = false;
#if defined(ARDUINO_ARCH_ESP32) #if defined(ARDUINO_ARCH_ESP32)
const char* getTaskName() override { const char* getTaskName() override {
@@ -29,104 +32,139 @@ protected:
#endif #endif
void loop() { void loop() {
float newTemp = vars.parameters.heatingSetpoint; this->indoorSensorsConnected = Sensors::existsConnectedSensorsByPurpose(Sensors::Purpose::INDOOR_TEMP);
//this->outdoorSensorsConnected = Sensors::existsConnectedSensorsByPurpose(Sensors::Purpose::OUTDOOR_TEMP);
if (vars.states.emergency) { if (settings.equitherm.enabled || settings.pid.enabled || settings.opentherm.nativeHeatingControl) {
if (settings.heating.turbo) { vars.master.heating.indoorTempControl = true;
settings.heating.turbo = false; vars.master.heating.minTemp = THERMOSTAT_INDOOR_MIN_TEMP;
vars.master.heating.maxTemp = THERMOSTAT_INDOOR_MAX_TEMP;
Log.sinfoln(FPSTR(L_REGULATOR), F("Turbo mode auto disabled"));
}
newTemp = this->getEmergencyModeTemp();
} else { } else {
if (settings.heating.turbo && (fabs(settings.heating.target - vars.temperatures.indoor) < 1 || !settings.heating.enable || (settings.equitherm.enable && settings.pid.enable))) { vars.master.heating.indoorTempControl = false;
settings.heating.turbo = false; vars.master.heating.minTemp = settings.heating.minTemp;
vars.master.heating.maxTemp = settings.heating.maxTemp;
Log.sinfoln(FPSTR(L_REGULATOR), F("Turbo mode auto disabled"));
}
newTemp = this->getNormalModeTemp();
} }
// Limits if (!settings.pid.enabled && fabsf(pidRegulator.integral) > 0.01f) {
newTemp = constrain( pidRegulator.integral = 0.0f;
newTemp,
!settings.opentherm.nativeHeatingControl ? settings.heating.minTemp : THERMOSTAT_INDOOR_MIN_TEMP, Log.sinfoln(FPSTR(L_REGULATOR_PID), F("Integral sum has been reset"));
!settings.opentherm.nativeHeatingControl ? settings.heating.maxTemp : THERMOSTAT_INDOOR_MAX_TEMP }
this->turbo();
this->hysteresis();
vars.master.heating.targetTemp = constrain(
this->getHeatingSetpoint(),
vars.master.heating.minTemp,
vars.master.heating.maxTemp
); );
if (fabs(vars.parameters.heatingSetpoint - newTemp) > 0.4999f) { Sensors::setValueByType(
vars.parameters.heatingSetpoint = newTemp; Sensors::Type::HEATING_SETPOINT_TEMP, vars.master.heating.targetTemp,
Sensors::ValueType::PRIMARY, true, true
);
}
void turbo() {
if (settings.heating.turbo) {
if (!settings.heating.enabled || vars.emergency.state || !this->indoorSensorsConnected) {
settings.heating.turbo = false;
} else if (!settings.pid.enabled && !settings.equitherm.enabled) {
settings.heating.turbo = false;
} else if (fabsf(settings.heating.target - vars.master.heating.indoorTemp) <= 1.0f) {
settings.heating.turbo = false;
}
if (!settings.heating.turbo) {
Log.sinfoln(FPSTR(L_REGULATOR), F("Turbo mode auto disabled"));
}
}
}
void hysteresis() {
bool useHyst = false;
if (settings.heating.hysteresis > 0.01f && this->indoorSensorsConnected) {
useHyst = settings.equitherm.enabled || settings.pid.enabled || settings.opentherm.nativeHeatingControl;
}
if (useHyst) {
if (!vars.master.heating.blocking && vars.master.heating.indoorTemp - settings.heating.target + 0.0001f >= settings.heating.hysteresis) {
vars.master.heating.blocking = true;
} else if (vars.master.heating.blocking && vars.master.heating.indoorTemp - settings.heating.target - 0.0001f <= -(settings.heating.hysteresis)) {
vars.master.heating.blocking = false;
}
} else if (vars.master.heating.blocking) {
vars.master.heating.blocking = false;
} }
} }
float getEmergencyModeTemp() { float getHeatingSetpoint() {
float newTemp = 0; float newTemp = 0;
// if use equitherm if (fabsf(prevHeatingTarget - settings.heating.target) > 0.0001f) {
if (settings.emergency.useEquitherm) {
float etResult = getEquithermTemp(settings.heating.minTemp, settings.heating.maxTemp);
if (fabs(prevEtResult - etResult) > 0.4999f) {
prevEtResult = etResult;
newTemp += etResult;
Log.sinfoln(FPSTR(L_REGULATOR_EQUITHERM), F("New emergency result: %.2f"), etResult);
} else {
newTemp += prevEtResult;
}
} else if(settings.emergency.usePid) {
if (vars.parameters.heatingEnabled) {
float pidResult = getPidTemp(
settings.heating.minTemp,
settings.heating.maxTemp
);
if (fabs(prevPidResult - pidResult) > 0.4999f) {
prevPidResult = pidResult;
newTemp += pidResult;
Log.sinfoln(FPSTR(L_REGULATOR_PID), F("New emergency result: %.2f"), pidResult);
} else {
newTemp += prevPidResult;
}
} else if (!vars.parameters.heatingEnabled && prevPidResult != 0) {
newTemp += prevPidResult;
}
} else {
// default temp, manual mode
newTemp = settings.emergency.target;
}
return newTemp;
}
float getNormalModeTemp() {
float newTemp = 0;
if (fabs(prevHeatingTarget - settings.heating.target) > 0.0001f) {
prevHeatingTarget = settings.heating.target; prevHeatingTarget = settings.heating.target;
Log.sinfoln(FPSTR(L_REGULATOR), F("New target: %.2f"), settings.heating.target); Log.sinfoln(FPSTR(L_REGULATOR), F("New target: %.2f"), settings.heating.target);
if (/*settings.equitherm.enable && */settings.pid.enable) { /*if (settings.pid.enabled) {
pidRegulator.integral = 0; pidRegulator.integral = 0.0f;
Log.sinfoln(FPSTR(L_REGULATOR_PID), F("Integral sum has been reset")); Log.sinfoln(FPSTR(L_REGULATOR_PID), F("Integral sum has been reset"));
} }*/
}
if (vars.emergency.state) {
return settings.emergency.target;
} else if (settings.opentherm.nativeHeatingControl) {
return settings.heating.target;
} else if (!settings.equitherm.enabled && !settings.pid.enabled) {
return settings.heating.target;
} }
// if use equitherm // if use equitherm
if (settings.equitherm.enable) { if (settings.equitherm.enabled) {
float etResult = getEquithermTemp(settings.heating.minTemp, settings.heating.maxTemp); unsigned short minTemp = settings.heating.minTemp;
unsigned short maxTemp = settings.heating.maxTemp;
float targetTemp = settings.heating.target;
float indoorTemp = vars.master.heating.indoorTemp;
float outdoorTemp = vars.master.heating.outdoorTemp;
if (fabs(prevEtResult - etResult) > 0.4999f) { if (settings.system.unitSystem == UnitSystem::IMPERIAL) {
minTemp = f2c(minTemp);
maxTemp = f2c(maxTemp);
targetTemp = f2c(targetTemp);
indoorTemp = f2c(indoorTemp);
outdoorTemp = f2c(outdoorTemp);
}
if (!this->indoorSensorsConnected || settings.pid.enabled) {
etRegulator.Kt = 0.0f;
etRegulator.indoorTemp = 0.0f;
} else {
etRegulator.Kt = settings.heating.turbo ? 0.0f : settings.equitherm.t_factor;
etRegulator.indoorTemp = indoorTemp;
}
etRegulator.setLimits(minTemp, maxTemp);
etRegulator.Kn = settings.equitherm.n_factor;
etRegulator.Kk = settings.equitherm.k_factor;
etRegulator.targetTemp = targetTemp;
etRegulator.outdoorTemp = outdoorTemp;
float etResult = etRegulator.getResult();
if (settings.system.unitSystem == UnitSystem::IMPERIAL) {
etResult = c2f(etResult);
}
if (fabsf(prevEtResult - etResult) > 0.09f) {
prevEtResult = etResult; prevEtResult = etResult;
newTemp += etResult; newTemp += etResult;
@@ -138,14 +176,27 @@ protected:
} }
// if use pid // if use pid
if (settings.pid.enable) { if (settings.pid.enabled) {
if (vars.parameters.heatingEnabled) { //if (vars.parameters.heatingEnabled) {
float pidResult = getPidTemp( if (settings.heating.enabled && this->indoorSensorsConnected) {
settings.equitherm.enable ? (settings.pid.maxTemp * -1) : settings.pid.minTemp, pidRegulator.Kp = settings.heating.turbo ? 0.0f : settings.pid.p_factor;
settings.pid.maxTemp pidRegulator.Kd = settings.pid.d_factor;
);
if (fabs(prevPidResult - pidResult) > 0.4999f) { pidRegulator.setLimits(settings.pid.minTemp, settings.pid.maxTemp);
pidRegulator.setDt(settings.pid.dt * 1000u);
pidRegulator.input = vars.master.heating.indoorTemp;
pidRegulator.setpoint = settings.heating.target;
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 pidResult = pidRegulator.getResultTimer();
if (fabsf(prevPidResult - pidResult) > 0.09f) {
prevPidResult = pidResult; prevPidResult = pidResult;
newTemp += pidResult; newTemp += pidResult;
@@ -155,108 +206,21 @@ protected:
} else { } else {
newTemp += prevPidResult; newTemp += prevPidResult;
} }
} else { } else {
newTemp += prevPidResult; newTemp += prevPidResult;
} }
} else if (fabs(pidRegulator.integral) > 0.0001f) {
pidRegulator.integral = 0;
Log.sinfoln(FPSTR(L_REGULATOR_PID), F("Integral sum has been reset"));
} }
// default temp, manual mode // Turbo mode
if (!settings.equitherm.enable && !settings.pid.enable) { if (settings.heating.turbo && (settings.equitherm.enabled || settings.pid.enabled)) {
newTemp = settings.heating.target; newTemp += constrain(
settings.heating.target - vars.master.heating.indoorTemp,
-3.0f,
3.0f
) * settings.heating.turboFactor;
} }
return newTemp; return newTemp;
} }
/**
* @brief Get the Equitherm Temp
* Calculations in degrees C, conversion occurs when using F
*
* @param minTemp
* @param maxTemp
* @return float
*/
float getEquithermTemp(int minTemp, int maxTemp) {
float targetTemp = vars.states.emergency ? settings.emergency.target : settings.heating.target;
float indoorTemp = vars.temperatures.indoor;
float outdoorTemp = vars.temperatures.outdoor;
if (settings.system.unitSystem == UnitSystem::IMPERIAL) {
minTemp = f2c(minTemp);
maxTemp = f2c(maxTemp);
targetTemp = f2c(targetTemp);
indoorTemp = f2c(indoorTemp);
outdoorTemp = f2c(outdoorTemp);
}
if (vars.states.emergency) {
if (settings.sensors.indoor.type == SensorType::MANUAL) {
etRegulator.Kt = 0;
etRegulator.indoorTemp = 0;
} else {
etRegulator.Kt = settings.equitherm.t_factor;
etRegulator.indoorTemp = indoorTemp;
}
etRegulator.outdoorTemp = outdoorTemp;
} else if (settings.pid.enable) {
etRegulator.Kt = 0;
etRegulator.indoorTemp = round(indoorTemp);
etRegulator.outdoorTemp = round(outdoorTemp);
} else {
if (settings.heating.turbo) {
etRegulator.Kt = 10;
} else {
etRegulator.Kt = settings.equitherm.t_factor;
}
etRegulator.indoorTemp = indoorTemp;
etRegulator.outdoorTemp = outdoorTemp;
}
etRegulator.setLimits(minTemp, maxTemp);
etRegulator.Kn = settings.equitherm.n_factor;
etRegulator.Kk = settings.equitherm.k_factor;
etRegulator.targetTemp = targetTemp;
float result = etRegulator.getResult();
if (settings.system.unitSystem == UnitSystem::IMPERIAL) {
result = c2f(result);
}
return result;
}
float getPidTemp(int minTemp, int maxTemp) {
if (fabs(pidRegulator.Kp - settings.pid.p_factor) >= 0.0001f) {
pidRegulator.Kp = settings.pid.p_factor;
pidRegulator.integral = 0;
Log.sinfoln(FPSTR(L_REGULATOR_PID), F("Integral sum has been reset"));
}
if (fabs(pidRegulator.Ki - settings.pid.i_factor) >= 0.0001f) {
pidRegulator.Ki = settings.pid.i_factor;
pidRegulator.integral = 0;
Log.sinfoln(FPSTR(L_REGULATOR_PID), F("Integral sum has been reset"));
}
if (fabs(pidRegulator.Kd - settings.pid.d_factor) >= 0.0001f) {
pidRegulator.Kd = settings.pid.d_factor;
pidRegulator.integral = 0;
Log.sinfoln(FPSTR(L_REGULATOR_PID), F("Integral sum has been reset"));
}
pidRegulator.setLimits(minTemp, maxTemp);
pidRegulator.setDt(settings.pid.dt * 1000u);
pidRegulator.input = vars.temperatures.indoor;
pidRegulator.setpoint = settings.heating.target;
return pidRegulator.getResultTimer();
}
}; };

424
src/Sensors.h Normal file
View File

@@ -0,0 +1,424 @@
#pragma once
class Sensors {
protected:
static uint8_t maxSensors;
public:
enum class Type : uint8_t {
OT_OUTDOOR_TEMP = 0,
OT_HEATING_TEMP = 1,
OT_HEATING_RETURN_TEMP = 2,
OT_DHW_TEMP = 3,
OT_DHW_TEMP2 = 4,
OT_DHW_FLOW_RATE = 5,
OT_CH2_TEMP = 6,
OT_EXHAUST_TEMP = 7,
OT_HEAT_EXCHANGER_TEMP = 8,
OT_PRESSURE = 9,
OT_MODULATION_LEVEL = 10,
OT_CURRENT_POWER = 11,
NTC_10K_TEMP = 50,
DALLAS_TEMP = 51,
BLUETOOTH = 52,
HEATING_SETPOINT_TEMP = 253,
MANUAL = 254,
NOT_CONFIGURED = 255
};
enum class Purpose : uint8_t {
OUTDOOR_TEMP = 0,
INDOOR_TEMP = 1,
HEATING_TEMP = 2,
HEATING_RETURN_TEMP = 3,
DHW_TEMP = 4,
DHW_RETURN_TEMP = 5,
DHW_FLOW_RATE = 6,
EXHAUST_TEMP = 7,
MODULATION_LEVEL = 8,
CURRENT_POWER = 9,
PRESSURE = 252,
HUMIDITY = 253,
TEMPERATURE = 254,
NOT_CONFIGURED = 255
};
enum class ValueType : uint8_t {
PRIMARY = 0,
TEMPERATURE = 0,
HUMIDITY = 1,
BATTERY = 2,
RSSI = 3
};
typedef struct {
bool enabled = false;
char name[33];
Purpose purpose = Purpose::NOT_CONFIGURED;
Type type = Type::NOT_CONFIGURED;
uint8_t gpio = GPIO_IS_NOT_CONFIGURED;
uint8_t address[8] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
float offset = 0.0f;
float factor = 1.0f;
bool filtering = false;
float filteringFactor = 0.15f;
} Settings;
typedef struct {
bool connected = false;
unsigned long activityTime = 0;
uint8_t signalQuality = 0;
//float raw[4] = {0.0f, 0.0f, 0.0f, 0.0f};
float values[4] = {0.0f, 0.0f, 0.0f, 0.0f};
} Result;
static Settings* settings;
static Result* results;
static inline void setMaxSensors(uint8_t value) {
maxSensors = value;
}
static inline uint8_t getMaxSensors() {
return maxSensors;
}
static uint8_t getMaxSensorId() {
uint8_t maxSensors = getMaxSensors();
return maxSensors > 1 ? (maxSensors - 1) : 0;
}
static inline bool isValidSensorId(const uint8_t id) {
return id >= 0 && id <= getMaxSensorId();
}
static inline bool isValidValueId(const uint8_t id) {
return id >= (uint8_t) ValueType::TEMPERATURE && id <= (uint8_t) ValueType::RSSI;
}
static bool hasEnabledAndValid(const uint8_t id) {
if (!isValidSensorId(id) || !settings[id].enabled) {
return false;
}
if (settings[id].type == Type::NOT_CONFIGURED || settings[id].purpose == Purpose::NOT_CONFIGURED) {
return false;
}
return true;
}
static uint8_t getAmountByType(Type type) {
if (settings == nullptr) {
return 0;
}
uint8_t amount = 0;
for (uint8_t id = 0; id < getMaxSensorId(); id++) {
if (settings[id].type == type) {
amount++;
}
}
return amount;
}
static int16_t getIdByName(const char* name) {
if (settings == nullptr) {
return 0;
}
for (uint8_t id = 0; id < getMaxSensorId(); id++) {
if (strcmp(settings[id].name, name) == 0) {
return id;
}
}
return -1;
}
static int16_t getIdByObjectId(const char* objectId) {
if (settings == nullptr) {
return 0;
}
for (uint8_t id = 0; id < getMaxSensorId(); id++) {
String _objectId = Sensors::makeObjectId(settings[id].name);
if (strcmp(_objectId.c_str(), objectId) == 0) {
return id;
}
}
return -1;
}
static bool setValueById(const uint8_t sensorId, float value, const ValueType valueType, const bool updateActivityTime = false, const bool markConnected = false) {
if (settings == nullptr || results == nullptr) {
return false;
}
uint8_t valueId = (uint8_t) valueType;
if (!isValidSensorId(sensorId) || !isValidValueId(valueId)) {
return false;
}
auto& sSensor = settings[sensorId];
auto& rSensor = results[sensorId];
float compensatedValue = value;
if (valueType == ValueType::PRIMARY) {
if (fabsf(sSensor.factor) > 0.001f) {
compensatedValue *= sSensor.factor;
}
if (fabsf(sSensor.offset) > 0.001f) {
compensatedValue += sSensor.offset;
}
} else if (valueType == ValueType::RSSI) {
if (sSensor.type == Type::BLUETOOTH) {
rSensor.signalQuality = Sensors::bluetoothRssiToQuality(value);
}
}
if (sSensor.filtering && fabs(rSensor.values[valueId]) >= 0.1f) {
rSensor.values[valueId] += (compensatedValue - rSensor.values[valueId]) * sSensor.filteringFactor;
} else {
rSensor.values[valueId] = compensatedValue;
}
if (updateActivityTime) {
rSensor.activityTime = millis();
}
if (markConnected) {
if (!rSensor.connected) {
rSensor.connected = true;
Log.snoticeln(
FPSTR(L_SENSORS), F("#%hhu '%s' new status: CONNECTED"),
sensorId, sSensor.name
);
}
}
Log.snoticeln(
FPSTR(L_SENSORS), F("#%hhu '%s' new value %hhu: %.2f, compensated: %.2f, raw: %.2f"),
sensorId, sSensor.name, valueId, rSensor.values[valueId], compensatedValue, value
);
return true;
}
static uint8_t setValueByType(Type type, float value, const ValueType valueType, const bool updateActivityTime = false, const bool markConnected = false) {
if (settings == nullptr) {
return 0;
}
uint8_t updated = 0;
// read sensors data for current instance
for (uint8_t sensorId = 0; sensorId < getMaxSensorId(); sensorId++) {
auto& sSensor = settings[sensorId];
// only target & valid sensors
if (!sSensor.enabled || sSensor.type != type) {
continue;
}
if (setValueById(sensorId, value, valueType, updateActivityTime, markConnected)) {
updated++;
}
}
return updated;
}
static bool getConnectionStatusById(const uint8_t sensorId) {
if (settings == nullptr || results == nullptr) {
return false;
}
if (!isValidSensorId(sensorId)) {
return false;
}
return results[sensorId].connected;
}
static bool setConnectionStatusById(const uint8_t sensorId, const bool status, const bool updateActivityTime = true) {
if (settings == nullptr || results == nullptr) {
return false;
}
if (!isValidSensorId(sensorId)) {
return false;
}
auto& sSensor = settings[sensorId];
auto& rSensor = results[sensorId];
if (rSensor.connected != status) {
Log.snoticeln(
FPSTR(L_SENSORS), F("#%hhu '%s' new status: %s"),
sensorId, sSensor.name, status ? F("CONNECTED") : F("DISCONNECTED")
);
rSensor.connected = status;
}
if (updateActivityTime) {
rSensor.activityTime = millis();
}
return true;
}
static uint8_t setConnectionStatusByType(Type type, const bool status, const bool updateActivityTime = true) {
if (settings == nullptr) {
return 0;
}
uint8_t updated = 0;
// read sensors data for current instance
for (uint8_t sensorId = 0; sensorId < getMaxSensorId(); sensorId++) {
auto& sSensor = settings[sensorId];
// only target & valid sensors
if (!sSensor.enabled || sSensor.type != type) {
continue;
}
if (setConnectionStatusById(sensorId, status, updateActivityTime)) {
updated++;
}
}
return updated;
}
static float getMeanValueByPurpose(Purpose purpose, const ValueType valueType, bool onlyConnected = true) {
if (settings == nullptr || results == nullptr) {
return 0;
}
uint8_t valueId = (uint8_t) valueType;
if (!isValidValueId(valueId)) {
return false;
}
float value = 0.0f;
uint8_t amount = 0;
for (uint8_t id = 0; id < getMaxSensorId(); id++) {
auto& sSensor = settings[id];
auto& rSensor = results[id];
if (sSensor.purpose == purpose && (!onlyConnected || rSensor.connected)) {
value += rSensor.values[valueId];
amount++;
}
}
if (!amount) {
return 0.0f;
} else if (amount == 1) {
return value;
} else {
return value / amount;
}
}
static bool existsConnectedSensorsByPurpose(Purpose purpose) {
if (settings == nullptr || results == nullptr) {
return 0;
}
for (uint8_t id = 0; id < getMaxSensorId(); id++) {
if (settings[id].purpose == purpose && results[id].connected) {
return true;
}
}
return false;
}
template <class T>
static String cleanName(T value, char space = ' ') {
String clean = value;
// only valid symbols
for (uint8_t pos = 0; pos < clean.length(); pos++) {
char symbol = clean.charAt(pos);
// 0..9
if (symbol >= 48 && symbol <= 57) {
continue;
}
// A..Z
if (symbol >= 65 && symbol <= 90) {
continue;
}
// a..z
if (symbol >= 97 && symbol <= 122) {
continue;
}
// _-
if (symbol == 95 || symbol == 45 || symbol == space) {
continue;
}
clean.setCharAt(pos, space);
}
clean.trim();
return clean;
}
template <class T>
static String makeObjectId(T value, char separator = '_') {
auto objId = cleanName(value);
objId.toLowerCase();
objId.replace(' ', separator);
return objId;
}
template <class TV, class TS>
static auto makeObjectIdWithSuffix(TV value, TS suffix, char separator = '_') {
auto objId = makeObjectId(value, separator);
objId += separator;
objId += suffix;
return objId;
}
template <class TV, class TP>
static auto makeObjectIdWithPrefix(TV value, TP prefix, char separator = '_') {
String objId = prefix;
objId += separator;
objId += makeObjectId(value, separator);
return objId;
}
static uint8_t bluetoothRssiToQuality(int rssi) {
return constrain(map(rssi, -110, -50, 0, 100), 0, 100);;
}
};
uint8_t Sensors::maxSensors = 0;
Sensors::Settings* Sensors::settings = nullptr;
Sensors::Result* Sensors::results = nullptr;

File diff suppressed because it is too large Load Diff

View File

@@ -24,16 +24,16 @@ struct NetworkSettings {
struct Settings { struct Settings {
struct { struct {
bool debug = DEBUG_BY_DEFAULT; uint8_t logLevel = DEFAULT_LOG_LEVEL;
struct { struct {
bool enable = USE_SERIAL; bool enabled = DEFAULT_SERIAL_ENABLED;
unsigned int baudrate = 115200; unsigned int baudrate = DEFAULT_SERIAL_BAUD;
} serial; } serial;
struct { struct {
bool enable = USE_TELNET; bool enabled = DEFAULT_TELNET_ENABLED;
unsigned short port = 23; unsigned short port = DEFAULT_TELNET_PORT;
} telnet; } telnet;
UnitSystem unitSystem = UnitSystem::METRIC; UnitSystem unitSystem = UnitSystem::METRIC;
@@ -51,9 +51,12 @@ struct Settings {
byte inGpio = DEFAULT_OT_IN_GPIO; byte inGpio = DEFAULT_OT_IN_GPIO;
byte outGpio = DEFAULT_OT_OUT_GPIO; byte outGpio = DEFAULT_OT_OUT_GPIO;
byte rxLedGpio = DEFAULT_OT_RX_LED_GPIO; byte rxLedGpio = DEFAULT_OT_RX_LED_GPIO;
byte faultStateGpio = DEFAULT_OT_FAULT_STATE_GPIO; uint8_t memberId = 0;
byte invertFaultState = false; uint8_t flags = 0;
unsigned int memberIdCode = 0; uint8_t maxModulation = 100;
float minPower = 0.0f;
float maxPower = 0.0f;
bool dhwPresent = true; bool dhwPresent = true;
bool summerWinterMode = false; bool summerWinterMode = false;
bool heatingCh2Enabled = true; bool heatingCh2Enabled = true;
@@ -63,10 +66,11 @@ struct Settings {
bool modulationSyncWithHeating = false; bool modulationSyncWithHeating = false;
bool getMinMaxTemp = true; bool getMinMaxTemp = true;
bool nativeHeatingControl = false; bool nativeHeatingControl = false;
bool immergasFix = false;
} opentherm; } opentherm;
struct { struct {
bool enable = false; bool enabled = DEFAULT_MQTT_ENABLED;
char server[81] = DEFAULT_MQTT_SERVER; char server[81] = DEFAULT_MQTT_SERVER;
unsigned short port = DEFAULT_MQTT_PORT; unsigned short port = DEFAULT_MQTT_PORT;
char user[33] = DEFAULT_MQTT_USER; char user[33] = DEFAULT_MQTT_USER;
@@ -77,64 +81,44 @@ struct Settings {
} mqtt; } mqtt;
struct { struct {
bool enable = true;
float target = DEFAULT_HEATING_TARGET_TEMP; float target = DEFAULT_HEATING_TARGET_TEMP;
unsigned short tresholdTime = 120; unsigned short tresholdTime = 120;
bool useEquitherm = false;
bool usePid = false;
bool onNetworkFault = true;
bool onMqttFault = true;
} emergency; } emergency;
struct { struct {
bool enable = true; bool enabled = true;
bool turbo = false; bool turbo = false;
float target = DEFAULT_HEATING_TARGET_TEMP; float target = DEFAULT_HEATING_TARGET_TEMP;
float hysteresis = 0.5f; float hysteresis = 0.5f;
float turboFactor = 7.5f;
byte minTemp = DEFAULT_HEATING_MIN_TEMP; byte minTemp = DEFAULT_HEATING_MIN_TEMP;
byte maxTemp = DEFAULT_HEATING_MAX_TEMP; byte maxTemp = DEFAULT_HEATING_MAX_TEMP;
byte maxModulation = 100;
} heating; } heating;
struct { struct {
bool enable = true; bool enabled = true;
float target = DEFAULT_DHW_TARGET_TEMP; float target = DEFAULT_DHW_TARGET_TEMP;
byte minTemp = DEFAULT_DHW_MIN_TEMP; byte minTemp = DEFAULT_DHW_MIN_TEMP;
byte maxTemp = DEFAULT_DHW_MAX_TEMP; byte maxTemp = DEFAULT_DHW_MAX_TEMP;
} dhw; } dhw;
struct { struct {
bool enable = false; bool enabled = false;
float p_factor = 2; float p_factor = 2.0f;
float i_factor = 0.0055f; float i_factor = 0.0055f;
float d_factor = 0; float d_factor = 0.0f;
unsigned short dt = 180; unsigned short dt = 180;
byte minTemp = 0; short minTemp = 0;
byte maxTemp = DEFAULT_HEATING_MAX_TEMP; short maxTemp = DEFAULT_HEATING_MAX_TEMP;
} pid; } pid;
struct { struct {
bool enable = false; bool enabled = false;
float n_factor = 0.7f; float n_factor = 0.7f;
float k_factor = 3.0f; float k_factor = 3.0f;
float t_factor = 2.0f; float t_factor = 2.0f;
} equitherm; } equitherm;
struct {
struct {
SensorType type = SensorType::BOILER;
byte gpio = DEFAULT_SENSOR_OUTDOOR_GPIO;
float offset = 0.0f;
} outdoor;
struct {
SensorType type = SensorType::MANUAL;
byte gpio = DEFAULT_SENSOR_INDOOR_GPIO;
uint8_t bleAddress[6] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
float offset = 0.0f;
} indoor;
} sensors;
struct { struct {
bool use = false; bool use = false;
byte gpio = DEFAULT_EXT_PUMP_GPIO; byte gpio = DEFAULT_EXT_PUMP_GPIO;
@@ -143,59 +127,222 @@ struct Settings {
unsigned short antiStuckTime = 300; unsigned short antiStuckTime = 300;
} externalPump; } externalPump;
struct {
struct {
bool enabled = false;
byte gpio = GPIO_IS_NOT_CONFIGURED;
byte invertState = false;
unsigned short thresholdTime = 60;
} input;
struct {
bool enabled = false;
byte gpio = GPIO_IS_NOT_CONFIGURED;
byte invertState = false;
unsigned short thresholdTime = 60;
bool onFault = true;
bool onLossConnection = true;
bool onEnabledHeating = false;
} output;
} cascadeControl;
char validationValue[8] = SETTINGS_VALID_VALUE; char validationValue[8] = SETTINGS_VALID_VALUE;
} settings; } settings;
Sensors::Settings sensorsSettings[SENSORS_AMOUNT] = {
{
false,
"Outdoor temp",
Sensors::Purpose::OUTDOOR_TEMP,
Sensors::Type::DALLAS_TEMP,
DEFAULT_SENSOR_OUTDOOR_GPIO
},
{
false,
"Indoor temp",
Sensors::Purpose::INDOOR_TEMP,
Sensors::Type::DALLAS_TEMP,
DEFAULT_SENSOR_INDOOR_GPIO
},
{
true,
"Heating temp",
Sensors::Purpose::HEATING_TEMP,
Sensors::Type::OT_HEATING_TEMP,
},
{
true,
"Heating return temp",
Sensors::Purpose::HEATING_RETURN_TEMP,
Sensors::Type::OT_HEATING_RETURN_TEMP,
},
{
true,
"Heating setpoint temp",
Sensors::Purpose::TEMPERATURE,
Sensors::Type::HEATING_SETPOINT_TEMP,
},
{
true,
"DHW temp",
Sensors::Purpose::DHW_TEMP,
Sensors::Type::OT_DHW_TEMP,
},
{
true,
"DHW flow rate",
Sensors::Purpose::DHW_FLOW_RATE,
Sensors::Type::OT_DHW_FLOW_RATE,
},
{
true,
"Exhaust temp",
Sensors::Purpose::EXHAUST_TEMP,
Sensors::Type::OT_EXHAUST_TEMP,
},
{
true,
"Pressure",
Sensors::Purpose::PRESSURE,
Sensors::Type::OT_PRESSURE,
},
{
true,
"Modulation level",
Sensors::Purpose::MODULATION_LEVEL,
Sensors::Type::OT_MODULATION_LEVEL,
},
{
true,
"Power",
Sensors::Purpose::CURRENT_POWER,
Sensors::Type::OT_CURRENT_POWER,
}
};
struct Variables { struct Variables {
struct { struct {
bool otStatus = false; bool connected = false;
bool emergency = false;
bool heating = false;
bool dhw = false;
bool flame = false;
bool fault = false;
bool diagnostic = false;
bool externalPump = false;
bool mqtt = false;
} states;
struct {
float modulation = 0.0f;
float pressure = 0.0f;
float dhwFlowRate = 0.0f;
byte faultCode = 0;
int8_t rssi = 0; int8_t rssi = 0;
} sensors; } network;
struct { struct {
float indoor = 0.0f; bool connected = false;
float outdoor = 0.0f; } mqtt;
float heating = 0.0f;
float heatingReturn = 0.0f;
float dhw = 0.0f;
float exhaust = 0.0f;
} temperatures;
struct { struct {
bool heatingEnabled = false; bool state = false;
byte heatingMinTemp = DEFAULT_HEATING_MIN_TEMP; } emergency;
byte heatingMaxTemp = DEFAULT_HEATING_MAX_TEMP;
float heatingSetpoint = 0; struct {
unsigned long extPumpLastEnableTime = 0; bool state = false;
byte dhwMinTemp = DEFAULT_DHW_MIN_TEMP; unsigned long lastEnableTime = 0;
byte dhwMaxTemp = DEFAULT_DHW_MAX_TEMP; } externalPump;
byte maxModulation = 0;
uint8_t slaveMemberId = 0; struct {
uint8_t slaveFlags = 0; bool input = false;
uint8_t slaveType = 0; bool output = false;
uint8_t slaveVersion = 0; } cascadeControl;
float slaveOtVersion = 0.0f;
uint8_t masterMemberId = 0; struct {
uint8_t masterFlags = 0; uint8_t memberId = 0;
uint8_t masterType = 0; uint8_t flags = 0;
uint8_t masterVersion = 0; uint8_t type = 0;
float masterOtVersion = 0; uint8_t appVersion = 0;
} parameters; float protocolVersion = 0.0f;
struct {
bool blocking = false;
bool enabled = false;
bool indoorTempControl = false;
float targetTemp = 0.0f;
float currentTemp = 0.0f;
float returnTemp = 0.0f;
float indoorTemp = 0.0f;
float outdoorTemp = 0.0f;
float minTemp = 0.0f;
float maxTemp = 0.0f;
} heating;
struct {
bool enabled = false;
float targetTemp = 0.0f;
float currentTemp = 0.0f;
float returnTemp = 0.0f;
} dhw;
struct {
bool enabled = false;
float targetTemp = 0.0f;
} ch2;
} master;
struct {
uint8_t memberId = 0;
uint8_t flags = 0;
uint8_t type = 0;
uint8_t appVersion = 0;
float protocolVersion = 0.0f;
bool connected = false;
bool flame = false;
float pressure = 0.0f;
float exhaustTemp = 0.0f;
float heatExchangerTemp = 0.0f;
struct {
bool active = false;
uint8_t code = 0;
} fault;
struct {
bool active = false;
uint16_t code = 0;
} diag;
struct {
uint8_t current = 0;
uint8_t min = 0;
uint8_t max = 100;
} modulation;
struct {
float current = 0.0f;
float min = 0.0f;
float max = 0.0f;
} power;
struct {
bool active = false;
bool enabled = false;
float targetTemp = 0.0f;
float currentTemp = 0.0f;
float returnTemp = 0.0f;
float indoorTemp = 0.0f;
float outdoorTemp = 0.0f;
uint8_t minTemp = DEFAULT_HEATING_MIN_TEMP;
uint8_t maxTemp = DEFAULT_HEATING_MAX_TEMP;
} heating;
struct {
bool active = false;
bool enabled = false;
float targetTemp = 0.0f;
float currentTemp = 0.0f;
float currentTemp2 = 0.0f;
float returnTemp = 0.0f;
float flowRate = 0.0f;
uint8_t minTemp = DEFAULT_DHW_MIN_TEMP;
uint8_t maxTemp = DEFAULT_DHW_MAX_TEMP;
} dhw;
struct {
bool enabled = false;
float targetTemp = 0.0f;
float currentTemp = 0.0f;
float indoorTemp = 0.0f;
} ch2;
} slave;
struct { struct {
bool restart = false; bool restart = false;

View File

@@ -2,10 +2,6 @@
#define PROJECT_REPO "https://github.com/Laxilef/OTGateway" #define PROJECT_REPO "https://github.com/Laxilef/OTGateway"
#define MQTT_RECONNECT_INTERVAL 15000 #define MQTT_RECONNECT_INTERVAL 15000
#define EXT_SENSORS_INTERVAL 5000
#define EXT_SENSORS_FILTER_K 0.15
#define CONFIG_URL "http://%s/" #define CONFIG_URL "http://%s/"
#define SETTINGS_VALID_VALUE "stvalid" // only 8 chars! #define SETTINGS_VALID_VALUE "stvalid" // only 8 chars!
#define GPIO_IS_NOT_CONFIGURED 0xff #define GPIO_IS_NOT_CONFIGURED 0xff
@@ -22,6 +18,13 @@
#define THERMOSTAT_INDOOR_MIN_TEMP 5 #define THERMOSTAT_INDOOR_MIN_TEMP 5
#define THERMOSTAT_INDOOR_MAX_TEMP 30 #define THERMOSTAT_INDOOR_MAX_TEMP 30
#define DEFAULT_NTC_NOMINAL_RESISTANCE 10000.0f
#define DEFAULT_NTC_NOMINAL_TEMP 25.0f
#define DEFAULT_NTC_REF_RESISTANCE 10000.0f
#define DEFAULT_NTC_BETA_FACTOR 3950.0f
#define DEFAULT_NTC_VREF 3300.0f
#define DEFAULT_NTC_VLOW_TRESHOLD 25.0f
#ifndef BUILD_VERSION #ifndef BUILD_VERSION
#define BUILD_VERSION "0.0.0" #define BUILD_VERSION "0.0.0"
#endif #endif
@@ -30,12 +33,20 @@
#define BUILD_ENV "undefined" #define BUILD_ENV "undefined"
#endif #endif
#ifndef USE_SERIAL #ifndef DEFAULT_SERIAL_ENABLED
#define USE_SERIAL true #define DEFAULT_SERIAL_ENABLED true
#endif #endif
#ifndef USE_TELNET #ifndef DEFAULT_SERIAL_BAUD
#define USE_TELNET true #define DEFAULT_SERIAL_BAUD 115200
#endif
#ifndef DEFAULT_TELNET_ENABLED
#define DEFAULT_TELNET_ENABLED true
#endif
#ifndef DEFAULT_TELNET_PORT
#define DEFAULT_TELNET_PORT 23
#endif #endif
#ifndef USE_BLE #ifndef USE_BLE
@@ -62,8 +73,8 @@
#define DEFAULT_STA_PASSWORD "" #define DEFAULT_STA_PASSWORD ""
#endif #endif
#ifndef DEBUG_BY_DEFAULT #ifndef DEFAULT_LOG_LEVEL
#define DEBUG_BY_DEFAULT false #define DEFAULT_LOG_LEVEL TinyLogger::Level::VERBOSE
#endif #endif
#ifndef DEFAULT_STATUS_LED_GPIO #ifndef DEFAULT_STATUS_LED_GPIO
@@ -78,6 +89,10 @@
#define DEFAULT_PORTAL_PASSWORD "" #define DEFAULT_PORTAL_PASSWORD ""
#endif #endif
#ifndef DEFAULT_MQTT_ENABLED
#define DEFAULT_MQTT_ENABLED false
#endif
#ifndef DEFAULT_MQTT_SERVER #ifndef DEFAULT_MQTT_SERVER
#define DEFAULT_MQTT_SERVER "" #define DEFAULT_MQTT_SERVER ""
#endif #endif
@@ -122,6 +137,10 @@
#define DEFAULT_SENSOR_INDOOR_GPIO GPIO_IS_NOT_CONFIGURED #define DEFAULT_SENSOR_INDOOR_GPIO GPIO_IS_NOT_CONFIGURED
#endif #endif
#ifndef SENSORS_AMOUNT
#define SENSORS_AMOUNT 20
#endif
#ifndef DEFAULT_EXT_PUMP_GPIO #ifndef DEFAULT_EXT_PUMP_GPIO
#define DEFAULT_EXT_PUMP_GPIO GPIO_IS_NOT_CONFIGURED #define DEFAULT_EXT_PUMP_GPIO GPIO_IS_NOT_CONFIGURED
#endif #endif
@@ -138,16 +157,9 @@
#define GPIO_IS_VALID(gpioNum) (gpioNum != GPIO_IS_NOT_CONFIGURED && GPIO_IS_VALID_GPIO(gpioNum)) #define GPIO_IS_VALID(gpioNum) (gpioNum != GPIO_IS_NOT_CONFIGURED && GPIO_IS_VALID_GPIO(gpioNum))
enum class SensorType : byte { enum class UnitSystem : uint8_t {
BOILER, METRIC = 0,
MANUAL, IMPERIAL = 1
DS18B20,
BLUETOOTH
};
enum class UnitSystem : byte {
METRIC,
IMPERIAL
}; };
char buffer[255]; char buffer[255];

View File

@@ -1,12 +1,15 @@
#include <Arduino.h> #include <Arduino.h>
#include "defines.h"
#include "strings.h"
#include <ArduinoJson.h> #include <ArduinoJson.h>
#include <FileData.h> #include <FileData.h>
#include <LittleFS.h> #include <LittleFS.h>
#include <ESPTelnetStream.h> #include <ESPTelnetStream.h>
#include <TinyLogger.h> #include <TinyLogger.h>
#include <NetworkMgr.h> #include <NetworkMgr.h>
#include "defines.h"
#include "strings.h"
#include "CrashRecorder.h"
#include "Sensors.h"
#include "Settings.h" #include "Settings.h"
#include "utils.h" #include "utils.h"
@@ -30,10 +33,13 @@
using namespace NetworkUtils; using namespace NetworkUtils;
// Vars // Vars
FileData fsNetworkSettings(&LittleFS, "/network.conf", 'n', &networkSettings, sizeof(networkSettings), 1000);
FileData fsSettings(&LittleFS, "/settings.conf", 's', &settings, sizeof(settings), 60000);
ESPTelnetStream* telnetStream = nullptr; ESPTelnetStream* telnetStream = nullptr;
NetworkMgr* network = nullptr; NetworkMgr* network = nullptr;
Sensors::Result sensorsResults[SENSORS_AMOUNT];
FileData fsNetworkSettings(&LittleFS, "/network.conf", 'n', &networkSettings, sizeof(networkSettings), 1000);
FileData fsSettings(&LittleFS, "/settings.conf", 's', &settings, sizeof(settings), 60000);
FileData fsSensorsSettings(&LittleFS, "/sensors.conf", 'e', &sensorsSettings, sizeof(sensorsSettings), 60000);
// Tasks // Tasks
MqttTask* tMqtt; MqttTask* tMqtt;
@@ -45,6 +51,10 @@ MainTask* tMain;
void setup() { void setup() {
CrashRecorder::init();
Sensors::setMaxSensors(SENSORS_AMOUNT);
Sensors::settings = sensorsSettings;
Sensors::results = sensorsResults;
LittleFS.begin(); LittleFS.begin();
Log.setLevel(TinyLogger::Level::VERBOSE); Log.setLevel(TinyLogger::Level::VERBOSE);
@@ -62,10 +72,14 @@ void setup() {
}); });
Serial.begin(115200); Serial.begin(115200);
#if ARDUINO_USB_MODE
Serial.setTxBufferSize(512);
#endif
Log.addStream(&Serial); Log.addStream(&Serial);
Log.print("\n\n\r"); Log.print("\n\n\r");
// network settings //
// Network settings
switch (fsNetworkSettings.read()) { switch (fsNetworkSettings.read()) {
case FD_FS_ERR: case FD_FS_ERR:
Log.swarningln(FPSTR(L_NETWORK_SETTINGS), F("Filesystem error, load default")); Log.swarningln(FPSTR(L_NETWORK_SETTINGS), F("Filesystem error, load default"));
@@ -84,7 +98,27 @@ void setup() {
break; break;
} }
// settings network = (new NetworkMgr)
->setHostname(networkSettings.hostname)
->setStaCredentials(
strlen(networkSettings.sta.ssid) ? networkSettings.sta.ssid : nullptr,
strlen(networkSettings.sta.password) ? networkSettings.sta.password : nullptr,
networkSettings.sta.channel
)->setApCredentials(
strlen(networkSettings.ap.ssid) ? networkSettings.ap.ssid : nullptr,
strlen(networkSettings.ap.password) ? networkSettings.ap.password : nullptr,
networkSettings.ap.channel
)
->setUseDhcp(networkSettings.useDhcp)
->setStaticConfig(
networkSettings.staticConfig.ip,
networkSettings.staticConfig.gateway,
networkSettings.staticConfig.subnet,
networkSettings.staticConfig.dns
);
//
// Settings
switch (fsSettings.read()) { switch (fsSettings.read()) {
case FD_FS_ERR: case FD_FS_ERR:
Log.swarningln(FPSTR(L_SETTINGS), F("Filesystem error, load default")); Log.swarningln(FPSTR(L_SETTINGS), F("Filesystem error, load default"));
@@ -110,8 +144,8 @@ void setup() {
break; break;
} }
// logs // Logs settings
if (!settings.system.serial.enable) { if (!settings.system.serial.enabled) {
Serial.end(); Serial.end();
Log.clearStreams(); Log.clearStreams();
@@ -123,46 +157,44 @@ void setup() {
Log.addStream(&Serial); Log.addStream(&Serial);
} }
if (settings.system.telnet.enable) { if (settings.system.telnet.enabled) {
telnetStream = new ESPTelnetStream; telnetStream = new ESPTelnetStream;
telnetStream->setKeepAliveInterval(500); telnetStream->setKeepAliveInterval(500);
Log.addStream(telnetStream); Log.addStream(telnetStream);
} }
Log.setLevel(settings.system.debug ? TinyLogger::Level::VERBOSE : TinyLogger::Level::INFO); if (settings.system.logLevel >= TinyLogger::Level::SILENT && settings.system.logLevel <= TinyLogger::Level::VERBOSE) {
Log.setLevel(static_cast<TinyLogger::Level>(settings.system.logLevel));
}
// network //
network = (new NetworkMgr) // Sensors settings
->setHostname(networkSettings.hostname) switch (fsSensorsSettings.read()) {
->setStaCredentials( case FD_FS_ERR:
#ifdef WOKWI Log.swarningln(FPSTR(L_SENSORS), F("Filesystem error, load default"));
"Wokwi-GUEST", nullptr, 6 break;
#else case FD_FILE_ERR:
strlen(networkSettings.sta.ssid) ? networkSettings.sta.ssid : nullptr, Log.swarningln(FPSTR(L_SENSORS), F("Bad data, load default"));
strlen(networkSettings.sta.password) ? networkSettings.sta.password : nullptr, break;
networkSettings.sta.channel case FD_WRITE:
#endif Log.sinfoln(FPSTR(L_SENSORS), F("Not found, load default"));
)->setApCredentials( break;
strlen(networkSettings.ap.ssid) ? networkSettings.ap.ssid : nullptr, case FD_ADD:
strlen(networkSettings.ap.password) ? networkSettings.ap.password : nullptr, case FD_READ:
networkSettings.ap.channel Log.sinfoln(FPSTR(L_SENSORS), F("Loaded"));
) default:
->setUseDhcp(networkSettings.useDhcp) break;
->setStaticConfig( }
networkSettings.staticConfig.ip,
networkSettings.staticConfig.gateway,
networkSettings.staticConfig.subnet,
networkSettings.staticConfig.dns
);
// tasks //
// Make tasks
tMqtt = new MqttTask(false, 500); tMqtt = new MqttTask(false, 500);
Scheduler.start(tMqtt); Scheduler.start(tMqtt);
tOt = new OpenThermTask(true, 750); tOt = new OpenThermTask(true, 750);
Scheduler.start(tOt); Scheduler.start(tOt);
tSensors = new SensorsTask(true, EXT_SENSORS_INTERVAL); tSensors = new SensorsTask(true, 1000);
Scheduler.start(tSensors); Scheduler.start(tSensors);
tRegulator = new RegulatorTask(true, 10000); tRegulator = new RegulatorTask(true, 10000);

View File

@@ -4,6 +4,9 @@
#endif #endif
const char L_SETTINGS[] PROGMEM = "SETTINGS"; 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[] PROGMEM = "NETWORK";
const char L_NETWORK_SETTINGS[] PROGMEM = "NETWORK.SETTINGS"; const char L_NETWORK_SETTINGS[] PROGMEM = "NETWORK.SETTINGS";
const char L_PORTAL_WEBSERVER[] PROGMEM = "PORTAL.WEBSERVER"; const char L_PORTAL_WEBSERVER[] PROGMEM = "PORTAL.WEBSERVER";
@@ -12,13 +15,20 @@ const char L_PORTAL_CAPTIVE[] PROGMEM = "PORTAL.CAPTIVE";
const char L_PORTAL_OTA[] PROGMEM = "PORTAL.OTA"; const char L_PORTAL_OTA[] PROGMEM = "PORTAL.OTA";
const char L_MAIN[] PROGMEM = "MAIN"; const char L_MAIN[] PROGMEM = "MAIN";
const char L_MQTT[] PROGMEM = "MQTT"; 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_MQTT_MSG[] PROGMEM = "MQTT.MSG";
const char L_OT[] PROGMEM = "OT"; const char L_OT[] PROGMEM = "OT";
const char L_OT_DHW[] PROGMEM = "OT.DHW"; const char L_OT_DHW[] PROGMEM = "OT.DHW";
const char L_OT_HEATING[] PROGMEM = "OT.HEATING"; const char L_OT_HEATING[] PROGMEM = "OT.HEATING";
const char L_SENSORS_OUTDOOR[] PROGMEM = "SENSORS.OUTDOOR"; const char L_OT_CH2[] PROGMEM = "OT.CH2";
const char L_SENSORS_INDOOR[] PROGMEM = "SENSORS.INDOOR"; 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_SENSORS_BLE[] PROGMEM = "SENSORS.BLE";
const char L_REGULATOR[] PROGMEM = "REGULATOR"; const char L_REGULATOR[] PROGMEM = "REGULATOR";
const char L_REGULATOR_PID[] PROGMEM = "REGULATOR.PID"; const char L_REGULATOR_PID[] PROGMEM = "REGULATOR.PID";
const char L_REGULATOR_EQUITHERM[] PROGMEM = "REGULATOR.EQUITHERM"; 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";

File diff suppressed because it is too large Load Diff

View File

@@ -8,6 +8,14 @@
"issues": "Issues & questions", "issues": "Issues & questions",
"releases": "Releases" "releases": "Releases"
}, },
"dbm": "dBm",
"kw": "kW",
"time": {
"days": "d.",
"hours": "h.",
"min": "min.",
"sec": "sec."
},
"button": { "button": {
"upgrade": "Upgrade", "upgrade": "Upgrade",
@@ -37,7 +45,8 @@
"title": "Build", "title": "Build",
"version": "Version", "version": "Version",
"date": "Date", "date": "Date",
"sdk": "Core/SDK" "core": "Core",
"sdk": "SDK"
}, },
"uptime": "Uptime", "uptime": "Uptime",
"memory": { "memory": {
@@ -65,8 +74,9 @@
"section": { "section": {
"control": "Control", "control": "Control",
"states": "States and sensors", "states": "States",
"otDiag": "OpenTherm diagnostic" "sensors": "Sensors",
"diag": "OpenTherm diagnostic"
}, },
"thermostat": { "thermostat": {
@@ -77,27 +87,35 @@
"turbo": "Turbo mode" "turbo": "Turbo mode"
}, },
"state": { "states": {
"ot": "OpenTherm connected", "mNetworkConnected": "Network connection",
"mqtt": "MQTT connected", "mMqttConnected": "MQTT connection",
"emergency": "Emergency", "mEmergencyState": "Emergency mode",
"heating": "Heating", "mExtPumpState": "External pump",
"dhw": "DHW", "mCascadeControlInput": "Cascade control (input)",
"flame": "Flame", "mCascadeControlOutput": "Cascade control (output)",
"fault": "Fault",
"diag": "Diagnostic", "sConnected": "OpenTherm connection",
"extpump": "External pump", "sFlame": "Flame",
"modulation": "Modulation", "sFaultActive": "Fault",
"pressure": "Pressure", "sFaultCode": "Faul code",
"dhwFlowRate": "DHW flow rate", "sDiagActive": "Diagnostic",
"faultCode": "Fault code", "sDiagCode": "Diagnostic code",
"indoorTemp": "Indoor temp",
"outdoorTemp": "Outdoor temp", "mHeatEnabled": "Heating enabled",
"heatingTemp": "Heating temp", "mHeatBlocking": "Heating blocked",
"heatingSetpointTemp": "Heating setpoint temp", "sHeatActive": "Heating active",
"heatingReturnTemp": "Heating return temp", "mHeatTargetTemp": "Heating setpoint temp",
"dhwTemp": "DHW temp", "mHeatCurrTemp": "Heating current temp",
"exhaustTemp": "Exhaust temp" "mHeatRetTemp": "Heating return temp",
"mHeatIndoorTemp": "Heating, indoor temp",
"mHeatOutdoorTemp": "Heating, outdoor temp",
"mDhwEnabled": "DHW enabled",
"sDhwActive": "DHW active",
"mDhwTargetTemp": "DHW setpoint temp",
"mDhwCurrTemp": "DHW current temp",
"mDhwRetTemp": "DHW return temp"
} }
}, },
@@ -140,6 +158,76 @@
} }
}, },
"sensors": {
"title": "Sensors settings - OpenTherm Gateway",
"name": "Sensors settings",
"enabled": "Enabled",
"sensorName": {
"title": "Sensor name",
"note": "May only contain: a-z, A-Z, 0-9, _ and space"
},
"purpose": "Purpose",
"purposes": {
"outdoorTemp": "Outdoor temperature",
"indoorTemp": "Indoor temperature",
"heatTemp": "Heating, temperature",
"heatRetTemp": "Heating, return temperature",
"dhwTemp": "DHW, temperature",
"dhwRetTemp": "DHW, return temperature",
"dhwFlowRate": "DHW, flow rate",
"exhaustTemp": "Exhaust temperature",
"modLevel": "Modulation level (in percents)",
"currentPower": "Current power (in kWt)",
"pressure": "Pressure",
"humidity": "Humidity",
"temperature": "Temperature",
"notConfigured": "Not configured"
},
"type": "Type/source",
"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",
"ntcTemp": "NTC sensor",
"dallasTemp": "DALLAS sensor",
"bluetooth": "BLE sensor",
"heatSetpointTemp": "Heating, setpoint temp",
"manual": "Manual via MQTT/API",
"notConfigured": "Not configured"
},
"gpio": "GPIO",
"address": {
"title": "Sensor address",
"note": "For auto detection of DALLAS sensors leave it at default, for BLE devices need a MAC address"
},
"correction": {
"desc": "Correction of values",
"offset": "Compensation (offset)",
"factor": "Multiplier"
},
"filtering": {
"desc": "Filtering values",
"enabled": {
"title": "Enabled filtering",
"note": "It can be useful if there is a lot of sharp noise on the charts. The filter used is \"Running Average\"."
},
"factor": {
"title": "Filtration factor",
"note": "The lower the value, the smoother and <u>longer</u> the change in numeric values."
}
}
},
"settings": { "settings": {
"title": "Settings - OpenTherm Gateway", "title": "Settings - OpenTherm Gateway",
"name": "Settings", "name": "Settings",
@@ -151,22 +239,19 @@
"heating": "Heating settings", "heating": "Heating settings",
"dhw": "DHW settings", "dhw": "DHW settings",
"emergency": "Emergency mode settings", "emergency": "Emergency mode settings",
"emergency.events": "Events",
"emergency.regulators": "Using regulators",
"equitherm": "Equitherm settings", "equitherm": "Equitherm settings",
"pid": "PID settings", "pid": "PID settings",
"ot": "OpenTherm settings", "ot": "OpenTherm settings",
"ot.options": "Options",
"mqtt": "MQTT settings", "mqtt": "MQTT settings",
"outdorSensor": "Outdoor sensor settings", "extPump": "External pump settings",
"indoorSensor": "Indoor sensor settings", "cascadeControl": "Cascade control settings"
"extPump": "External pump settings"
}, },
"enable": "Enable", "enable": "Enable",
"note": { "note": {
"restart": "After changing these settings, the device must be restarted for the changes to take effect.", "restart": "After changing these settings, the device must be restarted for the changes to take effect.",
"blankNotUse": "blank - not use" "blankNotUse": "blank - not use",
"bleDevice": "BLE device can be used <u>only</u> with some ESP32 boards with BLE support!"
}, },
"temp": { "temp": {
@@ -185,16 +270,13 @@
"metric": "Metric <small>(celsius, liters, bar)</small>", "metric": "Metric <small>(celsius, liters, bar)</small>",
"imperial": "Imperial <small>(fahrenheit, gallons, psi)</small>", "imperial": "Imperial <small>(fahrenheit, gallons, psi)</small>",
"statusLedGpio": "Status LED GPIO", "statusLedGpio": "Status LED GPIO",
"debug": "Debug mode", "logLevel": "Log level",
"serial": { "serial": {
"enable": "Enable Serial port", "enable": "Enabled Serial port",
"baud": { "baud": "Serial port baud rate"
"title": "Serial port baud rate",
"note": "Available: 9600, 19200, 38400, 57600, 74880, 115200"
}
}, },
"telnet": { "telnet": {
"enable": "Enable Telnet", "enable": "Enabled Telnet",
"port": { "port": {
"title": "Telnet port", "title": "Telnet port",
"note": "Default: 23" "note": "Default: 23"
@@ -203,28 +285,18 @@
}, },
"heating": { "heating": {
"hyst": "Hysteresis", "hyst": "Hysteresis <small>(in degrees)</small>",
"maxMod": "Max modulation level" "turboFactor": "Turbo mode coeff."
}, },
"emergency": { "emergency": {
"desc": "<b>!</b> Emergency mode can be useful <u>only</u> when using Equitherm and/or PID (when normal work) and when reporting indoor/outdoor temperature via MQTT or API. In this mode, sensor values that are reported via MQTT/API are not used.", "desc": "Emergency mode is activated automatically when «PID» or «Equitherm» cannot calculate the heat carrier setpoint:<br />- if «Equitherm» is enabled and the outdoor temperature sensor is disconnected;<br />- if «PID» or OT option <i>«Native heating control»</i> is enabled and the indoor temperature sensor is disconnected.<br /><b>Note:</b> On network fault or MQTT fault, sensors with <i>«Manual via MQTT/API»</i> type will be in DISCONNECTED state.",
"target": { "target": {
"title": "Target temperature", "title": "Target temperature",
"note": "<u>Indoor temperature</u> if Equitherm or PID is <b>enabled</b><br /><u>Heat carrier temperature</u> if Equitherm and PID <b>is disabled</b>" "note": "<b>Important:</b> <u>Target indoor temperature</u> if OT option <i>«Native heating control»</i> is enabled.<br />In all other cases, the <u>target heat carrier temperature</u>."
}, },
"treshold": "Treshold time <small>(sec)</small>", "treshold": "Treshold time <small>(sec)</small>"
"events": {
"network": "On network fault",
"mqtt": "On MQTT fault"
},
"regulators": {
"equitherm": "Equitherm <small>(requires at least an external/boiler <u>outdoor</u> sensor)</small>",
"pid": "PID <small>(requires at least an external/BLE <u>indoor</u> sensor)</small>"
}
}, },
"equitherm": { "equitherm": {
@@ -240,16 +312,29 @@
"p": "P factor", "p": "P factor",
"i": "I factor", "i": "I factor",
"d": "D factor", "d": "D factor",
"dt": "DT <small>in seconds</small>" "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>."
}, },
"ot": { "ot": {
"advanced": "Advanced Settings",
"inGpio": "In GPIO", "inGpio": "In GPIO",
"outGpio": "Out GPIO", "outGpio": "Out GPIO",
"ledGpio": "RX LED GPIO", "ledGpio": "RX LED GPIO",
"memberIdCode": "Master MemberID code", "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\"."
},
"maxPower": {
"title": "Max boiler power <small>(kW)</small>",
"note": "<b>0</b> - try detect automatically. Typically found in the boiler specification as \"maximum useful heat output\"."
},
"options": { "options": {
"desc": "Options",
"dhwPresent": "DHW present", "dhwPresent": "DHW present",
"summerWinterMode": "Summer/winter mode", "summerWinterMode": "Summer/winter mode",
"heatingCh2Enabled": "Heating CH2 always enabled", "heatingCh2Enabled": "Heating CH2 always enabled",
@@ -257,18 +342,13 @@
"dhwToCh2": "Duplicate DHW to CH2", "dhwToCh2": "Duplicate DHW to CH2",
"dhwBlocking": "DHW blocking", "dhwBlocking": "DHW blocking",
"modulationSyncWithHeating": "Sync modulation with heating", "modulationSyncWithHeating": "Sync modulation with heating",
"getMinMaxTemp": "Get min/max temp from boiler" "getMinMaxTemp": "Get min/max temp from boiler",
}, "immergasFix": "Fix for Immergas boilers"
"faultState": {
"gpio": "Fault state GPIO",
"note": "Can be useful to switch on another boiler <u>via relay</u>. Blank - not use.",
"invert": "Invert fault state"
}, },
"nativeHeating": { "nativeHeating": {
"title": "Native heating control (boiler)", "title": "Native heating control (boiler)",
"note": "Works <u>ONLY</u> if the boiler requires the desired room temperature and regulates the temperature of the coolant itself. Not compatible with PID and Equitherm regulators and hysteresis in firmware." "note": "Works <u>ONLY</u> if the boiler requires the desired room temperature and regulates the temperature of the coolant itself. Not compatible with PID and Equitherm regulators in firmware."
} }
}, },
@@ -282,28 +362,35 @@
"interval": "Publish interval <small>(sec)</small>" "interval": "Publish interval <small>(sec)</small>"
}, },
"tempSensor": {
"source": {
"type": "Source type",
"boiler": "From boiler via OpenTherm",
"manual": "Manual via MQTT/API",
"ext": "External (DS18B20)",
"ble": "BLE device <i>(ONLY for some ESP32 which support BLE)</i>"
},
"gpio": "GPIO",
"offset": "Temp offset <small>(calibration)</small>",
"bleAddress": {
"title": "BLE address",
"note": "ONLY for some ESP32 which support BLE"
}
},
"extPump": { "extPump": {
"use": "Use external pump", "use": "Use external pump",
"gpio": "Relay GPIO", "gpio": "Relay GPIO",
"postCirculationTime": "Post circulation time <small>(min)</small>", "postCirculationTime": "Post circulation time <small>(min)</small>",
"antiStuckInterval": "Anti stuck interval <small>(days)</small>", "antiStuckInterval": "Anti stuck interval <small>(days)</small>",
"antiStuckTime": "Anti stuck time <small>(min)</small>" "antiStuckTime": "Anti stuck time <small>(min)</small>"
},
"cascadeControl": {
"input": {
"desc": "Can be used to turn on the heating only if another boiler is faulty. The other boiler controller must change the state of the GPIO input in the event of a fault.",
"enable": "Enabled input",
"gpio": "GPIO",
"invertState": "Invert GPIO state",
"thresholdTime": "State change threshold time <small>(sec)</small>"
},
"output": {
"desc": "Can be used to switch on another boiler <u>via relay</u>.",
"enable": "Enabled output",
"gpio": "GPIO",
"invertState": "Invert GPIO state",
"thresholdTime": "State change threshold time <small>(sec)</small>",
"events": {
"desc": "Events",
"onFault": "If the fault state is active",
"onLossConnection": "If the connection via Opentherm is lost",
"onEnabledHeating": "If heating is enabled"
}
}
} }
}, },

View File

@@ -8,6 +8,14 @@
"issues": "Проблемы и вопросы", "issues": "Проблемы и вопросы",
"releases": "Релизы" "releases": "Релизы"
}, },
"dbm": "дБм",
"kw": "кВт",
"time": {
"days": "д.",
"hours": "ч.",
"min": "мин.",
"sec": "сек."
},
"button": { "button": {
"upgrade": "Обновить", "upgrade": "Обновить",
@@ -37,7 +45,8 @@
"title": "Билд", "title": "Билд",
"version": "Версия", "version": "Версия",
"date": "Дата", "date": "Дата",
"sdk": "Ядро/SDK" "core": "Ядро",
"sdk": "SDK"
}, },
"uptime": "Аптайм", "uptime": "Аптайм",
"memory": { "memory": {
@@ -65,8 +74,9 @@
"section": { "section": {
"control": "Управление", "control": "Управление",
"states": "Состояние и сенсоры", "states": "Состояние",
"otDiag": "Диагностика OpenTherm" "sensors": "Сенсоры",
"diag": "Диагностика OpenTherm"
}, },
"thermostat": { "thermostat": {
@@ -77,27 +87,35 @@
"turbo": "Турбо" "turbo": "Турбо"
}, },
"state": { "states": {
"ot": "OpenTherm подключение", "mNetworkConnected": одключение к сети",
"mqtt": "MQTT подключение", "mMqttConnected": "Подключение к MQTT",
"emergency": "Аварийный режим", "mEmergencyState": "Аварийный режим",
"heating": "Отопление", "mExtPumpState": "Внешний насос",
"dhw": "ГВС", "mCascadeControlInput": "Каскадное управление (вход)",
"flame": "Пламя", "mCascadeControlOutput": "Каскадное управление (выход)",
"fault": "Ошибка",
"diag": "Диагностика", "sConnected": "Подключение к OpenTherm",
"extpump": "Внешний насос", "sFlame": "Пламя",
"modulation": "Уровень модуляции", "sFaultActive": "Ошибка",
"pressure": "Давление", "sFaultCode": "Код ошибки",
"dhwFlowRate": "Расход ГВС", "sDiagActive": "Диагностика",
"faultCode": "Код ошибки", "sDiagCode": "Диагностический код",
"indoorTemp": "Внутренняя темп.",
"outdoorTemp": "Наружная темп.", "mHeatEnabled": "Отопление",
"heatingTemp": "Темп. отопления", "mHeatBlocking": "Блокировка отопления",
"heatingSetpointTemp": "Уставка темп. отопления", "sHeatActive": "Активность отопления",
"heatingReturnTemp": "Темп. обратки отопления", "mHeatTargetTemp": "Отопление, целевая температура",
"dhwTemp": "Темп. ГВС", "mHeatCurrTemp": "Отопление, текущая температура",
"exhaustTemp": "Темп. выхлопных газов" "mHeatRetTemp": "Отопление, температура обратки",
"mHeatIndoorTemp": "Отопление, внутренняя темп.",
"mHeatOutdoorTemp": "Отопление, наружная темп.",
"mDhwEnabled": "ГВС",
"sDhwActive": "Активность ГВС",
"mDhwTargetTemp": "ГВС, целевая температура",
"mDhwCurrTemp": "ГВС, текущая температура",
"mDhwRetTemp": "ГВС, температура обратки"
} }
}, },
@@ -140,6 +158,76 @@
} }
}, },
"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": "Уровень модуляции (в процентах)",
"currentPower": "Текущая мощность (в кВт)",
"pressure": "Давление",
"humidity": "Влажность",
"temperature": "Температура",
"notConfigured": "Не сконфигурировано"
},
"type": "Тип/источник",
"types": {
"otOutdoorTemp": "OpenTherm, внешняя температура",
"otHeatTemp": "OpenTherm, отопление, температура",
"otHeatRetTemp": "OpenTherm, отопление, температура обратки",
"otDhwTemp": "OpenTherm, ГВС, температура",
"otDhwTemp2": "OpenTherm, ГВС, температура 2",
"otDhwFlowRate": "OpenTherm, ГВС, расход/скорость потока",
"otCh2Temp": "OpenTherm, канал 2, температура",
"otExhaustTemp": "OpenTherm, температура выхлопных газов",
"otHeatExchangerTemp": "OpenTherm, температура теплообменника",
"otPressure": "OpenTherm, давление",
"otModLevel": "OpenTherm, уровень модуляции",
"otCurrentPower": "OpenTherm, текущая мощность",
"ntcTemp": "NTC датчик",
"dallasTemp": "DALLAS датчик",
"bluetooth": "BLE датчик",
"heatSetpointTemp": "Отопление, температура уставки",
"manual": "Вручную через MQTT/API",
"notConfigured": "Не сконфигурировано"
},
"gpio": "GPIO датчика",
"address": {
"title": "Адрес датчика",
"note": "Для DALLAS датчиков оставьте по умолчанию для автоопределения, для BLE устройств необходимо указать MAC адрес"
},
"correction": {
"desc": "Коррекция показаний",
"offset": "Компенсация (смещение)",
"factor": "Множитель"
},
"filtering": {
"desc": "Фильтрация показаний",
"enabled": {
"title": "Включить фильтрацию",
"note": "Может быть полезно, если на графиках много резкого шума. В качестве фильтра используется \"бегущее среднее\"."
},
"factor": {
"title": "Коэфф. фильтрации",
"note": "Чем меньше коэф., тем плавнее и <u>дольше</u> изменение числовых значений."
}
}
},
"settings": { "settings": {
"title": "Настройки - OpenTherm Gateway", "title": "Настройки - OpenTherm Gateway",
"name": "Настройки", "name": "Настройки",
@@ -151,22 +239,19 @@
"heating": "Настройки отопления", "heating": "Настройки отопления",
"dhw": "Настройки ГВС", "dhw": "Настройки ГВС",
"emergency": "Настройки аварийного режима", "emergency": "Настройки аварийного режима",
"emergency.events": "События",
"emergency.regulators": "Используемые регуляторы",
"equitherm": "Настройки ПЗА", "equitherm": "Настройки ПЗА",
"pid": "Настройки ПИД", "pid": "Настройки ПИД",
"ot": "Настройки OpenTherm", "ot": "Настройки OpenTherm",
"ot.options": "Опции",
"mqtt": "Настройки MQTT", "mqtt": "Настройки MQTT",
"outdorSensor": "Настройки наружного датчика температуры", "extPump": "Настройки дополнительного насоса",
"indoorSensor": "Настройки внутреннего датчика температуры", "cascadeControl": "Настройки каскадного управления"
"extPump": "Настройки дополнительного насоса"
}, },
"enable": "Вкл", "enable": "Вкл",
"note": { "note": {
"restart": "После изменения этих настроек устройство необходимо перезагрузить, чтобы изменения вступили в силу.", "restart": "После изменения этих настроек устройство необходимо перезагрузить, чтобы изменения вступили в силу.",
"blankNotUse": "пусто - не использовать" "blankNotUse": "пусто - не использовать",
"bleDevice": "BLE устройство можно использовать <u>только</u> с некоторыми платами ESP32, которые поддерживают BLE!"
}, },
"temp": { "temp": {
@@ -185,13 +270,10 @@
"metric": "Метрическая <small>(цильсии, литры, бары)</small>", "metric": "Метрическая <small>(цильсии, литры, бары)</small>",
"imperial": "Imperial <small>(фаренгейты, галлоны, psi)</small>", "imperial": "Imperial <small>(фаренгейты, галлоны, psi)</small>",
"statusLedGpio": "Статус LED GPIO", "statusLedGpio": "Статус LED GPIO",
"debug": "Отладка", "logLevel": "Уровень логирования",
"serial": { "serial": {
"enable": "Вкл. Serial порт", "enable": "Вкл. Serial порт",
"baud": { "baud": "Скорость Serial порта"
"title": "Скорость Serial порта",
"note": "Доступно: 9600, 19200, 38400, 57600, 74880, 115200"
}
}, },
"telnet": { "telnet": {
"enable": "Вкл. Telnet", "enable": "Вкл. Telnet",
@@ -203,28 +285,18 @@
}, },
"heating": { "heating": {
"hyst": "Гистерезис", "hyst": "Гистерезис <small>(в градусах)</small>",
"maxMod": "Макс. уровень модуляции" "turboFactor": "Коэфф. турбо режима"
}, },
"emergency": { "emergency": {
"desc": "<b>!</b> Аварийный режим может быть полезен <u>только</u> при использовании ПЗА и/или ПИД и при передачи наружной/внутренней температуры через MQTT или API. В этом режиме значения датчиков, передаваемые через MQTT/API, не используются.", "desc": "Аварийный режим активируется автоматически, если «ПИД» или «ПЗА» не могут рассчитать уставку теплоносителя:<br />- если «ПЗА» включен и датчик наружной температуры отключен;<br />- если включен «ПИД» или OT опция <i>«Передать управление отоплением котлу»</i> и датчик внутренней температуры отключен.<br /><b>Примечание:</b> При сбое сети или MQTT датчики с типом <i>«Вручную через MQTT/API»</i> будут находиться в состоянии ОТКЛЮЧЕН.",
"target": { "target": {
"title": "Целевая температура", "title": "Целевая температура",
"note": "Целевая <u>внутренняя температура</u> если ПЗА и/или ПИД <b>включены</b><br />Целевая <u>температура теплоносителя</u> если ПЗА и ПИД <b>выключены</b>" "note": "<b>Важно:</b> <u>Целевая температура в помещении</u>, если включена ОТ опция <i>«Передать управление отоплением котлу»</i>.<br />Во всех остальных случаях <u>целевая температура теплоносителя</u>."
}, },
"treshold": "Пороговое время включения <small>(сек)</small>", "treshold": "Пороговое время включения <small>(сек)</small>"
"events": {
"network": "При отключении сети",
"mqtt": "При отключении MQTT"
},
"regulators": {
"equitherm": "ПЗА <small>(требуется внешний или подключенный к котлу датчик <u>наружной</u> температуры)</small>",
"pid": "ПИД <small>(требуется внешний/BLE датчик <u>внутренней</u> температуры)</small>"
}
}, },
"equitherm": { "equitherm": {
@@ -240,16 +312,29 @@
"p": "Коэффициент P", "p": "Коэффициент P",
"i": "Коэффициент I", "i": "Коэффициент I",
"d": "Коэффициент D", "d": "Коэффициент D",
"dt": "DT <small>в секундах</small>" "dt": "DT <small>(сек)</small>",
"noteMinMaxTemp": "<b>Важно:</b> При использовании «ПЗА» и «ПИД» одновременно, мин. и макс. температура ограничивает влияние на расчётную температуру «ПЗА».<br />Таким образом, если мин. температура задана как -15, а макс. как 15, то конечная температура теплоносителя будет от <code>equitherm_result - 15</code> до <code>equitherm_result + 15</code>."
}, },
"ot": { "ot": {
"advanced": "Дополнительные настройки",
"inGpio": "Вход GPIO", "inGpio": "Вход GPIO",
"outGpio": "Выход GPIO", "outGpio": "Выход GPIO",
"ledGpio": "RX LED GPIO", "ledGpio": "RX LED GPIO",
"memberIdCode": "Master MemberID код", "memberId": "Master member ID",
"flags": "Master flags",
"maxMod": "Макс. уровень модуляции",
"minPower": {
"title": "Мин. мощность котла <small>(кВт)</small>",
"note": "Это значение соответствует уровню модуляции котла 01%. Обычно можно найти в спецификации котла как \"минимальная полезная тепловая мощность\"."
},
"maxPower": {
"title": "Макс. мощность котла <small>(кВт)</small>",
"note": "<b>0</b> - попробовать определить автоматически. Обычно можно найти в спецификации котла как \"максимальная полезная тепловая мощность\"."
},
"options": { "options": {
"desc": "Опции",
"dhwPresent": "Контур ГВС", "dhwPresent": "Контур ГВС",
"summerWinterMode": "Летний/зимний режим", "summerWinterMode": "Летний/зимний режим",
"heatingCh2Enabled": "Канал 2 отопления всегда вкл.", "heatingCh2Enabled": "Канал 2 отопления всегда вкл.",
@@ -257,18 +342,13 @@
"dhwToCh2": "Дублировать параметры ГВС в канал 2", "dhwToCh2": "Дублировать параметры ГВС в канал 2",
"dhwBlocking": "DHW blocking", "dhwBlocking": "DHW blocking",
"modulationSyncWithHeating": "Синхронизировать модуляцию с отоплением", "modulationSyncWithHeating": "Синхронизировать модуляцию с отоплением",
"getMinMaxTemp": "Получать мин. и макс. температуру от котла" "getMinMaxTemp": "Получать мин. и макс. температуру от котла",
}, "immergasFix": "Фикс для котлов Immergas"
"faultState": {
"gpio": "Fault state GPIO",
"note": "Can be useful to switch on another boiler <u>via relay</u>. Blank - not use.",
"invert": "Invert fault state"
}, },
"nativeHeating": { "nativeHeating": {
"title": "Передать управление отоплением котлу", "title": "Передать управление отоплением котлу",
"note": "Работает <u>ТОЛЬКО</u> если котел требует и принимает целевую температуру в помещении и сам регулирует температуру теплоносителя на основе встроенного режима кривых. Несовместимо с ПИД и ПЗА, а также с гистерезисом встроенного ПО." "note": "Работает <u>ТОЛЬКО</u> если котел требует и принимает целевую температуру в помещении и сам регулирует температуру теплоносителя на основе встроенного режима кривых. Несовместимо с ПИД и ПЗА."
} }
}, },
@@ -279,23 +359,7 @@
"user": "Имя пользователя", "user": "Имя пользователя",
"password": "Пароль", "password": "Пароль",
"prefix": "Префикс", "prefix": "Префикс",
"interval": "Интервал публикации (в секундах)" "interval": "Интервал публикации <small>(сек)</small>"
},
"tempSensor": {
"source": {
"type": "Источник данных",
"boiler": "От котла через OpenTherm",
"manual": "Вручную через MQTT/API",
"ext": "Внешний датчик (DS18B20)",
"ble": "BLE устройство <i>(ТОЛЬКО для некоторых плат ESP32 с поддержкой BLE)</i>"
},
"gpio": "GPIO",
"offset": "Смещение температуры <small>(калибровка)</small>",
"bleAddress": {
"title": "BLE адрес",
"note": "ТОЛЬКО для некоторых плат ESP32 с поддержкой BLE"
}
}, },
"extPump": { "extPump": {
@@ -304,6 +368,29 @@
"postCirculationTime": "Время постциркуляции <small>(в минутах)</small>", "postCirculationTime": "Время постциркуляции <small>(в минутах)</small>",
"antiStuckInterval": "Интервал защиты от блокировки <small>(в днях)</small>", "antiStuckInterval": "Интервал защиты от блокировки <small>(в днях)</small>",
"antiStuckTime": "Время работы насоса <small>(в минутах)</small>" "antiStuckTime": "Время работы насоса <small>(в минутах)</small>"
},
"cascadeControl": {
"input": {
"desc": "Может использоваться для включения отопления только при неисправности другого котла. Контроллер другого котла должен изменить состояние входа GPIO в случае неисправности.",
"enable": "Включить вход",
"gpio": "GPIO",
"invertState": "Инвертировать состояние GPIO",
"thresholdTime": "Пороговое время изменения состояния <small>(сек)</small>"
},
"output": {
"desc": "Может использоваться для включения другого котла <u>через реле</u>.",
"enable": "Включить выход",
"gpio": "GPIO",
"invertState": "Инвертировать состояние GPIO",
"thresholdTime": "Пороговое время изменения состояния <small>(сек)</small>",
"events": {
"desc": "События",
"onFault": "Если состояние fault (ошибки) активно",
"onLossConnection": "Если соединение по OpenTherm потеряно",
"onEnabledHeating": "Если отопление включено"
}
}
} }
}, },

View File

@@ -42,31 +42,31 @@
<div class="thermostat" id="thermostat-heating"> <div class="thermostat" id="thermostat-heating">
<div class="thermostat-header" data-i18n>dashboard.thermostat.heating</div> <div class="thermostat-header" data-i18n>dashboard.thermostat.heating</div>
<div class="thermostat-temp"> <div class="thermostat-temp">
<div class="thermostat-temp-target"><span id="thermostat-heating-target"></span> <span class="temp-unit"></span></div> <div class="thermostat-temp-target"><span id="tHeatTargetTemp"></span> <span class="tempUnit"></span></div>
<div class="thermostat-temp-current"><span data-i18n>dashboard.thermostat.temp.current</span>: <span id="thermostat-heating-current"></span> <span class="temp-unit"></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>
<div class="thermostat-minus"><button id="thermostat-heating-minus" class="outline"><i class="icons-down"></i></button></div> <div class="thermostat-minus"><button id="tHeatActionMinus" class="outline"><i class="icons-down"></i></button></div>
<div class="thermostat-plus"><button id="thermostat-heating-plus" class="outline"><i class="icons-up"></i></button></div> <div class="thermostat-plus"><button id="tHeatActionPlus" class="outline"><i class="icons-up"></i></button></div>
<div class="thermostat-control"> <div class="thermostat-control">
<input type="checkbox" role="switch" id="thermostat-heating-enabled" value="true"> <input type="checkbox" role="switch" id="tHeatEnabled" value="true">
<label htmlFor="thermostat-heating-enabled" data-i18n>dashboard.thermostat.enable</label> <label htmlFor="tHeatEnabled" data-i18n>dashboard.thermostat.enable</label>
<input type="checkbox" role="switch" id="thermostat-heating-turbo" value="true"> <input type="checkbox" role="switch" id="tHeatTurbo" value="true">
<label htmlFor="thermostat-heating-turbo" data-i18n>dashboard.thermostat.turbo</label> <label htmlFor="tHeatTurbo" data-i18n>dashboard.thermostat.turbo</label>
</div> </div>
</div> </div>
<div class="thermostat" id="thermostat-dhw"> <div class="thermostat" id="thermostat-dhw">
<div class="thermostat-header" data-i18n>dashboard.thermostat.dhw</div> <div class="thermostat-header" data-i18n>dashboard.thermostat.dhw</div>
<div class="thermostat-temp"> <div class="thermostat-temp">
<div class="thermostat-temp-target"><span id="thermostat-dhw-target"></span> <span class="temp-unit"></span></div> <div class="thermostat-temp-target"><span id="tDhwTargetTemp"></span> <span class="tempUnit"></span></div>
<div class="thermostat-temp-current"><span data-i18n>dashboard.thermostat.temp.current</span>: <span id="thermostat-dhw-current"></span> <span class="temp-unit"></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>
<div class="thermostat-minus"><button class="outline" id="thermostat-dhw-minus"><i class="icons-down"></i></button></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="thermostat-dhw-plus"><i class="icons-up"></i></button></div> <div class="thermostat-plus"><button class="outline" id="tDhwActionPlus"><i class="icons-up"></i></button></div>
<div class="thermostat-control"> <div class="thermostat-control">
<input type="checkbox" role="switch" id="thermostat-dhw-enabled" value="true"> <input type="checkbox" role="switch" id="tDhwEnabled" value="true">
<label htmlFor="thermostat-dhw-enabled" data-i18n>dashboard.thermostat.enable</label> <label htmlFor="tDhwEnabled" data-i18n>dashboard.thermostat.enable</label>
</div> </div>
</div> </div>
</div> </div>
@@ -79,84 +79,112 @@
<table> <table>
<tbody> <tbody>
<tr> <tr>
<th scope="row" data-i18n>dashboard.state.ot</th> <th scope="row" data-i18n>dashboard.states.mNetworkConnected</th>
<td><input type="radio" id="ot-connected" aria-invalid="false" checked disabled /></td> <td><input type="radio" class="mNetworkConnected" aria-invalid="false" checked disabled /></td>
</tr> </tr>
<tr> <tr>
<th scope="row" data-i18n>dashboard.state.mqtt</th> <th scope="row" data-i18n>dashboard.states.mMqttConnected</th>
<td><input type="radio" id="mqtt-connected" aria-invalid="false" checked disabled /></td> <td><input type="radio" class="mMqttConnected" aria-invalid="false" checked disabled /></td>
</tr> </tr>
<tr> <tr>
<th scope="row" data-i18n>dashboard.state.emergency</th> <th scope="row" data-i18n>dashboard.states.mEmergencyState</th>
<td><input type="radio" id="ot-emergency" aria-invalid="false" checked disabled /></td> <td><input type="radio" class="mEmergencyState" aria-invalid="false" checked disabled /></td>
</tr> </tr>
<tr> <tr>
<th scope="row" data-i18n>dashboard.state.heating</th> <th scope="row" data-i18n>dashboard.states.mExtPumpState</th>
<td><input type="radio" id="ot-heating" aria-invalid="false" checked disabled /></td> <td><input type="radio" class="mExtPumpState" aria-invalid="false" checked disabled /></td>
</tr> </tr>
<tr> <tr>
<th scope="row" data-i18n>dashboard.state.dhw</th> <th scope="row" data-i18n>dashboard.states.mCascadeControlInput</th>
<td><input type="radio" id="ot-dhw" aria-invalid="false" checked disabled /></td> <td><input type="radio" id="mCascadeControlInput" aria-invalid="false" checked disabled /></td>
</tr> </tr>
<tr> <tr>
<th scope="row" data-i18n>dashboard.state.flame</th> <th scope="row" data-i18n>dashboard.states.mCascadeControlOutput</th>
<td><input type="radio" id="ot-flame" aria-invalid="false" checked disabled /></td> <td><input type="radio" id="mCascadeControlOutput" aria-invalid="false" checked disabled /></td>
</tr>
<tr>
<th scope="row" data-i18n>dashboard.states.sConnected</th>
<td><input type="radio" class="sConnected" aria-invalid="false" checked disabled /></td>
</tr> </tr>
<tr> <tr>
<th scope="row" data-i18n>dashboard.state.fault</th> <th scope="row" data-i18n>dashboard.states.sFlame</th>
<td><input type="radio" id="ot-fault" aria-invalid="false" checked disabled /></td> <td><input type="radio" class="sFlame" aria-invalid="false" checked disabled /></td>
</tr>
<tr>
<th scope="row" data-i18n>dashboard.states.sFaultActive</th>
<td><input type="radio" class="sFaultActive" aria-invalid="false" checked disabled /></td>
</tr> </tr>
<tr> <tr>
<th scope="row" data-i18n>dashboard.state.diag</th> <th scope="row" data-i18n>dashboard.states.sFaultCode</th>
<td><input type="radio" id="ot-diagnostic" aria-invalid="false" checked disabled /></td> <td><b class="sFaultCode"></b></td>
</tr> </tr>
<tr> <tr>
<th scope="row" data-i18n>dashboard.state.extpump</th> <th scope="row" data-i18n>dashboard.states.sDiagActive</th>
<td><input type="radio" id="ot-external-pump" aria-invalid="false" checked disabled /></td> <td><input type="radio" class="sDiagActive" aria-invalid="false" checked disabled /></td>
</tr> </tr>
<tr> <tr>
<th scope="row" data-i18n>dashboard.state.modulation</th> <th scope="row" data-i18n>dashboard.states.sDiagCode</th>
<td><b id="ot-modulation"></b> %</td> <td><b class="sDiagCode"></b></td>
</tr>
<tr>
<th scope="row" data-i18n>dashboard.states.mHeatEnabled</th>
<td><input type="radio" class="mHeatEnabled" aria-invalid="false" checked disabled /></td>
</tr> </tr>
<tr> <tr>
<th scope="row" data-i18n>dashboard.state.pressure</th> <th scope="row" data-i18n>dashboard.states.mHeatBlocking</th>
<td><b id="ot-pressure"></b> <span class="pressure-unit"></span></td> <td><input type="radio" class="mHeatBlocking" aria-invalid="false" checked disabled /></td>
</tr> </tr>
<tr> <tr>
<th scope="row" data-i18n>dashboard.state.dhwFlowRate</th> <th scope="row" data-i18n>dashboard.states.sHeatActive</th>
<td><b id="ot-dhw-flow-rate"></b> <span class="volume-unit"></span>/min</td> <td><input type="radio" class="sHeatActive" aria-invalid="false" checked disabled /></td>
</tr> </tr>
<tr> <tr>
<th scope="row" data-i18n>dashboard.state.faultCode</th> <th scope="row" data-i18n>dashboard.states.mHeatTargetTemp</th>
<td><b id="ot-fault-code"></b></td> <td><b class="mHeatTargetTemp"></b> <span class="tempUnit"></span></td>
</tr> </tr>
<tr> <tr>
<th scope="row" data-i18n>dashboard.state.indoorTemp</th> <th scope="row" data-i18n>dashboard.states.mHeatCurrTemp</th>
<td><b id="indoor-temp"></b> <span class="temp-unit"></span></td> <td><b class="mHeatCurrTemp"></b> <span class="tempUnit"></span></td>
</tr> </tr>
<tr> <tr>
<th scope="row" data-i18n>dashboard.state.outdoorTemp</th> <th scope="row" data-i18n>dashboard.states.mHeatRetTemp</th>
<td><b id="outdoor-temp"></b> <span class="temp-unit"></span></td> <td><b class="mHeatRetTemp"></b> <span class="tempUnit"></span></td>
</tr> </tr>
<tr> <tr>
<th scope="row" data-i18n>dashboard.state.heatingTemp</th> <th scope="row" data-i18n>dashboard.states.mHeatIndoorTemp</th>
<td><b id="heating-temp"></b> <span class="temp-unit"></span></td> <td><b class="mHeatIndoorTemp"></b> <span class="tempUnit"></span></td>
</tr> </tr>
<tr> <tr>
<th scope="row" data-i18n>dashboard.state.heatingSetpointTemp</th> <th scope="row" data-i18n>dashboard.states.mHeatOutdoorTemp</th>
<td><b id="heating-setpoint-temp"></b> <span class="temp-unit"></span></td> <td><b class="mHeatOutdoorTemp"></b> <span class="tempUnit"></span></td>
</tr>
<tr>
<th scope="row" data-i18n>dashboard.states.mDhwEnabled</th>
<td><input type="radio" class="mDhwEnabled" aria-invalid="false" checked disabled /></td>
</tr> </tr>
<tr> <tr>
<th scope="row" data-i18n>dashboard.state.heatingReturnTemp</th> <th scope="row" data-i18n>dashboard.states.sDhwActive</th>
<td><b id="heating-return-temp"></b> <span class="temp-unit"></span></td> <td><input type="radio" class="sDhwActive" aria-invalid="false" checked disabled /></td>
</tr> </tr>
<tr> <tr>
<th scope="row" data-i18n>dashboard.state.dhwTemp</th> <th scope="row" data-i18n>dashboard.states.mDhwTargetTemp</th>
<td><b id="dhw-temp"></b> <span class="temp-unit"></span></td> <td><b class="mDhwTargetTemp"></b> <span class="tempUnit"></span></td>
</tr> </tr>
<tr> <tr>
<th scope="row" data-i18n>dashboard.state.exhaustTemp</th> <th scope="row" data-i18n>dashboard.states.mDhwCurrTemp</th>
<td><b id="exhaust-temp"></b> <span class="temp-unit"></span></td> <td><b class="mDhwCurrTemp"></b> <span class="tempUnit"></span></td>
</tr>
<tr>
<th scope="row" data-i18n>dashboard.states.mDhwRetTemp</th>
<td><b class="mDhwRetTemp"></b> <span class="tempUnit"></span></td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
@@ -165,15 +193,17 @@
<hr /> <hr />
<details> <details>
<summary><b data-i18n>dashboard.section.otDiag</b></summary> <summary><b data-i18n>dashboard.section.diag</b></summary>
<pre><b>Vendor:</b> <span id="slave-vendor"></span> <pre><b>Vendor:</b> <span class="sVendor"></span>
<b>Member ID:</b> <span id="slave-member-id"></span> <b>Member ID:</b> <span class="sMemberId"></span>
<b>Flags:</b> <span id="slave-flags"></span> <b>Flags:</b> <span class="sFlags"></span>
<b>Type:</b> <span id="slave-type"></span> <b>Type:</b> <span class="sType"></span>
<b>Version:</b> <span id="slave-version"></span> <b>AppVersion:</b> <span class="sAppVersion"></span>
<b>OT version:</b> <span id="slave-ot-version"></span> <b>OT version:</b> <span class="sProtocolVersion"></span>
<b>Heating limits:</b> <span id="heating-min-temp"></span>...<span id="heating-max-temp"></span> <span class="temp-unit"></span> <b>Modulation limits:</b> <span class="sModMin"></span>...<span class="sModMax"></span> %
<b>DHW limits:</b> <span id="dhw-min-temp"></span>...<span id="dhw-max-temp"></span> <span class="temp-unit"></span></pre> <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> </details>
</div> </div>
</article> </article>
@@ -211,7 +241,7 @@
const lang = new Lang(document.getElementById('lang')); const lang = new Lang(document.getElementById('lang'));
lang.build(); lang.build();
document.querySelector('#thermostat-heating-minus').addEventListener('click', (event) => { document.querySelector('#tHeatActionMinus').addEventListener('click', (event) => {
if (!prevSettings) { if (!prevSettings) {
return; return;
} }
@@ -230,10 +260,10 @@
newSettings.heating.target = minTemp; newSettings.heating.target = minTemp;
} }
setValue('#thermostat-heating-target', newSettings.heating.target); setValue('#tHeatTargetTemp', newSettings.heating.target);
}); });
document.querySelector('#thermostat-heating-plus').addEventListener('click', (event) => { document.querySelector('#tHeatActionPlus').addEventListener('click', (event) => {
if (!prevSettings) { if (!prevSettings) {
return; return;
} }
@@ -252,10 +282,10 @@
newSettings.heating.target = maxTemp; newSettings.heating.target = maxTemp;
} }
setValue('#thermostat-heating-target', newSettings.heating.target); setValue('#tHeatTargetTemp', newSettings.heating.target);
}); });
document.querySelector('#thermostat-dhw-minus').addEventListener('click', (event) => { document.querySelector('#tDhwActionMinus').addEventListener('click', (event) => {
if (!prevSettings) { if (!prevSettings) {
return; return;
} }
@@ -267,10 +297,10 @@
newSettings.dhw.target = prevSettings.dhw.minTemp; newSettings.dhw.target = prevSettings.dhw.minTemp;
} }
setValue('#thermostat-dhw-target', newSettings.dhw.target); setValue('#tDhwTargetTemp', newSettings.dhw.target);
}); });
document.querySelector('#thermostat-dhw-plus').addEventListener('click', (event) => { document.querySelector('#tDhwActionPlus').addEventListener('click', (event) => {
if (!prevSettings) { if (!prevSettings) {
return; return;
} }
@@ -282,22 +312,22 @@
newSettings.dhw.target = prevSettings.dhw.maxTemp; newSettings.dhw.target = prevSettings.dhw.maxTemp;
} }
setValue('#thermostat-dhw-target', newSettings.dhw.target); setValue('#tDhwTargetTemp', newSettings.dhw.target);
}); });
document.querySelector('#thermostat-heating-enabled').addEventListener('change', (event) => { document.querySelector('#tHeatEnabled').addEventListener('change', (event) => {
modifiedTime = Date.now(); modifiedTime = Date.now();
newSettings.heating.enable = event.currentTarget.checked; newSettings.heating.enabled = event.currentTarget.checked;
}); });
document.querySelector('#thermostat-heating-turbo').addEventListener('change', (event) => { document.querySelector('#tHeatTurbo').addEventListener('change', (event) => {
modifiedTime = Date.now(); modifiedTime = Date.now();
newSettings.heating.turbo = event.currentTarget.checked; newSettings.heating.turbo = event.currentTarget.checked;
}); });
document.querySelector('#thermostat-dhw-enabled').addEventListener('change', (event) => { document.querySelector('#tDhwEnabled').addEventListener('change', (event) => {
modifiedTime = Date.now(); modifiedTime = Date.now();
newSettings.dhw.enable = event.currentTarget.checked; newSettings.dhw.enabled = event.currentTarget.checked;
}); });
setTimeout(async function onLoadPage() { setTimeout(async function onLoadPage() {
@@ -313,10 +343,10 @@
// settings // settings
try { try {
let modified = prevSettings && ( let modified = prevSettings && (
(prevSettings.heating.enable != newSettings.heating.enable) (prevSettings.heating.enabled != newSettings.heating.enabled)
|| (prevSettings.heating.turbo != newSettings.heating.turbo) || (prevSettings.heating.turbo != newSettings.heating.turbo)
|| (prevSettings.heating.target != newSettings.heating.target) || (prevSettings.heating.target != newSettings.heating.target)
|| (prevSettings.opentherm.dhwPresent && prevSettings.dhw.enable != newSettings.dhw.enable) || (prevSettings.opentherm.dhwPresent && prevSettings.dhw.enabled != newSettings.dhw.enabled)
|| (prevSettings.opentherm.dhwPresent && prevSettings.dhw.target != newSettings.dhw.target) || (prevSettings.opentherm.dhwPresent && prevSettings.dhw.target != newSettings.dhw.target)
); );
@@ -336,12 +366,12 @@
} }
const result = await response.json(); const result = await response.json();
noRegulators = !result.opentherm.nativeHeatingControl && !result.equitherm.enable && !result.pid.enable; noRegulators = !result.opentherm.nativeHeatingControl && !result.equitherm.enabled && !result.pid.enabled;
prevSettings = result; prevSettings = result;
newSettings.heating.enable = result.heating.enable; newSettings.heating.enabled = result.heating.enabled;
newSettings.heating.turbo = result.heating.turbo; newSettings.heating.turbo = result.heating.turbo;
newSettings.heating.target = result.heating.target; newSettings.heating.target = result.heating.target;
newSettings.dhw.enable = result.dhw.enable; newSettings.dhw.enabled = result.dhw.enabled;
newSettings.dhw.target = result.dhw.target; newSettings.dhw.target = result.dhw.target;
if (result.opentherm.dhwPresent) { if (result.opentherm.dhwPresent) {
@@ -350,16 +380,16 @@
hide('#thermostat-dhw'); hide('#thermostat-dhw');
} }
setCheckboxValue('#thermostat-heating-enabled', result.heating.enable); setCheckboxValue('#tHeatEnabled', result.heating.enabled);
setCheckboxValue('#thermostat-heating-turbo', result.heating.turbo); setCheckboxValue('#tHeatTurbo', result.heating.turbo);
setValue('#thermostat-heating-target', result.heating.target); setValue('#tHeatTargetTemp', result.heating.target);
setCheckboxValue('#thermostat-dhw-enabled', result.dhw.enable); setCheckboxValue('#tDhwEnabled', result.dhw.enabled);
setValue('#thermostat-dhw-target', result.dhw.target); setValue('#tDhwTargetTemp', result.dhw.target);
setValue('.temp-unit', temperatureUnit(result.system.unitSystem)); setValue('.tempUnit', temperatureUnit(result.system.unitSystem));
setValue('.pressure-unit', pressureUnit(result.system.unitSystem)); setValue('.pressureUnit', pressureUnit(result.system.unitSystem));
setValue('.volume-unit', volumeUnit(result.system.unitSystem)); setValue('.volumeUnit', volumeUnit(result.system.unitSystem));
} catch (error) { } catch (error) {
console.log(error); console.log(error);
@@ -373,44 +403,83 @@
} }
const result = await response.json(); const result = await response.json();
setValue('#thermostat-heating-current', noRegulators ? result.temperatures.heating : result.temperatures.indoor);
setValue('#thermostat-dhw-current', result.temperatures.dhw); // Graph
setValue('#tHeatCurrentTemp', result.master.indoorTempControl
? result.master.heating.indoorTemp
: result.master.heating.currentTemp
);
setValue('#tDhwCurrentTemp', result.master.dhw.currentTemp);
setState('#ot-connected', result.states.otStatus); // SLAVE
setState('#ot-emergency', result.states.emergency); setValue('.sMemberId', result.slave.memberId);
setState('#ot-heating', result.states.heating); setValue('.sVendor', memberIdToVendor(result.slave.memberId));
setState('#ot-dhw', result.states.dhw); setValue('.sFlags', result.slave.flags);
setState('#ot-flame', result.states.flame); setValue('.sType', result.slave.type);
setState('#ot-fault', result.states.fault); setValue('.sAppVersion', result.slave.appVersion);
setState('#ot-diagnostic', result.states.diagnostic); setValue('.sProtocolVersion', result.slave.protocolVersion);
setState('#ot-external-pump', result.states.externalPump);
setState('#mqtt-connected', result.states.mqtt);
setValue('#ot-modulation', result.sensors.modulation); setState('.sConnected', result.slave.connected);
setValue('#ot-pressure', result.sensors.pressure); setState('.sFlame', result.slave.flame);
setValue('#ot-dhw-flow-rate', result.sensors.dhwFlowRate);
setValue('#ot-fault-code', result.sensors.faultCode ? ("E" + result.sensors.faultCode) : "-");
setValue('#indoor-temp', result.temperatures.indoor); setValue('.sModMin', result.slave.modulation.min);
setValue('#outdoor-temp', result.temperatures.outdoor); setValue('.sModMax', result.slave.modulation.max);
setValue('#heating-temp', result.temperatures.heating);
setValue('#heating-return-temp', result.temperatures.heatingReturn);
setValue('#dhw-temp', result.temperatures.dhw);
setValue('#exhaust-temp', result.temperatures.exhaust);
setValue('#heating-min-temp', result.parameters.heatingMinTemp); setValue('.sPowerMin', result.slave.power.min);
setValue('#heating-max-temp', result.parameters.heatingMaxTemp); setValue('.sPowerMax', result.slave.power.max);
setValue('#heating-setpoint-temp', result.parameters.heatingSetpoint);
setValue('#dhw-min-temp', result.parameters.dhwMinTemp);
setValue('#dhw-max-temp', result.parameters.dhwMaxTemp);
setValue('#slave-member-id', result.parameters.slaveMemberId); setState('.sHeatActive', result.slave.heating.active);
setValue('#slave-vendor', memberIdToVendor(result.parameters.slaveMemberId)); setValue('.sHeatMinTemp', result.slave.heating.minTemp);
setValue('.sHeatMaxTemp', result.slave.heating.maxTemp);
setState('.sDhwActive', result.slave.dhw.active);
setValue('.sDhwMinTemp', result.slave.dhw.minTemp);
setValue('.sDhwMaxTemp', result.slave.dhw.maxTemp);
setState('.sFaultActive', result.slave.fault.active);
setValue(
'.sFaultCode',
result.slave.fault.active
? (result.slave.fault.code + " (0x" + dec2hex(result.slave.fault.code) + ")")
: "-"
);
setState('.sDiagActive', result.slave.diag.active);
setValue(
'.sDiagCode',
result.slave.diag.active
? (result.slave.diag.code + " (0x" + dec2hex(result.slave.diag.code) + ")")
: "-"
);
// MASTER
setState('.mHeatEnabled', result.master.heating.enabled);
setState('.mHeatBlocking', result.master.heating.blocking);
setState('.mHeatIndoorTempControl', result.master.heating.indoorTempControl);
setValue('.mHeatTargetTemp', result.master.heating.targetTemp);
setValue('.mHeatCurrTemp', result.master.heating.currentTemp);
setValue('.mHeatRetTemp', result.master.heating.returnTemp);
setValue('.mHeatIndoorTemp', result.master.heating.indoorTemp);
setValue('.mHeatOutdoorTemp', result.master.heating.outdoorTemp);
setValue('.mHeatMinTemp', result.master.heating.minTemp);
setValue('.mHeatMaxTemp', result.master.heating.maxTemp);
setState('.mDhwEnabled', result.master.dhw.enabled);
setValue('.mDhwTargetTemp', result.master.dhw.targetTemp);
setValue('.mDhwCurrTemp', result.master.dhw.currentTemp);
setValue('.mDhwRetTemp', result.master.dhw.returnTemp);
setValue('.mDhwMinTemp', result.master.dhw.minTemp);
setValue('.mDhwMaxTemp', result.master.dhw.maxTemp);
setState('.mNetworkConnected', result.master.network.connected);
setState('.mMqttConnected', result.master.mqtt.connected);
setState('.mEmergencyState', result.master.emergency.state);
setState('.mExtPumpState', result.master.externalPump.state);
setState('.mCascadeControlInput', result.master.cascadeControl.input);
setState('.mCascadeControlOutput', result.master.cascadeControl.output);
setValue('#slave-flags', result.parameters.slaveFlags);
setValue('#slave-type', result.parameters.slaveType);
setValue('#slave-version', result.parameters.slaveVersion);
setValue('#slave-ot-version', result.parameters.slaveOtVersion);
setBusy('#dashboard-busy', '#dashboard-container', false); setBusy('#dashboard-busy', '#dashboard-container', false);
} catch (error) { } catch (error) {

View File

@@ -101,43 +101,48 @@
<td> <td>
Env: <b id="build-env"></b><br /> Env: <b id="build-env"></b><br />
<span data-i18n>index.system.build.date</span>: <b id="build-date"></b><br /> <span data-i18n>index.system.build.date</span>: <b id="build-date"></b><br />
<span data-i18n>index.system.build.sdk</span>: <b id="core-version"></b> <span data-i18n>index.system.build.core</span>: <b id="build-core"></b><br />
<span data-i18n>index.system.build.sdk</span>: <b id="build-sdk"></b>
</td> </td>
</tr> </tr>
<tr> <tr>
<th scope="row" data-i18n>index.system.uptime</th> <th scope="row" data-i18n>index.system.uptime</th>
<td> <td>
<b id="uptime-days"></b> days, <b id="uptime-days"></b> <span data-i18n>time.days</span>,
<b id="uptime-hours"></b> hours, <b id="uptime-hours"></b> <span data-i18n>time.hours</span>,
<b id="uptime-min"></b> min., <b id="uptime-min"></b> <span data-i18n>time.min</span>,
<b id="uptime-sec"></b> sec. <b id="uptime-sec"></b> <span data-i18n>time.sec</span>
</td> </td>
</tr> </tr>
<tr> <tr>
<th scope="row" data-i18n>index.system.memory.title</th> <th scope="row" data-i18n>index.system.memory.title</th>
<td> <td>
<b id="free-heap"></b> of <b id="total-heap"></b> bytes (<span data-i18n>index.system.memory.min</span>: <b id="min-free-heap"></b> bytes)<br /> <b id="heap-free"></b> of <b id="heap-total"></b> bytes (<span data-i18n>index.system.memory.min</span>: <b id="heap-min-free"></b> bytes)<br />
<span data-i18n>index.system.memory.maxFreeBlock</span>: <b id="max-free-block-heap"></b> bytes (<span data-i18n>index.system.memory.min</span>: <b id="min-max-free-block-heap"></b> bytes) <span data-i18n>index.system.memory.maxFreeBlock</span>: <b id="heap-max-free-block"></b> bytes (<span data-i18n>index.system.memory.min</span>: <b id="heap-min-max-free-block"></b> bytes)
</td> </td>
</tr> </tr>
<tr> <tr>
<th scope="row" data-i18n>index.system.board</th> <th scope="row" data-i18n>index.system.board</th>
<td> <td>
<span data-i18n>index.system.chip.model</span>: <b id="chip-model"></b> (rev. <span id="chip-revision"></span>)<br /> <span data-i18n>index.system.chip.model</span>: <b id="chip-model"></b> (rev. <span id="chip-rev"></span>)<br />
<span data-i18n>index.system.chip.cores</span>: <b id="chip-cores"></b>, <span data-i18n>index.system.chip.freq</span>: <b id="cpu-freq"></b> mHz<br /> <span data-i18n>index.system.chip.cores</span>: <b id="chip-cores"></b>, <span data-i18n>index.system.chip.freq</span>: <b id="chip-freq"></b> mHz<br />
<span data-i18n>index.system.flash.size</span>: <b id="flash-size"></b> MB (<span data-i18n>index.system.flash.realSize</span>: <b id="flash-real-size"></b> MB) <span data-i18n>index.system.flash.size</span>: <b id="flash-size"></b> MB (<span data-i18n>index.system.flash.realSize</span>: <b id="flash-real-size"></b> MB)
</td> </td>
</tr> </tr>
<tr> <tr>
<th scope="row" data-i18n>index.system.lastResetReason</th> <th scope="row" data-i18n>index.system.lastResetReason</th>
<td><b id="reset-reason"></b></td> <td>
<b id="reset-reason"></b><br />
<a href="/api/debug" target="_blank"><small>Save debug data</small></a>
</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
<div class="grid"> <div class="grid" style="grid-template-columns: repeat(auto-fit,minmax(12rem,1fr)) !important;">
<a href="/dashboard.html" role="button" data-i18n>dashboard.name</a> <a href="/dashboard.html" role="button" data-i18n>dashboard.name</a>
<a href="/settings.html" role="button" data-i18n>settings.name</a> <a href="/settings.html" role="button" data-i18n>settings.name</a>
<a href="/sensors.html" role="button" data-i18n>sensors.name</a>
<a href="/upgrade.html" role="button" data-i18n>upgrade.name</a> <a href="/upgrade.html" role="button" data-i18n>upgrade.name</a>
<a href="/restart.html" role="button" class="secondary restart" data-i18n>button.restart</a> <a href="/restart.html" role="button" class="secondary restart" data-i18n>button.restart</a>
</div> </div>
@@ -170,6 +175,13 @@
} }
const result = await response.json(); const result = await response.json();
setValue('#reset-reason', result.system.resetReason);
setValue('#uptime', result.system.uptime);
setValue('#uptime-days', Math.floor(result.system.uptime / 86400));
setValue('#uptime-hours', Math.floor(result.system.uptime % 86400 / 3600));
setValue('#uptime-min', Math.floor(result.system.uptime % 3600 / 60));
setValue('#uptime-sec', Math.floor(result.system.uptime % 60));
setValue('#network-hostname', result.network.hostname); setValue('#network-hostname', result.network.hostname);
setValue('#network-mac', result.network.mac); setValue('#network-mac', result.network.mac);
setState('#network-connected', result.network.connected); setState('#network-connected', result.network.connected);
@@ -181,28 +193,24 @@
setValue('#network-dns', result.network.dns); setValue('#network-dns', result.network.dns);
setBusy('#main-busy', '#main-table', false); setBusy('#main-busy', '#main-table', false);
setValue('#build-version', result.system.buildVersion); setValue('#build-version', result.build.version);
setValue('#build-date', result.system.buildDate); setValue('#build-date', result.build.date);
setValue('#build-env', result.system.buildEnv); setValue('#build-env', result.build.env);
setValue('#uptime', result.system.uptime); setValue('#build-core', result.build.core);
setValue('#uptime-days', Math.floor(result.system.uptime / 86400)); setValue('#build-sdk', result.build.sdk);
setValue('#uptime-hours', Math.floor(result.system.uptime % 86400 / 3600));
setValue('#uptime-min', Math.floor(result.system.uptime % 3600 / 60));
setValue('#uptime-sec', Math.floor(result.system.uptime % 60));
setValue('#total-heap', result.system.totalHeap);
setValue('#free-heap', result.system.freeHeap);
setValue('#min-free-heap', result.system.minFreeHeap);
setValue('#max-free-block-heap', result.system.maxFreeBlockHeap);
setValue('#min-max-free-block-heap', result.system.minMaxFreeBlockHeap);
setValue('#reset-reason', result.system.resetReason);
setValue('#chip-model', result.system.chipModel); setValue('#heap-total', result.heap.total);
setValue('#chip-revision', result.system.chipRevision); setValue('#heap-free', result.heap.free);
setValue('#chip-cores', result.system.chipCores); setValue('#heap-min-free', result.heap.minFree);
setValue('#cpu-freq', result.system.cpuFreq); setValue('#heap-max-free-block', result.heap.maxFreeBlock);
setValue('#core-version', result.system.coreVersion); setValue('#heap-min-max-free-block', result.heap.minMaxFreeBlock);
setValue('#flash-size', result.system.flashSize / 1024 / 1024);
setValue('#flash-real-size', result.system.flashRealSize / 1024 / 1024); setValue('#chip-model', result.chip.model);
setValue('#chip-rev', result.chip.rev);
setValue('#chip-cores', result.chip.cores);
setValue('#chip-freq', result.chip.freq);
setValue('#flash-size', result.flash.size / 1024 / 1024);
setValue('#flash-real-size', result.flash.realSize / 1024 / 1024);
setBusy('#system-busy', '#system-table', false); setBusy('#system-busy', '#system-table', false);

283
src_data/pages/sensors.html Normal file
View File

@@ -0,0 +1,283 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title data-i18n>sensors.title</title>
<link rel="stylesheet" href="/static/app.css" />
</head>
<body>
<header class="container">
<nav>
<ul>
<li><a href="/">
<div class="logo" data-i18n>logo</div>
</a></li>
</ul>
<ul>
<!--<li><a href="https://github.com/Laxilef/OTGateway/wiki" role="button" class="secondary" target="_blank">Help</a></li>-->
<li>
<select id="lang" aria-label="Lang">
<option value="en" selected>EN</option>
<option value="ru">RU</option>
</select>
</li>
</ul>
</nav>
</header>
<main class="container">
<article>
<hgroup>
<h2 data-i18n>sensors.name</h2>
<p></p>
</hgroup>
<details id="template" class="sensor hidden" data-id="" data-preloaded="0">
<summary><b>#<span class="id"></span>: <span class="name"></span></b></summary>
<div>
<div class="form-busy" aria-busy="true"></div>
<form action="/api/sensor?id={id}" class="hidden">
<fieldset>
<label>
<input type="checkbox" role="switch" name="enabled" value="true">
<span data-i18n>sensors.enabled</span>
</label>
<br />
<label>
<span data-i18n>sensors.sensorName.title</span>
<input type="text" name="name" maxlength="32" required>
<small data-i18n>sensors.sensorName.note</small>
</label>
<div class="grid">
<label>
<span data-i18n>sensors.purpose</span>
<select name="purpose" required>
<option value="0" data-i18n>sensors.purposes.outdoorTemp</option>
<option value="1" data-i18n>sensors.purposes.indoorTemp</option>
<option value="2" data-i18n>sensors.purposes.heatTemp</option>
<option value="3" data-i18n>sensors.purposes.heatRetTemp</option>
<option value="4" data-i18n>sensors.purposes.dhwTemp</option>
<option value="5" data-i18n>sensors.purposes.dhwRetTemp</option>
<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="9" data-i18n>sensors.purposes.currentPower</option>
<option value="252" data-i18n>sensors.purposes.pressure</option>
<option value="253" data-i18n>sensors.purposes.humidity</option>
<option value="254" data-i18n>sensors.purposes.temperature</option>
<option value="255" data-i18n>sensors.purposes.notConfigured</option>
</select>
</label>
<label>
<span data-i18n>sensors.type</span>
<select name="type" required>
<option value="0" data-i18n>sensors.types.otOutdoorTemp</option>
<option value="1" data-i18n>sensors.types.otHeatTemp</option>
<option value="2" data-i18n>sensors.types.otHeatRetTemp</option>
<option value="3" data-i18n>sensors.types.otDhwTemp</option>
<option value="4" data-i18n>sensors.types.otDhwTemp2</option>
<option value="5" data-i18n>sensors.types.otDhwFlowRate</option>
<option value="6" data-i18n>sensors.types.otCh2Temp</option>
<option value="7" data-i18n>sensors.types.otExhaustTemp</option>
<option value="8" data-i18n>sensors.types.otHeatExchangerTemp</option>
<option value="9" data-i18n>sensors.types.otPressure</option>
<option value="10" data-i18n>sensors.types.otModLevel</option>
<option value="11" data-i18n>sensors.types.otCurrentPower</option>
<option value="50" data-i18n>sensors.types.ntcTemp</option>
<option value="51" data-i18n>sensors.types.dallasTemp</option>
<option value="52" data-i18n>sensors.types.bluetooth</option>
<option value="253" data-i18n>sensors.types.heatSetpointTemp</option>
<option value="254" data-i18n>sensors.types.manual</option>
<option value="255" data-i18n>sensors.types.notConfigured</option>
</select>
</label>
</div>
</fieldset>
<div class="grid">
<label>
<span data-i18n>sensors.gpio</span>
<input type="number" outputmode="numeric" name="gpio" min="0" max="254" step="1">
</label>
<label>
<span data-i18n>sensors.address.title</span>
<input type="text" name="address">
<small data-i18n>sensors.address.note</small>
</label>
</div>
<hr />
<fieldset>
<legend><b data-i18n>sensors.correction.desc</b></legend>
<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>
</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>
</label>
</div>
</fieldset>
<hr />
<fieldset>
<legend><b data-i18n>sensors.filtering.desc</b></legend>
<label>
<input type="checkbox" name="filtering" value="true">
<span data-i18n>sensors.filtering.enabled.title</span>
<br />
<small data-i18n>sensors.filtering.enabled.note</small>
</label>
<label>
<span data-i18n>sensors.filtering.factor.title</span>
<input type="number" inputmode="numeric" name="filteringFactor" min="0.01" max="1" step="0.01">
<small data-i18n>sensors.filtering.factor.note</small>
</label>
</fieldset>
<button type="submit" data-i18n>button.save</button>
</form>
</div>
</details>
</article>
</main>
<footer class="container">
<small>
<b>Made by Laxilef</b>
<a href="https://github.com/Laxilef/OTGateway/blob/master/LICENSE" target="_blank" class="secondary" data-i18n>nav.license</a>
<a href="https://github.com/Laxilef/OTGateway/blob/master/" target="_blank" class="secondary" data-i18n>nav.source</a>
<a href="https://github.com/Laxilef/OTGateway/wiki" target="_blank" class="secondary" data-i18n>nav.help</a>
<a href="https://github.com/Laxilef/OTGateway/issues" target="_blank" class="secondary" data-i18n>nav.issues</a>
<a href="https://github.com/Laxilef/OTGateway/releases" target="_blank" class="secondary" data-i18n>nav.releases</a>
</small>
</footer>
<script src="/static/app.js"></script>
<script>
document.addEventListener("DOMContentLoaded", async () => {
const lang = new Lang(document.getElementById("lang"));
lang.build();
const container = document.querySelector("article");
const templateNode = container.querySelector("#template");
try {
const response = await fetch("/api/sensors", { cache: "no-cache" });
if (!response.ok) {
throw new Error("Response not valid");
}
const result = await response.json();
for (const sensorId in result) {
const sensorNode = templateNode.cloneNode(true);
sensorNode.removeAttribute("id");
sensorNode.classList.remove("hidden");
sensorNode.dataset.id = sensorId;
setValue(".id", sensorId, sensorNode);
setValue(".name", result[sensorId], sensorNode);
container.appendChild(sensorNode);
container.appendChild(document.createElement("hr"));
const sensorForm = sensorNode.querySelector("form");
const fillData = (data) => {
setCheckboxValue("[name='enabled']", data.enabled, sensorForm);
setInputValue("[name='name']", data.name, {}, sensorForm);
setSelectValue("[name='purpose']", data.purpose, sensorForm);
setSelectValue("[name='type']", data.type, sensorForm);
setInputValue("[name='gpio']", data.gpio < 255 ? data.gpio : "", {}, sensorForm);
setInputValue("[name='address']", data.address, {}, sensorForm);
setInputValue("[name='offset']", data.offset, {}, sensorForm);
setInputValue("[name='factor']", data.factor, {}, sensorForm);
setCheckboxValue("[name='filtering']", data.filtering, sensorForm);
setInputValue("[name='filteringFactor']", data.filteringFactor, {}, sensorForm);
sensorForm.querySelector("[name='type']").dispatchEvent(new Event("change"));
setBusy(".form-busy", "form", false, sensorNode);
};
sensorForm.action = sensorForm.action.replace("{id}", sensorId);
sensorForm.querySelector("[name='type']").addEventListener("change", async (event) => {
const gpio = sensorForm.querySelector("[name='gpio']");
const address = sensorForm.querySelector("[name='address']");
const parentGpio = gpio.parentElement;
const parentAddress = address.parentElement;
switch(parseInt(event.target.value)) {
// ntc
case 50:
parentGpio.classList.remove("hidden");
parentAddress.classList.add("hidden");
address.removeAttribute("pattern");
break;
// dallas
case 51:
parentGpio.classList.remove("hidden");
parentAddress.classList.remove("hidden");
address.setAttribute("pattern", "([A-Fa-f0-9]{2}:){7}[A-Fa-f0-9]{2}");
break;
// ble
case 52:
parentGpio.classList.add("hidden");
parentAddress.classList.remove("hidden");
address.setAttribute("pattern", "([A-Fa-f0-9]{2}:){5}[A-Fa-f0-9]{2}");
break;
// other
default:
parentGpio.classList.add("hidden");
parentAddress.classList.add("hidden");
address.removeAttribute("pattern");
break;
}
});
sensorNode.addEventListener("click", async (event) => {
if (parseInt(sensorNode.dataset.preloaded)) {
return;
}
try {
const response = await fetch(sensorForm.action, { cache: "no-cache" });
if (response.status != 200) {
return;
}
const result = await response.json();
fillData(result);
sensorNode.dataset.preloaded = 1;
} catch (error) {
console.log(error);
}
});
setupForm(".sensor[data-id='" + sensorId + "'] form", fillData, ['address']);
}
} catch (error) {
console.log(error);
}
});
</script>
</body>
</html>

View File

@@ -94,11 +94,6 @@
<fieldset> <fieldset>
<legend data-i18n>settings.section.diag</legend> <legend data-i18n>settings.section.diag</legend>
<label for="system-debug">
<input type="checkbox" id="system-debug" name="system[debug]" value="true">
<span data-i18n>settings.system.debug</span>
</label>
<label for="system-serial-enable"> <label for="system-serial-enable">
<input type="checkbox" id="system-serial-enable" name="system[serial][enable]" value="true"> <input type="checkbox" id="system-serial-enable" name="system[serial][enable]" value="true">
<span data-i18n>settings.system.serial.enable</span> <span data-i18n>settings.system.serial.enable</span>
@@ -109,11 +104,31 @@
<span data-i18n>settings.system.telnet.enable</span> <span data-i18n>settings.system.telnet.enable</span>
</label> </label>
<label for="system-log-level">
<span data-i18n>settings.system.logLevel</span>
<select id="system-log-level" name="system[logLevel]">
<option value="0">SILENT</option>
<option value="1">FATAL</option>
<option value="2">ERROR</option>
<option value="3">WARNING</option>
<option value="4">INFO</option>
<option value="5">NOTICE</option>
<option value="6">TRACE</option>
<option value="7">VERBOSE</option>
</select>
</label>
<div class="grid"> <div class="grid">
<label for="system-serial-baudrate"> <label for="system-serial-baudrate">
<span data-i18n>settings.system.serial.baud.title</span> <span data-i18n>settings.system.serial.baud</span>
<input type="number" inputmode="numeric" id="system-serial-baudrate" name="system[serial][baudrate]" min="9600" max="115200" step="1" required> <select id="system-serial-baudrate" name="system[serial][baudrate]" required>
<small data-i18n>settings.system.serial.baud.note</small> <option value="9600">9600</option>
<option value="19200">19200</option>
<option value="38400">38400</option>
<option value="57600">57600</option>
<option value="74880">74880</option>
<option value="115200">115200</option>
</select>
</label> </label>
<label for="system-telnet-port"> <label for="system-telnet-port">
@@ -157,9 +172,9 @@
<input type="number" inputmode="numeric" id="heating-hysteresis" name="heating[hysteresis]" min="0" max="5" step="0.05" required> <input type="number" inputmode="numeric" id="heating-hysteresis" name="heating[hysteresis]" min="0" max="5" step="0.05" required>
</label> </label>
<label for="heating-max-modulation"> <label for="heating-turbo-factor">
<span data-i18n>settings.heating.maxMod</span> <span data-i18n>settings.heating.turboFactor</span>
<input type="number" inputmode="numeric" id="heating-max-modulation" name="heating[maxModulation]" min="1" max="100" step="1" required> <input type="number" inputmode="numeric" id="heating-turbo-factor" name="heating[turboFactor]" min="1.5" max="10" step="0.1" required>
</label> </label>
</div> </div>
@@ -200,11 +215,6 @@
<div id="emergency-settings-busy" aria-busy="true"></div> <div id="emergency-settings-busy" aria-busy="true"></div>
<form action="/api/settings" id="emergency-settings" class="hidden"> <form action="/api/settings" id="emergency-settings" class="hidden">
<fieldset> <fieldset>
<label for="emergency-enable">
<input type="checkbox" id="emergency-enable" name="emergency[enable]" value="true">
<span data-i18n>settings.enable</span>
</label>
<small data-i18n>settings.emergency.desc</small> <small data-i18n>settings.emergency.desc</small>
</fieldset> </fieldset>
@@ -221,33 +231,6 @@
</label> </label>
</div> </div>
<fieldset>
<legend data-i18n>settings.section.emergency.events</legend>
<label for="emergency-on-network-fault">
<input type="checkbox" id="emergency-on-network-fault" name="emergency[onNetworkFault]" value="true">
<span data-i18n>settings.emergency.events.network</span>
</label>
<label for="emergency-on-mqtt-fault">
<input type="checkbox" id="emergency-on-mqtt-fault" name="emergency[onMqttFault]" value="true">
<span data-i18n>settings.emergency.events.mqtt</span>
</label>
</fieldset>
<fieldset>
<legend data-i18n>settings.section.emergency.regulators</legend>
<label for="emergency-use-equitherm">
<input type="checkbox" id="emergency-use-equitherm" name="emergency[useEquitherm]" value="true">
<span data-i18n>settings.emergency.regulators.equitherm</span>
</label>
<label for="emergency-use-pid">
<input type="checkbox" id="emergency-use-pid" name="emergency[usePid]" value="true">
<span data-i18n>settings.emergency.regulators.pid</span>
</label>
</fieldset>
<button type="submit" data-i18n>button.save</button> <button type="submit" data-i18n>button.save</button>
</form> </form>
</div> </div>
@@ -307,7 +290,7 @@
<div class="grid"> <div class="grid">
<label for="pid-p-factor"> <label for="pid-p-factor">
<span data-i18n>settings.pid.p</span> <span data-i18n>settings.pid.p</span>
<input type="number" inputmode="numeric" id="pid-p-factor" name="pid[p_factor]" min="0.1" max="1000" step="0.1" required> <input type="number" inputmode="numeric" id="pid-p-factor" name="pid[p_factor]" min="0.1" max="1000" step="0.01" required>
</label> </label>
<label for="pid-i-factor"> <label for="pid-i-factor">
@@ -317,13 +300,13 @@
<label for="pid-d-factor"> <label for="pid-d-factor">
<span data-i18n>settings.pid.d</span> <span data-i18n>settings.pid.d</span>
<input type="number" inputmode="numeric" id="pid-d-factor" name="pid[d_factor]" min="0" max="100000" step="1" required> <input type="number" inputmode="numeric" id="pid-d-factor" name="pid[d_factor]" min="0" max="100000" step="0.1" required>
</label> </label>
</div> </div>
<label for="pid-dt"> <label for="pid-dt">
<span data-i18n>settings.pid.dt</span> <span data-i18n>settings.pid.dt</span>
<input type="number" inputmode="numeric" id="pid-dt" name="pid[dt]" min="30" max="600" step="1" required> <input type="number" inputmode="numeric" id="pid-dt" name="pid[dt]" min="30" max="1800" step="1" required>
</label> </label>
<hr /> <hr />
@@ -340,6 +323,8 @@
</label> </label>
</div> </div>
<small data-i18n>settings.pid.noteMinMaxTemp</small>
<button type="submit" data-i18n>button.save</button> <button type="submit" data-i18n>button.save</button>
</form> </form>
</div> </div>
@@ -376,23 +361,48 @@
<span data-i18n>settings.ot.outGpio</span> <span data-i18n>settings.ot.outGpio</span>
<input type="number" inputmode="numeric" id="opentherm-out-gpio" name="opentherm[outGpio]" min="0" max="254" step="1"> <input type="number" inputmode="numeric" id="opentherm-out-gpio" name="opentherm[outGpio]" min="0" max="254" step="1">
</label> </label>
</div>
<div class="grid">
<label for="opentherm-rx-led-gpio"> <label for="opentherm-rx-led-gpio">
<span data-i18n>settings.ot.ledGpio</span> <span data-i18n>settings.ot.ledGpio</span>
<input type="number" inputmode="numeric" id="opentherm-rx-led-gpio" name="opentherm[rxLedGpio]" min="0" max="254" step="1"> <input type="number" inputmode="numeric" id="opentherm-rx-led-gpio" name="opentherm[rxLedGpio]" min="0" max="254" step="1">
<small data-i18n>settings.note.blankNotUse</small> <small data-i18n>settings.note.blankNotUse</small>
</label> </label>
</div>
<div class="grid">
<label for="opentherm-member-id-code"> <label for="opentherm-member-id-code">
<span data-i18n>settings.ot.memberIdCode</span> <span data-i18n>settings.ot.memberId</span>
<input type="number" inputmode="numeric" id="opentherm-member-id-code" name="opentherm[memberIdCode]" min="0" max="65535" step="1" required> <input type="number" inputmode="numeric" id="opentherm-member-id" name="opentherm[memberId]" min="0" max="255" step="1" required>
</label>
<label for="opentherm-flags">
<span data-i18n>settings.ot.flags</span>
<input type="number" inputmode="numeric" id="opentherm-flags" name="opentherm[flags]" min="0" max="255" step="1" required>
</label>
<label for="opentherm-max-modulation">
<span data-i18n>settings.ot.maxMod</span>
<input type="number" inputmode="numeric" id="opentherm-max-modulation" name="opentherm[maxModulation]" min="1" max="100" step="1" required>
</label>
</div>
<div class="grid">
<label for="opentherm-min-power">
<span data-i18n>settings.ot.minPower.title</span>
<input type="number" inputmode="numeric" id="opentherm-min-power" name="opentherm[minPower]" min="0" max="1000" step="0.1">
<small data-i18n>settings.ot.minPower.note</small>
</label>
<label for="opentherm-max-power">
<span data-i18n>settings.ot.maxPower.title</span>
<input type="number" inputmode="numeric" id="opentherm-max-power" name="opentherm[maxPower]" min="0" max="1000" step="0.1">
<small data-i18n>settings.ot.maxPower.note</small>
</label> </label>
</div> </div>
<fieldset> <fieldset>
<legend data-i18n>settings.section.ot.options</legend> <legend data-i18n>settings.ot.options.desc</legend>
<label for="opentherm-dhw-present"> <label for="opentherm-dhw-present">
<input type="checkbox" id="opentherm-dhw-present" name="opentherm[dhwPresent]" value="true"> <input type="checkbox" id="opentherm-dhw-present" name="opentherm[dhwPresent]" value="true">
<span data-i18n>settings.ot.options.dhwPresent</span> <span data-i18n>settings.ot.options.dhwPresent</span>
@@ -433,19 +443,10 @@
<span data-i18n>settings.ot.options.getMinMaxTemp</span> <span data-i18n>settings.ot.options.getMinMaxTemp</span>
</label> </label>
<hr /> <label for="opentherm-immergas-fix">
<fieldset> <input type="checkbox" id="opentherm-immergas-fix" name="opentherm[immergasFix]" value="true">
<label for="opentherm-fault-state-gpio"> <span data-i18n>settings.ot.options.immergasFix</span>
<span data-i18n>settings.ot.faultState.gpio</span> </label>
<input type="number" inputmode="numeric" id="opentherm-fault-state-gpio" name="opentherm[faultStateGpio]" min="0" max="254" step="1">
<small data-i18n>settings.ot.faultState.note</small>
</label>
<label for="opentherm-invert-fault-state">
<input type="checkbox" id="opentherm-invert-fault-state" name="opentherm[invertFaultState]" value="true">
<span data-i18n>settings.ot.faultState.invert</span>
</label>
</fieldset>
<hr /> <hr />
<label for="opentherm-native-heating-control"> <label for="opentherm-native-heating-control">
@@ -522,96 +523,6 @@
<hr /> <hr />
<details>
<summary><b data-i18n>settings.section.outdorSensor</b></summary>
<div>
<div id="outdoor-sensor-settings-busy" aria-busy="true"></div>
<form action="/api/settings" id="outdoor-sensor-settings" class="hidden">
<fieldset>
<legend data-i18n>settings.tempSensor.source.type</legend>
<label>
<input type="radio" class="outdoor-sensor-type" name="sensors[outdoor][type]" value="0" />
<span data-i18n>settings.tempSensor.source.boiler</span>
</label>
<label>
<input type="radio" class="outdoor-sensor-type" name="sensors[outdoor][type]" value="1" />
<span data-i18n>settings.tempSensor.source.manual</span>
</label>
<label>
<input type="radio" class="outdoor-sensor-type" name="sensors[outdoor][type]" value="2" />
<span data-i18n>settings.tempSensor.source.ext</span>
</label>
</fieldset>
<label for="outdoor-sensor-gpio">
<span data-i18n>settings.tempSensor.gpio</span>
<input type="number" inputmode="numeric" id="outdoor-sensor-gpio" name="sensors[outdoor][gpio]" min="0" max="254" step="1">
</label>
<label for="outdoor-sensor-offset">
<span data-i18n>settings.tempSensor.offset</span>
<input type="number" inputmode="numeric" id="outdoor-sensor-offset" name="sensors[outdoor][offset]" min="-10" max="10" step="0.01" required>
</label>
<button type="submit" data-i18n>button.save</button>
</form>
</div>
</details>
<hr />
<details>
<summary><b data-i18n>settings.section.indoorSensor</b></summary>
<div>
<div id="indoor-sensor-settings-busy" aria-busy="true"></div>
<form action="/api/settings" id="indoor-sensor-settings" class="hidden">
<fieldset>
<legend data-i18n>settings.tempSensor.source.type</legend>
<label>
<input type="radio" class="indoor-sensor-type" name="sensors[indoor][type]" value="1" />
<span data-i18n>settings.tempSensor.source.manual</span>
</label>
<label>
<input type="radio" class="indoor-sensor-type" name="sensors[indoor][type]" value="2" />
<span data-i18n>settings.tempSensor.source.ext</span>
</label>
<label>
<input type="radio" class="indoor-sensor-type" name="sensors[indoor][type]" value="3" />
<span data-i18n>settings.tempSensor.source.ble</span>
</label>
</fieldset>
<label for="indoor-sensor-gpio">
<span data-i18n>settings.tempSensor.gpio</span>
<input type="number" inputmode="numeric" id="indoor-sensor-gpio" name="sensors[indoor][gpio]" min="0" max="254" step="1">
</label>
<div class="grid">
<label for="indoor-sensor-offset">
<span data-i18n>settings.tempSensor.offset</span>
<input type="number" inputmode="numeric" id="indoor-sensor-offset" name="sensors[indoor][offset]" min="-10" max="10" step="0.01" required>
</label>
<label for="indoor-sensor-ble-addresss">
<span data-i18n>settings.tempSensor.bleAddress.title</span>
<input type="text" id="indoor-sensor-ble-addresss" name="sensors[indoor][bleAddress]" pattern="([A-Fa-f0-9]{2}:){5}[A-Fa-f0-9]{2}">
<small data-i18n>settings.tempSensor.bleAddress.note</small>
</label>
</div>
<button type="submit" data-i18n>button.save</button>
</form>
</div>
</details>
<hr />
<details> <details>
<summary><b data-i18n>settings.section.extPump</b></summary> <summary><b data-i18n>settings.section.extPump</b></summary>
<div> <div>
@@ -652,6 +563,91 @@
</form> </form>
</div> </div>
</details> </details>
<hr />
<details>
<summary><b data-i18n>settings.section.cascadeControl</b></summary>
<div>
<div id="cc-settings-busy" aria-busy="true"></div>
<form action="/api/settings" id="cc-settings" class="hidden">
<fieldset>
<label for="cc-input-enable">
<input type="checkbox" id="cc-input-enable" name="cascadeControl[input][enable]" value="true">
<span data-i18n>settings.cascadeControl.input.enable</span>
<br />
<small data-i18n>settings.cascadeControl.input.desc</small>
</label>
<label for="cc-input-invert-state">
<input type="checkbox" id="cc-input-invert-state" name="cascadeControl[input][invertState]" value="true">
<span data-i18n>settings.cascadeControl.input.invertState</span>
</label>
</fieldset>
<div class="grid">
<label for="cc-input-gpio">
<span data-i18n>settings.cascadeControl.input.gpio</span>
<input type="number" inputmode="numeric" id="cc-input-gpio" name="cascadeControl[input][gpio]" min="0" max="254" step="1">
</label>
<label for="cc-input-tt">
<span data-i18n>settings.cascadeControl.input.thresholdTime</span>
<input type="number" inputmode="numeric" id="cc-input-tt" name="cascadeControl[input][thresholdTime]" min="5" max="600" step="1" required>
</label>
</div>
<hr />
<fieldset>
<label for="cc-output-enable">
<input type="checkbox" id="cc-output-enable" name="cascadeControl[output][enable]" value="true">
<span data-i18n>settings.cascadeControl.output.enable</span>
<br />
<small data-i18n>settings.cascadeControl.output.desc</small>
</label>
<label for="cc-output-invert-state">
<input type="checkbox" id="cc-output-invert-state" name="cascadeControl[output][invertState]" value="true">
<span data-i18n>settings.cascadeControl.output.invertState</span>
</label>
</fieldset>
<div class="grid">
<label for="cc-output-gpio">
<span data-i18n>settings.cascadeControl.output.gpio</span>
<input type="number" outputmode="numeric" id="cc-output-gpio" name="cascadeControl[output][gpio]" min="0" max="254" step="1">
</label>
<label for="cc-output-tt">
<span data-i18n>settings.cascadeControl.output.thresholdTime</span>
<input type="number" outputmode="numeric" id="cc-output-tt" name="cascadeControl[output][thresholdTime]" min="5" max="600" step="1" required>
</label>
</div>
<fieldset>
<legend data-i18n>settings.cascadeControl.output.events.desc</legend>
<label for="cc-on-fault">
<input type="checkbox" id="cc-on-fault" name="cascadeControl[output][onFault]" value="true">
<span data-i18n>settings.cascadeControl.output.events.onFault</span>
</label>
<label for="cc-on-loss-conn">
<input type="checkbox" id="cc-on-loss-conn" name="cascadeControl[output][onLossConnection]" value="true">
<span data-i18n>settings.cascadeControl.output.events.onLossConnection</span>
</label>
<label for="cc-on-enabled-heating">
<input type="checkbox" id="cc-on-enabled-heating" name="cascadeControl[output][onEnabledHeating]" value="true">
<span data-i18n>settings.cascadeControl.output.events.onEnabledHeating</span>
</label>
</fieldset>
<button type="submit" data-i18n>button.save</button>
</form>
</div>
</details>
</article> </article>
</main> </main>
@@ -672,13 +668,12 @@
const lang = new Lang(document.getElementById('lang')); const lang = new Lang(document.getElementById('lang'));
lang.build(); lang.build();
const fillData = (data) => { const fillData = (data) => {
// System // System
setCheckboxValue('#system-debug', data.system.debug); setSelectValue('#system-log-level', data.system.logLevel);
setCheckboxValue('#system-serial-enable', data.system.serial.enable); setCheckboxValue('#system-serial-enable', data.system.serial.enabled);
setInputValue('#system-serial-baudrate', data.system.serial.baudrate); setSelectValue('#system-serial-baudrate', data.system.serial.baudrate);
setCheckboxValue('#system-telnet-enable', data.system.telnet.enable); setCheckboxValue('#system-telnet-enable', data.system.telnet.enabled);
setInputValue('#system-telnet-port', data.system.telnet.port); setInputValue('#system-telnet-port', data.system.telnet.port);
setRadioValue('.system-unit-system', data.system.unitSystem); setRadioValue('.system-unit-system', data.system.unitSystem);
setInputValue('#system-status-led-gpio', data.system.statusLedGpio < 255 ? data.system.statusLedGpio : ''); setInputValue('#system-status-led-gpio', data.system.statusLedGpio < 255 ? data.system.statusLedGpio : '');
@@ -695,9 +690,11 @@
setInputValue('#opentherm-in-gpio', data.opentherm.inGpio < 255 ? data.opentherm.inGpio : ''); setInputValue('#opentherm-in-gpio', data.opentherm.inGpio < 255 ? data.opentherm.inGpio : '');
setInputValue('#opentherm-out-gpio', data.opentherm.outGpio < 255 ? data.opentherm.outGpio : ''); setInputValue('#opentherm-out-gpio', data.opentherm.outGpio < 255 ? data.opentherm.outGpio : '');
setInputValue('#opentherm-rx-led-gpio', data.opentherm.rxLedGpio < 255 ? data.opentherm.rxLedGpio : ''); setInputValue('#opentherm-rx-led-gpio', data.opentherm.rxLedGpio < 255 ? data.opentherm.rxLedGpio : '');
setInputValue('#opentherm-fault-state-gpio', data.opentherm.faultStateGpio < 255 ? data.opentherm.faultStateGpio : ''); setInputValue('#opentherm-member-id', data.opentherm.memberId);
setCheckboxValue('#opentherm-invert-fault-state', data.opentherm.invertFaultState); setInputValue('#opentherm-flags', data.opentherm.flags);
setInputValue('#opentherm-member-id-code', data.opentherm.memberIdCode); setInputValue('#opentherm-max-modulation', data.opentherm.maxModulation);
setInputValue('#opentherm-min-power', data.opentherm.minPower);
setInputValue('#opentherm-max-power', data.opentherm.maxPower);
setCheckboxValue('#opentherm-dhw-present', data.opentherm.dhwPresent); setCheckboxValue('#opentherm-dhw-present', data.opentherm.dhwPresent);
setCheckboxValue('#opentherm-sw-mode', data.opentherm.summerWinterMode); setCheckboxValue('#opentherm-sw-mode', data.opentherm.summerWinterMode);
setCheckboxValue('#opentherm-heating-ch2-enabled', data.opentherm.heatingCh2Enabled); setCheckboxValue('#opentherm-heating-ch2-enabled', data.opentherm.heatingCh2Enabled);
@@ -707,10 +704,11 @@
setCheckboxValue('#opentherm-sync-modulation-with-heating', data.opentherm.modulationSyncWithHeating); setCheckboxValue('#opentherm-sync-modulation-with-heating', data.opentherm.modulationSyncWithHeating);
setCheckboxValue('#opentherm-get-min-max-temp', data.opentherm.getMinMaxTemp); setCheckboxValue('#opentherm-get-min-max-temp', data.opentherm.getMinMaxTemp);
setCheckboxValue('#opentherm-native-heating-control', data.opentherm.nativeHeatingControl); setCheckboxValue('#opentherm-native-heating-control', data.opentherm.nativeHeatingControl);
setCheckboxValue('#opentherm-immergas-fix', data.opentherm.immergasFix);
setBusy('#opentherm-settings-busy', '#opentherm-settings', false); setBusy('#opentherm-settings-busy', '#opentherm-settings', false);
// MQTT // MQTT
setCheckboxValue('#mqtt-enable', data.mqtt.enable); setCheckboxValue('#mqtt-enable', data.mqtt.enabled);
setCheckboxValue('#mqtt-ha-discovery', data.mqtt.homeAssistantDiscovery); setCheckboxValue('#mqtt-ha-discovery', data.mqtt.homeAssistantDiscovery);
setInputValue('#mqtt-server', data.mqtt.server); setInputValue('#mqtt-server', data.mqtt.server);
setInputValue('#mqtt-port', data.mqtt.port); setInputValue('#mqtt-port', data.mqtt.port);
@@ -720,19 +718,6 @@
setInputValue('#mqtt-interval', data.mqtt.interval); setInputValue('#mqtt-interval', data.mqtt.interval);
setBusy('#mqtt-settings-busy', '#mqtt-settings', false); setBusy('#mqtt-settings-busy', '#mqtt-settings', false);
// Outdoor sensor
setRadioValue('.outdoor-sensor-type', data.sensors.outdoor.type);
setInputValue('#outdoor-sensor-gpio', data.sensors.outdoor.gpio < 255 ? data.sensors.outdoor.gpio : '');
setInputValue('#outdoor-sensor-offset', data.sensors.outdoor.offset);
setBusy('#outdoor-sensor-settings-busy', '#outdoor-sensor-settings', false);
// Indoor sensor
setRadioValue('.indoor-sensor-type', data.sensors.indoor.type);
setInputValue('#indoor-sensor-gpio', data.sensors.indoor.gpio < 255 ? data.sensors.indoor.gpio : '');
setInputValue('#indoor-sensor-offset', data.sensors.indoor.offset);
setInputValue('#indoor-sensor-ble-addresss', data.sensors.indoor.bleAddress);
setBusy('#indoor-sensor-settings-busy', '#indoor-sensor-settings', false);
// Extpump // Extpump
setCheckboxValue('#extpump-use', data.externalPump.use); setCheckboxValue('#extpump-use', data.externalPump.use);
setInputValue('#extpump-gpio', data.externalPump.gpio < 255 ? data.externalPump.gpio : ''); setInputValue('#extpump-gpio', data.externalPump.gpio < 255 ? data.externalPump.gpio : '');
@@ -741,6 +726,21 @@
setInputValue('#extpump-as-time', data.externalPump.antiStuckTime); setInputValue('#extpump-as-time', data.externalPump.antiStuckTime);
setBusy('#extpump-settings-busy', '#extpump-settings', false); setBusy('#extpump-settings-busy', '#extpump-settings', false);
// Cascade control
setCheckboxValue('#cc-input-enable', data.cascadeControl.input.enabled);
setInputValue('#cc-input-gpio', data.cascadeControl.input.gpio < 255 ? data.cascadeControl.input.gpio : '');
setCheckboxValue('#cc-input-invert-state', data.cascadeControl.input.invertState);
setInputValue('#cc-input-tt', data.cascadeControl.input.thresholdTime);
setCheckboxValue('#cc-output-enable', data.cascadeControl.output.enabled);
setInputValue('#cc-output-gpio', data.cascadeControl.output.gpio < 255 ? data.cascadeControl.output.gpio : '');
setCheckboxValue('#cc-output-invert-state', data.cascadeControl.output.invertState);
setInputValue('#cc-output-tt', data.cascadeControl.output.thresholdTime);
setCheckboxValue('#cc-on-fault', data.cascadeControl.output.onFault);
setCheckboxValue('#cc-on-loss-conn', data.cascadeControl.output.onLossConnection);
setCheckboxValue('#cc-on-enabled-heating', data.cascadeControl.output.onEnabledHeating);
setBusy('#cc-settings-busy', '#cc-settings', false);
// Heating // Heating
setInputValue('#heating-min-temp', data.heating.minTemp, { setInputValue('#heating-min-temp', data.heating.minTemp, {
"min": data.system.unitSystem == 0 ? 0 : 32, "min": data.system.unitSystem == 0 ? 0 : 32,
@@ -751,7 +751,7 @@
"max": data.system.unitSystem == 0 ? 100 : 212 "max": data.system.unitSystem == 0 ? 100 : 212
}); });
setInputValue('#heating-hysteresis', data.heating.hysteresis); setInputValue('#heating-hysteresis', data.heating.hysteresis);
setInputValue('#heating-max-modulation', data.heating.maxModulation); setInputValue('#heating-turbo-factor', data.heating.turboFactor);
setBusy('#heating-settings-busy', '#heating-settings', false); setBusy('#heating-settings-busy', '#heating-settings', false);
// DHW // DHW
@@ -766,38 +766,42 @@
setBusy('#dhw-settings-busy', '#dhw-settings', false); setBusy('#dhw-settings-busy', '#dhw-settings', false);
// Emergency mode // Emergency mode
setCheckboxValue('#emergency-enable', data.emergency.enable);
setInputValue('#emergency-treshold-time', data.emergency.tresholdTime); setInputValue('#emergency-treshold-time', data.emergency.tresholdTime);
setCheckboxValue('#emergency-use-equitherm', data.emergency.useEquitherm); if (data.opentherm.nativeHeatingControl) {
setCheckboxValue('#emergency-use-pid', data.emergency.usePid); setInputValue('#emergency-target', data.emergency.target, {
setCheckboxValue('#emergency-on-network-fault', data.emergency.onNetworkFault); "min": data.system.unitSystem == 0 ? 5 : 41,
setCheckboxValue('#emergency-on-mqtt-fault', data.emergency.onMqttFault); "max": data.system.unitSystem == 0 ? 30 : 86
setInputValue('#emergency-target', data.emergency.target, { });
"min": (!data.emergency.useEquitherm && !data.emergency.usePid) ? data.heating.minTemp : 10,
"max": (!data.emergency.useEquitherm && !data.emergency.usePid) ? data.heating.maxTemp : 30, } else {
}); setInputValue('#emergency-target', data.emergency.target, {
"min": data.heating.minTemp,
"max": data.heating.maxTemp,
});
}
setBusy('#emergency-settings-busy', '#emergency-settings', false); setBusy('#emergency-settings-busy', '#emergency-settings', false);
// Equitherm // Equitherm
setCheckboxValue('#equitherm-enable', data.equitherm.enable); setCheckboxValue('#equitherm-enable', data.equitherm.enabled);
setInputValue('#equitherm-n-factor', data.equitherm.n_factor); setInputValue('#equitherm-n-factor', data.equitherm.n_factor);
setInputValue('#equitherm-k-factor', data.equitherm.k_factor); setInputValue('#equitherm-k-factor', data.equitherm.k_factor);
setInputValue('#equitherm-t-factor', data.equitherm.t_factor); setInputValue('#equitherm-t-factor', data.equitherm.t_factor);
setBusy('#equitherm-settings-busy', '#equitherm-settings', false); setBusy('#equitherm-settings-busy', '#equitherm-settings', false);
// PID // PID
setCheckboxValue('#pid-enable', data.pid.enable); setCheckboxValue('#pid-enable', data.pid.enabled);
setInputValue('#pid-p-factor', data.pid.p_factor); setInputValue('#pid-p-factor', data.pid.p_factor);
setInputValue('#pid-i-factor', data.pid.i_factor); setInputValue('#pid-i-factor', data.pid.i_factor);
setInputValue('#pid-d-factor', data.pid.d_factor); setInputValue('#pid-d-factor', data.pid.d_factor);
setInputValue('#pid-dt', data.pid.dt); setInputValue('#pid-dt', data.pid.dt);
setInputValue('#pid-min-temp', data.pid.minTemp, { setInputValue('#pid-min-temp', data.pid.minTemp, {
"min": 0, "min": data.equitherm.enabled ? (data.system.unitSystem == 0 ? -100 : -146) : (data.system.unitSystem == 0 ? 0 : 32),
"max": data.system.unitSystem == 0 ? 99 : 211 "max": (data.system.unitSystem == 0 ? 99 : 211)
}); });
setInputValue('#pid-max-temp', data.pid.maxTemp, { setInputValue('#pid-max-temp', data.pid.maxTemp, {
"min": 1, "min": (data.system.unitSystem == 0 ? 0 : 33),
"max": data.system.unitSystem == 0 ? 100 : 212 "max": (data.system.unitSystem == 0 ? 100 : 212)
}); });
setBusy('#pid-settings-busy', '#pid-settings', false); setBusy('#pid-settings-busy', '#pid-settings', false);
}; };
@@ -820,9 +824,8 @@
setupForm('#pid-settings', fillData); setupForm('#pid-settings', fillData);
setupForm('#opentherm-settings', fillData); setupForm('#opentherm-settings', fillData);
setupForm('#mqtt-settings', fillData, ['mqtt.user', 'mqtt.password', 'mqtt.prefix']); setupForm('#mqtt-settings', fillData, ['mqtt.user', 'mqtt.password', 'mqtt.prefix']);
setupForm('#outdoor-sensor-settings', fillData);
setupForm('#indoor-sensor-settings', fillData, ['sensors.indoor.bleAddress']);
setupForm('#extpump-settings', fillData); setupForm('#extpump-settings', fillData);
setupForm('#cc-settings', fillData);
} catch (error) { } catch (error) {
console.log(error); console.log(error);

View File

@@ -102,7 +102,7 @@ class Lang {
} }
getSuitableLocale(locales) { getSuitableLocale(locales) {
return locales.find(this.localeIsSupported) || this.defaultLocale; return locales.find(this.localeIsSupported, this) || this.defaultLocale;
} }
browserLocales(codeOnly = false) { browserLocales(codeOnly = false) {

View File

@@ -1,4 +1,4 @@
function setupForm(formSelector, onResultCallback = null, noCastItems = []) { const setupForm = (formSelector, onResultCallback = null, noCastItems = []) => {
const form = document.querySelector(formSelector); const form = document.querySelector(formSelector);
if (!form) { if (!form) {
return; return;
@@ -10,18 +10,15 @@ function setupForm(formSelector, onResultCallback = null, noCastItems = []) {
}) })
}); });
const url = form.action;
let button = form.querySelector('button[type="submit"]');
let defaultText;
if (button) {
defaultText = button.textContent;
}
form.addEventListener('submit', async (event) => { form.addEventListener('submit', async (event) => {
event.preventDefault(); event.preventDefault();
const url = form.action;
let button = form.querySelector('button[type="submit"]');
let defaultText;
if (button) { if (button) {
defaultText = button.textContent;
button.textContent = i18n("button.wait"); button.textContent = i18n("button.wait");
button.setAttribute('disabled', true); button.setAttribute('disabled', true);
button.setAttribute('aria-busy', true); button.setAttribute('aria-busy', true);
@@ -89,7 +86,7 @@ function setupForm(formSelector, onResultCallback = null, noCastItems = []) {
}); });
} }
function setupNetworkScanForm(formSelector, tableSelector) { const setupNetworkScanForm = (formSelector, tableSelector) => {
const form = document.querySelector(formSelector); const form = document.querySelector(formSelector);
if (!form) { if (!form) {
console.error("form not found"); console.error("form not found");
@@ -100,16 +97,13 @@ function setupNetworkScanForm(formSelector, tableSelector) {
let button = form.querySelector('button[type="submit"]'); let button = form.querySelector('button[type="submit"]');
let defaultText; let defaultText;
if (button) {
defaultText = button.innerHTML;
}
const onSubmitFn = async (event) => { const onSubmitFn = async (event) => {
if (event) { if (event) {
event.preventDefault(); event.preventDefault();
} }
if (button) { if (button) {
defaultText = button.innerHTML;
button.innerHTML = i18n('button.wait'); button.innerHTML = i18n('button.wait');
button.setAttribute('disabled', true); button.setAttribute('disabled', true);
button.setAttribute('aria-busy', true); button.setAttribute('aria-busy', true);
@@ -138,7 +132,7 @@ function setupNetworkScanForm(formSelector, tableSelector) {
let row = tbody.insertRow(-1); let row = tbody.insertRow(-1);
row.classList.add("network"); row.classList.add("network");
row.setAttribute('data-ssid', result[i].hidden ? '' : result[i].ssid); row.setAttribute('data-ssid', result[i].hidden ? '' : result[i].ssid);
row.onclick = function () { row.onclick = () => {
const input = document.querySelector('input#sta-ssid'); const input = document.querySelector('input#sta-ssid');
const ssid = this.getAttribute('data-ssid'); const ssid = this.getAttribute('data-ssid');
if (!input || !ssid) { if (!input || !ssid) {
@@ -252,7 +246,7 @@ function setupNetworkScanForm(formSelector, tableSelector) {
onSubmitFn(); onSubmitFn();
} }
function setupRestoreBackupForm(formSelector) { const setupRestoreBackupForm = (formSelector) => {
const form = document.querySelector(formSelector); const form = document.querySelector(formSelector);
if (!form) { if (!form) {
return; return;
@@ -262,20 +256,17 @@ function setupRestoreBackupForm(formSelector) {
let button = form.querySelector('button[type="submit"]'); let button = form.querySelector('button[type="submit"]');
let defaultText; let defaultText;
if (button) {
defaultText = button.textContent;
}
form.addEventListener('submit', async (event) => { form.addEventListener('submit', async (event) => {
event.preventDefault(); event.preventDefault();
if (button) { if (button) {
defaultText = button.textContent;
button.textContent = i18n('button.wait'); button.textContent = i18n('button.wait');
button.setAttribute('disabled', true); button.setAttribute('disabled', true);
button.setAttribute('aria-busy', true); button.setAttribute('aria-busy', true);
} }
const onSuccess = (response) => { const onSuccess = () => {
if (button) { if (button) {
button.textContent = i18n('button.restored'); button.textContent = i18n('button.restored');
button.classList.add('success'); button.classList.add('success');
@@ -289,7 +280,7 @@ function setupRestoreBackupForm(formSelector) {
} }
}; };
const onFailed = (response) => { const onFailed = () => {
if (button) { if (button) {
button.textContent = i18n('button.error'); button.textContent = i18n('button.error');
button.classList.add('failed'); button.classList.add('failed');
@@ -311,35 +302,79 @@ function setupRestoreBackupForm(formSelector) {
let reader = new FileReader(); let reader = new FileReader();
reader.readAsText(files[0]); reader.readAsText(files[0]);
reader.onload = async function () { reader.onload = async (event) => {
try { try {
let response = await fetch(url, { const data = JSON.parse(event.target.result);
method: 'POST', console.log("Backup: ", data);
cache: 'no-cache',
headers: { if (data.network != undefined) {
'Content-Type': 'application/json' let response = await fetch(url, {
}, method: 'POST',
body: reader.result cache: 'no-cache',
}); headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data.network)
});
if (response.ok) { if (!response.ok) {
onSuccess(response); onFailed();
return;
} else { }
onFailed(response);
} }
if (data.settings != undefined) {
let response = await fetch(url, {
method: 'POST',
cache: 'no-cache',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data.settings)
});
if (!response.ok) {
onFailed();
return;
}
}
if (data.sensors != undefined) {
for (const sensorId in data.sensors) {
const payload = {
"sensors": {}
};
payload["sensors"][sensorId] = data.sensors[sensorId];
const response = await fetch(url, {
method: 'POST',
cache: 'no-cache',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(payload)
});
if (!response.ok) {
onFailed();
return;
}
}
}
onSuccess();
} catch (err) { } catch (err) {
onFailed(false); onFailed();
} }
}; };
reader.onerror = function () { reader.onerror = () => {
console.log(reader.error); console.log(reader.error);
}; };
}); });
} }
function setupUpgradeForm(formSelector) { const setupUpgradeForm = (formSelector) => {
const form = document.querySelector(formSelector); const form = document.querySelector(formSelector);
if (!form) { if (!form) {
return; return;
@@ -349,10 +384,6 @@ function setupUpgradeForm(formSelector) {
let button = form.querySelector('button[type="submit"]'); let button = form.querySelector('button[type="submit"]');
let defaultText; let defaultText;
if (button) {
defaultText = button.textContent;
}
const statusToText = (status) => { const statusToText = (status) => {
switch (status) { switch (status) {
case 0: case 0:
@@ -456,6 +487,7 @@ function setupUpgradeForm(formSelector) {
hide('.upgrade-filesystem-result'); hide('.upgrade-filesystem-result');
if (button) { if (button) {
defaultText = button.textContent;
button.textContent = i18n('button.uploading'); button.textContent = i18n('button.uploading');
button.setAttribute('disabled', true); button.setAttribute('disabled', true);
button.setAttribute('aria-busy', true); button.setAttribute('aria-busy', true);
@@ -483,19 +515,23 @@ function setupUpgradeForm(formSelector) {
} }
function setBusy(busySelector, contentSelector, value) { const setBusy = (busySelector, contentSelector, value, parent = undefined) => {
if (!value) { if (!value) {
hide(busySelector); hide(busySelector, parent);
show(contentSelector); show(contentSelector, parent);
} else { } else {
show(busySelector); show(busySelector, parent);
hide(contentSelector); hide(contentSelector, parent);
} }
} }
function setState(selector, value) { const setState = (selector, value, parent = undefined) => {
let item = document.querySelector(selector); if (parent == undefined) {
parent = document;
}
let item = parent.querySelector(selector);
if (!item) { if (!item) {
return; return;
} }
@@ -503,8 +539,12 @@ function setState(selector, value) {
item.setAttribute('aria-invalid', !value); item.setAttribute('aria-invalid', !value);
} }
function setValue(selector, value) { const setValue = (selector, value, parent = undefined) => {
let items = document.querySelectorAll(selector); if (parent == undefined) {
parent = document;
}
let items = parent.querySelectorAll(selector);
if (!items.length) { if (!items.length) {
return; return;
} }
@@ -514,8 +554,12 @@ function setValue(selector, value) {
} }
} }
function setCheckboxValue(selector, value) { const setCheckboxValue = (selector, value, parent = undefined) => {
let item = document.querySelector(selector); if (parent == undefined) {
parent = document;
}
let item = parent.querySelector(selector);
if (!item) { if (!item) {
return; return;
} }
@@ -523,8 +567,12 @@ function setCheckboxValue(selector, value) {
item.checked = value; item.checked = value;
} }
function setRadioValue(selector, value) { const setRadioValue = (selector, value, parent = undefined) => {
let items = document.querySelectorAll(selector); if (parent == undefined) {
parent = document;
}
let items = parent.querySelectorAll(selector);
if (!items.length) { if (!items.length) {
return; return;
} }
@@ -534,8 +582,12 @@ function setRadioValue(selector, value) {
} }
} }
function setInputValue(selector, value, attrs = {}) { const setInputValue = (selector, value, attrs = {}, parent = undefined) => {
let items = document.querySelectorAll(selector); if (parent == undefined) {
parent = document;
}
let items = parent.querySelectorAll(selector);
if (!items.length) { if (!items.length) {
return; return;
} }
@@ -551,8 +603,27 @@ function setInputValue(selector, value, attrs = {}) {
} }
} }
function show(selector) { const setSelectValue = (selector, value, parent = undefined) => {
let items = document.querySelectorAll(selector); if (parent == undefined) {
parent = document;
}
let item = parent.querySelector(selector);
if (!item) {
return;
}
for (let option of item.options) {
option.selected = option.value == value;
}
}
const show = (selector, parent = undefined) => {
if (parent == undefined) {
parent = document;
}
let items = parent.querySelectorAll(selector);
if (!items.length) { if (!items.length) {
return; return;
} }
@@ -564,8 +635,12 @@ function show(selector) {
} }
} }
function hide(selector) { const hide = (selector, parent = undefined) => {
let items = document.querySelectorAll(selector); if (parent == undefined) {
parent = document;
}
let items = parent.querySelectorAll(selector);
if (!items.length) { if (!items.length) {
return; return;
} }
@@ -583,34 +658,34 @@ function unit2str(unitSystem, units = {}, defaultValue = '?') {
: defaultValue; : defaultValue;
} }
function temperatureUnit(unitSystem) { const temperatureUnit = (unitSystem) => {
return unit2str(unitSystem, { return unit2str(unitSystem, {
0: "°C", 0: "°C",
1: "°F" 1: "°F"
}); });
} }
function pressureUnit(unitSystem) { const pressureUnit = (unitSystem) => {
return unit2str(unitSystem, { return unit2str(unitSystem, {
0: "bar", 0: "bar",
1: "psi" 1: "psi"
}); });
} }
function volumeUnit(unitSystem) { const volumeUnit = (unitSystem) => {
return unit2str(unitSystem, { return unit2str(unitSystem, {
0: "L", 0: "L",
1: "gal" 1: "gal"
}); });
} }
function memberIdToVendor(memberId) { const memberIdToVendor = (memberId) => {
// https://github.com/Jeroen88/EasyOpenTherm/blob/main/src/EasyOpenTherm.h // https://github.com/Jeroen88/EasyOpenTherm/blob/main/src/EasyOpenTherm.h
// https://github.com/Evgen2/SmartTherm/blob/v0.7/src/Web.cpp // https://github.com/Evgen2/SmartTherm/blob/v0.7/src/Web.cpp
const vendorList = { const vendorList = {
1: "Baxi Fourtech/Luna 3", 1: "Baxi",
2: "AWB/Brink", 2: "AWB/Brink/Viessmann",
4: "ATAG/Brötje/ELCO/GEMINOX", 4: "ATAG/Baxi/Brötje/ELCO/GEMINOX",
5: "Itho Daalderop", 5: "Itho Daalderop",
6: "IDEAL", 6: "IDEAL",
8: "Buderus/Bosch/Hoval", 8: "Buderus/Bosch/Hoval",
@@ -621,8 +696,8 @@ function memberIdToVendor(memberId) {
27: "Baxi", 27: "Baxi",
29: "Itho Daalderop", 29: "Itho Daalderop",
33: "Viessmann", 33: "Viessmann",
41: "Italtherm", 41: "Italtherm/Radiant",
56: "Baxi Luna Duo-Tec", 56: "Baxi",
131: "Nefit", 131: "Nefit",
148: "Navien", 148: "Navien",
173: "Intergas", 173: "Intergas",
@@ -677,4 +752,13 @@ function form2json(data, noCastItems = []) {
let object = Array.from(data).reduce(method, {}); let object = Array.from(data).reduce(method, {});
return JSON.stringify(object); return JSON.stringify(object);
}
function dec2hex(i) {
let hex = parseInt(i).toString(16);
if (hex.length % 2 != 0) {
hex = "0" + hex;
}
return hex.toUpperCase();
} }

View File

@@ -6,10 +6,11 @@ Import("env")
def post_build(source, target, env): def post_build(source, target, env):
copy_to_build_dir({ copy_to_build_dir({
source[0].get_abspath(): "firmware_%s_%s.bin" % (env["PIOENV"], env.GetProjectOption("version")), source[0].get_abspath(): "firmware_%s_%s.bin" % (env["PIOENV"], env.GetProjectOption("version")),
env.subst("$BUILD_DIR/${PROGNAME}.factory.bin"): "firmware_%s_%s.factory.bin" % (env["PIOENV"], env.GetProjectOption("version")), env.subst("$BUILD_DIR/${PROGNAME}.factory.bin"): "firmware_%s_%s.factory.bin" % (env["PIOENV"], env.GetProjectOption("version")),
env.subst("$BUILD_DIR/${PROGNAME}.elf"): "firmware_%s_%s.elf" % (env["PIOENV"], env.GetProjectOption("version"))
}, os.path.join(env["PROJECT_DIR"], "build")); }, os.path.join(env["PROJECT_DIR"], "build"));
env.Execute("pio run --target buildfs --environment %s" % env["PIOENV"]); env.Execute("pio run --target buildfs --environment %s" % env["PIOENV"]);

View File

@@ -1,12 +0,0 @@
{
"version": 1,
"editor": "wokwi",
"parts": [
{ "type": "wokwi-esp32-devkit-v1", "id": "esp", "top": 0, "left": 0, "attrs": {} }
],
"connections": [
[ "esp:TX0", "$serialMonitor:RX", "", [] ],
[ "esp:RX0", "$serialMonitor:TX", "", [] ]
]
}

View File

@@ -1,8 +0,0 @@
[wokwi]
version = 1
elf = "../../.pio/build/nodemcu_32s/firmware.elf"
firmware = "../../.pio/build/nodemcu_32s/firmware.bin"
[[net.forward]]
from = "localhost:9080"
to = "target:80"