41 Commits

Author SHA1 Message Date
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
28 changed files with 2962 additions and 1324 deletions

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:
@@ -72,7 +72,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)

387
data/dashboard.html Normal file
View File

@@ -0,0 +1,387 @@
<!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"><b>-</b></button></div>
<div class="thermostat-plus"><button id="thermostat-heating-plus" class="outline"><b>+</b></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"><b>-</b></button></div>
<div class="thermostat-plus"><button class="outline" id="thermostat-dhw-plus"><b>+</b></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) => {
newSettings.heating.target -= 0.5;
modifiedTime = Date.now();
const minTemp = noRegulators ? prevSettings.heating.minTemp : 5;
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) => {
newSettings.heating.target += 0.5;
modifiedTime = Date.now();
const maxTemp = noRegulators ? prevSettings.heating.maxTemp : 30;
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) => {
newSettings.dhw.target -= 1;
modifiedTime = Date.now();
if (prevSettings && 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) => {
newSettings.dhw.target += 1;
modifiedTime = Date.now();
if (prevSettings && 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.heating.dhw = 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.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>

View File

@@ -28,44 +28,44 @@
<p></p> <p></p>
</hgroup> </hgroup>
<div class="main-busy" aria-busy="true"></div> <div id="main-busy" aria-busy="true"></div>
<table class="main-table hidden"> <table id="main-table" class="hidden">
<tbody> <tbody>
<tr> <tr>
<th scope="row">Hostname:</th> <th scope="row">Hostname:</th>
<td><b class="network-hostname"></b></td> <td><b id="network-hostname"></b></td>
</tr> </tr>
<tr> <tr>
<th scope="row">MAC:</th> <th scope="row">MAC:</th>
<td><b class="network-mac"></b></td> <td><b id="network-mac"></b></td>
</tr> </tr>
<tr> <tr>
<th scope="row">Connected:</th> <th scope="row">Connected:</th>
<td><input type="radio" class="network-connected" aria-invalid="false" checked disabled /></td> <td><input type="radio" id="network-connected" aria-invalid="false" checked disabled /></td>
</tr> </tr>
<tr> <tr>
<th scope="row">SSID:</th> <th scope="row">SSID:</th>
<td><b class="network-ssid"></b></td> <td><b id="network-ssid"></b></td>
</tr> </tr>
<tr> <tr>
<th scope="row">Signal:</th> <th scope="row">Signal:</th>
<td><b class="network-signal"></b> %</td> <td><b id="network-signal"></b> %</td>
</tr> </tr>
<tr> <tr>
<th scope="row">IP:</th> <th scope="row">IP:</th>
<td><b class="network-ip"></b></td> <td><b id="network-ip"></b></td>
</tr> </tr>
<tr> <tr>
<th scope="row">Subnet:</th> <th scope="row">Subnet:</th>
<td><b class="network-subnet"></b></td> <td><b id="network-subnet"></b></td>
</tr> </tr>
<tr> <tr>
<th scope="row">Gateway:</th> <th scope="row">Gateway:</th>
<td><b class="network-gateway"></b></td> <td><b id="network-gateway"></b></td>
</tr> </tr>
<tr> <tr>
<th scope="row">DNS:</th> <th scope="row">DNS:</th>
<td><b class="network-dns"></b></td> <td><b id="network-dns"></b></td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
@@ -82,126 +82,40 @@
<p></p> <p></p>
</hgroup> </hgroup>
<div class="system-busy" aria-busy="true"></div> <div id="system-busy" aria-busy="true"></div>
<table class="system-table hidden"> <table id="system-table" class="hidden">
<tbody> <tbody>
<tr> <tr>
<th scope="row">Version:</th> <th scope="row">Version:</th>
<td><b class="version"></b></td> <td><b id="version"></b></td>
</tr> </tr>
<tr> <tr>
<th scope="row">Build date:</th> <th scope="row">Build date:</th>
<td><b class="build-date"></b></td> <td><b id="build-date"></b></td>
</tr> </tr>
<tr> <tr>
<th scope="row">Uptime:</th> <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> <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>
<tr> <tr>
<th scope="row">Free memory:</th> <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> <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>
<tr> <tr>
<th scope="row">Last reset reason:</th> <th scope="row">Last reset reason:</th>
<td><b class="reset-reason"></b></td> <td><b id="reset-reason"></b></td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
<div class="grid"> <div class="grid">
<a href="/dashboard.html" role="button">Dashboard</a>
<a href="/settings.html" role="button">Settings</a> <a href="/settings.html" role="button">Settings</a>
<a href="/upgrade.html" role="button">Upgrade</a> <a href="/upgrade.html" role="button">Upgrade</a>
<a href="/restart.html" role="button" class="secondary restart">Restart</a> <a href="/restart.html" role="button" class="secondary restart">Restart</a>
</div> </div>
</div> </div>
</article> </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> </main>
<footer class="container"> <footer class="container">
@@ -210,7 +124,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>
@@ -219,8 +133,42 @@
<script> <script>
window.onload = async function () { window.onload = async function () {
setTimeout(async function onLoadPage() { setTimeout(async function onLoadPage() {
await loadNetworkStatus(); try {
await loadVars(); 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);
setBusy('#system-busy', '#system-table', false);
} catch (error) {
console.log(error);
}
setTimeout(onLoadPage, 10000); setTimeout(onLoadPage, 10000);
}, 1000); }, 1000);

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>
@@ -82,28 +88,32 @@
<tbody></tbody> <tbody></tbody>
</table> </table>
</figure> </figure>
<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,7 +167,34 @@
<script src="/static/app.js"></script> <script src="/static/app.js"></script>
<script> <script>
window.onload = async function () { window.onload = async function () {
await loadNetworkSettings(); 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();
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);
} catch (error) {
console.log(error);
}
setupForm('#network-settings'); setupForm('#network-settings');
setupNetworkScanForm('#network-scan', '#networks'); setupNetworkScanForm('#network-scan', '#networks');

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

Binary file not shown.

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

@@ -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,30 @@ 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); return isValidResponse(response);
@@ -128,9 +129,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 +141,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 +153,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 +163,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

@@ -67,7 +67,7 @@ namespace Network {
return this; return this;
} }
Manager* setStaticConfig(IPAddress &ip, IPAddress &gateway, IPAddress &subnet, IPAddress &dns) { Manager* 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;
@@ -181,7 +181,12 @@ namespace Network {
wifi_station_dhcpc_set_maxtry(5); wifi_station_dhcpc_set_maxtry(5);
#endif #endif
#ifdef ARDUINO_ARCH_ESP32
// Nothing. Because memory leaks when turn off WiFi on ESP32, bug?
return true;
#else
return WiFi.mode(WIFI_OFF); return WiFi.mode(WIFI_OFF);
#endif
} }
void reconnect() { void reconnect() {

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,9 +15,9 @@ 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/Laxilef/opentherm_library/archive/refs/heads/fix_lambda.zip
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
@@ -30,44 +30,43 @@ build_flags =
-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.0-rc.21
; Defaults ; Defaults
[esp8266_defaults] [esp8266_defaults]
platform = espressif8266 platform = espressif8266
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.6
board_build.partitions = esp32_partitions.csv board_build.partitions = esp32_partitions.csv
lib_deps = lib_deps =
${env.lib_deps} ${env.lib_deps}
@@ -91,12 +90,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 LED_STATUS_GPIO=13
-D LED_OT_RX_PIN=15 -D LED_OT_RX_GPIO=15
[env:d1_mini_lite] [env:d1_mini_lite]
platform = ${esp8266_defaults.platform} platform = ${esp8266_defaults.platform}
@@ -107,12 +106,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 LED_STATUS_GPIO=13
-D LED_OT_RX_PIN=15 -D LED_OT_RX_GPIO=15
[env:d1_mini_pro] [env:d1_mini_pro]
platform = ${esp8266_defaults.platform} platform = ${esp8266_defaults.platform}
@@ -123,12 +122,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 LED_STATUS_GPIO=13
-D LED_OT_RX_PIN=15 -D LED_OT_RX_GPIO=15
[env:s2_mini] [env:s2_mini]
platform = ${esp32_defaults.platform} platform = ${esp32_defaults.platform}
@@ -139,12 +138,12 @@ lib_ignore = ${esp32_defaults.lib_ignore}
extra_scripts = ${esp32_defaults.extra_scripts} extra_scripts = ${esp32_defaults.extra_scripts}
build_flags = build_flags =
${esp32_defaults.build_flags} ${esp32_defaults.build_flags}
-D OT_IN_PIN_DEFAULT=33 -D DEFAULT_OT_IN_GPIO=33
-D OT_OUT_PIN_DEFAULT=35 -D DEFAULT_OT_OUT_GPIO=35
-D SENSOR_OUTDOOR_PIN_DEFAULT=9 -D DEFAULT_SENSOR_OUTDOOR_GPIO=9
-D SENSOR_INDOOR_PIN_DEFAULT=7 -D DEFAULT_SENSOR_INDOOR_GPIO=7
-D LED_STATUS_PIN=11 -D LED_STATUS_GPIO=11
-D LED_OT_RX_PIN=12 -D LED_OT_RX_GPIO=12
[env:s3_mini] [env:s3_mini]
platform = ${esp32_defaults.platform} platform = ${esp32_defaults.platform}
@@ -158,12 +157,12 @@ 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=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 LED_STATUS_GPIO=11
-D LED_OT_RX_PIN=10 -D LED_OT_RX_GPIO=10
[env:c3_mini] [env:c3_mini]
platform = ${esp32_defaults.platform} platform = ${esp32_defaults.platform}
@@ -179,12 +178,12 @@ 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 LED_STATUS_GPIO=4
-D LED_OT_RX_PIN=5 -D LED_OT_RX_GPIO=5
[env:nodemcu_32s] [env:nodemcu_32s]
platform = ${esp32_defaults.platform} platform = ${esp32_defaults.platform}
@@ -198,12 +197,12 @@ 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 LED_STATUS_GPIO=2 ; 18
-D LED_OT_RX_PIN=19 -D LED_OT_RX_GPIO=19
;-D WOKWI=1 ;-D WOKWI=1
[env:d1_mini32] [env:d1_mini32]
@@ -218,9 +217,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 LED_STATUS_GPIO=2
-D LED_OT_RX_PIN=19 -D LED_OT_RX_GPIO=19

View File

@@ -27,22 +27,31 @@ public:
return this->publish(this->getTopic(FPSTR(HA_ENTITY_SWITCH), F("emergency")).c_str(), doc); return this->publish(this->getTopic(FPSTR(HA_ENTITY_SWITCH), F("emergency")).c_str(), doc);
} }
bool publishNumberEmergencyTarget(bool enabledByDefault = true) { bool publishNumberEmergencyTarget(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("emergency_target")); doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("emergency_target"));
doc[FPSTR(HA_OBJECT_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_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)] = 5;
doc[FPSTR(HA_MAX)] = 50;
} else if (unit == UnitSystem::IMPERIAL) {
doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR(HA_UNIT_OF_MEASUREMENT_F);
doc[FPSTR(HA_MIN)] = 41;
doc[FPSTR(HA_MAX)] = 122;
}
doc[FPSTR(HA_NAME)] = F("Emergency target temp"); doc[FPSTR(HA_NAME)] = F("Emergency target temp");
doc[FPSTR(HA_ICON)] = F("mdi:thermometer-alert"); doc[FPSTR(HA_ICON)] = F("mdi:thermometer-alert");
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.emergency.target|float(0)|round(1) }}"); 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_TOPIC)] = this->getDeviceTopic(F("settings/set"));
doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"emergency\": {\"target\" : {{ value }}}}"); 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_STEP)] = 0.5;
doc[FPSTR(HA_MODE)] = "box"; doc[FPSTR(HA_MODE)] = "box";
doc[FPSTR(HA_EXPIRE_AFTER)] = 120; doc[FPSTR(HA_EXPIRE_AFTER)] = 120;
@@ -142,7 +151,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 +159,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"));
@@ -167,14 +183,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"));
@@ -191,7 +214,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,7 +223,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 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"));
@@ -211,7 +241,7 @@ public:
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 +253,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 +271,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 +283,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 +301,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 +334,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 +414,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,7 +422,14 @@ 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"));
@@ -377,7 +446,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 +458,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 +476,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 +488,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 +506,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 +539,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;
@@ -579,22 +680,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 +713,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;
@@ -980,7 +1099,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 +1111,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 +1129,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 +1141,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,21 +1160,30 @@ 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_MAX)] = 99;
doc[FPSTR(HA_STEP)] = 0.01; 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;
@@ -1050,7 +1192,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 +1201,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,21 +1219,30 @@ 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_MAX)] = 99;
doc[FPSTR(HA_STEP)] = 0.01; 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;
@@ -1093,7 +1251,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 +1260,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 +1278,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 +1290,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 +1308,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 +1350,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 +1368,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 +1425,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 %}");
@@ -1213,7 +1459,7 @@ public:
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;
@@ -1231,6 +1477,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.dhw.target|int(0) }}"); doc[FPSTR(HA_TEMPERATURE_STATE_TEMPLATE)] = F("{{ value_json.dhw.target|int(0) }}");
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}}"
"{% elif value == 'off' %}{\"dhw\": {\"enable\" : false}}{% endif %}"); "{% elif value == 'off' %}{\"dhw\": {\"enable\" : false}}{% endif %}");

