76 Commits

Author SHA1 Message Date
Yurii
b631b496cb chore: bump version to 1.4.1 2024-06-13 17:15:37 +03:00
Yurii
36328a1db5 refactor: NetworkMgr code optimization 2024-06-13 17:09:41 +03:00
Yurii
a28c7f67b5 chore: added min version of espressif8266 for build #68 2024-06-13 17:07:53 +03:00
Yurii
bb9b6a5f4c fix: casting types in setupForm fixed #61 2024-06-11 20:19:54 +03:00
Yurii
ce7bd7e23b feat: migrate to arduino-esp32 core 3.0.1 2024-06-10 16:20:03 +03:00
Yurii
249d32ce35 chore: more info when scan wifi 2024-06-10 14:55:14 +03:00
Yurii
baf8adfb02 fix: validation GPIO and reset wifi for arduino-esp32 core 3.x.x fixed 2024-06-06 16:37:57 +03:00
Yurii
018a1c5188 fix: set ssid on portal fixed 2024-06-06 02:56:19 +03:00
Yurii
b600c130f0 fix: conflicts with sdk 3.x.x for esp32 fixed 2024-06-05 23:11:27 +03:00
Yurii
59eb05726a chore: bump platformio/framework-arduinoespressif32 to 2.0.17 2024-05-26 23:22:19 +03:00
Yurii
f245f37dfd chore: bump version 2024-05-26 17:41:12 +03:00
Yurii
a825412f37 feat: added fault state GPIO setting 2024-05-25 02:51:10 +03:00
Yurii
935f8bd0a8 fix: outdoor sensor GPIO validation fixed 2024-05-25 00:17:55 +03:00
Yurii
f78d2f38b8 fix: equitherm with BLE indoor sensor in emergency mode fixed 2024-04-25 13:38:03 +03:00
Yurii
ef083991e3 feat: added board info on portal 2024-04-23 10:30:42 +03:00
Yurii
3c0f846335 fix: added set target indoor temp to CH2 for native heating control #58 2024-04-23 08:13:03 +03:00
Yurii
85011ce4ea chore: bump version 2024-04-22 08:19:41 +03:00
Yurii
8687e122ca feat: added native heating control by boiler; refactoring; emergency settings removed from HA 2024-04-22 08:18:59 +03:00
Yurii
d35ea81080 fix: PID optimization, correction of default PID settings 2024-04-18 00:04:23 +03:00
Yurii
cca8ec58b4 fix: fix radio on settings page (portal) 2024-04-16 18:58:28 +03:00
Yurii
301b14bbd4 chore: bump version 2024-04-15 15:07:28 +03:00
Yurii
41ce9b268e fix: fix set heating temp on ITALTHERM TIME MAX 30F 2024-04-15 15:00:41 +03:00
Yurii
646939179e fix: serial on s2, s3 fixed 2024-04-15 05:49:46 +03:00
Yurii
73dddd18f0 fix: scan networks on s3 fixed 2024-04-15 02:47:42 +03:00
Yurii
f069de0415 chore: fix typo 2024-04-12 21:47:09 +03:00
Yurii
f4a4afeb29 chore: bump version 2024-04-12 20:47:02 +03:00
Yurii
f9824337dc chore: delete .gz files 2024-04-12 20:39:56 +03:00
Yurii
1cd8c6a336 chore: html files moved to src_data dir; compression files 2024-04-12 04:13:44 +03:00
Yurii
63228baebd chore: auto file compression when building a FS 2024-04-12 04:10:55 +03:00
Yurii
6bb261dfd7 feat: ability to use compressed files for StaticPage 2024-04-12 04:08:28 +03:00
Yurii
a026a962f0 fix: fixed thermostat temperature limits for different unit systems on dashboard 2024-04-12 02:03:43 +03:00
Yurii
db2faad741 feat: added Italtherm to vendor list 2024-04-12 01:58:33 +03:00
Yurii
fbc43dc535 feat: added settings for status led gpio, opentherm rx led gpio, emergency treshold time 2024-04-11 23:53:15 +03:00
Yurii
31dfc21d69 refactor: added info for emergency mode in settings 2024-04-11 04:38:12 +03:00
Yurii
96289cb0f7 fix: reset onewire before begin (fix DS18B20) 2024-04-11 04:09:11 +03:00
Yurii
73da3ee07a fix: fixing button groups on the mobile version 2024-04-11 03:13:39 +03:00
Yurii
a14281924f chore: bump version 2024-04-11 03:08:35 +03:00
Yurii
3dec390cce feat: many features
* added dashboard on portal
* added settings for serial port and telnet
* added on/off settings for mqtt
* added event selection for emergency mode
* refactor html & css
2024-04-11 03:06:56 +03:00
Yurii
9a29819d4f refactor: reworked layout and styles of the portal 2024-04-08 05:37:36 +03:00
Yurii
2af159d566 chore: updated css framework 2024-04-07 23:00:22 +03:00
Yurii
92ca257d32 feat: added slave parameters to index page on portal; added poll ID125 (opentherm protocol version) 2024-04-07 22:59:43 +03:00
Yurii
44b6620431 fix: rounding the DHW flow rate value 2024-04-07 19:22:15 +03:00
Yurii
b89f61ed58 chore: bump version 2024-04-06 20:28:41 +03:00
Yurii
ab1566bd45 fix: memory leak on esp32 fixed 2024-04-06 20:28:05 +03:00
Yurii
86734ab622 refactor: update portal (unit system) 2024-04-06 18:25:30 +03:00
Yurii
0a8dd2a076 feat: added support unit systems for pressure and flow rate 2024-04-06 18:19:06 +03:00
Yurii
a7a561622e Merge branch 'unit-system' 2024-04-06 17:38:24 +03:00
Yurii
b0e0f6fd7d feat: added setting to enable/disable polling of min and max temperatures via opentherm 2024-04-06 15:51:49 +03:00
Yurii
53eaa1d7f1 chore: bump version 2024-04-04 21:26:48 +03:00
Yurii
a7d796e0cc refactor: removed unused methods, replaced some methods to native 2024-03-31 22:29:53 +03:00
Yurii
4490b38130 fix: fixed reading exhaust gas temperature 2024-03-31 19:41:33 +03:00
Yurii
0ede2240a2 fix: temperature_unit for climate fixed; temp in vars.parameters.* by default fixed 2024-03-31 07:29:32 +03:00
Yurii
0cff35ee12 feat: update portal for unit systems 2024-03-31 06:32:23 +03:00
Yurii
14aef20234 fix: typo for HA_TEMPERATURE_UNIT 2024-03-31 02:49:36 +03:00
Yurii
560f8fbd51 feat: optimizing with different unit systems 2024-03-31 02:47:20 +03:00
Yurii
946414ad31 Merge branch 'master' into unit-system 2024-03-31 01:02:59 +03:00
Yurii
39a29042e1 fix: set max temp (ID57) as setpoint heating temp 2024-03-31 00:37:18 +03:00
Yurii
f544f01caa feat: polling of exhaust gas temperature (#42) and heating return temperature; added new sensors to HA 2024-03-30 00:04:51 +03:00
Yurii
41cca76bfa chore: update README 2024-03-24 20:38:02 +03:00
Yurii
942bc53043 chore: bump version 2024-03-24 19:28:26 +03:00
Yurii
1bad689b6b fix: revert board_build.ldscript for esp8266, update OpenTherm Library 2024-03-24 19:27:16 +03:00
Yurii
2f4dbcc205 feat: added unit system selection 2024-03-20 02:37:20 +03:00
Yurii
9e3ef7a465 chore: bump version 2024-03-14 13:08:11 +03:00
Yurii
a5f6749101 refactor: added SensorType enum 2024-03-14 13:07:42 +03:00
Yurii
b07dd46f55 refactor: optimization
* names changed: pin => gpio
* ability to change OpenTherm GPIO without rebooting
2024-03-10 04:10:18 +03:00
Yurii
07ab121788 chore: bump OpenTherm Library to master 2024-03-09 00:03:34 +03:00
Yurii
7cbc52a8b0 chore: bump version 2024-03-01 00:26:41 +03:00
Laxilef
e090be380c Merge pull request #49 from blitzu/patch-3
fix: fix typo in settings.html
2024-03-01 00:22:32 +03:00
Laxilef
0bf49d2249 Merge pull request #50 from blitzu/patch-2
fix: fix typo in network.html
2024-03-01 00:22:13 +03:00
Laxilef
a83d94d361 Merge pull request #51 from blitzu/patch-1
fix: fix typo in index.html
2024-03-01 00:21:49 +03:00
Laxilef
358980da4c Merge pull request #48 from blitzu/patch-4
fix: fix typo in upgrade.html
2024-03-01 00:21:13 +03:00
blitzu
f91e39d067 Update upgrade.html 2024-02-29 18:52:31 +02:00
blitzu
1d53f21d46 Update settings.html 2024-02-29 18:52:08 +02:00
blitzu
c225e7c2a8 Update network.html 2024-02-29 18:51:25 +02:00
blitzu
6831c4331f Update index.html 2024-02-29 18:50:36 +02:00
Yurii
8fb62ce8ae fix: set temperature for sensors in manual mode fixed 2024-02-23 03:50:30 +03:00
42 changed files with 4315 additions and 2217 deletions

1
.gitignore vendored
View File

@@ -1,5 +1,6 @@
.pio .pio
.vscode .vscode
build/*.bin build/*.bin
data/**/*.gz
secrets.ini secrets.ini
!.gitkeep !.gitkeep

View File

@@ -2,7 +2,7 @@
![logo](/assets/logo.svg) ![logo](/assets/logo.svg)
<br> <br>
[![GitHub version](https://img.shields.io/github/release/Laxilef/OTGateway.svg)](https://github.com/Laxilef/OTGateway/releases) [![GitHub version](https://img.shields.io/github/release/Laxilef/OTGateway.svg?include_prereleases)](https://github.com/Laxilef/OTGateway/releases)
[![GitHub download](https://img.shields.io/github/downloads/Laxilef/OTGateway/total.svg)](https://github.com/Laxilef/OTGateway/releases/latest) [![GitHub download](https://img.shields.io/github/downloads/Laxilef/OTGateway/total.svg)](https://github.com/Laxilef/OTGateway/releases/latest)
[![License](https://img.shields.io/github/license/Laxilef/OTGateway.svg)](LICENSE.txt) [![License](https://img.shields.io/github/license/Laxilef/OTGateway.svg)](LICENSE.txt)
[![Telegram](https://img.shields.io/badge/Telegram-Channel-33A8E3)](https://t.me/otgateway) [![Telegram](https://img.shields.io/badge/Telegram-Channel-33A8E3)](https://t.me/otgateway)
@@ -16,7 +16,7 @@
- PID - PID
- Equithermic curves - adjusts the temperature based on indoor and outdoor temperatures - Equithermic curves - adjusts the temperature based on indoor and outdoor temperatures
- Hysteresis setting (for accurate maintenance of room temperature) - Hysteresis setting (for accurate maintenance of room temperature)
- Ability to connect an external sensors to monitor outdoor and indoor temperature ([compatible sensors](#compatible-temperature-sensors)) - Ability to connect an external sensors to monitor outdoor and indoor temperature ([compatible sensors](https://github.com/Laxilef/OTGateway/wiki/Compatibility#temperature-sensors))
- Emergency mode. If the Wi-Fi connection is lost or the gateway cannot connect to the MQTT server, the mode will turn on. This mode will automatically maintain the set temperature and prevent your home from freezing. In this mode it is also possible to use equithermal curves (weather-compensated control). - Emergency mode. If the Wi-Fi connection is lost or the gateway cannot connect to the MQTT server, the mode will turn on. This mode will automatically maintain the set temperature and prevent your home from freezing. In this mode it is also possible to use equithermal curves (weather-compensated control).
- Automatic error reset (not with all boilers) - Automatic error reset (not with all boilers)
- Diagnostics: - Diagnostics:
@@ -31,7 +31,6 @@
- The current temperature of the heat carrier (usually the return heat carrier) - The current temperature of the heat carrier (usually the return heat carrier)
- Set heat carrier temperature (depending on the selected mode) - Set heat carrier temperature (depending on the selected mode)
- Current hot water temperature - Current hot water temperature
- Auto tuning of PID and Equitherm parameters *(in development)*
- [Home Assistant](https://www.home-assistant.io/) integration via MQTT. The ability to create any automation for the boiler! - [Home Assistant](https://www.home-assistant.io/) integration via MQTT. The ability to create any automation for the boiler!
![logo](/assets/ha.png) ![logo](/assets/ha.png)
@@ -72,7 +71,7 @@ All available information and instructions can be found in the wiki:
- [ESP32Scheduler](https://github.com/laxilef/ESP32Scheduler) (for ESP32) - [ESP32Scheduler](https://github.com/laxilef/ESP32Scheduler) (for ESP32)
- [ArduinoJson](https://github.com/bblanchon/ArduinoJson) - [ArduinoJson](https://github.com/bblanchon/ArduinoJson)
- [OpenTherm Library](https://github.com/ihormelnyk/opentherm_library) - [OpenTherm Library](https://github.com/ihormelnyk/opentherm_library)
- [ArduinoMqttClient](https://github.com/arduino-libraries/ArduinoMqttClient) / - [ArduinoMqttClient](https://github.com/arduino-libraries/ArduinoMqttClient)
- [ESPTelnet](https://github.com/LennartHennigs/ESPTelnet) - [ESPTelnet](https://github.com/LennartHennigs/ESPTelnet)
- [FileData](https://github.com/GyverLibs/FileData) - [FileData](https://github.com/GyverLibs/FileData)
- [GyverPID](https://github.com/GyverLibs/GyverPID) - [GyverPID](https://github.com/GyverLibs/GyverPID)

0
data/.gitkeep Normal file
View File

View File

@@ -1,230 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>OpenTherm Gateway</title>
<link rel="stylesheet" href="/static/pico.min.css">
<link rel="stylesheet" href="/static/app.css"/>
</head>
<body>
<header class="container">
<nav>
<ul>
<li><a href="/"><div class="logo">OpenTherm Gateway</div></a></li>
</ul>
<ul>
<li><a href="https://github.com/Laxilef/OTGateway/wiki" role="button" class="secondary" target="_blank">Help</a></li>
</ul>
</nav>
</header>
<main class="container">
<article>
<div>
<hgroup>
<h2>Network</h2>
<p></p>
</hgroup>
<div class="main-busy" aria-busy="true"></div>
<table class="main-table hidden">
<tbody>
<tr>
<th scope="row">Hostname:</th>
<td><b class="network-hostname"></b></td>
</tr>
<tr>
<th scope="row">MAC:</th>
<td><b class="network-mac"></b></td>
</tr>
<tr>
<th scope="row">Connected:</th>
<td><input type="radio" class="network-connected" aria-invalid="false" checked disabled /></td>
</tr>
<tr>
<th scope="row">SSID:</th>
<td><b class="network-ssid"></b></td>
</tr>
<tr>
<th scope="row">Signal:</th>
<td><b class="network-signal"></b> %</td>
</tr>
<tr>
<th scope="row">IP:</th>
<td><b class="network-ip"></b></td>
</tr>
<tr>
<th scope="row">Subnet:</th>
<td><b class="network-subnet"></b></td>
</tr>
<tr>
<th scope="row">Gateway:</th>
<td><b class="network-gateway"></b></td>
</tr>
<tr>
<th scope="row">DNS:</th>
<td><b class="network-dns"></b></td>
</tr>
</tbody>
</table>
<div class="grid">
<a href="/network.html" role="button">Network settings</a>
</div>
</div>
</article>
<article>
<div>
<hgroup>
<h2>System</h2>
<p></p>
</hgroup>
<div class="system-busy" aria-busy="true"></div>
<table class="system-table hidden">
<tbody>
<tr>
<th scope="row">Version:</th>
<td><b class="version"></b></td>
</tr>
<tr>
<th scope="row">Build date:</th>
<td><b class="build-date"></b></td>
</tr>
<tr>
<th scope="row">Uptime:</th>
<td><b class="uptime-days"></b> days, <b class="uptime-hours"></b> hours, <b class="uptime-min"></b> min., <b class="uptime-sec"></b> sec.</td>
</tr>
<tr>
<th scope="row">Free memory:</th>
<td><b class="free-heap"></b> of <b class="total-heap"></b> bytes (min: <b class="min-free-heap"></b> bytes)<br>max free block: <b class="max-free-block-heap"></b> bytes (min: <b class="min-max-free-block-heap"></b> bytes)</td>
</tr>
<tr>
<th scope="row">Last reset reason:</th>
<td><b class="reset-reason"></b></td>
</tr>
</tbody>
</table>
<div class="grid">
<a href="/settings.html" role="button">Settings</a>
<a href="/upgrade.html" role="button">Upgrade</a>
<a href="/restart.html" role="button" class="secondary restart">Restart</a>
</div>
</div>
</article>
<article>
<div>
<hgroup>
<h2>States and sensors</h2>
<p>More information and settings can be found in your home assistant after setting up network and MQTT</p>
</hgroup>
<div class="ot-busy" aria-busy="true"></div>
<table class="ot-table hidden">
<tbody>
<tr>
<th scope="row">OpenTherm connected:</th>
<td><input type="radio" class="ot-connected" aria-invalid="false" checked disabled /></td>
</tr>
<tr>
<th scope="row">MQTT connected:</th>
<td><input type="radio" class="mqtt-connected" aria-invalid="false" checked disabled /></td>
</tr>
<tr>
<th scope="row">Emergency:</th>
<td><input type="radio" class="ot-emergency" aria-invalid="false" checked disabled /></td>
</tr>
<tr>
<th scope="row">Heating:</th>
<td><input type="radio" class="ot-heating" aria-invalid="false" checked disabled /></td>
</tr>
<tr>
<th scope="row">DHW:</th>
<td><input type="radio" class="ot-dhw" aria-invalid="false" checked disabled /></td>
</tr>
<tr>
<th scope="row">Flame:</th>
<td><input type="radio" class="ot-flame" aria-invalid="false" checked disabled /></td>
</tr>
<tr>
<th scope="row">Fault:</th>
<td><input type="radio" class="ot-fault" aria-invalid="false" checked disabled /></td>
</tr>
<tr>
<th scope="row">Diagnostic:</th>
<td><input type="radio" class="ot-diagnostic" aria-invalid="false" checked disabled /></td>
</tr>
<tr>
<th scope="row">External pump:</th>
<td><input type="radio" class="ot-external-pump" aria-invalid="false" checked disabled /></td>
</tr>
<tr>
<th scope="row">Modulation:</th>
<td><b class="ot-modulation"></b> %</td>
</tr>
<tr>
<th scope="row">Pressure:</th>
<td><b class="ot-pressure"></b> bar</td>
</tr>
<tr>
<th scope="row">DHW flow rate:</th>
<td><b class="ot-dhw-flow-rate"></b> l/min</td>
</tr>
<tr>
<th scope="row">Fault code:</th>
<td><b class="ot-fault-code"></b></td>
</tr>
<tr>
<th scope="row">Indoor temp:</th>
<td><b class="indoor-temp"></b> C</td>
</tr>
<tr>
<th scope="row">Outdoor temp:</th>
<td><b class="outdoor-temp"></b> C</td>
</tr>
<tr>
<th scope="row">Heating temp:</th>
<td><b class="heating-temp"></b> C</td>
</tr>
<tr>
<th scope="row">Heating setpoint temp:</th>
<td><b class="heating-setpoint-temp"></b> C</td>
</tr>
<tr>
<th scope="row">DHW temp:</th>
<td><b class="dhw-temp"></b> C</td>
</tr>
</tbody>
</table>
</div>
</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">License</a>
<a href="https://github.com/Laxilef/OTGateway/blob/master/" target="_blank" class="secondary">Source code</a>
<a href="https://github.com/Laxilef/OTGateway/wiki" target="_blank" class="secondary">Help</a>
<a href="https://github.com/Laxilef/OTGateway/issue" target="_blank" class="secondary">Issue & questions</a>
<a href="https://github.com/Laxilef/OTGateway/releases" target="_blank" class="secondary">Releases</a>
</small>
</footer>
<script src="/static/app.js"></script>
<script>
window.onload = async function () {
setTimeout(async function onLoadPage() {
await loadNetworkStatus();
await loadVars();
setTimeout(onLoadPage, 10000);
}, 1000);
};
</script>
</body>
</html>

View File

@@ -1,357 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Settings - OpenTherm Gateway</title>
<link rel="stylesheet" href="/static/pico.min.css">
<link rel="stylesheet" href="/static/app.css" />
</head>
<body>
<header class="container">
<nav>
<ul>
<li><a href="/"><div class="logo">OpenTherm Gateway</div></a></li>
</ul>
<ul>
<li><a href="https://github.com/Laxilef/OTGateway/wiki" role="button" class="secondary" target="_blank">Help</a></li>
</ul>
</nav>
</header>
<main class="container">
<article>
<div>
<hgroup>
<h2>Portal settings</h2>
<p></p>
</hgroup>
<div id="portal-settings-busy" aria-busy="true"></div>
<form action="/api/settings" id="portal-settings" class="hidden">
<div class="grid">
<label for="portal-login">
Login
<input type="text" class="portal-login" name="portal[login]" maxlength="12" required>
</label>
<label for="portal-password">
Password
<input type="password" class="portal-password" name="portal[password]" maxlength="32" required>
</label>
</div>
<label for="portal-use-auth">
<input type="checkbox" class="portal-use-auth" name="portal[useAuth]" value="true">
Use auth
</label>
<br>
<button type="submit">Save</button>
</form>
</div>
</article>
<article>
<div>
<hgroup>
<h2>OpenTherm settings</h2>
<p></p>
</hgroup>
<div id="opentherm-settings-busy" aria-busy="true"></div>
<form action="/api/settings" id="opentherm-settings" class="hidden">
<div class="grid">
<label for="opentherm-in-pin">
In GPIO
<input type="number" inputmode="numeric" class="opentherm-in-pin" name="opentherm[inPin]" min="0" max="99" step="1" required>
</label>
<label for="opentherm-in-pin">
Out GPIO
<input type="number" inputmode="numeric" class="opentherm-out-pin" name="opentherm[outPin]" min="0" max="99" step="1" required>
</label>
<label for="opentherm-member-id-code">
Master MemberID code
<input type="number" inputmode="numeric" class="opentherm-member-id-code" name="opentherm[memberIdCode]" min="0" max="65535" step="1" required>
</label>
</div>
<fieldset>
<mark>After changing GPIO, the ESP must be restarted for the changes to take effect.</mark>
</fieldset>
<fieldset>
<legend>Options</legend>
<label for="opentherm-dhw-present">
<input type="checkbox" class="opentherm-dhw-present" name="opentherm[dhwPresent]" value="true">
DHW present
</label>
<label for="opentherm-sw-mode">
<input type="checkbox" class="opentherm-sw-mode" name="opentherm[summerWinterMode]" value="true">
Summer/winter mode
</label>
<label for="opentherm-heating-ch2-enabled">
<input type="checkbox" class="opentherm-heating-ch2-enabled" name="opentherm[heatingCh2Enabled]" value="true">
Heating CH2 always enabled
</label>
<label for="opentherm-heating-ch1-to-ch2">
<input type="checkbox" class="opentherm-heating-ch1-to-ch2" name="opentherm[heatingCh1ToCh2]" value="true">
Duplicate heating CH1 to CH2
</label>
<label for="opentherm-dhw-to-ch2">
<input type="checkbox" class="opentherm-dhw-to-ch2" name="opentherm[dhwToCh2]" value="true">
Duplicate DHW to CH2
</label>
<label for="opentherm-dhw-blocking">
<input type="checkbox" class="opentherm-dhw-blocking" name="opentherm[dhwBlocking]" value="true">
DHW blocking
</label>
<label for="opentherm-sync-modulation-with-heating">
<input type="checkbox" class="opentherm-sync-modulation-with-heating" name="opentherm[modulationSyncWithHeating]" value="true">
Sync modulation with heating
</label>
</fieldset>
<button type="submit">Save</button>
</form>
</div>
</article>
<article>
<div>
<hgroup>
<h2>MQTT settings</h2>
<p></p>
</hgroup>
<div id="mqtt-settings-busy" aria-busy="true"></div>
<form action="/api/settings" id="mqtt-settings" class="hidden">
<div class="grid">
<label for="mqtt-server">
Server
<input type="text" class="mqtt-server" name="mqtt[server]" maxlength="80" required>
</label>
<label for="mqtt-port">
Port
<input type="number" inputmode="numeric" class="mqtt-port" name="mqtt[port]" min="1" max="65535" step="1" required>
</label>
</div>
<div class="grid">
<label for="mqtt-user">
User
<input type="text" class="mqtt-user" name="mqtt[user]" maxlength="32" required>
</label>
<label for="mqtt-password">
Password
<input type="password" class="mqtt-password" name="mqtt[password]" maxlength="32">
</label>
</div>
<div class="grid">
<label for="mqtt-prefix">
Prefix
<input type="text" class="mqtt-prefix" name="mqtt[prefix]" maxlength="32" required>
</label>
<label for="mqtt-interval">
Publish interval <small>(sec)</small>
<input type="number" inputmode="numeric" class="mqtt-interval" name="mqtt[interval]" min="3" max="60" step="1" required>
</label>
</div>
<button type="submit">Save</button>
</form>
</div>
</article>
<article>
<div>
<hgroup>
<h2>Outdoor sensor settings</h2>
<p></p>
</hgroup>
<div id="outdoor-sensor-settings-busy" aria-busy="true"></div>
<form action="/api/settings" id="outdoor-sensor-settings" class="hidden">
<fieldset>
<legend>Source type</legend>
<label>
<input type="radio" class="outdoor-sensor-type" name="sensors[outdoor][type]" value="0" />
From boiler via OpenTherm
</label>
<label>
<input type="radio" class="outdoor-sensor-type" name="sensors[outdoor][type]" value="1" />
Manual via MQTT/API
</label>
<label>
<input type="radio" class="outdoor-sensor-type" name="sensors[outdoor][type]" value="2" />
External (DS18B20)
</label>
</fieldset>
<label for="outdoor-sensor-pin">
GPIO
<input type="number" inputmode="numeric" class="outdoor-sensor-pin" name="sensors[outdoor][pin]" min="0" max="99" step="1" required>
</label>
<label for="outdoor-sensor-offset">
Temp offset (calibration)
<input type="number" inputmode="numeric" class="outdoor-sensor-offset" name="sensors[outdoor][offset]" min="-20" max="20" step="0.01" required>
</label>
<button type="submit">Save</button>
</form>
</div>
</article>
<article>
<div>
<hgroup>
<h2>Indoor sensor settings</h2>
<p></p>
</hgroup>
<div id="indoor-sensor-settings-busy" aria-busy="true"></div>
<form action="/api/settings" id="indoor-sensor-settings" class="hidden">
<fieldset>
<legend>Source type</legend>
<label>
<input type="radio" class="indoor-sensor-type" name="sensors[indoor][type]" value="1" />
Manual via MQTT/API
</label>
<label>
<input type="radio" class="indoor-sensor-type" name="sensors[indoor][type]" value="2" />
External (DS18B20)
</label>
<label>
<input type="radio" class="indoor-sensor-type" name="sensors[indoor][type]" value="3" />
BLE device <i>(ONLY for some ESP32 which support BLE)</i>
</label>
</fieldset>
<label for="indoor-sensor-pin">
GPIO
<input type="number" inputmode="numeric" class="indoor-sensor-pin" name="sensors[indoor][pin]" min="0" max="99" step="1" required>
</label>
<div class="grid">
<label for="indoor-sensor-offset">
Temp offset (calibration)
<input type="number" inputmode="numeric" class="indoor-sensor-offset" name="sensors[indoor][offset]" min="-20" max="20" step="0.01" required>
</label>
<label for="indoor-sensor-ble-addresss">
BLE addresss
<input type="text" class="indoor-sensor-ble-addresss" name="sensors[indoor][bleAddresss]" pattern="([A-Fa-f0-9]{2}:){5}[A-Fa-f0-9]{2}">
<small>ONLY for some ESP32 which support BLE</small>
</label>
</div>
<button type="submit">Save</button>
</form>
</div>
</article>
<article>
<div>
<hgroup>
<h2>External pump settings</h2>
<p></p>
</hgroup>
<div id="extpump-settings-busy" aria-busy="true"></div>
<form action="/api/settings" id="extpump-settings" class="hidden">
<label for="extpump-use">
<input type="checkbox" class="extpump-use" name="externalPump[use]" value="true">
Use external pump
</label>
<br>
<div class="grid">
<label for="extpump-pin">
Relay GPIO
<input type="number" inputmode="numeric" class="extpump-pin" name="externalPump[pin]" min="0" max="99" step="1" required>
</label>
<label for="extpump-pc-time">
Post circulation time <small>(min)</small>
<input type="number" inputmode="numeric" class="extpump-pc-time" name="externalPump[postCirculationTime]" min="1" max="120" step="1" required>
</label>
</div>
<div class="grid">
<label for="extpump-as-interval">
Anti stuck interval <small>(days)</small>
<input type="number" inputmode="numeric" class="extpump-as-interval" name="externalPump[antiStuckInterval]" min="1" max="366" step="1" required>
</label>
<label for="extpump-as-time">
Anti stuck time <small>(min)</small>
<input type="number" inputmode="numeric" class="extpump-as-time" name="externalPump[antiStuckTime]" min="1" max="20" step="1" required>
</label>
</div>
<button type="submit">Save</button>
</form>
</div>
</article>
<article>
<div>
<hgroup>
<h2>System settings</h2>
<p></p>
</hgroup>
<div id="system-settings-busy" aria-busy="true"></div>
<form action="/api/settings" id="system-settings" class="hidden">
<fieldset>
<label for="system-debug">
<input type="checkbox" class="system-debug" name="system[debug]" value="true">
Debug mode
</label>
<label for="system-use-serial">
<input type="checkbox" class="system-use-serial" name="system[useSerial]" value="true">
Enable serial port
</label>
<label for="system-use-telnet">
<input type="checkbox" class="system-use-telnet" name="system[useTelnet]" value="true">
Enable telnet
</label>
</fieldset>
<fieldset>
<mark>After changing this settings, the ESP must be restarted for the changes to take effect.</mark>
</fieldset>
<button type="submit">Save</button>
</form>
</div>
</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">License</a>
<a href="https://github.com/Laxilef/OTGateway/blob/master/" target="_blank" class="secondary">Source code</a>
<a href="https://github.com/Laxilef/OTGateway/wiki" target="_blank" class="secondary">Help</a>
<a href="https://github.com/Laxilef/OTGateway/issue" target="_blank" class="secondary">Issue & questions</a>
<a href="https://github.com/Laxilef/OTGateway/releases" target="_blank" class="secondary">Releases</a>
</small>
</footer>
<script src="/static/app.js"></script>
<script>
window.onload = async function () {
await loadSettings();
setupForm('#portal-settings');
setupForm('#opentherm-settings');
setupForm('#mqtt-settings');
setupForm('#outdoor-sensor-settings');
setupForm('#indoor-sensor-settings');
setupForm('#extpump-settings');
setupForm('#system-settings');
};
</script>
</body>
</html>

0
data/static/.gitkeep Normal file
View File

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -8,6 +8,7 @@ public:
typedef std::function<void(unsigned long, unsigned long, OpenThermResponseStatus, byte)> AfterSendRequestCallback; typedef std::function<void(unsigned long, unsigned long, OpenThermResponseStatus, byte)> AfterSendRequestCallback;
CustomOpenTherm(int inPin = 4, int outPin = 5, bool isSlave = false) : OpenTherm(inPin, outPin, isSlave) {} CustomOpenTherm(int inPin = 4, int outPin = 5, bool isSlave = false) : OpenTherm(inPin, outPin, isSlave) {}
~CustomOpenTherm() {}
CustomOpenTherm* setYieldCallback(YieldCallback callback = nullptr) { CustomOpenTherm* setYieldCallback(YieldCallback callback = nullptr) {
this->yieldCallback = callback; this->yieldCallback = callback;
@@ -46,7 +47,7 @@ public:
unsigned long _response; unsigned long _response;
OpenThermResponseStatus _responseStatus = OpenThermResponseStatus::NONE; OpenThermResponseStatus _responseStatus = OpenThermResponseStatus::NONE;
if (!this->sendRequestAync(request)) { if (!this->sendRequestAsync(request)) {
_response = 0; _response = 0;
} else { } else {
@@ -88,7 +89,7 @@ public:
| (dhwBlocking << 6); | (dhwBlocking << 6);
data <<= 8; data <<= 8;
return this->sendRequest(this->buildRequest( return this->sendRequest(buildRequest(
OpenThermMessageType::READ_DATA, OpenThermMessageType::READ_DATA,
OpenThermMessageID::Status, OpenThermMessageID::Status,
data data
@@ -96,30 +97,60 @@ public:
} }
bool setHeatingCh1Temp(float temperature) { bool setHeatingCh1Temp(float temperature) {
unsigned long response = this->sendRequest(this->buildRequest( unsigned long response = this->sendRequest(buildRequest(
OpenThermMessageType::WRITE_DATA, OpenThermMessageType::WRITE_DATA,
OpenThermMessageID::TSet, OpenThermMessageID::TSet,
this->temperatureToData(temperature) temperatureToData(temperature)
)); ));
return isValidResponse(response); return isValidResponse(response);
} }
bool setHeatingCh2Temp(float temperature) { bool setHeatingCh2Temp(float temperature) {
unsigned long response = this->sendRequest(this->buildRequest( unsigned long response = this->sendRequest(buildRequest(
OpenThermMessageType::WRITE_DATA, OpenThermMessageType::WRITE_DATA,
OpenThermMessageID::TsetCH2, OpenThermMessageID::TsetCH2,
this->temperatureToData(temperature) temperatureToData(temperature)
)); ));
return isValidResponse(response); return isValidResponse(response);
} }
bool setDhwTemp(float temperature) { bool setDhwTemp(float temperature) {
unsigned long response = this->sendRequest(this->buildRequest( unsigned long response = this->sendRequest(buildRequest(
OpenThermMessageType::WRITE_DATA, OpenThermMessageType::WRITE_DATA,
OpenThermMessageID::TdhwSet, OpenThermMessageID::TdhwSet,
this->temperatureToData(temperature) 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); return isValidResponse(response);
@@ -128,9 +159,9 @@ public:
bool sendBoilerReset() { bool sendBoilerReset() {
unsigned int data = 1; unsigned int data = 1;
data <<= 8; data <<= 8;
unsigned long response = this->sendRequest(this->buildRequest( unsigned long response = this->sendRequest(buildRequest(
OpenThermMessageType::WRITE_DATA, OpenThermMessageType::WRITE_DATA,
OpenThermMessageID::Command, OpenThermMessageID::RemoteRequest,
data data
)); ));
@@ -140,9 +171,9 @@ public:
bool sendServiceReset() { bool sendServiceReset() {
unsigned int data = 10; unsigned int data = 10;
data <<= 8; data <<= 8;
unsigned long response = this->sendRequest(this->buildRequest( unsigned long response = this->sendRequest(buildRequest(
OpenThermMessageType::WRITE_DATA, OpenThermMessageType::WRITE_DATA,
OpenThermMessageID::Command, OpenThermMessageID::RemoteRequest,
data data
)); ));
@@ -152,9 +183,9 @@ public:
bool sendWaterFilling() { bool sendWaterFilling() {
unsigned int data = 2; unsigned int data = 2;
data <<= 8; data <<= 8;
unsigned long response = this->sendRequest(this->buildRequest( unsigned long response = this->sendRequest(buildRequest(
OpenThermMessageType::WRITE_DATA, OpenThermMessageType::WRITE_DATA,
OpenThermMessageID::Command, OpenThermMessageID::RemoteRequest,
data data
)); ));
@@ -162,24 +193,13 @@ public:
} }
// converters // converters
float fromF88(unsigned long response) { template <class T>
const byte valueLB = response & 0xFF; static unsigned int toFloat(const T val) {
const byte valueHB = (response >> 8) & 0xFF;
float value = (int8_t)valueHB;
return value + (float)valueLB / 256.0;
}
template <class T> unsigned int toF88(T val) {
return (unsigned int)(val * 256); return (unsigned int)(val * 256);
} }
int16_t fromS16(unsigned long response) { static short getInt(const unsigned long response) {
const byte valueLB = response & 0xFF; return response & 0xffff;
const byte valueHB = (response >> 8) & 0xFF;
int16_t value = valueHB;
return ((value << 8) + valueLB);
} }
protected: protected:

View File

@@ -33,6 +33,8 @@ const char HA_AVAILABILITY_MODE[] PROGMEM = "availability_mode";
const char HA_TOPIC[] PROGMEM = "topic"; const char HA_TOPIC[] PROGMEM = "topic";
const char HA_DEVICE_CLASS[] PROGMEM = "device_class"; const char HA_DEVICE_CLASS[] PROGMEM = "device_class";
const char HA_UNIT_OF_MEASUREMENT[] PROGMEM = "unit_of_measurement"; const char HA_UNIT_OF_MEASUREMENT[] PROGMEM = "unit_of_measurement";
const char HA_UNIT_OF_MEASUREMENT_C[] PROGMEM = "°C";
const char HA_UNIT_OF_MEASUREMENT_F[] PROGMEM = "°F";
const char HA_ICON[] PROGMEM = "icon"; const char HA_ICON[] PROGMEM = "icon";
const char HA_MIN[] PROGMEM = "min"; const char HA_MIN[] PROGMEM = "min";
const char HA_MAX[] PROGMEM = "max"; const char HA_MAX[] PROGMEM = "max";
@@ -50,6 +52,7 @@ const char HA_TEMPERATURE_COMMAND_TOPIC[] PROGMEM = "temperature_command_t
const char HA_TEMPERATURE_COMMAND_TEMPLATE[] PROGMEM = "temperature_command_template"; const char HA_TEMPERATURE_COMMAND_TEMPLATE[] PROGMEM = "temperature_command_template";
const char HA_TEMPERATURE_STATE_TOPIC[] PROGMEM = "temperature_state_topic"; const char HA_TEMPERATURE_STATE_TOPIC[] PROGMEM = "temperature_state_topic";
const char HA_TEMPERATURE_STATE_TEMPLATE[] PROGMEM = "temperature_state_template"; const char HA_TEMPERATURE_STATE_TEMPLATE[] PROGMEM = "temperature_state_template";
const char HA_TEMPERATURE_UNIT[] PROGMEM = "temperature_unit";
const char HA_MODE_COMMAND_TOPIC[] PROGMEM = "mode_command_topic"; const char HA_MODE_COMMAND_TOPIC[] PROGMEM = "mode_command_topic";
const char HA_MODE_COMMAND_TEMPLATE[] PROGMEM = "mode_command_template"; const char HA_MODE_COMMAND_TEMPLATE[] PROGMEM = "mode_command_template";
const char HA_MODE_STATE_TOPIC[] PROGMEM = "mode_state_topic"; const char HA_MODE_STATE_TOPIC[] PROGMEM = "mode_state_topic";

View File

@@ -1,35 +1,35 @@
#include "NetworkConnection.h" #include "NetworkConnection.h"
using namespace Network; using namespace NetworkUtils;
void Connection::setup(bool useDhcp) { void NetworkConnection::setup(bool useDhcp) {
setUseDhcp(useDhcp); setUseDhcp(useDhcp);
#if defined(ARDUINO_ARCH_ESP8266) #if defined(ARDUINO_ARCH_ESP8266)
wifi_set_event_handler_cb(Connection::onEvent); wifi_set_event_handler_cb(NetworkConnection::onEvent);
#elif defined(ARDUINO_ARCH_ESP32) #elif defined(ARDUINO_ARCH_ESP32)
WiFi.onEvent(Connection::onEvent); WiFi.onEvent(NetworkConnection::onEvent);
#endif #endif
} }
void Connection::reset() { void NetworkConnection::reset() {
status = Status::NONE; status = Status::NONE;
disconnectReason = DisconnectReason::NONE; disconnectReason = DisconnectReason::NONE;
} }
void Connection::setUseDhcp(bool value) { void NetworkConnection::setUseDhcp(bool value) {
useDhcp = value; useDhcp = value;
} }
Connection::Status Connection::getStatus() { NetworkConnection::Status NetworkConnection::getStatus() {
return status; return status;
} }
Connection::DisconnectReason Connection::getDisconnectReason() { NetworkConnection::DisconnectReason NetworkConnection::getDisconnectReason() {
return disconnectReason; return disconnectReason;
} }
#if defined(ARDUINO_ARCH_ESP8266) #if defined(ARDUINO_ARCH_ESP8266)
void Connection::onEvent(System_Event_t *event) { void NetworkConnection::onEvent(System_Event_t *event) {
switch (event->event) { switch (event->event) {
case EVENT_STAMODE_CONNECTED: case EVENT_STAMODE_CONNECTED:
status = useDhcp ? Status::CONNECTING : Status::CONNECTED; status = useDhcp ? Status::CONNECTING : Status::CONNECTED;
@@ -75,7 +75,7 @@ void Connection::onEvent(System_Event_t *event) {
} }
} }
#elif defined(ARDUINO_ARCH_ESP32) #elif defined(ARDUINO_ARCH_ESP32)
void Connection::onEvent(WiFiEvent_t event, WiFiEventInfo_t info) { void NetworkConnection::onEvent(WiFiEvent_t event, WiFiEventInfo_t info) {
switch (event) { switch (event) {
case ARDUINO_EVENT_WIFI_STA_CONNECTED: case ARDUINO_EVENT_WIFI_STA_CONNECTED:
status = useDhcp ? Status::CONNECTING : Status::CONNECTED; status = useDhcp ? Status::CONNECTING : Status::CONNECTED;
@@ -106,7 +106,7 @@ void Connection::onEvent(WiFiEvent_t event, WiFiEventInfo_t info) {
} }
#endif #endif
Connection::DisconnectReason Connection::convertDisconnectReason(uint8_t reason) { NetworkConnection::DisconnectReason NetworkConnection::convertDisconnectReason(uint8_t reason) {
switch (reason) { switch (reason) {
#if defined(ARDUINO_ARCH_ESP8266) #if defined(ARDUINO_ARCH_ESP8266)
case REASON_BEACON_TIMEOUT: case REASON_BEACON_TIMEOUT:
@@ -145,6 +145,6 @@ Connection::DisconnectReason Connection::convertDisconnectReason(uint8_t reason)
} }
} }
bool Connection::useDhcp = false; bool NetworkConnection::useDhcp = false;
Connection::Status Connection::status = Status::NONE; NetworkConnection::Status NetworkConnection::status = Status::NONE;
Connection::DisconnectReason Connection::disconnectReason = DisconnectReason::NONE; NetworkConnection::DisconnectReason NetworkConnection::disconnectReason = DisconnectReason::NONE;

View File

@@ -5,8 +5,8 @@
#include <WiFi.h> #include <WiFi.h>
#endif #endif
namespace Network { namespace NetworkUtils {
struct Connection { struct NetworkConnection {
enum class Status { enum class Status {
CONNECTED, CONNECTED,
CONNECTING, CONNECTING,

View File

@@ -7,35 +7,35 @@
#endif #endif
#include <NetworkConnection.h> #include <NetworkConnection.h>
namespace Network { namespace NetworkUtils {
class Manager { class NetworkMgr {
public: public:
typedef std::function<void()> YieldCallback; typedef std::function<void()> YieldCallback;
typedef std::function<void(unsigned int)> DelayCallback; typedef std::function<void(unsigned int)> DelayCallback;
Manager() { NetworkMgr() {
Connection::setup(this->useDhcp); NetworkConnection::setup(this->useDhcp);
this->resetWifi(); this->resetWifi();
} }
Manager* setYieldCallback(YieldCallback callback = nullptr) { NetworkMgr* setYieldCallback(YieldCallback callback = nullptr) {
this->yieldCallback = callback; this->yieldCallback = callback;
return this; return this;
} }
Manager* setDelayCallback(DelayCallback callback = nullptr) { NetworkMgr* setDelayCallback(DelayCallback callback = nullptr) {
this->delayCallback = callback; this->delayCallback = callback;
return this; return this;
} }
Manager* setHostname(const char* value) { NetworkMgr* setHostname(const char* value) {
this->hostname = value; this->hostname = value;
return this; return this;
} }
Manager* setApCredentials(const char* ssid, const char* password = nullptr, byte channel = 0) { NetworkMgr* setApCredentials(const char* ssid, const char* password = nullptr, byte channel = 0) {
this->apName = ssid; this->apName = ssid;
this->apPassword = password; this->apPassword = password;
this->apChannel = channel; this->apChannel = channel;
@@ -43,7 +43,7 @@ namespace Network {
return this; return this;
} }
Manager* setStaCredentials(const char* ssid = nullptr, const char* password = nullptr, byte channel = 0) { NetworkMgr* setStaCredentials(const char* ssid = nullptr, const char* password = nullptr, byte channel = 0) {
this->staSsid = ssid; this->staSsid = ssid;
this->staPassword = password; this->staPassword = password;
this->staChannel = channel; this->staChannel = channel;
@@ -51,14 +51,14 @@ namespace Network {
return this; return this;
} }
Manager* setUseDhcp(bool value) { NetworkMgr* setUseDhcp(bool value) {
this->useDhcp = value; this->useDhcp = value;
Connection::setup(this->useDhcp); NetworkConnection::setup(this->useDhcp);
return this; return this;
} }
Manager* setStaticConfig(const char* ip, const char* gateway, const char* subnet, const char* dns) { NetworkMgr* setStaticConfig(const char* ip, const char* gateway, const char* subnet, const char* dns) {
this->staticIp.fromString(ip); this->staticIp.fromString(ip);
this->staticGateway.fromString(gateway); this->staticGateway.fromString(gateway);
this->staticSubnet.fromString(subnet); this->staticSubnet.fromString(subnet);
@@ -67,7 +67,7 @@ namespace Network {
return this; return this;
} }
Manager* setStaticConfig(IPAddress &ip, IPAddress &gateway, IPAddress &subnet, IPAddress &dns) { NetworkMgr* setStaticConfig(IPAddress& ip, IPAddress& gateway, IPAddress& subnet, IPAddress& dns) {
this->staticIp = ip; this->staticIp = ip;
this->staticGateway = gateway; this->staticGateway = gateway;
this->staticSubnet = subnet; this->staticSubnet = subnet;
@@ -81,11 +81,11 @@ namespace Network {
} }
bool isConnected() { bool isConnected() {
return this->isStaEnabled() && Connection::getStatus() == Connection::Status::CONNECTED; return this->isStaEnabled() && NetworkConnection::getStatus() == NetworkConnection::Status::CONNECTED;
} }
bool isConnecting() { bool isConnecting() {
return this->isStaEnabled() && Connection::getStatus() == Connection::Status::CONNECTING; return this->isStaEnabled() && NetworkConnection::getStatus() == NetworkConnection::Status::CONNECTING;
} }
bool isStaEnabled() { bool isStaEnabled() {
@@ -147,16 +147,19 @@ namespace Network {
bool resetWifi() { bool resetWifi() {
// set policy manual for work 13 ch // set policy manual for work 13 ch
{ {
wifi_country_t country = {"CN", 1, 13, WIFI_COUNTRY_POLICY_MANUAL};
#ifdef ARDUINO_ARCH_ESP8266 #ifdef ARDUINO_ARCH_ESP8266
wifi_country_t country = {"CN", 1, 13, WIFI_COUNTRY_POLICY_AUTO};
wifi_set_country(&country); wifi_set_country(&country);
#elif defined(ARDUINO_ARCH_ESP32) #elif defined(ARDUINO_ARCH_ESP32)
const wifi_country_t country = {"CN", 1, 13, CONFIG_ESP32_PHY_MAX_WIFI_TX_POWER, WIFI_COUNTRY_POLICY_AUTO};
esp_wifi_set_country(&country); esp_wifi_set_country(&country);
#endif #endif
} }
WiFi.persistent(false); WiFi.persistent(false);
#if !defined(ESP_ARDUINO_VERSION_MAJOR) || ESP_ARDUINO_VERSION_MAJOR < 3
WiFi.setAutoConnect(false); WiFi.setAutoConnect(false);
#endif
WiFi.setAutoReconnect(false); WiFi.setAutoReconnect(false);
#ifdef ARDUINO_ARCH_ESP8266 #ifdef ARDUINO_ARCH_ESP8266
@@ -181,7 +184,12 @@ namespace Network {
wifi_station_dhcpc_set_maxtry(5); wifi_station_dhcpc_set_maxtry(5);
#endif #endif
#if defined(ARDUINO_ARCH_ESP32) && ESP_ARDUINO_VERSION_MAJOR < 3
// Nothing. Because memory leaks when turn off WiFi on ESP32 SDK < 3.0.0
return true;
#else
return WiFi.mode(WIFI_OFF); return WiFi.mode(WIFI_OFF);
#endif
} }
void reconnect() { void reconnect() {
@@ -195,6 +203,7 @@ namespace Network {
if (force && !this->isApEnabled()) { if (force && !this->isApEnabled()) {
this->resetWifi(); this->resetWifi();
NetworkConnection::reset();
} else { } else {
/*#ifdef ARDUINO_ARCH_ESP8266 /*#ifdef ARDUINO_ARCH_ESP8266
@@ -210,7 +219,7 @@ namespace Network {
return false; return false;
} }
this->delayCallback(200); this->delayCallback(250);
#ifdef ARDUINO_ARCH_ESP32 #ifdef ARDUINO_ARCH_ESP32
if (this->setWifiHostname(this->hostname)) { if (this->setWifiHostname(this->hostname)) {
@@ -225,7 +234,7 @@ namespace Network {
return false; return false;
} }
this->delayCallback(200); this->delayCallback(250);
#ifdef ARDUINO_ARCH_ESP8266 #ifdef ARDUINO_ARCH_ESP8266
if (this->setWifiHostname(this->hostname)) { if (this->setWifiHostname(this->hostname)) {
@@ -235,7 +244,7 @@ namespace Network {
Log.serrorln(FPSTR(L_NETWORK), F("Set hostname '%s': fail"), this->hostname); Log.serrorln(FPSTR(L_NETWORK), F("Set hostname '%s': fail"), this->hostname);
} }
this->delayCallback(200); this->delayCallback(250);
#endif #endif
if (!this->useDhcp) { if (!this->useDhcp) {
@@ -248,9 +257,9 @@ namespace Network {
while (millis() - beginConnectionTime < timeout) { while (millis() - beginConnectionTime < timeout) {
this->delayCallback(100); this->delayCallback(100);
Connection::Status status = Connection::getStatus(); NetworkConnection::Status status = NetworkConnection::getStatus();
if (status != Connection::Status::CONNECTING && status != Connection::Status::NONE) { if (status != NetworkConnection::Status::CONNECTING && status != NetworkConnection::Status::NONE) {
return status == Connection::Status::CONNECTED; return status == NetworkConnection::Status::CONNECTED;
} }
} }
@@ -258,13 +267,22 @@ namespace Network {
} }
void loop() { void loop() {
if (this->isConnected() && !this->hasStaCredentials()) { if (this->reconnectFlag) {
this->delayCallback(5000);
Log.sinfoln(FPSTR(L_NETWORK), F("Reconnecting..."));
this->reconnectFlag = false;
this->resetWifi();
NetworkConnection::reset();
this->delayCallback(1000);
} else if (this->isConnected() && !this->hasStaCredentials()) {
Log.sinfoln(FPSTR(L_NETWORK), F("Reset")); Log.sinfoln(FPSTR(L_NETWORK), F("Reset"));
this->resetWifi(); this->resetWifi();
Connection::reset(); NetworkConnection::reset();
this->delayCallback(200); this->delayCallback(1000);
} else if (this->isConnected() && !this->reconnectFlag) { } else if (this->isConnected()) {
if (!this->connected) { if (!this->connected) {
this->connectedTime = millis(); this->connectedTime = millis();
this->connected = true; this->connected = true;
@@ -300,7 +318,7 @@ namespace Network {
Log.sinfoln( Log.sinfoln(
FPSTR(L_NETWORK), FPSTR(L_NETWORK),
F("Disconnected, reason: %d, uptime: %lu s."), F("Disconnected, reason: %d, uptime: %lu s."),
Connection::getDisconnectReason(), NetworkConnection::getDisconnectReason(),
(millis() - this->connectedTime) / 1000 (millis() - this->connectedTime) / 1000
); );
} }
@@ -322,16 +340,15 @@ namespace Network {
} else if (this->isConnecting() && millis() - this->prevReconnectingTime > this->resetConnectionTimeout) { } else if (this->isConnecting() && millis() - this->prevReconnectingTime > this->resetConnectionTimeout) {
Log.swarningln(FPSTR(L_NETWORK), F("Connection timeout, reset wifi...")); Log.swarningln(FPSTR(L_NETWORK), F("Connection timeout, reset wifi..."));
this->resetWifi(); this->resetWifi();
Connection::reset(); NetworkConnection::reset();
this->delayCallback(200); this->delayCallback(250);
} else if (!this->isConnecting() && this->hasStaCredentials() && (!this->prevReconnectingTime || millis() - this->prevReconnectingTime > this->reconnectInterval)) { } else if (!this->isConnecting() && this->hasStaCredentials() && (!this->prevReconnectingTime || millis() - this->prevReconnectingTime > this->reconnectInterval)) {
Log.sinfoln(FPSTR(L_NETWORK), F("Try connect...")); Log.sinfoln(FPSTR(L_NETWORK), F("Try connect..."));
this->reconnectFlag = false; NetworkConnection::reset();
Connection::reset();
if (!this->connect(true, this->connectionTimeout)) { if (!this->connect(true, this->connectionTimeout)) {
Log.straceln(FPSTR(L_NETWORK), F("Connection failed. Status: %d, reason: %d"), Connection::getStatus(), Connection::getDisconnectReason()); Log.straceln(FPSTR(L_NETWORK), F("Connection failed. Status: %d, reason: %d"), NetworkConnection::getStatus(), NetworkConnection::getDisconnectReason());
} }
this->prevReconnectingTime = millis(); this->prevReconnectingTime = millis();

View File

@@ -1,4 +1,7 @@
#include <FS.h> #include <FS.h>
#include <detail/mimetable.h>
using namespace mime;
class StaticPage : public RequestHandler { class StaticPage : public RequestHandler {
public: public:
@@ -61,6 +64,14 @@ public:
} }
#endif #endif
if (!this->path.endsWith(FPSTR(mimeTable[gz].endsWith)) && !this->fs->exists(path)) {
String pathWithGz = this->path + FPSTR(mimeTable[gz].endsWith);
if (this->fs->exists(pathWithGz)) {
this->path += FPSTR(mimeTable[gz].endsWith);
}
}
File file = this->fs->open(this->path, "r"); File file = this->fs->open(this->path, "r");
if (!file) { if (!file) {
return false; return false;
@@ -93,6 +104,6 @@ protected:
BeforeSendCallback beforeSendCallback; BeforeSendCallback beforeSendCallback;
String eTag; String eTag;
const char* uri = nullptr; const char* uri = nullptr;
const char* path = nullptr; String path;
const char* cacheHeader = nullptr; const char* cacheHeader = nullptr;
}; };

View File

@@ -174,7 +174,7 @@ public:
Log.serrorln( Log.serrorln(
FPSTR(L_PORTAL_OTA), FPSTR(L_PORTAL_OTA),
F("File '%s', on writing %d bytes: %s"), F("File '%s', on writing %d bytes: %s"),
upload.filename.c_str(), upload.totalSize, result->error upload.filename.c_str(), upload.totalSize, result->error.c_str()
); );
} else { } else {

View File

@@ -15,59 +15,65 @@ extra_configs = secrets.default.ini
[env] [env]
framework = arduino framework = arduino
lib_deps = lib_deps =
bblanchon/ArduinoJson@^7.0.3 bblanchon/ArduinoJson@^7.0.4
;ihormelnyk/OpenTherm Library@^1.1.4 ;ihormelnyk/OpenTherm Library@^1.1.5
https://github.com/Laxilef/opentherm_library/archive/refs/heads/fix_start_bit.zip https://github.com/ihormelnyk/opentherm_library#master
arduino-libraries/ArduinoMqttClient@^0.1.8 arduino-libraries/ArduinoMqttClient@^0.1.8
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.0
https://github.com/PaulStoffregen/OneWire#master
milesburton/DallasTemperature@^3.11.0 milesburton/DallasTemperature@^3.11.0
laxilef/TinyLogger@^1.1.0 laxilef/TinyLogger@^1.1.0
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_ESPRESSIF_SDK305 -D PIO_FRAMEWORK_ARDUINO_ESPRESSIF_SDK305
-mtext-section-literals -mtext-section-literals
-D MQTT_CLIENT_STD_FUNCTION_CALLBACK=1 -D MQTT_CLIENT_STD_FUNCTION_CALLBACK=1
;-D DEBUG_ESP_CORE -D DEBUG_ESP_WIFI -D DEBUG_ESP_PORT=Serial ;-D DEBUG_ESP_CORE -D DEBUG_ESP_WIFI -D DEBUG_ESP_HTTP_SERVER -D DEBUG_ESP_PORT=Serial
-D USE_SERIAL=${secrets.use_serial} -D USE_SERIAL=${secrets.use_serial}
-D USE_TELNET=${secrets.use_telnet} -D USE_TELNET=${secrets.use_telnet}
-D DEBUG_BY_DEFAULT=${secrets.debug} -D DEBUG_BY_DEFAULT=${secrets.debug}
-D HOSTNAME_DEFAULT='"${secrets.hostname}"' -D DEFAULT_HOSTNAME='"${secrets.hostname}"'
-D AP_SSID_DEFAULT='"${secrets.ap_ssid}"' -D DEFAULT_AP_SSID='"${secrets.ap_ssid}"'
-D AP_PASSWORD_DEFAULT='"${secrets.ap_password}"' -D DEFAULT_AP_PASSWORD='"${secrets.ap_password}"'
-D STA_SSID_DEFAULT='"${secrets.sta_ssid}"' -D DEFAULT_STA_SSID='"${secrets.sta_ssid}"'
-D STA_PASSWORD_DEFAULT='"${secrets.sta_password}"' -D DEFAULT_STA_PASSWORD='"${secrets.sta_password}"'
-D PORTAL_LOGIN_DEFAULT='"${secrets.portal_login}"' -D DEFAULT_PORTAL_LOGIN='"${secrets.portal_login}"'
-D PORTAL_PASSWORD_DEFAULT='"${secrets.portal_password}"' -D DEFAULT_PORTAL_PASSWORD='"${secrets.portal_password}"'
-D MQTT_SERVER_DEFAULT='"${secrets.mqtt_server}"' -D DEFAULT_MQTT_SERVER='"${secrets.mqtt_server}"'
-D MQTT_PORT_DEFAULT=${secrets.mqtt_port} -D DEFAULT_MQTT_PORT=${secrets.mqtt_port}
-D MQTT_USER_DEFAULT='"${secrets.mqtt_user}"' -D DEFAULT_MQTT_USER='"${secrets.mqtt_user}"'
-D MQTT_PASSWORD_DEFAULT='"${secrets.mqtt_password}"' -D DEFAULT_MQTT_PASSWORD='"${secrets.mqtt_password}"'
-D MQTT_PREFIX_DEFAULT='"${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
board_build.flash_mode = dio board_build.flash_mode = dio
board_build.filesystem = littlefs board_build.filesystem = littlefs
version = 1.4.0-rc.15 version = 1.4.1
; Defaults ; Defaults
[esp8266_defaults] [esp8266_defaults]
platform = espressif8266 platform = espressif8266@^4.2.1
lib_deps = lib_deps =
${env.lib_deps} ${env.lib_deps}
nrwiersma/ESP8266Scheduler@^1.1 nrwiersma/ESP8266Scheduler@^1.2
lib_ignore = lib_ignore =
extra_scripts = extra_scripts =
post:tools/build.py post:tools/build.py
build_flags = ${env.build_flags} build_flags = ${env.build_flags}
board_build.ldscript = eagle.flash.1m256.ld board_build.ldscript = eagle.flash.4m1m.ld
;board_build.ldscript = eagle.flash.4m1m.ld
[esp32_defaults] [esp32_defaults]
platform = espressif32 ;platform = espressif32@^6.7
platform = https://github.com/platformio/platform-espressif32.git
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}
@@ -91,12 +97,12 @@ extra_scripts = ${esp8266_defaults.extra_scripts}
board_build.ldscript = ${esp8266_defaults.board_build.ldscript} board_build.ldscript = ${esp8266_defaults.board_build.ldscript}
build_flags = build_flags =
${esp8266_defaults.build_flags} ${esp8266_defaults.build_flags}
-D OT_IN_PIN_DEFAULT=4 -D DEFAULT_OT_IN_GPIO=4
-D OT_OUT_PIN_DEFAULT=5 -D DEFAULT_OT_OUT_GPIO=5
-D SENSOR_OUTDOOR_PIN_DEFAULT=12 -D DEFAULT_SENSOR_OUTDOOR_GPIO=12
-D SENSOR_INDOOR_PIN_DEFAULT=14 -D DEFAULT_SENSOR_INDOOR_GPIO=14
-D LED_STATUS_PIN=13 -D DEFAULT_STATUS_LED_GPIO=13
-D LED_OT_RX_PIN=15 -D DEFAULT_OT_RX_LED_GPIO=15
[env:d1_mini_lite] [env:d1_mini_lite]
platform = ${esp8266_defaults.platform} platform = ${esp8266_defaults.platform}
@@ -107,12 +113,12 @@ extra_scripts = ${esp8266_defaults.extra_scripts}
board_build.ldscript = ${esp8266_defaults.board_build.ldscript} board_build.ldscript = ${esp8266_defaults.board_build.ldscript}
build_flags = build_flags =
${esp8266_defaults.build_flags} ${esp8266_defaults.build_flags}
-D OT_IN_PIN_DEFAULT=4 -D DEFAULT_OT_IN_GPIO=4
-D OT_OUT_PIN_DEFAULT=5 -D DEFAULT_OT_OUT_GPIO=5
-D SENSOR_OUTDOOR_PIN_DEFAULT=12 -D DEFAULT_SENSOR_OUTDOOR_GPIO=12
-D SENSOR_INDOOR_PIN_DEFAULT=14 -D DEFAULT_SENSOR_INDOOR_GPIO=14
-D LED_STATUS_PIN=13 -D DEFAULT_STATUS_LED_GPIO=13
-D LED_OT_RX_PIN=15 -D DEFAULT_OT_RX_LED_GPIO=15
[env:d1_mini_pro] [env:d1_mini_pro]
platform = ${esp8266_defaults.platform} platform = ${esp8266_defaults.platform}
@@ -123,31 +129,37 @@ extra_scripts = ${esp8266_defaults.extra_scripts}
board_build.ldscript = ${esp8266_defaults.board_build.ldscript} board_build.ldscript = ${esp8266_defaults.board_build.ldscript}
build_flags = build_flags =
${esp8266_defaults.build_flags} ${esp8266_defaults.build_flags}
-D OT_IN_PIN_DEFAULT=4 -D DEFAULT_OT_IN_GPIO=4
-D OT_OUT_PIN_DEFAULT=5 -D DEFAULT_OT_OUT_GPIO=5
-D SENSOR_OUTDOOR_PIN_DEFAULT=12 -D DEFAULT_SENSOR_OUTDOOR_GPIO=12
-D SENSOR_INDOOR_PIN_DEFAULT=14 -D DEFAULT_SENSOR_INDOOR_GPIO=14
-D LED_STATUS_PIN=13 -D DEFAULT_STATUS_LED_GPIO=13
-D LED_OT_RX_PIN=15 -D DEFAULT_OT_RX_LED_GPIO=15
[env:s2_mini] [env:s2_mini]
platform = ${esp32_defaults.platform} platform = ${esp32_defaults.platform}
platform_packages = ${esp32_defaults.platform_packages}
board = lolin_s2_mini board = lolin_s2_mini
board_build.partitions = ${esp32_defaults.board_build.partitions} board_build.partitions = ${esp32_defaults.board_build.partitions}
lib_deps = ${esp32_defaults.lib_deps} lib_deps = ${esp32_defaults.lib_deps}
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 =
-DARDUINO_USB_MODE=1
build_flags = build_flags =
${esp32_defaults.build_flags} ${esp32_defaults.build_flags}
-D OT_IN_PIN_DEFAULT=33 -D ARDUINO_USB_MODE=0
-D OT_OUT_PIN_DEFAULT=35 -D ARDUINO_USB_CDC_ON_BOOT=1
-D SENSOR_OUTDOOR_PIN_DEFAULT=9 -D DEFAULT_OT_IN_GPIO=33
-D SENSOR_INDOOR_PIN_DEFAULT=7 -D DEFAULT_OT_OUT_GPIO=35
-D LED_STATUS_PIN=11 -D DEFAULT_SENSOR_OUTDOOR_GPIO=9
-D LED_OT_RX_PIN=12 -D DEFAULT_SENSOR_INDOOR_GPIO=7
-D DEFAULT_STATUS_LED_GPIO=11
-D DEFAULT_OT_RX_LED_GPIO=12
[env:s3_mini] [env:s3_mini]
platform = ${esp32_defaults.platform} platform = ${esp32_defaults.platform}
platform_packages = ${esp32_defaults.platform_packages}
board = lolin_s3_mini board = lolin_s3_mini
board_build.partitions = ${esp32_defaults.board_build.partitions} board_build.partitions = ${esp32_defaults.board_build.partitions}
lib_deps = lib_deps =
@@ -155,18 +167,23 @@ lib_deps =
h2zero/NimBLE-Arduino@^1.4.1 h2zero/NimBLE-Arduino@^1.4.1
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 =
-DARDUINO_USB_MODE=1
build_flags = build_flags =
${esp32_defaults.build_flags} ${esp32_defaults.build_flags}
-D ARDUINO_USB_MODE=0
-D ARDUINO_USB_CDC_ON_BOOT=1
-D USE_BLE=1 -D USE_BLE=1
-D OT_IN_PIN_DEFAULT=35 -D DEFAULT_OT_IN_GPIO=35
-D OT_OUT_PIN_DEFAULT=36 -D DEFAULT_OT_OUT_GPIO=36
-D SENSOR_OUTDOOR_PIN_DEFAULT=13 -D DEFAULT_SENSOR_OUTDOOR_GPIO=13
-D SENSOR_INDOOR_PIN_DEFAULT=12 -D DEFAULT_SENSOR_INDOOR_GPIO=12
-D LED_STATUS_PIN=11 -D DEFAULT_STATUS_LED_GPIO=11
-D LED_OT_RX_PIN=10 -D DEFAULT_OT_RX_LED_GPIO=10
[env:c3_mini] [env:c3_mini]
platform = ${esp32_defaults.platform} platform = ${esp32_defaults.platform}
platform_packages = ${esp32_defaults.platform_packages}
board = lolin_c3_mini board = lolin_c3_mini
board_build.partitions = ${esp32_defaults.board_build.partitions} board_build.partitions = ${esp32_defaults.board_build.partitions}
lib_deps = lib_deps =
@@ -179,15 +196,16 @@ build_unflags =
build_flags = build_flags =
${esp32_defaults.build_flags} ${esp32_defaults.build_flags}
-D USE_BLE=1 -D USE_BLE=1
-D OT_IN_PIN_DEFAULT=8 -D DEFAULT_OT_IN_GPIO=8
-D OT_OUT_PIN_DEFAULT=10 -D DEFAULT_OT_OUT_GPIO=10
-D SENSOR_OUTDOOR_PIN_DEFAULT=0 -D DEFAULT_SENSOR_OUTDOOR_GPIO=0
-D SENSOR_INDOOR_PIN_DEFAULT=1 -D DEFAULT_SENSOR_INDOOR_GPIO=1
-D LED_STATUS_PIN=4 -D DEFAULT_STATUS_LED_GPIO=4
-D LED_OT_RX_PIN=5 -D DEFAULT_OT_RX_LED_GPIO=5
[env:nodemcu_32s] [env:nodemcu_32s]
platform = ${esp32_defaults.platform} platform = ${esp32_defaults.platform}
platform_packages = ${esp32_defaults.platform_packages}
board = nodemcu-32s board = nodemcu-32s
board_build.partitions = ${esp32_defaults.board_build.partitions} board_build.partitions = ${esp32_defaults.board_build.partitions}
lib_deps = lib_deps =
@@ -198,16 +216,17 @@ extra_scripts = ${esp32_defaults.extra_scripts}
build_flags = build_flags =
${esp32_defaults.build_flags} ${esp32_defaults.build_flags}
-D USE_BLE=1 -D USE_BLE=1
-D OT_IN_PIN_DEFAULT=21 -D DEFAULT_OT_IN_GPIO=21
-D OT_OUT_PIN_DEFAULT=22 -D DEFAULT_OT_OUT_GPIO=22
-D SENSOR_OUTDOOR_PIN_DEFAULT=12 -D DEFAULT_SENSOR_OUTDOOR_GPIO=12
-D SENSOR_INDOOR_PIN_DEFAULT=13 -D DEFAULT_SENSOR_INDOOR_GPIO=13
-D LED_STATUS_PIN=2 ; 18 -D DEFAULT_STATUS_LED_GPIO=2 ; 18
-D LED_OT_RX_PIN=19 -D DEFAULT_OT_RX_LED_GPIO=19
;-D WOKWI=1 ;-D WOKWI=1
[env:d1_mini32] [env:d1_mini32]
platform = ${esp32_defaults.platform} platform = ${esp32_defaults.platform}
platform_packages = ${esp32_defaults.platform_packages}
board = wemos_d1_mini32 board = wemos_d1_mini32
board_build.partitions = ${esp32_defaults.board_build.partitions} board_build.partitions = ${esp32_defaults.board_build.partitions}
lib_deps = lib_deps =
@@ -218,9 +237,9 @@ extra_scripts = ${esp32_defaults.extra_scripts}
build_flags = build_flags =
${esp32_defaults.build_flags} ${esp32_defaults.build_flags}
-D USE_BLE=1 -D USE_BLE=1
-D OT_IN_PIN_DEFAULT=21 -D DEFAULT_OT_IN_GPIO=21
-D OT_OUT_PIN_DEFAULT=22 -D DEFAULT_OT_OUT_GPIO=22
-D SENSOR_OUTDOOR_PIN_DEFAULT=12 -D DEFAULT_SENSOR_OUTDOOR_GPIO=12
-D SENSOR_INDOOR_PIN_DEFAULT=18 -D DEFAULT_SENSOR_INDOOR_GPIO=18
-D LED_STATUS_PIN=2 -D DEFAULT_STATUS_LED_GPIO=2
-D LED_OT_RX_PIN=19 -D DEFAULT_OT_RX_LED_GPIO=19

View File

@@ -6,98 +6,6 @@ public:
static const byte TEMP_SOURCE_HEATING = 0; static const byte TEMP_SOURCE_HEATING = 0;
static const byte TEMP_SOURCE_INDOOR = 1; static const byte TEMP_SOURCE_INDOOR = 1;
bool publishSwitchEmergency(bool enabledByDefault = true) {
JsonDocument doc;
doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault;
doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("emergency"));
doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("emergency"));
doc[FPSTR(HA_ENTITY_CATEGORY)] = F("config");
doc[FPSTR(HA_NAME)] = F("Use emergency");
doc[FPSTR(HA_ICON)] = F("mdi:sun-snowflake-variant");
doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("settings"));
doc[FPSTR(HA_STATE_ON)] = true;
doc[FPSTR(HA_STATE_OFF)] = false;
doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.emergency.enable }}");
doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("settings/set"));
doc[FPSTR(HA_PAYLOAD_ON)] = F("{\"emergency\": {\"enable\" : true}}");
doc[FPSTR(HA_PAYLOAD_OFF)] = F("{\"emergency\": {\"enable\" : false}}");
doc[FPSTR(HA_EXPIRE_AFTER)] = 120;
doc.shrinkToFit();
return this->publish(this->getTopic(FPSTR(HA_ENTITY_SWITCH), F("emergency")).c_str(), doc);
}
bool publishNumberEmergencyTarget(bool enabledByDefault = true) {
JsonDocument doc;
doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault;
doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("emergency_target"));
doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("emergency_target"));
doc[FPSTR(HA_ENTITY_CATEGORY)] = F("config");
doc[FPSTR(HA_DEVICE_CLASS)] = F("temperature");
doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = F("°C");
doc[FPSTR(HA_NAME)] = F("Emergency target temp");
doc[FPSTR(HA_ICON)] = F("mdi:thermometer-alert");
doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("settings"));
doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.emergency.target|float(0)|round(1) }}");
doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("settings/set"));
doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"emergency\": {\"target\" : {{ value }}}}");
doc[FPSTR(HA_MIN)] = 5;
doc[FPSTR(HA_MAX)] = 50;
doc[FPSTR(HA_STEP)] = 0.5;
doc[FPSTR(HA_MODE)] = "box";
doc[FPSTR(HA_EXPIRE_AFTER)] = 120;
doc.shrinkToFit();
return this->publish(this->getTopic(FPSTR(HA_ENTITY_NUMBER), F("emergency_target")).c_str(), doc);
}
bool publishSwitchEmergencyUseEquitherm(bool enabledByDefault = true) {
JsonDocument doc;
doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("settings"));
doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_VALUE_TEMPLATE)] = F("{{ iif(value_json.sensors.outdoor.type != 1, 'online', 'offline') }}");
doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault;
doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("emergency_use_equitherm"));
doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("emergency_use_equitherm"));
doc[FPSTR(HA_ENTITY_CATEGORY)] = F("config");
doc[FPSTR(HA_NAME)] = F("Use equitherm in emergency");
doc[FPSTR(HA_ICON)] = F("mdi:snowflake-alert");
doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("settings"));
doc[FPSTR(HA_STATE_ON)] = true;
doc[FPSTR(HA_STATE_OFF)] = false;
doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.emergency.useEquitherm }}");
doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("settings/set"));
doc[FPSTR(HA_PAYLOAD_ON)] = F("{\"emergency\": {\"useEquitherm\" : true}}");
doc[FPSTR(HA_PAYLOAD_OFF)] = F("{\"emergency\": {\"useEquitherm\" : false}}");
doc[FPSTR(HA_EXPIRE_AFTER)] = 120;
doc.shrinkToFit();
return this->publish(this->getTopic(FPSTR(HA_ENTITY_SWITCH), F("emergency_use_equitherm")).c_str(), doc);
}
bool publishSwitchEmergencyUsePid(bool enabledByDefault = true) {
JsonDocument doc;
doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("settings"));
doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_VALUE_TEMPLATE)] = F("{{ iif(value_json.sensors.indoor.type != 1, 'online', 'offline') }}");
doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault;
doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("emergency_use_pid"));
doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("emergency_use_pid"));
doc[FPSTR(HA_ENTITY_CATEGORY)] = F("config");
doc[FPSTR(HA_NAME)] = F("Use PID in emergency");
doc[FPSTR(HA_ICON)] = F("mdi:snowflake-alert");
doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("settings"));
doc[FPSTR(HA_STATE_ON)] = true;
doc[FPSTR(HA_STATE_OFF)] = false;
doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.emergency.usePid }}");
doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("settings/set"));
doc[FPSTR(HA_PAYLOAD_ON)] = F("{\"emergency\": {\"usePid\" : true}}");
doc[FPSTR(HA_PAYLOAD_OFF)] = F("{\"emergency\": {\"usePid\" : false}}");
doc[FPSTR(HA_EXPIRE_AFTER)] = 120;
doc.shrinkToFit();
return this->publish(this->getTopic(FPSTR(HA_ENTITY_SWITCH), F("emergency_use_pid")).c_str(), doc);
}
bool publishSwitchHeating(bool enabledByDefault = true) { bool publishSwitchHeating(bool enabledByDefault = true) {
JsonDocument doc; JsonDocument doc;
doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status"));
@@ -142,7 +50,7 @@ public:
return this->publish(this->getTopic(FPSTR(HA_ENTITY_SWITCH), F("heating_turbo")).c_str(), doc); return this->publish(this->getTopic(FPSTR(HA_ENTITY_SWITCH), F("heating_turbo")).c_str(), doc);
} }
bool publishNumberHeatingTarget(byte minTemp = 20, byte maxTemp = 90, bool enabledByDefault = true) { bool publishNumberHeatingTarget(UnitSystem unit = UnitSystem::METRIC, byte minTemp = 20, byte maxTemp = 90, bool enabledByDefault = true) {
JsonDocument doc; JsonDocument doc;
doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status"));
doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault;
@@ -150,7 +58,14 @@ public:
doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("heating_target")); doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("heating_target"));
doc[FPSTR(HA_ENTITY_CATEGORY)] = F("config"); doc[FPSTR(HA_ENTITY_CATEGORY)] = F("config");
doc[FPSTR(HA_DEVICE_CLASS)] = F("temperature"); doc[FPSTR(HA_DEVICE_CLASS)] = F("temperature");
doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = F("°C");
if (unit == UnitSystem::METRIC) {
doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR(HA_UNIT_OF_MEASUREMENT_C);
} else if (unit == UnitSystem::IMPERIAL) {
doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR(HA_UNIT_OF_MEASUREMENT_F);
}
doc[FPSTR(HA_NAME)] = F("Heating target"); doc[FPSTR(HA_NAME)] = F("Heating target");
doc[FPSTR(HA_ICON)] = F("mdi:radiator"); doc[FPSTR(HA_ICON)] = F("mdi:radiator");
doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("settings")); doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("settings"));
@@ -159,7 +74,7 @@ public:
doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"heating\": {\"target\" : {{ value }}}}"); doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"heating\": {\"target\" : {{ value }}}}");
doc[FPSTR(HA_MIN)] = minTemp; doc[FPSTR(HA_MIN)] = minTemp;
doc[FPSTR(HA_MAX)] = maxTemp; doc[FPSTR(HA_MAX)] = maxTemp;
doc[FPSTR(HA_STEP)] = 0.5; doc[FPSTR(HA_STEP)] = 0.5f;
doc[FPSTR(HA_MODE)] = "box"; doc[FPSTR(HA_MODE)] = "box";
doc[FPSTR(HA_EXPIRE_AFTER)] = 120; doc[FPSTR(HA_EXPIRE_AFTER)] = 120;
doc.shrinkToFit(); doc.shrinkToFit();
@@ -167,14 +82,21 @@ public:
return this->publish(this->getTopic(FPSTR(HA_ENTITY_NUMBER), F("heating_target")).c_str(), doc); return this->publish(this->getTopic(FPSTR(HA_ENTITY_NUMBER), F("heating_target")).c_str(), doc);
} }
bool publishNumberHeatingHysteresis(bool enabledByDefault = true) { bool publishNumberHeatingHysteresis(UnitSystem unit = UnitSystem::METRIC, bool enabledByDefault = true) {
JsonDocument doc; JsonDocument doc;
doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault;
doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("heating_hysteresis")); doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("heating_hysteresis"));
doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("heating_hysteresis")); doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("heating_hysteresis"));
doc[FPSTR(HA_ENTITY_CATEGORY)] = F("config"); doc[FPSTR(HA_ENTITY_CATEGORY)] = F("config");
doc[FPSTR(HA_DEVICE_CLASS)] = F("temperature"); doc[FPSTR(HA_DEVICE_CLASS)] = F("temperature");
doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = F("°C");
if (unit == UnitSystem::METRIC) {
doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR(HA_UNIT_OF_MEASUREMENT_C);
} else if (unit == UnitSystem::IMPERIAL) {
doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR(HA_UNIT_OF_MEASUREMENT_F);
}
doc[FPSTR(HA_NAME)] = F("Heating hysteresis"); doc[FPSTR(HA_NAME)] = F("Heating hysteresis");
doc[FPSTR(HA_ICON)] = F("mdi:altimeter"); doc[FPSTR(HA_ICON)] = F("mdi:altimeter");
doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("settings")); doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("settings"));
@@ -183,7 +105,7 @@ public:
doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"heating\": {\"hysteresis\" : {{ value }}}}"); doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"heating\": {\"hysteresis\" : {{ value }}}}");
doc[FPSTR(HA_MIN)] = 0; doc[FPSTR(HA_MIN)] = 0;
doc[FPSTR(HA_MAX)] = 5; doc[FPSTR(HA_MAX)] = 5;
doc[FPSTR(HA_STEP)] = 0.1; doc[FPSTR(HA_STEP)] = 0.1f;
doc[FPSTR(HA_MODE)] = "box"; doc[FPSTR(HA_MODE)] = "box";
doc[FPSTR(HA_EXPIRE_AFTER)] = 120; doc[FPSTR(HA_EXPIRE_AFTER)] = 120;
doc.shrinkToFit(); doc.shrinkToFit();
@@ -191,7 +113,7 @@ public:
return this->publish(this->getTopic(FPSTR(HA_ENTITY_NUMBER), F("heating_hysteresis")).c_str(), doc); return this->publish(this->getTopic(FPSTR(HA_ENTITY_NUMBER), F("heating_hysteresis")).c_str(), doc);
} }
bool publishSensorHeatingSetpoint(bool enabledByDefault = true) { bool publishSensorHeatingSetpoint(UnitSystem unit = UnitSystem::METRIC, bool enabledByDefault = true) {
JsonDocument doc; JsonDocument doc;
doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status"));
doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault;
@@ -200,18 +122,25 @@ public:
doc[FPSTR(HA_ENTITY_CATEGORY)] = F("diagnostic"); doc[FPSTR(HA_ENTITY_CATEGORY)] = F("diagnostic");
doc[FPSTR(HA_DEVICE_CLASS)] = F("temperature"); doc[FPSTR(HA_DEVICE_CLASS)] = F("temperature");
doc[FPSTR(HA_STATE_CLASS)] = F("measurement"); doc[FPSTR(HA_STATE_CLASS)] = F("measurement");
doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = F("°C");
if (unit == UnitSystem::METRIC) {
doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR(HA_UNIT_OF_MEASUREMENT_C);
} else if (unit == UnitSystem::IMPERIAL) {
doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR(HA_UNIT_OF_MEASUREMENT_F);
}
doc[FPSTR(HA_NAME)] = F("Heating setpoint"); doc[FPSTR(HA_NAME)] = F("Heating setpoint");
doc[FPSTR(HA_ICON)] = F("mdi:coolant-temperature"); doc[FPSTR(HA_ICON)] = F("mdi:coolant-temperature");
doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("state")); doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("state"));
doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.parameters.heatingSetpoint|int(0) }}"); doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.parameters.heatingSetpoint|float(0)|round(1) }}");
doc[FPSTR(HA_EXPIRE_AFTER)] = 120; doc[FPSTR(HA_EXPIRE_AFTER)] = 120;
doc.shrinkToFit(); doc.shrinkToFit();
return this->publish(this->getTopic(FPSTR(HA_ENTITY_SENSOR), F("heating_setpoint")).c_str(), doc); return this->publish(this->getTopic(FPSTR(HA_ENTITY_SENSOR), F("heating_setpoint")).c_str(), doc);
} }
bool publishSensorBoilerHeatingMinTemp(bool enabledByDefault = true) { bool publishSensorBoilerHeatingMinTemp(UnitSystem unit = UnitSystem::METRIC, bool enabledByDefault = true) {
JsonDocument doc; JsonDocument doc;
doc[FPSTR(HA_AVAILABILITY)][0][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); doc[FPSTR(HA_AVAILABILITY)][0][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status"));
doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("state")); doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("state"));
@@ -223,7 +152,14 @@ public:
doc[FPSTR(HA_ENTITY_CATEGORY)] = F("diagnostic"); doc[FPSTR(HA_ENTITY_CATEGORY)] = F("diagnostic");
doc[FPSTR(HA_DEVICE_CLASS)] = F("temperature"); doc[FPSTR(HA_DEVICE_CLASS)] = F("temperature");
doc[FPSTR(HA_STATE_CLASS)] = F("measurement"); doc[FPSTR(HA_STATE_CLASS)] = F("measurement");
doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = F("°C");
if (unit == UnitSystem::METRIC) {
doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR(HA_UNIT_OF_MEASUREMENT_C);
} else if (unit == UnitSystem::IMPERIAL) {
doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR(HA_UNIT_OF_MEASUREMENT_F);
}
doc[FPSTR(HA_NAME)] = F("Boiler heating min temp"); doc[FPSTR(HA_NAME)] = F("Boiler heating min temp");
doc[FPSTR(HA_ICON)] = F("mdi:thermometer-chevron-down"); doc[FPSTR(HA_ICON)] = F("mdi:thermometer-chevron-down");
doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("state")); doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("state"));
@@ -234,7 +170,7 @@ public:
return this->publish(this->getTopic(FPSTR(HA_ENTITY_SENSOR), F("boiler_heating_min_temp")).c_str(), doc); return this->publish(this->getTopic(FPSTR(HA_ENTITY_SENSOR), F("boiler_heating_min_temp")).c_str(), doc);
} }
bool publishSensorBoilerHeatingMaxTemp(bool enabledByDefault = true) { bool publishSensorBoilerHeatingMaxTemp(UnitSystem unit = UnitSystem::METRIC, bool enabledByDefault = true) {
JsonDocument doc; JsonDocument doc;
doc[FPSTR(HA_AVAILABILITY)][0][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); doc[FPSTR(HA_AVAILABILITY)][0][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status"));
doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("state")); doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("state"));
@@ -246,7 +182,14 @@ public:
doc[FPSTR(HA_ENTITY_CATEGORY)] = F("diagnostic"); doc[FPSTR(HA_ENTITY_CATEGORY)] = F("diagnostic");
doc[FPSTR(HA_DEVICE_CLASS)] = F("temperature"); doc[FPSTR(HA_DEVICE_CLASS)] = F("temperature");
doc[FPSTR(HA_STATE_CLASS)] = F("measurement"); doc[FPSTR(HA_STATE_CLASS)] = F("measurement");
doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = F("°C");
if (unit == UnitSystem::METRIC) {
doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR(HA_UNIT_OF_MEASUREMENT_C);
} else if (unit == UnitSystem::IMPERIAL) {
doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR(HA_UNIT_OF_MEASUREMENT_F);
}
doc[FPSTR(HA_NAME)] = F("Boiler heating max temp"); doc[FPSTR(HA_NAME)] = F("Boiler heating max temp");
doc[FPSTR(HA_ICON)] = F("mdi:thermometer-chevron-up"); doc[FPSTR(HA_ICON)] = F("mdi:thermometer-chevron-up");
doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("state")); doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("state"));
@@ -257,22 +200,31 @@ public:
return this->publish(this->getTopic(FPSTR(HA_ENTITY_SENSOR), F("boiler_heating_max_temp")).c_str(), doc); return this->publish(this->getTopic(FPSTR(HA_ENTITY_SENSOR), F("boiler_heating_max_temp")).c_str(), doc);
} }
bool publishNumberHeatingMinTemp(bool enabledByDefault = true) { bool publishNumberHeatingMinTemp(UnitSystem unit = UnitSystem::METRIC, bool enabledByDefault = true) {
JsonDocument doc; JsonDocument doc;
doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault;
doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("heating_min_temp")); doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("heating_min_temp"));
doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("heating_min_temp")); doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("heating_min_temp"));
doc[FPSTR(HA_ENTITY_CATEGORY)] = F("config"); doc[FPSTR(HA_ENTITY_CATEGORY)] = F("config");
doc[FPSTR(HA_DEVICE_CLASS)] = F("temperature"); doc[FPSTR(HA_DEVICE_CLASS)] = F("temperature");
doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = F("°C");
if (unit == UnitSystem::METRIC) {
doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR(HA_UNIT_OF_MEASUREMENT_C);
doc[FPSTR(HA_MIN)] = 0;
doc[FPSTR(HA_MAX)] = 99;
} else if (unit == UnitSystem::IMPERIAL) {
doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR(HA_UNIT_OF_MEASUREMENT_F);
doc[FPSTR(HA_MIN)] = 32;
doc[FPSTR(HA_MAX)] = 211;
}
doc[FPSTR(HA_NAME)] = F("Heating min temp"); doc[FPSTR(HA_NAME)] = F("Heating min temp");
doc[FPSTR(HA_ICON)] = F("mdi:thermometer-chevron-down"); doc[FPSTR(HA_ICON)] = F("mdi:thermometer-chevron-down");
doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("settings")); doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("settings"));
doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.heating.minTemp|float(0)|round(1) }}"); doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.heating.minTemp|float(0)|round(1) }}");
doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("settings/set")); doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("settings/set"));
doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"heating\": {\"minTemp\" : {{ value }}}}"); doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"heating\": {\"minTemp\" : {{ value }}}}");
doc[FPSTR(HA_MIN)] = 0;
doc[FPSTR(HA_MAX)] = 99;
doc[FPSTR(HA_STEP)] = 1; doc[FPSTR(HA_STEP)] = 1;
doc[FPSTR(HA_MODE)] = "box"; doc[FPSTR(HA_MODE)] = "box";
doc[FPSTR(HA_EXPIRE_AFTER)] = 120; doc[FPSTR(HA_EXPIRE_AFTER)] = 120;
@@ -281,22 +233,31 @@ public:
return this->publish(this->getTopic(FPSTR(HA_ENTITY_NUMBER), F("heating_min_temp")).c_str(), doc); return this->publish(this->getTopic(FPSTR(HA_ENTITY_NUMBER), F("heating_min_temp")).c_str(), doc);
} }
bool publishNumberHeatingMaxTemp(bool enabledByDefault = true) { bool publishNumberHeatingMaxTemp(UnitSystem unit = UnitSystem::METRIC, bool enabledByDefault = true) {
JsonDocument doc; JsonDocument doc;
doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault;
doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("heating_max_temp")); doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("heating_max_temp"));
doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("heating_max_temp")); doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("heating_max_temp"));
doc[FPSTR(HA_ENTITY_CATEGORY)] = F("config"); doc[FPSTR(HA_ENTITY_CATEGORY)] = F("config");
doc[FPSTR(HA_DEVICE_CLASS)] = F("temperature"); doc[FPSTR(HA_DEVICE_CLASS)] = F("temperature");
doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = F("°C");
if (unit == UnitSystem::METRIC) {
doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR(HA_UNIT_OF_MEASUREMENT_C);
doc[FPSTR(HA_MIN)] = 1;
doc[FPSTR(HA_MAX)] = 100;
} else if (unit == UnitSystem::IMPERIAL) {
doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR(HA_UNIT_OF_MEASUREMENT_F);
doc[FPSTR(HA_MIN)] = 33;
doc[FPSTR(HA_MAX)] = 212;
}
doc[FPSTR(HA_NAME)] = F("Heating max temp"); doc[FPSTR(HA_NAME)] = F("Heating max temp");
doc[FPSTR(HA_ICON)] = F("mdi:thermometer-chevron-up"); doc[FPSTR(HA_ICON)] = F("mdi:thermometer-chevron-up");
doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("settings")); doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("settings"));
doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.heating.maxTemp|float(0)|round(1) }}"); doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.heating.maxTemp|float(0)|round(1) }}");
doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("settings/set")); doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("settings/set"));
doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"heating\": {\"maxTemp\" : {{ value }}}}"); doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"heating\": {\"maxTemp\" : {{ value }}}}");
doc[FPSTR(HA_MIN)] = 1;
doc[FPSTR(HA_MAX)] = 100;
doc[FPSTR(HA_STEP)] = 1; doc[FPSTR(HA_STEP)] = 1;
doc[FPSTR(HA_MODE)] = "box"; doc[FPSTR(HA_MODE)] = "box";
doc[FPSTR(HA_EXPIRE_AFTER)] = 120; doc[FPSTR(HA_EXPIRE_AFTER)] = 120;
@@ -352,7 +313,7 @@ public:
return this->publish(this->getTopic(FPSTR(HA_ENTITY_SWITCH), F("dhw")).c_str(), doc); return this->publish(this->getTopic(FPSTR(HA_ENTITY_SWITCH), F("dhw")).c_str(), doc);
} }
bool publishNumberDhwTarget(byte minTemp = 40, byte maxTemp = 60, bool enabledByDefault = true) { bool publishNumberDhwTarget(UnitSystem unit = UnitSystem::METRIC, byte minTemp = 40, byte maxTemp = 60, bool enabledByDefault = true) {
JsonDocument doc; JsonDocument doc;
doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status"));
doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault;
@@ -360,11 +321,18 @@ public:
doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("dhw_target")); doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("dhw_target"));
doc[FPSTR(HA_ENTITY_CATEGORY)] = F("config"); doc[FPSTR(HA_ENTITY_CATEGORY)] = F("config");
doc[FPSTR(HA_DEVICE_CLASS)] = F("temperature"); doc[FPSTR(HA_DEVICE_CLASS)] = F("temperature");
doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = F("°C");
if (unit == UnitSystem::METRIC) {
doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR(HA_UNIT_OF_MEASUREMENT_C);
} else if (unit == UnitSystem::IMPERIAL) {
doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR(HA_UNIT_OF_MEASUREMENT_F);
}
doc[FPSTR(HA_NAME)] = F("DHW target"); doc[FPSTR(HA_NAME)] = F("DHW target");
doc[FPSTR(HA_ICON)] = F("mdi:water-pump"); doc[FPSTR(HA_ICON)] = F("mdi:water-pump");
doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("settings")); doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("settings"));
doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.dhw.target|int(0) }}"); doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.dhw.target|float(0)|round(1) }}");
doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("settings/set")); doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("settings/set"));
doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"dhw\": {\"target\" : {{ value|int(0) }}}}"); doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"dhw\": {\"target\" : {{ value|int(0) }}}}");
doc[FPSTR(HA_MIN)] = minTemp; doc[FPSTR(HA_MIN)] = minTemp;
@@ -377,7 +345,7 @@ public:
return this->publish(this->getTopic(FPSTR(HA_ENTITY_NUMBER), F("dhw_target")).c_str(), doc); return this->publish(this->getTopic(FPSTR(HA_ENTITY_NUMBER), F("dhw_target")).c_str(), doc);
} }
bool publishSensorBoilerDhwMinTemp(bool enabledByDefault = true) { bool publishSensorBoilerDhwMinTemp(UnitSystem unit = UnitSystem::METRIC, bool enabledByDefault = true) {
JsonDocument doc; JsonDocument doc;
doc[FPSTR(HA_AVAILABILITY)][0][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); doc[FPSTR(HA_AVAILABILITY)][0][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status"));
doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("state")); doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("state"));
@@ -389,7 +357,14 @@ public:
doc[FPSTR(HA_ENTITY_CATEGORY)] = F("diagnostic"); doc[FPSTR(HA_ENTITY_CATEGORY)] = F("diagnostic");
doc[FPSTR(HA_DEVICE_CLASS)] = F("temperature"); doc[FPSTR(HA_DEVICE_CLASS)] = F("temperature");
doc[FPSTR(HA_STATE_CLASS)] = F("measurement"); doc[FPSTR(HA_STATE_CLASS)] = F("measurement");
doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = F("°C");
if (unit == UnitSystem::METRIC) {
doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR(HA_UNIT_OF_MEASUREMENT_C);
} else if (unit == UnitSystem::IMPERIAL) {
doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR(HA_UNIT_OF_MEASUREMENT_F);
}
doc[FPSTR(HA_NAME)] = F("Boiler DHW min temp"); doc[FPSTR(HA_NAME)] = F("Boiler DHW min temp");
doc[FPSTR(HA_ICON)] = F("mdi:thermometer-chevron-down"); doc[FPSTR(HA_ICON)] = F("mdi:thermometer-chevron-down");
doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("state")); doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("state"));
@@ -400,7 +375,7 @@ public:
return this->publish(this->getTopic(FPSTR(HA_ENTITY_SENSOR), F("boiler_dhw_min_temp")).c_str(), doc); return this->publish(this->getTopic(FPSTR(HA_ENTITY_SENSOR), F("boiler_dhw_min_temp")).c_str(), doc);
} }
bool publishSensorBoilerDhwMaxTemp(bool enabledByDefault = true) { bool publishSensorBoilerDhwMaxTemp(UnitSystem unit = UnitSystem::METRIC, bool enabledByDefault = true) {
JsonDocument doc; JsonDocument doc;
doc[FPSTR(HA_AVAILABILITY)][0][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); doc[FPSTR(HA_AVAILABILITY)][0][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status"));
doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("state")); doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("state"));
@@ -412,7 +387,14 @@ public:
doc[FPSTR(HA_ENTITY_CATEGORY)] = F("diagnostic"); doc[FPSTR(HA_ENTITY_CATEGORY)] = F("diagnostic");
doc[FPSTR(HA_DEVICE_CLASS)] = F("temperature"); doc[FPSTR(HA_DEVICE_CLASS)] = F("temperature");
doc[FPSTR(HA_STATE_CLASS)] = F("measurement"); doc[FPSTR(HA_STATE_CLASS)] = F("measurement");
doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = F("°C");
if (unit == UnitSystem::METRIC) {
doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR(HA_UNIT_OF_MEASUREMENT_C);
} else if (unit == UnitSystem::IMPERIAL) {
doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR(HA_UNIT_OF_MEASUREMENT_F);
}
doc[FPSTR(HA_NAME)] = F("Boiler DHW max temp"); doc[FPSTR(HA_NAME)] = F("Boiler DHW max temp");
doc[FPSTR(HA_ICON)] = F("mdi:thermometer-chevron-up"); doc[FPSTR(HA_ICON)] = F("mdi:thermometer-chevron-up");
doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("state")); doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("state"));
@@ -423,22 +405,31 @@ public:
return this->publish(this->getTopic(FPSTR(HA_ENTITY_SENSOR), F("boiler_dhw_max_temp")).c_str(), doc); return this->publish(this->getTopic(FPSTR(HA_ENTITY_SENSOR), F("boiler_dhw_max_temp")).c_str(), doc);
} }
bool publishNumberDhwMinTemp(bool enabledByDefault = true) { bool publishNumberDhwMinTemp(UnitSystem unit = UnitSystem::METRIC, bool enabledByDefault = true) {
JsonDocument doc; JsonDocument doc;
doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault;
doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("dhw_min_temp")); doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("dhw_min_temp"));
doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("dhw_min_temp")); doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("dhw_min_temp"));
doc[FPSTR(HA_ENTITY_CATEGORY)] = F("config"); doc[FPSTR(HA_ENTITY_CATEGORY)] = F("config");
doc[FPSTR(HA_DEVICE_CLASS)] = F("temperature"); doc[FPSTR(HA_DEVICE_CLASS)] = F("temperature");
doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = F("°C");
if (unit == UnitSystem::METRIC) {
doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR(HA_UNIT_OF_MEASUREMENT_C);
doc[FPSTR(HA_MIN)] = 0;
doc[FPSTR(HA_MAX)] = 99;
} else if (unit == UnitSystem::IMPERIAL) {
doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR(HA_UNIT_OF_MEASUREMENT_F);
doc[FPSTR(HA_MIN)] = 32;
doc[FPSTR(HA_MAX)] = 211;
}
doc[FPSTR(HA_NAME)] = F("DHW min temp"); doc[FPSTR(HA_NAME)] = F("DHW min temp");
doc[FPSTR(HA_ICON)] = F("mdi:thermometer-chevron-down"); doc[FPSTR(HA_ICON)] = F("mdi:thermometer-chevron-down");
doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("settings")); doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("settings"));
doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.dhw.minTemp|float(0)|round(1) }}"); doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.dhw.minTemp|float(0)|round(1) }}");
doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("settings/set")); doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("settings/set"));
doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"dhw\": {\"minTemp\" : {{ value }}}}"); doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"dhw\": {\"minTemp\" : {{ value }}}}");
doc[FPSTR(HA_MIN)] = 0;
doc[FPSTR(HA_MAX)] = 99;
doc[FPSTR(HA_STEP)] = 1; doc[FPSTR(HA_STEP)] = 1;
doc[FPSTR(HA_MODE)] = "box"; doc[FPSTR(HA_MODE)] = "box";
doc[FPSTR(HA_EXPIRE_AFTER)] = 120; doc[FPSTR(HA_EXPIRE_AFTER)] = 120;
@@ -447,22 +438,31 @@ public:
return this->publish(this->getTopic(FPSTR(HA_ENTITY_NUMBER), F("dhw_min_temp")).c_str(), doc); return this->publish(this->getTopic(FPSTR(HA_ENTITY_NUMBER), F("dhw_min_temp")).c_str(), doc);
} }
bool publishNumberDhwMaxTemp(bool enabledByDefault = true) { bool publishNumberDhwMaxTemp(UnitSystem unit = UnitSystem::METRIC, bool enabledByDefault = true) {
JsonDocument doc; JsonDocument doc;
doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault;
doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("dhw_max_temp")); doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("dhw_max_temp"));
doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("dhw_max_temp")); doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("dhw_max_temp"));
doc[FPSTR(HA_ENTITY_CATEGORY)] = F("config"); doc[FPSTR(HA_ENTITY_CATEGORY)] = F("config");
doc[FPSTR(HA_DEVICE_CLASS)] = F("temperature"); doc[FPSTR(HA_DEVICE_CLASS)] = F("temperature");
doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = F("°C");
if (unit == UnitSystem::METRIC) {
doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR(HA_UNIT_OF_MEASUREMENT_C);
doc[FPSTR(HA_MIN)] = 1;
doc[FPSTR(HA_MAX)] = 100;
} else if (unit == UnitSystem::IMPERIAL) {
doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR(HA_UNIT_OF_MEASUREMENT_F);
doc[FPSTR(HA_MIN)] = 33;
doc[FPSTR(HA_MAX)] = 212;
}
doc[FPSTR(HA_NAME)] = F("DHW max temp"); doc[FPSTR(HA_NAME)] = F("DHW max temp");
doc[FPSTR(HA_ICON)] = F("mdi:thermometer-chevron-up"); doc[FPSTR(HA_ICON)] = F("mdi:thermometer-chevron-up");
doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("settings")); doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("settings"));
doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.dhw.maxTemp|float(0)|round(1) }}"); doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.dhw.maxTemp|float(0)|round(1) }}");
doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("settings/set")); doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("settings/set"));
doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"dhw\": {\"maxTemp\" : {{ value }}}}"); doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"dhw\": {\"maxTemp\" : {{ value }}}}");
doc[FPSTR(HA_MIN)] = 1;
doc[FPSTR(HA_MAX)] = 100;
doc[FPSTR(HA_STEP)] = 1; doc[FPSTR(HA_STEP)] = 1;
doc[FPSTR(HA_MODE)] = "box"; doc[FPSTR(HA_MODE)] = "box";
doc[FPSTR(HA_EXPIRE_AFTER)] = 120; doc[FPSTR(HA_EXPIRE_AFTER)] = 120;
@@ -504,9 +504,9 @@ public:
doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.pid.p_factor|float(0)|round(3) }}"); doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.pid.p_factor|float(0)|round(3) }}");
doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("settings/set")); doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("settings/set"));
doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"pid\": {\"p_factor\" : {{ value }}}}"); doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"pid\": {\"p_factor\" : {{ value }}}}");
doc[FPSTR(HA_MIN)] = 0.1; doc[FPSTR(HA_MIN)] = 0.1f;
doc[FPSTR(HA_MAX)] = 1000; doc[FPSTR(HA_MAX)] = 1000;
doc[FPSTR(HA_STEP)] = 0.1; doc[FPSTR(HA_STEP)] = 0.1f;
doc[FPSTR(HA_MODE)] = "box"; doc[FPSTR(HA_MODE)] = "box";
doc[FPSTR(HA_EXPIRE_AFTER)] = 120; doc[FPSTR(HA_EXPIRE_AFTER)] = 120;
doc.shrinkToFit(); doc.shrinkToFit();
@@ -522,12 +522,12 @@ public:
doc[FPSTR(HA_NAME)] = F("PID factor I"); doc[FPSTR(HA_NAME)] = F("PID factor I");
doc[FPSTR(HA_ICON)] = F("mdi:alpha-i-circle-outline"); doc[FPSTR(HA_ICON)] = F("mdi:alpha-i-circle-outline");
doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("settings")); doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("settings"));
doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.pid.i_factor|float(0)|round(3) }}"); doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.pid.i_factor|float(0)|round(4) }}");
doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("settings/set")); doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("settings/set"));
doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"pid\": {\"i_factor\" : {{ value }}}}"); doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"pid\": {\"i_factor\" : {{ value }}}}");
doc[FPSTR(HA_MIN)] = 0; doc[FPSTR(HA_MIN)] = 0;
doc[FPSTR(HA_MAX)] = 100; doc[FPSTR(HA_MAX)] = 100;
doc[FPSTR(HA_STEP)] = 0.001; doc[FPSTR(HA_STEP)] = 0.001f;
doc[FPSTR(HA_MODE)] = "box"; doc[FPSTR(HA_MODE)] = "box";
doc[FPSTR(HA_EXPIRE_AFTER)] = 120; doc[FPSTR(HA_EXPIRE_AFTER)] = 120;
doc.shrinkToFit(); doc.shrinkToFit();
@@ -579,22 +579,31 @@ public:
return this->publish(this->getTopic(FPSTR(HA_ENTITY_NUMBER), F("pid_dt")).c_str(), doc); return this->publish(this->getTopic(FPSTR(HA_ENTITY_NUMBER), F("pid_dt")).c_str(), doc);
} }
bool publishNumberPidMinTemp(bool enabledByDefault = true) { bool publishNumberPidMinTemp(UnitSystem unit = UnitSystem::METRIC, bool enabledByDefault = true) {
JsonDocument doc; JsonDocument doc;
doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault;
doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("pid_min_temp")); doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("pid_min_temp"));
doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("pid_min_temp")); doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("pid_min_temp"));
doc[FPSTR(HA_ENTITY_CATEGORY)] = F("config"); doc[FPSTR(HA_ENTITY_CATEGORY)] = F("config");
doc[FPSTR(HA_DEVICE_CLASS)] = F("temperature"); doc[FPSTR(HA_DEVICE_CLASS)] = F("temperature");
doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = F("°C");
if (unit == UnitSystem::METRIC) {
doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR(HA_UNIT_OF_MEASUREMENT_C);
doc[FPSTR(HA_MIN)] = 0;
doc[FPSTR(HA_MAX)] = 99;
} else if (unit == UnitSystem::IMPERIAL) {
doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR(HA_UNIT_OF_MEASUREMENT_F);
doc[FPSTR(HA_MIN)] = 0;
doc[FPSTR(HA_MAX)] = 211;
}
doc[FPSTR(HA_NAME)] = F("PID min temp"); doc[FPSTR(HA_NAME)] = F("PID min temp");
doc[FPSTR(HA_ICON)] = F("mdi:thermometer-chevron-down"); doc[FPSTR(HA_ICON)] = F("mdi:thermometer-chevron-down");
doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("settings")); doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("settings"));
doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.pid.minTemp|float(0)|round(1) }}"); doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.pid.minTemp|float(0)|round(1) }}");
doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("settings/set")); doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("settings/set"));
doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"pid\": {\"minTemp\" : {{ value }}}}"); doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"pid\": {\"minTemp\" : {{ value }}}}");
doc[FPSTR(HA_MIN)] = 0;
doc[FPSTR(HA_MAX)] = 99;
doc[FPSTR(HA_STEP)] = 1; doc[FPSTR(HA_STEP)] = 1;
doc[FPSTR(HA_MODE)] = "box"; doc[FPSTR(HA_MODE)] = "box";
doc[FPSTR(HA_EXPIRE_AFTER)] = 120; doc[FPSTR(HA_EXPIRE_AFTER)] = 120;
@@ -603,22 +612,31 @@ public:
return this->publish(this->getTopic(FPSTR(HA_ENTITY_NUMBER), F("pid_min_temp")).c_str(), doc); return this->publish(this->getTopic(FPSTR(HA_ENTITY_NUMBER), F("pid_min_temp")).c_str(), doc);
} }
bool publishNumberPidMaxTemp(bool enabledByDefault = true) { bool publishNumberPidMaxTemp(UnitSystem unit = UnitSystem::METRIC, bool enabledByDefault = true) {
JsonDocument doc; JsonDocument doc;
doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault;
doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("pid_max_temp")); doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("pid_max_temp"));
doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("pid_max_temp")); doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("pid_max_temp"));
doc[FPSTR(HA_ENTITY_CATEGORY)] = F("config"); doc[FPSTR(HA_ENTITY_CATEGORY)] = F("config");
doc[FPSTR(HA_DEVICE_CLASS)] = F("temperature"); doc[FPSTR(HA_DEVICE_CLASS)] = F("temperature");
doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = F("°C");
if (unit == UnitSystem::METRIC) {
doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR(HA_UNIT_OF_MEASUREMENT_C);
doc[FPSTR(HA_MIN)] = 1;
doc[FPSTR(HA_MAX)] = 100;
} else if (unit == UnitSystem::IMPERIAL) {
doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR(HA_UNIT_OF_MEASUREMENT_F);
doc[FPSTR(HA_MIN)] = 1;
doc[FPSTR(HA_MAX)] = 212;
}
doc[FPSTR(HA_NAME)] = F("PID max temp"); doc[FPSTR(HA_NAME)] = F("PID max temp");
doc[FPSTR(HA_ICON)] = F("mdi:thermometer-chevron-up"); doc[FPSTR(HA_ICON)] = F("mdi:thermometer-chevron-up");
doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("settings")); doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("settings"));
doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.pid.maxTemp|float(0)|round(1) }}"); doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.pid.maxTemp|float(0)|round(1) }}");
doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("settings/set")); doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("settings/set"));
doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"pid\": {\"maxTemp\" : {{ value }}}}"); doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"pid\": {\"maxTemp\" : {{ value }}}}");
doc[FPSTR(HA_MIN)] = 1;
doc[FPSTR(HA_MAX)] = 100;
doc[FPSTR(HA_STEP)] = 1; doc[FPSTR(HA_STEP)] = 1;
doc[FPSTR(HA_MODE)] = "box"; doc[FPSTR(HA_MODE)] = "box";
doc[FPSTR(HA_EXPIRE_AFTER)] = 120; doc[FPSTR(HA_EXPIRE_AFTER)] = 120;
@@ -660,9 +678,9 @@ public:
doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.equitherm.n_factor|float(0)|round(3) }}"); doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.equitherm.n_factor|float(0)|round(3) }}");
doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("settings/set")); doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("settings/set"));
doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"equitherm\": {\"n_factor\" : {{ value }}}}"); doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"equitherm\": {\"n_factor\" : {{ value }}}}");
doc[FPSTR(HA_MIN)] = 0.001; doc[FPSTR(HA_MIN)] = 0.001f;
doc[FPSTR(HA_MAX)] = 10; doc[FPSTR(HA_MAX)] = 10;
doc[FPSTR(HA_STEP)] = 0.001; doc[FPSTR(HA_STEP)] = 0.001f;
doc[FPSTR(HA_MODE)] = "box"; doc[FPSTR(HA_MODE)] = "box";
doc[FPSTR(HA_EXPIRE_AFTER)] = 120; doc[FPSTR(HA_EXPIRE_AFTER)] = 120;
doc.shrinkToFit(); doc.shrinkToFit();
@@ -683,7 +701,7 @@ public:
doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"equitherm\": {\"k_factor\" : {{ value }}}}"); doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"equitherm\": {\"k_factor\" : {{ value }}}}");
doc[FPSTR(HA_MIN)] = 0; doc[FPSTR(HA_MIN)] = 0;
doc[FPSTR(HA_MAX)] = 10; doc[FPSTR(HA_MAX)] = 10;
doc[FPSTR(HA_STEP)] = 0.01; doc[FPSTR(HA_STEP)] = 0.01f;
doc[FPSTR(HA_MODE)] = "box"; doc[FPSTR(HA_MODE)] = "box";
doc[FPSTR(HA_EXPIRE_AFTER)] = 120; doc[FPSTR(HA_EXPIRE_AFTER)] = 120;
doc.shrinkToFit(); doc.shrinkToFit();
@@ -706,7 +724,7 @@ public:
doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"equitherm\": {\"t_factor\" : {{ value }}}}"); doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"equitherm\": {\"t_factor\" : {{ value }}}}");
doc[FPSTR(HA_MIN)] = 0; doc[FPSTR(HA_MIN)] = 0;
doc[FPSTR(HA_MAX)] = 10; doc[FPSTR(HA_MAX)] = 10;
doc[FPSTR(HA_STEP)] = 0.01; doc[FPSTR(HA_STEP)] = 0.01f;
doc[FPSTR(HA_MODE)] = "box"; doc[FPSTR(HA_MODE)] = "box";
doc[FPSTR(HA_EXPIRE_AFTER)] = 120; doc[FPSTR(HA_EXPIRE_AFTER)] = 120;
doc.shrinkToFit(); doc.shrinkToFit();
@@ -715,48 +733,6 @@ public:
} }
bool publishSwitchTuning(bool enabledByDefault = true) {
JsonDocument doc;
doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault;
doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("tuning"));
doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("tuning"));
doc[FPSTR(HA_ENTITY_CATEGORY)] = F("config");
doc[FPSTR(HA_NAME)] = F("Tuning");
doc[FPSTR(HA_ICON)] = F("mdi:tune-vertical");
doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("state"));
doc[FPSTR(HA_STATE_ON)] = true;
doc[FPSTR(HA_STATE_OFF)] = false;
doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.tuning.enable }}");
doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("state/set"));
doc[FPSTR(HA_PAYLOAD_ON)] = F("{\"tuning\": {\"enable\" : true}}");
doc[FPSTR(HA_PAYLOAD_OFF)] = F("{\"tuning\": {\"enable\" : false}}");
doc[FPSTR(HA_EXPIRE_AFTER)] = 120;
doc.shrinkToFit();
return this->publish(this->getTopic(FPSTR(HA_ENTITY_SWITCH), F("tuning")).c_str(), doc);
}
bool publishSelectTuningRegulator(bool enabledByDefault = true) {
JsonDocument doc;
doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status"));
doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("state/set"));
doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"tuning\": {\"regulator\": {% if value == 'Equitherm' %}0{% elif value == 'PID' %}1{% endif %}}}");
doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault;
doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("tuning_regulator"));
doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("tuning_regulator"));
doc[FPSTR(HA_ENTITY_CATEGORY)] = F("config");
doc[FPSTR(HA_NAME)] = F("Tuning regulator");
doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("state"));
doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{% if value_json.tuning.regulator == 0 %}Equitherm{% elif value_json.tuning.regulator == 1 %}PID{% endif %}");
doc[FPSTR(HA_OPTIONS)][0] = F("Equitherm");
doc[FPSTR(HA_OPTIONS)][1] = F("PID");
doc[FPSTR(HA_EXPIRE_AFTER)] = 120;
doc.shrinkToFit();
return this->publish(this->getTopic(FPSTR(HA_ENTITY_SELECT), F("tuning_regulator")).c_str(), doc);
}
bool publishBinSensorStatus(bool enabledByDefault = true) { bool publishBinSensorStatus(bool enabledByDefault = true) {
JsonDocument doc; JsonDocument doc;
doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault;
@@ -980,7 +956,7 @@ public:
return this->publish(this->getTopic(FPSTR(HA_ENTITY_SENSOR), F("modulation")).c_str(), doc); return this->publish(this->getTopic(FPSTR(HA_ENTITY_SENSOR), F("modulation")).c_str(), doc);
} }
bool publishSensorPressure(bool enabledByDefault = true) { bool publishSensorPressure(UnitSystem unit = UnitSystem::METRIC, bool enabledByDefault = true) {
JsonDocument doc; JsonDocument doc;
doc[FPSTR(HA_AVAILABILITY)][0][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); doc[FPSTR(HA_AVAILABILITY)][0][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status"));
doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("state")); doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("state"));
@@ -992,7 +968,14 @@ public:
doc[FPSTR(HA_ENTITY_CATEGORY)] = F("diagnostic"); doc[FPSTR(HA_ENTITY_CATEGORY)] = F("diagnostic");
doc[FPSTR(HA_DEVICE_CLASS)] = F("pressure"); doc[FPSTR(HA_DEVICE_CLASS)] = F("pressure");
doc[FPSTR(HA_STATE_CLASS)] = F("measurement"); doc[FPSTR(HA_STATE_CLASS)] = F("measurement");
doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = F("bar");
if (unit == UnitSystem::METRIC) {
doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = F("bar");
} else if (unit == UnitSystem::IMPERIAL) {
doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = F("psi");
}
doc[FPSTR(HA_NAME)] = F("Pressure"); doc[FPSTR(HA_NAME)] = F("Pressure");
doc[FPSTR(HA_ICON)] = F("mdi:gauge"); doc[FPSTR(HA_ICON)] = F("mdi:gauge");
doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("state")); doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("state"));
@@ -1003,7 +986,7 @@ public:
return this->publish(this->getTopic(FPSTR(HA_ENTITY_SENSOR), F("pressure")).c_str(), doc); return this->publish(this->getTopic(FPSTR(HA_ENTITY_SENSOR), F("pressure")).c_str(), doc);
} }
bool publishSensorDhwFlowRate(bool enabledByDefault = true) { bool publishSensorDhwFlowRate(UnitSystem unit = UnitSystem::METRIC, bool enabledByDefault = true) {
JsonDocument doc; JsonDocument doc;
doc[FPSTR(HA_AVAILABILITY)][0][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); doc[FPSTR(HA_AVAILABILITY)][0][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status"));
doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("state")); doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("state"));
@@ -1015,7 +998,14 @@ public:
doc[FPSTR(HA_ENTITY_CATEGORY)] = F("diagnostic"); doc[FPSTR(HA_ENTITY_CATEGORY)] = F("diagnostic");
doc[FPSTR(HA_DEVICE_CLASS)] = F("volume_flow_rate"); doc[FPSTR(HA_DEVICE_CLASS)] = F("volume_flow_rate");
doc[FPSTR(HA_STATE_CLASS)] = F("measurement"); doc[FPSTR(HA_STATE_CLASS)] = F("measurement");
doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = F("L/min");
if (unit == UnitSystem::METRIC) {
doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = F("L/min");
} else if (unit == UnitSystem::IMPERIAL) {
doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = F("gal/min");
}
doc[FPSTR(HA_NAME)] = F("DHW flow rate"); doc[FPSTR(HA_NAME)] = F("DHW flow rate");
doc[FPSTR(HA_ICON)] = F("mdi:water-pump"); doc[FPSTR(HA_ICON)] = F("mdi:water-pump");
doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("state")); doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("state"));
@@ -1027,22 +1017,31 @@ public:
} }
bool publishNumberIndoorTemp(bool enabledByDefault = true) { bool publishNumberIndoorTemp(UnitSystem unit = UnitSystem::METRIC, bool enabledByDefault = true) {
JsonDocument doc; JsonDocument doc;
doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault;
doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("indoor_temp")); doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("indoor_temp"));
doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("indoor_temp")); doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("indoor_temp"));
doc[FPSTR(HA_ENTITY_CATEGORY)] = F("config"); doc[FPSTR(HA_ENTITY_CATEGORY)] = F("config");
doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = F("°C");
if (unit == UnitSystem::METRIC) {
doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR(HA_UNIT_OF_MEASUREMENT_C);
doc[FPSTR(HA_MIN)] = -99;
doc[FPSTR(HA_MAX)] = 99;
} else if (unit == UnitSystem::IMPERIAL) {
doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR(HA_UNIT_OF_MEASUREMENT_F);
doc[FPSTR(HA_MIN)] = -147;
doc[FPSTR(HA_MAX)] = 211;
}
doc[FPSTR(HA_NAME)] = F("Indoor temperature"); doc[FPSTR(HA_NAME)] = F("Indoor temperature");
doc[FPSTR(HA_ICON)] = F("mdi:home-thermometer"); doc[FPSTR(HA_ICON)] = F("mdi:home-thermometer");
doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("state")); doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("state"));
doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.temperatures.indoor|float(0)|round(1) }}"); doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.temperatures.indoor|float(0)|round(1) }}");
doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("state/set")); doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("state/set"));
doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"temperatures\": {\"indoor\":{{ value }}}}"); doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"temperatures\": {\"indoor\":{{ value }}}}");
doc[FPSTR(HA_MIN)] = -99; doc[FPSTR(HA_STEP)] = 0.01f;
doc[FPSTR(HA_MAX)] = 99;
doc[FPSTR(HA_STEP)] = 0.01;
doc[FPSTR(HA_MODE)] = "box"; doc[FPSTR(HA_MODE)] = "box";
doc[FPSTR(HA_EXPIRE_AFTER)] = 120; doc[FPSTR(HA_EXPIRE_AFTER)] = 120;
doc.shrinkToFit(); doc.shrinkToFit();
@@ -1050,7 +1049,7 @@ public:
return this->publish(this->getTopic(FPSTR(HA_ENTITY_NUMBER), F("indoor_temp")).c_str(), doc); return this->publish(this->getTopic(FPSTR(HA_ENTITY_NUMBER), F("indoor_temp")).c_str(), doc);
} }
bool publishSensorIndoorTemp(bool enabledByDefault = true) { bool publishSensorIndoorTemp(UnitSystem unit = UnitSystem::METRIC, bool enabledByDefault = true) {
JsonDocument doc; JsonDocument doc;
doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status"));
doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault;
@@ -1059,7 +1058,14 @@ public:
doc[FPSTR(HA_ENTITY_CATEGORY)] = F("diagnostic"); doc[FPSTR(HA_ENTITY_CATEGORY)] = F("diagnostic");
doc[FPSTR(HA_DEVICE_CLASS)] = F("temperature"); doc[FPSTR(HA_DEVICE_CLASS)] = F("temperature");
doc[FPSTR(HA_STATE_CLASS)] = F("measurement"); doc[FPSTR(HA_STATE_CLASS)] = F("measurement");
doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = F("°C");
if (unit == UnitSystem::METRIC) {
doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR(HA_UNIT_OF_MEASUREMENT_C);
} else if (unit == UnitSystem::IMPERIAL) {
doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR(HA_UNIT_OF_MEASUREMENT_F);
}
doc[FPSTR(HA_NAME)] = F("Indoor temperature"); doc[FPSTR(HA_NAME)] = F("Indoor temperature");
doc[FPSTR(HA_ICON)] = F("mdi:home-thermometer"); doc[FPSTR(HA_ICON)] = F("mdi:home-thermometer");
doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("state")); doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("state"));
@@ -1070,22 +1076,31 @@ public:
return this->publish(this->getTopic(FPSTR(HA_ENTITY_SENSOR), F("indoor_temp")).c_str(), doc); return this->publish(this->getTopic(FPSTR(HA_ENTITY_SENSOR), F("indoor_temp")).c_str(), doc);
} }
bool publishNumberOutdoorTemp(bool enabledByDefault = true) { bool publishNumberOutdoorTemp(UnitSystem unit = UnitSystem::METRIC, bool enabledByDefault = true) {
JsonDocument doc; JsonDocument doc;
doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault;
doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("outdoor_temp")); doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("outdoor_temp"));
doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("outdoor_temp")); doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("outdoor_temp"));
doc[FPSTR(HA_ENTITY_CATEGORY)] = F("config"); doc[FPSTR(HA_ENTITY_CATEGORY)] = F("config");
doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = F("°C");
if (unit == UnitSystem::METRIC) {
doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR(HA_UNIT_OF_MEASUREMENT_C);
doc[FPSTR(HA_MIN)] = -99;
doc[FPSTR(HA_MAX)] = 99;
} else if (unit == UnitSystem::IMPERIAL) {
doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR(HA_UNIT_OF_MEASUREMENT_F);
doc[FPSTR(HA_MIN)] = -147;
doc[FPSTR(HA_MAX)] = 211;
}
doc[FPSTR(HA_NAME)] = F("Outdoor temperature"); doc[FPSTR(HA_NAME)] = F("Outdoor temperature");
doc[FPSTR(HA_ICON)] = F("mdi:home-thermometer-outline"); doc[FPSTR(HA_ICON)] = F("mdi:home-thermometer-outline");
doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("state")); doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("state"));
doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.temperatures.outdoor|float(0)|round(1) }}"); doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.temperatures.outdoor|float(0)|round(1) }}");
doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("state/set")); doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("state/set"));
doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"temperatures\": {\"outdoor\":{{ value }}}}"); doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"temperatures\": {\"outdoor\":{{ value }}}}");
doc[FPSTR(HA_MIN)] = -99; doc[FPSTR(HA_STEP)] = 0.01f;
doc[FPSTR(HA_MAX)] = 99;
doc[FPSTR(HA_STEP)] = 0.01;
doc[FPSTR(HA_MODE)] = "box"; doc[FPSTR(HA_MODE)] = "box";
doc[FPSTR(HA_EXPIRE_AFTER)] = 120; doc[FPSTR(HA_EXPIRE_AFTER)] = 120;
doc.shrinkToFit(); doc.shrinkToFit();
@@ -1093,7 +1108,7 @@ public:
return this->publish(this->getTopic(FPSTR(HA_ENTITY_NUMBER), F("outdoor_temp")).c_str(), doc); return this->publish(this->getTopic(FPSTR(HA_ENTITY_NUMBER), F("outdoor_temp")).c_str(), doc);
} }
bool publishSensorOutdoorTemp(bool enabledByDefault = true) { bool publishSensorOutdoorTemp(UnitSystem unit = UnitSystem::METRIC, bool enabledByDefault = true) {
JsonDocument doc; JsonDocument doc;
doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status"));
doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault;
@@ -1102,7 +1117,14 @@ public:
doc[FPSTR(HA_ENTITY_CATEGORY)] = F("diagnostic"); doc[FPSTR(HA_ENTITY_CATEGORY)] = F("diagnostic");
doc[FPSTR(HA_DEVICE_CLASS)] = F("temperature"); doc[FPSTR(HA_DEVICE_CLASS)] = F("temperature");
doc[FPSTR(HA_STATE_CLASS)] = F("measurement"); doc[FPSTR(HA_STATE_CLASS)] = F("measurement");
doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = F("°C");
if (unit == UnitSystem::METRIC) {
doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR(HA_UNIT_OF_MEASUREMENT_C);
} else if (unit == UnitSystem::IMPERIAL) {
doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR(HA_UNIT_OF_MEASUREMENT_F);
}
doc[FPSTR(HA_NAME)] = F("Outdoor temperature"); doc[FPSTR(HA_NAME)] = F("Outdoor temperature");
doc[FPSTR(HA_ICON)] = F("mdi:home-thermometer-outline"); doc[FPSTR(HA_ICON)] = F("mdi:home-thermometer-outline");
doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("state")); doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("state"));
@@ -1113,7 +1135,7 @@ public:
return this->publish(this->getTopic(FPSTR(HA_ENTITY_SENSOR), F("outdoor_temp")).c_str(), doc); return this->publish(this->getTopic(FPSTR(HA_ENTITY_SENSOR), F("outdoor_temp")).c_str(), doc);
} }
bool publishSensorHeatingTemp(bool enabledByDefault = true) { bool publishSensorHeatingTemp(UnitSystem unit = UnitSystem::METRIC, bool enabledByDefault = true) {
JsonDocument doc; JsonDocument doc;
doc[FPSTR(HA_AVAILABILITY)][0][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); doc[FPSTR(HA_AVAILABILITY)][0][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status"));
doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("state")); doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("state"));
@@ -1125,7 +1147,14 @@ public:
doc[FPSTR(HA_ENTITY_CATEGORY)] = F("diagnostic"); doc[FPSTR(HA_ENTITY_CATEGORY)] = F("diagnostic");
doc[FPSTR(HA_DEVICE_CLASS)] = F("temperature"); doc[FPSTR(HA_DEVICE_CLASS)] = F("temperature");
doc[FPSTR(HA_STATE_CLASS)] = F("measurement"); doc[FPSTR(HA_STATE_CLASS)] = F("measurement");
doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = F("°C");
if (unit == UnitSystem::METRIC) {
doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR(HA_UNIT_OF_MEASUREMENT_C);
} else if (unit == UnitSystem::IMPERIAL) {
doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR(HA_UNIT_OF_MEASUREMENT_F);
}
doc[FPSTR(HA_NAME)] = F("Heating temperature"); doc[FPSTR(HA_NAME)] = F("Heating temperature");
doc[FPSTR(HA_ICON)] = F("mdi:radiator"); doc[FPSTR(HA_ICON)] = F("mdi:radiator");
doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("state")); doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("state"));
@@ -1136,7 +1165,37 @@ public:
return this->publish(this->getTopic(FPSTR(HA_ENTITY_SENSOR), F("heating_temp")).c_str(), doc); return this->publish(this->getTopic(FPSTR(HA_ENTITY_SENSOR), F("heating_temp")).c_str(), doc);
} }
bool publishSensorDhwTemp(bool enabledByDefault = true) { bool publishSensorHeatingReturnTemp(UnitSystem unit = UnitSystem::METRIC, bool enabledByDefault = true) {
JsonDocument doc;
doc[FPSTR(HA_AVAILABILITY)][0][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status"));
doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("state"));
doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_VALUE_TEMPLATE)] = F("{{ iif(value_json.states.otStatus, 'online', 'offline') }}");
doc[FPSTR(HA_AVAILABILITY_MODE)] = F("all");
doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault;
doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("heating_return_temp"));
doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("heating_return_temp"));
doc[FPSTR(HA_ENTITY_CATEGORY)] = F("diagnostic");
doc[FPSTR(HA_DEVICE_CLASS)] = F("temperature");
doc[FPSTR(HA_STATE_CLASS)] = F("measurement");
if (unit == UnitSystem::METRIC) {
doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR(HA_UNIT_OF_MEASUREMENT_C);
} else if (unit == UnitSystem::IMPERIAL) {
doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR(HA_UNIT_OF_MEASUREMENT_F);
}
doc[FPSTR(HA_NAME)] = F("Heating return temperature");
doc[FPSTR(HA_ICON)] = F("mdi:radiator");
doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("state"));
doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.temperatures.heatingReturn|float(0)|round(2) }}");
doc[FPSTR(HA_EXPIRE_AFTER)] = 120;
doc.shrinkToFit();
return this->publish(this->getTopic(FPSTR(HA_ENTITY_SENSOR), F("heating_return_temp")).c_str(), doc);
}
bool publishSensorDhwTemp(UnitSystem unit = UnitSystem::METRIC, bool enabledByDefault = true) {
JsonDocument doc; JsonDocument doc;
doc[FPSTR(HA_AVAILABILITY)][0][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); doc[FPSTR(HA_AVAILABILITY)][0][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status"));
doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("state")); doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("state"));
@@ -1148,7 +1207,14 @@ public:
doc[FPSTR(HA_ENTITY_CATEGORY)] = F("diagnostic"); doc[FPSTR(HA_ENTITY_CATEGORY)] = F("diagnostic");
doc[FPSTR(HA_DEVICE_CLASS)] = F("temperature"); doc[FPSTR(HA_DEVICE_CLASS)] = F("temperature");
doc[FPSTR(HA_STATE_CLASS)] = F("measurement"); doc[FPSTR(HA_STATE_CLASS)] = F("measurement");
doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = F("°C");
if (unit == UnitSystem::METRIC) {
doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR(HA_UNIT_OF_MEASUREMENT_C);
} else if (unit == UnitSystem::IMPERIAL) {
doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR(HA_UNIT_OF_MEASUREMENT_F);
}
doc[FPSTR(HA_NAME)] = F("DHW temperature"); doc[FPSTR(HA_NAME)] = F("DHW temperature");
doc[FPSTR(HA_ICON)] = F("mdi:water-pump"); doc[FPSTR(HA_ICON)] = F("mdi:water-pump");
doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("state")); doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("state"));
@@ -1159,8 +1225,38 @@ public:
return this->publish(this->getTopic(FPSTR(HA_ENTITY_SENSOR), F("dhw_temp")).c_str(), doc); return this->publish(this->getTopic(FPSTR(HA_ENTITY_SENSOR), F("dhw_temp")).c_str(), doc);
} }
bool publishSensorExhaustTemp(UnitSystem unit = UnitSystem::METRIC, bool enabledByDefault = true) {
JsonDocument doc;
doc[FPSTR(HA_AVAILABILITY)][0][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status"));
doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("state"));
doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_VALUE_TEMPLATE)] = F("{{ iif(value_json.states.otStatus, 'online', 'offline') }}");
doc[FPSTR(HA_AVAILABILITY_MODE)] = F("all");
doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault;
doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("exhaust_temp"));
doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("exhaust_temp"));
doc[FPSTR(HA_ENTITY_CATEGORY)] = F("diagnostic");
doc[FPSTR(HA_DEVICE_CLASS)] = F("temperature");
doc[FPSTR(HA_STATE_CLASS)] = F("measurement");
bool publishClimateHeating(byte minTemp = 20, byte maxTemp = 90, byte currentTempSource = HaHelper::TEMP_SOURCE_HEATING, bool enabledByDefault = true) { if (unit == UnitSystem::METRIC) {
doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR(HA_UNIT_OF_MEASUREMENT_C);
} else if (unit == UnitSystem::IMPERIAL) {
doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR(HA_UNIT_OF_MEASUREMENT_F);
}
doc[FPSTR(HA_NAME)] = F("Exhaust temperature");
doc[FPSTR(HA_ICON)] = F("mdi:smoke");
doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("state"));
doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.temperatures.exhaust|float(0)|round(2) }}");
doc[FPSTR(HA_EXPIRE_AFTER)] = 120;
doc.shrinkToFit();
return this->publish(this->getTopic(FPSTR(HA_ENTITY_SENSOR), F("exhaust_temp")).c_str(), doc);
}
bool publishClimateHeating(UnitSystem unit = UnitSystem::METRIC, byte minTemp = 20, byte maxTemp = 90, byte currentTempSource = HaHelper::TEMP_SOURCE_HEATING, bool enabledByDefault = true) {
JsonDocument doc; JsonDocument doc;
doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status"));
doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault;
@@ -1186,6 +1282,13 @@ public:
doc[FPSTR(HA_TEMPERATURE_STATE_TOPIC)] = this->getDeviceTopic(F("settings")); doc[FPSTR(HA_TEMPERATURE_STATE_TOPIC)] = this->getDeviceTopic(F("settings"));
doc[FPSTR(HA_TEMPERATURE_STATE_TEMPLATE)] = F("{{ value_json.heating.target|float(0)|round(1) }}"); doc[FPSTR(HA_TEMPERATURE_STATE_TEMPLATE)] = F("{{ value_json.heating.target|float(0)|round(1) }}");
if (unit == UnitSystem::METRIC) {
doc[FPSTR(HA_TEMPERATURE_UNIT)] = "C";
} else if (unit == UnitSystem::IMPERIAL) {
doc[FPSTR(HA_TEMPERATURE_UNIT)] = "F";
}
doc[FPSTR(HA_MODE_COMMAND_TOPIC)] = this->getDeviceTopic(F("settings/set")); doc[FPSTR(HA_MODE_COMMAND_TOPIC)] = this->getDeviceTopic(F("settings/set"));
doc[FPSTR(HA_MODE_COMMAND_TEMPLATE)] = F("{% if value == 'heat' %}{\"heating\": {\"enable\" : true}}" doc[FPSTR(HA_MODE_COMMAND_TEMPLATE)] = F("{% if value == 'heat' %}{\"heating\": {\"enable\" : true}}"
"{% elif value == 'off' %}{\"heating\": {\"enable\" : false}}{% endif %}"); "{% elif value == 'off' %}{\"heating\": {\"enable\" : false}}{% endif %}");
@@ -1206,14 +1309,14 @@ public:
doc[FPSTR(HA_MIN_TEMP)] = minTemp; doc[FPSTR(HA_MIN_TEMP)] = minTemp;
doc[FPSTR(HA_MAX_TEMP)] = maxTemp; doc[FPSTR(HA_MAX_TEMP)] = maxTemp;
doc[FPSTR(HA_TEMP_STEP)] = 0.5; doc[FPSTR(HA_TEMP_STEP)] = 0.5f;
doc[FPSTR(HA_EXPIRE_AFTER)] = 120; doc[FPSTR(HA_EXPIRE_AFTER)] = 120;
doc.shrinkToFit(); doc.shrinkToFit();
return this->publish(this->getTopic(FPSTR(HA_ENTITY_CLIMATE), F("heating"), '_').c_str(), doc); return this->publish(this->getTopic(FPSTR(HA_ENTITY_CLIMATE), F("heating"), '_').c_str(), doc);
} }
bool publishClimateDhw(byte minTemp = 40, byte maxTemp = 60, bool enabledByDefault = true) { bool publishClimateDhw(UnitSystem unit = UnitSystem::METRIC, byte minTemp = 40, byte maxTemp = 60, bool enabledByDefault = true) {
JsonDocument doc; JsonDocument doc;
doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status"));
doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault;
@@ -1229,7 +1332,14 @@ public:
doc[FPSTR(HA_TEMPERATURE_COMMAND_TEMPLATE)] = F("{\"dhw\": {\"target\" : {{ value|int(0) }}}}"); doc[FPSTR(HA_TEMPERATURE_COMMAND_TEMPLATE)] = F("{\"dhw\": {\"target\" : {{ value|int(0) }}}}");
doc[FPSTR(HA_TEMPERATURE_STATE_TOPIC)] = this->getDeviceTopic(F("settings")); doc[FPSTR(HA_TEMPERATURE_STATE_TOPIC)] = this->getDeviceTopic(F("settings"));
doc[FPSTR(HA_TEMPERATURE_STATE_TEMPLATE)] = F("{{ value_json.dhw.target|int(0) }}"); doc[FPSTR(HA_TEMPERATURE_STATE_TEMPLATE)] = F("{{ value_json.dhw.target|float(0)|round(1) }}");
if (unit == UnitSystem::METRIC) {
doc[FPSTR(HA_TEMPERATURE_UNIT)] = "C";
} else if (unit == UnitSystem::IMPERIAL) {
doc[FPSTR(HA_TEMPERATURE_UNIT)] = "F";
}
doc[FPSTR(HA_MODE_COMMAND_TOPIC)] = this->getDeviceTopic(F("settings/set")); doc[FPSTR(HA_MODE_COMMAND_TOPIC)] = this->getDeviceTopic(F("settings/set"));
doc[FPSTR(HA_MODE_COMMAND_TEMPLATE)] = F("{% if value == 'heat' %}{\"dhw\": {\"enable\" : true}}" doc[FPSTR(HA_MODE_COMMAND_TEMPLATE)] = F("{% if value == 'heat' %}{\"dhw\": {\"enable\" : true}}"

View File

@@ -1,6 +1,8 @@
#include <Blinker.h> #include <Blinker.h>
extern Network::Manager* network; using namespace NetworkUtils;
extern NetworkMgr* network;
extern MqttTask* tMqtt; extern MqttTask* tMqtt;
extern OpenThermTask* tOt; extern OpenThermTask* tOt;
extern FileData fsSettings, fsNetworkSettings; extern FileData fsSettings, fsNetworkSettings;
@@ -27,7 +29,6 @@ protected:
enum class PumpStartReason {NONE, HEATING, ANTISTUCK}; enum class PumpStartReason {NONE, HEATING, ANTISTUCK};
Blinker* blinker = nullptr; Blinker* blinker = nullptr;
bool blinkerInitialized = false;
unsigned long firstFailConnect = 0; unsigned long firstFailConnect = 0;
unsigned long lastHeapInfo = 0; unsigned long lastHeapInfo = 0;
unsigned int minFreeHeap = 0; unsigned int minFreeHeap = 0;
@@ -39,29 +40,21 @@ protected:
unsigned long externalPumpStartTime = 0; unsigned long externalPumpStartTime = 0;
bool telnetStarted = false; bool telnetStarted = false;
const char* getTaskName() { #if defined(ARDUINO_ARCH_ESP32)
const char* getTaskName() override {
return "Main"; return "Main";
} }
/*int getTaskCore() { /*BaseType_t getTaskCore() override {
return 1; return 1;
}*/ }*/
int getTaskPriority() { int getTaskPriority() override {
return 3; return 3;
} }
#endif
void setup() { void setup() {}
#ifdef LED_STATUS_PIN
pinMode(LED_STATUS_PIN, OUTPUT);
digitalWrite(LED_STATUS_PIN, LOW);
#endif
if (settings.externalPump.pin != 0) {
pinMode(settings.externalPump.pin, OUTPUT);
digitalWrite(settings.externalPump.pin, LOW);
}
}
void loop() { void loop() {
network->loop(); network->loop();
@@ -89,20 +82,31 @@ 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."));
} }
if (!tOt->isEnabled() && settings.opentherm.inPin > 0 && settings.opentherm.outPin > 0 && settings.opentherm.inPin != settings.opentherm.outPin) { vars.states.mqtt = tMqtt->isConnected();
tOt->enable(); vars.sensors.rssi = network->isConnected() ? WiFi.RSSI() : 0;
if (vars.states.emergency && !settings.emergency.enable) {
vars.states.emergency = false;
} }
if (network->isConnected()) { if (network->isConnected()) {
vars.sensors.rssi = WiFi.RSSI();
if (!this->telnetStarted && telnetStream != nullptr) { if (!this->telnetStarted && telnetStream != nullptr) {
telnetStream->begin(23, false); telnetStream->begin(23, false);
this->telnetStarted = true; this->telnetStarted = true;
} }
if (!tMqtt->isEnabled() && strlen(settings.mqtt.server) > 0) { if (settings.mqtt.enable && !tMqtt->isEnabled()) {
tMqtt->enable(); tMqtt->enable();
} else if (!settings.mqtt.enable && tMqtt->isEnabled()) {
tMqtt->disable();
}
if (!vars.states.emergency && settings.emergency.enable && settings.emergency.onMqttFault && !tMqtt->isEnabled()) {
vars.states.emergency = true;
} else if (vars.states.emergency && !settings.emergency.onMqttFault) {
vars.states.emergency = false;
} }
if (this->firstFailConnect != 0) { if (this->firstFailConnect != 0) {
@@ -126,12 +130,12 @@ protected:
tMqtt->disable(); tMqtt->disable();
} }
if (settings.emergency.enable && !vars.states.emergency) { if (!vars.states.emergency && settings.emergency.enable && settings.emergency.onNetworkFault) {
if (this->firstFailConnect == 0) { if (this->firstFailConnect == 0) {
this->firstFailConnect = millis(); this->firstFailConnect = millis();
} }
if (millis() - this->firstFailConnect > EMERGENCY_TIME_TRESHOLD) { if (millis() - this->firstFailConnect > (settings.emergency.tresholdTime * 1000)) {
vars.states.emergency = true; vars.states.emergency = true;
Log.sinfoln(FPSTR(L_MAIN), F("Emergency mode enabled")); Log.sinfoln(FPSTR(L_MAIN), F("Emergency mode enabled"));
} }
@@ -140,9 +144,7 @@ protected:
this->yield(); this->yield();
#ifdef LED_STATUS_PIN this->ledStatus();
this->ledStatus(LED_STATUS_PIN);
#endif
this->externalPump(); this->externalPump();
this->yield(); this->yield();
@@ -211,16 +213,32 @@ protected:
} }
} }
void ledStatus(uint8_t ledPin) { void ledStatus() {
uint8_t errors[4]; uint8_t errors[4];
uint8_t errCount = 0; uint8_t errCount = 0;
static uint8_t errPos = 0; static uint8_t errPos = 0;
static unsigned long endBlinkTime = 0; static unsigned long endBlinkTime = 0;
static bool ledOn = false; static bool ledOn = false;
static uint8_t configuredGpio = GPIO_IS_NOT_CONFIGURED;
if (!this->blinkerInitialized) { if (settings.system.statusLedGpio != configuredGpio) {
this->blinker->init(ledPin); if (configuredGpio != GPIO_IS_NOT_CONFIGURED) {
this->blinkerInitialized = true; digitalWrite(configuredGpio, LOW);
}
if (GPIO_IS_VALID(settings.system.statusLedGpio)) {
configuredGpio = settings.system.statusLedGpio;
pinMode(configuredGpio, OUTPUT);
digitalWrite(configuredGpio, LOW);
this->blinker->init(configuredGpio);
} else if (configuredGpio != GPIO_IS_NOT_CONFIGURED) {
configuredGpio = GPIO_IS_NOT_CONFIGURED;
}
}
if (configuredGpio == GPIO_IS_NOT_CONFIGURED) {
return;
} }
if (!network->isConnected()) { if (!network->isConnected()) {
@@ -247,14 +265,14 @@ protected:
if (!this->blinker->running() && millis() - endBlinkTime >= 5000) { if (!this->blinker->running() && millis() - endBlinkTime >= 5000) {
if (errCount == 0) { if (errCount == 0) {
if (!ledOn) { if (!ledOn) {
digitalWrite(ledPin, HIGH); digitalWrite(configuredGpio, HIGH);
ledOn = true; ledOn = true;
} }
return; return;
} else if (ledOn) { } else if (ledOn) {
digitalWrite(ledPin, LOW); digitalWrite(configuredGpio, LOW);
ledOn = false; ledOn = false;
endBlinkTime = millis(); endBlinkTime = millis();
return; return;
@@ -275,6 +293,34 @@ protected:
} }
void externalPump() { void externalPump() {
static uint8_t configuredGpio = GPIO_IS_NOT_CONFIGURED;
if (settings.externalPump.gpio != configuredGpio) {
if (configuredGpio != GPIO_IS_NOT_CONFIGURED) {
digitalWrite(configuredGpio, LOW);
}
if (GPIO_IS_VALID(settings.externalPump.gpio)) {
configuredGpio = settings.externalPump.gpio;
pinMode(configuredGpio, OUTPUT);
digitalWrite(configuredGpio, LOW);
} else if (configuredGpio != GPIO_IS_NOT_CONFIGURED) {
configuredGpio = GPIO_IS_NOT_CONFIGURED;
}
}
if (configuredGpio == GPIO_IS_NOT_CONFIGURED) {
if (vars.states.externalPump) {
vars.states.externalPump = false;
vars.parameters.extPumpLastEnableTime = millis();
Log.sinfoln("EXTPUMP", F("Disabled: use = off"));
}
return;
}
if (!vars.states.heating && this->heatingEnabled) { if (!vars.states.heating && this->heatingEnabled) {
this->heatingEnabled = false; this->heatingEnabled = false;
this->heatingDisabledTime = millis(); this->heatingDisabledTime = millis();
@@ -283,11 +329,9 @@ protected:
this->heatingEnabled = true; this->heatingEnabled = true;
} }
if (!settings.externalPump.use || settings.externalPump.pin == 0) { if (!settings.externalPump.use) {
if (vars.states.externalPump) { if (vars.states.externalPump) {
if (settings.externalPump.pin != 0) { digitalWrite(configuredGpio, LOW);
digitalWrite(settings.externalPump.pin, LOW);
}
vars.states.externalPump = false; vars.states.externalPump = false;
vars.parameters.extPumpLastEnableTime = millis(); vars.parameters.extPumpLastEnableTime = millis();
@@ -300,7 +344,7 @@ protected:
if (vars.states.externalPump && !this->heatingEnabled) { if (vars.states.externalPump && !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(settings.externalPump.pin, LOW); digitalWrite(configuredGpio, LOW);
vars.states.externalPump = false; vars.states.externalPump = false;
vars.parameters.extPumpLastEnableTime = millis(); vars.parameters.extPumpLastEnableTime = millis();
@@ -308,7 +352,7 @@ protected:
Log.sinfoln("EXTPUMP", F("Disabled: expired post circulation time")); Log.sinfoln("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(settings.externalPump.pin, LOW); digitalWrite(configuredGpio, LOW);
vars.states.externalPump = false; vars.states.externalPump = false;
vars.parameters.extPumpLastEnableTime = millis(); vars.parameters.extPumpLastEnableTime = millis();
@@ -324,7 +368,7 @@ protected:
this->externalPumpStartTime = millis(); this->externalPumpStartTime = millis();
this->extPumpStartReason = MainTask::PumpStartReason::HEATING; this->extPumpStartReason = MainTask::PumpStartReason::HEATING;
digitalWrite(settings.externalPump.pin, HIGH); digitalWrite(configuredGpio, HIGH);
Log.sinfoln("EXTPUMP", F("Enabled: heating on")); Log.sinfoln("EXTPUMP", F("Enabled: heating on"));
@@ -333,7 +377,7 @@ protected:
this->externalPumpStartTime = millis(); this->externalPumpStartTime = millis();
this->extPumpStartReason = MainTask::PumpStartReason::ANTISTUCK; this->extPumpStartReason = MainTask::PumpStartReason::ANTISTUCK;
digitalWrite(settings.externalPump.pin, HIGH); digitalWrite(configuredGpio, HIGH);
Log.sinfoln("EXTPUMP", F("Enabled: anti stuck")); Log.sinfoln("EXTPUMP", F("Enabled: anti stuck"));
} }

View File

@@ -20,6 +20,11 @@ public:
if (this->client != nullptr) { if (this->client != nullptr) {
if (this->client->connected()) { if (this->client->connected()) {
this->client->stop(); this->client->stop();
if (this->connected) {
this->onDisconnect();
this->connected = false;
}
} }
delete this->client; delete this->client;
@@ -31,6 +36,12 @@ public:
void disable() { void disable() {
this->client->stop(); this->client->stop();
if (this->connected) {
this->onDisconnect();
this->connected = false;
}
Task::disable(); Task::disable();
Log.sinfoln(FPSTR(L_MQTT), F("Disabled")); Log.sinfoln(FPSTR(L_MQTT), F("Disabled"));
@@ -42,15 +53,25 @@ public:
Log.sinfoln(FPSTR(L_MQTT), F("Enabled")); Log.sinfoln(FPSTR(L_MQTT), F("Enabled"));
} }
bool isConnected() { inline bool isConnected() {
return this->connected; return this->connected;
} }
inline void resetPublishedSettingsTime() {
this->prevPubSettingsTime = 0;
}
inline void resetPublishedVarsTime() {
this->prevPubVarsTime = 0;
}
protected: protected:
MqttWiFiClient* wifiClient = nullptr; MqttWiFiClient* wifiClient = nullptr;
MqttClient* client = nullptr; MqttClient* client = nullptr;
HaHelper* haHelper = nullptr; HaHelper* haHelper = nullptr;
MqttWriter* writer = nullptr; MqttWriter* writer = nullptr;
UnitSystem currentUnitSystem = UnitSystem::METRIC;
bool currentHomeAssistantDiscovery = false;
unsigned short readyForSendTime = 15000; unsigned short readyForSendTime = 15000;
unsigned long lastReconnectTime = 0; unsigned long lastReconnectTime = 0;
unsigned long connectedTime = 0; unsigned long connectedTime = 0;
@@ -60,19 +81,21 @@ protected:
bool connected = false; bool connected = false;
bool newConnection = false; bool newConnection = false;
const char* getTaskName() { #if defined(ARDUINO_ARCH_ESP32)
const char* getTaskName() override {
return "Mqtt"; return "Mqtt";
} }
/*int getTaskCore() { /*BaseType_t getTaskCore() override {
return 1; return 1;
}*/ }*/
int getTaskPriority() { int getTaskPriority() override {
return 1; return 2;
} }
#endif
bool isReadyForSend() { inline bool isReadyForSend() {
return millis() - this->connectedTime > this->readyForSendTime; return millis() - this->connectedTime > this->readyForSendTime;
} }
@@ -175,13 +198,15 @@ protected:
this->onConnect(); this->onConnect();
} }
if (!this->connected && settings.emergency.enable && !vars.states.emergency && millis() - this->disconnectedTime > EMERGENCY_TIME_TRESHOLD) { if (settings.emergency.enable && settings.emergency.onMqttFault) {
vars.states.emergency = true; if (!this->connected && !vars.states.emergency && millis() - this->disconnectedTime > (settings.emergency.tresholdTime * 1000)) {
Log.sinfoln(FPSTR(L_MQTT), F("Emergency mode enabled")); vars.states.emergency = true;
Log.sinfoln(FPSTR(L_MQTT), F("Emergency mode enabled"));
} else if (this->connected && vars.states.emergency && millis() - this->connectedTime > 10000) { } else if (this->connected && vars.states.emergency && millis() - this->connectedTime > 10000) {
vars.states.emergency = false; vars.states.emergency = false;
Log.sinfoln(FPSTR(L_MQTT), F("Emergency mode disabled")); Log.sinfoln(FPSTR(L_MQTT), F("Emergency mode disabled"));
}
} }
if (!this->connected) { if (!this->connected) {
@@ -213,14 +238,24 @@ protected:
} }
// publish ha entities if not published // publish ha entities if not published
if (this->newConnection) { if (settings.mqtt.homeAssistantDiscovery) {
this->publishHaEntities(); if (this->newConnection || !this->currentHomeAssistantDiscovery || this->currentUnitSystem != settings.system.unitSystem) {
this->publishNonStaticHaEntities(true); this->publishHaEntities();
this->newConnection = false; this->publishNonStaticHaEntities(true);
this->currentHomeAssistantDiscovery = true;
this->currentUnitSystem = settings.system.unitSystem;
} else { } else {
// publish non static ha entities // publish non static ha entities
this->publishNonStaticHaEntities(); this->publishNonStaticHaEntities();
}
} else if (this->currentHomeAssistantDiscovery) {
this->currentHomeAssistantDiscovery = false;
}
if (this->newConnection) {
this->newConnection = false;
} }
} }
@@ -276,61 +311,35 @@ protected:
Log.swarningln(FPSTR(L_MQTT_MSG), F("Not valid json")); Log.swarningln(FPSTR(L_MQTT_MSG), F("Not valid json"));
return; return;
} }
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(this->haHelper->getDeviceTopic("state/set").c_str(), nullptr, 0, true);
this->updateVariables(doc);
if (jsonToVars(doc, vars)) {
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(this->haHelper->getDeviceTopic("settings/set").c_str(), nullptr, 0, true);
this->updateSettings(doc);
if (safeJsonToSettings(doc, settings)) {
this->resetPublishedSettingsTime();
fsSettings.update();
}
} }
} }
bool updateSettings(JsonDocument& doc) {
bool changed = safeJsonToSettings(doc, settings);
doc.clear();
doc.shrinkToFit();
if (changed) {
this->prevPubSettingsTime = 0;
fsSettings.update();
return true;
}
return false;
}
bool updateVariables(JsonDocument& doc) {
bool changed = jsonToVars(doc, vars);
doc.clear();
doc.shrinkToFit();
if (changed) {
this->prevPubVarsTime = 0;
return true;
}
return false;
}
void publishHaEntities() { void publishHaEntities() {
// emergency
this->haHelper->publishSwitchEmergency();
this->haHelper->publishNumberEmergencyTarget();
this->haHelper->publishSwitchEmergencyUseEquitherm();
this->haHelper->publishSwitchEmergencyUsePid();
// heating // heating
this->haHelper->publishSwitchHeating(false); this->haHelper->publishSwitchHeating(false);
this->haHelper->publishSwitchHeatingTurbo(); this->haHelper->publishSwitchHeatingTurbo();
this->haHelper->publishNumberHeatingHysteresis(); this->haHelper->publishNumberHeatingHysteresis(settings.system.unitSystem);
this->haHelper->publishSensorHeatingSetpoint(false); this->haHelper->publishSensorHeatingSetpoint(settings.system.unitSystem, false);
this->haHelper->publishSensorBoilerHeatingMinTemp(false); this->haHelper->publishSensorBoilerHeatingMinTemp(settings.system.unitSystem, false);
this->haHelper->publishSensorBoilerHeatingMaxTemp(false); this->haHelper->publishSensorBoilerHeatingMaxTemp(settings.system.unitSystem, false);
this->haHelper->publishNumberHeatingMinTemp(false); this->haHelper->publishNumberHeatingMinTemp(settings.system.unitSystem, false);
this->haHelper->publishNumberHeatingMaxTemp(false); this->haHelper->publishNumberHeatingMaxTemp(settings.system.unitSystem, false);
this->haHelper->publishNumberHeatingMaxModulation(false); this->haHelper->publishNumberHeatingMaxModulation(false);
// pid // pid
@@ -339,8 +348,8 @@ protected:
this->haHelper->publishNumberPidFactorI(); this->haHelper->publishNumberPidFactorI();
this->haHelper->publishNumberPidFactorD(); this->haHelper->publishNumberPidFactorD();
this->haHelper->publishNumberPidDt(false); this->haHelper->publishNumberPidDt(false);
this->haHelper->publishNumberPidMinTemp(false); this->haHelper->publishNumberPidMinTemp(settings.system.unitSystem, false);
this->haHelper->publishNumberPidMaxTemp(false); this->haHelper->publishNumberPidMaxTemp(settings.system.unitSystem, false);
// equitherm // equitherm
this->haHelper->publishSwitchEquitherm(); this->haHelper->publishSwitchEquitherm();
@@ -348,10 +357,6 @@ protected:
this->haHelper->publishNumberEquithermFactorK(); this->haHelper->publishNumberEquithermFactorK();
this->haHelper->publishNumberEquithermFactorT(); this->haHelper->publishNumberEquithermFactorT();
// tuning
this->haHelper->publishSwitchTuning();
this->haHelper->publishSelectTuningRegulator();
// states // states
this->haHelper->publishBinSensorStatus(); this->haHelper->publishBinSensorStatus();
this->haHelper->publishBinSensorOtStatus(); this->haHelper->publishBinSensorOtStatus();
@@ -362,14 +367,16 @@ protected:
// sensors // sensors
this->haHelper->publishSensorModulation(false); this->haHelper->publishSensorModulation(false);
this->haHelper->publishSensorPressure(false); this->haHelper->publishSensorPressure(settings.system.unitSystem, false);
this->haHelper->publishSensorFaultCode(); this->haHelper->publishSensorFaultCode();
this->haHelper->publishSensorRssi(false); this->haHelper->publishSensorRssi(false);
this->haHelper->publishSensorUptime(false); this->haHelper->publishSensorUptime(false);
// temperatures // temperatures
this->haHelper->publishNumberIndoorTemp(); this->haHelper->publishNumberIndoorTemp(settings.system.unitSystem);
this->haHelper->publishSensorHeatingTemp(); 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->publishButtonRestart(false);
@@ -379,27 +386,36 @@ protected:
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 _isStupidMode, _editableOutdoorTemp, _editableIndoorTemp, _dhwPresent = false; static bool _noRegulators, _editableOutdoorTemp, _editableIndoorTemp, _dhwPresent = false;
bool published = false; bool published = false;
bool isStupidMode = !settings.pid.enable && !settings.equitherm.enable; bool noRegulators = !settings.opentherm.nativeHeatingControl && !settings.pid.enable && !settings.equitherm.enable;
byte heatingMinTemp = isStupidMode ? settings.heating.minTemp : 10; byte heatingMinTemp = 0;
byte heatingMaxTemp = isStupidMode ? settings.heating.maxTemp : 30; byte heatingMaxTemp = 0;
bool editableOutdoorTemp = settings.sensors.outdoor.type == 1; bool editableOutdoorTemp = settings.sensors.outdoor.type == SensorType::MANUAL;
bool editableIndoorTemp = settings.sensors.indoor.type == 1; 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->publishSwitchDhw(false);
this->haHelper->publishSensorBoilerDhwMinTemp(false); this->haHelper->publishSensorBoilerDhwMinTemp(settings.system.unitSystem, false);
this->haHelper->publishSensorBoilerDhwMaxTemp(false); this->haHelper->publishSensorBoilerDhwMaxTemp(settings.system.unitSystem, false);
this->haHelper->publishNumberDhwMinTemp(false); this->haHelper->publishNumberDhwMinTemp(settings.system.unitSystem, false);
this->haHelper->publishNumberDhwMaxTemp(false); this->haHelper->publishNumberDhwMaxTemp(settings.system.unitSystem, false);
this->haHelper->publishBinSensorDhw(); this->haHelper->publishBinSensorDhw();
this->haHelper->publishSensorDhwTemp(); this->haHelper->publishSensorDhwTemp(settings.system.unitSystem);
this->haHelper->publishSensorDhwFlowRate(false); this->haHelper->publishSensorDhwFlowRate(settings.system.unitSystem, false);
} else { } else {
this->haHelper->deleteSwitchDhw(); this->haHelper->deleteSwitchDhw();
@@ -417,30 +433,17 @@ protected:
published = true; published = true;
} }
if (force || _heatingMinTemp != heatingMinTemp || _heatingMaxTemp != heatingMaxTemp) { if (force || _noRegulators != noRegulators || _heatingMinTemp != heatingMinTemp || _heatingMaxTemp != heatingMaxTemp) {
if (settings.heating.target < heatingMinTemp || settings.heating.target > heatingMaxTemp) {
settings.heating.target = constrain(settings.heating.target, heatingMinTemp, heatingMaxTemp);
}
_heatingMinTemp = heatingMinTemp; _heatingMinTemp = heatingMinTemp;
_heatingMaxTemp = heatingMaxTemp; _heatingMaxTemp = heatingMaxTemp;
_isStupidMode = isStupidMode; _noRegulators = noRegulators;
this->haHelper->publishNumberHeatingTarget(heatingMinTemp, heatingMaxTemp, false); this->haHelper->publishNumberHeatingTarget(settings.system.unitSystem, heatingMinTemp, heatingMaxTemp, false);
this->haHelper->publishClimateHeating( this->haHelper->publishClimateHeating(
settings.system.unitSystem,
heatingMinTemp, heatingMinTemp,
heatingMaxTemp, heatingMaxTemp,
isStupidMode ? HaHelper::TEMP_SOURCE_HEATING : HaHelper::TEMP_SOURCE_INDOOR noRegulators ? HaHelper::TEMP_SOURCE_HEATING : HaHelper::TEMP_SOURCE_INDOOR
);
published = true;
} else if (_isStupidMode != isStupidMode) {
_isStupidMode = isStupidMode;
this->haHelper->publishClimateHeating(
heatingMinTemp,
heatingMaxTemp,
isStupidMode ? HaHelper::TEMP_SOURCE_HEATING : HaHelper::TEMP_SOURCE_INDOOR
); );
published = true; published = true;
@@ -450,8 +453,8 @@ protected:
_dhwMinTemp = settings.dhw.minTemp; _dhwMinTemp = settings.dhw.minTemp;
_dhwMaxTemp = settings.dhw.maxTemp; _dhwMaxTemp = settings.dhw.maxTemp;
this->haHelper->publishNumberDhwTarget(settings.dhw.minTemp, settings.dhw.maxTemp, false); this->haHelper->publishNumberDhwTarget(settings.system.unitSystem, settings.dhw.minTemp, settings.dhw.maxTemp, false);
this->haHelper->publishClimateDhw(settings.dhw.minTemp, settings.dhw.maxTemp); this->haHelper->publishClimateDhw(settings.system.unitSystem, settings.dhw.minTemp, settings.dhw.maxTemp);
published = true; published = true;
} }
@@ -461,10 +464,10 @@ protected:
if (editableOutdoorTemp) { if (editableOutdoorTemp) {
this->haHelper->deleteSensorOutdoorTemp(); this->haHelper->deleteSensorOutdoorTemp();
this->haHelper->publishNumberOutdoorTemp(); this->haHelper->publishNumberOutdoorTemp(settings.system.unitSystem);
} else { } else {
this->haHelper->deleteNumberOutdoorTemp(); this->haHelper->deleteNumberOutdoorTemp();
this->haHelper->publishSensorOutdoorTemp(); this->haHelper->publishSensorOutdoorTemp(settings.system.unitSystem);
} }
published = true; published = true;
@@ -475,10 +478,10 @@ protected:
if (editableIndoorTemp) { if (editableIndoorTemp) {
this->haHelper->deleteSensorIndoorTemp(); this->haHelper->deleteSensorIndoorTemp();
this->haHelper->publishNumberIndoorTemp(); this->haHelper->publishNumberIndoorTemp(settings.system.unitSystem);
} else { } else {
this->haHelper->deleteNumberIndoorTemp(); this->haHelper->deleteNumberIndoorTemp();
this->haHelper->publishSensorIndoorTemp(); this->haHelper->publishSensorIndoorTemp(settings.system.unitSystem);
} }
published = true; published = true;

File diff suppressed because it is too large Load Diff

View File

@@ -14,7 +14,9 @@ using WebServer = ESP8266WebServer;
#include <UpgradeHandler.h> #include <UpgradeHandler.h>
#include <DNSServer.h> #include <DNSServer.h>
extern Network::Manager* network; using namespace NetworkUtils;
extern NetworkMgr* network;
extern FileData fsSettings, fsNetworkSettings; extern FileData fsSettings, fsNetworkSettings;
extern MqttTask* tMqtt; extern MqttTask* tMqtt;
@@ -53,17 +55,19 @@ protected:
unsigned long webServerChangeState = 0; unsigned long webServerChangeState = 0;
unsigned long dnsServerChangeState = 0; unsigned long dnsServerChangeState = 0;
const char* getTaskName() { #if defined(ARDUINO_ARCH_ESP32)
const char* getTaskName() override {
return "Portal"; return "Portal";
} }
/*int getTaskCore() { /*BaseType_t getTaskCore() override {
return 1; return 1;
}*/ }*/
int getTaskPriority() { int getTaskPriority() override {
return 0; return 1;
} }
#endif
void setup() { void setup() {
this->dnsServer->setTTL(0); this->dnsServer->setTTL(0);
@@ -86,9 +90,21 @@ protected:
this->webServer->addHandler(indexPage);*/ this->webServer->addHandler(indexPage);*/
this->webServer->addHandler(new StaticPage("/", &LittleFS, "/index.html", PORTAL_CACHE)); this->webServer->addHandler(new StaticPage("/", &LittleFS, "/index.html", PORTAL_CACHE));
// dashboard page
auto dashboardPage = (new StaticPage("/dashboard.html", &LittleFS, "/dashboard.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(dashboardPage);
// restart // restart
this->webServer->on("/restart.html", HTTP_GET, [this]() { this->webServer->on("/restart.html", HTTP_GET, [this]() {
if (this->isNeedAuth()) { if (this->isAuthRequired()) {
if (!this->webServer->authenticate(settings.portal.login, settings.portal.password)) { if (!this->webServer->authenticate(settings.portal.login, settings.portal.password)) {
this->webServer->send(401); this->webServer->send(401);
return; return;
@@ -103,7 +119,7 @@ protected:
// network settings page // network settings page
auto networkPage = (new StaticPage("/network.html", &LittleFS, "/network.html", PORTAL_CACHE)) auto networkPage = (new StaticPage("/network.html", &LittleFS, "/network.html", PORTAL_CACHE))
->setBeforeSendCallback([this]() { ->setBeforeSendCallback([this]() {
if (this->isNeedAuth() && !this->webServer->authenticate(settings.portal.login, settings.portal.password)) { if (this->isAuthRequired() && !this->webServer->authenticate(settings.portal.login, settings.portal.password)) {
this->webServer->requestAuthentication(DIGEST_AUTH); this->webServer->requestAuthentication(DIGEST_AUTH);
return false; return false;
} }
@@ -115,7 +131,7 @@ protected:
// settings page // settings page
auto settingsPage = (new StaticPage("/settings.html", &LittleFS, "/settings.html", PORTAL_CACHE)) auto settingsPage = (new StaticPage("/settings.html", &LittleFS, "/settings.html", PORTAL_CACHE))
->setBeforeSendCallback([this]() { ->setBeforeSendCallback([this]() {
if (this->isNeedAuth() && !this->webServer->authenticate(settings.portal.login, settings.portal.password)) { if (this->isAuthRequired() && !this->webServer->authenticate(settings.portal.login, settings.portal.password)) {
this->webServer->requestAuthentication(DIGEST_AUTH); this->webServer->requestAuthentication(DIGEST_AUTH);
return false; return false;
} }
@@ -127,7 +143,7 @@ protected:
// upgrade page // upgrade page
auto upgradePage = (new StaticPage("/upgrade.html", &LittleFS, "/upgrade.html", PORTAL_CACHE)) auto upgradePage = (new StaticPage("/upgrade.html", &LittleFS, "/upgrade.html", PORTAL_CACHE))
->setBeforeSendCallback([this]() { ->setBeforeSendCallback([this]() {
if (this->isNeedAuth() && !this->webServer->authenticate(settings.portal.login, settings.portal.password)) { if (this->isAuthRequired() && !this->webServer->authenticate(settings.portal.login, settings.portal.password)) {
this->webServer->requestAuthentication(DIGEST_AUTH); this->webServer->requestAuthentication(DIGEST_AUTH);
return false; return false;
} }
@@ -138,7 +154,7 @@ protected:
// OTA // OTA
auto upgradeHandler = (new UpgradeHandler("/api/upgrade"))->setCanUploadCallback([this](const String& uri) { auto upgradeHandler = (new UpgradeHandler("/api/upgrade"))->setCanUploadCallback([this](const String& uri) {
if (this->isNeedAuth() && !this->webServer->authenticate(settings.portal.login, settings.portal.password)) { if (this->isAuthRequired() && !this->webServer->authenticate(settings.portal.login, settings.portal.password)) {
this->webServer->sendHeader("Connection", "close"); this->webServer->sendHeader("Connection", "close");
this->webServer->send(401); this->webServer->send(401);
return false; return false;
@@ -172,7 +188,7 @@ protected:
// backup // backup
this->webServer->on("/api/backup/save", HTTP_GET, [this]() { this->webServer->on("/api/backup/save", HTTP_GET, [this]() {
if (this->isNeedAuth()) { if (this->isAuthRequired()) {
if (!this->webServer->authenticate(settings.portal.login, settings.portal.password)) { if (!this->webServer->authenticate(settings.portal.login, settings.portal.password)) {
return this->webServer->send(401); return this->webServer->send(401);
} }
@@ -196,7 +212,7 @@ protected:
}); });
this->webServer->on("/api/backup/restore", HTTP_POST, [this]() { this->webServer->on("/api/backup/restore", HTTP_POST, [this]() {
if (this->isNeedAuth()) { if (this->isAuthRequired()) {
if (!this->webServer->authenticate(settings.portal.login, settings.portal.password)) { if (!this->webServer->authenticate(settings.portal.login, settings.portal.password)) {
return this->webServer->send(401); return this->webServer->send(401);
} }
@@ -253,7 +269,7 @@ protected:
// network // network
this->webServer->on("/api/network/settings", HTTP_GET, [this]() { this->webServer->on("/api/network/settings", HTTP_GET, [this]() {
if (this->isNeedAuth()) { if (this->isAuthRequired()) {
if (!this->webServer->authenticate(settings.portal.login, settings.portal.password)) { if (!this->webServer->authenticate(settings.portal.login, settings.portal.password)) {
return this->webServer->send(401); return this->webServer->send(401);
} }
@@ -267,7 +283,7 @@ protected:
}); });
this->webServer->on("/api/network/settings", HTTP_POST, [this]() { this->webServer->on("/api/network/settings", HTTP_POST, [this]() {
if (this->isNeedAuth()) { if (this->isAuthRequired()) {
if (!this->webServer->authenticate(settings.portal.login, settings.portal.password)) { if (!this->webServer->authenticate(settings.portal.login, settings.portal.password)) {
return this->webServer->send(401); return this->webServer->send(401);
} }
@@ -298,8 +314,14 @@ protected:
doc.clear(); doc.clear();
doc.shrinkToFit(); doc.shrinkToFit();
networkSettingsToJson(networkSettings, doc);
doc.shrinkToFit();
this->bufferedWebServer->send(changed ? 201 : 200, "application/json", doc);
if (changed) { if (changed) {
this->webServer->send(201); doc.clear();
doc.shrinkToFit();
fsNetworkSettings.update(); fsNetworkSettings.update();
network->setHostname(networkSettings.hostname) network->setHostname(networkSettings.hostname)
@@ -312,33 +334,11 @@ protected:
networkSettings.staticConfig.dns networkSettings.staticConfig.dns
) )
->reconnect(); ->reconnect();
} else {
this->webServer->send(200);
} }
}); });
this->webServer->on("/api/network/status", HTTP_GET, [this]() {
bool isConnected = network->isConnected();
JsonDocument doc;
doc["hostname"] = networkSettings.hostname;
doc["mac"] = network->getStaMac();
doc["isConnected"] = isConnected;
doc["ssid"] = network->getStaSsid();
doc["signalQuality"] = isConnected ? Network::Manager::rssiToSignalQuality(network->getRssi()) : 0;
doc["channel"] = isConnected ? network->getStaChannel() : 0;
doc["ip"] = isConnected ? network->getStaIp().toString() : "";
doc["subnet"] = isConnected ? network->getStaSubnet().toString() : "";
doc["gateway"] = isConnected ? network->getStaGateway().toString() : "";
doc["dns"] = isConnected ? network->getStaDns().toString() : "";
doc.shrinkToFit();
this->bufferedWebServer->send(200, "application/json", doc);
});
this->webServer->on("/api/network/scan", HTTP_GET, [this]() { this->webServer->on("/api/network/scan", HTTP_GET, [this]() {
if (this->isNeedAuth()) { if (this->isAuthRequired()) {
if (!this->webServer->authenticate(settings.portal.login, settings.portal.password)) { if (!this->webServer->authenticate(settings.portal.login, settings.portal.password)) {
this->webServer->send(401); this->webServer->send(401);
return; return;
@@ -348,7 +348,11 @@ protected:
auto apCount = WiFi.scanComplete(); auto apCount = WiFi.scanComplete();
if (apCount <= 0) { if (apCount <= 0) {
if (apCount != WIFI_SCAN_RUNNING) { if (apCount != WIFI_SCAN_RUNNING) {
#ifdef ARDUINO_ARCH_ESP8266
WiFi.scanNetworks(true, true); WiFi.scanNetworks(true, true);
#else
WiFi.scanNetworks(true, true, true);
#endif
} }
this->webServer->send(404); this->webServer->send(404);
@@ -359,10 +363,16 @@ protected:
for (short int i = 0; i < apCount; i++) { for (short int i = 0; i < apCount; i++) {
String ssid = WiFi.SSID(i); String ssid = WiFi.SSID(i);
doc[i]["ssid"] = ssid; doc[i]["ssid"] = ssid;
doc[i]["signalQuality"] = Network::Manager::rssiToSignalQuality(WiFi.RSSI(i)); doc[i]["bssid"] = WiFi.BSSIDstr(i);
doc[i]["signalQuality"] = NetworkMgr::rssiToSignalQuality(WiFi.RSSI(i));
doc[i]["channel"] = WiFi.channel(i); doc[i]["channel"] = WiFi.channel(i);
doc[i]["hidden"] = !ssid.length(); doc[i]["hidden"] = !ssid.length();
doc[i]["encryptionType"] = WiFi.encryptionType(i); #ifdef ARDUINO_ARCH_ESP8266
const bss_info* info = WiFi.getScanInfoByIndex(i);
doc[i]["auth"] = info->authmode;
#else
doc[i]["auth"] = WiFi.encryptionType(i);
#endif
} }
doc.shrinkToFit(); doc.shrinkToFit();
@@ -374,7 +384,7 @@ protected:
// settings // settings
this->webServer->on("/api/settings", HTTP_GET, [this]() { this->webServer->on("/api/settings", HTTP_GET, [this]() {
if (this->isNeedAuth()) { if (this->isAuthRequired()) {
if (!this->webServer->authenticate(settings.portal.login, settings.portal.password)) { if (!this->webServer->authenticate(settings.portal.login, settings.portal.password)) {
return this->webServer->send(401); return this->webServer->send(401);
} }
@@ -388,7 +398,7 @@ protected:
}); });
this->webServer->on("/api/settings", HTTP_POST, [this]() { this->webServer->on("/api/settings", HTTP_POST, [this]() {
if (this->isNeedAuth()) { if (this->isAuthRequired()) {
if (!this->webServer->authenticate(settings.portal.login, settings.portal.password)) { if (!this->webServer->authenticate(settings.portal.login, settings.portal.password)) {
return this->webServer->send(401); return this->webServer->send(401);
} }
@@ -419,12 +429,17 @@ protected:
doc.clear(); doc.clear();
doc.shrinkToFit(); doc.shrinkToFit();
if (changed) { settingsToJson(settings, doc);
fsSettings.update(); doc.shrinkToFit();
this->webServer->send(201);
} else { this->bufferedWebServer->send(changed ? 201 : 200, "application/json", doc);
this->webServer->send(200);
if (changed) {
doc.clear();
doc.shrinkToFit();
fsSettings.update();
tMqtt->resetPublishedSettingsTime();
} }
}); });
@@ -433,24 +448,13 @@ protected:
this->webServer->on("/api/vars", HTTP_GET, [this]() { this->webServer->on("/api/vars", HTTP_GET, [this]() {
JsonDocument doc; JsonDocument doc;
varsToJson(vars, doc); varsToJson(vars, doc);
doc["system"]["version"] = PROJECT_VERSION;
doc["system"]["buildDate"] = __DATE__ " " __TIME__;
doc["system"]["uptime"] = millis() / 1000ul;
doc["system"]["totalHeap"] = getTotalHeap();
doc["system"]["freeHeap"] = getFreeHeap();
doc["system"]["minFreeHeap"] = getFreeHeap(true);
doc["system"]["maxFreeBlockHeap"] = getMaxFreeBlockHeap();
doc["system"]["minMaxFreeBlockHeap"] = getMaxFreeBlockHeap(true);
doc["system"]["resetReason"] = getResetReason();
doc["system"]["mqttConnected"] = tMqtt->isConnected();
doc.shrinkToFit(); doc.shrinkToFit();
this->bufferedWebServer->send(200, "application/json", doc); this->bufferedWebServer->send(200, "application/json", doc);
}); });
this->webServer->on("/api/vars", HTTP_POST, [this]() { this->webServer->on("/api/vars", HTTP_POST, [this]() {
if (this->isNeedAuth()) { if (this->isAuthRequired()) {
if (!this->webServer->authenticate(settings.portal.login, settings.portal.password)) { if (!this->webServer->authenticate(settings.portal.login, settings.portal.password)) {
return this->webServer->send(401); return this->webServer->send(401);
} }
@@ -481,14 +485,76 @@ protected:
doc.clear(); doc.clear();
doc.shrinkToFit(); doc.shrinkToFit();
if (changed) { varsToJson(vars, doc);
this->webServer->send(201); doc.shrinkToFit();
} else { this->bufferedWebServer->send(changed ? 201 : 200, "application/json", doc);
this->webServer->send(200);
if (changed) {
doc.clear();
doc.shrinkToFit();
tMqtt->resetPublishedVarsTime();
} }
}); });
this->webServer->on("/api/info", HTTP_GET, [this]() {
bool isConnected = network->isConnected();
JsonDocument doc;
doc["network"]["hostname"] = networkSettings.hostname;
doc["network"]["mac"] = network->getStaMac();
doc["network"]["connected"] = isConnected;
doc["network"]["ssid"] = network->getStaSsid();
doc["network"]["signalQuality"] = isConnected ? NetworkMgr::rssiToSignalQuality(network->getRssi()) : 0;
doc["network"]["channel"] = isConnected ? network->getStaChannel() : 0;
doc["network"]["ip"] = isConnected ? network->getStaIp().toString() : "";
doc["network"]["subnet"] = isConnected ? network->getStaSubnet().toString() : "";
doc["network"]["gateway"] = isConnected ? network->getStaGateway().toString() : "";
doc["network"]["dns"] = isConnected ? network->getStaDns().toString() : "";
doc["system"]["version"] = PROJECT_VERSION;
doc["system"]["buildDate"] = __DATE__ " " __TIME__;
doc["system"]["uptime"] = millis() / 1000ul;
doc["system"]["totalHeap"] = getTotalHeap();
doc["system"]["freeHeap"] = getFreeHeap();
doc["system"]["minFreeHeap"] = getFreeHeap(true);
doc["system"]["maxFreeBlockHeap"] = getMaxFreeBlockHeap();
doc["system"]["minMaxFreeBlockHeap"] = getMaxFreeBlockHeap(true);
doc["system"]["resetReason"] = getResetReason();
#ifdef ARDUINO_ARCH_ESP8266
doc["system"]["chipModel"] = esp_is_8285() ? "ESP8285" : "ESP8266";
doc["system"]["chipRevision"] = 0;
doc["system"]["chipCores"] = 1;
doc["system"]["cpuFreq"] = ESP.getCpuFreqMHz();
doc["system"]["coreVersion"] = ESP.getCoreVersion();
doc["system"]["flashSize"] = ESP.getFlashChipSize();
doc["system"]["flashRealSize"] = ESP.getFlashChipRealSize();
#elif ARDUINO_ARCH_ESP32
doc["system"]["chipModel"] = ESP.getChipModel();
doc["system"]["chipRevision"] = ESP.getChipRevision();
doc["system"]["chipCores"] = ESP.getChipCores();
doc["system"]["cpuFreq"] = ESP.getCpuFreqMHz();
doc["system"]["coreVersion"] = ESP.getSdkVersion();
doc["system"]["flashSize"] = ESP.getFlashChipSize();
doc["system"]["flashRealSize"] = doc["system"]["flashSize"];
#else
doc["system"]["chipModel"] = 0;
doc["system"]["chipRevision"] = 0;
doc["system"]["chipCores"] = 0;
doc["system"]["cpuFreq"] = 0;
doc["system"]["coreVersion"] = 0;
doc["system"]["flashSize"] = 0;
doc["system"]["flashRealSize"] = 0;
#endif
doc.shrinkToFit();
this->bufferedWebServer->send(200, "application/json", doc);
});
// not found // not found
this->webServer->onNotFound([this]() { this->webServer->onNotFound([this]() {
@@ -559,8 +625,8 @@ protected:
} }
} }
bool isNeedAuth() { bool isAuthRequired() {
return !network->isApEnabled() && settings.portal.useAuth && strlen(settings.portal.password); return !network->isApEnabled() && settings.portal.auth && strlen(settings.portal.password);
} }
void onCaptivePortal() { void onCaptivePortal() {

View File

@@ -1,10 +1,8 @@
#include <Equitherm.h> #include <Equitherm.h>
#include <GyverPID.h> #include <GyverPID.h>
#include <PIDtuner.h>
Equitherm etRegulator; Equitherm etRegulator;
GyverPID pidRegulator(0, 0, 0); GyverPID pidRegulator(0, 0, 0);
PIDtuner pidTuner;
class RegulatorTask : public LeanTask { class RegulatorTask : public LeanTask {
@@ -12,27 +10,26 @@ 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:
bool tunerInit = false;
byte tunerState = 0;
byte tunerRegulator = 0;
float prevHeatingTarget = 0; float prevHeatingTarget = 0;
float prevEtResult = 0; float prevEtResult = 0;
float prevPidResult = 0; float prevPidResult = 0;
const char* getTaskName() { #if defined(ARDUINO_ARCH_ESP32)
const char* getTaskName() override {
return "Regulator"; return "Regulator";
} }
/*int getTaskCore() { /*BaseType_t getTaskCore() override {
return 1; return 1;
}*/ }*/
int getTaskPriority() { int getTaskPriority() override {
return 4; return 4;
} }
#endif
void loop() { void loop() {
byte newTemp = vars.parameters.heatingSetpoint; float newTemp = vars.parameters.heatingSetpoint;
if (vars.states.emergency) { if (vars.states.emergency) {
if (settings.heating.turbo) { if (settings.heating.turbo) {
@@ -41,74 +38,60 @@ protected:
Log.sinfoln(FPSTR(L_REGULATOR), F("Turbo mode auto disabled")); Log.sinfoln(FPSTR(L_REGULATOR), F("Turbo mode auto disabled"));
} }
newTemp = getEmergencyModeTemp(); newTemp = this->getEmergencyModeTemp();
} else { } else {
if (vars.tuning.enable || tunerInit) { if (settings.heating.turbo && (fabs(settings.heating.target - vars.temperatures.indoor) < 1 || !settings.heating.enable || (settings.equitherm.enable && settings.pid.enable))) {
if (settings.heating.turbo) { settings.heating.turbo = false;
settings.heating.turbo = false;
Log.sinfoln(FPSTR(L_REGULATOR), F("Turbo mode auto disabled")); Log.sinfoln(FPSTR(L_REGULATOR), F("Turbo mode auto disabled"));
}
newTemp = getTuningModeTemp();
if (newTemp == 0) {
vars.tuning.enable = false;
}
} }
if (!vars.tuning.enable) { newTemp = this->getNormalModeTemp();
if (settings.heating.turbo && (fabs(settings.heating.target - vars.temperatures.indoor) < 1 || !settings.heating.enable || (settings.equitherm.enable && settings.pid.enable))) {
settings.heating.turbo = false;
Log.sinfoln(FPSTR(L_REGULATOR), F("Turbo mode auto disabled"));
}
newTemp = getNormalModeTemp();
}
} }
// Ограничиваем, если до этого не ограничило // Limits
if (newTemp < settings.heating.minTemp || newTemp > settings.heating.maxTemp) { newTemp = constrain(
newTemp = constrain(newTemp, settings.heating.minTemp, settings.heating.maxTemp); newTemp,
} !settings.opentherm.nativeHeatingControl ? settings.heating.minTemp : THERMOSTAT_INDOOR_MIN_TEMP,
!settings.opentherm.nativeHeatingControl ? settings.heating.maxTemp : THERMOSTAT_INDOOR_MAX_TEMP
);
if (abs(vars.parameters.heatingSetpoint - newTemp) + 0.0001 >= 1) { if (fabs(vars.parameters.heatingSetpoint - newTemp) > 0.4999f) {
vars.parameters.heatingSetpoint = newTemp; vars.parameters.heatingSetpoint = newTemp;
} }
} }
byte getEmergencyModeTemp() { float getEmergencyModeTemp() {
float newTemp = 0; float newTemp = 0;
// if use equitherm // if use equitherm
if (settings.emergency.useEquitherm && settings.sensors.outdoor.type != 1) { if (settings.emergency.useEquitherm) {
float etResult = getEquithermTemp(settings.heating.minTemp, settings.heating.maxTemp); float etResult = getEquithermTemp(settings.heating.minTemp, settings.heating.maxTemp);
if (fabs(prevEtResult - etResult) + 0.0001 >= 0.5) { if (fabs(prevEtResult - etResult) > 0.4999f) {
prevEtResult = etResult; prevEtResult = etResult;
newTemp += etResult; newTemp += etResult;
Log.sinfoln(FPSTR(L_REGULATOR_EQUITHERM), F("New emergency result: %hhu (%.2f)"), (uint8_t) round(etResult), etResult); Log.sinfoln(FPSTR(L_REGULATOR_EQUITHERM), F("New emergency result: %.2f"), etResult);
} else { } else {
newTemp += prevEtResult; newTemp += prevEtResult;
} }
} else if(settings.emergency.usePid && settings.sensors.indoor.type != 1) { } else if(settings.emergency.usePid) {
if (vars.parameters.heatingEnabled) { if (vars.parameters.heatingEnabled) {
float pidResult = getPidTemp( float pidResult = getPidTemp(
settings.heating.minTemp, settings.heating.minTemp,
settings.heating.maxTemp settings.heating.maxTemp
); );
if (fabs(prevPidResult - pidResult) + 0.0001 >= 0.5) { if (fabs(prevPidResult - pidResult) > 0.4999f) {
prevPidResult = pidResult; prevPidResult = pidResult;
newTemp += pidResult; newTemp += pidResult;
Log.sinfoln(FPSTR(L_REGULATOR_PID), F("New emergency result: %hhu (%.2f)"), (uint8_t) round(pidResult), pidResult); Log.sinfoln(FPSTR(L_REGULATOR_PID), F("New emergency result: %.2f"), pidResult);
} else { } else {
newTemp += prevPidResult; newTemp += prevPidResult;
@@ -123,17 +106,17 @@ protected:
newTemp = settings.emergency.target; newTemp = settings.emergency.target;
} }
return round(newTemp); return newTemp;
} }
byte getNormalModeTemp() { float getNormalModeTemp() {
float newTemp = 0; float newTemp = 0;
if (fabs(prevHeatingTarget - settings.heating.target) > 0.0001) { 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.equitherm.enable && */settings.pid.enable) {
pidRegulator.integral = 0; pidRegulator.integral = 0;
Log.sinfoln(FPSTR(L_REGULATOR_PID), F("Integral sum has been reset")); Log.sinfoln(FPSTR(L_REGULATOR_PID), F("Integral sum has been reset"));
} }
@@ -143,11 +126,11 @@ protected:
if (settings.equitherm.enable) { if (settings.equitherm.enable) {
float etResult = getEquithermTemp(settings.heating.minTemp, settings.heating.maxTemp); float etResult = getEquithermTemp(settings.heating.minTemp, settings.heating.maxTemp);
if (fabs(prevEtResult - etResult) + 0.0001 >= 0.5) { if (fabs(prevEtResult - etResult) > 0.4999f) {
prevEtResult = etResult; prevEtResult = etResult;
newTemp += etResult; newTemp += etResult;
Log.sinfoln(FPSTR(L_REGULATOR_EQUITHERM), F("New result: %hhu (%.2f)"), (uint8_t) round(etResult), etResult); Log.sinfoln(FPSTR(L_REGULATOR_EQUITHERM), F("New result: %.2f"), etResult);
} else { } else {
newTemp += prevEtResult; newTemp += prevEtResult;
@@ -162,11 +145,12 @@ protected:
settings.pid.maxTemp settings.pid.maxTemp
); );
if (fabs(prevPidResult - pidResult) + 0.0001 >= 0.5) { if (fabs(prevPidResult - pidResult) > 0.4999f) {
prevPidResult = pidResult; prevPidResult = pidResult;
newTemp += pidResult; newTemp += pidResult;
Log.sinfoln(FPSTR(L_REGULATOR_PID), F("New result: %hhd (%.2f)"), (int8_t) round(pidResult), pidResult); Log.sinfoln(FPSTR(L_REGULATOR_PID), F("New result: %.2f"), pidResult);
Log.straceln(FPSTR(L_REGULATOR_PID), F("Integral: %.2f"), pidRegulator.integral);
} else { } else {
newTemp += prevPidResult; newTemp += prevPidResult;
@@ -174,6 +158,10 @@ protected:
} 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 // default temp, manual mode
@@ -181,108 +169,46 @@ protected:
newTemp = settings.heating.target; newTemp = settings.heating.target;
} }
newTemp = round(newTemp);
newTemp = constrain(newTemp, 0, 100);
return newTemp; return newTemp;
} }
byte getTuningModeTemp() { /**
if (tunerInit && (!vars.tuning.enable || vars.tuning.regulator != tunerRegulator)) { * @brief Get the Equitherm Temp
if (tunerRegulator == 0) { * Calculations in degrees C, conversion occurs when using F
pidTuner.reset(); *
} * @param minTemp
* @param maxTemp
tunerInit = false; * @return float
tunerRegulator = 0; */
tunerState = 0;
Log.sinfoln("REGULATOR.TUNING", F("Stopped"));
}
if (!vars.tuning.enable) {
return 0;
}
if (vars.tuning.regulator == 0) {
// @TODO дописать
Log.sinfoln("REGULATOR.TUNING.EQUITHERM", F("Not implemented"));
return 0;
} else if (vars.tuning.regulator == 1) {
// PID tuner
float defaultTemp = settings.equitherm.enable
? getEquithermTemp(settings.heating.minTemp, settings.heating.maxTemp)
: settings.heating.target;
if (tunerInit && pidTuner.getState() == 3) {
Log.sinfoln("REGULATOR.TUNING.PID", F("Finished"));
for (Stream* stream : Log.getStreams()) {
pidTuner.debugText(stream);
}
pidTuner.reset();
tunerInit = false;
tunerRegulator = 0;
tunerState = 0;
if (pidTuner.getAccuracy() < 90) {
Log.swarningln("REGULATOR.TUNING.PID", F("Bad result, try again..."));
} else {
settings.pid.p_factor = pidTuner.getPID_p();
settings.pid.i_factor = pidTuner.getPID_i();
settings.pid.d_factor = pidTuner.getPID_d();
return 0;
}
}
if (!tunerInit) {
Log.sinfoln("REGULATOR.TUNING.PID", F("Start..."));
float step;
if (vars.temperatures.indoor - vars.temperatures.outdoor > 10) {
step = ceil(vars.parameters.heatingSetpoint / vars.temperatures.indoor * 2);
} else {
step = 5.0f;
}
float startTemp = step;
Log.sinfoln("REGULATOR.TUNING.PID", F("Started. Start value: %f, step: %f"), startTemp, step);
pidTuner.setParameters(NORMAL, startTemp, step, 20 * 60 * 1000, 0.15, 60 * 1000, 10000);
tunerInit = true;
tunerRegulator = 1;
}
pidTuner.setInput(vars.temperatures.indoor);
pidTuner.compute();
if (tunerState > 0 && pidTuner.getState() != tunerState) {
Log.sinfoln("REGULATOR.TUNING.PID", F("Log:"));
for (Stream* stream : Log.getStreams()) {
pidTuner.debugText(stream);
}
tunerState = pidTuner.getState();
}
return round(defaultTemp + pidTuner.getOutput());
} else {
return 0;
}
}
float getEquithermTemp(int minTemp, int maxTemp) { 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 (vars.states.emergency) {
etRegulator.Kt = 0; if (settings.sensors.indoor.type == SensorType::MANUAL) {
etRegulator.indoorTemp = 0; etRegulator.Kt = 0;
etRegulator.outdoorTemp = vars.temperatures.outdoor; etRegulator.indoorTemp = 0;
} else {
etRegulator.Kt = settings.equitherm.t_factor;
etRegulator.indoorTemp = indoorTemp;
}
etRegulator.outdoorTemp = outdoorTemp;
} else if (settings.pid.enable) { } else if (settings.pid.enable) {
etRegulator.Kt = 0; etRegulator.Kt = 0;
etRegulator.indoorTemp = round(vars.temperatures.indoor); etRegulator.indoorTemp = round(indoorTemp);
etRegulator.outdoorTemp = round(vars.temperatures.outdoor); etRegulator.outdoorTemp = round(outdoorTemp);
} else { } else {
if (settings.heating.turbo) { if (settings.heating.turbo) {
@@ -290,23 +216,41 @@ protected:
} else { } else {
etRegulator.Kt = settings.equitherm.t_factor; etRegulator.Kt = settings.equitherm.t_factor;
} }
etRegulator.indoorTemp = vars.temperatures.indoor; etRegulator.indoorTemp = indoorTemp;
etRegulator.outdoorTemp = vars.temperatures.outdoor; etRegulator.outdoorTemp = outdoorTemp;
} }
etRegulator.setLimits(minTemp, maxTemp); etRegulator.setLimits(minTemp, maxTemp);
etRegulator.Kn = settings.equitherm.n_factor; etRegulator.Kn = settings.equitherm.n_factor;
// etRegulator.Kn = tuneEquithermN(etRegulator.Kn, vars.temperatures.indoor, settings.heating.target, 300, 1800, 0.01, 1);
etRegulator.Kk = settings.equitherm.k_factor; etRegulator.Kk = settings.equitherm.k_factor;
etRegulator.targetTemp = vars.states.emergency ? settings.emergency.target : settings.heating.target; etRegulator.targetTemp = targetTemp;
float result = etRegulator.getResult();
return etRegulator.getResult(); if (settings.system.unitSystem == UnitSystem::IMPERIAL) {
result = c2f(result);
}
return result;
} }
float getPidTemp(int minTemp, int maxTemp) { float getPidTemp(int minTemp, int maxTemp) {
pidRegulator.Kp = settings.pid.p_factor; if (fabs(pidRegulator.Kp - settings.pid.p_factor) >= 0.0001f) {
pidRegulator.Ki = settings.pid.i_factor; pidRegulator.Kp = settings.pid.p_factor;
pidRegulator.Kd = settings.pid.d_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.setLimits(minTemp, maxTemp);
pidRegulator.setDt(settings.pid.dt * 1000u); pidRegulator.setDt(settings.pid.dt * 1000u);
@@ -315,32 +259,4 @@ protected:
return pidRegulator.getResultTimer(); return pidRegulator.getResultTimer();
} }
float tuneEquithermN(float ratio, float currentTemp, float setTemp, unsigned int dirtyInterval = 60, unsigned int accurateInterval = 1800, float accurateStep = 0.01, float accurateStepAfter = 1) {
static uint32_t _prevIteration = millis();
if (abs(currentTemp - setTemp) < accurateStepAfter) {
if (millis() - _prevIteration < (accurateInterval * 1000)) {
return ratio;
}
if (currentTemp - setTemp > 0.1f) {
ratio -= accurateStep;
} else if (currentTemp - setTemp < -0.1f) {
ratio += accurateStep;
}
} else {
if (millis() - _prevIteration < (dirtyInterval * 1000)) {
return ratio;
}
ratio = ratio * (setTemp / currentTemp);
}
_prevIteration = millis();
return ratio;
}
}; };

View File

@@ -32,61 +32,95 @@ protected:
DallasTemperature* indoorSensor = nullptr; DallasTemperature* indoorSensor = nullptr;
bool initOutdoorSensor = false; bool initOutdoorSensor = false;
unsigned long initOutdoorSensorTime = 0;
unsigned long startOutdoorConversionTime = 0; unsigned long startOutdoorConversionTime = 0;
float filteredOutdoorTemp = 0; float filteredOutdoorTemp = 0;
bool emptyOutdoorTemp = true; bool emptyOutdoorTemp = true;
bool initIndoorSensor = false; bool initIndoorSensor = false;
unsigned long initIndoorSensorTime = 0;
unsigned long startIndoorConversionTime = 0; unsigned long startIndoorConversionTime = 0;
float filteredIndoorTemp = 0; float filteredIndoorTemp = 0;
bool emptyIndoorTemp = true; bool emptyIndoorTemp = true;
#if USE_BLE #if defined(ARDUINO_ARCH_ESP32)
#if USE_BLE
BLEClient* pBleClient = nullptr; BLEClient* pBleClient = nullptr;
bool initBleSensor = false; bool initBleSensor = false;
bool initBleNotify = false; bool initBleNotify = false;
#endif #endif
const char* getTaskName() { const char* getTaskName() override {
return "Sensors"; return "Sensors";
} }
/*int getTaskCore() { BaseType_t getTaskCore() override {
return 1; // https://github.com/h2zero/NimBLE-Arduino/issues/676
}*/ #if USE_BLE && defined(CONFIG_BT_NIMBLE_PINNED_TO_CORE)
return CONFIG_BT_NIMBLE_PINNED_TO_CORE;
#else
return tskNO_AFFINITY;
#endif
}
int getTaskPriority() { int getTaskPriority() override {
return 4; return 4;
} }
#endif
void loop() { void loop() {
if (settings.sensors.outdoor.type == 2 && settings.sensors.outdoor.pin) { bool indoorTempUpdated = false;
bool outdoorTempUpdated = false;
if (settings.sensors.outdoor.type == SensorType::DS18B20 && GPIO_IS_VALID(settings.sensors.outdoor.gpio)) {
outdoorTemperatureSensor(); outdoorTemperatureSensor();
outdoorTempUpdated = true;
} }
if (settings.sensors.indoor.type == 2 && settings.sensors.indoor.pin) { if (settings.sensors.indoor.type == SensorType::DS18B20 && GPIO_IS_VALID(settings.sensors.indoor.gpio)) {
indoorTemperatureSensor(); indoorTemperatureSensor();
indoorTempUpdated = true;
}
#if USE_BLE
else if (settings.sensors.indoor.type == SensorType::BLUETOOTH) {
indoorTemperatureBluetoothSensor();
indoorTempUpdated = true;
}
#endif
if (outdoorTempUpdated) {
float newTemp = settings.sensors.outdoor.offset;
if (settings.system.unitSystem == UnitSystem::METRIC) {
newTemp += this->filteredOutdoorTemp;
} else if (settings.system.unitSystem == UnitSystem::IMPERIAL) {
newTemp += c2f(this->filteredOutdoorTemp);
}
if (fabs(vars.temperatures.outdoor - newTemp) > 0.099f) {
vars.temperatures.outdoor = newTemp;
Log.sinfoln(FPSTR(L_SENSORS_OUTDOOR), F("New temp: %f"), vars.temperatures.outdoor);
}
} }
#if USE_BLE if (indoorTempUpdated) {
if (settings.sensors.indoor.type == 3) { float newTemp = settings.sensors.indoor.offset;
bluetoothSensor(); if (settings.system.unitSystem == UnitSystem::METRIC) {
} newTemp += this->filteredIndoorTemp;
#endif
if (fabs(vars.temperatures.outdoor - this->filteredOutdoorTemp) > 0.099) { } else if (settings.system.unitSystem == UnitSystem::IMPERIAL) {
vars.temperatures.outdoor = this->filteredOutdoorTemp + settings.sensors.outdoor.offset; newTemp += c2f(this->filteredIndoorTemp);
Log.sinfoln(FPSTR(L_SENSORS_OUTDOOR), F("New temp: %f"), vars.temperatures.outdoor); }
}
if (fabs(vars.temperatures.indoor - this->filteredIndoorTemp) > 0.099) { if (fabs(vars.temperatures.indoor - newTemp) > 0.099f) {
vars.temperatures.indoor = this->filteredIndoorTemp + settings.sensors.indoor.offset; vars.temperatures.indoor = newTemp;
Log.sinfoln(FPSTR(L_SENSORS_INDOOR), F("New temp: %f"), vars.temperatures.indoor); Log.sinfoln(FPSTR(L_SENSORS_INDOOR), F("New temp: %f"), vars.temperatures.indoor);
}
} }
} }
#if USE_BLE #if USE_BLE
void bluetoothSensor() { void indoorTemperatureBluetoothSensor() {
static bool initBleNotify = false; static bool initBleNotify = false;
if (!initBleSensor && millis() > 5000) { if (!initBleSensor && millis() > 5000) {
Log.sinfoln(FPSTR(L_SENSORS_BLE), F("Init BLE")); Log.sinfoln(FPSTR(L_SENSORS_BLE), F("Init BLE"));
@@ -136,7 +170,7 @@ protected:
return; return;
} }
float rawTemp = ((pData[0] | (pData[1] << 8)) * 0.01); float rawTemp = ((pData[0] | (pData[1] << 8)) * 0.01f);
Log.straceln(FPSTR(L_SENSORS_INDOOR), F("Raw temp: %f"), rawTemp); Log.straceln(FPSTR(L_SENSORS_INDOOR), F("Raw temp: %f"), rawTemp);
if (this->emptyIndoorTemp) { if (this->emptyIndoorTemp) {
@@ -204,10 +238,16 @@ protected:
void outdoorTemperatureSensor() { void outdoorTemperatureSensor() {
if (!this->initOutdoorSensor) { if (!this->initOutdoorSensor) {
Log.sinfoln(FPSTR(L_SENSORS_OUTDOOR), F("Starting on gpio %hhu..."), settings.sensors.outdoor.pin); if (this->initOutdoorSensorTime && millis() - this->initOutdoorSensorTime < EXT_SENSORS_INTERVAL * 10) {
return;
}
this->oneWireOutdoorSensor->begin(settings.sensors.outdoor.pin); Log.sinfoln(FPSTR(L_SENSORS_OUTDOOR), F("Starting on GPIO %hhu..."), settings.sensors.outdoor.gpio);
this->oneWireOutdoorSensor->begin(settings.sensors.outdoor.gpio);
this->oneWireOutdoorSensor->reset();
this->outdoorSensor->begin(); this->outdoorSensor->begin();
this->initOutdoorSensorTime = millis();
Log.straceln( Log.straceln(
FPSTR(L_SENSORS_OUTDOOR), FPSTR(L_SENSORS_OUTDOOR),
@@ -270,10 +310,16 @@ protected:
void indoorTemperatureSensor() { void indoorTemperatureSensor() {
if (!this->initIndoorSensor) { if (!this->initIndoorSensor) {
Log.sinfoln(FPSTR(L_SENSORS_INDOOR), F("Starting on gpio %hhu..."), settings.sensors.indoor.pin); if (this->initIndoorSensorTime && millis() - this->initIndoorSensorTime < EXT_SENSORS_INTERVAL * 10) {
return;
}
this->oneWireIndoorSensor->begin(settings.sensors.indoor.pin); Log.sinfoln(FPSTR(L_SENSORS_INDOOR), F("Starting on GPIO %hhu..."), settings.sensors.indoor.gpio);
this->oneWireIndoorSensor->begin(settings.sensors.indoor.gpio);
this->oneWireIndoorSensor->reset();
this->indoorSensor->begin(); this->indoorSensor->begin();
this->initIndoorSensorTime = millis();
Log.straceln( Log.straceln(
FPSTR(L_SENSORS_INDOOR), FPSTR(L_SENSORS_INDOOR),

View File

@@ -1,5 +1,5 @@
struct NetworkSettings { struct NetworkSettings {
char hostname[25] = HOSTNAME_DEFAULT; char hostname[25] = DEFAULT_HOSTNAME;
bool useDhcp = true; bool useDhcp = true;
struct { struct {
@@ -10,14 +10,14 @@ struct NetworkSettings {
} staticConfig; } staticConfig;
struct { struct {
char ssid[33] = AP_SSID_DEFAULT; char ssid[33] = DEFAULT_AP_SSID;
char password[65] = AP_PASSWORD_DEFAULT; char password[65] = DEFAULT_AP_PASSWORD;
byte channel = 6; byte channel = 6;
} ap; } ap;
struct { struct {
char ssid[33] = STA_SSID_DEFAULT; char ssid[33] = DEFAULT_STA_SSID;
char password[65] = STA_PASSWORD_DEFAULT; char password[65] = DEFAULT_STA_PASSWORD;
byte channel = 0; byte channel = 0;
} sta; } sta;
} networkSettings; } networkSettings;
@@ -25,19 +25,34 @@ struct NetworkSettings {
struct Settings { struct Settings {
struct { struct {
bool debug = DEBUG_BY_DEFAULT; bool debug = DEBUG_BY_DEFAULT;
bool useSerial = USE_SERIAL;
bool useTelnet = USE_TELNET; struct {
bool enable = USE_SERIAL;
unsigned int baudrate = 115200;
} serial;
struct {
bool enable = USE_TELNET;
unsigned short port = 23;
} telnet;
UnitSystem unitSystem = UnitSystem::METRIC;
byte statusLedGpio = DEFAULT_STATUS_LED_GPIO;
} system; } system;
struct { struct {
bool useAuth = false; bool auth = false;
char login[13] = PORTAL_LOGIN_DEFAULT; char login[13] = DEFAULT_PORTAL_LOGIN;
char password[33] = PORTAL_PASSWORD_DEFAULT; char password[33] = DEFAULT_PORTAL_PASSWORD;
} portal; } portal;
struct { struct {
byte inPin = OT_IN_PIN_DEFAULT; UnitSystem unitSystem = UnitSystem::METRIC;
byte outPin = OT_OUT_PIN_DEFAULT; byte inGpio = DEFAULT_OT_IN_GPIO;
byte outGpio = DEFAULT_OT_OUT_GPIO;
byte rxLedGpio = DEFAULT_OT_RX_LED_GPIO;
byte faultStateGpio = DEFAULT_OT_FAULT_STATE_GPIO;
byte invertFaultState = false;
unsigned int memberIdCode = 0; unsigned int memberIdCode = 0;
bool dhwPresent = true; bool dhwPresent = true;
bool summerWinterMode = false; bool summerWinterMode = false;
@@ -46,28 +61,35 @@ struct Settings {
bool dhwToCh2 = false; bool dhwToCh2 = false;
bool dhwBlocking = false; bool dhwBlocking = false;
bool modulationSyncWithHeating = false; bool modulationSyncWithHeating = false;
bool getMinMaxTemp = true;
bool nativeHeatingControl = false;
} opentherm; } opentherm;
struct { struct {
char server[81] = MQTT_SERVER_DEFAULT; bool enable = false;
unsigned short port = MQTT_PORT_DEFAULT; char server[81] = DEFAULT_MQTT_SERVER;
char user[33] = MQTT_USER_DEFAULT; unsigned short port = DEFAULT_MQTT_PORT;
char password[33] = MQTT_PASSWORD_DEFAULT; char user[33] = DEFAULT_MQTT_USER;
char prefix[33] = MQTT_PREFIX_DEFAULT; char password[33] = DEFAULT_MQTT_PASSWORD;
char prefix[33] = DEFAULT_MQTT_PREFIX;
unsigned short interval = 5; unsigned short interval = 5;
bool homeAssistantDiscovery = true;
} mqtt; } mqtt;
struct { struct {
bool enable = true; bool enable = true;
float target = 40.0f; float target = DEFAULT_HEATING_TARGET_TEMP;
unsigned short tresholdTime = 120;
bool useEquitherm = false; bool useEquitherm = false;
bool usePid = false; bool usePid = false;
bool onNetworkFault = true;
bool onMqttFault = true;
} emergency; } emergency;
struct { struct {
bool enable = true; bool enable = true;
bool turbo = false; bool turbo = false;
float target = 40.0f; float target = DEFAULT_HEATING_TARGET_TEMP;
float hysteresis = 0.5f; float hysteresis = 0.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;
@@ -76,16 +98,16 @@ struct Settings {
struct { struct {
bool enable = true; bool enable = true;
byte target = 40; 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 enable = false;
float p_factor = 50; float p_factor = 2;
float i_factor = 0.006f; float i_factor = 0.0055f;
float d_factor = 10000; float d_factor = 0;
unsigned short dt = 180; unsigned short dt = 180;
byte minTemp = 0; byte minTemp = 0;
byte maxTemp = DEFAULT_HEATING_MAX_TEMP; byte maxTemp = DEFAULT_HEATING_MAX_TEMP;
@@ -100,16 +122,14 @@ struct Settings {
struct { struct {
struct { struct {
// 0 - boiler, 1 - manual, 2 - ds18b20 SensorType type = SensorType::BOILER;
byte type = 0; byte gpio = DEFAULT_SENSOR_OUTDOOR_GPIO;
byte pin = SENSOR_OUTDOOR_PIN_DEFAULT;
float offset = 0.0f; float offset = 0.0f;
} outdoor; } outdoor;
struct { struct {
// 1 - manual, 2 - ds18b20, 3 - ble SensorType type = SensorType::MANUAL;
byte type = 1; byte gpio = DEFAULT_SENSOR_INDOOR_GPIO;
byte pin = SENSOR_INDOOR_PIN_DEFAULT;
uint8_t bleAddresss[6] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; uint8_t bleAddresss[6] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
float offset = 0.0f; float offset = 0.0f;
} indoor; } indoor;
@@ -117,7 +137,7 @@ struct Settings {
struct { struct {
bool use = false; bool use = false;
byte pin = EXT_PUMP_PIN_DEFAULT; byte gpio = DEFAULT_EXT_PUMP_GPIO;
unsigned short postCirculationTime = 600; unsigned short postCirculationTime = 600;
unsigned int antiStuckInterval = 2592000; unsigned int antiStuckInterval = 2592000;
unsigned short antiStuckTime = 300; unsigned short antiStuckTime = 300;
@@ -127,11 +147,6 @@ struct Settings {
} settings; } settings;
struct Variables { struct Variables {
struct {
bool enable = false;
byte regulator = 0;
} tuning;
struct { struct {
bool otStatus = false; bool otStatus = false;
bool emergency = false; bool emergency = false;
@@ -141,6 +156,7 @@ struct Variables {
bool fault = false; bool fault = false;
bool diagnostic = false; bool diagnostic = false;
bool externalPump = false; bool externalPump = false;
bool mqtt = false;
} states; } states;
struct { struct {
@@ -155,14 +171,16 @@ struct Variables {
float indoor = 0.0f; float indoor = 0.0f;
float outdoor = 0.0f; float outdoor = 0.0f;
float heating = 0.0f; float heating = 0.0f;
float heatingReturn = 0.0f;
float dhw = 0.0f; float dhw = 0.0f;
float exhaust = 0.0f;
} temperatures; } temperatures;
struct { struct {
bool heatingEnabled = false; bool heatingEnabled = false;
byte heatingMinTemp = DEFAULT_HEATING_MIN_TEMP; byte heatingMinTemp = DEFAULT_HEATING_MIN_TEMP;
byte heatingMaxTemp = DEFAULT_HEATING_MAX_TEMP; byte heatingMaxTemp = DEFAULT_HEATING_MAX_TEMP;
byte heatingSetpoint = 0; float heatingSetpoint = 0;
unsigned long extPumpLastEnableTime = 0; unsigned long extPumpLastEnableTime = 0;
byte dhwMinTemp = DEFAULT_DHW_MIN_TEMP; byte dhwMinTemp = DEFAULT_DHW_MIN_TEMP;
byte dhwMaxTemp = DEFAULT_DHW_MAX_TEMP; byte dhwMaxTemp = DEFAULT_DHW_MAX_TEMP;

View File

@@ -1,28 +1,27 @@
#define PROJECT_NAME "OpenTherm Gateway" #define PROJECT_NAME "OpenTherm Gateway"
#define PROJECT_VERSION "1.4.0-rc.15" #define PROJECT_VERSION "1.4.1"
#define PROJECT_REPO "https://github.com/Laxilef/OTGateway" #define PROJECT_REPO "https://github.com/Laxilef/OTGateway"
#define EMERGENCY_TIME_TRESHOLD 120000 #define MQTT_RECONNECT_INTERVAL 15000
#define MQTT_RECONNECT_INTERVAL 15000
#define MQTT_KEEPALIVE 30
#define OPENTHERM_OFFLINE_TRESHOLD 10 #define EXT_SENSORS_INTERVAL 5000
#define EXT_SENSORS_FILTER_K 0.15
#define EXT_SENSORS_INTERVAL 5000 #define CONFIG_URL "http://%s/"
#define EXT_SENSORS_FILTER_K 0.15 #define SETTINGS_VALID_VALUE "stvalid" // only 8 chars!
#define GPIO_IS_NOT_CONFIGURED 0xff
#define CONFIG_URL "http://%s/" #define DEFAULT_HEATING_TARGET_TEMP 40
#define SETTINGS_VALID_VALUE "stvalid" // only 8 chars! #define DEFAULT_HEATING_MIN_TEMP 20
#define DEFAULT_HEATING_MAX_TEMP 90
#define DEFAULT_HEATING_MIN_TEMP 20 #define DEFAULT_DHW_TARGET_TEMP 40
#define DEFAULT_HEATING_MAX_TEMP 90 #define DEFAULT_DHW_MIN_TEMP 30
#define DEFAULT_DHW_MIN_TEMP 30 #define DEFAULT_DHW_MAX_TEMP 60
#define DEFAULT_DHW_MAX_TEMP 60
#define THERMOSTAT_INDOOR_DEFAULT_TEMP 20
#ifndef WM_DEBUG_MODE #define THERMOSTAT_INDOOR_MIN_TEMP 5
#define WM_DEBUG_MODE WM_DEBUG_NOTIFY #define THERMOSTAT_INDOOR_MAX_TEMP 30
#endif
#ifndef USE_SERIAL #ifndef USE_SERIAL
#define USE_SERIAL true #define USE_SERIAL true
@@ -36,80 +35,112 @@
#define USE_BLE false #define USE_BLE false
#endif #endif
#ifndef HOSTNAME_DEFAULT #ifndef DEFAULT_HOSTNAME
#define HOSTNAME_DEFAULT "opentherm" #define DEFAULT_HOSTNAME "opentherm"
#endif #endif
#ifndef AP_SSID_DEFAULT #ifndef DEFAULT_AP_SSID
#define AP_SSID_DEFAULT "OpenTherm Gateway" #define DEFAULT_AP_SSID "OpenTherm Gateway"
#endif #endif
#ifndef AP_PASSWORD_DEFAULT #ifndef DEFAULT_AP_PASSWORD
#define AP_PASSWORD_DEFAULT "otgateway123456" #define DEFAULT_AP_PASSWORD "otgateway123456"
#endif #endif
#ifndef STA_SSID_DEFAULT #ifndef DEFAULT_STA_SSID
#define STA_SSID_DEFAULT "" #define DEFAULT_STA_SSID ""
#endif #endif
#ifndef STA_PASSWORD_DEFAULT #ifndef DEFAULT_STA_PASSWORD
#define STA_PASSWORD_DEFAULT "" #define DEFAULT_STA_PASSWORD ""
#endif #endif
#ifndef DEBUG_BY_DEFAULT #ifndef DEBUG_BY_DEFAULT
#define DEBUG_BY_DEFAULT false #define DEBUG_BY_DEFAULT false
#endif #endif
#ifndef PORTAL_LOGIN_DEFAULT #ifndef DEFAULT_STATUS_LED_GPIO
#define PORTAL_LOGIN_DEFAULT "" #define DEFAULT_STATUS_LED_GPIO GPIO_IS_NOT_CONFIGURED
#endif #endif
#ifndef PORTAL_PASSWORD_DEFAULT #ifndef DEFAULT_PORTAL_LOGIN
#define PORTAL_PASSWORD_DEFAULT "" #define DEFAULT_PORTAL_LOGIN ""
#endif #endif
#ifndef MQTT_SERVER_DEFAULT #ifndef DEFAULT_PORTAL_PASSWORD
#define MQTT_SERVER_DEFAULT "" #define DEFAULT_PORTAL_PASSWORD ""
#endif #endif
#ifndef MQTT_PORT_DEFAULT #ifndef DEFAULT_MQTT_SERVER
#define MQTT_PORT_DEFAULT 1883 #define DEFAULT_MQTT_SERVER ""
#endif #endif
#ifndef MQTT_USER_DEFAULT #ifndef DEFAULT_MQTT_PORT
#define MQTT_USER_DEFAULT "" #define DEFAULT_MQTT_PORT 1883
#endif #endif
#ifndef MQTT_PASSWORD_DEFAULT #ifndef DEFAULT_MQTT_USER
#define MQTT_PASSWORD_DEFAULT "" #define DEFAULT_MQTT_USER ""
#endif #endif
#ifndef MQTT_PREFIX_DEFAULT #ifndef DEFAULT_MQTT_PASSWORD
#define MQTT_PREFIX_DEFAULT "opentherm" #define DEFAULT_MQTT_PASSWORD ""
#endif #endif
#ifndef OT_IN_PIN_DEFAULT #ifndef DEFAULT_MQTT_PREFIX
#define OT_IN_PIN_DEFAULT 0 #define DEFAULT_MQTT_PREFIX "opentherm"
#endif #endif
#ifndef OT_OUT_PIN_DEFAULT #ifndef DEFAULT_OT_IN_GPIO
#define OT_OUT_PIN_DEFAULT 0 #define DEFAULT_OT_IN_GPIO GPIO_IS_NOT_CONFIGURED
#endif #endif
#ifndef SENSOR_OUTDOOR_PIN_DEFAULT #ifndef DEFAULT_OT_OUT_GPIO
#define SENSOR_OUTDOOR_PIN_DEFAULT 0 #define DEFAULT_OT_OUT_GPIO GPIO_IS_NOT_CONFIGURED
#endif #endif
#ifndef SENSOR_INDOOR_PIN_DEFAULT #ifndef DEFAULT_OT_RX_LED_GPIO
#define SENSOR_INDOOR_PIN_DEFAULT 0 #define DEFAULT_OT_RX_LED_GPIO GPIO_IS_NOT_CONFIGURED
#endif #endif
#ifndef EXT_PUMP_PIN_DEFAULT #ifndef DEFAULT_OT_FAULT_STATE_GPIO
#define EXT_PUMP_PIN_DEFAULT 0 #define DEFAULT_OT_FAULT_STATE_GPIO GPIO_IS_NOT_CONFIGURED
#endif
#ifndef DEFAULT_SENSOR_OUTDOOR_GPIO
#define DEFAULT_SENSOR_OUTDOOR_GPIO GPIO_IS_NOT_CONFIGURED
#endif
#ifndef DEFAULT_SENSOR_INDOOR_GPIO
#define DEFAULT_SENSOR_INDOOR_GPIO GPIO_IS_NOT_CONFIGURED
#endif
#ifndef DEFAULT_EXT_PUMP_GPIO
#define DEFAULT_EXT_PUMP_GPIO GPIO_IS_NOT_CONFIGURED
#endif #endif
#ifndef PROGMEM #ifndef PROGMEM
#define PROGMEM #define PROGMEM
#endif #endif
#ifdef ARDUINO_ARCH_ESP32
#include <driver/gpio.h>
#elif !defined(GPIO_IS_VALID_GPIO)
#define GPIO_IS_VALID_GPIO(gpioNum) (gpioNum >= 0 && gpioNum <= 16)
#endif
#define GPIO_IS_VALID(gpioNum) (gpioNum != GPIO_IS_NOT_CONFIGURED && GPIO_IS_VALID_GPIO(gpioNum))
enum class SensorType : byte {
BOILER,
MANUAL,
DS18B20,
BLUETOOTH
};
enum class UnitSystem : byte {
METRIC,
IMPERIAL
};
char buffer[255]; char buffer[255];

View File

@@ -4,11 +4,11 @@
#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 <NetworkManager.h> #include <NetworkMgr.h>
#include "Settings.h" #include "Settings.h"
#include <utils.h> #include "utils.h"
#if defined(ARDUINO_ARCH_ESP32) #if defined(ARDUINO_ARCH_ESP32)
#include <ESP32Scheduler.h> #include <ESP32Scheduler.h>
@@ -27,11 +27,13 @@
#include "PortalTask.h" #include "PortalTask.h"
#include "MainTask.h" #include "MainTask.h"
using namespace NetworkUtils;
// Vars // Vars
FileData fsNetworkSettings(&LittleFS, "/network.conf", 'n', &networkSettings, sizeof(networkSettings), 1000); FileData fsNetworkSettings(&LittleFS, "/network.conf", 'n', &networkSettings, sizeof(networkSettings), 1000);
FileData fsSettings(&LittleFS, "/settings.conf", 's', &settings, sizeof(settings), 60000); FileData fsSettings(&LittleFS, "/settings.conf", 's', &settings, sizeof(settings), 60000);
ESPTelnetStream* telnetStream = nullptr; ESPTelnetStream* telnetStream = nullptr;
Network::Manager* network = nullptr; NetworkMgr* network = nullptr;
// Tasks // Tasks
MqttTask* tMqtt; MqttTask* tMqtt;
@@ -109,12 +111,19 @@ void setup() {
} }
// logs // logs
if (!settings.system.useSerial) { if (!settings.system.serial.enable) {
Log.clearStreams();
Serial.end(); Serial.end();
Log.clearStreams();
} else if (settings.system.serial.baudrate != 115200) {
Serial.end();
Log.clearStreams();
Serial.begin(settings.system.serial.baudrate);
Log.addStream(&Serial);
} }
if (settings.system.useTelnet) { if (settings.system.telnet.enable) {
telnetStream = new ESPTelnetStream; telnetStream = new ESPTelnetStream;
telnetStream->setKeepAliveInterval(500); telnetStream->setKeepAliveInterval(500);
Log.addStream(telnetStream); Log.addStream(telnetStream);
@@ -123,7 +132,7 @@ void setup() {
Log.setLevel(settings.system.debug ? TinyLogger::Level::VERBOSE : TinyLogger::Level::INFO); Log.setLevel(settings.system.debug ? TinyLogger::Level::VERBOSE : TinyLogger::Level::INFO);
// network // network
network = (new Network::Manager) network = (new NetworkMgr)
->setHostname(networkSettings.hostname) ->setHostname(networkSettings.hostname)
->setStaCredentials( ->setStaCredentials(
#ifdef WOKWI #ifdef WOKWI
@@ -143,7 +152,7 @@ void setup() {
tMqtt = new MqttTask(false, 500); tMqtt = new MqttTask(false, 500);
Scheduler.start(tMqtt); Scheduler.start(tMqtt);
tOt = new OpenThermTask(false, 750); tOt = new OpenThermTask(true, 750);
Scheduler.start(tOt); Scheduler.start(tOt);
tSensors = new SensorsTask(true, EXT_SENSORS_INTERVAL); tSensors = new SensorsTask(true, EXT_SENSORS_INTERVAL);

File diff suppressed because it is too large Load Diff

415
src_data/dashboard.html Normal file
View File

@@ -0,0 +1,415 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Dashboard - OpenTherm Gateway</title>
<link rel="stylesheet" href="/static/pico.min.css">
<link rel="stylesheet" href="/static/app.css"/>
</head>
<body>
<header class="container">
<nav>
<ul>
<li><a href="/"><div class="logo">OpenTherm Gateway</div></a></li>
</ul>
<ul>
<li><a href="https://github.com/Laxilef/OTGateway/wiki" role="button" class="secondary" target="_blank">Help</a></li>
</ul>
</nav>
</header>
<main class="container">
<article>
<hgroup>
<h2>Dashboard</h2>
<p></p>
</hgroup>
<div id="dashboard-busy" aria-busy="true"></div>
<div id="dashboard-container" class="hidden">
<details open>
<summary><b>Control</b></summary>
<div class="grid">
<div class="thermostat" id="thermostat-heating">
<div class="thermostat-header">Heating</div>
<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-current">Current: <span id="thermostat-heating-current"></span> <span class="temp-unit"></span></div>
</div>
<div class="thermostat-minus"><button id="thermostat-heating-minus" 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-control">
<input type="checkbox" role="switch" id="thermostat-heating-enabled" value="true">
<label htmlFor="thermostat-heating-enabled">Enable</label>
<input type="checkbox" role="switch" id="thermostat-heating-turbo" value="true">
<label htmlFor="thermostat-heating-turbo">Turbo mode</label>
</div>
</div>
<div class="thermostat" id="thermostat-dhw">
<div class="thermostat-header">DHW</div>
<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-current">Current: <span id="thermostat-dhw-current"></span> <span class="temp-unit"></span></div>
</div>
<div class="thermostat-minus"><button class="outline" id="thermostat-dhw-minus"><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-control">
<input type="checkbox" role="switch" id="thermostat-dhw-enabled" value="true">
<label htmlFor="thermostat-dhw-enabled">Enable</label>
</div>
</div>
</div>
</details>
<hr />
<details>
<summary><b>States and sensors</b></summary>
<table>
<tbody>
<tr>
<th scope="row">OpenTherm connected:</th>
<td><input type="radio" id="ot-connected" aria-invalid="false" checked disabled /></td>
</tr>
<tr>
<th scope="row">MQTT connected:</th>
<td><input type="radio" id="mqtt-connected" aria-invalid="false" checked disabled /></td>
</tr>
<tr>
<th scope="row">Emergency:</th>
<td><input type="radio" id="ot-emergency" aria-invalid="false" checked disabled /></td>
</tr>
<tr>
<th scope="row">Heating:</th>
<td><input type="radio" id="ot-heating" aria-invalid="false" checked disabled /></td>
</tr>
<tr>
<th scope="row">DHW:</th>
<td><input type="radio" id="ot-dhw" aria-invalid="false" checked disabled /></td>
</tr>
<tr>
<th scope="row">Flame:</th>
<td><input type="radio" id="ot-flame" aria-invalid="false" checked disabled /></td>
</tr>
<tr>
<th scope="row">Fault:</th>
<td><input type="radio" id="ot-fault" aria-invalid="false" checked disabled /></td>
</tr>
<tr>
<th scope="row">Diagnostic:</th>
<td><input type="radio" id="ot-diagnostic" aria-invalid="false" checked disabled /></td>
</tr>
<tr>
<th scope="row">External pump:</th>
<td><input type="radio" id="ot-external-pump" aria-invalid="false" checked disabled /></td>
</tr>
<tr>
<th scope="row">Modulation:</th>
<td><b id="ot-modulation"></b> %</td>
</tr>
<tr>
<th scope="row">Pressure:</th>
<td><b id="ot-pressure"></b> <span class="pressure-unit"></span></td>
</tr>
<tr>
<th scope="row">DHW flow rate:</th>
<td><b id="ot-dhw-flow-rate"></b> <span class="volume-unit"></span>/min</td>
</tr>
<tr>
<th scope="row">Fault code:</th>
<td><b id="ot-fault-code"></b></td>
</tr>
<tr>
<th scope="row">Indoor temp:</th>
<td><b id="indoor-temp"></b> <span class="temp-unit"></span></td>
</tr>
<tr>
<th scope="row">Outdoor temp:</th>
<td><b id="outdoor-temp"></b> <span class="temp-unit"></span></td>
</tr>
<tr>
<th scope="row">Heating temp:</th>
<td><b id="heating-temp"></b> <span class="temp-unit"></span></td>
</tr>
<tr>
<th scope="row">Heating setpoint temp:</th>
<td><b id="heating-setpoint-temp"></b> <span class="temp-unit"></span></td>
</tr>
<tr>
<th scope="row">Heating return temp:</th>
<td><b id="heating-return-temp"></b> <span class="temp-unit"></span></td>
</tr>
<tr>
<th scope="row">DHW temp:</th>
<td><b id="dhw-temp"></b> <span class="temp-unit"></span></td>
</tr>
<tr>
<th scope="row">Exhaust temp:</th>
<td><b id="exhaust-temp"></b> <span class="temp-unit"></span></td>
</tr>
</tbody>
</table>
</details>
<hr />
<details>
<summary><b>OpenTherm diagnostic</b></summary>
<pre><b>Vendor:</b> <span id="slave-vendor"></span>
<b>Member ID:</b> <span id="slave-member-id"></span>
<b>Flags:</b> <span id="slave-flags"></span>
<b>Type:</b> <span id="slave-type"></span>
<b>Version:</b> <span id="slave-version"></span>
<b>OT version:</b> <span id="slave-ot-version"></span>
<b>Heating limits:</b> <span id="heating-min-temp"></span>...<span id="heating-max-temp"></span> <span class="temp-unit"></span>
<b>DHW limits:</b> <span id="dhw-min-temp"></span>...<span id="dhw-max-temp"></span> <span class="temp-unit"></span></pre>
</details>
</div>
</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">License</a>
<a href="https://github.com/Laxilef/OTGateway/blob/master/" target="_blank" class="secondary">Source code</a>
<a href="https://github.com/Laxilef/OTGateway/wiki" target="_blank" class="secondary">Help</a>
<a href="https://github.com/Laxilef/OTGateway/issues" target="_blank" class="secondary">Issue & questions</a>
<a href="https://github.com/Laxilef/OTGateway/releases" target="_blank" class="secondary">Releases</a>
</small>
</footer>
<script src="/static/app.js"></script>
<script>
let modifiedTime = null;
let noRegulators;
let prevSettings;
let newSettings = {
heating: {
enable: false,
turbo: false,
target: 0
},
dhw: {
enable: false,
target: 0
}
};
window.onload = async function () {
document.querySelector('#thermostat-heating-minus').addEventListener('click', (event) => {
if (!prevSettings) {
return;
}
newSettings.heating.target -= 0.5;
modifiedTime = Date.now();
let minTemp;
if (noRegulators) {
minTemp = prevSettings.heating.minTemp;
} else {
minTemp = prevSettings.system.unitSystem == 0 ? 5 : 41;
}
if (prevSettings && newSettings.heating.target < minTemp) {
newSettings.heating.target = minTemp;
}
setValue('#thermostat-heating-target', newSettings.heating.target);
});
document.querySelector('#thermostat-heating-plus').addEventListener('click', (event) => {
if (!prevSettings) {
return;
}
newSettings.heating.target += 0.5;
modifiedTime = Date.now();
let maxTemp;
if (noRegulators) {
maxTemp = prevSettings.heating.maxTemp;
} else {
maxTemp = prevSettings.system.unitSystem == 0 ? 30 : 86;
}
if (prevSettings && newSettings.heating.target > maxTemp) {
newSettings.heating.target = maxTemp;
}
setValue('#thermostat-heating-target', newSettings.heating.target);
});
document.querySelector('#thermostat-dhw-minus').addEventListener('click', (event) => {
if (!prevSettings) {
return;
}
newSettings.dhw.target -= 1.0;
modifiedTime = Date.now();
if (newSettings.dhw.target < prevSettings.dhw.minTemp) {
newSettings.dhw.target = prevSettings.dhw.minTemp;
}
setValue('#thermostat-dhw-target', newSettings.dhw.target);
});
document.querySelector('#thermostat-dhw-plus').addEventListener('click', (event) => {
if (!prevSettings) {
return;
}
newSettings.dhw.target += 1.0;
modifiedTime = Date.now();
if (newSettings.dhw.target > prevSettings.dhw.maxTemp) {
newSettings.dhw.target = prevSettings.dhw.maxTemp;
}
setValue('#thermostat-dhw-target', newSettings.dhw.target);
});
document.querySelector('#thermostat-heating-enabled').addEventListener('change', (event) => {
modifiedTime = Date.now();
newSettings.heating.enable = event.currentTarget.checked;
});
document.querySelector('#thermostat-heating-turbo').addEventListener('change', (event) => {
modifiedTime = Date.now();
newSettings.heating.turbo = event.currentTarget.checked;
});
document.querySelector('#thermostat-dhw-enabled').addEventListener('change', (event) => {
modifiedTime = Date.now();
newSettings.dhw.enable = event.currentTarget.checked;
});
setTimeout(async function onLoadPage() {
if (modifiedTime) {
if ((Date.now() - modifiedTime) < 5000) {
setTimeout(onLoadPage, 1000);
return;
}
modifiedTime = null;
}
// settings
try {
let modified = prevSettings && (
(prevSettings.heating.enable != newSettings.heating.enable)
|| (prevSettings.heating.turbo != newSettings.heating.turbo)
|| (prevSettings.heating.target != newSettings.heating.target)
|| (prevSettings.opentherm.dhwPresent && prevSettings.dhw.enable != newSettings.dhw.enable)
|| (prevSettings.opentherm.dhwPresent && prevSettings.dhw.target != newSettings.dhw.target)
);
if (modified) {
console.log(newSettings);
}
let parameters = {cache: 'no-cache'};
if (modified) {
parameters.method = "POST";
parameters.body = JSON.stringify(newSettings);
}
const response = await fetch('/api/settings', parameters);
if (!response.ok) {
throw new Error('Response not valid');
}
const result = await response.json();
noRegulators = !result.opentherm.nativeHeatingControl && !result.equitherm.enable && !result.pid.enable;
prevSettings = result;
newSettings.heating.enable = result.heating.enable;
newSettings.heating.turbo = result.heating.turbo;
newSettings.heating.target = result.heating.target;
newSettings.dhw.enable = result.dhw.enable;
newSettings.dhw.target = result.dhw.target;
if (result.opentherm.dhwPresent) {
show('#thermostat-dhw');
} else {
hide('#thermostat-dhw');
}
setCheckboxValue('#thermostat-heating-enabled', result.heating.enable);
setCheckboxValue('#thermostat-heating-turbo', result.heating.turbo);
setValue('#thermostat-heating-target', result.heating.target);
setCheckboxValue('#thermostat-dhw-enabled', result.dhw.enable);
setValue('#thermostat-dhw-target', result.dhw.target);
setValue('.temp-unit', temperatureUnit(result.system.unitSystem));
setValue('.pressure-unit', pressureUnit(result.system.unitSystem));
setValue('.volume-unit', volumeUnit(result.system.unitSystem));
} catch (error) {
console.log(error);
}
// vars
try {
const response = await fetch('/api/vars', { cache: 'no-cache' });
if (!response.ok) {
throw new Error('Response not valid');
}
const result = await response.json();
setValue('#thermostat-heating-current', noRegulators ? result.temperatures.heating : result.temperatures.indoor);
setValue('#thermostat-dhw-current', result.temperatures.dhw);
setState('#ot-connected', result.states.otStatus);
setState('#ot-emergency', result.states.emergency);
setState('#ot-heating', result.states.heating);
setState('#ot-dhw', result.states.dhw);
setState('#ot-flame', result.states.flame);
setState('#ot-fault', result.states.fault);
setState('#ot-diagnostic', result.states.diagnostic);
setState('#ot-external-pump', result.states.externalPump);
setState('#mqtt-connected', result.states.mqtt);
setValue('#ot-modulation', result.sensors.modulation);
setValue('#ot-pressure', result.sensors.pressure);
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('#outdoor-temp', result.temperatures.outdoor);
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('#heating-max-temp', result.parameters.heatingMaxTemp);
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);
setValue('#slave-vendor', memberIdToVendor(result.parameters.slaveMemberId));
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);
} catch (error) {
console.log(error);
}
setTimeout(onLoadPage, 10000);
}, 1000);
};
</script>
</body>
</html>

191
src_data/index.html Normal file
View File

@@ -0,0 +1,191 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>OpenTherm Gateway</title>
<link rel="stylesheet" href="/static/pico.min.css">
<link rel="stylesheet" href="/static/app.css"/>
</head>
<body>
<header class="container">
<nav>
<ul>
<li><a href="/"><div class="logo">OpenTherm Gateway</div></a></li>
</ul>
<ul>
<li><a href="https://github.com/Laxilef/OTGateway/wiki" role="button" class="secondary" target="_blank">Help</a></li>
</ul>
</nav>
</header>
<main class="container">
<article>
<div>
<hgroup>
<h2>Network</h2>
<p></p>
</hgroup>
<div id="main-busy" aria-busy="true"></div>
<table id="main-table" class="hidden">
<tbody>
<tr>
<th scope="row">Hostname:</th>
<td><b id="network-hostname"></b></td>
</tr>
<tr>
<th scope="row">MAC:</th>
<td><b id="network-mac"></b></td>
</tr>
<tr>
<th scope="row">Connected:</th>
<td><input type="radio" id="network-connected" aria-invalid="false" checked disabled /></td>
</tr>
<tr>
<th scope="row">SSID:</th>
<td><b id="network-ssid"></b></td>
</tr>
<tr>
<th scope="row">Signal:</th>
<td><b id="network-signal"></b> %</td>
</tr>
<tr>
<th scope="row">IP:</th>
<td><b id="network-ip"></b></td>
</tr>
<tr>
<th scope="row">Subnet:</th>
<td><b id="network-subnet"></b></td>
</tr>
<tr>
<th scope="row">Gateway:</th>
<td><b id="network-gateway"></b></td>
</tr>
<tr>
<th scope="row">DNS:</th>
<td><b id="network-dns"></b></td>
</tr>
</tbody>
</table>
<div class="grid">
<a href="/network.html" role="button">Network settings</a>
</div>
</div>
</article>
<article>
<div>
<hgroup>
<h2>System</h2>
<p></p>
</hgroup>
<div id="system-busy" aria-busy="true"></div>
<table id="system-table" class="hidden">
<tbody>
<tr>
<th scope="row">Version:</th>
<td><b id="version"></b>, core/sdk: <b id="core-version"></b></td>
</tr>
<tr>
<th scope="row">Build date:</th>
<td><b id="build-date"></b></td>
</tr>
<tr>
<th scope="row">Uptime:</th>
<td><b id="uptime-days"></b> days, <b id="uptime-hours"></b> hours, <b id="uptime-min"></b> min., <b id="uptime-sec"></b> sec.</td>
</tr>
<tr>
<th scope="row">Free memory:</th>
<td><b id="free-heap"></b> of <b id="total-heap"></b> bytes (min: <b id="min-free-heap"></b> bytes)<br />max free block: <b id="max-free-block-heap"></b> bytes (min: <b id="min-max-free-block-heap"></b> bytes)</td>
</tr>
<tr>
<th scope="row">Board:</th>
<td>Chip <b id="chip-model"></b> (rev. <span id="chip-revision"></span>)<br />Cores: <b id="chip-cores"></b>, frequency: <b id="cpu-freq"></b> mHz<br />Flash size: <b id="flash-size"></b> MB (real: <b id="flash-real-size"></b> MB)</td>
</tr>
<tr>
<th scope="row">Last reset reason:</th>
<td><b id="reset-reason"></b></td>
</tr>
</tbody>
</table>
<div class="grid">
<a href="/dashboard.html" role="button">Dashboard</a>
<a href="/settings.html" role="button">Settings</a>
<a href="/upgrade.html" role="button">Upgrade</a>
<a href="/restart.html" role="button" class="secondary restart">Restart</a>
</div>
</div>
</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">License</a>
<a href="https://github.com/Laxilef/OTGateway/blob/master/" target="_blank" class="secondary">Source code</a>
<a href="https://github.com/Laxilef/OTGateway/wiki" target="_blank" class="secondary">Help</a>
<a href="https://github.com/Laxilef/OTGateway/issues" target="_blank" class="secondary">Issue & questions</a>
<a href="https://github.com/Laxilef/OTGateway/releases" target="_blank" class="secondary">Releases</a>
</small>
</footer>
<script src="/static/app.js"></script>
<script>
window.onload = async function () {
setTimeout(async function onLoadPage() {
try {
const response = await fetch('/api/info', { cache: 'no-cache' });
if (!response.ok) {
throw new Error('Response not valid');
}
const result = await response.json();
setValue('#network-hostname', result.network.hostname);
setValue('#network-mac', result.network.mac);
setState('#network-connected', result.network.connected);
setValue('#network-ssid', result.network.ssid);
setValue('#network-signal', result.network.signalQuality);
setValue('#network-ip', result.network.ip);
setValue('#network-subnet', result.network.subnet);
setValue('#network-gateway', result.network.gateway);
setValue('#network-dns', result.network.dns);
setBusy('#main-busy', '#main-table', false);
setValue('#version', result.system.version);
setValue('#build-date', result.system.buildDate);
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('#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('#chip-revision', result.system.chipRevision);
setValue('#chip-cores', result.system.chipCores);
setValue('#cpu-freq', result.system.cpuFreq);
setValue('#core-version', result.system.coreVersion);
setValue('#flash-size', result.system.flashSize / 1024 / 1024);
setValue('#flash-real-size', result.system.flashRealSize / 1024 / 1024);
setBusy('#system-busy', '#system-table', false);
} catch (error) {
console.log(error);
}
setTimeout(onLoadPage, 10000);
}, 1000);
};
</script>
</body>
</html>

View File

@@ -31,32 +31,38 @@
<div id="network-settings-busy" aria-busy="true"></div> <div id="network-settings-busy" aria-busy="true"></div>
<form action="/api/network/settings" id="network-settings" class="hidden"> <form action="/api/network/settings" id="network-settings" class="hidden">
<label for="hostname"> <label for="network-hostname">
Hostname Hostname
<input type="text" class="network-hostname" name="hostname" maxlength="24" pattern="[A-Za-z0-9]+[A-Za-z0-9\-]+[A-Za-z0-9]+" required> <input type="text" id="network-hostname" name="hostname" maxlength="24" pattern="[A-Za-z0-9]+[A-Za-z0-9\-]+[A-Za-z0-9]+" required>
</label> </label>
<label for="network-use-dhcp"> <label for="network-use-dhcp">
<input type="checkbox" class="network-use-dhcp" name="useDhcp" value="true"> <input type="checkbox" id="network-use-dhcp" name="useDhcp" value="true">
Use DHCP Use DHCP
</label> </label>
<br> <br />
<hr> <hr />
<label for="network-static-ip"> <label for="network-static-ip">
Static IP: Static IP:
<input type="text" class="network-static-ip" name="staticConfig[ip]" value="true" maxlength="16" pattern="\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}" required> <input type="text" id="network-static-ip" name="staticConfig[ip]" value="true" maxlength="16" pattern="\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}" required>
</label> </label>
<label for="network-static-gateway"> <label for="network-static-gateway">
Static gateway: Static gateway:
<input type="text" class="network-static-gateway" name="staticConfig[gateway]" maxlength="16" pattern="\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}" required> <input type="text" id="network-static-gateway" name="staticConfig[gateway]" maxlength="16" pattern="\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}" required>
</label> </label>
<label for="network-static-subnet"> <label for="network-static-subnet">
Static subnet: Static subnet:
<input type="text" class="network-static-subnet" name="staticConfig[subnet]" maxlength="16" pattern="\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}" required> <input type="text" id="network-static-subnet" name="staticConfig[subnet]" maxlength="16" pattern="\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}" required>
</label> </label>
<label for="network-static-dns"> <label for="network-static-dns">
Static DNS: Static DNS:
<input type="text" class="network-static-dns" name="staticConfig[dns]" maxlength="16" pattern="\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}" required> <input type="text" id="network-static-dns" name="staticConfig[dns]" maxlength="16" pattern="\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}" required>
</label> </label>
<button type="submit">Save</button> <button type="submit">Save</button>
</form> </form>
</div> </div>
@@ -70,40 +76,44 @@
</hgroup> </hgroup>
<form action="/api/network/scan" id="network-scan"> <form action="/api/network/scan" id="network-scan">
<figure style="max-height: 25em;"> <div style="max-height: 25rem;" class="overflow-auto">
<table id="networks" role="grid"> <table id="networks" role="grid">
<thead> <thead>
<tr> <tr>
<th scope="col">#</th> <th scope="col">#</th>
<th scope="col">SSID</th> <th scope="col">SSID</th>
<th scope="col">Signal</th> <th scope="col">Info</th>
</tr> </tr>
</thead> </thead>
<tbody></tbody> <tbody></tbody>
</table> </table>
</figure> </div>
<button type="submit">Refresh</button> <button type="submit">Refresh</button>
</form> </form>
<hr> <hr />
<div> <div>
<hgroup> <hgroup>
<h2>WiFi settings</h2> <h2>WiFi settings</h2>
<p></p> <p></p>
</hgroup> </hgroup>
<div id="sta-settings-busy" aria-busy="true"></div> <div id="sta-settings-busy" aria-busy="true"></div>
<form action="/api/network/settings" id="sta-settings" class="hidden"> <form action="/api/network/settings" id="sta-settings" class="hidden">
<label for="sta-ssid"> <label for="sta-ssid">
SSID: SSID:
<input type="text" class="sta-ssid" name="sta[ssid]" maxlength="32" required> <input type="text" id="sta-ssid" name="sta[ssid]" maxlength="32" required>
</label> </label>
<label for="sta-password"> <label for="sta-password">
Password: Password:
<input type="password" class="sta-password" name="sta[password]" maxlength="64" required> <input type="password" id="sta-password" name="sta[password]" maxlength="64" required>
</label> </label>
<label for="sta-channel"> <label for="sta-channel">
Channel: Channel:
<input type="number" inputmode="numeric" class="sta-channel" name="sta[channel]" min="0" max="12" step="1" required> <input type="number" inputmode="numeric" id="sta-channel" name="sta[channel]" min="0" max="12" step="1" required>
<small>set 0 for auto select</small> <small>set 0 for auto select</small>
</label> </label>
@@ -119,20 +129,24 @@
<h2>AP settings</h2> <h2>AP settings</h2>
<p></p> <p></p>
</hgroup> </hgroup>
<div id="ap-settings-busy" aria-busy="true"></div> <div id="ap-settings-busy" aria-busy="true"></div>
<form action="/api/network/settings" id="ap-settings" class="hidden"> <form action="/api/network/settings" id="ap-settings" class="hidden">
<label for="ap-ssid"> <label for="ap-ssid">
SSID: SSID:
<input type="text" class="ap-ssid" name="ap[ssid]" maxlength="32" required> <input type="text" id="ap-ssid" name="ap[ssid]" maxlength="32" required>
</label> </label>
<label for="ap-password"> <label for="ap-password">
Password: Password:
<input type="text" class="ap-password" name="ap[password]" maxlength="64" required> <input type="text" id="ap-password" name="ap[password]" maxlength="64" required>
</label> </label>
<label for="ap-channel"> <label for="ap-channel">
Channel: Channel:
<input type="number" inputmode="numeric" class="ap-channel" name="ap[channel]" min="1" max="12" step="1" required> <input type="number" inputmode="numeric" id="ap-channel" name="ap[channel]" min="1" max="12" step="1" required>
</label> </label>
<button type="submit">Save</button> <button type="submit">Save</button>
</form> </form>
</div> </div>
@@ -145,7 +159,7 @@
<a href="https://github.com/Laxilef/OTGateway/blob/master/LICENSE" target="_blank" class="secondary">License</a> <a href="https://github.com/Laxilef/OTGateway/blob/master/LICENSE" target="_blank" class="secondary">License</a>
<a href="https://github.com/Laxilef/OTGateway/blob/master/" target="_blank" class="secondary">Source code</a> <a href="https://github.com/Laxilef/OTGateway/blob/master/" target="_blank" class="secondary">Source code</a>
<a href="https://github.com/Laxilef/OTGateway/wiki" target="_blank" class="secondary">Help</a> <a href="https://github.com/Laxilef/OTGateway/wiki" target="_blank" class="secondary">Help</a>
<a href="https://github.com/Laxilef/OTGateway/issue" target="_blank" class="secondary">Issue & questions</a> <a href="https://github.com/Laxilef/OTGateway/issues" target="_blank" class="secondary">Issue & questions</a>
<a href="https://github.com/Laxilef/OTGateway/releases" target="_blank" class="secondary">Releases</a> <a href="https://github.com/Laxilef/OTGateway/releases" target="_blank" class="secondary">Releases</a>
</small> </small>
</footer> </footer>
@@ -153,12 +167,43 @@
<script src="/static/app.js"></script> <script src="/static/app.js"></script>
<script> <script>
window.onload = async function () { window.onload = async function () {
await loadNetworkSettings(); const fillData = (data) => {
setInputValue('#network-hostname', data.hostname);
setCheckboxValue('#network-use-dhcp', data.useDhcp);
setInputValue('#network-static-ip', data.staticConfig.ip);
setInputValue('#network-static-gateway', data.staticConfig.gateway);
setInputValue('#network-static-subnet', data.staticConfig.subnet);
setInputValue('#network-static-dns', data.staticConfig.dns);
setBusy('#network-settings-busy', '#network-settings', false);
setupForm('#network-settings'); setInputValue('#sta-ssid', data.sta.ssid);
setupNetworkScanForm('#network-scan', '#networks'); setInputValue('#sta-password', data.sta.password);
setupForm('#sta-settings'); setInputValue('#sta-channel', data.sta.channel);
setupForm('#ap-settings'); setBusy('#sta-settings-busy', '#sta-settings', false);
setInputValue('#ap-ssid', data.ap.ssid);
setInputValue('#ap-password', data.ap.password);
setInputValue('#ap-channel', data.ap.channel);
setBusy('#ap-settings-busy', '#ap-settings', false);
};
try {
const response = await fetch('/api/network/settings', { cache: 'no-cache' });
if (!response.ok) {
throw new Error('Response not valid');
}
const result = await response.json();
fillData(result);
setupForm('#network-settings', fillData, ['hostname']);
setupNetworkScanForm('#network-scan', '#networks');
setupForm('#sta-settings', fillData, ['sta.ssid', 'sta.password']);
setupForm('#ap-settings', fillData, ['ap.ssid', 'ap.password']);
} catch (error) {
console.log(error);
}
}; };
</script> </script>
</body> </body>

833
src_data/settings.html Normal file
View File

@@ -0,0 +1,833 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Settings - OpenTherm Gateway</title>
<link rel="stylesheet" href="/static/pico.min.css">
<link rel="stylesheet" href="/static/app.css" />
</head>
<body>
<header class="container">
<nav>
<ul>
<li><a href="/"><div class="logo">OpenTherm Gateway</div></a></li>
</ul>
<ul>
<li><a href="https://github.com/Laxilef/OTGateway/wiki" role="button" class="secondary" target="_blank">Help</a></li>
</ul>
</nav>
</header>
<main class="container">
<article>
<hgroup>
<h2>Settings</h2>
<p></p>
</hgroup>
<details>
<summary><b>Portal settings</b></summary>
<div>
<div id="portal-settings-busy" aria-busy="true"></div>
<form action="/api/settings" id="portal-settings" class="hidden">
<div class="grid">
<label for="portal-login">
Login
<input type="text" id="portal-login" name="portal[login]" maxlength="12" required>
</label>
<label for="portal-password">
Password
<input type="password" id="portal-password" name="portal[password]" maxlength="32" required>
</label>
</div>
<label for="portal-auth">
<input type="checkbox" id="portal-auth" name="portal[auth]" value="true">
Require authentication
</label>
<br />
<button type="submit">Save</button>
</form>
</div>
</details>
<hr />
<details>
<summary><b>System settings</b></summary>
<div>
<div id="system-settings-busy" aria-busy="true"></div>
<form action="/api/settings" id="system-settings" class="hidden">
<fieldset>
<legend>Unit system</legend>
<label>
<input type="radio" class="system-unit-system" name="system[unitSystem]" value="0" />
Metric (celsius, liters, bar)
</label>
<label>
<input type="radio" class="system-unit-system" name="system[unitSystem]" value="1" />
Imperial (fahrenheit, gallons, psi)
</label>
</fieldset>
<fieldset>
<label for="system-status-led-gpio">
Status LED GPIO
<input type="number" inputmode="numeric" id="system-status-led-gpio" name="system[statusLedGpio]" min="0" max="254" step="1">
<small>blank - not use</small>
</label>
</fieldset>
<fieldset>
<legend>Diagnostic</legend>
<label for="system-debug">
<input type="checkbox" id="system-debug" name="system[debug]" value="true">
Debug mode
</label>
<label for="system-serial-enable">
<input type="checkbox" id="system-serial-enable" name="system[serial][enable]" value="true">
Enable Serial port
</label>
<label for="system-telnet-enable">
<input type="checkbox" id="system-telnet-enable" name="system[telnet][enable]" value="true">
Enable Telnet
</label>
<div class="grid">
<label for="system-serial-baudrate">
Serial port baud rate
<input type="number" inputmode="numeric" id="system-serial-baudrate" name="system[serial][baudrate]" min="9600" max="115200" step="1" required>
<small>Available: 9600, 19200, 38400, 57600, 74880, 115200</small>
</label>
<label for="system-telnet-port">
Telnet port
<input type="number" inputmode="numeric" id="system-telnet-port" name="system[telnet][port]" min="1" max="65535" step="1" required>
<small>Default: 23</small>
</label>
</div>
<mark>After changing this settings, the ESP must be restarted for the changes to take effect.</mark>
</fieldset>
<button type="submit">Save</button>
</form>
</div>
</details>
<hr />
<details>
<summary><b>Heating settings</b></summary>
<div>
<div id="heating-settings-busy" aria-busy="true"></div>
<form action="/api/settings" id="heating-settings" class="hidden">
<div class="grid">
<label for="heating-min-temp">
Minimum temperature
<input type="number" inputmode="numeric" id="heating-min-temp" name="heating[minTemp]" min="0" max="0" step="1" required>
</label>
<label for="heating-max-temp">
Maximum temperature
<input type="number" inputmode="numeric" id="heating-max-temp" name="heating[maxTemp]" min="0" max="0" step="1" required>
</label>
</div>
<div class="grid">
<label for="heating-hysteresis">
Hysteresis
<input type="number" inputmode="numeric" id="heating-hysteresis" name="heating[hysteresis]" min="0" max="5" step="0.05" required>
</label>
<label for="heating-max-modulation">
Max modulation level
<input type="number" inputmode="numeric" id="heating-max-modulation" name="heating[maxModulation]" min="1" max="100" step="1" required>
</label>
</div>
<button type="submit">Save</button>
</form>
</div>
</details>
<hr />
<details>
<summary><b>DHW settings</b></summary>
<div>
<div id="dhw-settings-busy" aria-busy="true"></div>
<form action="/api/settings" id="dhw-settings" class="hidden">
<div class="grid">
<label for="dhw-min-temp">
Minimum temperature
<input type="number" inputmode="numeric" id="dhw-min-temp" name="dhw[minTemp]" min="0" max="0" step="1" required>
</label>
<label for="dhw-max-temp">
Maximum temperature
<input type="number" inputmode="numeric" id="dhw-max-temp" name="dhw[maxTemp]" min="0" max="0" step="1" required>
</label>
</div>
<button type="submit">Save</button>
</form>
</div>
</details>
<hr />
<details>
<summary><b>Emergency mode settings</b></summary>
<div>
<div id="emergency-settings-busy" aria-busy="true"></div>
<form action="/api/settings" id="emergency-settings" class="hidden">
<fieldset>
<label for="emergency-enable">
<input type="checkbox" id="emergency-enable" name="emergency[enable]" value="true">
Enable
</label>
<small>
<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.
</small>
</fieldset>
<div class="grid">
<label for="emergency-target">
Target temperature
<input type="number" inputmode="numeric" id="emergency-target" name="emergency[target]" min="0" max="0" step="1" required>
<small>
<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>
</small>
</label>
<label for="emergency-treshold-time">
Treshold time <small>(sec)</small>
<input type="number" inputmode="numeric" id="emergency-treshold-time" name="emergency[tresholdTime]" min="60" max="1800" step="1" required>
</label>
</div>
<fieldset>
<legend>Events</legend>
<label for="emergency-on-network-fault">
<input type="checkbox" id="emergency-on-network-fault" name="emergency[onNetworkFault]" value="true">
On network fault
</label>
<label for="emergency-on-mqtt-fault">
<input type="checkbox" id="emergency-on-mqtt-fault" name="emergency[onMqttFault]" value="true">
On MQTT fault
</label>
</fieldset>
<fieldset>
<legend>Using regulators</legend>
<label for="emergency-use-equitherm">
<input type="checkbox" id="emergency-use-equitherm" name="emergency[useEquitherm]" value="true">
<span>
Equitherm <small>(requires at least an external/boiler <u>outdoor</u> sensor)</small>
</span>
</label>
<label for="emergency-use-pid">
<input type="checkbox" id="emergency-use-pid" name="emergency[usePid]" value="true">
<span>
PID <small>(requires at least an external/BLE <u>indoor</u> sensor)</small>
</span>
</label>
</fieldset>
<button type="submit">Save</button>
</form>
</div>
</details>
<hr />
<details>
<summary><b>Equitherm settings</b></summary>
<div>
<div id="equitherm-settings-busy" aria-busy="true"></div>
<form action="/api/settings" id="equitherm-settings" class="hidden">
<fieldset>
<label for="equitherm-enable">
<input type="checkbox" id="equitherm-enable" name="equitherm[enable]" value="true">
Enable
</label>
</fieldset>
<div class="grid">
<label for="equitherm-n-factor">
N factor
<input type="number" inputmode="numeric" id="equitherm-n-factor" name="equitherm[n_factor]" min="0.001" max="10" step="0.001" required>
</label>
<label for="equitherm-k-factor">
K factor
<input type="number" inputmode="numeric" id="equitherm-k-factor" name="equitherm[k_factor]" min="0" max="10" step="0.01" required>
</label>
<label for="equitherm-t-factor">
T factor
<input type="number" inputmode="numeric" id="equitherm-t-factor" name="equitherm[t_factor]" min="0" max="10" step="0.01" required>
<small>Not used if PID is enabled</small>
</label>
</div>
<button type="submit">Save</button>
</form>
</div>
</details>
<hr />
<details>
<summary><b>PID settings</b></summary>
<div>
<div id="pid-settings-busy" aria-busy="true"></div>
<form action="/api/settings" id="pid-settings" class="hidden">
<fieldset>
<label for="pid-enable">
<input type="checkbox" id="pid-enable" name="pid[enable]" value="true">
Enable
</label>
</fieldset>
<div class="grid">
<label for="pid-p-factor">
P factor
<input type="number" inputmode="numeric" id="pid-p-factor" name="pid[p_factor]" min="0.1" max="1000" step="0.1" required>
</label>
<label for="pid-i-factor">
I factor
<input type="number" inputmode="numeric" id="pid-i-factor" name="pid[i_factor]" min="0" max="100" step="0.0001" required>
</label>
<label for="pid-d-factor">
D factor
<input type="number" inputmode="numeric" id="pid-d-factor" name="pid[d_factor]" min="0" max="100000" step="1" required>
</label>
</div>
<label for="pid-dt">
DT <small>in seconds</small>
<input type="number" inputmode="numeric" id="pid-dt" name="pid[dt]" min="30" max="600" step="1" required>
</label>
<hr />
<div class="grid">
<label for="pid-min-temp">
Minimum temperature
<input type="number" inputmode="numeric" id="pid-min-temp" name="pid[minTemp]" min="0" max="0" step="1" required>
</label>
<label for="pid-max-temp">
Maximum temperature
<input type="number" inputmode="numeric" id="pid-max-temp" name="pid[maxTemp]" min="0" max="0" step="1" required>
</label>
</div>
<button type="submit">Save</button>
</form>
</div>
</details>
<hr />
<details>
<summary><b>OpenTherm settings</b></summary>
<div>
<div id="opentherm-settings-busy" aria-busy="true"></div>
<form action="/api/settings" id="opentherm-settings" class="hidden">
<fieldset>
<legend>Unit system</legend>
<label>
<input type="radio" class="opentherm-unit-system" name="opentherm[unitSystem]" value="0" />
Metric (celsius)
</label>
<label>
<input type="radio" class="opentherm-unit-system" name="opentherm[unitSystem]" value="1" />
Imperial (fahrenheit)
</label>
</fieldset>
<div class="grid">
<label for="opentherm-in-gpio">
In GPIO
<input type="number" inputmode="numeric" id="opentherm-in-gpio" name="opentherm[inGpio]" min="0" max="254" step="1">
</label>
<label for="opentherm-in-gpio">
Out GPIO
<input type="number" inputmode="numeric" id="opentherm-out-gpio" name="opentherm[outGpio]" min="0" max="254" step="1">
</label>
</div>
<div class="grid">
<label for="opentherm-rx-led-gpio">
RX LED GPIO
<input type="number" inputmode="numeric" id="opentherm-rx-led-gpio" name="opentherm[rxLedGpio]" min="0" max="254" step="1">
<small>blank - not use</small>
</label>
<label for="opentherm-member-id-code">
Master MemberID code
<input type="number" inputmode="numeric" id="opentherm-member-id-code" name="opentherm[memberIdCode]" min="0" max="65535" step="1" required>
</label>
</div>
<fieldset>
<legend>Options</legend>
<label for="opentherm-dhw-present">
<input type="checkbox" id="opentherm-dhw-present" name="opentherm[dhwPresent]" value="true">
DHW present
</label>
<label for="opentherm-sw-mode">
<input type="checkbox" id="opentherm-sw-mode" name="opentherm[summerWinterMode]" value="true">
Summer/winter mode
</label>
<label for="opentherm-heating-ch2-enabled">
<input type="checkbox" id="opentherm-heating-ch2-enabled" name="opentherm[heatingCh2Enabled]" value="true">
Heating CH2 always enabled
</label>
<label for="opentherm-heating-ch1-to-ch2">
<input type="checkbox" id="opentherm-heating-ch1-to-ch2" name="opentherm[heatingCh1ToCh2]" value="true">
Duplicate heating CH1 to CH2
</label>
<label for="opentherm-dhw-to-ch2">
<input type="checkbox" id="opentherm-dhw-to-ch2" name="opentherm[dhwToCh2]" value="true">
Duplicate DHW to CH2
</label>
<label for="opentherm-dhw-blocking">
<input type="checkbox" id="opentherm-dhw-blocking" name="opentherm[dhwBlocking]" value="true">
DHW blocking
</label>
<label for="opentherm-sync-modulation-with-heating">
<input type="checkbox" id="opentherm-sync-modulation-with-heating" name="opentherm[modulationSyncWithHeating]" value="true">
Sync modulation with heating
</label>
<label for="opentherm-get-min-max-temp">
<input type="checkbox" id="opentherm-get-min-max-temp" name="opentherm[getMinMaxTemp]" value="true">
Get min/max temp from boiler
</label>
<hr />
<fieldset>
<label for="opentherm-fault-state-gpio">
Fault state GPIO
<input type="number" inputmode="numeric" id="opentherm-fault-state-gpio" name="opentherm[faultStateGpio]" min="0" max="254" step="1">
<small>Can be useful to switch on another boiler <u>via relay</u>. Blank - not use.</small>
</label>
<label for="opentherm-invert-fault-state">
<input type="checkbox" id="opentherm-invert-fault-state" name="opentherm[invertFaultState]" value="true">
Invert fault state
</label>
</fieldset>
<hr />
<label for="opentherm-native-heating-control">
<input type="checkbox" id="opentherm-native-heating-control" name="opentherm[nativeHeatingControl]" value="true">
Native heating control (boiler)<br />
<small>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.</small>
</label>
</fieldset>
<button type="submit">Save</button>
</form>
</div>
</details>
<hr />
<details>
<summary><b>MQTT settings</b></summary>
<div>
<div id="mqtt-settings-busy" aria-busy="true"></div>
<form action="/api/settings" id="mqtt-settings" class="hidden">
<fieldset>
<label for="mqtt-enable">
<input type="checkbox" id="mqtt-enable" name="mqtt[enable]" value="true">
Enable
</label>
<label for="mqtt-ha-discovery">
<input type="checkbox" id="mqtt-ha-discovery" name="mqtt[homeAssistantDiscovery]" value="true">
Home Assistant Discovery
</label>
</fieldset>
<div class="grid">
<label for="mqtt-server">
Server
<input type="text" id="mqtt-server" name="mqtt[server]" maxlength="80" required>
</label>
<label for="mqtt-port">
Port
<input type="number" inputmode="numeric" id="mqtt-port" name="mqtt[port]" min="1" max="65535" step="1" required>
</label>
</div>
<div class="grid">
<label for="mqtt-user">
User
<input type="text" id="mqtt-user" name="mqtt[user]" maxlength="32" required>
</label>
<label for="mqtt-password">
Password
<input type="password" id="mqtt-password" name="mqtt[password]" maxlength="32">
</label>
</div>
<div class="grid">
<label for="mqtt-prefix">
Prefix
<input type="text" id="mqtt-prefix" name="mqtt[prefix]" maxlength="32" required>
</label>
<label for="mqtt-interval">
Publish interval <small>(sec)</small>
<input type="number" inputmode="numeric" id="mqtt-interval" name="mqtt[interval]" min="3" max="60" step="1" required>
</label>
</div>
<button type="submit">Save</button>
</form>
</div>
</details>
<hr />
<details>
<summary><b>Outdoor sensor settings</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>Source type</legend>
<label>
<input type="radio" class="outdoor-sensor-type" name="sensors[outdoor][type]" value="0" />
From boiler via OpenTherm
</label>
<label>
<input type="radio" class="outdoor-sensor-type" name="sensors[outdoor][type]" value="1" />
Manual via MQTT/API
</label>
<label>
<input type="radio" class="outdoor-sensor-type" name="sensors[outdoor][type]" value="2" />
External (DS18B20)
</label>
</fieldset>
<label for="outdoor-sensor-gpio">
GPIO
<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">
Temp offset (calibration)
<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">Save</button>
</form>
</div>
</details>
<hr />
<details>
<summary><b>Indoor sensor settings</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>Source type</legend>
<label>
<input type="radio" class="indoor-sensor-type" name="sensors[indoor][type]" value="1" />
Manual via MQTT/API
</label>
<label>
<input type="radio" class="indoor-sensor-type" name="sensors[indoor][type]" value="2" />
External (DS18B20)
</label>
<label>
<input type="radio" class="indoor-sensor-type" name="sensors[indoor][type]" value="3" />
BLE device <i>(ONLY for some ESP32 which support BLE)</i>
</label>
</fieldset>
<label for="indoor-sensor-gpio">
GPIO
<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">
Temp offset (calibration)
<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">
BLE addresss
<input type="text" id="indoor-sensor-ble-addresss" name="sensors[indoor][bleAddresss]" pattern="([A-Fa-f0-9]{2}:){5}[A-Fa-f0-9]{2}">
<small>ONLY for some ESP32 which support BLE</small>
</label>
</div>
<button type="submit">Save</button>
</form>
</div>
</details>
<hr />
<details>
<summary><b>External pump settings</b></summary>
<div>
<div id="extpump-settings-busy" aria-busy="true"></div>
<form action="/api/settings" id="extpump-settings" class="hidden">
<fieldset>
<label for="extpump-use">
<input type="checkbox" id="extpump-use" name="externalPump[use]" value="true">
Use external pump
</label>
</fieldset>
<div class="grid">
<label for="extpump-gpio">
Relay GPIO
<input type="number" inputmode="numeric" id="extpump-gpio" name="externalPump[gpio]" min="0" max="254" step="1">
</label>
<label for="extpump-pc-time">
Post circulation time <small>(min)</small>
<input type="number" inputmode="numeric" id="extpump-pc-time" name="externalPump[postCirculationTime]" min="1" max="120" step="1" required>
</label>
</div>
<div class="grid">
<label for="extpump-as-interval">
Anti stuck interval <small>(days)</small>
<input type="number" inputmode="numeric" id="extpump-as-interval" name="externalPump[antiStuckInterval]" min="1" max="366" step="1" required>
</label>
<label for="extpump-as-time">
Anti stuck time <small>(min)</small>
<input type="number" inputmode="numeric" id="extpump-as-time" name="externalPump[antiStuckTime]" min="1" max="20" step="1" required>
</label>
</div>
<button type="submit">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">License</a>
<a href="https://github.com/Laxilef/OTGateway/blob/master/" target="_blank" class="secondary">Source code</a>
<a href="https://github.com/Laxilef/OTGateway/wiki" target="_blank" class="secondary">Help</a>
<a href="https://github.com/Laxilef/OTGateway/issues" target="_blank" class="secondary">Issue & questions</a>
<a href="https://github.com/Laxilef/OTGateway/releases" target="_blank" class="secondary">Releases</a>
</small>
</footer>
<script src="/static/app.js"></script>
<script>
window.onload = async function () {
const fillData = (data) => {
// System
setCheckboxValue('#system-debug', data.system.debug);
setCheckboxValue('#system-serial-enable', data.system.serial.enable);
setInputValue('#system-serial-baudrate', data.system.serial.baudrate);
setCheckboxValue('#system-telnet-enable', data.system.telnet.enable);
setInputValue('#system-telnet-port', data.system.telnet.port);
setRadioValue('.system-unit-system', data.system.unitSystem);
setInputValue('#system-status-led-gpio', data.system.statusLedGpio < 255 ? data.system.statusLedGpio : '');
setBusy('#system-settings-busy', '#system-settings', false);
// Portal
setCheckboxValue('#portal-auth', data.portal.auth);
setInputValue('#portal-login', data.portal.login);
setInputValue('#portal-password', data.portal.password);
setBusy('#portal-settings-busy', '#portal-settings', false);
// Opentherm
setRadioValue('.opentherm-unit-system', data.opentherm.unitSystem);
setInputValue('#opentherm-in-gpio', data.opentherm.inGpio < 255 ? data.opentherm.inGpio : '');
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-fault-state-gpio', data.opentherm.faultStateGpio < 255 ? data.opentherm.faultStateGpio : '');
setCheckboxValue('#opentherm-invert-fault-state', data.opentherm.invertFaultState);
setInputValue('#opentherm-member-id-code', data.opentherm.memberIdCode);
setCheckboxValue('#opentherm-dhw-present', data.opentherm.dhwPresent);
setCheckboxValue('#opentherm-sw-mode', data.opentherm.summerWinterMode);
setCheckboxValue('#opentherm-heating-ch2-enabled', data.opentherm.heatingCh2Enabled);
setCheckboxValue('#opentherm-heating-ch1-to-ch2', data.opentherm.heatingCh1ToCh2);
setCheckboxValue('#opentherm-dhw-to-ch2', data.opentherm.dhwToCh2);
setCheckboxValue('#opentherm-dhw-blocking', data.opentherm.dhwBlocking);
setCheckboxValue('#opentherm-sync-modulation-with-heating', data.opentherm.modulationSyncWithHeating);
setCheckboxValue('#opentherm-get-min-max-temp', data.opentherm.getMinMaxTemp);
setCheckboxValue('#opentherm-native-heating-control', data.opentherm.nativeHeatingControl);
setBusy('#opentherm-settings-busy', '#opentherm-settings', false);
// MQTT
setCheckboxValue('#mqtt-enable', data.mqtt.enable);
setCheckboxValue('#mqtt-ha-discovery', data.mqtt.homeAssistantDiscovery);
setInputValue('#mqtt-server', data.mqtt.server);
setInputValue('#mqtt-port', data.mqtt.port);
setInputValue('#mqtt-user', data.mqtt.user);
setInputValue('#mqtt-password', data.mqtt.password);
setInputValue('#mqtt-prefix', data.mqtt.prefix);
setInputValue('#mqtt-interval', data.mqtt.interval);
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.bleAddresss);
setBusy('#indoor-sensor-settings-busy', '#indoor-sensor-settings', false);
// Extpump
setCheckboxValue('#extpump-use', data.externalPump.use);
setInputValue('#extpump-gpio', data.externalPump.gpio < 255 ? data.externalPump.gpio : '');
setInputValue('#extpump-pc-time', data.externalPump.postCirculationTime);
setInputValue('#extpump-as-interval', data.externalPump.antiStuckInterval);
setInputValue('#extpump-as-time', data.externalPump.antiStuckTime);
setBusy('#extpump-settings-busy', '#extpump-settings', false);
// Heating
setInputValue('#heating-min-temp', data.heating.minTemp, {
"min": data.system.unitSystem == 0 ? 0 : 32,
"max": data.system.unitSystem == 0 ? 99 : 211
});
setInputValue('#heating-max-temp', data.heating.maxTemp, {
"min": data.system.unitSystem == 0 ? 1 : 33,
"max": data.system.unitSystem == 0 ? 100 : 212
});
setInputValue('#heating-hysteresis', data.heating.hysteresis);
setInputValue('#heating-max-modulation', data.heating.maxModulation);
setBusy('#heating-settings-busy', '#heating-settings', false);
// DHW
setInputValue('#dhw-min-temp', data.dhw.minTemp, {
"min": data.system.unitSystem == 0 ? 0 : 32,
"max": data.system.unitSystem == 0 ? 99 : 211
});
setInputValue('#dhw-max-temp', data.dhw.maxTemp, {
"min": data.system.unitSystem == 0 ? 1 : 33,
"max": data.system.unitSystem == 0 ? 100 : 212
});
setBusy('#dhw-settings-busy', '#dhw-settings', false);
// Emergency mode
setCheckboxValue('#emergency-enable', data.emergency.enable);
setInputValue('#emergency-treshold-time', data.emergency.tresholdTime);
setCheckboxValue('#emergency-use-equitherm', data.emergency.useEquitherm);
setCheckboxValue('#emergency-use-pid', data.emergency.usePid);
setCheckboxValue('#emergency-on-network-fault', data.emergency.onNetworkFault);
setCheckboxValue('#emergency-on-mqtt-fault', data.emergency.onMqttFault);
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,
});
setBusy('#emergency-settings-busy', '#emergency-settings', false);
// Equitherm
setCheckboxValue('#equitherm-enable', data.equitherm.enable);
setInputValue('#equitherm-n-factor', data.equitherm.n_factor);
setInputValue('#equitherm-k-factor', data.equitherm.k_factor);
setInputValue('#equitherm-t-factor', data.equitherm.t_factor);
setBusy('#equitherm-settings-busy', '#equitherm-settings', false);
// PID
setCheckboxValue('#pid-enable', data.pid.enable);
setInputValue('#pid-p-factor', data.pid.p_factor);
setInputValue('#pid-i-factor', data.pid.i_factor);
setInputValue('#pid-d-factor', data.pid.d_factor);
setInputValue('#pid-dt', data.pid.dt);
setInputValue('#pid-min-temp', data.pid.minTemp, {
"min": 0,
"max": data.system.unitSystem == 0 ? 99 : 211
});
setInputValue('#pid-max-temp', data.pid.maxTemp, {
"min": 1,
"max": data.system.unitSystem == 0 ? 100 : 212
});
setBusy('#pid-settings-busy', '#pid-settings', false);
};
try {
const response = await fetch('/api/settings', { cache: 'no-cache' });
if (!response.ok) {
throw new Error('Response not valid');
}
const result = await response.json();
fillData(result);
setupForm('#portal-settings', fillData, ['portal.login', 'portal.password']);
setupForm('#system-settings', fillData);
setupForm('#heating-settings', fillData);
setupForm('#dhw-settings', fillData);
setupForm('#emergency-settings', fillData);
setupForm('#equitherm-settings', fillData);
setupForm('#pid-settings', fillData);
setupForm('#opentherm-settings', fillData);
setupForm('#mqtt-settings', fillData, ['mqtt.user', 'mqtt.password', 'mqtt.prefix']);
setupForm('#outdoor-sensor-settings', fillData);
setupForm('#indoor-sensor-settings', fillData, ['sensors.indoor.bleAddresss']);
setupForm('#extpump-settings', fillData);
} catch (error) {
console.log(error);
}
};
</script>
</body>
</html>

View File

@@ -1,22 +1,47 @@
@media (min-width: 576px) {
article {
--pico-block-spacing-vertical: calc(var(--pico-spacing) * 0.75);
--pico-block-spacing-horizontal: calc(var(--pico-spacing) * 0.75);
}
}
@media (min-width: 768px) {
article {
--pico-block-spacing-vertical: var(--pico-spacing);
--pico-block-spacing-horizontal: var(--pico-spacing);
}
}
@media (min-width: 1024px) {
article {
--pico-block-spacing-vertical: calc(var(--pico-spacing) * 1.25);
--pico-block-spacing-horizontal: calc(var(--pico-spacing) * 1.25);
}
}
@media (min-width: 1280px) { @media (min-width: 1280px) {
article {
--pico-block-spacing-vertical: calc(var(--pico-spacing) * 1.5);
--pico-block-spacing-horizontal: calc(var(--pico-spacing) * 1.5);
}
.container { .container {
max-width: 1000px; max-width: 1000px;
} }
} }
@media (min-width: 1536px) { @media (min-width: 1536px) {
article {
--pico-block-spacing-vertical: calc(var(--pico-spacing) * 1.75);
--pico-block-spacing-horizontal: calc(var(--pico-spacing) * 1.75);
}
.container { .container {
max-width: 1000px; max-width: 1000px;
} }
} }
.hidden { header, main, footer {
display: none !important;
}
header,
main,
footer {
padding-top: 1rem !important; padding-top: 1rem !important;
padding-bottom: 1rem !important; padding-bottom: 1rem !important;
} }
@@ -29,6 +54,23 @@ footer {
text-align: center; text-align: center;
} }
nav li a:has(> div.logo) {
margin-bottom: 0;
}
details > div {
padding: 0 var(--pico-form-element-spacing-horizontal);
}
pre {
padding: 0.5rem;
}
.hidden {
display: none !important;
}
button.success { button.success {
background-color: var(--pico-form-element-valid-border-color); background-color: var(--pico-form-element-valid-border-color);
border-color: var(--pico-form-element-valid-border-color); border-color: var(--pico-form-element-valid-border-color);
@@ -44,18 +86,6 @@ tr.network:hover {
cursor: pointer; cursor: pointer;
} }
.greatSignal {
background-color: var(--pico-form-element-valid-border-color);
}
.normalSignal {
background-color: #e48500;
}
.badSignal {
background-color: var(--pico-form-element-invalid-border-color);
}
.primary { .primary {
border: 0.25rem solid var(--pico-form-element-invalid-border-color); border: 0.25rem solid var(--pico-form-element-invalid-border-color);
padding: 1rem; padding: 1rem;
@@ -75,6 +105,90 @@ tr.network:hover {
font-family: var(--pico-font-family-monospace); font-family: var(--pico-font-family-monospace);
} }
nav li a:has(> div.logo) { .thermostat {
margin-bottom: 0; display: grid;
grid-template-columns: 0.5fr 2fr 0.5fr;
grid-template-rows: 0.25fr 1fr 0.25fr;
gap: 0px 0px;
grid-auto-flow: row;
justify-content: center;
justify-items: center;
grid-template-areas:
". thermostat-header ."
"thermostat-minus thermostat-temp thermostat-plus"
"thermostat-control thermostat-control thermostat-control";
border: .25rem solid var(--pico-blockquote-border-color);
padding: 0.5rem;
} }
.thermostat-header {
justify-self: center;
align-self: end;
grid-area: thermostat-header;
font-size: 1rem;
font-weight: bold;
border-bottom: .25rem solid var(--pico-primary-hover-border);
margin: 0 0 1rem 0;
}
.thermostat-temp {
display: grid;
grid-template-columns: 1fr;
grid-template-rows: 1fr 0.5fr;
gap: 0px 0px;
grid-auto-flow: row;
grid-template-areas:
"thermostat-temp-target"
"thermostat-temp-current";
grid-area: thermostat-temp;
}
.thermostat-temp-target {
justify-self: center;
align-self: center;
grid-area: thermostat-temp-target;
font-weight: bold;
font-size: 1.75rem;
}
.thermostat-temp-current {
justify-self: center;
align-self: start;
grid-area: thermostat-temp-current;
color: var(--pico-secondary);
font-size: 0.85rem;
}
.thermostat-minus {
justify-self: end;
align-self: center;
grid-area: thermostat-minus;
}
.thermostat-plus {
justify-self: start;
align-self: center;
grid-area: thermostat-plus;
}
.thermostat-control {
justify-self: center;
align-self: start;
grid-area: thermostat-control;
margin: 1.25rem 0;
}
[class*=" icons-"],[class=icons],[class^=icons-] {font-size: 1.35rem; }
*:has(> [class*=" icons-"], > [class=icons], > [class^=icons-]):has(+ * > [class*=" icons-"], + * > [class=icons], + * > [class^=icons-]) { margin: 0 0.5rem 0 0; }
[data-tooltip]:has(> [class*=" icons-"], > [class=icons], > [class^=icons-]) { border: 0!important; }
/*!
* Icons icon font. Generated by Iconly: https://iconly.io/
*/
@font-face{font-display:auto;font-family:"Icons";font-style:normal;font-weight:400;src:url(./fonts/iconly.eot?1717885802370);src:url(./fonts/iconly.eot?#iefix) format("embedded-opentype"),url(./fonts/iconly.woff2?1717885802370) format("woff2"),url(./fonts/iconly.woff?1717885802370) format("woff"),url(./fonts/iconly.ttf?1717885802370) format("truetype")}[class*=" icons-"],[class=icons],[class^=icons-]{display:inline-block;font-family:"Icons"!important;font-weight:400;font-style:normal;font-variant:normal;text-rendering:auto;line-height:1;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased}.icons-plus:before{content:"\e000"}.icons-minus:before{content:"\e001"}.icons-unlocked:before{content:"\e002"}.icons-locked:before{content:"\e003"}.icons-wifi-strength-1:before{content:"\e004"}.icons-wifi-strength-0:before{content:"\e005"}.icons-wifi-strength-2:before{content:"\e006"}.icons-wifi-strength-3:before{content:"\e008"}.icons-down:before{content:"\e009"}.icons-wifi-strength-4:before{content:"\e00a"}.icons-up:before{content:"\e00c"}

View File

@@ -1,4 +1,4 @@
function setupForm(formSelector) { function setupForm(formSelector, onResultCallback = null, noCastItems = []) {
const form = document.querySelector(formSelector); const form = document.querySelector(formSelector);
if (!form) { if (!form) {
return; return;
@@ -27,7 +27,7 @@ function setupForm(formSelector) {
button.setAttribute('aria-busy', true); button.setAttribute('aria-busy', true);
} }
const onSuccess = (response) => { const onSuccess = (result) => {
if (button) { if (button) {
button.textContent = 'Saved'; button.textContent = 'Saved';
button.classList.add('success'); button.classList.add('success');
@@ -41,7 +41,7 @@ function setupForm(formSelector) {
} }
}; };
const onFailed = (response) => { const onFailed = () => {
if (button) { if (button) {
button.textContent = 'Error'; button.textContent = 'Error';
button.classList.add('failed'); button.classList.add('failed');
@@ -68,18 +68,23 @@ function setupForm(formSelector) {
headers: { headers: {
'Content-Type': 'application/json' 'Content-Type': 'application/json'
}, },
body: form2json(fd) body: form2json(fd, noCastItems)
}); });
if (response.ok) { if (!response.ok) {
onSuccess(response); throw new Error('Response not valid');
}
} else { const result = response.status != 204 ? (await response.json()) : null;
onFailed(response); onSuccess(result);
if (onResultCallback instanceof Function) {
onResultCallback(result);
} }
} catch (err) { } catch (err) {
onFailed(false); console.log(err);
onFailed();
} }
}); });
} }
@@ -134,7 +139,7 @@ function setupNetworkScanForm(formSelector, tableSelector) {
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 = function () {
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) {
return; return;
@@ -145,19 +150,56 @@ function setupNetworkScanForm(formSelector, tableSelector) {
}; };
row.insertCell().textContent = "#" + (i + 1); row.insertCell().textContent = "#" + (i + 1);
row.insertCell().innerHTML = result[i].hidden ? '<i>Hidden</i>' : result[i].ssid; row.insertCell().innerHTML = result[i].hidden ? ("<i>" + result[i].bssid + "</i>") : result[i].ssid;
const signalCell = row.insertCell(); // info cell
const signalElement = document.createElement("kbd"); let infoCell = row.insertCell();
signalElement.textContent = result[i].signalQuality + "%";
if (result[i].signalQuality > 60) { // signal quality
signalElement.classList.add('greatSignal'); let signalQualityIcon = document.createElement("i");
if (result[i].signalQuality > 80) {
signalQualityIcon.classList.add('icons-wifi-strength-4');
} else if (result[i].signalQuality > 60) {
signalQualityIcon.classList.add('icons-wifi-strength-3');
} else if (result[i].signalQuality > 40) { } else if (result[i].signalQuality > 40) {
signalElement.classList.add('normalSignal'); signalQualityIcon.classList.add('icons-wifi-strength-2');
} else if (result[i].signalQuality > 20) {
signalQualityIcon.classList.add('icons-wifi-strength-1');
} else { } else {
signalElement.classList.add('badSignal'); signalQualityIcon.classList.add('icons-wifi-strength-0');
} }
signalCell.appendChild(signalElement);
let signalQualityContainer = document.createElement("span");
signalQualityContainer.setAttribute('data-tooltip', result[i].signalQuality + "%");
signalQualityContainer.appendChild(signalQualityIcon);
infoCell.appendChild(signalQualityContainer);
// auth
const authList = {
0: "Open",
1: "WEP",
2: "WPA",
3: "WPA2",
4: "WPA/WPA2",
5: "WPA/WPA2 Enterprise",
6: "WPA3",
7: "WPA2/WPA3",
8: "WAPI",
9: "OWE",
10: "WPA3 Enterprise"
};
let authIcon = document.createElement("i");
if (result[i].auth == 0) {
authIcon.classList.add('icons-unlocked');
} else {
authIcon.classList.add('icons-locked');
}
let authContainer = document.createElement("span");
authContainer.setAttribute('data-tooltip', (result[i].auth in authList) ? authList[result[i].auth] : "unknown");
authContainer.appendChild(authIcon);
infoCell.appendChild(authContainer);
} }
if (button) { if (button) {
@@ -410,6 +452,9 @@ function setupUpgradeForm(formSelector) {
form.addEventListener('submit', async (event) => { form.addEventListener('submit', async (event) => {
event.preventDefault(); event.preventDefault();
hide('.upgrade-firmware-result');
hide('.upgrade-filesystem-result');
if (button) { if (button) {
button.textContent = 'Uploading...'; button.textContent = 'Uploading...';
button.setAttribute('disabled', true); button.setAttribute('disabled', true);
@@ -437,157 +482,15 @@ function setupUpgradeForm(formSelector) {
}); });
} }
async function loadNetworkStatus() {
let response = await fetch('/api/network/status', { cache: 'no-cache' });
let result = await response.json();
setValue('.network-hostname', result.hostname);
setValue('.network-mac', result.mac);
setState('.network-connected', result.isConnected);
setValue('.network-ssid', result.ssid);
setValue('.network-signal', result.signalQuality);
setValue('.network-ip', result.ip);
setValue('.network-subnet', result.subnet);
setValue('.network-gateway', result.gateway);
setValue('.network-dns', result.dns);
setBusy('.main-busy', '.main-table', false);
}
async function loadNetworkSettings() {
let response = await fetch('/api/network/settings', { cache: 'no-cache' });
let result = await response.json();
setInputValue('.network-hostname', result.hostname);
setCheckboxValue('.network-use-dhcp', result.useDhcp);
setInputValue('.network-static-ip', result.staticConfig.ip);
setInputValue('.network-static-gateway', result.staticConfig.gateway);
setInputValue('.network-static-subnet', result.staticConfig.subnet);
setInputValue('.network-static-dns', result.staticConfig.dns);
setBusy('#network-settings-busy', '#network-settings', false);
setInputValue('.sta-ssid', result.sta.ssid);
setInputValue('.sta-password', result.sta.password);
setInputValue('.sta-channel', result.sta.channel);
setBusy('#sta-settings-busy', '#sta-settings', false);
setInputValue('.ap-ssid', result.ap.ssid);
setInputValue('.ap-password', result.ap.password);
setInputValue('.ap-channel', result.ap.channel);
setBusy('#ap-settings-busy', '#ap-settings', false);
}
async function loadSettings() {
let response = await fetch('/api/settings', { cache: 'no-cache' });
let result = await response.json();
setCheckboxValue('.system-debug', result.system.debug);
setCheckboxValue('.system-use-serial', result.system.useSerial);
setCheckboxValue('.system-use-telnet', result.system.useTelnet);
setBusy('#system-settings-busy', '#system-settings', false);
setCheckboxValue('.portal-use-auth', result.portal.useAuth);
setInputValue('.portal-login', result.portal.login);
setInputValue('.portal-password', result.portal.password);
setBusy('#portal-settings-busy', '#portal-settings', false);
setInputValue('.opentherm-in-pin', result.opentherm.inPin);
setInputValue('.opentherm-out-pin', result.opentherm.outPin);
setInputValue('.opentherm-member-id-code', result.opentherm.memberIdCode);
setCheckboxValue('.opentherm-dhw-present', result.opentherm.dhwPresent);
setCheckboxValue('.opentherm-sw-mode', result.opentherm.summerWinterMode);
setCheckboxValue('.opentherm-heating-ch2-enabled', result.opentherm.heatingCh2Enabled);
setCheckboxValue('.opentherm-heating-ch1-to-ch2', result.opentherm.heatingCh1ToCh2);
setCheckboxValue('.opentherm-dhw-to-ch2', result.opentherm.dhwToCh2);
setCheckboxValue('.opentherm-dhw-blocking', result.opentherm.dhwBlocking);
setCheckboxValue('.opentherm-sync-modulation-with-heating', result.opentherm.modulationSyncWithHeating);
setBusy('#opentherm-settings-busy', '#opentherm-settings', false);
setInputValue('.mqtt-server', result.mqtt.server);
setInputValue('.mqtt-port', result.mqtt.port);
setInputValue('.mqtt-user', result.mqtt.user);
setInputValue('.mqtt-password', result.mqtt.password);
setInputValue('.mqtt-prefix', result.mqtt.prefix);
setInputValue('.mqtt-interval', result.mqtt.interval);
setBusy('#mqtt-settings-busy', '#mqtt-settings', false);
setRadioValue('.outdoor-sensor-type', result.sensors.outdoor.type);
setInputValue('.outdoor-sensor-pin', result.sensors.outdoor.pin);
setInputValue('.outdoor-sensor-offset', result.sensors.outdoor.offset);
setBusy('#outdoor-sensor-settings-busy', '#outdoor-sensor-settings', false);
setRadioValue('.indoor-sensor-type', result.sensors.indoor.type);
setInputValue('.indoor-sensor-pin', result.sensors.indoor.pin);
setInputValue('.indoor-sensor-offset', result.sensors.indoor.offset);
setInputValue('.indoor-sensor-ble-addresss', result.sensors.indoor.bleAddresss);
setBusy('#indoor-sensor-settings-busy', '#indoor-sensor-settings', false);
setCheckboxValue('.extpump-use', result.externalPump.use);
setInputValue('.extpump-pin', result.externalPump.pin);
setInputValue('.extpump-pc-time', result.externalPump.postCirculationTime);
setInputValue('.extpump-as-interval', result.externalPump.antiStuckInterval);
setInputValue('.extpump-as-time', result.externalPump.antiStuckTime);
setBusy('#extpump-settings-busy', '#extpump-settings', false);
}
async function loadVars() {
let response = await fetch('/api/vars');
let result = await response.json();
setState('.ot-connected', result.states.otStatus);
setState('.ot-emergency', result.states.emergency);
setState('.ot-heating', result.states.heating);
setState('.ot-dhw', result.states.dhw);
setState('.ot-flame', result.states.flame);
setState('.ot-fault', result.states.fault);
setState('.ot-diagnostic', result.states.diagnostic);
setState('.ot-external-pump', result.states.externalPump);
setValue('.ot-modulation', result.sensors.modulation);
setValue('.ot-pressure', result.sensors.pressure);
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('.outdoor-temp', result.temperatures.outdoor);
setValue('.heating-temp', result.temperatures.heating);
setValue('.heating-setpoint-temp', result.parameters.heatingSetpoint);
setValue('.dhw-temp', result.temperatures.dhw);
setBusy('.ot-busy', '.ot-table', false);
setValue('.version', result.system.version);
setValue('.build-date', result.system.buildDate);
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('.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);
setState('.mqtt-connected', result.system.mqttConnected);
setBusy('.system-busy', '.system-table', false);
}
function setBusy(busySelector, contentSelector, value) { function setBusy(busySelector, contentSelector, value) {
let busy = document.querySelector(busySelector);
let content = document.querySelector(contentSelector);
if (!busy || !content) {
return;
}
if (!value) { if (!value) {
busy.classList.add('hidden'); hide(busySelector);
content.classList.remove('hidden'); show(contentSelector);
} else { } else {
busy.classList.remove('hidden'); show(busySelector);
content.classList.add('hidden'); hide(contentSelector);
} }
} }
@@ -601,12 +504,14 @@ function setState(selector, value) {
} }
function setValue(selector, value) { function setValue(selector, value) {
let item = document.querySelector(selector); let items = document.querySelectorAll(selector);
if (!item) { if (!items.length) {
return; return;
} }
item.innerHTML = value; for (let item of items) {
item.innerHTML = value;
}
} }
function setCheckboxValue(selector, value) { function setCheckboxValue(selector, value) {
@@ -629,25 +534,120 @@ function setRadioValue(selector, value) {
} }
} }
function setInputValue(selector, value) { function setInputValue(selector, value, attrs = {}) {
let item = document.querySelector(selector); let items = document.querySelectorAll(selector);
if (!item) { if (!items.length) {
return; return;
} }
item.value = value; for (let item of items) {
item.value = value;
if (attrs instanceof Object) {
for (let attrKey of Object.keys(attrs)) {
item.setAttribute(attrKey, attrs[attrKey]);
}
}
}
} }
function show(selector) {
let items = document.querySelectorAll(selector);
if (!items.length) {
return;
}
function form2json(data) { for (let item of items) {
if (item.classList.contains('hidden')) {
item.classList.remove('hidden');
}
}
}
function hide(selector) {
let items = document.querySelectorAll(selector);
if (!items.length) {
return;
}
for (let item of items) {
if (!item.classList.contains('hidden')) {
item.classList.add('hidden');
}
}
}
function unit2str(unitSystem, units = {}, defaultValue = '?') {
return (unitSystem in units)
? units[unitSystem]
: defaultValue;
}
function temperatureUnit(unitSystem) {
return unit2str(unitSystem, {
0: "°C",
1: "°F"
});
}
function pressureUnit(unitSystem) {
return unit2str(unitSystem, {
0: "bar",
1: "psi"
});
}
function volumeUnit(unitSystem) {
return unit2str(unitSystem, {
0: "L",
1: "gal"
});
}
function memberIdToVendor(memberId) {
// https://github.com/Jeroen88/EasyOpenTherm/blob/main/src/EasyOpenTherm.h
// https://github.com/Evgen2/SmartTherm/blob/v0.7/src/Web.cpp
const vendorList = {
1: "Baxi Fourtech/Luna 3",
2: "AWB/Brink",
4: "ATAG/Brötje/ELCO/GEMINOX",
5: "Itho Daalderop",
6: "IDEAL",
8: "Buderus/Bosch/Hoval",
9: "Ferroli",
11: "Remeha/De Dietrich",
16: "Unical",
24: "Vaillant/Bulex",
27: "Baxi",
29: "Itho Daalderop",
33: "Viessmann",
41: "Italtherm",
56: "Baxi Luna Duo-Tec",
131: "Nefit",
148: "Navien",
173: "Intergas",
247: "Baxi Ampera",
248: "Zota Lux-X"
};
return (memberId in vendorList)
? vendorList[memberId]
: "unknown vendor";
}
function form2json(data, noCastItems = []) {
let method = function (object, pair) { let method = function (object, pair) {
let keys = pair[0].replace(/\]/g, '').split('['); let keys = pair[0].replace(/\]/g, '').split('[');
let key = keys[0]; let key = keys[0];
let value = pair[1]; let value = pair[1];
if (value === 'true' || value === 'false') {
value = value === 'true'; if (!noCastItems.includes(keys.join('.'))) {
} else if (typeof (value) === 'string' && value.trim() !== '' && !isNaN(value)) { if (value === 'true' || value === 'false') {
value = parseFloat(value); value = value === 'true';
} else if (typeof (value) === 'string' && value.trim() !== '' && !isNaN(value)) {
value = parseFloat(value);
}
} }
if (keys.length > 1) { if (keys.length > 1) {

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

View File

@@ -50,7 +50,7 @@
<hgroup> <hgroup>
<h2>Upgrade</h2> <h2>Upgrade</h2>
<p> <p>
In this section you can upgrade the firmware and filesystem of your device.<br> In this section you can upgrade the firmware and filesystem of your device.<br />
Latest releases can be downloaded from the <a href="https://github.com/Laxilef/OTGateway/releases" target="_blank">Releases page</a> of the project repository. Latest releases can be downloaded from the <a href="https://github.com/Laxilef/OTGateway/releases" target="_blank">Releases page</a> of the project repository.
</p> </p>
</hgroup> </hgroup>
@@ -91,7 +91,7 @@
<a href="https://github.com/Laxilef/OTGateway/blob/master/LICENSE" target="_blank" class="secondary">License</a> <a href="https://github.com/Laxilef/OTGateway/blob/master/LICENSE" target="_blank" class="secondary">License</a>
<a href="https://github.com/Laxilef/OTGateway/blob/master/" target="_blank" class="secondary">Source code</a> <a href="https://github.com/Laxilef/OTGateway/blob/master/" target="_blank" class="secondary">Source code</a>
<a href="https://github.com/Laxilef/OTGateway/wiki" target="_blank" class="secondary">Help</a> <a href="https://github.com/Laxilef/OTGateway/wiki" target="_blank" class="secondary">Help</a>
<a href="https://github.com/Laxilef/OTGateway/issue" target="_blank" class="secondary">Issue & questions</a> <a href="https://github.com/Laxilef/OTGateway/issues" target="_blank" class="secondary">Issue & questions</a>
<a href="https://github.com/Laxilef/OTGateway/releases" target="_blank" class="secondary">Releases</a> <a href="https://github.com/Laxilef/OTGateway/releases" target="_blank" class="secondary">Releases</a>
</small> </small>
</footer> </footer>

View File

@@ -1,4 +1,5 @@
import shutil import shutil
import gzip
import os import os
Import("env") Import("env")
@@ -12,6 +13,26 @@ def post_build(source, target, env):
env.Execute("pio run --target buildfs --environment %s" % env["PIOENV"]); env.Execute("pio run --target buildfs --environment %s" % env["PIOENV"]);
def before_buildfs(source, target, env):
src = os.path.join(env["PROJECT_DIR"], "src_data")
dst = os.path.join(env["PROJECT_DIR"], "data")
for root, dirs, files in os.walk(src, topdown=False):
for name in files:
src_path = os.path.join(root, name)
with open(src_path, 'rb') as f_in:
dst_name = name + ".gz"
dst_path = os.path.join(dst, os.path.relpath(root, src), dst_name)
if os.path.exists(os.path.join(dst, os.path.relpath(root, src))) == False:
os.mkdir(os.path.join(dst, os.path.relpath(root, src)))
with gzip.open(dst_path, 'wb', 9) as f_out:
shutil.copyfileobj(f_in, f_out)
print("Compressed '%s' to '%s'" % (src_path, dst_path))
def after_buildfs(source, target, env): def after_buildfs(source, target, env):
copy_to_build_dir({ copy_to_build_dir({
source[0].get_abspath(): "filesystem_%s_%s.bin" % (env["PIOENV"], env.GetProjectOption("version")), source[0].get_abspath(): "filesystem_%s_%s.bin" % (env["PIOENV"], env.GetProjectOption("version")),
@@ -31,4 +52,6 @@ def copy_to_build_dir(files, build_dir):
env.AddPostAction("buildprog", post_build) env.AddPostAction("buildprog", post_build)
env.AddPreAction("$BUILD_DIR/spiffs.bin", before_buildfs)
env.AddPreAction("$BUILD_DIR/littlefs.bin", before_buildfs)
env.AddPostAction("buildfs", after_buildfs) env.AddPostAction("buildfs", after_buildfs)