View File

@@ -52,14 +52,14 @@ protected:
} }
void setup() { void setup() {
#ifdef LED_STATUS_PIN #ifdef LED_STATUS_GPIO
pinMode(LED_STATUS_PIN, OUTPUT); pinMode(LED_STATUS_GPIO, OUTPUT);
digitalWrite(LED_STATUS_PIN, LOW); digitalWrite(LED_STATUS_GPIO, LOW);
#endif #endif
if (settings.externalPump.pin != 0) { if (GPIO_IS_VALID(settings.externalPump.gpio)) {
pinMode(settings.externalPump.pin, OUTPUT); pinMode(settings.externalPump.gpio, OUTPUT);
digitalWrite(settings.externalPump.pin, LOW); digitalWrite(settings.externalPump.gpio, LOW);
} }
} }
@@ -89,20 +89,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,7 +137,7 @@ 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();
} }
@@ -140,8 +151,8 @@ protected:
this->yield(); this->yield();
#ifdef LED_STATUS_PIN #ifdef LED_STATUS_GPIO
this->ledStatus(LED_STATUS_PIN); this->ledStatus(LED_STATUS_GPIO);
#endif #endif
this->externalPump(); this->externalPump();
this->yield(); this->yield();
@@ -211,7 +222,7 @@ protected:
} }
} }
void ledStatus(uint8_t ledPin) { void ledStatus(uint8_t gpio) {
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;
@@ -219,7 +230,7 @@ protected:
static bool ledOn = false; static bool ledOn = false;
if (!this->blinkerInitialized) { if (!this->blinkerInitialized) {
this->blinker->init(ledPin); this->blinker->init(gpio);
this->blinkerInitialized = true; this->blinkerInitialized = true;
} }
@@ -247,14 +258,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(gpio, HIGH);
ledOn = true; ledOn = true;
} }
return; return;
} else if (ledOn) { } else if (ledOn) {
digitalWrite(ledPin, LOW); digitalWrite(gpio, LOW);
ledOn = false; ledOn = false;
endBlinkTime = millis(); endBlinkTime = millis();
return; return;
@@ -283,10 +294,10 @@ protected:
this->heatingEnabled = true; this->heatingEnabled = true;
} }
if (!settings.externalPump.use || settings.externalPump.pin == 0) { if (!settings.externalPump.use || !GPIO_IS_VALID(settings.externalPump.gpio)) {
if (vars.states.externalPump) { if (vars.states.externalPump) {
if (settings.externalPump.pin != 0) { if (GPIO_IS_VALID(settings.externalPump.gpio)) {
digitalWrite(settings.externalPump.pin, LOW); digitalWrite(settings.externalPump.gpio, LOW);
} }
vars.states.externalPump = false; vars.states.externalPump = false;
@@ -300,7 +311,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(settings.externalPump.gpio, LOW);
vars.states.externalPump = false; vars.states.externalPump = false;
vars.parameters.extPumpLastEnableTime = millis(); vars.parameters.extPumpLastEnableTime = millis();
@@ -308,7 +319,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(settings.externalPump.gpio, LOW);
vars.states.externalPump = false; vars.states.externalPump = false;
vars.parameters.extPumpLastEnableTime = millis(); vars.parameters.extPumpLastEnableTime = millis();
@@ -324,7 +335,7 @@ protected:
this->externalPumpStartTime = millis(); this->externalPumpStartTime = millis();
this->extPumpStartReason = MainTask::PumpStartReason::HEATING; this->extPumpStartReason = MainTask::PumpStartReason::HEATING;
digitalWrite(settings.externalPump.pin, HIGH); digitalWrite(settings.externalPump.gpio, HIGH);
Log.sinfoln("EXTPUMP", F("Enabled: heating on")); Log.sinfoln("EXTPUMP", F("Enabled: heating on"));
@@ -333,7 +344,7 @@ protected:
this->externalPumpStartTime = millis(); this->externalPumpStartTime = millis();
this->extPumpStartReason = MainTask::PumpStartReason::ANTISTUCK; this->extPumpStartReason = MainTask::PumpStartReason::ANTISTUCK;
digitalWrite(settings.externalPump.pin, HIGH); digitalWrite(settings.externalPump.gpio, 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"));
@@ -51,6 +62,7 @@ protected:
MqttClient* client = nullptr; MqttClient* client = nullptr;
HaHelper* haHelper = nullptr; HaHelper* haHelper = nullptr;
MqttWriter* writer = nullptr; MqttWriter* writer = nullptr;
UnitSystem currentUnitSystem = UnitSystem::METRIC;
unsigned short readyForSendTime = 15000; unsigned short readyForSendTime = 15000;
unsigned long lastReconnectTime = 0; unsigned long lastReconnectTime = 0;
unsigned long connectedTime = 0; unsigned long connectedTime = 0;
@@ -69,7 +81,7 @@ protected:
}*/ }*/
int getTaskPriority() { int getTaskPriority() {
return 1; return 2;
} }
bool isReadyForSend() { bool isReadyForSend() {
@@ -175,13 +187,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 > EMERGENCY_TIME_TRESHOLD) {
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,10 +227,11 @@ protected:
} }
// publish ha entities if not published // publish ha entities if not published
if (this->newConnection) { if (this->newConnection || this->currentUnitSystem != settings.system.unitSystem) {
this->publishHaEntities(); this->publishHaEntities();
this->publishNonStaticHaEntities(true); this->publishNonStaticHaEntities(true);
this->newConnection = false; this->newConnection = false;
this->currentUnitSystem = settings.system.unitSystem;
} else { } else {
// publish non static ha entities // publish non static ha entities
@@ -318,19 +333,19 @@ protected:
void publishHaEntities() { void publishHaEntities() {
// emergency // emergency
this->haHelper->publishSwitchEmergency(); this->haHelper->publishSwitchEmergency();
this->haHelper->publishNumberEmergencyTarget(); this->haHelper->publishNumberEmergencyTarget(settings.system.unitSystem);
this->haHelper->publishSwitchEmergencyUseEquitherm(); this->haHelper->publishSwitchEmergencyUseEquitherm();
this->haHelper->publishSwitchEmergencyUsePid(); 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 +354,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();
@@ -362,14 +377,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);
@@ -383,23 +400,36 @@ protected:
bool published = false; bool published = false;
bool isStupidMode = !settings.pid.enable && !settings.equitherm.enable; bool isStupidMode = !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 (isStupidMode) {
heatingMinTemp = settings.heating.minTemp;
heatingMaxTemp = settings.heating.maxTemp;
} else if (settings.system.unitSystem == UnitSystem::METRIC) {
heatingMinTemp = 5;
heatingMaxTemp = 30;
} else if (settings.system.unitSystem == UnitSystem::IMPERIAL) {
heatingMinTemp = 41;
heatingMaxTemp = 86;
}
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();
@@ -426,8 +456,9 @@ protected:
_heatingMaxTemp = heatingMaxTemp; _heatingMaxTemp = heatingMaxTemp;
_isStupidMode = isStupidMode; _isStupidMode = isStupidMode;
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 isStupidMode ? HaHelper::TEMP_SOURCE_HEATING : HaHelper::TEMP_SOURCE_INDOOR
@@ -438,6 +469,7 @@ protected:
} else if (_isStupidMode != isStupidMode) { } else if (_isStupidMode != isStupidMode) {
_isStupidMode = isStupidMode; _isStupidMode = isStupidMode;
this->haHelper->publishClimateHeating( this->haHelper->publishClimateHeating(
settings.system.unitSystem,
heatingMinTemp, heatingMinTemp,
heatingMaxTemp, heatingMaxTemp,
isStupidMode ? HaHelper::TEMP_SOURCE_HEATING : HaHelper::TEMP_SOURCE_INDOOR isStupidMode ? HaHelper::TEMP_SOURCE_HEATING : HaHelper::TEMP_SOURCE_INDOOR
@@ -450,8 +482,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 +493,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 +507,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;

View File

@@ -1,31 +1,35 @@
#include <CustomOpenTherm.h> #include <CustomOpenTherm.h>
CustomOpenTherm* ot;
extern FileData fsSettings; extern FileData fsSettings;
class OpenThermTask : public Task { class OpenThermTask : public Task {
public: public:
OpenThermTask(bool _enabled = false, unsigned long _interval = 0) : Task(_enabled, _interval) { OpenThermTask(bool _enabled = false, unsigned long _interval = 0) : Task(_enabled, _interval) {}
ot = new CustomOpenTherm(settings.opentherm.inPin, settings.opentherm.outPin);
}
static void IRAM_ATTR handleInterrupt() { ~OpenThermTask() {
ot->handleInterrupt(); delete this->instance;
} }
protected: protected:
unsigned short readyTime = 60000; const unsigned short readyTime = 60000;
unsigned short dhwSetTempInterval = 60000; const unsigned short dhwSetTempInterval = 60000;
unsigned short heatingSetTempInterval = 60000; const unsigned short heatingSetTempInterval = 60000;
const unsigned int initializingInterval = 3600000;
CustomOpenTherm* instance = nullptr;
unsigned long instanceCreatedTime = 0;
byte instanceInGpio = 0;
byte instanceOutGpio = 0;
bool isInitialized = false;
unsigned long initializedTime = 0;
unsigned int initializedMemberIdCode = 0;
byte dhwFlowRateMultiplier = 1;
byte pressureMultiplier = 1;
bool pump = true; bool pump = true;
unsigned long lastSuccessResponse = 0; unsigned long lastSuccessResponse = 0;
unsigned long prevUpdateNonEssentialVars = 0; unsigned long prevUpdateNonEssentialVars = 0;
unsigned long startupTime = millis();
unsigned long dhwSetTempTime = 0; unsigned long dhwSetTempTime = 0;
unsigned long heatingSetTempTime = 0; unsigned long heatingSetTempTime = 0;
byte dhwFlowRateMultiplier = 1;
byte pressureMultiplier = 1;
const char* getTaskName() { const char* getTaskName() {
return "OpenTherm"; return "OpenTherm";
@@ -36,85 +40,88 @@ protected:
} }
int getTaskPriority() { int getTaskPriority() {
return 2; return 5;
} }
void setup() { void setup() {
Log.sinfoln(FPSTR(L_OT), F("Started. GPIO IN: %hhu, GPIO OUT: %hhu"), settings.opentherm.inPin, settings.opentherm.outPin); if (settings.system.unitSystem != UnitSystem::METRIC) {
vars.parameters.heatingMinTemp = convertTemp(vars.parameters.heatingMinTemp, UnitSystem::METRIC, settings.system.unitSystem);
vars.parameters.heatingMaxTemp = convertTemp(vars.parameters.heatingMaxTemp, UnitSystem::METRIC, settings.system.unitSystem);
vars.parameters.dhwMinTemp = convertTemp(vars.parameters.dhwMinTemp, UnitSystem::METRIC, settings.system.unitSystem);
vars.parameters.dhwMaxTemp = convertTemp(vars.parameters.dhwMaxTemp, UnitSystem::METRIC, settings.system.unitSystem);
}
ot->setAfterSendRequestCallback([this](unsigned long request, unsigned long response, OpenThermResponseStatus status, byte attempt) { #ifdef LED_OT_RX_GPIO
pinMode(LED_OT_RX_GPIO, OUTPUT);
digitalWrite(LED_OT_RX_GPIO, LOW);
#endif
// delete instance
if (this->instance != nullptr) {
delete this->instance;
this->instance = nullptr;
Log.sinfoln(FPSTR(L_OT), F("Stopped"));
}
if (!GPIO_IS_VALID(settings.opentherm.inGpio) || !GPIO_IS_VALID(settings.opentherm.outGpio)) {
Log.swarningln(FPSTR(L_OT), F("Not started. GPIO IN: %hhu or GPIO OUT: %hhu is not valid"), settings.opentherm.inGpio, settings.opentherm.outGpio);
return;
}
// create instance
this->instance = new CustomOpenTherm(settings.opentherm.inGpio, settings.opentherm.outGpio);
// flags
this->instanceCreatedTime = millis();
this->instanceInGpio = settings.opentherm.inGpio;
this->instanceOutGpio = settings.opentherm.outGpio;
this->isInitialized = false;
Log.sinfoln(FPSTR(L_OT), F("Started. GPIO IN: %hhu, GPIO OUT: %hhu"), settings.opentherm.inGpio, settings.opentherm.outGpio);
this->instance->setAfterSendRequestCallback([this](unsigned long request, unsigned long response, OpenThermResponseStatus status, byte attempt) {
Log.straceln( Log.straceln(
FPSTR(L_OT), FPSTR(L_OT),
F("ID: %4d Request: %8lx Response: %8lx Attempt: %2d Status: %s"), F("ID: %4d Request: %8lx Response: %8lx Attempt: %2d Status: %s"),
ot->getDataID(request), request, response, attempt, ot->statusToString(status) CustomOpenTherm::getDataID(request), request, response, attempt, CustomOpenTherm::statusToString(status)
); );
if (status == OpenThermResponseStatus::SUCCESS) { if (status == OpenThermResponseStatus::SUCCESS) {
this->lastSuccessResponse = millis(); this->lastSuccessResponse = millis();
#ifdef LED_OT_RX_PIN #ifdef LED_OT_RX_GPIO
{ {
digitalWrite(LED_OT_RX_PIN, HIGH); digitalWrite(LED_OT_RX_GPIO, HIGH);
delayMicroseconds(2000); delayMicroseconds(2000);
digitalWrite(LED_OT_RX_PIN, LOW); digitalWrite(LED_OT_RX_GPIO, LOW);
} }
#endif #endif
} }
}); });
ot->setYieldCallback([this]() { this->instance->setYieldCallback([this]() {
this->delay(25); this->delay(25);
}); });
ot->setMinWaitTimeForStartBit(20000); this->instance->begin();
ot->begin(OpenThermTask::handleInterrupt);
#ifdef LED_OT_RX_PIN
pinMode(LED_OT_RX_PIN, OUTPUT);
digitalWrite(LED_OT_RX_PIN, LOW);
#endif
}
void initBoiler() {
this->dhwFlowRateMultiplier = 1;
this->pressureMultiplier = 1;
// Not all boilers support these, only try once when the boiler becomes connected
if (updateSlaveVersion()) {
Log.straceln(FPSTR(L_OT), F("Slave version: %u, type: %u"), vars.parameters.slaveVersion, vars.parameters.slaveType);
} else {
Log.swarningln(FPSTR(L_OT), F("Get slave version failed"));
}
// 0x013F
if (setMasterVersion(0x3F, 0x01)) {
Log.straceln(FPSTR(L_OT), F("Master version: %u, type: %u"), vars.parameters.masterVersion, vars.parameters.masterType);
} else {
Log.swarningln(FPSTR(L_OT), F("Set master version failed"));
}
if (updateSlaveConfig()) {
Log.straceln(FPSTR(L_OT), F("Slave member id: %u, flags: %u"), vars.parameters.slaveMemberId, vars.parameters.slaveFlags);
} else {
Log.swarningln(FPSTR(L_OT), F("Get slave config failed"));
}
if (setMasterConfig(settings.opentherm.memberIdCode & 0xFF, (settings.opentherm.memberIdCode & 0xFFFF) >> 8)) {
Log.straceln(FPSTR(L_OT), F("Master member id: %u, flags: %u"), vars.parameters.masterMemberId, vars.parameters.masterFlags);
} else {
Log.swarningln(FPSTR(L_OT), F("Set master config failed"));
}
} }
void loop() { void loop() {
static byte currentHeatingTemp, currentDhwTemp = 0; static byte currentHeatingTemp, currentDhwTemp = 0;
unsigned long localResponse;
bool heatingEnabled = (vars.states.emergency || settings.heating.enable) && this->pump && isReady(); if (this->instanceInGpio != settings.opentherm.inGpio || this->instanceOutGpio != settings.opentherm.outGpio) {
this->setup();
} else if (this->initializedMemberIdCode != settings.opentherm.memberIdCode || millis() - this->initializedTime > this->initializingInterval) {
this->isInitialized = false;
}
if (this->instance == nullptr) {
this->delay(5000);
return;
}
bool heatingEnabled = (vars.states.emergency || settings.heating.enable) && this->pump && this->isReady();
bool heatingCh2Enabled = settings.opentherm.heatingCh2Enabled; bool heatingCh2Enabled = settings.opentherm.heatingCh2Enabled;
if (settings.opentherm.heatingCh1ToCh2) { if (settings.opentherm.heatingCh1ToCh2) {
heatingCh2Enabled = heatingEnabled; heatingCh2Enabled = heatingEnabled;
@@ -123,7 +130,7 @@ protected:
heatingCh2Enabled = settings.opentherm.dhwPresent && settings.dhw.enable; heatingCh2Enabled = settings.opentherm.dhwPresent && settings.dhw.enable;
} }
localResponse = ot->setBoilerStatus( unsigned long response = this->instance->setBoilerStatus(
heatingEnabled, heatingEnabled,
settings.opentherm.dhwPresent && settings.dhw.enable, settings.opentherm.dhwPresent && settings.dhw.enable,
false, false,
@@ -133,20 +140,20 @@ protected:
settings.opentherm.dhwBlocking settings.opentherm.dhwBlocking
); );
if (!ot->isValidResponse(localResponse)) { if (!CustomOpenTherm::isValidResponse(response)) {
Log.swarningln(FPSTR(L_OT), F("Invalid response after setBoilerStatus: %s"), ot->statusToString(ot->getLastResponseStatus())); Log.swarningln(FPSTR(L_OT), F("Invalid response after setBoilerStatus: %s"), CustomOpenTherm::statusToString(this->instance->getLastResponseStatus()));
} }
if (!vars.states.otStatus && millis() - this->lastSuccessResponse < 1150) { if (!vars.states.otStatus && millis() - this->lastSuccessResponse < 1150) {
Log.sinfoln(FPSTR(L_OT), F("Connected. Initializing")); Log.sinfoln(FPSTR(L_OT), F("Connected"));
vars.states.otStatus = true; vars.states.otStatus = true;
this->initBoiler();
} else if (vars.states.otStatus && millis() - this->lastSuccessResponse > 1150) { } else if (vars.states.otStatus && millis() - this->lastSuccessResponse > 1150) {
Log.swarningln(FPSTR(L_OT), F("Disconnected")); Log.swarningln(FPSTR(L_OT), F("Disconnected"));
vars.states.otStatus = false; vars.states.otStatus = false;
this->isInitialized = false;
} }
// If boiler is disconnected, no need try setting other OT stuff // If boiler is disconnected, no need try setting other OT stuff
@@ -160,17 +167,27 @@ protected:
return; return;
} }
if (!this->isInitialized) {
Log.sinfoln(FPSTR(L_OT), F("Initializing..."));
this->isInitialized = true;
this->initializedTime = millis();
this->initializedMemberIdCode = settings.opentherm.memberIdCode;
this->dhwFlowRateMultiplier = 1;
this->pressureMultiplier = 1;
this->initialize();
}
if (vars.parameters.heatingEnabled != heatingEnabled) { if (vars.parameters.heatingEnabled != heatingEnabled) {
this->prevUpdateNonEssentialVars = 0; this->prevUpdateNonEssentialVars = 0;
vars.parameters.heatingEnabled = heatingEnabled; vars.parameters.heatingEnabled = heatingEnabled;
Log.sinfoln(FPSTR(L_OT_HEATING), "%s", heatingEnabled ? F("Enabled") : F("Disabled")); Log.sinfoln(FPSTR(L_OT_HEATING), "%s", heatingEnabled ? F("Enabled") : F("Disabled"));
} }
vars.states.heating = ot->isCentralHeatingActive(localResponse); vars.states.heating = CustomOpenTherm::isCentralHeatingActive(response);
vars.states.dhw = settings.opentherm.dhwPresent ? ot->isHotWaterActive(localResponse) : false; vars.states.dhw = settings.opentherm.dhwPresent ? CustomOpenTherm::isHotWaterActive(response) : false;
vars.states.flame = ot->isFlameOn(localResponse); vars.states.flame = CustomOpenTherm::isFlameOn(response);
vars.states.fault = ot->isFault(localResponse); vars.states.fault = CustomOpenTherm::isFault(response);
vars.states.diagnostic = ot->isDiagnostic(localResponse); vars.states.diagnostic = CustomOpenTherm::isDiagnostic(response);
// These parameters will be updated every minute // These parameters will be updated every minute
if (millis() - this->prevUpdateNonEssentialVars > 60000) { if (millis() - this->prevUpdateNonEssentialVars > 60000) {
@@ -193,7 +210,7 @@ protected:
// Get DHW min/max temp (if necessary) // Get DHW min/max temp (if necessary)
if (settings.opentherm.dhwPresent) { if (settings.opentherm.dhwPresent && settings.opentherm.getMinMaxTemp) {
if (updateMinMaxDhwTemp()) { if (updateMinMaxDhwTemp()) {
if (settings.dhw.minTemp < vars.parameters.dhwMinTemp) { if (settings.dhw.minTemp < vars.parameters.dhwMinTemp) {
settings.dhw.minTemp = vars.parameters.dhwMinTemp; settings.dhw.minTemp = vars.parameters.dhwMinTemp;
@@ -208,46 +225,51 @@ protected:
} }
} else { } else {
vars.parameters.dhwMinTemp = convertTemp(DEFAULT_DHW_MIN_TEMP, UnitSystem::METRIC, settings.system.unitSystem);
vars.parameters.dhwMaxTemp = convertTemp(DEFAULT_DHW_MAX_TEMP, UnitSystem::METRIC, settings.system.unitSystem);
Log.swarningln(FPSTR(L_OT_DHW), F("Failed get min/max temp")); Log.swarningln(FPSTR(L_OT_DHW), F("Failed get min/max temp"));
} }
if (settings.dhw.minTemp >= settings.dhw.maxTemp) { if (settings.dhw.minTemp >= settings.dhw.maxTemp) {
settings.dhw.minTemp = 30; settings.dhw.minTemp = vars.parameters.dhwMinTemp;
settings.dhw.maxTemp = 60; settings.dhw.maxTemp = vars.parameters.dhwMaxTemp;
fsSettings.update(); fsSettings.update();
} }
} }
// Get heating min/max temp // Get heating min/max temp
if (updateMinMaxHeatingTemp()) { if (settings.opentherm.getMinMaxTemp) {
if (settings.heating.minTemp < vars.parameters.heatingMinTemp) { if (updateMinMaxHeatingTemp()) {
settings.heating.minTemp = vars.parameters.heatingMinTemp; if (settings.heating.minTemp < vars.parameters.heatingMinTemp) {
fsSettings.update(); settings.heating.minTemp = vars.parameters.heatingMinTemp;
Log.snoticeln(FPSTR(L_OT_HEATING), F("Updated min temp: %hhu"), settings.heating.minTemp); fsSettings.update();
} Log.snoticeln(FPSTR(L_OT_HEATING), F("Updated min temp: %hhu"), settings.heating.minTemp);
}
if (settings.heating.maxTemp > vars.parameters.heatingMaxTemp) { if (settings.heating.maxTemp > vars.parameters.heatingMaxTemp) {
settings.heating.maxTemp = vars.parameters.heatingMaxTemp; settings.heating.maxTemp = vars.parameters.heatingMaxTemp;
fsSettings.update(); fsSettings.update();
Log.snoticeln(FPSTR(L_OT_HEATING), F("Updated max temp: %hhu"), settings.heating.maxTemp); Log.snoticeln(FPSTR(L_OT_HEATING), F("Updated max temp: %hhu"), settings.heating.maxTemp);
} }
} else { } else {
Log.swarningln(FPSTR(L_OT_HEATING), F("Failed get min/max temp")); vars.parameters.heatingMinTemp = convertTemp(DEFAULT_HEATING_MIN_TEMP, UnitSystem::METRIC, settings.system.unitSystem);
vars.parameters.heatingMaxTemp = convertTemp(DEFAULT_HEATING_MAX_TEMP, UnitSystem::METRIC, settings.system.unitSystem);
Log.swarningln(FPSTR(L_OT_HEATING), F("Failed get min/max temp"));
}
} }
if (settings.heating.minTemp >= settings.heating.maxTemp) { if (settings.heating.minTemp >= settings.heating.maxTemp) {
settings.heating.minTemp = 20; settings.heating.minTemp = vars.parameters.heatingMinTemp;
settings.heating.maxTemp = 90; settings.heating.maxTemp = vars.parameters.heatingMaxTemp;
fsSettings.update(); fsSettings.update();
} }
// Force set max heating temp
setMaxHeatingTemp(settings.heating.maxTemp);
// Get outdoor temp (if necessary) // Get outdoor temp (if necessary)
if (settings.sensors.outdoor.type == 0) { if (settings.sensors.outdoor.type == SensorType::BOILER) {
updateOutsideTemp(); updateOutsideTemp();
} }
@@ -263,7 +285,7 @@ protected:
// Get current modulation level (if necessary) // Get current modulation level (if necessary)
if ((settings.opentherm.dhwPresent && settings.dhw.enable) || settings.heating.enable || heatingEnabled) { if (vars.states.flame) {
updateModulationLevel(); updateModulationLevel();
} else { } else {
@@ -283,11 +305,17 @@ protected:
// Get current heating temp // Get current heating temp
updateHeatingTemp(); updateHeatingTemp();
// Get heating return temp
updateHeatingReturnTemp();
// Get exhaust temp
updateExhaustTemp();
// Fault reset action // Fault reset action
if (vars.actions.resetFault) { if (vars.actions.resetFault) {
if (vars.states.fault) { if (vars.states.fault) {
if (ot->sendBoilerReset()) { if (this->instance->sendBoilerReset()) {
Log.sinfoln(FPSTR(L_OT), F("Boiler fault reset successfully")); Log.sinfoln(FPSTR(L_OT), F("Boiler fault reset successfully"));
} else { } else {
@@ -301,7 +329,7 @@ protected:
// Diag reset action // Diag reset action
if (vars.actions.resetDiagnostic) { if (vars.actions.resetDiagnostic) {
if (vars.states.diagnostic) { if (vars.states.diagnostic) {
if (ot->sendServiceReset()) { if (this->instance->sendServiceReset()) {
Log.sinfoln(FPSTR(L_OT), F("Boiler diagnostic reset successfully")); Log.sinfoln(FPSTR(L_OT), F("Boiler diagnostic reset successfully"));
} else { } else {
@@ -315,15 +343,16 @@ protected:
// Update DHW temp // Update DHW temp
byte newDhwTemp = settings.dhw.target; byte newDhwTemp = settings.dhw.target;
if (settings.opentherm.dhwPresent && settings.dhw.enable && (needSetDhwTemp() || newDhwTemp != currentDhwTemp)) { if (settings.opentherm.dhwPresent && settings.dhw.enable && (this->needSetDhwTemp() || newDhwTemp != currentDhwTemp)) {
if (newDhwTemp < settings.dhw.minTemp || newDhwTemp > settings.dhw.maxTemp) { if (newDhwTemp < settings.dhw.minTemp || newDhwTemp > settings.dhw.maxTemp) {
newDhwTemp = constrain(newDhwTemp, settings.dhw.minTemp, settings.dhw.maxTemp); newDhwTemp = constrain(newDhwTemp, settings.dhw.minTemp, settings.dhw.maxTemp);
} }
Log.sinfoln(FPSTR(L_OT_DHW), F("Set temp = %u"), newDhwTemp); float convertedTemp = convertTemp(newDhwTemp, settings.system.unitSystem, settings.opentherm.unitSystem);
Log.sinfoln(FPSTR(L_OT_DHW), F("Set temp: %u (converted: %.2f)"), newDhwTemp, convertedTemp);
// Set DHW temp // Set DHW temp
if (ot->setDhwTemp(newDhwTemp)) { if (this->instance->setDhwTemp(convertedTemp)) {
currentDhwTemp = newDhwTemp; currentDhwTemp = newDhwTemp;
this->dhwSetTempTime = millis(); this->dhwSetTempTime = millis();
@@ -333,7 +362,7 @@ protected:
// Set DHW temp to CH2 // Set DHW temp to CH2
if (settings.opentherm.dhwToCh2) { if (settings.opentherm.dhwToCh2) {
if (!ot->setHeatingCh2Temp(newDhwTemp)) { if (!this->instance->setHeatingCh2Temp(convertedTemp)) {
Log.swarningln(FPSTR(L_OT_DHW), F("Failed set ch2 temp")); Log.swarningln(FPSTR(L_OT_DHW), F("Failed set ch2 temp"));
} }
} }
@@ -341,11 +370,12 @@ protected:
// Update heating temp // Update heating temp
if (heatingEnabled && (needSetHeatingTemp() || fabs(vars.parameters.heatingSetpoint - currentHeatingTemp) > 0.0001)) { if (heatingEnabled && (this->needSetHeatingTemp() || fabs(vars.parameters.heatingSetpoint - currentHeatingTemp) > 0.0001)) {
Log.sinfoln(FPSTR(L_OT_HEATING), F("Set temp = %u"), vars.parameters.heatingSetpoint); float convertedTemp = convertTemp(vars.parameters.heatingSetpoint, settings.system.unitSystem, settings.opentherm.unitSystem);
Log.sinfoln(FPSTR(L_OT_HEATING), F("Set temp: %u (converted: %.2f)"), vars.parameters.heatingSetpoint, convertedTemp);
// Set heating temp // Set heating temp
if (ot->setHeatingCh1Temp(vars.parameters.heatingSetpoint)) { if (this->instance->setHeatingCh1Temp(convertedTemp) || this->setMaxHeatingTemp(convertedTemp)) {
currentHeatingTemp = vars.parameters.heatingSetpoint; currentHeatingTemp = vars.parameters.heatingSetpoint;
this->heatingSetTempTime = millis(); this->heatingSetTempTime = millis();
@@ -355,7 +385,7 @@ protected:
// Set heating temp to CH2 // Set heating temp to CH2
if (settings.opentherm.heatingCh1ToCh2) { if (settings.opentherm.heatingCh1ToCh2) {
if (!ot->setHeatingCh2Temp(vars.parameters.heatingSetpoint)) { if (!this->instance->setHeatingCh2Temp(convertedTemp)) {
Log.swarningln(FPSTR(L_OT_HEATING), F("Failed set ch2 temp")); Log.swarningln(FPSTR(L_OT_HEATING), F("Failed set ch2 temp"));
} }
} }
@@ -378,8 +408,47 @@ protected:
} }
} }
void initialize() {
// Not all boilers support these, only try once when the boiler becomes connected
if (this->updateSlaveVersion()) {
Log.straceln(FPSTR(L_OT), F("Slave version: %u, type: %u"), vars.parameters.slaveVersion, vars.parameters.slaveType);
} else {
Log.swarningln(FPSTR(L_OT), F("Get slave version failed"));
}
// 0x013F
if (this->setMasterVersion(0x3F, 0x01)) {
Log.straceln(FPSTR(L_OT), F("Master version: %u, type: %u"), vars.parameters.masterVersion, vars.parameters.masterType);
} else {
Log.swarningln(FPSTR(L_OT), F("Set master version failed"));
}
if (this->updateSlaveOtVersion()) {
Log.straceln(FPSTR(L_OT), F("Slave OT version: %f"), vars.parameters.slaveOtVersion);
} else {
Log.swarningln(FPSTR(L_OT), F("Get slave OT version failed"));
}
if (this->updateSlaveConfig()) {
Log.straceln(FPSTR(L_OT), F("Slave member id: %u, flags: %u"), vars.parameters.slaveMemberId, vars.parameters.slaveFlags);
} else {
Log.swarningln(FPSTR(L_OT), F("Get slave config failed"));
}
if (this->setMasterConfig(settings.opentherm.memberIdCode & 0xFF, (settings.opentherm.memberIdCode & 0xFFFF) >> 8)) {
Log.straceln(FPSTR(L_OT), F("Master member id: %u, flags: %u"), vars.parameters.masterMemberId, vars.parameters.masterFlags);
} else {
Log.swarningln(FPSTR(L_OT), F("Set master config failed"));
}
}
bool isReady() { bool isReady() {
return millis() - this->startupTime > this->readyTime; return millis() - this->instanceCreatedTime > this->readyTime;
} }
bool needSetDhwTemp() { bool needSetDhwTemp() {
@@ -391,13 +460,13 @@ protected:
} }
bool updateSlaveConfig() { bool updateSlaveConfig() {
unsigned long response = ot->sendRequest(ot->buildRequest( unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
OpenThermRequestType::READ_DATA, OpenThermRequestType::READ_DATA,
OpenThermMessageID::SConfigSMemberIDcode, OpenThermMessageID::SConfigSMemberIDcode,
0 0
)); ));
if (!ot->isValidResponse(response)) { if (!CustomOpenTherm::isValidResponse(response)) {
return false; return false;
} }
@@ -452,69 +521,69 @@ protected:
return true; return true;
} }
unsigned long response = ot->sendRequest(ot->buildRequest( unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
OpenThermRequestType::WRITE_DATA, OpenThermRequestType::WRITE_DATA,
OpenThermMessageID::MConfigMMemberIDcode, OpenThermMessageID::MConfigMMemberIDcode,
request request
)); ));
return ot->isValidResponse(response); return CustomOpenTherm::isValidResponse(response);
} }
bool setMaxModulationLevel(byte value) { bool setMaxModulationLevel(byte value) {
unsigned long response = ot->sendRequest(ot->buildRequest( unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
OpenThermRequestType::WRITE_DATA, OpenThermRequestType::WRITE_DATA,
OpenThermMessageID::MaxRelModLevelSetting, OpenThermMessageID::MaxRelModLevelSetting,
ot->toF88(value) CustomOpenTherm::toFloat(value)
)); ));
if (!ot->isValidResponse(response)) { if (!CustomOpenTherm::isValidResponse(response)) {
return false; return false;
} }
vars.parameters.maxModulation = ot->fromF88(response); vars.parameters.maxModulation = CustomOpenTherm::getFloat(response);
return true; return true;
} }
bool updateSlaveOtVersion() { bool updateSlaveOtVersion() {
unsigned long response = ot->sendRequest(ot->buildRequest( unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
OpenThermRequestType::READ_DATA, OpenThermRequestType::READ_DATA,
OpenThermMessageID::OpenThermVersionSlave, OpenThermMessageID::OpenThermVersionSlave,
0 0
)); ));
if (!ot->isValidResponse(response)) { if (!CustomOpenTherm::isValidResponse(response)) {
return false; return false;
} }
vars.parameters.slaveOtVersion = ot->getFloat(response); vars.parameters.slaveOtVersion = CustomOpenTherm::getFloat(response);
return true; return true;
} }
bool setMasterOtVersion(float version) { bool setMasterOtVersion(float version) {
unsigned long response = ot->sendRequest(ot->buildRequest( unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
OpenThermRequestType::WRITE_DATA, OpenThermRequestType::WRITE_DATA,
OpenThermMessageID::OpenThermVersionMaster, OpenThermMessageID::OpenThermVersionMaster,
ot->toF88(version) CustomOpenTherm::toFloat(version)
)); ));
if (!ot->isValidResponse(response)) { if (!CustomOpenTherm::isValidResponse(response)) {
return false; return false;
} }
vars.parameters.masterOtVersion = ot->fromF88(response); vars.parameters.masterOtVersion = CustomOpenTherm::getFloat(response);
return true; return true;
} }
bool updateSlaveVersion() { bool updateSlaveVersion() {
unsigned long response = ot->sendRequest(ot->buildRequest( unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
OpenThermRequestType::READ_DATA, OpenThermRequestType::READ_DATA,
OpenThermMessageID::SlaveVersion, OpenThermMessageID::SlaveVersion,
0 0
)); ));
if (!ot->isValidResponse(response)) { if (!CustomOpenTherm::isValidResponse(response)) {
return false; return false;
} }
@@ -525,13 +594,13 @@ protected:
} }
bool setMasterVersion(uint8_t version, uint8_t type) { bool setMasterVersion(uint8_t version, uint8_t type) {
unsigned long response = ot->sendRequest(ot->buildRequest( unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
OpenThermRequestType::WRITE_DATA, OpenThermRequestType::WRITE_DATA,
OpenThermMessageID::MasterVersion, OpenThermMessageID::MasterVersion,
(unsigned int) version | (unsigned int) type << 8 (unsigned int) version | (unsigned int) type << 8
)); ));
if (!ot->isValidResponse(response)) { if (!CustomOpenTherm::isValidResponse(response)) {
return false; return false;
} }
@@ -542,13 +611,13 @@ protected:
} }
bool updateMinMaxDhwTemp() { bool updateMinMaxDhwTemp() {
unsigned long response = ot->sendRequest(ot->buildRequest( unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
OpenThermRequestType::READ_DATA, OpenThermRequestType::READ_DATA,
OpenThermMessageID::TdhwSetUBTdhwSetLB, OpenThermMessageID::TdhwSetUBTdhwSetLB,
0 0
)); ));
if (!ot->isValidResponse(response)) { if (!CustomOpenTherm::isValidResponse(response)) {
return false; return false;
} }
@@ -556,8 +625,8 @@ protected:
byte maxTemp = (response & 0xFFFF) >> 8; byte maxTemp = (response & 0xFFFF) >> 8;
if (minTemp >= 0 && maxTemp > 0 && maxTemp > minTemp) { if (minTemp >= 0 && maxTemp > 0 && maxTemp > minTemp) {
vars.parameters.dhwMinTemp = minTemp; vars.parameters.dhwMinTemp = convertTemp(minTemp, settings.opentherm.unitSystem, settings.system.unitSystem);
vars.parameters.dhwMaxTemp = maxTemp; vars.parameters.dhwMaxTemp = convertTemp(maxTemp, settings.opentherm.unitSystem, settings.system.unitSystem);
return true; return true;
} }
@@ -566,13 +635,13 @@ protected:
} }
bool updateMinMaxHeatingTemp() { bool updateMinMaxHeatingTemp() {
unsigned long response = ot->sendRequest(ot->buildRequest( unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
OpenThermRequestType::READ_DATA, OpenThermRequestType::READ_DATA,
OpenThermMessageID::MaxTSetUBMaxTSetLB, OpenThermMessageID::MaxTSetUBMaxTSetLB,
0 0
)); ));
if (!ot->isValidResponse(response)) { if (!CustomOpenTherm::isValidResponse(response)) {
return false; return false;
} }
@@ -580,8 +649,8 @@ protected:
byte maxTemp = (response & 0xFFFF) >> 8; byte maxTemp = (response & 0xFFFF) >> 8;
if (minTemp >= 0 && maxTemp > 0 && maxTemp > minTemp) { if (minTemp >= 0 && maxTemp > 0 && maxTemp > minTemp) {
vars.parameters.heatingMinTemp = minTemp; vars.parameters.heatingMinTemp = convertTemp(minTemp, settings.opentherm.unitSystem, settings.system.unitSystem);
vars.parameters.heatingMaxTemp = maxTemp; vars.parameters.heatingMaxTemp = convertTemp(maxTemp, settings.opentherm.unitSystem, settings.system.unitSystem);
return true; return true;
} }
@@ -589,99 +658,164 @@ protected:
} }
bool setMaxHeatingTemp(byte value) { bool setMaxHeatingTemp(byte value) {
unsigned long response = ot->sendRequest(ot->buildRequest( unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
OpenThermMessageType::WRITE_DATA, OpenThermMessageType::WRITE_DATA,
OpenThermMessageID::MaxTSet, OpenThermMessageID::MaxTSet,
ot->temperatureToData(value) CustomOpenTherm::temperatureToData(value)
)); ));
return ot->isValidResponse(response); return CustomOpenTherm::isValidResponse(response);
} }
bool updateOutsideTemp() { bool updateOutsideTemp() {
unsigned long response = ot->sendRequest(ot->buildRequest( unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
OpenThermRequestType::READ_DATA, OpenThermRequestType::READ_DATA,
OpenThermMessageID::Toutside, OpenThermMessageID::Toutside,
0 0
)); ));
if (!ot->isValidResponse(response)) { if (!CustomOpenTherm::isValidResponse(response)) {
return false; return false;
} }
vars.temperatures.outdoor = ot->getFloat(response) + settings.sensors.outdoor.offset; vars.temperatures.outdoor = settings.sensors.outdoor.offset + convertTemp(
CustomOpenTherm::getFloat(response),
settings.opentherm.unitSystem,
settings.system.unitSystem
);
return true;
}
bool updateExhaustTemp() {
unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
OpenThermRequestType::READ_DATA,
OpenThermMessageID::Texhaust,
0
));
if (!CustomOpenTherm::isValidResponse(response)) {
return false;
}
float value = (float) CustomOpenTherm::getInt(response);
if (!isValidTemp(value, settings.opentherm.unitSystem, -40, 500)) {
return false;
}
vars.temperatures.exhaust = convertTemp(
value,
settings.opentherm.unitSystem,
settings.system.unitSystem
);
return true; return true;
} }
bool updateHeatingTemp() { bool updateHeatingTemp() {
unsigned long response = ot->sendRequest(ot->buildRequest( unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
OpenThermMessageType::READ_DATA, OpenThermMessageType::READ_DATA,
OpenThermMessageID::Tboiler, OpenThermMessageID::Tboiler,
0 0
)); ));
if (!ot->isValidResponse(response)) { if (!CustomOpenTherm::isValidResponse(response)) {
return false; return false;
} }
float value = ot->getFloat(response); float value = CustomOpenTherm::getFloat(response);
if (value <= 0) { if (value <= 0) {
return false; return false;
} }
vars.temperatures.heating = value; vars.temperatures.heating = convertTemp(
value,
settings.opentherm.unitSystem,
settings.system.unitSystem
);
return true;
}
bool updateHeatingReturnTemp() {
unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
OpenThermMessageType::READ_DATA,
OpenThermMessageID::Tret,
0
));
if (!CustomOpenTherm::isValidResponse(response)) {
return false;
}
vars.temperatures.heatingReturn = convertTemp(
CustomOpenTherm::getFloat(response),
settings.opentherm.unitSystem,
settings.system.unitSystem
);
return true; return true;
} }
bool updateDhwTemp() { bool updateDhwTemp() {
unsigned long response = ot->sendRequest(ot->buildRequest( unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
OpenThermMessageType::READ_DATA, OpenThermMessageType::READ_DATA,
OpenThermMessageID::Tdhw, OpenThermMessageID::Tdhw,
0 0
)); ));
if (!ot->isValidResponse(response)) { if (!CustomOpenTherm::isValidResponse(response)) {
return false; return false;
} }
float value = ot->getFloat(response); float value = CustomOpenTherm::getFloat(response);
if (value <= 0) { if (value <= 0) {
return false; return false;
} }
vars.temperatures.dhw = value; vars.temperatures.dhw = convertTemp(
value,
settings.opentherm.unitSystem,
settings.system.unitSystem
);
return true; return true;
} }
bool updateDhwFlowRate() { bool updateDhwFlowRate() {
unsigned long response = ot->sendRequest(ot->buildRequest( unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
OpenThermMessageType::READ_DATA, OpenThermMessageType::READ_DATA,
OpenThermMessageID::DHWFlowRate, OpenThermMessageID::DHWFlowRate,
0 0
)); ));
if (!ot->isValidResponse(response)) { if (!CustomOpenTherm::isValidResponse(response)) {
return false; return false;
} }
float value = ot->getFloat(response); float value = CustomOpenTherm::getFloat(response);
if (value > 16 && this->dhwFlowRateMultiplier != 10) { if (this->dhwFlowRateMultiplier != 10 && value > convertVolume(16, UnitSystem::METRIC, settings.opentherm.unitSystem)) {
this->dhwFlowRateMultiplier = 10; this->dhwFlowRateMultiplier = 10;
} }
vars.sensors.dhwFlowRate = this->dhwFlowRateMultiplier == 1 ? value : value / this->dhwFlowRateMultiplier;
vars.sensors.dhwFlowRate = convertVolume(
value / this->dhwFlowRateMultiplier,
settings.opentherm.unitSystem,
settings.system.unitSystem
);
return true; return true;
} }
bool updateFaultCode() { bool updateFaultCode() {
unsigned long response = ot->sendRequest(ot->buildRequest( unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
OpenThermRequestType::READ_DATA, OpenThermRequestType::READ_DATA,
OpenThermMessageID::ASFflags, OpenThermMessageID::ASFflags,
0 0
)); ));
if (!ot->isValidResponse(response)) { if (!CustomOpenTherm::isValidResponse(response)) {
return false; return false;
} }
@@ -690,42 +824,42 @@ protected:
} }
bool updateModulationLevel() { bool updateModulationLevel() {
unsigned long response = ot->sendRequest(ot->buildRequest( unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
OpenThermRequestType::READ_DATA, OpenThermRequestType::READ_DATA,
OpenThermMessageID::RelModLevel, OpenThermMessageID::RelModLevel,
0 0
)); ));
if (!ot->isValidResponse(response)) { if (!CustomOpenTherm::isValidResponse(response)) {
return false; return false;
} }
float modulation = ot->fromF88(response); vars.sensors.modulation = CustomOpenTherm::getFloat(response);
if (!vars.states.flame) {
vars.sensors.modulation = 0;
} else {
vars.sensors.modulation = modulation;
}
return true; return true;
} }
bool updatePressure() { bool updatePressure() {
unsigned long response = ot->sendRequest(ot->buildRequest( unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
OpenThermRequestType::READ_DATA, OpenThermRequestType::READ_DATA,
OpenThermMessageID::CHPressure, OpenThermMessageID::CHPressure,
0 0
)); ));
if (!ot->isValidResponse(response)) { if (!CustomOpenTherm::isValidResponse(response)) {
return false; return false;
} }
float value = ot->getFloat(response); float value = CustomOpenTherm::getFloat(response);
if (value > 5 && this->pressureMultiplier != 10) { if (this->pressureMultiplier != 10 && value > convertPressure(5, UnitSystem::METRIC, settings.opentherm.unitSystem)) {
this->pressureMultiplier = 10; this->pressureMultiplier = 10;
} }
vars.sensors.pressure = this->pressureMultiplier == 1 ? value : value / this->pressureMultiplier;
vars.sensors.pressure = convertPressure(
value / this->pressureMultiplier,
settings.opentherm.unitSystem,
settings.system.unitSystem
);
return true; return true;
} }

View File

@@ -62,7 +62,7 @@ protected:
}*/ }*/
int getTaskPriority() { int getTaskPriority() {
return 0; return 1;
} }
void setup() { void setup() {
@@ -86,9 +86,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 +115,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 +127,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 +139,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 +150,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 +184,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 +208,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 +265,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 +279,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 +310,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 +330,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;
@@ -374,7 +370,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 +384,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 +415,15 @@ 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();
} }
}); });
@@ -433,24 +432,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,12 +469,39 @@ 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); });
}
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 ? Network::Manager::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();
doc.shrinkToFit();
this->bufferedWebServer->send(200, "application/json", doc);
}); });
@@ -559,8 +574,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

@@ -69,7 +69,7 @@ protected:
} }
} }
// Ограничиваем, если до этого не ограничило // Limits
if (newTemp < settings.heating.minTemp || newTemp > settings.heating.maxTemp) { if (newTemp < settings.heating.minTemp || newTemp > settings.heating.maxTemp) {
newTemp = constrain(newTemp, settings.heating.minTemp, settings.heating.maxTemp); newTemp = constrain(newTemp, settings.heating.minTemp, settings.heating.maxTemp);
} }
@@ -84,7 +84,7 @@ protected:
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 && settings.sensors.outdoor.type != SensorType::MANUAL) {
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.0001 >= 0.5) {
@@ -97,7 +97,7 @@ protected:
newTemp += prevEtResult; newTemp += prevEtResult;
} }
} else if(settings.emergency.usePid && settings.sensors.indoor.type != 1) { } else if(settings.emergency.usePid && settings.sensors.indoor.type != SensorType::MANUAL) {
if (vars.parameters.heatingEnabled) { if (vars.parameters.heatingEnabled) {
float pidResult = getPidTemp( float pidResult = getPidTemp(
settings.heating.minTemp, settings.heating.minTemp,
@@ -182,7 +182,6 @@ protected:
} }
newTemp = round(newTemp); newTemp = round(newTemp);
newTemp = constrain(newTemp, 0, 100);
return newTemp; return newTemp;
} }
@@ -273,16 +272,36 @@ protected:
} }
} }
/**
* @brief Get the Equitherm Temp
* Calculations in degrees C, conversion occurs when using F
*
* @param minTemp
* @param maxTemp
* @return float
*/
float getEquithermTemp(int minTemp, int maxTemp) { float 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; etRegulator.Kt = 0;
etRegulator.indoorTemp = 0; etRegulator.indoorTemp = 0;
etRegulator.outdoorTemp = vars.temperatures.outdoor; 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,17 +309,21 @@ 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) {

View File

@@ -60,33 +60,58 @@ protected:
} }
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.indoor.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.099) {
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.099) {
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"));
@@ -204,9 +229,9 @@ 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); Log.sinfoln(FPSTR(L_SENSORS_OUTDOOR), F("Starting on gpio %hhu..."), settings.sensors.outdoor.gpio);
this->oneWireOutdoorSensor->begin(settings.sensors.outdoor.pin); this->oneWireOutdoorSensor->begin(settings.sensors.outdoor.gpio);
this->outdoorSensor->begin(); this->outdoorSensor->begin();
Log.straceln( Log.straceln(
@@ -270,9 +295,9 @@ 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); Log.sinfoln(FPSTR(L_SENSORS_INDOOR), F("Starting on gpio %hhu..."), settings.sensors.indoor.gpio);
this->oneWireIndoorSensor->begin(settings.sensors.indoor.pin); this->oneWireIndoorSensor->begin(settings.sensors.indoor.gpio);
this->indoorSensor->begin(); this->indoorSensor->begin();
Log.straceln( Log.straceln(

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,30 @@ 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;
} 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;
unsigned int memberIdCode = 0; unsigned int memberIdCode = 0;
bool dhwPresent = true; bool dhwPresent = true;
bool summerWinterMode = false; bool summerWinterMode = false;
@@ -46,14 +57,16 @@ struct Settings {
bool dhwToCh2 = false; bool dhwToCh2 = false;
bool dhwBlocking = false; bool dhwBlocking = false;
bool modulationSyncWithHeating = false; bool modulationSyncWithHeating = false;
bool getMinMaxTemp = true;
} 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;
} mqtt; } mqtt;
@@ -62,6 +75,8 @@ struct Settings {
float target = 40.0f; float target = 40.0f;
bool useEquitherm = false; bool useEquitherm = false;
bool usePid = false; bool usePid = false;
bool onNetworkFault = true;
bool onMqttFault = true;
} emergency; } emergency;
struct { struct {
@@ -100,16 +115,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 +130,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;
@@ -141,6 +154,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,7 +169,9 @@ 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 {

View File

@@ -1,12 +1,9 @@
#define PROJECT_NAME "OpenTherm Gateway" #define PROJECT_NAME "OpenTherm Gateway"
#define PROJECT_VERSION "1.4.0-rc.15" #define PROJECT_VERSION "1.4.0-rc.21"
#define PROJECT_REPO "https://github.com/Laxilef/OTGateway" #define PROJECT_REPO "https://github.com/Laxilef/OTGateway"
#define EMERGENCY_TIME_TRESHOLD 120000 #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_INTERVAL 5000
#define EXT_SENSORS_FILTER_K 0.15 #define EXT_SENSORS_FILTER_K 0.15
@@ -14,16 +11,12 @@
#define CONFIG_URL "http://%s/" #define CONFIG_URL "http://%s/"
#define SETTINGS_VALID_VALUE "stvalid" // only 8 chars! #define SETTINGS_VALID_VALUE "stvalid" // only 8 chars!
#define GPIO_IS_NOT_CONFIGURED 0xff
#define DEFAULT_HEATING_MIN_TEMP 20 #define DEFAULT_HEATING_MIN_TEMP 20
#define DEFAULT_HEATING_MAX_TEMP 90 #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
#ifndef WM_DEBUG_MODE
#define WM_DEBUG_MODE WM_DEBUG_NOTIFY
#endif
#ifndef USE_SERIAL #ifndef USE_SERIAL
#define USE_SERIAL true #define USE_SERIAL true
#endif #endif
@@ -36,80 +29,98 @@
#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_PORTAL_LOGIN
#define PORTAL_LOGIN_DEFAULT "" #define DEFAULT_PORTAL_LOGIN ""
#endif #endif
#ifndef PORTAL_PASSWORD_DEFAULT #ifndef DEFAULT_PORTAL_PASSWORD
#define PORTAL_PASSWORD_DEFAULT "" #define DEFAULT_PORTAL_PASSWORD ""
#endif #endif
#ifndef MQTT_SERVER_DEFAULT #ifndef DEFAULT_MQTT_SERVER
#define MQTT_SERVER_DEFAULT "" #define DEFAULT_MQTT_SERVER ""
#endif #endif
#ifndef MQTT_PORT_DEFAULT #ifndef DEFAULT_MQTT_PORT
#define MQTT_PORT_DEFAULT 1883 #define DEFAULT_MQTT_PORT 1883
#endif #endif
#ifndef MQTT_USER_DEFAULT #ifndef DEFAULT_MQTT_USER
#define MQTT_USER_DEFAULT "" #define DEFAULT_MQTT_USER ""
#endif #endif
#ifndef MQTT_PASSWORD_DEFAULT #ifndef DEFAULT_MQTT_PASSWORD
#define MQTT_PASSWORD_DEFAULT "" #define DEFAULT_MQTT_PASSWORD ""
#endif #endif
#ifndef MQTT_PREFIX_DEFAULT #ifndef DEFAULT_MQTT_PREFIX
#define MQTT_PREFIX_DEFAULT "opentherm" #define DEFAULT_MQTT_PREFIX "opentherm"
#endif #endif
#ifndef OT_IN_PIN_DEFAULT #ifndef DEFAULT_OT_IN_GPIO
#define OT_IN_PIN_DEFAULT 0 #define DEFAULT_OT_IN_GPIO GPIO_IS_NOT_CONFIGURED
#endif #endif
#ifndef OT_OUT_PIN_DEFAULT #ifndef DEFAULT_OT_OUT_GPIO
#define OT_OUT_PIN_DEFAULT 0 #define DEFAULT_OT_OUT_GPIO GPIO_IS_NOT_CONFIGURED
#endif #endif
#ifndef SENSOR_OUTDOOR_PIN_DEFAULT #ifndef DEFAULT_SENSOR_OUTDOOR_GPIO
#define SENSOR_OUTDOOR_PIN_DEFAULT 0 #define DEFAULT_SENSOR_OUTDOOR_GPIO GPIO_IS_NOT_CONFIGURED
#endif #endif
#ifndef SENSOR_INDOOR_PIN_DEFAULT #ifndef DEFAULT_SENSOR_INDOOR_GPIO
#define SENSOR_INDOOR_PIN_DEFAULT 0 #define DEFAULT_SENSOR_INDOOR_GPIO GPIO_IS_NOT_CONFIGURED
#endif #endif
#ifndef EXT_PUMP_PIN_DEFAULT #ifndef DEFAULT_EXT_PUMP_GPIO
#define EXT_PUMP_PIN_DEFAULT 0 #define DEFAULT_EXT_PUMP_GPIO GPIO_IS_NOT_CONFIGURED
#endif #endif
#ifndef PROGMEM #ifndef PROGMEM
#define PROGMEM #define PROGMEM
#endif #endif
#ifndef 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

@@ -59,7 +59,7 @@ void setup() {
return tm{sec, min, hour}; return tm{sec, min, hour};
}); });
Serial.begin(115200); Serial.begin(settings.system.serial.baudrate);
Log.addStream(&Serial); Log.addStream(&Serial);
Log.print("\n\n\r"); Log.print("\n\n\r");
@@ -109,12 +109,12 @@ void setup() {
} }
// logs // logs
if (!settings.system.useSerial) { if (!settings.system.serial.enable) {
Log.clearStreams(); Log.clearStreams();
Serial.end(); Serial.end();
} }
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);
@@ -143,7 +143,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);

View File

@@ -1,5 +1,66 @@
#include <Arduino.h> #include <Arduino.h>
inline float liter2gallon(float value) {
return value / 4.546091879f;
}
inline float gallon2liter(float value) {
return value * 4.546091879f;
}
float convertVolume(float value, const UnitSystem unitFrom, const UnitSystem unitTo) {
if (unitFrom == UnitSystem::METRIC && unitTo == UnitSystem::IMPERIAL) {
value = liter2gallon(value);
} else if (unitFrom == UnitSystem::IMPERIAL && unitTo == UnitSystem::METRIC) {
value = gallon2liter(value);
}
return value;
}
inline float bar2psi(float value) {
return value * 14.5038f;
}
inline float psi2bar(float value) {
return value / 14.5038f;
}
float convertPressure(float value, const UnitSystem unitFrom, const UnitSystem unitTo) {
if (unitFrom == UnitSystem::METRIC && unitTo == UnitSystem::IMPERIAL) {
value = bar2psi(value);
} else if (unitFrom == UnitSystem::IMPERIAL && unitTo == UnitSystem::METRIC) {
value = psi2bar(value);
}
return value;
}
inline float c2f(float value) {
return (9.0f / 5.0f) * value + 32.0f;
}
inline float f2c(float value) {
return (value - 32.0f) * (5.0f / 9.0f);
}
float convertTemp(float value, const UnitSystem unitFrom, const UnitSystem unitTo) {
if (unitFrom == UnitSystem::METRIC && unitTo == UnitSystem::IMPERIAL) {
value = c2f(value);
} else if (unitFrom == UnitSystem::IMPERIAL && unitTo == UnitSystem::METRIC) {
value = f2c(value);
}
return value;
}
bool isValidTemp(const float value, UnitSystem unit, const float min = 0.1f, const float max = 99.9f, const UnitSystem minMaxUnit = UnitSystem::METRIC) {
return value >= convertTemp(min, minMaxUnit, unit) && value <= convertTemp(max, minMaxUnit, unit);
}
double roundd(double value, uint8_t decimals = 2) { double roundd(double value, uint8_t decimals = 2) {
if (decimals == 0) { if (decimals == 0) {
return (int)(value + 0.5); return (int)(value + 0.5);
@@ -13,7 +74,7 @@ double roundd(double value, uint8_t decimals = 2) {
return (int)(value * multiplier) / multiplier; return (int)(value * multiplier) / multiplier;
} }
size_t getTotalHeap() { inline size_t getTotalHeap() {
#if defined(ARDUINO_ARCH_ESP32) #if defined(ARDUINO_ARCH_ESP32)
return ESP.getHeapSize(); return ESP.getHeapSize();
#elif defined(ARDUINO_ARCH_ESP8266) #elif defined(ARDUINO_ARCH_ESP8266)
@@ -63,7 +124,7 @@ size_t getMaxFreeBlockHeap(bool getMinValue = false) {
return getMinValue ? minValue : value; return getMinValue ? minValue : value;
} }
uint8_t getHeapFrag() { inline uint8_t getHeapFrag() {
return 100 - getMaxFreeBlockHeap() * 100.0 / getFreeHeap(); return 100 - getMaxFreeBlockHeap() * 100.0 / getFreeHeap();
} }
@@ -266,15 +327,19 @@ bool jsonToNetworkSettings(const JsonVariantConst src, NetworkSettings& dst) {
void settingsToJson(const Settings& src, JsonVariant dst, bool safe = false) { void settingsToJson(const Settings& src, JsonVariant dst, bool safe = false) {
if (!safe) { if (!safe) {
dst["system"]["debug"] = src.system.debug; dst["system"]["debug"] = src.system.debug;
dst["system"]["useSerial"] = src.system.useSerial; dst["system"]["serial"]["enable"] = src.system.serial.enable;
dst["system"]["useTelnet"] = src.system.useTelnet; dst["system"]["serial"]["baudrate"] = src.system.serial.baudrate;
dst["system"]["telnet"]["enable"] = src.system.telnet.enable;
dst["system"]["telnet"]["port"] = src.system.telnet.port;
dst["system"]["unitSystem"] = static_cast<byte>(src.system.unitSystem);
dst["portal"]["useAuth"] = src.portal.useAuth; dst["portal"]["auth"] = src.portal.auth;
dst["portal"]["login"] = src.portal.login; dst["portal"]["login"] = src.portal.login;
dst["portal"]["password"] = src.portal.password; dst["portal"]["password"] = src.portal.password;
dst["opentherm"]["inPin"] = src.opentherm.inPin; dst["opentherm"]["unitSystem"] = static_cast<byte>(src.opentherm.unitSystem);
dst["opentherm"]["outPin"] = src.opentherm.outPin; dst["opentherm"]["inGpio"] = src.opentherm.inGpio;
dst["opentherm"]["outGpio"] = src.opentherm.outGpio;
dst["opentherm"]["memberIdCode"] = src.opentherm.memberIdCode; dst["opentherm"]["memberIdCode"] = src.opentherm.memberIdCode;
dst["opentherm"]["dhwPresent"] = src.opentherm.dhwPresent; dst["opentherm"]["dhwPresent"] = src.opentherm.dhwPresent;
dst["opentherm"]["summerWinterMode"] = src.opentherm.summerWinterMode; dst["opentherm"]["summerWinterMode"] = src.opentherm.summerWinterMode;
@@ -283,7 +348,9 @@ void settingsToJson(const Settings& src, JsonVariant dst, bool safe = false) {
dst["opentherm"]["dhwToCh2"] = src.opentherm.dhwToCh2; dst["opentherm"]["dhwToCh2"] = src.opentherm.dhwToCh2;
dst["opentherm"]["dhwBlocking"] = src.opentherm.dhwBlocking; dst["opentherm"]["dhwBlocking"] = src.opentherm.dhwBlocking;
dst["opentherm"]["modulationSyncWithHeating"] = src.opentherm.modulationSyncWithHeating; dst["opentherm"]["modulationSyncWithHeating"] = src.opentherm.modulationSyncWithHeating;
dst["opentherm"]["getMinMaxTemp"] = src.opentherm.getMinMaxTemp;
dst["mqtt"]["enable"] = src.mqtt.enable;
dst["mqtt"]["server"] = src.mqtt.server; dst["mqtt"]["server"] = src.mqtt.server;
dst["mqtt"]["port"] = src.mqtt.port; dst["mqtt"]["port"] = src.mqtt.port;
dst["mqtt"]["user"] = src.mqtt.user; dst["mqtt"]["user"] = src.mqtt.user;
@@ -296,6 +363,8 @@ void settingsToJson(const Settings& src, JsonVariant dst, bool safe = false) {
dst["emergency"]["target"] = roundd(src.emergency.target, 2); dst["emergency"]["target"] = roundd(src.emergency.target, 2);
dst["emergency"]["useEquitherm"] = src.emergency.useEquitherm; dst["emergency"]["useEquitherm"] = src.emergency.useEquitherm;
dst["emergency"]["usePid"] = src.emergency.usePid; dst["emergency"]["usePid"] = src.emergency.usePid;
dst["emergency"]["onNetworkFault"] = src.emergency.onNetworkFault;
dst["emergency"]["onMqttFault"] = src.emergency.onMqttFault;
dst["heating"]["enable"] = src.heating.enable; dst["heating"]["enable"] = src.heating.enable;
dst["heating"]["turbo"] = src.heating.turbo; dst["heating"]["turbo"] = src.heating.turbo;
@@ -323,12 +392,12 @@ void settingsToJson(const Settings& src, JsonVariant dst, bool safe = false) {
dst["equitherm"]["k_factor"] = roundd(src.equitherm.k_factor, 3); dst["equitherm"]["k_factor"] = roundd(src.equitherm.k_factor, 3);
dst["equitherm"]["t_factor"] = roundd(src.equitherm.t_factor, 3); dst["equitherm"]["t_factor"] = roundd(src.equitherm.t_factor, 3);
dst["sensors"]["outdoor"]["type"] = src.sensors.outdoor.type; dst["sensors"]["outdoor"]["type"] = static_cast<byte>(src.sensors.outdoor.type);
dst["sensors"]["outdoor"]["pin"] = src.sensors.outdoor.pin; dst["sensors"]["outdoor"]["gpio"] = src.sensors.outdoor.gpio;
dst["sensors"]["outdoor"]["offset"] = roundd(src.sensors.outdoor.offset, 2); dst["sensors"]["outdoor"]["offset"] = roundd(src.sensors.outdoor.offset, 2);
dst["sensors"]["indoor"]["type"] = src.sensors.indoor.type; dst["sensors"]["indoor"]["type"] = static_cast<byte>(src.sensors.indoor.type);
dst["sensors"]["indoor"]["pin"] = src.sensors.indoor.pin; dst["sensors"]["indoor"]["gpio"] = src.sensors.indoor.gpio;
char bleAddress[18]; char bleAddress[18];
sprintf( sprintf(
@@ -346,7 +415,7 @@ void settingsToJson(const Settings& src, JsonVariant dst, bool safe = false) {
if (!safe) { if (!safe) {
dst["externalPump"]["use"] = src.externalPump.use; dst["externalPump"]["use"] = src.externalPump.use;
dst["externalPump"]["pin"] = src.externalPump.pin; dst["externalPump"]["gpio"] = src.externalPump.gpio;
dst["externalPump"]["postCirculationTime"] = roundd(src.externalPump.postCirculationTime / 60, 0); dst["externalPump"]["postCirculationTime"] = roundd(src.externalPump.postCirculationTime / 60, 0);
dst["externalPump"]["antiStuckInterval"] = roundd(src.externalPump.antiStuckInterval / 86400, 0); dst["externalPump"]["antiStuckInterval"] = roundd(src.externalPump.antiStuckInterval / 86400, 0);
dst["externalPump"]["antiStuckTime"] = roundd(src.externalPump.antiStuckTime / 60, 0); dst["externalPump"]["antiStuckTime"] = roundd(src.externalPump.antiStuckTime / 60, 0);
@@ -367,20 +436,71 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false
changed = true; changed = true;
} }
if (src["system"]["useSerial"].is<bool>()) { if (src["system"]["serial"]["enable"].is<bool>()) {
dst.system.useSerial = src["system"]["useSerial"].as<bool>(); dst.system.serial.enable = src["system"]["serial"]["enable"].as<bool>();
changed = true; changed = true;
} }
if (src["system"]["useTelnet"].is<bool>()) { if (!src["system"]["serial"]["baudrate"].isNull()) {
dst.system.useTelnet = src["system"]["useTelnet"].as<bool>(); unsigned int value = src["system"]["serial"]["baudrate"].as<unsigned int>();
if (value == 9600 || value == 19200 || value == 38400 || value == 57600 || value == 74880 || value == 115200) {
dst.system.serial.baudrate = value;
changed = true;
}
}
if (src["system"]["telnet"]["enable"].is<bool>()) {
dst.system.telnet.enable = src["system"]["telnet"]["enable"].as<bool>();
changed = true; changed = true;
} }
if (!src["system"]["telnet"]["port"].isNull()) {
unsigned short value = src["system"]["telnet"]["port"].as<unsigned short>();
if (value > 0 && value <= 65535) {
dst.system.telnet.port = value;
changed = true;
}
}
if (!src["system"]["unitSystem"].isNull()) {
byte value = src["system"]["unitSystem"].as<unsigned char>();
UnitSystem prevUnitSystem = dst.system.unitSystem;
switch (value) {
case static_cast<byte>(UnitSystem::METRIC):
dst.system.unitSystem = UnitSystem::METRIC;
changed = true;
break;
case static_cast<byte>(UnitSystem::IMPERIAL):
dst.system.unitSystem = UnitSystem::IMPERIAL;
changed = true;
break;
default:
break;
}
// convert temps
if (dst.system.unitSystem != prevUnitSystem) {
dst.emergency.target = convertTemp(dst.emergency.target, prevUnitSystem, dst.system.unitSystem);
dst.heating.target = convertTemp(dst.heating.target, prevUnitSystem, dst.system.unitSystem);
dst.heating.minTemp = convertTemp(dst.heating.minTemp, prevUnitSystem, dst.system.unitSystem);
dst.heating.maxTemp = convertTemp(dst.heating.maxTemp, prevUnitSystem, dst.system.unitSystem);
dst.dhw.target = convertTemp(dst.dhw.target, prevUnitSystem, dst.system.unitSystem);
dst.dhw.minTemp = convertTemp(dst.dhw.minTemp, prevUnitSystem, dst.system.unitSystem);
dst.dhw.maxTemp = convertTemp(dst.dhw.maxTemp, prevUnitSystem, dst.system.unitSystem);
dst.pid.minTemp = convertTemp(dst.pid.minTemp, prevUnitSystem, dst.system.unitSystem);
dst.pid.maxTemp = convertTemp(dst.pid.maxTemp, prevUnitSystem, dst.system.unitSystem);
}
}
// portal // portal
if (src["portal"]["useAuth"].is<bool>()) { if (src["portal"]["auth"].is<bool>()) {
dst.portal.useAuth = src["portal"]["useAuth"].as<bool>(); dst.portal.auth = src["portal"]["auth"].as<bool>();
changed = true; changed = true;
} }
@@ -404,21 +524,56 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false
// opentherm // opentherm
if (!src["opentherm"]["inPin"].isNull()) { if (!src["opentherm"]["unitSystem"].isNull()) {
unsigned char value = src["opentherm"]["inPin"].as<unsigned char>(); byte value = src["opentherm"]["unitSystem"].as<unsigned char>();
if (value >= 0 && value < 50) { switch (value) {
dst.opentherm.inPin = value; case static_cast<byte>(UnitSystem::METRIC):
changed = true; dst.opentherm.unitSystem = UnitSystem::METRIC;
changed = true;
break;
case static_cast<byte>(UnitSystem::IMPERIAL):
dst.opentherm.unitSystem = UnitSystem::IMPERIAL;
changed = true;
break;
default:
break;
} }
} }
if (!src["opentherm"]["outPin"].isNull()) { if (!src["opentherm"]["inGpio"].isNull()) {
unsigned char value = src["opentherm"]["outPin"].as<unsigned char>(); if (src["opentherm"]["inGpio"].is<JsonString>() && src["opentherm"]["inGpio"].as<JsonString>().size() == 0) {
if (dst.opentherm.inGpio != GPIO_IS_NOT_CONFIGURED) {
dst.opentherm.inGpio = GPIO_IS_NOT_CONFIGURED;
changed = true;
}
if (value >= 0 && value < 50) { } else {
dst.opentherm.outPin = value; unsigned char value = src["opentherm"]["inGpio"].as<unsigned char>();
changed = true;
if (value >= 0 && value <= 254) {
dst.opentherm.inGpio = value;
changed = true;
}
}
}
if (!src["opentherm"]["outGpio"].isNull()) {
if (src["opentherm"]["outGpio"].is<JsonString>() && src["opentherm"]["outGpio"].as<JsonString>().size() == 0) {
if (dst.opentherm.outGpio != GPIO_IS_NOT_CONFIGURED) {
dst.opentherm.outGpio = GPIO_IS_NOT_CONFIGURED;
changed = true;
}
} else {
unsigned char value = src["opentherm"]["outGpio"].as<unsigned char>();
if (value >= 0 && value <= 254) {
dst.opentherm.outGpio = value;
changed = true;
}
} }
} }
@@ -484,8 +639,18 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false
changed = true; changed = true;
} }
if (src["opentherm"]["getMinMaxTemp"].is<bool>()) {
dst.opentherm.getMinMaxTemp = src["opentherm"]["getMinMaxTemp"].as<bool>();
changed = true;
}
// mqtt // mqtt
if (src["mqtt"]["enable"].is<bool>()) {
dst.mqtt.enable = src["mqtt"]["enable"].as<bool>();
changed = true;
}
if (!src["mqtt"]["server"].isNull()) { if (!src["mqtt"]["server"].isNull()) {
String value = src["mqtt"]["server"].as<String>(); String value = src["mqtt"]["server"].as<String>();
@@ -548,17 +713,8 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false
changed = true; changed = true;
} }
if (!src["emergency"]["target"].isNull()) {
double value = src["emergency"]["target"].as<double>();
if (value > 0 && value < 100) {
dst.emergency.target = roundd(value, 2);
changed = true;
}
}
if (src["emergency"]["useEquitherm"].is<bool>()) { if (src["emergency"]["useEquitherm"].is<bool>()) {
if (dst.sensors.outdoor.type != 1) { if (dst.sensors.outdoor.type != SensorType::MANUAL) {
dst.emergency.useEquitherm = src["emergency"]["useEquitherm"].as<bool>(); dst.emergency.useEquitherm = src["emergency"]["useEquitherm"].as<bool>();
} else { } else {
@@ -573,7 +729,7 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false
} }
if (src["emergency"]["usePid"].is<bool>()) { if (src["emergency"]["usePid"].is<bool>()) {
if (dst.sensors.indoor.type != 1) { if (dst.sensors.indoor.type != SensorType::MANUAL) {
dst.emergency.usePid = src["emergency"]["usePid"].as<bool>(); dst.emergency.usePid = src["emergency"]["usePid"].as<bool>();
} else { } else {
@@ -587,6 +743,128 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false
changed = true; changed = true;
} }
if (src["emergency"]["onNetworkFault"].is<bool>()) {
dst.emergency.onNetworkFault = src["emergency"]["onNetworkFault"].as<bool>();
changed = true;
}
if (src["emergency"]["onMqttFault"].is<bool>()) {
dst.emergency.onMqttFault = src["emergency"]["onMqttFault"].as<bool>();
changed = true;
}
if (!src["emergency"]["target"].isNull()) {
double value = src["emergency"]["target"].as<double>();
bool noRegulators = (!dst.emergency.useEquitherm && !dst.emergency.usePid);
bool valid = isValidTemp(
value,
dst.system.unitSystem,
noRegulators ? dst.heating.minTemp : 5,
noRegulators ? dst.heating.maxTemp : 30,
noRegulators ? dst.system.unitSystem : UnitSystem::METRIC
);
if (valid) {
dst.emergency.target = roundd(value, 2);
changed = true;
}
}
// pid
if (src["pid"]["enable"].is<bool>()) {
dst.pid.enable = src["pid"]["enable"].as<bool>();
changed = true;
}
if (!src["pid"]["p_factor"].isNull()) {
double value = src["pid"]["p_factor"].as<double>();
if (value > 0 && value <= 1000) {
dst.pid.p_factor = roundd(value, 3);
changed = true;
}
}
if (!src["pid"]["i_factor"].isNull()) {
double value = src["pid"]["i_factor"].as<double>();
if (value >= 0 && value <= 100) {
dst.pid.i_factor = roundd(value, 3);
changed = true;
}
}
if (!src["pid"]["d_factor"].isNull()) {
double value = src["pid"]["d_factor"].as<double>();
if (value >= 0 && value <= 100000) {
dst.pid.d_factor = roundd(value, 1);
changed = true;
}
}
if (!src["pid"]["dt"].isNull()) {
unsigned short value = src["pid"]["dt"].as<unsigned short>();
if (value >= 30 && value <= 600) {
dst.pid.dt = value;
changed = true;
}
}
if (!src["pid"]["maxTemp"].isNull()) {
unsigned char value = src["pid"]["maxTemp"].as<unsigned char>();
if (isValidTemp(value, dst.system.unitSystem) && value > dst.pid.minTemp) {
dst.pid.maxTemp = value;
changed = true;
}
}
if (!src["pid"]["minTemp"].isNull()) {
unsigned char value = src["pid"]["minTemp"].as<unsigned char>();
if (isValidTemp(value, dst.system.unitSystem) && value < dst.pid.maxTemp) {
dst.pid.minTemp = value;
changed = true;
}
}
// equitherm
if (src["equitherm"]["enable"].is<bool>()) {
dst.equitherm.enable = src["equitherm"]["enable"].as<bool>();
changed = true;
}
if (!src["equitherm"]["n_factor"].isNull()) {
double value = src["equitherm"]["n_factor"].as<double>();
if (value > 0 && value <= 10) {
dst.equitherm.n_factor = roundd(value, 3);
changed = true;
}
}
if (!src["equitherm"]["k_factor"].isNull()) {
double value = src["equitherm"]["k_factor"].as<double>();
if (value >= 0 && value <= 10) {
dst.equitherm.k_factor = roundd(value, 3);
changed = true;
}
}
if (!src["equitherm"]["t_factor"].isNull()) {
double value = src["equitherm"]["t_factor"].as<double>();
if (value >= 0 && value <= 10) {
dst.equitherm.t_factor = roundd(value, 3);
changed = true;
}
}
// heating // heating
if (src["heating"]["enable"].is<bool>()) { if (src["heating"]["enable"].is<bool>()) {
@@ -601,8 +879,16 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false
if (!src["heating"]["target"].isNull()) { if (!src["heating"]["target"].isNull()) {
double value = src["heating"]["target"].as<double>(); double value = src["heating"]["target"].as<double>();
bool noRegulators = (!dst.equitherm.enable && !dst.pid.enable);
bool valid = isValidTemp(
value,
dst.system.unitSystem,
noRegulators ? dst.heating.minTemp : 5,
noRegulators ? dst.heating.maxTemp : 30,
noRegulators ? dst.system.unitSystem : UnitSystem::METRIC
);
if (value > 0 && value < 100) { if (valid) {
dst.heating.target = roundd(value, 2); dst.heating.target = roundd(value, 2);
changed = true; changed = true;
} }
@@ -654,7 +940,7 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false
if (!src["dhw"]["target"].isNull()) { if (!src["dhw"]["target"].isNull()) {
unsigned char value = src["dhw"]["target"].as<unsigned char>(); unsigned char value = src["dhw"]["target"].as<unsigned char>();
if (value >= 0 && value < 100) { if (isValidTemp(value, dst.system.unitSystem, dst.dhw.minTemp, dst.dhw.maxTemp, dst.system.unitSystem)) {
dst.dhw.target = value; dst.dhw.target = value;
changed = true; changed = true;
} }
@@ -679,122 +965,46 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false
} }
// pid
if (src["pid"]["enable"].is<bool>()) {
dst.pid.enable = src["pid"]["enable"].as<bool>();
changed = true;
}
if (!src["pid"]["p_factor"].isNull()) {
double value = src["pid"]["p_factor"].as<double>();
if (value > 0 && value <= 1000) {
dst.pid.p_factor = roundd(value, 3);
changed = true;
}
}
if (!src["pid"]["i_factor"].isNull()) {
double value = src["pid"]["i_factor"].as<double>();
if (value >= 0 && value <= 100) {
dst.pid.i_factor = roundd(value, 3);
changed = true;
}
}
if (!src["pid"]["d_factor"].isNull()) {
double value = src["pid"]["d_factor"].as<double>();
if (value >= 0 && value <= 100000) {
dst.pid.d_factor = roundd(value, 1);
changed = true;
}
}
if (!src["pid"]["dt"].isNull()) {
unsigned short value = src["pid"]["dt"].as<unsigned short>();
if (value >= 30 && value <= 600) {
dst.pid.dt = value;
changed = true;
}
}
if (!src["pid"]["maxTemp"].isNull()) {
unsigned char value = src["pid"]["maxTemp"].as<unsigned char>();
if (value > 0 && value <= 100 && value > dst.pid.minTemp) {
dst.pid.maxTemp = value;
changed = true;
}
}
if (!src["pid"]["minTemp"].isNull()) {
unsigned char value = src["pid"]["minTemp"].as<unsigned char>();
if (value >= 0 && value < 100 && value < dst.pid.maxTemp) {
dst.pid.minTemp = value;
changed = true;
}
}
// equitherm
if (src["equitherm"]["enable"].is<bool>()) {
dst.equitherm.enable = src["equitherm"]["enable"].as<bool>();
changed = true;
}
if (!src["equitherm"]["n_factor"].isNull()) {
double value = src["equitherm"]["n_factor"].as<double>();
if (value > 0 && value <= 10) {
dst.equitherm.n_factor = roundd(value, 3);
changed = true;
}
}
if (!src["equitherm"]["k_factor"].isNull()) {
double value = src["equitherm"]["k_factor"].as<double>();
if (value >= 0 && value <= 10) {
dst.equitherm.k_factor = roundd(value, 3);
changed = true;
}
}
if (!src["equitherm"]["t_factor"].isNull()) {
double value = src["equitherm"]["t_factor"].as<double>();
if (value >= 0 && value <= 10) {
dst.equitherm.t_factor = roundd(value, 3);
changed = true;
}
}
// sensors // sensors
if (!src["sensors"]["outdoor"]["type"].isNull()) { if (!src["sensors"]["outdoor"]["type"].isNull()) {
unsigned char value = src["sensors"]["outdoor"]["type"].as<unsigned char>(); byte value = src["sensors"]["outdoor"]["type"].as<unsigned char>();
if (value >= 0 && value <= 2) { switch (value) {
dst.sensors.outdoor.type = value; case static_cast<byte>(SensorType::BOILER):
dst.sensors.outdoor.type = SensorType::BOILER;
changed = true;
break;
if (dst.sensors.outdoor.type == 1) { case static_cast<byte>(SensorType::MANUAL):
dst.sensors.outdoor.type = SensorType::MANUAL;
dst.emergency.useEquitherm = false; dst.emergency.useEquitherm = false;
} changed = true;
break;
changed = true; case static_cast<byte>(SensorType::DS18B20):
dst.sensors.outdoor.type = SensorType::DS18B20;
changed = true;
break;
default:
break;
} }
} }
if (!src["sensors"]["outdoor"]["pin"].isNull()) { if (!src["sensors"]["outdoor"]["gpio"].isNull()) {
unsigned char value = src["sensors"]["outdoor"]["pin"].as<unsigned char>(); if (src["sensors"]["outdoor"]["gpio"].is<JsonString>() && src["sensors"]["outdoor"]["gpio"].as<JsonString>().size() == 0) {
if (dst.sensors.outdoor.gpio != GPIO_IS_NOT_CONFIGURED) {
dst.sensors.outdoor.gpio = GPIO_IS_NOT_CONFIGURED;
changed = true;
}
if (value >= 0 && value <= 50) { } else {
dst.sensors.outdoor.pin = value; unsigned char value = src["sensors"]["outdoor"]["gpio"].as<unsigned char>();
changed = true;
if (value >= 0 && value <= 254) {
dst.sensors.outdoor.gpio = value;
changed = true;
}
} }
} }
@@ -808,25 +1018,51 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false
} }
if (!src["sensors"]["indoor"]["type"].isNull()) { if (!src["sensors"]["indoor"]["type"].isNull()) {
unsigned char value = src["sensors"]["indoor"]["type"].as<unsigned char>(); byte value = src["sensors"]["indoor"]["type"].as<unsigned char>();
if (value >= 1 && value <= 3) { switch (value) {
dst.sensors.indoor.type = value; case static_cast<byte>(SensorType::BOILER):
dst.sensors.indoor.type = SensorType::BOILER;
changed = true;
break;
if (dst.sensors.indoor.type == 1) { case static_cast<byte>(SensorType::MANUAL):
dst.sensors.indoor.type = SensorType::MANUAL;
dst.emergency.usePid = false; dst.emergency.usePid = false;
} changed = true;
break;
changed = true; case static_cast<byte>(SensorType::DS18B20):
dst.sensors.indoor.type = SensorType::DS18B20;
changed = true;
break;
#if USE_BLE
case static_cast<byte>(SensorType::BLUETOOTH):
dst.sensors.indoor.type = SensorType::BLUETOOTH;
changed = true;
break;
#endif
default:
break;
} }
} }
if (!src["sensors"]["indoor"]["pin"].isNull()) { if (!src["sensors"]["indoor"]["gpio"].isNull()) {
unsigned char value = src["sensors"]["indoor"]["pin"].as<unsigned char>(); if (src["sensors"]["indoor"]["gpio"].is<JsonString>() && src["sensors"]["indoor"]["gpio"].as<JsonString>().size() == 0) {
if (dst.sensors.indoor.gpio != GPIO_IS_NOT_CONFIGURED) {
dst.sensors.indoor.gpio = GPIO_IS_NOT_CONFIGURED;
changed = true;
}
if (value >= 0 && value <= 50) { } else {
dst.sensors.indoor.pin = value; unsigned char value = src["sensors"]["indoor"]["gpio"].as<unsigned char>();
changed = true;
if (value >= 0 && value <= 254) {
dst.sensors.indoor.gpio = value;
changed = true;
}
} }
} }
@@ -861,12 +1097,20 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false
changed = true; changed = true;
} }
if (!src["externalPump"]["pin"].isNull()) { if (!src["externalPump"]["gpio"].isNull()) {
unsigned char value = src["externalPump"]["pin"].as<unsigned char>(); if (src["externalPump"]["gpio"].is<JsonString>() && src["externalPump"]["gpio"].as<JsonString>().size() == 0) {
if (dst.externalPump.gpio != GPIO_IS_NOT_CONFIGURED) {
dst.externalPump.gpio = GPIO_IS_NOT_CONFIGURED;
changed = true;
}
if (value >= 0 && value <= 50) { } else {
dst.externalPump.pin = value; unsigned char value = src["externalPump"]["gpio"].as<unsigned char>();
changed = true;
if (value >= 0 && value <= 254) {
dst.externalPump.gpio = value;
changed = true;
}
} }
} }
@@ -917,10 +1161,11 @@ void varsToJson(const Variables& src, JsonVariant dst) {
dst["states"]["fault"] = src.states.fault; dst["states"]["fault"] = src.states.fault;
dst["states"]["diagnostic"] = src.states.diagnostic; dst["states"]["diagnostic"] = src.states.diagnostic;
dst["states"]["externalPump"] = src.states.externalPump; dst["states"]["externalPump"] = src.states.externalPump;
dst["states"]["mqtt"] = src.states.mqtt;
dst["sensors"]["modulation"] = roundd(src.sensors.modulation, 2); dst["sensors"]["modulation"] = roundd(src.sensors.modulation, 2);
dst["sensors"]["pressure"] = roundd(src.sensors.pressure, 2); dst["sensors"]["pressure"] = roundd(src.sensors.pressure, 2);
dst["sensors"]["dhwFlowRate"] = src.sensors.dhwFlowRate; dst["sensors"]["dhwFlowRate"] = roundd(src.sensors.dhwFlowRate, 2);
dst["sensors"]["faultCode"] = src.sensors.faultCode; dst["sensors"]["faultCode"] = src.sensors.faultCode;
dst["sensors"]["rssi"] = src.sensors.rssi; dst["sensors"]["rssi"] = src.sensors.rssi;
dst["sensors"]["uptime"] = millis() / 1000ul; dst["sensors"]["uptime"] = millis() / 1000ul;
@@ -928,7 +1173,9 @@ void varsToJson(const Variables& src, JsonVariant dst) {
dst["temperatures"]["indoor"] = roundd(src.temperatures.indoor, 2); dst["temperatures"]["indoor"] = roundd(src.temperatures.indoor, 2);
dst["temperatures"]["outdoor"] = roundd(src.temperatures.outdoor, 2); dst["temperatures"]["outdoor"] = roundd(src.temperatures.outdoor, 2);
dst["temperatures"]["heating"] = roundd(src.temperatures.heating, 2); dst["temperatures"]["heating"] = roundd(src.temperatures.heating, 2);
dst["temperatures"]["heatingReturn"] = roundd(src.temperatures.heatingReturn, 2);
dst["temperatures"]["dhw"] = roundd(src.temperatures.dhw, 2); dst["temperatures"]["dhw"] = roundd(src.temperatures.dhw, 2);
dst["temperatures"]["exhaust"] = roundd(src.temperatures.exhaust, 2);
dst["parameters"]["heatingEnabled"] = src.parameters.heatingEnabled; dst["parameters"]["heatingEnabled"] = src.parameters.heatingEnabled;
dst["parameters"]["heatingMinTemp"] = src.parameters.heatingMinTemp; dst["parameters"]["heatingMinTemp"] = src.parameters.heatingMinTemp;
@@ -936,6 +1183,12 @@ void varsToJson(const Variables& src, JsonVariant dst) {
dst["parameters"]["heatingSetpoint"] = src.parameters.heatingSetpoint; dst["parameters"]["heatingSetpoint"] = src.parameters.heatingSetpoint;
dst["parameters"]["dhwMinTemp"] = src.parameters.dhwMinTemp; dst["parameters"]["dhwMinTemp"] = src.parameters.dhwMinTemp;
dst["parameters"]["dhwMaxTemp"] = src.parameters.dhwMaxTemp; dst["parameters"]["dhwMaxTemp"] = src.parameters.dhwMaxTemp;
dst["parameters"]["slaveMemberId"] = src.parameters.slaveMemberId;
dst["parameters"]["slaveFlags"] = src.parameters.slaveFlags;
dst["parameters"]["slaveType"] = src.parameters.slaveType;
dst["parameters"]["slaveVersion"] = src.parameters.slaveVersion;
dst["parameters"]["slaveOtVersion"] = src.parameters.slaveOtVersion;
} }
bool jsonToVars(const JsonVariantConst src, Variables& dst) { bool jsonToVars(const JsonVariantConst src, Variables& dst) {
@@ -961,7 +1214,7 @@ bool jsonToVars(const JsonVariantConst src, Variables& dst) {
if (!src["temperatures"]["indoor"].isNull()) { if (!src["temperatures"]["indoor"].isNull()) {
double value = src["temperatures"]["indoor"].as<double>(); double value = src["temperatures"]["indoor"].as<double>();
if (settings.sensors.indoor.type == 1 && value > -100 && value < 100) { if (settings.sensors.indoor.type == SensorType::MANUAL && isValidTemp(value, settings.system.unitSystem, -99.9f, 99.9f)) {
dst.temperatures.indoor = roundd(value, 2); dst.temperatures.indoor = roundd(value, 2);
changed = true; changed = true;
} }
@@ -970,7 +1223,7 @@ bool jsonToVars(const JsonVariantConst src, Variables& dst) {
if (!src["temperatures"]["outdoor"].isNull()) { if (!src["temperatures"]["outdoor"].isNull()) {
double value = src["temperatures"]["outdoor"].as<double>(); double value = src["temperatures"]["outdoor"].as<double>();
if (settings.sensors.outdoor.type == 1 && value > -100 && value < 100) { if (settings.sensors.outdoor.type == SensorType::MANUAL && isValidTemp(value, settings.system.unitSystem, -99.9f, 99.9f)) {
dst.temperatures.outdoor = roundd(value, 2); dst.temperatures.outdoor = roundd(value, 2);
changed = true; changed = true;
} }

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);
@@ -75,6 +117,80 @@ 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;
} }

View File

@@ -1,4 +1,4 @@
function setupForm(formSelector) { function setupForm(formSelector, onResultCallback = null) {
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');
@@ -71,15 +71,20 @@ function setupForm(formSelector) {
body: form2json(fd) body: form2json(fd)
}); });
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();
} }
}); });
} }
@@ -410,6 +415,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 +445,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 +467,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,15 +497,105 @@ 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;
}
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",
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) { function form2json(data) {
let method = function (object, pair) { let method = function (object, pair) {

File diff suppressed because one or more lines are too long