mirror of
https://github.com/Laxilef/OTGateway.git
synced 2025-12-12 03:04:27 +05:00
Compare commits
41 Commits
1.4.0-rc.1
...
1.4.0-rc.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f4a4afeb29 | ||
|
|
f9824337dc | ||
|
|
1cd8c6a336 | ||
|
|
63228baebd | ||
|
|
6bb261dfd7 | ||
|
|
a026a962f0 | ||
|
|
db2faad741 | ||
|
|
fbc43dc535 | ||
|
|
31dfc21d69 | ||
|
|
96289cb0f7 | ||
|
|
73da3ee07a | ||
|
|
a14281924f | ||
|
|
3dec390cce | ||
|
|
9a29819d4f | ||
|
|
2af159d566 | ||
|
|
92ca257d32 | ||
|
|
44b6620431 | ||
|
|
b89f61ed58 | ||
|
|
ab1566bd45 | ||
|
|
86734ab622 | ||
|
|
0a8dd2a076 | ||
|
|
a7a561622e | ||
|
|
b0e0f6fd7d | ||
|
|
53eaa1d7f1 | ||
|
|
a7d796e0cc | ||
|
|
4490b38130 | ||
|
|
0ede2240a2 | ||
|
|
0cff35ee12 | ||
|
|
14aef20234 | ||
|
|
560f8fbd51 | ||
|
|
946414ad31 | ||
|
|
39a29042e1 | ||
|
|
f544f01caa | ||
|
|
41cca76bfa | ||
|
|
942bc53043 | ||
|
|
1bad689b6b | ||
|
|
2f4dbcc205 | ||
|
|
9e3ef7a465 | ||
|
|
a5f6749101 | ||
|
|
b07dd46f55 | ||
|
|
07ab121788 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,5 +1,6 @@
|
||||
.pio
|
||||
.vscode
|
||||
build/*.bin
|
||||
data/**/*.gz
|
||||
secrets.ini
|
||||
!.gitkeep
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||

|
||||
<br>
|
||||
[](https://github.com/Laxilef/OTGateway/releases)
|
||||
[](https://github.com/Laxilef/OTGateway/releases)
|
||||
[](https://github.com/Laxilef/OTGateway/releases/latest)
|
||||
[](LICENSE.txt)
|
||||
[](https://t.me/otgateway)
|
||||
|
||||
0
data/.gitkeep
Normal file
0
data/.gitkeep
Normal file
230
data/index.html
230
data/index.html
@@ -1,230 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>OpenTherm Gateway</title>
|
||||
<link rel="stylesheet" href="/static/pico.min.css">
|
||||
<link rel="stylesheet" href="/static/app.css"/>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<header class="container">
|
||||
<nav>
|
||||
<ul>
|
||||
<li><a href="/"><div class="logo">OpenTherm Gateway</div></a></li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li><a href="https://github.com/Laxilef/OTGateway/wiki" role="button" class="secondary" target="_blank">Help</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main class="container">
|
||||
<article>
|
||||
<div>
|
||||
<hgroup>
|
||||
<h2>Network</h2>
|
||||
<p></p>
|
||||
</hgroup>
|
||||
|
||||
<div class="main-busy" aria-busy="true"></div>
|
||||
<table class="main-table hidden">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="row">Hostname:</th>
|
||||
<td><b class="network-hostname"></b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">MAC:</th>
|
||||
<td><b class="network-mac"></b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Connected:</th>
|
||||
<td><input type="radio" class="network-connected" aria-invalid="false" checked disabled /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">SSID:</th>
|
||||
<td><b class="network-ssid"></b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Signal:</th>
|
||||
<td><b class="network-signal"></b> %</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">IP:</th>
|
||||
<td><b class="network-ip"></b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Subnet:</th>
|
||||
<td><b class="network-subnet"></b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Gateway:</th>
|
||||
<td><b class="network-gateway"></b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">DNS:</th>
|
||||
<td><b class="network-dns"></b></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="grid">
|
||||
<a href="/network.html" role="button">Network settings</a>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article>
|
||||
<div>
|
||||
<hgroup>
|
||||
<h2>System</h2>
|
||||
<p></p>
|
||||
</hgroup>
|
||||
|
||||
<div class="system-busy" aria-busy="true"></div>
|
||||
<table class="system-table hidden">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="row">Version:</th>
|
||||
<td><b class="version"></b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Build date:</th>
|
||||
<td><b class="build-date"></b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Uptime:</th>
|
||||
<td><b class="uptime-days"></b> days, <b class="uptime-hours"></b> hours, <b class="uptime-min"></b> min., <b class="uptime-sec"></b> sec.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Free memory:</th>
|
||||
<td><b class="free-heap"></b> of <b class="total-heap"></b> bytes (min: <b class="min-free-heap"></b> bytes)<br>max free block: <b class="max-free-block-heap"></b> bytes (min: <b class="min-max-free-block-heap"></b> bytes)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Last reset reason:</th>
|
||||
<td><b class="reset-reason"></b></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="grid">
|
||||
<a href="/settings.html" role="button">Settings</a>
|
||||
<a href="/upgrade.html" role="button">Upgrade</a>
|
||||
<a href="/restart.html" role="button" class="secondary restart">Restart</a>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article>
|
||||
<div>
|
||||
<hgroup>
|
||||
<h2>States and sensors</h2>
|
||||
<p>More information and settings can be found in your home assistant after setting up network and MQTT</p>
|
||||
</hgroup>
|
||||
|
||||
<div class="ot-busy" aria-busy="true"></div>
|
||||
<table class="ot-table hidden">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="row">OpenTherm connected:</th>
|
||||
<td><input type="radio" class="ot-connected" aria-invalid="false" checked disabled /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">MQTT connected:</th>
|
||||
<td><input type="radio" class="mqtt-connected" aria-invalid="false" checked disabled /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Emergency:</th>
|
||||
<td><input type="radio" class="ot-emergency" aria-invalid="false" checked disabled /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Heating:</th>
|
||||
<td><input type="radio" class="ot-heating" aria-invalid="false" checked disabled /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">DHW:</th>
|
||||
<td><input type="radio" class="ot-dhw" aria-invalid="false" checked disabled /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Flame:</th>
|
||||
<td><input type="radio" class="ot-flame" aria-invalid="false" checked disabled /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Fault:</th>
|
||||
<td><input type="radio" class="ot-fault" aria-invalid="false" checked disabled /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Diagnostic:</th>
|
||||
<td><input type="radio" class="ot-diagnostic" aria-invalid="false" checked disabled /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">External pump:</th>
|
||||
<td><input type="radio" class="ot-external-pump" aria-invalid="false" checked disabled /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Modulation:</th>
|
||||
<td><b class="ot-modulation"></b> %</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Pressure:</th>
|
||||
<td><b class="ot-pressure"></b> bar</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">DHW flow rate:</th>
|
||||
<td><b class="ot-dhw-flow-rate"></b> l/min</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Fault code:</th>
|
||||
<td><b class="ot-fault-code"></b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Indoor temp:</th>
|
||||
<td><b class="indoor-temp"></b> C</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Outdoor temp:</th>
|
||||
<td><b class="outdoor-temp"></b> C</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Heating temp:</th>
|
||||
<td><b class="heating-temp"></b> C</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Heating setpoint temp:</th>
|
||||
<td><b class="heating-setpoint-temp"></b> C</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">DHW temp:</th>
|
||||
<td><b class="dhw-temp"></b> C</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</article>
|
||||
</main>
|
||||
|
||||
<footer class="container">
|
||||
<small>
|
||||
<b>Made by Laxilef</b>
|
||||
• <a href="https://github.com/Laxilef/OTGateway/blob/master/LICENSE" target="_blank" class="secondary">License</a>
|
||||
• <a href="https://github.com/Laxilef/OTGateway/blob/master/" target="_blank" class="secondary">Source code</a>
|
||||
• <a href="https://github.com/Laxilef/OTGateway/wiki" target="_blank" class="secondary">Help</a>
|
||||
• <a href="https://github.com/Laxilef/OTGateway/issues" target="_blank" class="secondary">Issue & questions</a>
|
||||
• <a href="https://github.com/Laxilef/OTGateway/releases" target="_blank" class="secondary">Releases</a>
|
||||
</small>
|
||||
</footer>
|
||||
|
||||
<script src="/static/app.js"></script>
|
||||
<script>
|
||||
window.onload = async function () {
|
||||
setTimeout(async function onLoadPage() {
|
||||
await loadNetworkStatus();
|
||||
await loadVars();
|
||||
|
||||
setTimeout(onLoadPage, 10000);
|
||||
}, 1000);
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,357 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Settings - OpenTherm Gateway</title>
|
||||
<link rel="stylesheet" href="/static/pico.min.css">
|
||||
<link rel="stylesheet" href="/static/app.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<header class="container">
|
||||
<nav>
|
||||
<ul>
|
||||
<li><a href="/"><div class="logo">OpenTherm Gateway</div></a></li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li><a href="https://github.com/Laxilef/OTGateway/wiki" role="button" class="secondary" target="_blank">Help</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main class="container">
|
||||
<article>
|
||||
<div>
|
||||
<hgroup>
|
||||
<h2>Portal settings</h2>
|
||||
<p></p>
|
||||
</hgroup>
|
||||
|
||||
<div id="portal-settings-busy" aria-busy="true"></div>
|
||||
<form action="/api/settings" id="portal-settings" class="hidden">
|
||||
<div class="grid">
|
||||
<label for="portal-login">
|
||||
Login
|
||||
<input type="text" class="portal-login" name="portal[login]" maxlength="12" required>
|
||||
</label>
|
||||
<label for="portal-password">
|
||||
Password
|
||||
<input type="password" class="portal-password" name="portal[password]" maxlength="32" required>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<label for="portal-use-auth">
|
||||
<input type="checkbox" class="portal-use-auth" name="portal[useAuth]" value="true">
|
||||
Use auth
|
||||
</label>
|
||||
<br>
|
||||
|
||||
<button type="submit">Save</button>
|
||||
</form>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article>
|
||||
<div>
|
||||
<hgroup>
|
||||
<h2>OpenTherm settings</h2>
|
||||
<p></p>
|
||||
</hgroup>
|
||||
|
||||
<div id="opentherm-settings-busy" aria-busy="true"></div>
|
||||
<form action="/api/settings" id="opentherm-settings" class="hidden">
|
||||
<div class="grid">
|
||||
<label for="opentherm-in-pin">
|
||||
In GPIO
|
||||
<input type="number" inputmode="numeric" class="opentherm-in-pin" name="opentherm[inPin]" min="0" max="99" step="1" required>
|
||||
</label>
|
||||
<label for="opentherm-in-pin">
|
||||
Out GPIO
|
||||
<input type="number" inputmode="numeric" class="opentherm-out-pin" name="opentherm[outPin]" min="0" max="99" step="1" required>
|
||||
</label>
|
||||
<label for="opentherm-member-id-code">
|
||||
Master MemberID code
|
||||
<input type="number" inputmode="numeric" class="opentherm-member-id-code" name="opentherm[memberIdCode]" min="0" max="65535" step="1" required>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<fieldset>
|
||||
<mark>After changing GPIO, the ESP must be restarted for the changes to take effect.</mark>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<legend>Options</legend>
|
||||
<label for="opentherm-dhw-present">
|
||||
<input type="checkbox" class="opentherm-dhw-present" name="opentherm[dhwPresent]" value="true">
|
||||
DHW present
|
||||
</label>
|
||||
<label for="opentherm-sw-mode">
|
||||
<input type="checkbox" class="opentherm-sw-mode" name="opentherm[summerWinterMode]" value="true">
|
||||
Summer/winter mode
|
||||
</label>
|
||||
<label for="opentherm-heating-ch2-enabled">
|
||||
<input type="checkbox" class="opentherm-heating-ch2-enabled" name="opentherm[heatingCh2Enabled]" value="true">
|
||||
Heating CH2 always enabled
|
||||
</label>
|
||||
<label for="opentherm-heating-ch1-to-ch2">
|
||||
<input type="checkbox" class="opentherm-heating-ch1-to-ch2" name="opentherm[heatingCh1ToCh2]" value="true">
|
||||
Duplicate heating CH1 to CH2
|
||||
</label>
|
||||
<label for="opentherm-dhw-to-ch2">
|
||||
<input type="checkbox" class="opentherm-dhw-to-ch2" name="opentherm[dhwToCh2]" value="true">
|
||||
Duplicate DHW to CH2
|
||||
</label>
|
||||
<label for="opentherm-dhw-blocking">
|
||||
<input type="checkbox" class="opentherm-dhw-blocking" name="opentherm[dhwBlocking]" value="true">
|
||||
DHW blocking
|
||||
</label>
|
||||
<label for="opentherm-sync-modulation-with-heating">
|
||||
<input type="checkbox" class="opentherm-sync-modulation-with-heating" name="opentherm[modulationSyncWithHeating]" value="true">
|
||||
Sync modulation with heating
|
||||
</label>
|
||||
</fieldset>
|
||||
|
||||
<button type="submit">Save</button>
|
||||
</form>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article>
|
||||
<div>
|
||||
<hgroup>
|
||||
<h2>MQTT settings</h2>
|
||||
<p></p>
|
||||
</hgroup>
|
||||
|
||||
<div id="mqtt-settings-busy" aria-busy="true"></div>
|
||||
<form action="/api/settings" id="mqtt-settings" class="hidden">
|
||||
<div class="grid">
|
||||
<label for="mqtt-server">
|
||||
Server
|
||||
<input type="text" class="mqtt-server" name="mqtt[server]" maxlength="80" required>
|
||||
</label>
|
||||
<label for="mqtt-port">
|
||||
Port
|
||||
<input type="number" inputmode="numeric" class="mqtt-port" name="mqtt[port]" min="1" max="65535" step="1" required>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="grid">
|
||||
<label for="mqtt-user">
|
||||
User
|
||||
<input type="text" class="mqtt-user" name="mqtt[user]" maxlength="32" required>
|
||||
</label>
|
||||
<label for="mqtt-password">
|
||||
Password
|
||||
<input type="password" class="mqtt-password" name="mqtt[password]" maxlength="32">
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="grid">
|
||||
<label for="mqtt-prefix">
|
||||
Prefix
|
||||
<input type="text" class="mqtt-prefix" name="mqtt[prefix]" maxlength="32" required>
|
||||
</label>
|
||||
<label for="mqtt-interval">
|
||||
Publish interval <small>(sec)</small>
|
||||
<input type="number" inputmode="numeric" class="mqtt-interval" name="mqtt[interval]" min="3" max="60" step="1" required>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<button type="submit">Save</button>
|
||||
</form>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article>
|
||||
<div>
|
||||
<hgroup>
|
||||
<h2>Outdoor sensor settings</h2>
|
||||
<p></p>
|
||||
</hgroup>
|
||||
|
||||
<div id="outdoor-sensor-settings-busy" aria-busy="true"></div>
|
||||
<form action="/api/settings" id="outdoor-sensor-settings" class="hidden">
|
||||
<fieldset>
|
||||
<legend>Source type</legend>
|
||||
<label>
|
||||
<input type="radio" class="outdoor-sensor-type" name="sensors[outdoor][type]" value="0" />
|
||||
From boiler via OpenTherm
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" class="outdoor-sensor-type" name="sensors[outdoor][type]" value="1" />
|
||||
Manual via MQTT/API
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" class="outdoor-sensor-type" name="sensors[outdoor][type]" value="2" />
|
||||
External (DS18B20)
|
||||
</label>
|
||||
</fieldset>
|
||||
|
||||
<label for="outdoor-sensor-pin">
|
||||
GPIO
|
||||
<input type="number" inputmode="numeric" class="outdoor-sensor-pin" name="sensors[outdoor][pin]" min="0" max="99" step="1" required>
|
||||
</label>
|
||||
<label for="outdoor-sensor-offset">
|
||||
Temp offset (calibration)
|
||||
<input type="number" inputmode="numeric" class="outdoor-sensor-offset" name="sensors[outdoor][offset]" min="-20" max="20" step="0.01" required>
|
||||
</label>
|
||||
|
||||
<button type="submit">Save</button>
|
||||
</form>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article>
|
||||
<div>
|
||||
<hgroup>
|
||||
<h2>Indoor sensor settings</h2>
|
||||
<p></p>
|
||||
</hgroup>
|
||||
|
||||
<div id="indoor-sensor-settings-busy" aria-busy="true"></div>
|
||||
<form action="/api/settings" id="indoor-sensor-settings" class="hidden">
|
||||
<fieldset>
|
||||
<legend>Source type</legend>
|
||||
<label>
|
||||
<input type="radio" class="indoor-sensor-type" name="sensors[indoor][type]" value="1" />
|
||||
Manual via MQTT/API
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" class="indoor-sensor-type" name="sensors[indoor][type]" value="2" />
|
||||
External (DS18B20)
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" class="indoor-sensor-type" name="sensors[indoor][type]" value="3" />
|
||||
BLE device <i>(ONLY for some ESP32 which support BLE)</i>
|
||||
</label>
|
||||
</fieldset>
|
||||
|
||||
<label for="indoor-sensor-pin">
|
||||
GPIO
|
||||
<input type="number" inputmode="numeric" class="indoor-sensor-pin" name="sensors[indoor][pin]" min="0" max="99" step="1" required>
|
||||
</label>
|
||||
|
||||
<div class="grid">
|
||||
<label for="indoor-sensor-offset">
|
||||
Temp offset (calibration)
|
||||
<input type="number" inputmode="numeric" class="indoor-sensor-offset" name="sensors[indoor][offset]" min="-20" max="20" step="0.01" required>
|
||||
</label>
|
||||
<label for="indoor-sensor-ble-addresss">
|
||||
BLE addresss
|
||||
<input type="text" class="indoor-sensor-ble-addresss" name="sensors[indoor][bleAddresss]" pattern="([A-Fa-f0-9]{2}:){5}[A-Fa-f0-9]{2}">
|
||||
<small>ONLY for some ESP32 which support BLE</small>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<button type="submit">Save</button>
|
||||
</form>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article>
|
||||
<div>
|
||||
<hgroup>
|
||||
<h2>External pump settings</h2>
|
||||
<p></p>
|
||||
</hgroup>
|
||||
|
||||
<div id="extpump-settings-busy" aria-busy="true"></div>
|
||||
<form action="/api/settings" id="extpump-settings" class="hidden">
|
||||
<label for="extpump-use">
|
||||
<input type="checkbox" class="extpump-use" name="externalPump[use]" value="true">
|
||||
Use external pump
|
||||
</label>
|
||||
<br>
|
||||
|
||||
<div class="grid">
|
||||
<label for="extpump-pin">
|
||||
Relay GPIO
|
||||
<input type="number" inputmode="numeric" class="extpump-pin" name="externalPump[pin]" min="0" max="99" step="1" required>
|
||||
</label>
|
||||
<label for="extpump-pc-time">
|
||||
Post circulation time <small>(min)</small>
|
||||
<input type="number" inputmode="numeric" class="extpump-pc-time" name="externalPump[postCirculationTime]" min="1" max="120" step="1" required>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="grid">
|
||||
<label for="extpump-as-interval">
|
||||
Anti stuck interval <small>(days)</small>
|
||||
<input type="number" inputmode="numeric" class="extpump-as-interval" name="externalPump[antiStuckInterval]" min="1" max="366" step="1" required>
|
||||
</label>
|
||||
<label for="extpump-as-time">
|
||||
Anti stuck time <small>(min)</small>
|
||||
<input type="number" inputmode="numeric" class="extpump-as-time" name="externalPump[antiStuckTime]" min="1" max="20" step="1" required>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<button type="submit">Save</button>
|
||||
</form>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article>
|
||||
<div>
|
||||
<hgroup>
|
||||
<h2>System settings</h2>
|
||||
<p></p>
|
||||
</hgroup>
|
||||
|
||||
<div id="system-settings-busy" aria-busy="true"></div>
|
||||
<form action="/api/settings" id="system-settings" class="hidden">
|
||||
<fieldset>
|
||||
<label for="system-debug">
|
||||
<input type="checkbox" class="system-debug" name="system[debug]" value="true">
|
||||
Debug mode
|
||||
</label>
|
||||
<label for="system-use-serial">
|
||||
<input type="checkbox" class="system-use-serial" name="system[useSerial]" value="true">
|
||||
Enable serial port
|
||||
</label>
|
||||
<label for="system-use-telnet">
|
||||
<input type="checkbox" class="system-use-telnet" name="system[useTelnet]" value="true">
|
||||
Enable telnet
|
||||
</label>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<mark>After changing this settings, the ESP must be restarted for the changes to take effect.</mark>
|
||||
</fieldset>
|
||||
|
||||
<button type="submit">Save</button>
|
||||
</form>
|
||||
</div>
|
||||
</article>
|
||||
</main>
|
||||
|
||||
<footer class="container">
|
||||
<small>
|
||||
<b>Made by Laxilef</b>
|
||||
• <a href="https://github.com/Laxilef/OTGateway/blob/master/LICENSE" target="_blank" class="secondary">License</a>
|
||||
• <a href="https://github.com/Laxilef/OTGateway/blob/master/" target="_blank" class="secondary">Source code</a>
|
||||
• <a href="https://github.com/Laxilef/OTGateway/wiki" target="_blank" class="secondary">Help</a>
|
||||
• <a href="https://github.com/Laxilef/OTGateway/issues" target="_blank" class="secondary">Issue & questions</a>
|
||||
• <a href="https://github.com/Laxilef/OTGateway/releases" target="_blank" class="secondary">Releases</a>
|
||||
</small>
|
||||
</footer>
|
||||
|
||||
<script src="/static/app.js"></script>
|
||||
<script>
|
||||
window.onload = async function () {
|
||||
await loadSettings();
|
||||
|
||||
setupForm('#portal-settings');
|
||||
setupForm('#opentherm-settings');
|
||||
setupForm('#mqtt-settings');
|
||||
setupForm('#outdoor-sensor-settings');
|
||||
setupForm('#indoor-sensor-settings');
|
||||
setupForm('#extpump-settings');
|
||||
setupForm('#system-settings');
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
0
data/static/.gitkeep
Normal file
0
data/static/.gitkeep
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -8,6 +8,7 @@ public:
|
||||
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() {}
|
||||
|
||||
CustomOpenTherm* setYieldCallback(YieldCallback callback = nullptr) {
|
||||
this->yieldCallback = callback;
|
||||
@@ -46,7 +47,7 @@ public:
|
||||
|
||||
unsigned long _response;
|
||||
OpenThermResponseStatus _responseStatus = OpenThermResponseStatus::NONE;
|
||||
if (!this->sendRequestAync(request)) {
|
||||
if (!this->sendRequestAsync(request)) {
|
||||
_response = 0;
|
||||
|
||||
} else {
|
||||
@@ -88,7 +89,7 @@ public:
|
||||
| (dhwBlocking << 6);
|
||||
data <<= 8;
|
||||
|
||||
return this->sendRequest(this->buildRequest(
|
||||
return this->sendRequest(buildRequest(
|
||||
OpenThermMessageType::READ_DATA,
|
||||
OpenThermMessageID::Status,
|
||||
data
|
||||
@@ -96,30 +97,30 @@ public:
|
||||
}
|
||||
|
||||
bool setHeatingCh1Temp(float temperature) {
|
||||
unsigned long response = this->sendRequest(this->buildRequest(
|
||||
unsigned long response = this->sendRequest(buildRequest(
|
||||
OpenThermMessageType::WRITE_DATA,
|
||||
OpenThermMessageID::TSet,
|
||||
this->temperatureToData(temperature)
|
||||
temperatureToData(temperature)
|
||||
));
|
||||
|
||||
return isValidResponse(response);
|
||||
}
|
||||
|
||||
bool setHeatingCh2Temp(float temperature) {
|
||||
unsigned long response = this->sendRequest(this->buildRequest(
|
||||
unsigned long response = this->sendRequest(buildRequest(
|
||||
OpenThermMessageType::WRITE_DATA,
|
||||
OpenThermMessageID::TsetCH2,
|
||||
this->temperatureToData(temperature)
|
||||
temperatureToData(temperature)
|
||||
));
|
||||
|
||||
return isValidResponse(response);
|
||||
}
|
||||
|
||||
bool setDhwTemp(float temperature) {
|
||||
unsigned long response = this->sendRequest(this->buildRequest(
|
||||
unsigned long response = this->sendRequest(buildRequest(
|
||||
OpenThermMessageType::WRITE_DATA,
|
||||
OpenThermMessageID::TdhwSet,
|
||||
this->temperatureToData(temperature)
|
||||
temperatureToData(temperature)
|
||||
));
|
||||
|
||||
return isValidResponse(response);
|
||||
@@ -128,9 +129,9 @@ public:
|
||||
bool sendBoilerReset() {
|
||||
unsigned int data = 1;
|
||||
data <<= 8;
|
||||
unsigned long response = this->sendRequest(this->buildRequest(
|
||||
unsigned long response = this->sendRequest(buildRequest(
|
||||
OpenThermMessageType::WRITE_DATA,
|
||||
OpenThermMessageID::Command,
|
||||
OpenThermMessageID::RemoteRequest,
|
||||
data
|
||||
));
|
||||
|
||||
@@ -140,9 +141,9 @@ public:
|
||||
bool sendServiceReset() {
|
||||
unsigned int data = 10;
|
||||
data <<= 8;
|
||||
unsigned long response = this->sendRequest(this->buildRequest(
|
||||
unsigned long response = this->sendRequest(buildRequest(
|
||||
OpenThermMessageType::WRITE_DATA,
|
||||
OpenThermMessageID::Command,
|
||||
OpenThermMessageID::RemoteRequest,
|
||||
data
|
||||
));
|
||||
|
||||
@@ -152,9 +153,9 @@ public:
|
||||
bool sendWaterFilling() {
|
||||
unsigned int data = 2;
|
||||
data <<= 8;
|
||||
unsigned long response = this->sendRequest(this->buildRequest(
|
||||
unsigned long response = this->sendRequest(buildRequest(
|
||||
OpenThermMessageType::WRITE_DATA,
|
||||
OpenThermMessageID::Command,
|
||||
OpenThermMessageID::RemoteRequest,
|
||||
data
|
||||
));
|
||||
|
||||
@@ -162,24 +163,13 @@ public:
|
||||
}
|
||||
|
||||
// converters
|
||||
float fromF88(unsigned long response) {
|
||||
const byte valueLB = response & 0xFF;
|
||||
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) {
|
||||
template <class T>
|
||||
static unsigned int toFloat(const T val) {
|
||||
return (unsigned int)(val * 256);
|
||||
}
|
||||
|
||||
int16_t fromS16(unsigned long response) {
|
||||
const byte valueLB = response & 0xFF;
|
||||
const byte valueHB = (response >> 8) & 0xFF;
|
||||
|
||||
int16_t value = valueHB;
|
||||
return ((value << 8) + valueLB);
|
||||
static short getInt(const unsigned long response) {
|
||||
return response & 0xffff;
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
@@ -33,6 +33,8 @@ const char HA_AVAILABILITY_MODE[] PROGMEM = "availability_mode";
|
||||
const char HA_TOPIC[] PROGMEM = "topic";
|
||||
const char HA_DEVICE_CLASS[] PROGMEM = "device_class";
|
||||
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_MIN[] PROGMEM = "min";
|
||||
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_STATE_TOPIC[] PROGMEM = "temperature_state_topic";
|
||||
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_TEMPLATE[] PROGMEM = "mode_command_template";
|
||||
const char HA_MODE_STATE_TOPIC[] PROGMEM = "mode_state_topic";
|
||||
|
||||
@@ -67,7 +67,7 @@ namespace Network {
|
||||
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->staticGateway = gateway;
|
||||
this->staticSubnet = subnet;
|
||||
@@ -181,7 +181,12 @@ namespace Network {
|
||||
wifi_station_dhcpc_set_maxtry(5);
|
||||
#endif
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
// Nothing. Because memory leaks when turn off WiFi on ESP32, bug?
|
||||
return true;
|
||||
#else
|
||||
return WiFi.mode(WIFI_OFF);
|
||||
#endif
|
||||
}
|
||||
|
||||
void reconnect() {
|
||||
@@ -328,12 +333,12 @@ namespace Network {
|
||||
} else if (!this->isConnecting() && this->hasStaCredentials() && (!this->prevReconnectingTime || millis() - this->prevReconnectingTime > this->reconnectInterval)) {
|
||||
Log.sinfoln(FPSTR(L_NETWORK), F("Try connect..."));
|
||||
|
||||
this->reconnectFlag = false;
|
||||
this->reconnectFlag = false;
|
||||
Connection::reset();
|
||||
if (!this->connect(true, this->connectionTimeout)) {
|
||||
Log.straceln(FPSTR(L_NETWORK), F("Connection failed. Status: %d, reason: %d"), Connection::getStatus(), Connection::getDisconnectReason());
|
||||
}
|
||||
|
||||
|
||||
this->prevReconnectingTime = millis();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
#include <FS.h>
|
||||
#include <detail/mimetable.h>
|
||||
|
||||
using namespace mime;
|
||||
|
||||
class StaticPage : public RequestHandler {
|
||||
public:
|
||||
@@ -61,6 +64,14 @@ public:
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!this->path.endsWith(FPSTR(mimeTable[gz].endsWith)) && !this->fs->exists(path)) {
|
||||
String pathWithGz = this->path + FPSTR(mimeTable[gz].endsWith);
|
||||
|
||||
if (this->fs->exists(pathWithGz)) {
|
||||
this->path += FPSTR(mimeTable[gz].endsWith);
|
||||
}
|
||||
}
|
||||
|
||||
File file = this->fs->open(this->path, "r");
|
||||
if (!file) {
|
||||
return false;
|
||||
@@ -93,6 +104,6 @@ protected:
|
||||
BeforeSendCallback beforeSendCallback;
|
||||
String eTag;
|
||||
const char* uri = nullptr;
|
||||
const char* path = nullptr;
|
||||
String path;
|
||||
const char* cacheHeader = nullptr;
|
||||
};
|
||||
@@ -174,7 +174,7 @@ public:
|
||||
Log.serrorln(
|
||||
FPSTR(L_PORTAL_OTA),
|
||||
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 {
|
||||
|
||||
137
platformio.ini
137
platformio.ini
@@ -15,9 +15,9 @@ extra_configs = secrets.default.ini
|
||||
[env]
|
||||
framework = arduino
|
||||
lib_deps =
|
||||
bblanchon/ArduinoJson@^7.0.3
|
||||
;ihormelnyk/OpenTherm Library@^1.1.4
|
||||
https://github.com/Laxilef/opentherm_library/archive/refs/heads/fix_start_bit.zip
|
||||
bblanchon/ArduinoJson@^7.0.4
|
||||
;ihormelnyk/OpenTherm Library@^1.1.5
|
||||
https://github.com/Laxilef/opentherm_library/archive/refs/heads/fix_lambda.zip
|
||||
arduino-libraries/ArduinoMqttClient@^0.1.8
|
||||
lennarthennigs/ESP Telnet@^2.2
|
||||
gyverlibs/FileData@^1.0.2
|
||||
@@ -30,44 +30,43 @@ build_flags =
|
||||
-D PIO_FRAMEWORK_ARDUINO_ESPRESSIF_SDK305
|
||||
-mtext-section-literals
|
||||
-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_TELNET=${secrets.use_telnet}
|
||||
-D DEBUG_BY_DEFAULT=${secrets.debug}
|
||||
-D HOSTNAME_DEFAULT='"${secrets.hostname}"'
|
||||
-D AP_SSID_DEFAULT='"${secrets.ap_ssid}"'
|
||||
-D AP_PASSWORD_DEFAULT='"${secrets.ap_password}"'
|
||||
-D STA_SSID_DEFAULT='"${secrets.sta_ssid}"'
|
||||
-D STA_PASSWORD_DEFAULT='"${secrets.sta_password}"'
|
||||
-D PORTAL_LOGIN_DEFAULT='"${secrets.portal_login}"'
|
||||
-D PORTAL_PASSWORD_DEFAULT='"${secrets.portal_password}"'
|
||||
-D MQTT_SERVER_DEFAULT='"${secrets.mqtt_server}"'
|
||||
-D MQTT_PORT_DEFAULT=${secrets.mqtt_port}
|
||||
-D MQTT_USER_DEFAULT='"${secrets.mqtt_user}"'
|
||||
-D MQTT_PASSWORD_DEFAULT='"${secrets.mqtt_password}"'
|
||||
-D MQTT_PREFIX_DEFAULT='"${secrets.mqtt_prefix}"'
|
||||
-D DEFAULT_HOSTNAME='"${secrets.hostname}"'
|
||||
-D DEFAULT_AP_SSID='"${secrets.ap_ssid}"'
|
||||
-D DEFAULT_AP_PASSWORD='"${secrets.ap_password}"'
|
||||
-D DEFAULT_STA_SSID='"${secrets.sta_ssid}"'
|
||||
-D DEFAULT_STA_PASSWORD='"${secrets.sta_password}"'
|
||||
-D DEFAULT_PORTAL_LOGIN='"${secrets.portal_login}"'
|
||||
-D DEFAULT_PORTAL_PASSWORD='"${secrets.portal_password}"'
|
||||
-D DEFAULT_MQTT_SERVER='"${secrets.mqtt_server}"'
|
||||
-D DEFAULT_MQTT_PORT=${secrets.mqtt_port}
|
||||
-D DEFAULT_MQTT_USER='"${secrets.mqtt_user}"'
|
||||
-D DEFAULT_MQTT_PASSWORD='"${secrets.mqtt_password}"'
|
||||
-D DEFAULT_MQTT_PREFIX='"${secrets.mqtt_prefix}"'
|
||||
upload_speed = 921600
|
||||
monitor_speed = 115200
|
||||
monitor_filters = direct
|
||||
board_build.flash_mode = dio
|
||||
board_build.filesystem = littlefs
|
||||
version = 1.4.0-rc.16
|
||||
version = 1.4.0-rc.22
|
||||
|
||||
; Defaults
|
||||
[esp8266_defaults]
|
||||
platform = espressif8266
|
||||
lib_deps =
|
||||
${env.lib_deps}
|
||||
nrwiersma/ESP8266Scheduler@^1.1
|
||||
nrwiersma/ESP8266Scheduler@^1.2
|
||||
lib_ignore =
|
||||
extra_scripts =
|
||||
post:tools/build.py
|
||||
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]
|
||||
platform = espressif32
|
||||
platform = espressif32@^6.6
|
||||
board_build.partitions = esp32_partitions.csv
|
||||
lib_deps =
|
||||
${env.lib_deps}
|
||||
@@ -91,12 +90,12 @@ extra_scripts = ${esp8266_defaults.extra_scripts}
|
||||
board_build.ldscript = ${esp8266_defaults.board_build.ldscript}
|
||||
build_flags =
|
||||
${esp8266_defaults.build_flags}
|
||||
-D OT_IN_PIN_DEFAULT=4
|
||||
-D OT_OUT_PIN_DEFAULT=5
|
||||
-D SENSOR_OUTDOOR_PIN_DEFAULT=12
|
||||
-D SENSOR_INDOOR_PIN_DEFAULT=14
|
||||
-D LED_STATUS_PIN=13
|
||||
-D LED_OT_RX_PIN=15
|
||||
-D DEFAULT_OT_IN_GPIO=4
|
||||
-D DEFAULT_OT_OUT_GPIO=5
|
||||
-D DEFAULT_SENSOR_OUTDOOR_GPIO=12
|
||||
-D DEFAULT_SENSOR_INDOOR_GPIO=14
|
||||
-D DEFAULT_STATUS_LED_GPIO=13
|
||||
-D DEFAULT_OT_RX_LED_GPIO=15
|
||||
|
||||
[env:d1_mini_lite]
|
||||
platform = ${esp8266_defaults.platform}
|
||||
@@ -107,12 +106,12 @@ extra_scripts = ${esp8266_defaults.extra_scripts}
|
||||
board_build.ldscript = ${esp8266_defaults.board_build.ldscript}
|
||||
build_flags =
|
||||
${esp8266_defaults.build_flags}
|
||||
-D OT_IN_PIN_DEFAULT=4
|
||||
-D OT_OUT_PIN_DEFAULT=5
|
||||
-D SENSOR_OUTDOOR_PIN_DEFAULT=12
|
||||
-D SENSOR_INDOOR_PIN_DEFAULT=14
|
||||
-D LED_STATUS_PIN=13
|
||||
-D LED_OT_RX_PIN=15
|
||||
-D DEFAULT_OT_IN_GPIO=4
|
||||
-D DEFAULT_OT_OUT_GPIO=5
|
||||
-D DEFAULT_SENSOR_OUTDOOR_GPIO=12
|
||||
-D DEFAULT_SENSOR_INDOOR_GPIO=14
|
||||
-D DEFAULT_STATUS_LED_GPIO=13
|
||||
-D DEFAULT_OT_RX_LED_GPIO=15
|
||||
|
||||
[env:d1_mini_pro]
|
||||
platform = ${esp8266_defaults.platform}
|
||||
@@ -123,12 +122,12 @@ extra_scripts = ${esp8266_defaults.extra_scripts}
|
||||
board_build.ldscript = ${esp8266_defaults.board_build.ldscript}
|
||||
build_flags =
|
||||
${esp8266_defaults.build_flags}
|
||||
-D OT_IN_PIN_DEFAULT=4
|
||||
-D OT_OUT_PIN_DEFAULT=5
|
||||
-D SENSOR_OUTDOOR_PIN_DEFAULT=12
|
||||
-D SENSOR_INDOOR_PIN_DEFAULT=14
|
||||
-D LED_STATUS_PIN=13
|
||||
-D LED_OT_RX_PIN=15
|
||||
-D DEFAULT_OT_IN_GPIO=4
|
||||
-D DEFAULT_OT_OUT_GPIO=5
|
||||
-D DEFAULT_SENSOR_OUTDOOR_GPIO=12
|
||||
-D DEFAULT_SENSOR_INDOOR_GPIO=14
|
||||
-D DEFAULT_STATUS_LED_GPIO=13
|
||||
-D DEFAULT_OT_RX_LED_GPIO=15
|
||||
|
||||
[env:s2_mini]
|
||||
platform = ${esp32_defaults.platform}
|
||||
@@ -139,12 +138,12 @@ lib_ignore = ${esp32_defaults.lib_ignore}
|
||||
extra_scripts = ${esp32_defaults.extra_scripts}
|
||||
build_flags =
|
||||
${esp32_defaults.build_flags}
|
||||
-D OT_IN_PIN_DEFAULT=33
|
||||
-D OT_OUT_PIN_DEFAULT=35
|
||||
-D SENSOR_OUTDOOR_PIN_DEFAULT=9
|
||||
-D SENSOR_INDOOR_PIN_DEFAULT=7
|
||||
-D LED_STATUS_PIN=11
|
||||
-D LED_OT_RX_PIN=12
|
||||
-D DEFAULT_OT_IN_GPIO=33
|
||||
-D DEFAULT_OT_OUT_GPIO=35
|
||||
-D DEFAULT_SENSOR_OUTDOOR_GPIO=9
|
||||
-D DEFAULT_SENSOR_INDOOR_GPIO=7
|
||||
-D DEFAULT_STATUS_LED_GPIO=11
|
||||
-D DEFAULT_OT_RX_LED_GPIO=12
|
||||
|
||||
[env:s3_mini]
|
||||
platform = ${esp32_defaults.platform}
|
||||
@@ -158,12 +157,12 @@ extra_scripts = ${esp32_defaults.extra_scripts}
|
||||
build_flags =
|
||||
${esp32_defaults.build_flags}
|
||||
-D USE_BLE=1
|
||||
-D OT_IN_PIN_DEFAULT=35
|
||||
-D OT_OUT_PIN_DEFAULT=36
|
||||
-D SENSOR_OUTDOOR_PIN_DEFAULT=13
|
||||
-D SENSOR_INDOOR_PIN_DEFAULT=12
|
||||
-D LED_STATUS_PIN=11
|
||||
-D LED_OT_RX_PIN=10
|
||||
-D DEFAULT_OT_IN_GPIO=35
|
||||
-D DEFAULT_OT_OUT_GPIO=36
|
||||
-D DEFAULT_SENSOR_OUTDOOR_GPIO=13
|
||||
-D DEFAULT_SENSOR_INDOOR_GPIO=12
|
||||
-D DEFAULT_STATUS_LED_GPIO=11
|
||||
-D DEFAULT_OT_RX_LED_GPIO=10
|
||||
|
||||
[env:c3_mini]
|
||||
platform = ${esp32_defaults.platform}
|
||||
@@ -179,12 +178,12 @@ build_unflags =
|
||||
build_flags =
|
||||
${esp32_defaults.build_flags}
|
||||
-D USE_BLE=1
|
||||
-D OT_IN_PIN_DEFAULT=8
|
||||
-D OT_OUT_PIN_DEFAULT=10
|
||||
-D SENSOR_OUTDOOR_PIN_DEFAULT=0
|
||||
-D SENSOR_INDOOR_PIN_DEFAULT=1
|
||||
-D LED_STATUS_PIN=4
|
||||
-D LED_OT_RX_PIN=5
|
||||
-D DEFAULT_OT_IN_GPIO=8
|
||||
-D DEFAULT_OT_OUT_GPIO=10
|
||||
-D DEFAULT_SENSOR_OUTDOOR_GPIO=0
|
||||
-D DEFAULT_SENSOR_INDOOR_GPIO=1
|
||||
-D DEFAULT_STATUS_LED_GPIO=4
|
||||
-D DEFAULT_OT_RX_LED_GPIO=5
|
||||
|
||||
[env:nodemcu_32s]
|
||||
platform = ${esp32_defaults.platform}
|
||||
@@ -198,12 +197,12 @@ extra_scripts = ${esp32_defaults.extra_scripts}
|
||||
build_flags =
|
||||
${esp32_defaults.build_flags}
|
||||
-D USE_BLE=1
|
||||
-D OT_IN_PIN_DEFAULT=21
|
||||
-D OT_OUT_PIN_DEFAULT=22
|
||||
-D SENSOR_OUTDOOR_PIN_DEFAULT=12
|
||||
-D SENSOR_INDOOR_PIN_DEFAULT=13
|
||||
-D LED_STATUS_PIN=2 ; 18
|
||||
-D LED_OT_RX_PIN=19
|
||||
-D DEFAULT_OT_IN_GPIO=21
|
||||
-D DEFAULT_OT_OUT_GPIO=22
|
||||
-D DEFAULT_SENSOR_OUTDOOR_GPIO=12
|
||||
-D DEFAULT_SENSOR_INDOOR_GPIO=13
|
||||
-D DEFAULT_STATUS_LED_GPIO=2 ; 18
|
||||
-D DEFAULT_OT_RX_LED_GPIO=19
|
||||
;-D WOKWI=1
|
||||
|
||||
[env:d1_mini32]
|
||||
@@ -218,9 +217,9 @@ extra_scripts = ${esp32_defaults.extra_scripts}
|
||||
build_flags =
|
||||
${esp32_defaults.build_flags}
|
||||
-D USE_BLE=1
|
||||
-D OT_IN_PIN_DEFAULT=21
|
||||
-D OT_OUT_PIN_DEFAULT=22
|
||||
-D SENSOR_OUTDOOR_PIN_DEFAULT=12
|
||||
-D SENSOR_INDOOR_PIN_DEFAULT=18
|
||||
-D LED_STATUS_PIN=2
|
||||
-D LED_OT_RX_PIN=19
|
||||
-D DEFAULT_OT_IN_GPIO=21
|
||||
-D DEFAULT_OT_OUT_GPIO=22
|
||||
-D DEFAULT_SENSOR_OUTDOOR_GPIO=12
|
||||
-D DEFAULT_SENSOR_INDOOR_GPIO=18
|
||||
-D DEFAULT_STATUS_LED_GPIO=2
|
||||
-D DEFAULT_OT_RX_LED_GPIO=19
|
||||
|
||||
385
src/HaHelper.h
385
src/HaHelper.h
@@ -27,22 +27,31 @@ public:
|
||||
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;
|
||||
doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault;
|
||||
doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("emergency_target"));
|
||||
doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("emergency_target"));
|
||||
doc[FPSTR(HA_ENTITY_CATEGORY)] = F("config");
|
||||
doc[FPSTR(HA_DEVICE_CLASS)] = F("temperature");
|
||||
doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = F("°C");
|
||||
|
||||
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_ICON)] = F("mdi:thermometer-alert");
|
||||
doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("settings"));
|
||||
doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.emergency.target|float(0)|round(1) }}");
|
||||
doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("settings/set"));
|
||||
doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"emergency\": {\"target\" : {{ value }}}}");
|
||||
doc[FPSTR(HA_MIN)] = 5;
|
||||
doc[FPSTR(HA_MAX)] = 50;
|
||||
doc[FPSTR(HA_STEP)] = 0.5;
|
||||
doc[FPSTR(HA_MODE)] = "box";
|
||||
doc[FPSTR(HA_EXPIRE_AFTER)] = 120;
|
||||
@@ -142,7 +151,7 @@ public:
|
||||
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;
|
||||
doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status"));
|
||||
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_ENTITY_CATEGORY)] = F("config");
|
||||
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_ICON)] = F("mdi:radiator");
|
||||
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);
|
||||
}
|
||||
|
||||
bool publishNumberHeatingHysteresis(bool enabledByDefault = true) {
|
||||
bool publishNumberHeatingHysteresis(UnitSystem unit = UnitSystem::METRIC, bool enabledByDefault = true) {
|
||||
JsonDocument doc;
|
||||
doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault;
|
||||
doc[FPSTR(HA_UNIQUE_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_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_ICON)] = F("mdi:altimeter");
|
||||
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);
|
||||
}
|
||||
|
||||
bool publishSensorHeatingSetpoint(bool enabledByDefault = true) {
|
||||
bool publishSensorHeatingSetpoint(UnitSystem unit = UnitSystem::METRIC, bool enabledByDefault = true) {
|
||||
JsonDocument doc;
|
||||
doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status"));
|
||||
doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault;
|
||||
@@ -200,7 +223,14 @@ public:
|
||||
doc[FPSTR(HA_ENTITY_CATEGORY)] = F("diagnostic");
|
||||
doc[FPSTR(HA_DEVICE_CLASS)] = F("temperature");
|
||||
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_ICON)] = F("mdi:coolant-temperature");
|
||||
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);
|
||||
}
|
||||
|
||||
bool publishSensorBoilerHeatingMinTemp(bool enabledByDefault = true) {
|
||||
bool publishSensorBoilerHeatingMinTemp(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"));
|
||||
@@ -223,7 +253,14 @@ public:
|
||||
doc[FPSTR(HA_ENTITY_CATEGORY)] = F("diagnostic");
|
||||
doc[FPSTR(HA_DEVICE_CLASS)] = F("temperature");
|
||||
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_ICON)] = F("mdi:thermometer-chevron-down");
|
||||
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);
|
||||
}
|
||||
|
||||
bool publishSensorBoilerHeatingMaxTemp(bool enabledByDefault = true) {
|
||||
bool publishSensorBoilerHeatingMaxTemp(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"));
|
||||
@@ -246,7 +283,14 @@ public:
|
||||
doc[FPSTR(HA_ENTITY_CATEGORY)] = F("diagnostic");
|
||||
doc[FPSTR(HA_DEVICE_CLASS)] = F("temperature");
|
||||
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_ICON)] = F("mdi:thermometer-chevron-up");
|
||||
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);
|
||||
}
|
||||
|
||||
bool publishNumberHeatingMinTemp(bool enabledByDefault = true) {
|
||||
bool publishNumberHeatingMinTemp(UnitSystem unit = UnitSystem::METRIC, bool enabledByDefault = true) {
|
||||
JsonDocument doc;
|
||||
doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault;
|
||||
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_ENTITY_CATEGORY)] = F("config");
|
||||
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_ICON)] = F("mdi:thermometer-chevron-down");
|
||||
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_COMMAND_TOPIC)] = this->getDeviceTopic(F("settings/set"));
|
||||
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_MODE)] = "box";
|
||||
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);
|
||||
}
|
||||
|
||||
bool publishNumberHeatingMaxTemp(bool enabledByDefault = true) {
|
||||
bool publishNumberHeatingMaxTemp(UnitSystem unit = UnitSystem::METRIC, bool enabledByDefault = true) {
|
||||
JsonDocument doc;
|
||||
doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault;
|
||||
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_ENTITY_CATEGORY)] = F("config");
|
||||
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_ICON)] = F("mdi:thermometer-chevron-up");
|
||||
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_COMMAND_TOPIC)] = this->getDeviceTopic(F("settings/set"));
|
||||
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_MODE)] = "box";
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status"));
|
||||
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_ENTITY_CATEGORY)] = F("config");
|
||||
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_ICON)] = F("mdi:water-pump");
|
||||
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);
|
||||
}
|
||||
|
||||
bool publishSensorBoilerDhwMinTemp(bool enabledByDefault = true) {
|
||||
bool publishSensorBoilerDhwMinTemp(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"));
|
||||
@@ -389,7 +458,14 @@ public:
|
||||
doc[FPSTR(HA_ENTITY_CATEGORY)] = F("diagnostic");
|
||||
doc[FPSTR(HA_DEVICE_CLASS)] = F("temperature");
|
||||
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_ICON)] = F("mdi:thermometer-chevron-down");
|
||||
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);
|
||||
}
|
||||
|
||||
bool publishSensorBoilerDhwMaxTemp(bool enabledByDefault = true) {
|
||||
bool publishSensorBoilerDhwMaxTemp(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"));
|
||||
@@ -412,7 +488,14 @@ public:
|
||||
doc[FPSTR(HA_ENTITY_CATEGORY)] = F("diagnostic");
|
||||
doc[FPSTR(HA_DEVICE_CLASS)] = F("temperature");
|
||||
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_ICON)] = F("mdi:thermometer-chevron-up");
|
||||
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);
|
||||
}
|
||||
|
||||
bool publishNumberDhwMinTemp(bool enabledByDefault = true) {
|
||||
bool publishNumberDhwMinTemp(UnitSystem unit = UnitSystem::METRIC, bool enabledByDefault = true) {
|
||||
JsonDocument doc;
|
||||
doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault;
|
||||
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_ENTITY_CATEGORY)] = F("config");
|
||||
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_ICON)] = F("mdi:thermometer-chevron-down");
|
||||
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_COMMAND_TOPIC)] = this->getDeviceTopic(F("settings/set"));
|
||||
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_MODE)] = "box";
|
||||
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);
|
||||
}
|
||||
|
||||
bool publishNumberDhwMaxTemp(bool enabledByDefault = true) {
|
||||
bool publishNumberDhwMaxTemp(UnitSystem unit = UnitSystem::METRIC, bool enabledByDefault = true) {
|
||||
JsonDocument doc;
|
||||
doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault;
|
||||
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_ENTITY_CATEGORY)] = F("config");
|
||||
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_ICON)] = F("mdi:thermometer-chevron-up");
|
||||
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_COMMAND_TOPIC)] = this->getDeviceTopic(F("settings/set"));
|
||||
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_MODE)] = "box";
|
||||
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);
|
||||
}
|
||||
|
||||
bool publishNumberPidMinTemp(bool enabledByDefault = true) {
|
||||
bool publishNumberPidMinTemp(UnitSystem unit = UnitSystem::METRIC, bool enabledByDefault = true) {
|
||||
JsonDocument doc;
|
||||
doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault;
|
||||
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_ENTITY_CATEGORY)] = F("config");
|
||||
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_ICON)] = F("mdi:thermometer-chevron-down");
|
||||
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_COMMAND_TOPIC)] = this->getDeviceTopic(F("settings/set"));
|
||||
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_MODE)] = "box";
|
||||
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);
|
||||
}
|
||||
|
||||
bool publishNumberPidMaxTemp(bool enabledByDefault = true) {
|
||||
bool publishNumberPidMaxTemp(UnitSystem unit = UnitSystem::METRIC, bool enabledByDefault = true) {
|
||||
JsonDocument doc;
|
||||
doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault;
|
||||
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_ENTITY_CATEGORY)] = F("config");
|
||||
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_ICON)] = F("mdi:thermometer-chevron-up");
|
||||
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_COMMAND_TOPIC)] = this->getDeviceTopic(F("settings/set"));
|
||||
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_MODE)] = "box";
|
||||
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);
|
||||
}
|
||||
|
||||
bool publishSensorPressure(bool enabledByDefault = true) {
|
||||
bool publishSensorPressure(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"));
|
||||
@@ -992,7 +1111,14 @@ public:
|
||||
doc[FPSTR(HA_ENTITY_CATEGORY)] = F("diagnostic");
|
||||
doc[FPSTR(HA_DEVICE_CLASS)] = F("pressure");
|
||||
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_ICON)] = F("mdi:gauge");
|
||||
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);
|
||||
}
|
||||
|
||||
bool publishSensorDhwFlowRate(bool enabledByDefault = true) {
|
||||
bool publishSensorDhwFlowRate(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"));
|
||||
@@ -1015,7 +1141,14 @@ public:
|
||||
doc[FPSTR(HA_ENTITY_CATEGORY)] = F("diagnostic");
|
||||
doc[FPSTR(HA_DEVICE_CLASS)] = F("volume_flow_rate");
|
||||
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_ICON)] = F("mdi:water-pump");
|
||||
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;
|
||||
doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault;
|
||||
doc[FPSTR(HA_UNIQUE_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_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_ICON)] = F("mdi:home-thermometer");
|
||||
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_COMMAND_TOPIC)] = this->getDeviceTopic(F("state/set"));
|
||||
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_MODE)] = "box";
|
||||
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);
|
||||
}
|
||||
|
||||
bool publishSensorIndoorTemp(bool enabledByDefault = true) {
|
||||
bool publishSensorIndoorTemp(UnitSystem unit = UnitSystem::METRIC, bool enabledByDefault = true) {
|
||||
JsonDocument doc;
|
||||
doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status"));
|
||||
doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault;
|
||||
@@ -1059,7 +1201,14 @@ public:
|
||||
doc[FPSTR(HA_ENTITY_CATEGORY)] = F("diagnostic");
|
||||
doc[FPSTR(HA_DEVICE_CLASS)] = F("temperature");
|
||||
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_ICON)] = F("mdi:home-thermometer");
|
||||
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);
|
||||
}
|
||||
|
||||
bool publishNumberOutdoorTemp(bool enabledByDefault = true) {
|
||||
bool publishNumberOutdoorTemp(UnitSystem unit = UnitSystem::METRIC, bool enabledByDefault = true) {
|
||||
JsonDocument doc;
|
||||
doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault;
|
||||
doc[FPSTR(HA_UNIQUE_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_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_ICON)] = F("mdi:home-thermometer-outline");
|
||||
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_COMMAND_TOPIC)] = this->getDeviceTopic(F("state/set"));
|
||||
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_MODE)] = "box";
|
||||
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);
|
||||
}
|
||||
|
||||
bool publishSensorOutdoorTemp(bool enabledByDefault = true) {
|
||||
bool publishSensorOutdoorTemp(UnitSystem unit = UnitSystem::METRIC, bool enabledByDefault = true) {
|
||||
JsonDocument doc;
|
||||
doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status"));
|
||||
doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault;
|
||||
@@ -1102,7 +1260,14 @@ public:
|
||||
doc[FPSTR(HA_ENTITY_CATEGORY)] = F("diagnostic");
|
||||
doc[FPSTR(HA_DEVICE_CLASS)] = F("temperature");
|
||||
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_ICON)] = F("mdi:home-thermometer-outline");
|
||||
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);
|
||||
}
|
||||
|
||||
bool publishSensorHeatingTemp(bool enabledByDefault = true) {
|
||||
bool publishSensorHeatingTemp(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"));
|
||||
@@ -1125,7 +1290,14 @@ public:
|
||||
doc[FPSTR(HA_ENTITY_CATEGORY)] = F("diagnostic");
|
||||
doc[FPSTR(HA_DEVICE_CLASS)] = F("temperature");
|
||||
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_ICON)] = F("mdi:radiator");
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
doc[FPSTR(HA_AVAILABILITY)][0][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status"));
|
||||
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_DEVICE_CLASS)] = F("temperature");
|
||||
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_ICON)] = F("mdi:water-pump");
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status"));
|
||||
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_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_TEMPLATE)] = F("{% if value == 'heat' %}{\"heating\": {\"enable\" : true}}"
|
||||
"{% 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);
|
||||
}
|
||||
|
||||
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;
|
||||
doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status"));
|
||||
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_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_TEMPLATE)] = F("{% if value == 'heat' %}{\"dhw\": {\"enable\" : true}}"
|
||||
"{% elif value == 'off' %}{\"dhw\": {\"enable\" : false}}{% endif %}");
|
||||
|
||||
112
src/MainTask.h
112
src/MainTask.h
@@ -27,7 +27,6 @@ protected:
|
||||
enum class PumpStartReason {NONE, HEATING, ANTISTUCK};
|
||||
|
||||
Blinker* blinker = nullptr;
|
||||
bool blinkerInitialized = false;
|
||||
unsigned long firstFailConnect = 0;
|
||||
unsigned long lastHeapInfo = 0;
|
||||
unsigned int minFreeHeap = 0;
|
||||
@@ -51,17 +50,7 @@ protected:
|
||||
return 3;
|
||||
}
|
||||
|
||||
void setup() {
|
||||
#ifdef LED_STATUS_PIN
|
||||
pinMode(LED_STATUS_PIN, OUTPUT);
|
||||
digitalWrite(LED_STATUS_PIN, LOW);
|
||||
#endif
|
||||
|
||||
if (settings.externalPump.pin != 0) {
|
||||
pinMode(settings.externalPump.pin, OUTPUT);
|
||||
digitalWrite(settings.externalPump.pin, LOW);
|
||||
}
|
||||
}
|
||||
void setup() {}
|
||||
|
||||
void loop() {
|
||||
network->loop();
|
||||
@@ -89,20 +78,31 @@ protected:
|
||||
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) {
|
||||
tOt->enable();
|
||||
vars.states.mqtt = tMqtt->isConnected();
|
||||
vars.sensors.rssi = network->isConnected() ? WiFi.RSSI() : 0;
|
||||
|
||||
if (vars.states.emergency && !settings.emergency.enable) {
|
||||
vars.states.emergency = false;
|
||||
}
|
||||
|
||||
if (network->isConnected()) {
|
||||
vars.sensors.rssi = WiFi.RSSI();
|
||||
|
||||
if (!this->telnetStarted && telnetStream != nullptr) {
|
||||
telnetStream->begin(23, false);
|
||||
this->telnetStarted = true;
|
||||
}
|
||||
|
||||
if (!tMqtt->isEnabled() && strlen(settings.mqtt.server) > 0) {
|
||||
if (settings.mqtt.enable && !tMqtt->isEnabled()) {
|
||||
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) {
|
||||
@@ -126,12 +126,12 @@ protected:
|
||||
tMqtt->disable();
|
||||
}
|
||||
|
||||
if (settings.emergency.enable && !vars.states.emergency) {
|
||||
if (!vars.states.emergency && settings.emergency.enable && settings.emergency.onNetworkFault) {
|
||||
if (this->firstFailConnect == 0) {
|
||||
this->firstFailConnect = millis();
|
||||
}
|
||||
|
||||
if (millis() - this->firstFailConnect > EMERGENCY_TIME_TRESHOLD) {
|
||||
if (millis() - this->firstFailConnect > (settings.emergency.tresholdTime * 1000)) {
|
||||
vars.states.emergency = true;
|
||||
Log.sinfoln(FPSTR(L_MAIN), F("Emergency mode enabled"));
|
||||
}
|
||||
@@ -140,9 +140,7 @@ protected:
|
||||
this->yield();
|
||||
|
||||
|
||||
#ifdef LED_STATUS_PIN
|
||||
this->ledStatus(LED_STATUS_PIN);
|
||||
#endif
|
||||
this->ledStatus();
|
||||
this->externalPump();
|
||||
this->yield();
|
||||
|
||||
@@ -211,16 +209,32 @@ protected:
|
||||
}
|
||||
}
|
||||
|
||||
void ledStatus(uint8_t ledPin) {
|
||||
void ledStatus() {
|
||||
uint8_t errors[4];
|
||||
uint8_t errCount = 0;
|
||||
static uint8_t errPos = 0;
|
||||
static unsigned long endBlinkTime = 0;
|
||||
static bool ledOn = false;
|
||||
static uint8_t configuredGpio = GPIO_IS_NOT_CONFIGURED;
|
||||
|
||||
if (!this->blinkerInitialized) {
|
||||
this->blinker->init(ledPin);
|
||||
this->blinkerInitialized = true;
|
||||
if (settings.system.statusLedGpio != configuredGpio) {
|
||||
if (configuredGpio != GPIO_IS_NOT_CONFIGURED) {
|
||||
digitalWrite(configuredGpio, LOW);
|
||||
}
|
||||
|
||||
if (GPIO_IS_VALID(settings.system.statusLedGpio)) {
|
||||
configuredGpio = settings.system.statusLedGpio;
|
||||
pinMode(configuredGpio, OUTPUT);
|
||||
digitalWrite(configuredGpio, LOW);
|
||||
this->blinker->init(configuredGpio);
|
||||
|
||||
} else if (configuredGpio != GPIO_IS_NOT_CONFIGURED) {
|
||||
configuredGpio = GPIO_IS_NOT_CONFIGURED;
|
||||
}
|
||||
}
|
||||
|
||||
if (configuredGpio == GPIO_IS_NOT_CONFIGURED) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!network->isConnected()) {
|
||||
@@ -247,14 +261,14 @@ protected:
|
||||
if (!this->blinker->running() && millis() - endBlinkTime >= 5000) {
|
||||
if (errCount == 0) {
|
||||
if (!ledOn) {
|
||||
digitalWrite(ledPin, HIGH);
|
||||
digitalWrite(configuredGpio, HIGH);
|
||||
ledOn = true;
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
} else if (ledOn) {
|
||||
digitalWrite(ledPin, LOW);
|
||||
digitalWrite(configuredGpio, LOW);
|
||||
ledOn = false;
|
||||
endBlinkTime = millis();
|
||||
return;
|
||||
@@ -275,6 +289,34 @@ protected:
|
||||
}
|
||||
|
||||
void externalPump() {
|
||||
static uint8_t configuredGpio = GPIO_IS_NOT_CONFIGURED;
|
||||
|
||||
if (settings.externalPump.gpio != configuredGpio) {
|
||||
if (configuredGpio != GPIO_IS_NOT_CONFIGURED) {
|
||||
digitalWrite(configuredGpio, LOW);
|
||||
}
|
||||
|
||||
if (GPIO_IS_VALID(settings.externalPump.gpio)) {
|
||||
configuredGpio = settings.externalPump.gpio;
|
||||
pinMode(configuredGpio, OUTPUT);
|
||||
digitalWrite(configuredGpio, LOW);
|
||||
|
||||
} else if (configuredGpio != GPIO_IS_NOT_CONFIGURED) {
|
||||
configuredGpio = GPIO_IS_NOT_CONFIGURED;
|
||||
}
|
||||
}
|
||||
|
||||
if (configuredGpio == GPIO_IS_NOT_CONFIGURED) {
|
||||
if (vars.states.externalPump) {
|
||||
vars.states.externalPump = false;
|
||||
vars.parameters.extPumpLastEnableTime = millis();
|
||||
|
||||
Log.sinfoln("EXTPUMP", F("Disabled: use = off"));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!vars.states.heating && this->heatingEnabled) {
|
||||
this->heatingEnabled = false;
|
||||
this->heatingDisabledTime = millis();
|
||||
@@ -283,11 +325,9 @@ protected:
|
||||
this->heatingEnabled = true;
|
||||
}
|
||||
|
||||
if (!settings.externalPump.use || settings.externalPump.pin == 0) {
|
||||
if (!settings.externalPump.use) {
|
||||
if (vars.states.externalPump) {
|
||||
if (settings.externalPump.pin != 0) {
|
||||
digitalWrite(settings.externalPump.pin, LOW);
|
||||
}
|
||||
digitalWrite(configuredGpio, LOW);
|
||||
|
||||
vars.states.externalPump = false;
|
||||
vars.parameters.extPumpLastEnableTime = millis();
|
||||
@@ -300,7 +340,7 @@ protected:
|
||||
|
||||
if (vars.states.externalPump && !this->heatingEnabled) {
|
||||
if (this->extPumpStartReason == MainTask::PumpStartReason::HEATING && millis() - this->heatingDisabledTime > (settings.externalPump.postCirculationTime * 1000u)) {
|
||||
digitalWrite(settings.externalPump.pin, LOW);
|
||||
digitalWrite(configuredGpio, LOW);
|
||||
|
||||
vars.states.externalPump = false;
|
||||
vars.parameters.extPumpLastEnableTime = millis();
|
||||
@@ -308,7 +348,7 @@ protected:
|
||||
Log.sinfoln("EXTPUMP", F("Disabled: expired post circulation time"));
|
||||
|
||||
} else if (this->extPumpStartReason == MainTask::PumpStartReason::ANTISTUCK && millis() - this->externalPumpStartTime >= (settings.externalPump.antiStuckTime * 1000u)) {
|
||||
digitalWrite(settings.externalPump.pin, LOW);
|
||||
digitalWrite(configuredGpio, LOW);
|
||||
|
||||
vars.states.externalPump = false;
|
||||
vars.parameters.extPumpLastEnableTime = millis();
|
||||
@@ -324,7 +364,7 @@ protected:
|
||||
this->externalPumpStartTime = millis();
|
||||
this->extPumpStartReason = MainTask::PumpStartReason::HEATING;
|
||||
|
||||
digitalWrite(settings.externalPump.pin, HIGH);
|
||||
digitalWrite(configuredGpio, HIGH);
|
||||
|
||||
Log.sinfoln("EXTPUMP", F("Enabled: heating on"));
|
||||
|
||||
@@ -333,7 +373,7 @@ protected:
|
||||
this->externalPumpStartTime = millis();
|
||||
this->extPumpStartReason = MainTask::PumpStartReason::ANTISTUCK;
|
||||
|
||||
digitalWrite(settings.externalPump.pin, HIGH);
|
||||
digitalWrite(configuredGpio, HIGH);
|
||||
|
||||
Log.sinfoln("EXTPUMP", F("Enabled: anti stuck"));
|
||||
}
|
||||
|
||||
106
src/MqttTask.h
106
src/MqttTask.h
@@ -20,6 +20,11 @@ public:
|
||||
if (this->client != nullptr) {
|
||||
if (this->client->connected()) {
|
||||
this->client->stop();
|
||||
|
||||
if (this->connected) {
|
||||
this->onDisconnect();
|
||||
this->connected = false;
|
||||
}
|
||||
}
|
||||
|
||||
delete this->client;
|
||||
@@ -31,6 +36,12 @@ public:
|
||||
|
||||
void disable() {
|
||||
this->client->stop();
|
||||
|
||||
if (this->connected) {
|
||||
this->onDisconnect();
|
||||
this->connected = false;
|
||||
}
|
||||
|
||||
Task::disable();
|
||||
|
||||
Log.sinfoln(FPSTR(L_MQTT), F("Disabled"));
|
||||
@@ -51,6 +62,7 @@ protected:
|
||||
MqttClient* client = nullptr;
|
||||
HaHelper* haHelper = nullptr;
|
||||
MqttWriter* writer = nullptr;
|
||||
UnitSystem currentUnitSystem = UnitSystem::METRIC;
|
||||
unsigned short readyForSendTime = 15000;
|
||||
unsigned long lastReconnectTime = 0;
|
||||
unsigned long connectedTime = 0;
|
||||
@@ -69,7 +81,7 @@ protected:
|
||||
}*/
|
||||
|
||||
int getTaskPriority() {
|
||||
return 1;
|
||||
return 2;
|
||||
}
|
||||
|
||||
bool isReadyForSend() {
|
||||
@@ -175,13 +187,15 @@ protected:
|
||||
this->onConnect();
|
||||
}
|
||||
|
||||
if (!this->connected && settings.emergency.enable && !vars.states.emergency && millis() - this->disconnectedTime > EMERGENCY_TIME_TRESHOLD) {
|
||||
vars.states.emergency = true;
|
||||
Log.sinfoln(FPSTR(L_MQTT), F("Emergency mode enabled"));
|
||||
if (settings.emergency.enable && settings.emergency.onMqttFault) {
|
||||
if (!this->connected && !vars.states.emergency && millis() - this->disconnectedTime > (settings.emergency.tresholdTime * 1000)) {
|
||||
vars.states.emergency = true;
|
||||
Log.sinfoln(FPSTR(L_MQTT), F("Emergency mode enabled"));
|
||||
|
||||
} else if (this->connected && vars.states.emergency && millis() - this->connectedTime > 10000) {
|
||||
vars.states.emergency = false;
|
||||
Log.sinfoln(FPSTR(L_MQTT), F("Emergency mode disabled"));
|
||||
} else if (this->connected && vars.states.emergency && millis() - this->connectedTime > 10000) {
|
||||
vars.states.emergency = false;
|
||||
Log.sinfoln(FPSTR(L_MQTT), F("Emergency mode disabled"));
|
||||
}
|
||||
}
|
||||
|
||||
if (!this->connected) {
|
||||
@@ -213,10 +227,11 @@ protected:
|
||||
}
|
||||
|
||||
// publish ha entities if not published
|
||||
if (this->newConnection) {
|
||||
if (this->newConnection || this->currentUnitSystem != settings.system.unitSystem) {
|
||||
this->publishHaEntities();
|
||||
this->publishNonStaticHaEntities(true);
|
||||
this->newConnection = false;
|
||||
this->currentUnitSystem = settings.system.unitSystem;
|
||||
|
||||
} else {
|
||||
// publish non static ha entities
|
||||
@@ -318,19 +333,19 @@ protected:
|
||||
void publishHaEntities() {
|
||||
// emergency
|
||||
this->haHelper->publishSwitchEmergency();
|
||||
this->haHelper->publishNumberEmergencyTarget();
|
||||
this->haHelper->publishNumberEmergencyTarget(settings.system.unitSystem);
|
||||
this->haHelper->publishSwitchEmergencyUseEquitherm();
|
||||
this->haHelper->publishSwitchEmergencyUsePid();
|
||||
|
||||
// heating
|
||||
this->haHelper->publishSwitchHeating(false);
|
||||
this->haHelper->publishSwitchHeatingTurbo();
|
||||
this->haHelper->publishNumberHeatingHysteresis();
|
||||
this->haHelper->publishSensorHeatingSetpoint(false);
|
||||
this->haHelper->publishSensorBoilerHeatingMinTemp(false);
|
||||
this->haHelper->publishSensorBoilerHeatingMaxTemp(false);
|
||||
this->haHelper->publishNumberHeatingMinTemp(false);
|
||||
this->haHelper->publishNumberHeatingMaxTemp(false);
|
||||
this->haHelper->publishNumberHeatingHysteresis(settings.system.unitSystem);
|
||||
this->haHelper->publishSensorHeatingSetpoint(settings.system.unitSystem, false);
|
||||
this->haHelper->publishSensorBoilerHeatingMinTemp(settings.system.unitSystem, false);
|
||||
this->haHelper->publishSensorBoilerHeatingMaxTemp(settings.system.unitSystem, false);
|
||||
this->haHelper->publishNumberHeatingMinTemp(settings.system.unitSystem, false);
|
||||
this->haHelper->publishNumberHeatingMaxTemp(settings.system.unitSystem, false);
|
||||
this->haHelper->publishNumberHeatingMaxModulation(false);
|
||||
|
||||
// pid
|
||||
@@ -339,8 +354,8 @@ protected:
|
||||
this->haHelper->publishNumberPidFactorI();
|
||||
this->haHelper->publishNumberPidFactorD();
|
||||
this->haHelper->publishNumberPidDt(false);
|
||||
this->haHelper->publishNumberPidMinTemp(false);
|
||||
this->haHelper->publishNumberPidMaxTemp(false);
|
||||
this->haHelper->publishNumberPidMinTemp(settings.system.unitSystem, false);
|
||||
this->haHelper->publishNumberPidMaxTemp(settings.system.unitSystem, false);
|
||||
|
||||
// equitherm
|
||||
this->haHelper->publishSwitchEquitherm();
|
||||
@@ -362,14 +377,16 @@ protected:
|
||||
|
||||
// sensors
|
||||
this->haHelper->publishSensorModulation(false);
|
||||
this->haHelper->publishSensorPressure(false);
|
||||
this->haHelper->publishSensorPressure(settings.system.unitSystem, false);
|
||||
this->haHelper->publishSensorFaultCode();
|
||||
this->haHelper->publishSensorRssi(false);
|
||||
this->haHelper->publishSensorUptime(false);
|
||||
|
||||
// temperatures
|
||||
this->haHelper->publishNumberIndoorTemp();
|
||||
this->haHelper->publishSensorHeatingTemp();
|
||||
this->haHelper->publishNumberIndoorTemp(settings.system.unitSystem);
|
||||
this->haHelper->publishSensorHeatingTemp(settings.system.unitSystem);
|
||||
this->haHelper->publishSensorHeatingReturnTemp(settings.system.unitSystem, false);
|
||||
this->haHelper->publishSensorExhaustTemp(settings.system.unitSystem, false);
|
||||
|
||||
// buttons
|
||||
this->haHelper->publishButtonRestart(false);
|
||||
@@ -383,23 +400,36 @@ protected:
|
||||
|
||||
bool published = false;
|
||||
bool isStupidMode = !settings.pid.enable && !settings.equitherm.enable;
|
||||
byte heatingMinTemp = isStupidMode ? settings.heating.minTemp : 10;
|
||||
byte heatingMaxTemp = isStupidMode ? settings.heating.maxTemp : 30;
|
||||
bool editableOutdoorTemp = settings.sensors.outdoor.type == 1;
|
||||
bool editableIndoorTemp = settings.sensors.indoor.type == 1;
|
||||
byte heatingMinTemp = 0;
|
||||
byte heatingMaxTemp = 0;
|
||||
bool editableOutdoorTemp = settings.sensors.outdoor.type == SensorType::MANUAL;
|
||||
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) {
|
||||
_dhwPresent = settings.opentherm.dhwPresent;
|
||||
|
||||
if (_dhwPresent) {
|
||||
this->haHelper->publishSwitchDhw(false);
|
||||
this->haHelper->publishSensorBoilerDhwMinTemp(false);
|
||||
this->haHelper->publishSensorBoilerDhwMaxTemp(false);
|
||||
this->haHelper->publishNumberDhwMinTemp(false);
|
||||
this->haHelper->publishNumberDhwMaxTemp(false);
|
||||
this->haHelper->publishSensorBoilerDhwMinTemp(settings.system.unitSystem, false);
|
||||
this->haHelper->publishSensorBoilerDhwMaxTemp(settings.system.unitSystem, false);
|
||||
this->haHelper->publishNumberDhwMinTemp(settings.system.unitSystem, false);
|
||||
this->haHelper->publishNumberDhwMaxTemp(settings.system.unitSystem, false);
|
||||
this->haHelper->publishBinSensorDhw();
|
||||
this->haHelper->publishSensorDhwTemp();
|
||||
this->haHelper->publishSensorDhwFlowRate(false);
|
||||
this->haHelper->publishSensorDhwTemp(settings.system.unitSystem);
|
||||
this->haHelper->publishSensorDhwFlowRate(settings.system.unitSystem, false);
|
||||
|
||||
} else {
|
||||
this->haHelper->deleteSwitchDhw();
|
||||
@@ -426,8 +456,9 @@ protected:
|
||||
_heatingMaxTemp = heatingMaxTemp;
|
||||
_isStupidMode = isStupidMode;
|
||||
|
||||
this->haHelper->publishNumberHeatingTarget(heatingMinTemp, heatingMaxTemp, false);
|
||||
this->haHelper->publishNumberHeatingTarget(settings.system.unitSystem, heatingMinTemp, heatingMaxTemp, false);
|
||||
this->haHelper->publishClimateHeating(
|
||||
settings.system.unitSystem,
|
||||
heatingMinTemp,
|
||||
heatingMaxTemp,
|
||||
isStupidMode ? HaHelper::TEMP_SOURCE_HEATING : HaHelper::TEMP_SOURCE_INDOOR
|
||||
@@ -438,6 +469,7 @@ protected:
|
||||
} else if (_isStupidMode != isStupidMode) {
|
||||
_isStupidMode = isStupidMode;
|
||||
this->haHelper->publishClimateHeating(
|
||||
settings.system.unitSystem,
|
||||
heatingMinTemp,
|
||||
heatingMaxTemp,
|
||||
isStupidMode ? HaHelper::TEMP_SOURCE_HEATING : HaHelper::TEMP_SOURCE_INDOOR
|
||||
@@ -450,8 +482,8 @@ protected:
|
||||
_dhwMinTemp = settings.dhw.minTemp;
|
||||
_dhwMaxTemp = settings.dhw.maxTemp;
|
||||
|
||||
this->haHelper->publishNumberDhwTarget(settings.dhw.minTemp, settings.dhw.maxTemp, false);
|
||||
this->haHelper->publishClimateDhw(settings.dhw.minTemp, settings.dhw.maxTemp);
|
||||
this->haHelper->publishNumberDhwTarget(settings.system.unitSystem, settings.dhw.minTemp, settings.dhw.maxTemp, false);
|
||||
this->haHelper->publishClimateDhw(settings.system.unitSystem, settings.dhw.minTemp, settings.dhw.maxTemp);
|
||||
|
||||
published = true;
|
||||
}
|
||||
@@ -461,10 +493,10 @@ protected:
|
||||
|
||||
if (editableOutdoorTemp) {
|
||||
this->haHelper->deleteSensorOutdoorTemp();
|
||||
this->haHelper->publishNumberOutdoorTemp();
|
||||
this->haHelper->publishNumberOutdoorTemp(settings.system.unitSystem);
|
||||
} else {
|
||||
this->haHelper->deleteNumberOutdoorTemp();
|
||||
this->haHelper->publishSensorOutdoorTemp();
|
||||
this->haHelper->publishSensorOutdoorTemp(settings.system.unitSystem);
|
||||
}
|
||||
|
||||
published = true;
|
||||
@@ -475,10 +507,10 @@ protected:
|
||||
|
||||
if (editableIndoorTemp) {
|
||||
this->haHelper->deleteSensorIndoorTemp();
|
||||
this->haHelper->publishNumberIndoorTemp();
|
||||
this->haHelper->publishNumberIndoorTemp(settings.system.unitSystem);
|
||||
} else {
|
||||
this->haHelper->deleteNumberIndoorTemp();
|
||||
this->haHelper->publishSensorIndoorTemp();
|
||||
this->haHelper->publishSensorIndoorTemp(settings.system.unitSystem);
|
||||
}
|
||||
|
||||
published = true;
|
||||
|
||||
@@ -1,31 +1,36 @@
|
||||
#include <CustomOpenTherm.h>
|
||||
|
||||
CustomOpenTherm* ot;
|
||||
extern FileData fsSettings;
|
||||
|
||||
class OpenThermTask : public Task {
|
||||
public:
|
||||
OpenThermTask(bool _enabled = false, unsigned long _interval = 0) : Task(_enabled, _interval) {
|
||||
ot = new CustomOpenTherm(settings.opentherm.inPin, settings.opentherm.outPin);
|
||||
}
|
||||
OpenThermTask(bool _enabled = false, unsigned long _interval = 0) : Task(_enabled, _interval) {}
|
||||
|
||||
static void IRAM_ATTR handleInterrupt() {
|
||||
ot->handleInterrupt();
|
||||
~OpenThermTask() {
|
||||
delete this->instance;
|
||||
}
|
||||
|
||||
protected:
|
||||
unsigned short readyTime = 60000;
|
||||
unsigned short dhwSetTempInterval = 60000;
|
||||
unsigned short heatingSetTempInterval = 60000;
|
||||
const unsigned short readyTime = 60000;
|
||||
const unsigned short dhwSetTempInterval = 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;
|
||||
unsigned long lastSuccessResponse = 0;
|
||||
unsigned long prevUpdateNonEssentialVars = 0;
|
||||
unsigned long startupTime = millis();
|
||||
unsigned long dhwSetTempTime = 0;
|
||||
unsigned long heatingSetTempTime = 0;
|
||||
byte dhwFlowRateMultiplier = 1;
|
||||
byte pressureMultiplier = 1;
|
||||
byte configuredRxLedGpio = GPIO_IS_NOT_CONFIGURED;
|
||||
|
||||
|
||||
const char* getTaskName() {
|
||||
return "OpenTherm";
|
||||
@@ -36,85 +41,96 @@ protected:
|
||||
}
|
||||
|
||||
int getTaskPriority() {
|
||||
return 2;
|
||||
return 5;
|
||||
}
|
||||
|
||||
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) {
|
||||
// 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(
|
||||
FPSTR(L_OT),
|
||||
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) {
|
||||
this->lastSuccessResponse = millis();
|
||||
|
||||
#ifdef LED_OT_RX_PIN
|
||||
{
|
||||
digitalWrite(LED_OT_RX_PIN, HIGH);
|
||||
if (this->configuredRxLedGpio != GPIO_IS_NOT_CONFIGURED) {
|
||||
digitalWrite(this->configuredRxLedGpio, HIGH);
|
||||
delayMicroseconds(2000);
|
||||
digitalWrite(LED_OT_RX_PIN, LOW);
|
||||
digitalWrite(this->configuredRxLedGpio, LOW);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
});
|
||||
|
||||
ot->setYieldCallback([this]() {
|
||||
this->instance->setYieldCallback([this]() {
|
||||
this->delay(25);
|
||||
});
|
||||
|
||||
ot->setMinWaitTimeForStartBit(20000);
|
||||
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"));
|
||||
}
|
||||
this->instance->begin();
|
||||
}
|
||||
|
||||
void loop() {
|
||||
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;
|
||||
}
|
||||
|
||||
if (settings.opentherm.rxLedGpio != this->configuredRxLedGpio) {
|
||||
if (this->configuredRxLedGpio != GPIO_IS_NOT_CONFIGURED) {
|
||||
digitalWrite(this->configuredRxLedGpio, LOW);
|
||||
}
|
||||
|
||||
if (GPIO_IS_VALID(settings.opentherm.rxLedGpio)) {
|
||||
this->configuredRxLedGpio = settings.opentherm.rxLedGpio;
|
||||
pinMode(this->configuredRxLedGpio, OUTPUT);
|
||||
digitalWrite(this->configuredRxLedGpio, LOW);
|
||||
|
||||
} else if (this->configuredRxLedGpio != GPIO_IS_NOT_CONFIGURED) {
|
||||
this->configuredRxLedGpio = GPIO_IS_NOT_CONFIGURED;
|
||||
}
|
||||
}
|
||||
|
||||
bool heatingEnabled = (vars.states.emergency || settings.heating.enable) && this->pump && this->isReady();
|
||||
bool heatingCh2Enabled = settings.opentherm.heatingCh2Enabled;
|
||||
if (settings.opentherm.heatingCh1ToCh2) {
|
||||
heatingCh2Enabled = heatingEnabled;
|
||||
@@ -123,7 +139,7 @@ protected:
|
||||
heatingCh2Enabled = settings.opentherm.dhwPresent && settings.dhw.enable;
|
||||
}
|
||||
|
||||
localResponse = ot->setBoilerStatus(
|
||||
unsigned long response = this->instance->setBoilerStatus(
|
||||
heatingEnabled,
|
||||
settings.opentherm.dhwPresent && settings.dhw.enable,
|
||||
false,
|
||||
@@ -133,20 +149,20 @@ protected:
|
||||
settings.opentherm.dhwBlocking
|
||||
);
|
||||
|
||||
if (!ot->isValidResponse(localResponse)) {
|
||||
Log.swarningln(FPSTR(L_OT), F("Invalid response after setBoilerStatus: %s"), ot->statusToString(ot->getLastResponseStatus()));
|
||||
if (!CustomOpenTherm::isValidResponse(response)) {
|
||||
Log.swarningln(FPSTR(L_OT), F("Invalid response after setBoilerStatus: %s"), CustomOpenTherm::statusToString(this->instance->getLastResponseStatus()));
|
||||
}
|
||||
|
||||
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;
|
||||
this->initBoiler();
|
||||
|
||||
} else if (vars.states.otStatus && millis() - this->lastSuccessResponse > 1150) {
|
||||
Log.swarningln(FPSTR(L_OT), F("Disconnected"));
|
||||
|
||||
vars.states.otStatus = false;
|
||||
this->isInitialized = false;
|
||||
}
|
||||
|
||||
// If boiler is disconnected, no need try setting other OT stuff
|
||||
@@ -160,17 +176,27 @@ protected:
|
||||
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) {
|
||||
this->prevUpdateNonEssentialVars = 0;
|
||||
vars.parameters.heatingEnabled = heatingEnabled;
|
||||
Log.sinfoln(FPSTR(L_OT_HEATING), "%s", heatingEnabled ? F("Enabled") : F("Disabled"));
|
||||
}
|
||||
|
||||
vars.states.heating = ot->isCentralHeatingActive(localResponse);
|
||||
vars.states.dhw = settings.opentherm.dhwPresent ? ot->isHotWaterActive(localResponse) : false;
|
||||
vars.states.flame = ot->isFlameOn(localResponse);
|
||||
vars.states.fault = ot->isFault(localResponse);
|
||||
vars.states.diagnostic = ot->isDiagnostic(localResponse);
|
||||
vars.states.heating = CustomOpenTherm::isCentralHeatingActive(response);
|
||||
vars.states.dhw = settings.opentherm.dhwPresent ? CustomOpenTherm::isHotWaterActive(response) : false;
|
||||
vars.states.flame = CustomOpenTherm::isFlameOn(response);
|
||||
vars.states.fault = CustomOpenTherm::isFault(response);
|
||||
vars.states.diagnostic = CustomOpenTherm::isDiagnostic(response);
|
||||
|
||||
// These parameters will be updated every minute
|
||||
if (millis() - this->prevUpdateNonEssentialVars > 60000) {
|
||||
@@ -193,7 +219,7 @@ protected:
|
||||
|
||||
|
||||
// Get DHW min/max temp (if necessary)
|
||||
if (settings.opentherm.dhwPresent) {
|
||||
if (settings.opentherm.dhwPresent && settings.opentherm.getMinMaxTemp) {
|
||||
if (updateMinMaxDhwTemp()) {
|
||||
if (settings.dhw.minTemp < vars.parameters.dhwMinTemp) {
|
||||
settings.dhw.minTemp = vars.parameters.dhwMinTemp;
|
||||
@@ -208,46 +234,51 @@ protected:
|
||||
}
|
||||
|
||||
} 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"));
|
||||
}
|
||||
|
||||
if (settings.dhw.minTemp >= settings.dhw.maxTemp) {
|
||||
settings.dhw.minTemp = 30;
|
||||
settings.dhw.maxTemp = 60;
|
||||
settings.dhw.minTemp = vars.parameters.dhwMinTemp;
|
||||
settings.dhw.maxTemp = vars.parameters.dhwMaxTemp;
|
||||
fsSettings.update();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Get heating min/max temp
|
||||
if (updateMinMaxHeatingTemp()) {
|
||||
if (settings.heating.minTemp < vars.parameters.heatingMinTemp) {
|
||||
settings.heating.minTemp = vars.parameters.heatingMinTemp;
|
||||
fsSettings.update();
|
||||
Log.snoticeln(FPSTR(L_OT_HEATING), F("Updated min temp: %hhu"), settings.heating.minTemp);
|
||||
}
|
||||
if (settings.opentherm.getMinMaxTemp) {
|
||||
if (updateMinMaxHeatingTemp()) {
|
||||
if (settings.heating.minTemp < vars.parameters.heatingMinTemp) {
|
||||
settings.heating.minTemp = vars.parameters.heatingMinTemp;
|
||||
fsSettings.update();
|
||||
Log.snoticeln(FPSTR(L_OT_HEATING), F("Updated min temp: %hhu"), settings.heating.minTemp);
|
||||
}
|
||||
|
||||
if (settings.heating.maxTemp > vars.parameters.heatingMaxTemp) {
|
||||
settings.heating.maxTemp = vars.parameters.heatingMaxTemp;
|
||||
fsSettings.update();
|
||||
Log.snoticeln(FPSTR(L_OT_HEATING), F("Updated max temp: %hhu"), settings.heating.maxTemp);
|
||||
}
|
||||
if (settings.heating.maxTemp > vars.parameters.heatingMaxTemp) {
|
||||
settings.heating.maxTemp = vars.parameters.heatingMaxTemp;
|
||||
fsSettings.update();
|
||||
Log.snoticeln(FPSTR(L_OT_HEATING), F("Updated max temp: %hhu"), settings.heating.maxTemp);
|
||||
}
|
||||
|
||||
} else {
|
||||
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);
|
||||
|
||||
} else {
|
||||
Log.swarningln(FPSTR(L_OT_HEATING), F("Failed get min/max temp"));
|
||||
Log.swarningln(FPSTR(L_OT_HEATING), F("Failed get min/max temp"));
|
||||
}
|
||||
}
|
||||
|
||||
if (settings.heating.minTemp >= settings.heating.maxTemp) {
|
||||
settings.heating.minTemp = 20;
|
||||
settings.heating.maxTemp = 90;
|
||||
settings.heating.minTemp = vars.parameters.heatingMinTemp;
|
||||
settings.heating.maxTemp = vars.parameters.heatingMaxTemp;
|
||||
fsSettings.update();
|
||||
}
|
||||
|
||||
// Force set max heating temp
|
||||
setMaxHeatingTemp(settings.heating.maxTemp);
|
||||
|
||||
// Get outdoor temp (if necessary)
|
||||
if (settings.sensors.outdoor.type == 0) {
|
||||
if (settings.sensors.outdoor.type == SensorType::BOILER) {
|
||||
updateOutsideTemp();
|
||||
}
|
||||
|
||||
@@ -263,7 +294,7 @@ protected:
|
||||
|
||||
|
||||
// Get current modulation level (if necessary)
|
||||
if ((settings.opentherm.dhwPresent && settings.dhw.enable) || settings.heating.enable || heatingEnabled) {
|
||||
if (vars.states.flame) {
|
||||
updateModulationLevel();
|
||||
|
||||
} else {
|
||||
@@ -283,11 +314,17 @@ protected:
|
||||
// Get current heating temp
|
||||
updateHeatingTemp();
|
||||
|
||||
// Get heating return temp
|
||||
updateHeatingReturnTemp();
|
||||
|
||||
// Get exhaust temp
|
||||
updateExhaustTemp();
|
||||
|
||||
|
||||
// Fault reset action
|
||||
if (vars.actions.resetFault) {
|
||||
if (vars.states.fault) {
|
||||
if (ot->sendBoilerReset()) {
|
||||
if (this->instance->sendBoilerReset()) {
|
||||
Log.sinfoln(FPSTR(L_OT), F("Boiler fault reset successfully"));
|
||||
|
||||
} else {
|
||||
@@ -301,7 +338,7 @@ protected:
|
||||
// Diag reset action
|
||||
if (vars.actions.resetDiagnostic) {
|
||||
if (vars.states.diagnostic) {
|
||||
if (ot->sendServiceReset()) {
|
||||
if (this->instance->sendServiceReset()) {
|
||||
Log.sinfoln(FPSTR(L_OT), F("Boiler diagnostic reset successfully"));
|
||||
|
||||
} else {
|
||||
@@ -315,15 +352,16 @@ protected:
|
||||
|
||||
// Update DHW temp
|
||||
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) {
|
||||
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
|
||||
if (ot->setDhwTemp(newDhwTemp)) {
|
||||
if (this->instance->setDhwTemp(convertedTemp)) {
|
||||
currentDhwTemp = newDhwTemp;
|
||||
this->dhwSetTempTime = millis();
|
||||
|
||||
@@ -333,7 +371,7 @@ protected:
|
||||
|
||||
// Set DHW temp to CH2
|
||||
if (settings.opentherm.dhwToCh2) {
|
||||
if (!ot->setHeatingCh2Temp(newDhwTemp)) {
|
||||
if (!this->instance->setHeatingCh2Temp(convertedTemp)) {
|
||||
Log.swarningln(FPSTR(L_OT_DHW), F("Failed set ch2 temp"));
|
||||
}
|
||||
}
|
||||
@@ -341,11 +379,12 @@ protected:
|
||||
|
||||
|
||||
// Update heating temp
|
||||
if (heatingEnabled && (needSetHeatingTemp() || fabs(vars.parameters.heatingSetpoint - currentHeatingTemp) > 0.0001)) {
|
||||
Log.sinfoln(FPSTR(L_OT_HEATING), F("Set temp = %u"), vars.parameters.heatingSetpoint);
|
||||
if (heatingEnabled && (this->needSetHeatingTemp() || fabs(vars.parameters.heatingSetpoint - currentHeatingTemp) > 0.0001)) {
|
||||
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
|
||||
if (ot->setHeatingCh1Temp(vars.parameters.heatingSetpoint)) {
|
||||
if (this->instance->setHeatingCh1Temp(convertedTemp) || this->setMaxHeatingTemp(convertedTemp)) {
|
||||
currentHeatingTemp = vars.parameters.heatingSetpoint;
|
||||
this->heatingSetTempTime = millis();
|
||||
|
||||
@@ -355,7 +394,7 @@ protected:
|
||||
|
||||
// Set heating temp to CH2
|
||||
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"));
|
||||
}
|
||||
}
|
||||
@@ -378,8 +417,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() {
|
||||
return millis() - this->startupTime > this->readyTime;
|
||||
return millis() - this->instanceCreatedTime > this->readyTime;
|
||||
}
|
||||
|
||||
bool needSetDhwTemp() {
|
||||
@@ -391,13 +469,13 @@ protected:
|
||||
}
|
||||
|
||||
bool updateSlaveConfig() {
|
||||
unsigned long response = ot->sendRequest(ot->buildRequest(
|
||||
unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
|
||||
OpenThermRequestType::READ_DATA,
|
||||
OpenThermMessageID::SConfigSMemberIDcode,
|
||||
0
|
||||
));
|
||||
|
||||
if (!ot->isValidResponse(response)) {
|
||||
if (!CustomOpenTherm::isValidResponse(response)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -452,69 +530,69 @@ protected:
|
||||
return true;
|
||||
}
|
||||
|
||||
unsigned long response = ot->sendRequest(ot->buildRequest(
|
||||
unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
|
||||
OpenThermRequestType::WRITE_DATA,
|
||||
OpenThermMessageID::MConfigMMemberIDcode,
|
||||
request
|
||||
));
|
||||
|
||||
return ot->isValidResponse(response);
|
||||
return CustomOpenTherm::isValidResponse(response);
|
||||
}
|
||||
|
||||
bool setMaxModulationLevel(byte value) {
|
||||
unsigned long response = ot->sendRequest(ot->buildRequest(
|
||||
unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
|
||||
OpenThermRequestType::WRITE_DATA,
|
||||
OpenThermMessageID::MaxRelModLevelSetting,
|
||||
ot->toF88(value)
|
||||
CustomOpenTherm::toFloat(value)
|
||||
));
|
||||
|
||||
if (!ot->isValidResponse(response)) {
|
||||
if (!CustomOpenTherm::isValidResponse(response)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
vars.parameters.maxModulation = ot->fromF88(response);
|
||||
vars.parameters.maxModulation = CustomOpenTherm::getFloat(response);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool updateSlaveOtVersion() {
|
||||
unsigned long response = ot->sendRequest(ot->buildRequest(
|
||||
unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
|
||||
OpenThermRequestType::READ_DATA,
|
||||
OpenThermMessageID::OpenThermVersionSlave,
|
||||
0
|
||||
));
|
||||
|
||||
if (!ot->isValidResponse(response)) {
|
||||
if (!CustomOpenTherm::isValidResponse(response)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
vars.parameters.slaveOtVersion = ot->getFloat(response);
|
||||
vars.parameters.slaveOtVersion = CustomOpenTherm::getFloat(response);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool setMasterOtVersion(float version) {
|
||||
unsigned long response = ot->sendRequest(ot->buildRequest(
|
||||
unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
|
||||
OpenThermRequestType::WRITE_DATA,
|
||||
OpenThermMessageID::OpenThermVersionMaster,
|
||||
ot->toF88(version)
|
||||
CustomOpenTherm::toFloat(version)
|
||||
));
|
||||
|
||||
if (!ot->isValidResponse(response)) {
|
||||
if (!CustomOpenTherm::isValidResponse(response)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
vars.parameters.masterOtVersion = ot->fromF88(response);
|
||||
vars.parameters.masterOtVersion = CustomOpenTherm::getFloat(response);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool updateSlaveVersion() {
|
||||
unsigned long response = ot->sendRequest(ot->buildRequest(
|
||||
unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
|
||||
OpenThermRequestType::READ_DATA,
|
||||
OpenThermMessageID::SlaveVersion,
|
||||
0
|
||||
));
|
||||
|
||||
if (!ot->isValidResponse(response)) {
|
||||
if (!CustomOpenTherm::isValidResponse(response)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -525,13 +603,13 @@ protected:
|
||||
}
|
||||
|
||||
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,
|
||||
OpenThermMessageID::MasterVersion,
|
||||
(unsigned int) version | (unsigned int) type << 8
|
||||
));
|
||||
|
||||
if (!ot->isValidResponse(response)) {
|
||||
if (!CustomOpenTherm::isValidResponse(response)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -542,13 +620,13 @@ protected:
|
||||
}
|
||||
|
||||
bool updateMinMaxDhwTemp() {
|
||||
unsigned long response = ot->sendRequest(ot->buildRequest(
|
||||
unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
|
||||
OpenThermRequestType::READ_DATA,
|
||||
OpenThermMessageID::TdhwSetUBTdhwSetLB,
|
||||
0
|
||||
));
|
||||
|
||||
if (!ot->isValidResponse(response)) {
|
||||
if (!CustomOpenTherm::isValidResponse(response)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -556,8 +634,8 @@ protected:
|
||||
byte maxTemp = (response & 0xFFFF) >> 8;
|
||||
|
||||
if (minTemp >= 0 && maxTemp > 0 && maxTemp > minTemp) {
|
||||
vars.parameters.dhwMinTemp = minTemp;
|
||||
vars.parameters.dhwMaxTemp = maxTemp;
|
||||
vars.parameters.dhwMinTemp = convertTemp(minTemp, settings.opentherm.unitSystem, settings.system.unitSystem);
|
||||
vars.parameters.dhwMaxTemp = convertTemp(maxTemp, settings.opentherm.unitSystem, settings.system.unitSystem);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -566,13 +644,13 @@ protected:
|
||||
}
|
||||
|
||||
bool updateMinMaxHeatingTemp() {
|
||||
unsigned long response = ot->sendRequest(ot->buildRequest(
|
||||
unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
|
||||
OpenThermRequestType::READ_DATA,
|
||||
OpenThermMessageID::MaxTSetUBMaxTSetLB,
|
||||
0
|
||||
));
|
||||
|
||||
if (!ot->isValidResponse(response)) {
|
||||
if (!CustomOpenTherm::isValidResponse(response)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -580,8 +658,8 @@ protected:
|
||||
byte maxTemp = (response & 0xFFFF) >> 8;
|
||||
|
||||
if (minTemp >= 0 && maxTemp > 0 && maxTemp > minTemp) {
|
||||
vars.parameters.heatingMinTemp = minTemp;
|
||||
vars.parameters.heatingMaxTemp = maxTemp;
|
||||
vars.parameters.heatingMinTemp = convertTemp(minTemp, settings.opentherm.unitSystem, settings.system.unitSystem);
|
||||
vars.parameters.heatingMaxTemp = convertTemp(maxTemp, settings.opentherm.unitSystem, settings.system.unitSystem);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -589,99 +667,164 @@ protected:
|
||||
}
|
||||
|
||||
bool setMaxHeatingTemp(byte value) {
|
||||
unsigned long response = ot->sendRequest(ot->buildRequest(
|
||||
unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
|
||||
OpenThermMessageType::WRITE_DATA,
|
||||
OpenThermMessageID::MaxTSet,
|
||||
ot->temperatureToData(value)
|
||||
CustomOpenTherm::temperatureToData(value)
|
||||
));
|
||||
|
||||
return ot->isValidResponse(response);
|
||||
return CustomOpenTherm::isValidResponse(response);
|
||||
}
|
||||
|
||||
bool updateOutsideTemp() {
|
||||
unsigned long response = ot->sendRequest(ot->buildRequest(
|
||||
unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
|
||||
OpenThermRequestType::READ_DATA,
|
||||
OpenThermMessageID::Toutside,
|
||||
0
|
||||
));
|
||||
|
||||
if (!ot->isValidResponse(response)) {
|
||||
if (!CustomOpenTherm::isValidResponse(response)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
vars.temperatures.outdoor = ot->getFloat(response) + settings.sensors.outdoor.offset;
|
||||
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;
|
||||
}
|
||||
|
||||
bool updateHeatingTemp() {
|
||||
unsigned long response = ot->sendRequest(ot->buildRequest(
|
||||
unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
|
||||
OpenThermMessageType::READ_DATA,
|
||||
OpenThermMessageID::Tboiler,
|
||||
0
|
||||
));
|
||||
|
||||
if (!ot->isValidResponse(response)) {
|
||||
if (!CustomOpenTherm::isValidResponse(response)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
float value = ot->getFloat(response);
|
||||
float value = CustomOpenTherm::getFloat(response);
|
||||
if (value <= 0) {
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
bool updateDhwTemp() {
|
||||
unsigned long response = ot->sendRequest(ot->buildRequest(
|
||||
unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
|
||||
OpenThermMessageType::READ_DATA,
|
||||
OpenThermMessageID::Tdhw,
|
||||
0
|
||||
));
|
||||
|
||||
if (!ot->isValidResponse(response)) {
|
||||
if (!CustomOpenTherm::isValidResponse(response)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
float value = ot->getFloat(response);
|
||||
float value = CustomOpenTherm::getFloat(response);
|
||||
if (value <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
vars.temperatures.dhw = value;
|
||||
vars.temperatures.dhw = convertTemp(
|
||||
value,
|
||||
settings.opentherm.unitSystem,
|
||||
settings.system.unitSystem
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool updateDhwFlowRate() {
|
||||
unsigned long response = ot->sendRequest(ot->buildRequest(
|
||||
unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
|
||||
OpenThermMessageType::READ_DATA,
|
||||
OpenThermMessageID::DHWFlowRate,
|
||||
0
|
||||
));
|
||||
|
||||
if (!ot->isValidResponse(response)) {
|
||||
if (!CustomOpenTherm::isValidResponse(response)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
float value = ot->getFloat(response);
|
||||
if (value > 16 && this->dhwFlowRateMultiplier != 10) {
|
||||
|
||||
float value = CustomOpenTherm::getFloat(response);
|
||||
if (this->dhwFlowRateMultiplier != 10 && value > convertVolume(16, UnitSystem::METRIC, settings.opentherm.unitSystem)) {
|
||||
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;
|
||||
}
|
||||
|
||||
bool updateFaultCode() {
|
||||
unsigned long response = ot->sendRequest(ot->buildRequest(
|
||||
unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
|
||||
OpenThermRequestType::READ_DATA,
|
||||
OpenThermMessageID::ASFflags,
|
||||
0
|
||||
));
|
||||
|
||||
if (!ot->isValidResponse(response)) {
|
||||
if (!CustomOpenTherm::isValidResponse(response)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -690,42 +833,42 @@ protected:
|
||||
}
|
||||
|
||||
bool updateModulationLevel() {
|
||||
unsigned long response = ot->sendRequest(ot->buildRequest(
|
||||
unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
|
||||
OpenThermRequestType::READ_DATA,
|
||||
OpenThermMessageID::RelModLevel,
|
||||
0
|
||||
));
|
||||
|
||||
if (!ot->isValidResponse(response)) {
|
||||
if (!CustomOpenTherm::isValidResponse(response)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
float modulation = ot->fromF88(response);
|
||||
if (!vars.states.flame) {
|
||||
vars.sensors.modulation = 0;
|
||||
} else {
|
||||
vars.sensors.modulation = modulation;
|
||||
}
|
||||
vars.sensors.modulation = CustomOpenTherm::getFloat(response);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool updatePressure() {
|
||||
unsigned long response = ot->sendRequest(ot->buildRequest(
|
||||
unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
|
||||
OpenThermRequestType::READ_DATA,
|
||||
OpenThermMessageID::CHPressure,
|
||||
0
|
||||
));
|
||||
|
||||
if (!ot->isValidResponse(response)) {
|
||||
if (!CustomOpenTherm::isValidResponse(response)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
float value = ot->getFloat(response);
|
||||
if (value > 5 && this->pressureMultiplier != 10) {
|
||||
float value = CustomOpenTherm::getFloat(response);
|
||||
if (this->pressureMultiplier != 10 && value > convertPressure(5, UnitSystem::METRIC, settings.opentherm.unitSystem)) {
|
||||
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;
|
||||
}
|
||||
|
||||
135
src/PortalTask.h
135
src/PortalTask.h
@@ -62,7 +62,7 @@ protected:
|
||||
}*/
|
||||
|
||||
int getTaskPriority() {
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
void setup() {
|
||||
@@ -86,9 +86,21 @@ protected:
|
||||
this->webServer->addHandler(indexPage);*/
|
||||
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
|
||||
this->webServer->on("/restart.html", HTTP_GET, [this]() {
|
||||
if (this->isNeedAuth()) {
|
||||
if (this->isAuthRequired()) {
|
||||
if (!this->webServer->authenticate(settings.portal.login, settings.portal.password)) {
|
||||
this->webServer->send(401);
|
||||
return;
|
||||
@@ -103,7 +115,7 @@ protected:
|
||||
// network settings page
|
||||
auto networkPage = (new StaticPage("/network.html", &LittleFS, "/network.html", PORTAL_CACHE))
|
||||
->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);
|
||||
return false;
|
||||
}
|
||||
@@ -115,7 +127,7 @@ protected:
|
||||
// settings page
|
||||
auto settingsPage = (new StaticPage("/settings.html", &LittleFS, "/settings.html", PORTAL_CACHE))
|
||||
->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);
|
||||
return false;
|
||||
}
|
||||
@@ -127,7 +139,7 @@ protected:
|
||||
// upgrade page
|
||||
auto upgradePage = (new StaticPage("/upgrade.html", &LittleFS, "/upgrade.html", PORTAL_CACHE))
|
||||
->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);
|
||||
return false;
|
||||
}
|
||||
@@ -138,7 +150,7 @@ protected:
|
||||
|
||||
// OTA
|
||||
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->send(401);
|
||||
return false;
|
||||
@@ -172,7 +184,7 @@ protected:
|
||||
|
||||
// backup
|
||||
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)) {
|
||||
return this->webServer->send(401);
|
||||
}
|
||||
@@ -196,7 +208,7 @@ protected:
|
||||
});
|
||||
|
||||
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)) {
|
||||
return this->webServer->send(401);
|
||||
}
|
||||
@@ -253,7 +265,7 @@ protected:
|
||||
|
||||
// network
|
||||
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)) {
|
||||
return this->webServer->send(401);
|
||||
}
|
||||
@@ -267,7 +279,7 @@ protected:
|
||||
});
|
||||
|
||||
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)) {
|
||||
return this->webServer->send(401);
|
||||
}
|
||||
@@ -298,8 +310,14 @@ protected:
|
||||
doc.clear();
|
||||
doc.shrinkToFit();
|
||||
|
||||
networkSettingsToJson(networkSettings, doc);
|
||||
doc.shrinkToFit();
|
||||
|
||||
this->bufferedWebServer->send(changed ? 201 : 200, "application/json", doc);
|
||||
|
||||
if (changed) {
|
||||
this->webServer->send(201);
|
||||
doc.clear();
|
||||
doc.shrinkToFit();
|
||||
|
||||
fsNetworkSettings.update();
|
||||
network->setHostname(networkSettings.hostname)
|
||||
@@ -312,33 +330,11 @@ protected:
|
||||
networkSettings.staticConfig.dns
|
||||
)
|
||||
->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]() {
|
||||
if (this->isNeedAuth()) {
|
||||
if (this->isAuthRequired()) {
|
||||
if (!this->webServer->authenticate(settings.portal.login, settings.portal.password)) {
|
||||
this->webServer->send(401);
|
||||
return;
|
||||
@@ -374,7 +370,7 @@ protected:
|
||||
|
||||
// settings
|
||||
this->webServer->on("/api/settings", HTTP_GET, [this]() {
|
||||
if (this->isNeedAuth()) {
|
||||
if (this->isAuthRequired()) {
|
||||
if (!this->webServer->authenticate(settings.portal.login, settings.portal.password)) {
|
||||
return this->webServer->send(401);
|
||||
}
|
||||
@@ -388,7 +384,7 @@ protected:
|
||||
});
|
||||
|
||||
this->webServer->on("/api/settings", HTTP_POST, [this]() {
|
||||
if (this->isNeedAuth()) {
|
||||
if (this->isAuthRequired()) {
|
||||
if (!this->webServer->authenticate(settings.portal.login, settings.portal.password)) {
|
||||
return this->webServer->send(401);
|
||||
}
|
||||
@@ -419,12 +415,15 @@ protected:
|
||||
doc.clear();
|
||||
doc.shrinkToFit();
|
||||
|
||||
if (changed) {
|
||||
fsSettings.update();
|
||||
this->webServer->send(201);
|
||||
settingsToJson(settings, doc);
|
||||
doc.shrinkToFit();
|
||||
|
||||
} else {
|
||||
this->webServer->send(200);
|
||||
this->bufferedWebServer->send(changed ? 201 : 200, "application/json", doc);
|
||||
|
||||
if (changed) {
|
||||
doc.clear();
|
||||
doc.shrinkToFit();
|
||||
fsSettings.update();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -433,24 +432,13 @@ protected:
|
||||
this->webServer->on("/api/vars", HTTP_GET, [this]() {
|
||||
JsonDocument 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();
|
||||
|
||||
this->bufferedWebServer->send(200, "application/json", doc);
|
||||
});
|
||||
|
||||
this->webServer->on("/api/vars", HTTP_POST, [this]() {
|
||||
if (this->isNeedAuth()) {
|
||||
if (this->isAuthRequired()) {
|
||||
if (!this->webServer->authenticate(settings.portal.login, settings.portal.password)) {
|
||||
return this->webServer->send(401);
|
||||
}
|
||||
@@ -481,12 +469,39 @@ protected:
|
||||
doc.clear();
|
||||
doc.shrinkToFit();
|
||||
|
||||
if (changed) {
|
||||
this->webServer->send(201);
|
||||
varsToJson(vars, doc);
|
||||
doc.shrinkToFit();
|
||||
|
||||
this->bufferedWebServer->send(changed ? 201 : 200, "application/json", doc);
|
||||
});
|
||||
|
||||
} else {
|
||||
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() {
|
||||
return !network->isApEnabled() && settings.portal.useAuth && strlen(settings.portal.password);
|
||||
bool isAuthRequired() {
|
||||
return !network->isApEnabled() && settings.portal.auth && strlen(settings.portal.password);
|
||||
}
|
||||
|
||||
void onCaptivePortal() {
|
||||
|
||||
@@ -69,7 +69,7 @@ protected:
|
||||
}
|
||||
}
|
||||
|
||||
// Ограничиваем, если до этого не ограничило
|
||||
// Limits
|
||||
if (newTemp < settings.heating.minTemp || newTemp > settings.heating.maxTemp) {
|
||||
newTemp = constrain(newTemp, settings.heating.minTemp, settings.heating.maxTemp);
|
||||
}
|
||||
@@ -84,7 +84,7 @@ protected:
|
||||
float newTemp = 0;
|
||||
|
||||
// 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);
|
||||
|
||||
if (fabs(prevEtResult - etResult) + 0.0001 >= 0.5) {
|
||||
@@ -97,7 +97,7 @@ protected:
|
||||
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) {
|
||||
float pidResult = getPidTemp(
|
||||
settings.heating.minTemp,
|
||||
@@ -182,7 +182,6 @@ protected:
|
||||
}
|
||||
|
||||
newTemp = round(newTemp);
|
||||
newTemp = constrain(newTemp, 0, 100);
|
||||
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 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) {
|
||||
etRegulator.Kt = 0;
|
||||
etRegulator.indoorTemp = 0;
|
||||
etRegulator.outdoorTemp = vars.temperatures.outdoor;
|
||||
etRegulator.outdoorTemp = outdoorTemp;
|
||||
|
||||
} else if (settings.pid.enable) {
|
||||
etRegulator.Kt = 0;
|
||||
etRegulator.indoorTemp = round(vars.temperatures.indoor);
|
||||
etRegulator.outdoorTemp = round(vars.temperatures.outdoor);
|
||||
etRegulator.indoorTemp = round(indoorTemp);
|
||||
etRegulator.outdoorTemp = round(outdoorTemp);
|
||||
|
||||
} else {
|
||||
if (settings.heating.turbo) {
|
||||
@@ -290,17 +309,21 @@ protected:
|
||||
} else {
|
||||
etRegulator.Kt = settings.equitherm.t_factor;
|
||||
}
|
||||
etRegulator.indoorTemp = vars.temperatures.indoor;
|
||||
etRegulator.outdoorTemp = vars.temperatures.outdoor;
|
||||
etRegulator.indoorTemp = indoorTemp;
|
||||
etRegulator.outdoorTemp = outdoorTemp;
|
||||
}
|
||||
|
||||
etRegulator.setLimits(minTemp, maxTemp);
|
||||
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.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) {
|
||||
|
||||
@@ -32,11 +32,13 @@ protected:
|
||||
DallasTemperature* indoorSensor = nullptr;
|
||||
|
||||
bool initOutdoorSensor = false;
|
||||
unsigned long initOutdoorSensorTime = 0;
|
||||
unsigned long startOutdoorConversionTime = 0;
|
||||
float filteredOutdoorTemp = 0;
|
||||
bool emptyOutdoorTemp = true;
|
||||
|
||||
bool initIndoorSensor = false;
|
||||
unsigned long initIndoorSensorTime = 0;
|
||||
unsigned long startIndoorConversionTime = 0;
|
||||
float filteredIndoorTemp = 0;
|
||||
bool emptyIndoorTemp = true;
|
||||
@@ -60,39 +62,58 @@ protected:
|
||||
}
|
||||
|
||||
void loop() {
|
||||
bool needUpdateIndoorTemp = false;
|
||||
bool needUpdateOutdoorTemp = false;
|
||||
bool indoorTempUpdated = false;
|
||||
bool outdoorTempUpdated = false;
|
||||
|
||||
if (settings.sensors.outdoor.type == 2 && settings.sensors.outdoor.pin) {
|
||||
if (settings.sensors.outdoor.type == SensorType::DS18B20 && GPIO_IS_VALID(settings.sensors.indoor.gpio)) {
|
||||
outdoorTemperatureSensor();
|
||||
needUpdateOutdoorTemp = true;
|
||||
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();
|
||||
needUpdateIndoorTemp = true;
|
||||
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 (settings.sensors.indoor.type == 3) {
|
||||
bluetoothSensor();
|
||||
needUpdateIndoorTemp = true;
|
||||
}
|
||||
#endif
|
||||
if (indoorTempUpdated) {
|
||||
float newTemp = settings.sensors.indoor.offset;
|
||||
if (settings.system.unitSystem == UnitSystem::METRIC) {
|
||||
newTemp += this->filteredIndoorTemp;
|
||||
|
||||
if (needUpdateOutdoorTemp && fabs(vars.temperatures.outdoor - this->filteredOutdoorTemp) > 0.099) {
|
||||
vars.temperatures.outdoor = this->filteredOutdoorTemp + settings.sensors.outdoor.offset;
|
||||
Log.sinfoln(FPSTR(L_SENSORS_OUTDOOR), F("New temp: %f"), vars.temperatures.outdoor);
|
||||
}
|
||||
} else if (settings.system.unitSystem == UnitSystem::IMPERIAL) {
|
||||
newTemp += c2f(this->filteredIndoorTemp);
|
||||
}
|
||||
|
||||
if (needUpdateIndoorTemp && fabs(vars.temperatures.indoor - this->filteredIndoorTemp) > 0.099) {
|
||||
vars.temperatures.indoor = this->filteredIndoorTemp + settings.sensors.indoor.offset;
|
||||
Log.sinfoln(FPSTR(L_SENSORS_INDOOR), F("New temp: %f"), vars.temperatures.indoor);
|
||||
if (fabs(vars.temperatures.indoor - newTemp) > 0.099) {
|
||||
vars.temperatures.indoor = newTemp;
|
||||
Log.sinfoln(FPSTR(L_SENSORS_INDOOR), F("New temp: %f"), vars.temperatures.indoor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if USE_BLE
|
||||
void bluetoothSensor() {
|
||||
void indoorTemperatureBluetoothSensor() {
|
||||
static bool initBleNotify = false;
|
||||
if (!initBleSensor && millis() > 5000) {
|
||||
Log.sinfoln(FPSTR(L_SENSORS_BLE), F("Init BLE"));
|
||||
@@ -210,10 +231,16 @@ protected:
|
||||
|
||||
void outdoorTemperatureSensor() {
|
||||
if (!this->initOutdoorSensor) {
|
||||
Log.sinfoln(FPSTR(L_SENSORS_OUTDOOR), F("Starting on gpio %hhu..."), settings.sensors.outdoor.pin);
|
||||
if (this->initOutdoorSensorTime && millis() - this->initOutdoorSensorTime < EXT_SENSORS_INTERVAL * 10) {
|
||||
return;
|
||||
}
|
||||
|
||||
this->oneWireOutdoorSensor->begin(settings.sensors.outdoor.pin);
|
||||
Log.sinfoln(FPSTR(L_SENSORS_OUTDOOR), F("Starting on gpio %hhu..."), settings.sensors.outdoor.gpio);
|
||||
|
||||
this->oneWireOutdoorSensor->begin(settings.sensors.outdoor.gpio);
|
||||
this->oneWireOutdoorSensor->reset();
|
||||
this->outdoorSensor->begin();
|
||||
this->initOutdoorSensorTime = millis();
|
||||
|
||||
Log.straceln(
|
||||
FPSTR(L_SENSORS_OUTDOOR),
|
||||
@@ -276,10 +303,16 @@ protected:
|
||||
|
||||
void indoorTemperatureSensor() {
|
||||
if (!this->initIndoorSensor) {
|
||||
Log.sinfoln(FPSTR(L_SENSORS_INDOOR), F("Starting on gpio %hhu..."), settings.sensors.indoor.pin);
|
||||
if (this->initIndoorSensorTime && millis() - this->initIndoorSensorTime < EXT_SENSORS_INTERVAL * 10) {
|
||||
return;
|
||||
}
|
||||
|
||||
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->oneWireIndoorSensor->reset();
|
||||
this->indoorSensor->begin();
|
||||
this->initIndoorSensorTime = millis();
|
||||
|
||||
Log.straceln(
|
||||
FPSTR(L_SENSORS_INDOOR),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
struct NetworkSettings {
|
||||
char hostname[25] = HOSTNAME_DEFAULT;
|
||||
char hostname[25] = DEFAULT_HOSTNAME;
|
||||
bool useDhcp = true;
|
||||
|
||||
struct {
|
||||
@@ -10,14 +10,14 @@ struct NetworkSettings {
|
||||
} staticConfig;
|
||||
|
||||
struct {
|
||||
char ssid[33] = AP_SSID_DEFAULT;
|
||||
char password[65] = AP_PASSWORD_DEFAULT;
|
||||
char ssid[33] = DEFAULT_AP_SSID;
|
||||
char password[65] = DEFAULT_AP_PASSWORD;
|
||||
byte channel = 6;
|
||||
} ap;
|
||||
|
||||
struct {
|
||||
char ssid[33] = STA_SSID_DEFAULT;
|
||||
char password[65] = STA_PASSWORD_DEFAULT;
|
||||
char ssid[33] = DEFAULT_STA_SSID;
|
||||
char password[65] = DEFAULT_STA_PASSWORD;
|
||||
byte channel = 0;
|
||||
} sta;
|
||||
} networkSettings;
|
||||
@@ -25,19 +25,32 @@ struct NetworkSettings {
|
||||
struct Settings {
|
||||
struct {
|
||||
bool debug = DEBUG_BY_DEFAULT;
|
||||
bool useSerial = USE_SERIAL;
|
||||
bool useTelnet = USE_TELNET;
|
||||
|
||||
struct {
|
||||
bool enable = USE_SERIAL;
|
||||
unsigned int baudrate = 115200;
|
||||
} serial;
|
||||
|
||||
struct {
|
||||
bool enable = USE_TELNET;
|
||||
unsigned short port = 23;
|
||||
} telnet;
|
||||
|
||||
UnitSystem unitSystem = UnitSystem::METRIC;
|
||||
byte statusLedGpio = DEFAULT_STATUS_LED_GPIO;
|
||||
} system;
|
||||
|
||||
struct {
|
||||
bool useAuth = false;
|
||||
char login[13] = PORTAL_LOGIN_DEFAULT;
|
||||
char password[33] = PORTAL_PASSWORD_DEFAULT;
|
||||
bool auth = false;
|
||||
char login[13] = DEFAULT_PORTAL_LOGIN;
|
||||
char password[33] = DEFAULT_PORTAL_PASSWORD;
|
||||
} portal;
|
||||
|
||||
struct {
|
||||
byte inPin = OT_IN_PIN_DEFAULT;
|
||||
byte outPin = OT_OUT_PIN_DEFAULT;
|
||||
UnitSystem unitSystem = UnitSystem::METRIC;
|
||||
byte inGpio = DEFAULT_OT_IN_GPIO;
|
||||
byte outGpio = DEFAULT_OT_OUT_GPIO;
|
||||
byte rxLedGpio = DEFAULT_OT_RX_LED_GPIO;
|
||||
unsigned int memberIdCode = 0;
|
||||
bool dhwPresent = true;
|
||||
bool summerWinterMode = false;
|
||||
@@ -46,22 +59,27 @@ struct Settings {
|
||||
bool dhwToCh2 = false;
|
||||
bool dhwBlocking = false;
|
||||
bool modulationSyncWithHeating = false;
|
||||
bool getMinMaxTemp = true;
|
||||
} opentherm;
|
||||
|
||||
struct {
|
||||
char server[81] = MQTT_SERVER_DEFAULT;
|
||||
unsigned short port = MQTT_PORT_DEFAULT;
|
||||
char user[33] = MQTT_USER_DEFAULT;
|
||||
char password[33] = MQTT_PASSWORD_DEFAULT;
|
||||
char prefix[33] = MQTT_PREFIX_DEFAULT;
|
||||
bool enable = false;
|
||||
char server[81] = DEFAULT_MQTT_SERVER;
|
||||
unsigned short port = DEFAULT_MQTT_PORT;
|
||||
char user[33] = DEFAULT_MQTT_USER;
|
||||
char password[33] = DEFAULT_MQTT_PASSWORD;
|
||||
char prefix[33] = DEFAULT_MQTT_PREFIX;
|
||||
unsigned short interval = 5;
|
||||
} mqtt;
|
||||
|
||||
struct {
|
||||
bool enable = true;
|
||||
float target = 40.0f;
|
||||
unsigned short tresholdTime = 120;
|
||||
bool useEquitherm = false;
|
||||
bool usePid = false;
|
||||
bool onNetworkFault = true;
|
||||
bool onMqttFault = true;
|
||||
} emergency;
|
||||
|
||||
struct {
|
||||
@@ -100,16 +118,14 @@ struct Settings {
|
||||
|
||||
struct {
|
||||
struct {
|
||||
// 0 - boiler, 1 - manual, 2 - ds18b20
|
||||
byte type = 0;
|
||||
byte pin = SENSOR_OUTDOOR_PIN_DEFAULT;
|
||||
SensorType type = SensorType::BOILER;
|
||||
byte gpio = DEFAULT_SENSOR_OUTDOOR_GPIO;
|
||||
float offset = 0.0f;
|
||||
} outdoor;
|
||||
|
||||
struct {
|
||||
// 1 - manual, 2 - ds18b20, 3 - ble
|
||||
byte type = 1;
|
||||
byte pin = SENSOR_INDOOR_PIN_DEFAULT;
|
||||
SensorType type = SensorType::MANUAL;
|
||||
byte gpio = DEFAULT_SENSOR_INDOOR_GPIO;
|
||||
uint8_t bleAddresss[6] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
float offset = 0.0f;
|
||||
} indoor;
|
||||
@@ -117,7 +133,7 @@ struct Settings {
|
||||
|
||||
struct {
|
||||
bool use = false;
|
||||
byte pin = EXT_PUMP_PIN_DEFAULT;
|
||||
byte gpio = DEFAULT_EXT_PUMP_GPIO;
|
||||
unsigned short postCirculationTime = 600;
|
||||
unsigned int antiStuckInterval = 2592000;
|
||||
unsigned short antiStuckTime = 300;
|
||||
@@ -141,6 +157,7 @@ struct Variables {
|
||||
bool fault = false;
|
||||
bool diagnostic = false;
|
||||
bool externalPump = false;
|
||||
bool mqtt = false;
|
||||
} states;
|
||||
|
||||
struct {
|
||||
@@ -155,7 +172,9 @@ struct Variables {
|
||||
float indoor = 0.0f;
|
||||
float outdoor = 0.0f;
|
||||
float heating = 0.0f;
|
||||
float heatingReturn = 0.0f;
|
||||
float dhw = 0.0f;
|
||||
float exhaust = 0.0f;
|
||||
} temperatures;
|
||||
|
||||
struct {
|
||||
|
||||
106
src/defines.h
106
src/defines.h
@@ -1,12 +1,8 @@
|
||||
#define PROJECT_NAME "OpenTherm Gateway"
|
||||
#define PROJECT_VERSION "1.4.0-rc.16"
|
||||
#define PROJECT_VERSION "1.4.0-rc.22"
|
||||
#define PROJECT_REPO "https://github.com/Laxilef/OTGateway"
|
||||
|
||||
#define EMERGENCY_TIME_TRESHOLD 120000
|
||||
#define MQTT_RECONNECT_INTERVAL 15000
|
||||
#define MQTT_KEEPALIVE 30
|
||||
|
||||
#define OPENTHERM_OFFLINE_TRESHOLD 10
|
||||
|
||||
#define EXT_SENSORS_INTERVAL 5000
|
||||
#define EXT_SENSORS_FILTER_K 0.15
|
||||
@@ -14,16 +10,12 @@
|
||||
#define CONFIG_URL "http://%s/"
|
||||
#define SETTINGS_VALID_VALUE "stvalid" // only 8 chars!
|
||||
|
||||
#define GPIO_IS_NOT_CONFIGURED 0xff
|
||||
#define DEFAULT_HEATING_MIN_TEMP 20
|
||||
#define DEFAULT_HEATING_MAX_TEMP 90
|
||||
#define DEFAULT_DHW_MIN_TEMP 30
|
||||
#define DEFAULT_DHW_MAX_TEMP 60
|
||||
|
||||
|
||||
#ifndef WM_DEBUG_MODE
|
||||
#define WM_DEBUG_MODE WM_DEBUG_NOTIFY
|
||||
#endif
|
||||
|
||||
#ifndef USE_SERIAL
|
||||
#define USE_SERIAL true
|
||||
#endif
|
||||
@@ -36,80 +28,106 @@
|
||||
#define USE_BLE false
|
||||
#endif
|
||||
|
||||
#ifndef HOSTNAME_DEFAULT
|
||||
#define HOSTNAME_DEFAULT "opentherm"
|
||||
#ifndef DEFAULT_HOSTNAME
|
||||
#define DEFAULT_HOSTNAME "opentherm"
|
||||
#endif
|
||||
|
||||
#ifndef AP_SSID_DEFAULT
|
||||
#define AP_SSID_DEFAULT "OpenTherm Gateway"
|
||||
#ifndef DEFAULT_AP_SSID
|
||||
#define DEFAULT_AP_SSID "OpenTherm Gateway"
|
||||
#endif
|
||||
|
||||
#ifndef AP_PASSWORD_DEFAULT
|
||||
#define AP_PASSWORD_DEFAULT "otgateway123456"
|
||||
#ifndef DEFAULT_AP_PASSWORD
|
||||
#define DEFAULT_AP_PASSWORD "otgateway123456"
|
||||
#endif
|
||||
|
||||
#ifndef STA_SSID_DEFAULT
|
||||
#define STA_SSID_DEFAULT ""
|
||||
#ifndef DEFAULT_STA_SSID
|
||||
#define DEFAULT_STA_SSID ""
|
||||
#endif
|
||||
|
||||
#ifndef STA_PASSWORD_DEFAULT
|
||||
#define STA_PASSWORD_DEFAULT ""
|
||||
#ifndef DEFAULT_STA_PASSWORD
|
||||
#define DEFAULT_STA_PASSWORD ""
|
||||
#endif
|
||||
|
||||
#ifndef DEBUG_BY_DEFAULT
|
||||
#define DEBUG_BY_DEFAULT false
|
||||
#endif
|
||||
|
||||
#ifndef PORTAL_LOGIN_DEFAULT
|
||||
#define PORTAL_LOGIN_DEFAULT ""
|
||||
#ifndef DEFAULT_STATUS_LED_GPIO
|
||||
#define DEFAULT_STATUS_LED_GPIO GPIO_IS_NOT_CONFIGURED
|
||||
#endif
|
||||
|
||||
#ifndef PORTAL_PASSWORD_DEFAULT
|
||||
#define PORTAL_PASSWORD_DEFAULT ""
|
||||
#ifndef DEFAULT_PORTAL_LOGIN
|
||||
#define DEFAULT_PORTAL_LOGIN ""
|
||||
#endif
|
||||
|
||||
#ifndef MQTT_SERVER_DEFAULT
|
||||
#define MQTT_SERVER_DEFAULT ""
|
||||
#ifndef DEFAULT_PORTAL_PASSWORD
|
||||
#define DEFAULT_PORTAL_PASSWORD ""
|
||||
#endif
|
||||
|
||||
#ifndef MQTT_PORT_DEFAULT
|
||||
#define MQTT_PORT_DEFAULT 1883
|
||||
#ifndef DEFAULT_MQTT_SERVER
|
||||
#define DEFAULT_MQTT_SERVER ""
|
||||
#endif
|
||||
|
||||
#ifndef MQTT_USER_DEFAULT
|
||||
#define MQTT_USER_DEFAULT ""
|
||||
#ifndef DEFAULT_MQTT_PORT
|
||||
#define DEFAULT_MQTT_PORT 1883
|
||||
#endif
|
||||
|
||||
#ifndef MQTT_PASSWORD_DEFAULT
|
||||
#define MQTT_PASSWORD_DEFAULT ""
|
||||
#ifndef DEFAULT_MQTT_USER
|
||||
#define DEFAULT_MQTT_USER ""
|
||||
#endif
|
||||
|
||||
#ifndef MQTT_PREFIX_DEFAULT
|
||||
#define MQTT_PREFIX_DEFAULT "opentherm"
|
||||
#ifndef DEFAULT_MQTT_PASSWORD
|
||||
#define DEFAULT_MQTT_PASSWORD ""
|
||||
#endif
|
||||
|
||||
#ifndef OT_IN_PIN_DEFAULT
|
||||
#define OT_IN_PIN_DEFAULT 0
|
||||
#ifndef DEFAULT_MQTT_PREFIX
|
||||
#define DEFAULT_MQTT_PREFIX "opentherm"
|
||||
#endif
|
||||
|
||||
#ifndef OT_OUT_PIN_DEFAULT
|
||||
#define OT_OUT_PIN_DEFAULT 0
|
||||
#ifndef DEFAULT_OT_IN_GPIO
|
||||
#define DEFAULT_OT_IN_GPIO GPIO_IS_NOT_CONFIGURED
|
||||
#endif
|
||||
|
||||
#ifndef SENSOR_OUTDOOR_PIN_DEFAULT
|
||||
#define SENSOR_OUTDOOR_PIN_DEFAULT 0
|
||||
#ifndef DEFAULT_OT_OUT_GPIO
|
||||
#define DEFAULT_OT_OUT_GPIO GPIO_IS_NOT_CONFIGURED
|
||||
#endif
|
||||
|
||||
#ifndef SENSOR_INDOOR_PIN_DEFAULT
|
||||
#define SENSOR_INDOOR_PIN_DEFAULT 0
|
||||
#ifndef DEFAULT_OT_RX_LED_GPIO
|
||||
#define DEFAULT_OT_RX_LED_GPIO GPIO_IS_NOT_CONFIGURED
|
||||
#endif
|
||||
|
||||
#ifndef EXT_PUMP_PIN_DEFAULT
|
||||
#define EXT_PUMP_PIN_DEFAULT 0
|
||||
#ifndef DEFAULT_SENSOR_OUTDOOR_GPIO
|
||||
#define DEFAULT_SENSOR_OUTDOOR_GPIO GPIO_IS_NOT_CONFIGURED
|
||||
#endif
|
||||
|
||||
#ifndef DEFAULT_SENSOR_INDOOR_GPIO
|
||||
#define DEFAULT_SENSOR_INDOOR_GPIO GPIO_IS_NOT_CONFIGURED
|
||||
#endif
|
||||
|
||||
#ifndef DEFAULT_EXT_PUMP_GPIO
|
||||
#define DEFAULT_EXT_PUMP_GPIO GPIO_IS_NOT_CONFIGURED
|
||||
#endif
|
||||
|
||||
#ifndef PROGMEM
|
||||
#define PROGMEM
|
||||
#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];
|
||||
@@ -59,7 +59,7 @@ void setup() {
|
||||
return tm{sec, min, hour};
|
||||
});
|
||||
|
||||
Serial.begin(115200);
|
||||
Serial.begin(settings.system.serial.baudrate);
|
||||
Log.addStream(&Serial);
|
||||
Log.print("\n\n\r");
|
||||
|
||||
@@ -109,12 +109,12 @@ void setup() {
|
||||
}
|
||||
|
||||
// logs
|
||||
if (!settings.system.useSerial) {
|
||||
if (!settings.system.serial.enable) {
|
||||
Log.clearStreams();
|
||||
Serial.end();
|
||||
}
|
||||
|
||||
if (settings.system.useTelnet) {
|
||||
if (settings.system.telnet.enable) {
|
||||
telnetStream = new ESPTelnetStream;
|
||||
telnetStream->setKeepAliveInterval(500);
|
||||
Log.addStream(telnetStream);
|
||||
@@ -143,7 +143,7 @@ void setup() {
|
||||
tMqtt = new MqttTask(false, 500);
|
||||
Scheduler.start(tMqtt);
|
||||
|
||||
tOt = new OpenThermTask(false, 750);
|
||||
tOt = new OpenThermTask(true, 750);
|
||||
Scheduler.start(tOt);
|
||||
|
||||
tSensors = new SensorsTask(true, EXT_SENSORS_INTERVAL);
|
||||
|
||||
627
src/utils.h
627
src/utils.h
@@ -1,5 +1,66 @@
|
||||
#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) {
|
||||
if (decimals == 0) {
|
||||
return (int)(value + 0.5);
|
||||
@@ -13,7 +74,7 @@ double roundd(double value, uint8_t decimals = 2) {
|
||||
return (int)(value * multiplier) / multiplier;
|
||||
}
|
||||
|
||||
size_t getTotalHeap() {
|
||||
inline size_t getTotalHeap() {
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
return ESP.getHeapSize();
|
||||
#elif defined(ARDUINO_ARCH_ESP8266)
|
||||
@@ -63,7 +124,7 @@ size_t getMaxFreeBlockHeap(bool getMinValue = false) {
|
||||
return getMinValue ? minValue : value;
|
||||
}
|
||||
|
||||
uint8_t getHeapFrag() {
|
||||
inline uint8_t getHeapFrag() {
|
||||
return 100 - getMaxFreeBlockHeap() * 100.0 / getFreeHeap();
|
||||
}
|
||||
|
||||
@@ -266,15 +327,21 @@ bool jsonToNetworkSettings(const JsonVariantConst src, NetworkSettings& dst) {
|
||||
void settingsToJson(const Settings& src, JsonVariant dst, bool safe = false) {
|
||||
if (!safe) {
|
||||
dst["system"]["debug"] = src.system.debug;
|
||||
dst["system"]["useSerial"] = src.system.useSerial;
|
||||
dst["system"]["useTelnet"] = src.system.useTelnet;
|
||||
dst["system"]["serial"]["enable"] = src.system.serial.enable;
|
||||
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["system"]["statusLedGpio"] = src.system.statusLedGpio;
|
||||
|
||||
dst["portal"]["useAuth"] = src.portal.useAuth;
|
||||
dst["portal"]["auth"] = src.portal.auth;
|
||||
dst["portal"]["login"] = src.portal.login;
|
||||
dst["portal"]["password"] = src.portal.password;
|
||||
|
||||
dst["opentherm"]["inPin"] = src.opentherm.inPin;
|
||||
dst["opentherm"]["outPin"] = src.opentherm.outPin;
|
||||
dst["opentherm"]["unitSystem"] = static_cast<byte>(src.opentherm.unitSystem);
|
||||
dst["opentherm"]["inGpio"] = src.opentherm.inGpio;
|
||||
dst["opentherm"]["outGpio"] = src.opentherm.outGpio;
|
||||
dst["opentherm"]["rxLedGpio"] = src.opentherm.rxLedGpio;
|
||||
dst["opentherm"]["memberIdCode"] = src.opentherm.memberIdCode;
|
||||
dst["opentherm"]["dhwPresent"] = src.opentherm.dhwPresent;
|
||||
dst["opentherm"]["summerWinterMode"] = src.opentherm.summerWinterMode;
|
||||
@@ -283,7 +350,9 @@ void settingsToJson(const Settings& src, JsonVariant dst, bool safe = false) {
|
||||
dst["opentherm"]["dhwToCh2"] = src.opentherm.dhwToCh2;
|
||||
dst["opentherm"]["dhwBlocking"] = src.opentherm.dhwBlocking;
|
||||
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"]["port"] = src.mqtt.port;
|
||||
dst["mqtt"]["user"] = src.mqtt.user;
|
||||
@@ -294,8 +363,11 @@ void settingsToJson(const Settings& src, JsonVariant dst, bool safe = false) {
|
||||
|
||||
dst["emergency"]["enable"] = src.emergency.enable;
|
||||
dst["emergency"]["target"] = roundd(src.emergency.target, 2);
|
||||
dst["emergency"]["tresholdTime"] = src.emergency.tresholdTime;
|
||||
dst["emergency"]["useEquitherm"] = src.emergency.useEquitherm;
|
||||
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"]["turbo"] = src.heating.turbo;
|
||||
@@ -323,12 +395,12 @@ void settingsToJson(const Settings& src, JsonVariant dst, bool safe = false) {
|
||||
dst["equitherm"]["k_factor"] = roundd(src.equitherm.k_factor, 3);
|
||||
dst["equitherm"]["t_factor"] = roundd(src.equitherm.t_factor, 3);
|
||||
|
||||
dst["sensors"]["outdoor"]["type"] = src.sensors.outdoor.type;
|
||||
dst["sensors"]["outdoor"]["pin"] = src.sensors.outdoor.pin;
|
||||
dst["sensors"]["outdoor"]["type"] = static_cast<byte>(src.sensors.outdoor.type);
|
||||
dst["sensors"]["outdoor"]["gpio"] = src.sensors.outdoor.gpio;
|
||||
dst["sensors"]["outdoor"]["offset"] = roundd(src.sensors.outdoor.offset, 2);
|
||||
|
||||
dst["sensors"]["indoor"]["type"] = src.sensors.indoor.type;
|
||||
dst["sensors"]["indoor"]["pin"] = src.sensors.indoor.pin;
|
||||
dst["sensors"]["indoor"]["type"] = static_cast<byte>(src.sensors.indoor.type);
|
||||
dst["sensors"]["indoor"]["gpio"] = src.sensors.indoor.gpio;
|
||||
|
||||
char bleAddress[18];
|
||||
sprintf(
|
||||
@@ -346,7 +418,7 @@ void settingsToJson(const Settings& src, JsonVariant dst, bool safe = false) {
|
||||
|
||||
if (!safe) {
|
||||
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"]["antiStuckInterval"] = roundd(src.externalPump.antiStuckInterval / 86400, 0);
|
||||
dst["externalPump"]["antiStuckTime"] = roundd(src.externalPump.antiStuckTime / 60, 0);
|
||||
@@ -367,20 +439,88 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (src["system"]["useSerial"].is<bool>()) {
|
||||
dst.system.useSerial = src["system"]["useSerial"].as<bool>();
|
||||
if (src["system"]["serial"]["enable"].is<bool>()) {
|
||||
dst.system.serial.enable = src["system"]["serial"]["enable"].as<bool>();
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (src["system"]["useTelnet"].is<bool>()) {
|
||||
dst.system.useTelnet = src["system"]["useTelnet"].as<bool>();
|
||||
if (!src["system"]["serial"]["baudrate"].isNull()) {
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
if (!src["system"]["statusLedGpio"].isNull()) {
|
||||
if (src["system"]["statusLedGpio"].is<JsonString>() && src["system"]["statusLedGpio"].as<JsonString>().size() == 0) {
|
||||
if (dst.system.statusLedGpio != GPIO_IS_NOT_CONFIGURED) {
|
||||
dst.system.statusLedGpio = GPIO_IS_NOT_CONFIGURED;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
} else {
|
||||
unsigned char value = src["system"]["statusLedGpio"].as<unsigned char>();
|
||||
|
||||
if (value >= 0 && value <= 254) {
|
||||
dst.system.statusLedGpio = value;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// portal
|
||||
if (src["portal"]["useAuth"].is<bool>()) {
|
||||
dst.portal.useAuth = src["portal"]["useAuth"].as<bool>();
|
||||
if (src["portal"]["auth"].is<bool>()) {
|
||||
dst.portal.auth = src["portal"]["auth"].as<bool>();
|
||||
changed = true;
|
||||
}
|
||||
|
||||
@@ -404,21 +544,73 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false
|
||||
|
||||
|
||||
// opentherm
|
||||
if (!src["opentherm"]["inPin"].isNull()) {
|
||||
unsigned char value = src["opentherm"]["inPin"].as<unsigned char>();
|
||||
if (!src["opentherm"]["unitSystem"].isNull()) {
|
||||
byte value = src["opentherm"]["unitSystem"].as<unsigned char>();
|
||||
|
||||
if (value >= 0 && value < 50) {
|
||||
dst.opentherm.inPin = value;
|
||||
changed = true;
|
||||
switch (value) {
|
||||
case static_cast<byte>(UnitSystem::METRIC):
|
||||
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"]["inGpio"].isNull()) {
|
||||
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;
|
||||
}
|
||||
|
||||
} else {
|
||||
unsigned char value = src["opentherm"]["inGpio"].as<unsigned char>();
|
||||
|
||||
if (value >= 0 && value <= 254) {
|
||||
dst.opentherm.inGpio = value;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!src["opentherm"]["outPin"].isNull()) {
|
||||
unsigned char value = src["opentherm"]["outPin"].as<unsigned char>();
|
||||
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 < 50) {
|
||||
dst.opentherm.outPin = value;
|
||||
changed = true;
|
||||
if (value >= 0 && value <= 254) {
|
||||
dst.opentherm.outGpio = value;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!src["opentherm"]["rxLedGpio"].isNull()) {
|
||||
if (src["opentherm"]["rxLedGpio"].is<JsonString>() && src["opentherm"]["rxLedGpio"].as<JsonString>().size() == 0) {
|
||||
if (dst.opentherm.rxLedGpio != GPIO_IS_NOT_CONFIGURED) {
|
||||
dst.opentherm.rxLedGpio = GPIO_IS_NOT_CONFIGURED;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
} else {
|
||||
unsigned char value = src["opentherm"]["rxLedGpio"].as<unsigned char>();
|
||||
|
||||
if (value >= 0 && value <= 254) {
|
||||
dst.opentherm.rxLedGpio = value;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -484,8 +676,18 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (src["opentherm"]["getMinMaxTemp"].is<bool>()) {
|
||||
dst.opentherm.getMinMaxTemp = src["opentherm"]["getMinMaxTemp"].as<bool>();
|
||||
changed = true;
|
||||
}
|
||||
|
||||
|
||||
// mqtt
|
||||
if (src["mqtt"]["enable"].is<bool>()) {
|
||||
dst.mqtt.enable = src["mqtt"]["enable"].as<bool>();
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (!src["mqtt"]["server"].isNull()) {
|
||||
String value = src["mqtt"]["server"].as<String>();
|
||||
|
||||
@@ -548,17 +750,17 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (!src["emergency"]["target"].isNull()) {
|
||||
double value = src["emergency"]["target"].as<double>();
|
||||
if (!src["emergency"]["tresholdTime"].isNull()) {
|
||||
unsigned short value = src["emergency"]["tresholdTime"].as<unsigned short>();
|
||||
|
||||
if (value > 0 && value < 100) {
|
||||
dst.emergency.target = roundd(value, 2);
|
||||
changed = true;
|
||||
if (value >= 60 && value <= 1800) {
|
||||
dst.emergency.tresholdTime = value;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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>();
|
||||
|
||||
} else {
|
||||
@@ -573,7 +775,7 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false
|
||||
}
|
||||
|
||||
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>();
|
||||
|
||||
} else {
|
||||
@@ -587,6 +789,128 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false
|
||||
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
|
||||
if (src["heating"]["enable"].is<bool>()) {
|
||||
@@ -601,8 +925,16 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false
|
||||
|
||||
if (!src["heating"]["target"].isNull()) {
|
||||
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);
|
||||
changed = true;
|
||||
}
|
||||
@@ -654,7 +986,7 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false
|
||||
if (!src["dhw"]["target"].isNull()) {
|
||||
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;
|
||||
changed = true;
|
||||
}
|
||||
@@ -679,122 +1011,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
|
||||
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) {
|
||||
dst.sensors.outdoor.type = value;
|
||||
switch (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;
|
||||
}
|
||||
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()) {
|
||||
unsigned char value = src["sensors"]["outdoor"]["pin"].as<unsigned char>();
|
||||
if (!src["sensors"]["outdoor"]["gpio"].isNull()) {
|
||||
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;
|
||||
}
|
||||
|
||||
} else {
|
||||
unsigned char value = src["sensors"]["outdoor"]["gpio"].as<unsigned char>();
|
||||
|
||||
if (value >= 0 && value <= 50) {
|
||||
dst.sensors.outdoor.pin = value;
|
||||
changed = true;
|
||||
if (value >= 0 && value <= 254) {
|
||||
dst.sensors.outdoor.gpio = value;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -808,25 +1064,51 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false
|
||||
}
|
||||
|
||||
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) {
|
||||
dst.sensors.indoor.type = value;
|
||||
switch (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;
|
||||
}
|
||||
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()) {
|
||||
unsigned char value = src["sensors"]["indoor"]["pin"].as<unsigned char>();
|
||||
|
||||
if (value >= 0 && value <= 50) {
|
||||
dst.sensors.indoor.pin = value;
|
||||
changed = true;
|
||||
if (!src["sensors"]["indoor"]["gpio"].isNull()) {
|
||||
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;
|
||||
}
|
||||
|
||||
} else {
|
||||
unsigned char value = src["sensors"]["indoor"]["gpio"].as<unsigned char>();
|
||||
|
||||
if (value >= 0 && value <= 254) {
|
||||
dst.sensors.indoor.gpio = value;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -861,12 +1143,20 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (!src["externalPump"]["pin"].isNull()) {
|
||||
unsigned char value = src["externalPump"]["pin"].as<unsigned char>();
|
||||
if (!src["externalPump"]["gpio"].isNull()) {
|
||||
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;
|
||||
}
|
||||
|
||||
} else {
|
||||
unsigned char value = src["externalPump"]["gpio"].as<unsigned char>();
|
||||
|
||||
if (value >= 0 && value <= 50) {
|
||||
dst.externalPump.pin = value;
|
||||
changed = true;
|
||||
if (value >= 0 && value <= 254) {
|
||||
dst.externalPump.gpio = value;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -917,10 +1207,11 @@ void varsToJson(const Variables& src, JsonVariant dst) {
|
||||
dst["states"]["fault"] = src.states.fault;
|
||||
dst["states"]["diagnostic"] = src.states.diagnostic;
|
||||
dst["states"]["externalPump"] = src.states.externalPump;
|
||||
dst["states"]["mqtt"] = src.states.mqtt;
|
||||
|
||||
dst["sensors"]["modulation"] = roundd(src.sensors.modulation, 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"]["rssi"] = src.sensors.rssi;
|
||||
dst["sensors"]["uptime"] = millis() / 1000ul;
|
||||
@@ -928,7 +1219,9 @@ void varsToJson(const Variables& src, JsonVariant dst) {
|
||||
dst["temperatures"]["indoor"] = roundd(src.temperatures.indoor, 2);
|
||||
dst["temperatures"]["outdoor"] = roundd(src.temperatures.outdoor, 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"]["exhaust"] = roundd(src.temperatures.exhaust, 2);
|
||||
|
||||
dst["parameters"]["heatingEnabled"] = src.parameters.heatingEnabled;
|
||||
dst["parameters"]["heatingMinTemp"] = src.parameters.heatingMinTemp;
|
||||
@@ -936,6 +1229,12 @@ void varsToJson(const Variables& src, JsonVariant dst) {
|
||||
dst["parameters"]["heatingSetpoint"] = src.parameters.heatingSetpoint;
|
||||
dst["parameters"]["dhwMinTemp"] = src.parameters.dhwMinTemp;
|
||||
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) {
|
||||
@@ -961,7 +1260,7 @@ bool jsonToVars(const JsonVariantConst src, Variables& dst) {
|
||||
if (!src["temperatures"]["indoor"].isNull()) {
|
||||
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);
|
||||
changed = true;
|
||||
}
|
||||
@@ -970,7 +1269,7 @@ bool jsonToVars(const JsonVariantConst src, Variables& dst) {
|
||||
if (!src["temperatures"]["outdoor"].isNull()) {
|
||||
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);
|
||||
changed = true;
|
||||
}
|
||||
|
||||
415
src_data/dashboard.html
Normal file
415
src_data/dashboard.html
Normal file
@@ -0,0 +1,415 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Dashboard - OpenTherm Gateway</title>
|
||||
<link rel="stylesheet" href="/static/pico.min.css">
|
||||
<link rel="stylesheet" href="/static/app.css"/>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<header class="container">
|
||||
<nav>
|
||||
<ul>
|
||||
<li><a href="/"><div class="logo">OpenTherm Gateway</div></a></li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li><a href="https://github.com/Laxilef/OTGateway/wiki" role="button" class="secondary" target="_blank">Help</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main class="container">
|
||||
<article>
|
||||
<hgroup>
|
||||
<h2>Dashboard</h2>
|
||||
<p></p>
|
||||
</hgroup>
|
||||
|
||||
<div id="dashboard-busy" aria-busy="true"></div>
|
||||
<div id="dashboard-container" class="hidden">
|
||||
<details open>
|
||||
<summary><b>Control</b></summary>
|
||||
<div class="grid">
|
||||
<div class="thermostat" id="thermostat-heating">
|
||||
<div class="thermostat-header">Heating</div>
|
||||
<div class="thermostat-temp">
|
||||
<div class="thermostat-temp-target"><span id="thermostat-heating-target"></span> <span class="temp-unit"></span></div>
|
||||
<div class="thermostat-temp-current">Current: <span id="thermostat-heating-current"></span> <span class="temp-unit"></span></div>
|
||||
</div>
|
||||
<div class="thermostat-minus"><button id="thermostat-heating-minus" class="outline"><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) => {
|
||||
if (!prevSettings) {
|
||||
return;
|
||||
}
|
||||
|
||||
newSettings.heating.target -= 0.5;
|
||||
modifiedTime = Date.now();
|
||||
|
||||
let minTemp;
|
||||
if (noRegulators) {
|
||||
minTemp = prevSettings.heating.minTemp;
|
||||
} else {
|
||||
minTemp = prevSettings.system.unitSystem == 0 ? 5 : 41;
|
||||
}
|
||||
|
||||
if (prevSettings && newSettings.heating.target < minTemp) {
|
||||
newSettings.heating.target = minTemp;
|
||||
}
|
||||
|
||||
setValue('#thermostat-heating-target', newSettings.heating.target);
|
||||
});
|
||||
|
||||
document.querySelector('#thermostat-heating-plus').addEventListener('click', (event) => {
|
||||
if (!prevSettings) {
|
||||
return;
|
||||
}
|
||||
|
||||
newSettings.heating.target += 0.5;
|
||||
modifiedTime = Date.now();
|
||||
|
||||
let maxTemp;
|
||||
if (noRegulators) {
|
||||
maxTemp = prevSettings.heating.maxTemp;
|
||||
} else {
|
||||
maxTemp = prevSettings.system.unitSystem == 0 ? 30 : 86;
|
||||
}
|
||||
|
||||
if (prevSettings && newSettings.heating.target > maxTemp) {
|
||||
newSettings.heating.target = maxTemp;
|
||||
}
|
||||
|
||||
setValue('#thermostat-heating-target', newSettings.heating.target);
|
||||
});
|
||||
|
||||
document.querySelector('#thermostat-dhw-minus').addEventListener('click', (event) => {
|
||||
if (!prevSettings) {
|
||||
return;
|
||||
}
|
||||
|
||||
newSettings.dhw.target -= 1;
|
||||
modifiedTime = Date.now();
|
||||
|
||||
if (newSettings.dhw.target < prevSettings.dhw.minTemp) {
|
||||
newSettings.dhw.target = prevSettings.dhw.minTemp;
|
||||
}
|
||||
|
||||
setValue('#thermostat-dhw-target', newSettings.dhw.target);
|
||||
});
|
||||
|
||||
document.querySelector('#thermostat-dhw-plus').addEventListener('click', (event) => {
|
||||
if (!prevSettings) {
|
||||
return;
|
||||
}
|
||||
|
||||
newSettings.dhw.target += 1;
|
||||
modifiedTime = Date.now();
|
||||
|
||||
if (newSettings.dhw.target > prevSettings.dhw.maxTemp) {
|
||||
newSettings.dhw.target = prevSettings.dhw.maxTemp;
|
||||
}
|
||||
|
||||
setValue('#thermostat-dhw-target', newSettings.dhw.target);
|
||||
});
|
||||
|
||||
document.querySelector('#thermostat-heating-enabled').addEventListener('change', (event) => {
|
||||
modifiedTime = Date.now();
|
||||
newSettings.heating.enable = event.currentTarget.checked;
|
||||
});
|
||||
|
||||
document.querySelector('#thermostat-heating-turbo').addEventListener('change', (event) => {
|
||||
modifiedTime = Date.now();
|
||||
newSettings.heating.turbo = event.currentTarget.checked;
|
||||
});
|
||||
|
||||
document.querySelector('#thermostat-dhw-enabled').addEventListener('change', (event) => {
|
||||
modifiedTime = Date.now();
|
||||
newSettings.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>
|
||||
178
src_data/index.html
Normal file
178
src_data/index.html
Normal file
@@ -0,0 +1,178 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>OpenTherm Gateway</title>
|
||||
<link rel="stylesheet" href="/static/pico.min.css">
|
||||
<link rel="stylesheet" href="/static/app.css"/>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<header class="container">
|
||||
<nav>
|
||||
<ul>
|
||||
<li><a href="/"><div class="logo">OpenTherm Gateway</div></a></li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li><a href="https://github.com/Laxilef/OTGateway/wiki" role="button" class="secondary" target="_blank">Help</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main class="container">
|
||||
<article>
|
||||
<div>
|
||||
<hgroup>
|
||||
<h2>Network</h2>
|
||||
<p></p>
|
||||
</hgroup>
|
||||
|
||||
<div id="main-busy" aria-busy="true"></div>
|
||||
<table id="main-table" class="hidden">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="row">Hostname:</th>
|
||||
<td><b id="network-hostname"></b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">MAC:</th>
|
||||
<td><b id="network-mac"></b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Connected:</th>
|
||||
<td><input type="radio" id="network-connected" aria-invalid="false" checked disabled /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">SSID:</th>
|
||||
<td><b id="network-ssid"></b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Signal:</th>
|
||||
<td><b id="network-signal"></b> %</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">IP:</th>
|
||||
<td><b id="network-ip"></b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Subnet:</th>
|
||||
<td><b id="network-subnet"></b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Gateway:</th>
|
||||
<td><b id="network-gateway"></b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">DNS:</th>
|
||||
<td><b id="network-dns"></b></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="grid">
|
||||
<a href="/network.html" role="button">Network settings</a>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article>
|
||||
<div>
|
||||
<hgroup>
|
||||
<h2>System</h2>
|
||||
<p></p>
|
||||
</hgroup>
|
||||
|
||||
<div id="system-busy" aria-busy="true"></div>
|
||||
<table id="system-table" class="hidden">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="row">Version:</th>
|
||||
<td><b id="version"></b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Build date:</th>
|
||||
<td><b id="build-date"></b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Uptime:</th>
|
||||
<td><b id="uptime-days"></b> days, <b id="uptime-hours"></b> hours, <b id="uptime-min"></b> min., <b id="uptime-sec"></b> sec.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Free memory:</th>
|
||||
<td><b id="free-heap"></b> of <b id="total-heap"></b> bytes (min: <b id="min-free-heap"></b> bytes)<br />max free block: <b id="max-free-block-heap"></b> bytes (min: <b id="min-max-free-block-heap"></b> bytes)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Last reset reason:</th>
|
||||
<td><b id="reset-reason"></b></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="grid">
|
||||
<a href="/dashboard.html" role="button">Dashboard</a>
|
||||
<a href="/settings.html" role="button">Settings</a>
|
||||
<a href="/upgrade.html" role="button">Upgrade</a>
|
||||
<a href="/restart.html" role="button" class="secondary restart">Restart</a>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</main>
|
||||
|
||||
<footer class="container">
|
||||
<small>
|
||||
<b>Made by Laxilef</b>
|
||||
• <a href="https://github.com/Laxilef/OTGateway/blob/master/LICENSE" target="_blank" class="secondary">License</a>
|
||||
• <a href="https://github.com/Laxilef/OTGateway/blob/master/" target="_blank" class="secondary">Source code</a>
|
||||
• <a href="https://github.com/Laxilef/OTGateway/wiki" target="_blank" class="secondary">Help</a>
|
||||
• <a href="https://github.com/Laxilef/OTGateway/issues" target="_blank" class="secondary">Issue & questions</a>
|
||||
• <a href="https://github.com/Laxilef/OTGateway/releases" target="_blank" class="secondary">Releases</a>
|
||||
</small>
|
||||
</footer>
|
||||
|
||||
<script src="/static/app.js"></script>
|
||||
<script>
|
||||
window.onload = async function () {
|
||||
setTimeout(async function onLoadPage() {
|
||||
try {
|
||||
const response = await fetch('/api/info', { cache: 'no-cache' });
|
||||
if (!response.ok) {
|
||||
throw new Error('Response not valid');
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
setValue('#network-hostname', result.network.hostname);
|
||||
setValue('#network-mac', result.network.mac);
|
||||
setState('#network-connected', result.network.connected);
|
||||
setValue('#network-ssid', result.network.ssid);
|
||||
setValue('#network-signal', result.network.signalQuality);
|
||||
setValue('#network-ip', result.network.ip);
|
||||
setValue('#network-subnet', result.network.subnet);
|
||||
setValue('#network-gateway', result.network.gateway);
|
||||
setValue('#network-dns', result.network.dns);
|
||||
setBusy('#main-busy', '#main-table', false);
|
||||
|
||||
setValue('#version', result.system.version);
|
||||
setValue('#build-date', result.system.buildDate);
|
||||
setValue('#uptime', result.system.uptime);
|
||||
setValue('#uptime-days', Math.floor(result.system.uptime / 86400));
|
||||
setValue('#uptime-hours', Math.floor(result.system.uptime % 86400 / 3600));
|
||||
setValue('#uptime-min', Math.floor(result.system.uptime % 3600 / 60));
|
||||
setValue('#uptime-sec', Math.floor(result.system.uptime % 60));
|
||||
setValue('#total-heap', result.system.totalHeap);
|
||||
setValue('#free-heap', result.system.freeHeap);
|
||||
setValue('#min-free-heap', result.system.minFreeHeap);
|
||||
setValue('#max-free-block-heap', result.system.maxFreeBlockHeap);
|
||||
setValue('#min-max-free-block-heap', result.system.minMaxFreeBlockHeap);
|
||||
setValue('#reset-reason', result.system.resetReason);
|
||||
setBusy('#system-busy', '#system-table', false);
|
||||
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
|
||||
setTimeout(onLoadPage, 10000);
|
||||
}, 1000);
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -31,32 +31,38 @@
|
||||
|
||||
<div id="network-settings-busy" aria-busy="true"></div>
|
||||
<form action="/api/network/settings" id="network-settings" class="hidden">
|
||||
<label for="hostname">
|
||||
<label for="network-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 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
|
||||
</label>
|
||||
<br>
|
||||
<hr>
|
||||
<br />
|
||||
<hr />
|
||||
|
||||
<label for="network-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 for="network-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 for="network-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 for="network-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>
|
||||
|
||||
<button type="submit">Save</button>
|
||||
</form>
|
||||
</div>
|
||||
@@ -82,28 +88,32 @@
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
</figure>
|
||||
|
||||
<button type="submit">Refresh</button>
|
||||
</form>
|
||||
|
||||
<hr>
|
||||
<hr />
|
||||
<div>
|
||||
<hgroup>
|
||||
<h2>WiFi settings</h2>
|
||||
<p></p>
|
||||
</hgroup>
|
||||
|
||||
<div id="sta-settings-busy" aria-busy="true"></div>
|
||||
<form action="/api/network/settings" id="sta-settings" class="hidden">
|
||||
<label for="sta-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 for="sta-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 for="sta-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>
|
||||
</label>
|
||||
|
||||
@@ -119,20 +129,24 @@
|
||||
<h2>AP settings</h2>
|
||||
<p></p>
|
||||
</hgroup>
|
||||
|
||||
<div id="ap-settings-busy" aria-busy="true"></div>
|
||||
<form action="/api/network/settings" id="ap-settings" class="hidden">
|
||||
<label for="ap-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 for="ap-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 for="ap-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>
|
||||
|
||||
<button type="submit">Save</button>
|
||||
</form>
|
||||
</div>
|
||||
@@ -153,7 +167,34 @@
|
||||
<script src="/static/app.js"></script>
|
||||
<script>
|
||||
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');
|
||||
setupNetworkScanForm('#network-scan', '#networks');
|
||||
804
src_data/settings.html
Normal file
804
src_data/settings.html
Normal file
@@ -0,0 +1,804 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Settings - OpenTherm Gateway</title>
|
||||
<link rel="stylesheet" href="/static/pico.min.css">
|
||||
<link rel="stylesheet" href="/static/app.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<header class="container">
|
||||
<nav>
|
||||
<ul>
|
||||
<li><a href="/"><div class="logo">OpenTherm Gateway</div></a></li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li><a href="https://github.com/Laxilef/OTGateway/wiki" role="button" class="secondary" target="_blank">Help</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main class="container">
|
||||
<article>
|
||||
<hgroup>
|
||||
<h2>Settings</h2>
|
||||
<p></p>
|
||||
</hgroup>
|
||||
|
||||
<details>
|
||||
<summary><b>Portal settings</b></summary>
|
||||
<div>
|
||||
<div id="portal-settings-busy" aria-busy="true"></div>
|
||||
<form action="/api/settings" id="portal-settings" class="hidden">
|
||||
<div class="grid">
|
||||
<label for="portal-login">
|
||||
Login
|
||||
<input type="text" id="portal-login" name="portal[login]" maxlength="12" required>
|
||||
</label>
|
||||
|
||||
<label for="portal-password">
|
||||
Password
|
||||
<input type="password" id="portal-password" name="portal[password]" maxlength="32" required>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<label for="portal-auth">
|
||||
<input type="checkbox" id="portal-auth" name="portal[auth]" value="true">
|
||||
Require authentication
|
||||
</label>
|
||||
<br />
|
||||
|
||||
<button type="submit">Save</button>
|
||||
</form>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<hr />
|
||||
|
||||
<details>
|
||||
<summary><b>System settings</b></summary>
|
||||
<div>
|
||||
<div id="system-settings-busy" aria-busy="true"></div>
|
||||
<form action="/api/settings" id="system-settings" class="hidden">
|
||||
<fieldset>
|
||||
<legend>Unit system</legend>
|
||||
|
||||
<label>
|
||||
<input type="radio" id="system-unit-system" name="system[unitSystem]" value="0" />
|
||||
Metric (celsius, liters, bar)
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<input type="radio" id="system-unit-system" name="system[unitSystem]" value="1" />
|
||||
Imperial (fahrenheit, gallons, psi)
|
||||
</label>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<label for="system-status-led-gpio">
|
||||
Status LED GPIO
|
||||
<input type="number" inputmode="numeric" id="system-status-led-gpio" name="system[statusLedGpio]" min="0" max="254" step="1">
|
||||
<small>blank - not use</small>
|
||||
</label>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<legend>Diagnostic</legend>
|
||||
|
||||
<label for="system-debug">
|
||||
<input type="checkbox" id="system-debug" name="system[debug]" value="true">
|
||||
Debug mode
|
||||
</label>
|
||||
|
||||
<label for="system-serial-enable">
|
||||
<input type="checkbox" id="system-serial-enable" name="system[serial][enable]" value="true">
|
||||
Enable Serial port
|
||||
</label>
|
||||
|
||||
<label for="system-telnet-enable">
|
||||
<input type="checkbox" id="system-telnet-enable" name="system[telnet][enable]" value="true">
|
||||
Enable Telnet
|
||||
</label>
|
||||
|
||||
<div class="grid">
|
||||
<label for="system-serial-baudrate">
|
||||
Serial port baud rate
|
||||
<input type="number" inputmode="numeric" id="system-serial-baudrate" name="system[serial][baudrate]" min="9600" max="115200" step="1" required>
|
||||
<small>Available: 9600, 19200, 38400, 57600, 74880, 115200</small>
|
||||
</label>
|
||||
|
||||
<label for="system-telnet-port">
|
||||
Telnet port
|
||||
<input type="number" inputmode="numeric" id="system-telnet-port" name="system[telnet][port]" min="1" max="65535" step="1" required>
|
||||
<small>Default: 23</small>
|
||||
</label>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<mark>After changing this settings, the ESP must be restarted for the changes to take effect.</mark>
|
||||
</fieldset>
|
||||
|
||||
<button type="submit">Save</button>
|
||||
</form>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
|
||||
<hr />
|
||||
|
||||
<details>
|
||||
<summary><b>Heating settings</b></summary>
|
||||
<div>
|
||||
<div id="heating-settings-busy" aria-busy="true"></div>
|
||||
<form action="/api/settings" id="heating-settings" class="hidden">
|
||||
<div class="grid">
|
||||
<label for="heating-min-temp">
|
||||
Minimum temperature
|
||||
<input type="number" inputmode="numeric" id="heating-min-temp" name="heating[minTemp]" min="0" max="0" step="1" required>
|
||||
</label>
|
||||
|
||||
<label for="heating-max-temp">
|
||||
Maximum temperature
|
||||
<input type="number" inputmode="numeric" id="heating-max-temp" name="heating[maxTemp]" min="0" max="0" step="1" required>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="grid">
|
||||
<label for="heating-hysteresis">
|
||||
Hysteresis
|
||||
<input type="number" inputmode="numeric" id="heating-hysteresis" name="heating[hysteresis]" min="0" max="5" step="0.05" required>
|
||||
</label>
|
||||
|
||||
<label for="heating-max-modulation">
|
||||
Max modulation level
|
||||
<input type="number" inputmode="numeric" id="heating-max-modulation" name="heating[maxModulation]" min="1" max="100" step="1" required>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<button type="submit">Save</button>
|
||||
</form>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<hr />
|
||||
|
||||
<details>
|
||||
<summary><b>DHW settings</b></summary>
|
||||
<div>
|
||||
<div id="dhw-settings-busy" aria-busy="true"></div>
|
||||
<form action="/api/settings" id="dhw-settings" class="hidden">
|
||||
<div class="grid">
|
||||
<label for="dhw-min-temp">
|
||||
Minimum temperature
|
||||
<input type="number" inputmode="numeric" id="dhw-min-temp" name="dhw[minTemp]" min="0" max="0" step="1" required>
|
||||
</label>
|
||||
|
||||
<label for="dhw-max-temp">
|
||||
Maximum temperature
|
||||
<input type="number" inputmode="numeric" id="dhw-max-temp" name="dhw[maxTemp]" min="0" max="0" step="1" required>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<button type="submit">Save</button>
|
||||
</form>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<hr />
|
||||
|
||||
<details>
|
||||
<summary><b>Emergency mode settings</b></summary>
|
||||
<div>
|
||||
<div id="emergency-settings-busy" aria-busy="true"></div>
|
||||
<form action="/api/settings" id="emergency-settings" class="hidden">
|
||||
<fieldset>
|
||||
<label for="emergency-enable">
|
||||
<input type="checkbox" id="emergency-enable" name="emergency[enable]" value="true">
|
||||
Enable
|
||||
</label>
|
||||
|
||||
<small>
|
||||
<b>!</b> Emergency mode can be useful <u>only</u> when using Equitherm and/or PID (when normal work) and when reporting indoor/outdoor temperature via MQTT or API. In this mode, sensor values that are reported via MQTT/API are not used.
|
||||
</small>
|
||||
</fieldset>
|
||||
|
||||
<div class="grid">
|
||||
<label for="emergency-target">
|
||||
Target temperature
|
||||
<input type="number" inputmode="numeric" id="emergency-target" name="emergency[target]" min="0" max="0" step="1" required>
|
||||
<small>
|
||||
<u>Indoor temperature</u> if Equitherm or PID is <b>enabled</b><br />
|
||||
<u>Heat carrier temperature</u> if Equitherm and PID <b>is disabled</b>
|
||||
</small>
|
||||
</label>
|
||||
|
||||
<label for="emergency-treshold-time">
|
||||
Treshold time <small>(sec)</small>
|
||||
<input type="number" inputmode="numeric" id="emergency-treshold-time" name="emergency[tresholdTime]" min="60" max="1800" step="1" required>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<fieldset>
|
||||
<legend>Events</legend>
|
||||
|
||||
<label for="emergency-on-network-fault">
|
||||
<input type="checkbox" id="emergency-on-network-fault" name="emergency[onNetworkFault]" value="true">
|
||||
On network fault
|
||||
</label>
|
||||
|
||||
<label for="emergency-on-mqtt-fault">
|
||||
<input type="checkbox" id="emergency-on-mqtt-fault" name="emergency[onMqttFault]" value="true">
|
||||
On MQTT fault
|
||||
</label>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<legend>Using regulators</legend>
|
||||
|
||||
<label for="emergency-use-equitherm">
|
||||
<input type="checkbox" id="emergency-use-equitherm" name="emergency[useEquitherm]" value="true">
|
||||
<span>
|
||||
Equitherm <small>(requires at least an external/boiler <u>outdoor</u> sensor)</small>
|
||||
</span>
|
||||
</label>
|
||||
<label for="emergency-use-pid">
|
||||
<input type="checkbox" id="emergency-use-pid" name="emergency[usePid]" value="true">
|
||||
<span>
|
||||
PID <small>(requires at least an external/BLE <u>indoor</u> sensor)</small>
|
||||
</span>
|
||||
</label>
|
||||
</fieldset>
|
||||
|
||||
<button type="submit">Save</button>
|
||||
</form>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<hr />
|
||||
|
||||
<details>
|
||||
<summary><b>Equitherm settings</b></summary>
|
||||
<div>
|
||||
<div id="equitherm-settings-busy" aria-busy="true"></div>
|
||||
<form action="/api/settings" id="equitherm-settings" class="hidden">
|
||||
<fieldset>
|
||||
<label for="equitherm-enable">
|
||||
<input type="checkbox" id="equitherm-enable" name="equitherm[enable]" value="true">
|
||||
Enable
|
||||
</label>
|
||||
</fieldset>
|
||||
|
||||
<div class="grid">
|
||||
<label for="equitherm-n-factor">
|
||||
N factor
|
||||
<input type="number" inputmode="numeric" id="equitherm-n-factor" name="equitherm[n_factor]" min="0.001" max="10" step="0.001" required>
|
||||
</label>
|
||||
|
||||
<label for="equitherm-k-factor">
|
||||
K factor
|
||||
<input type="number" inputmode="numeric" id="equitherm-k-factor" name="equitherm[k_factor]" min="0" max="10" step="0.01" required>
|
||||
</label>
|
||||
|
||||
<label for="equitherm-t-factor">
|
||||
T factor
|
||||
<input type="number" inputmode="numeric" id="equitherm-t-factor" name="equitherm[t_factor]" min="0" max="10" step="0.01" required>
|
||||
<small>Not used if PID is enabled</small>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<button type="submit">Save</button>
|
||||
</form>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<hr />
|
||||
|
||||
<details>
|
||||
<summary><b>PID settings</b></summary>
|
||||
<div>
|
||||
<div id="pid-settings-busy" aria-busy="true"></div>
|
||||
<form action="/api/settings" id="pid-settings" class="hidden">
|
||||
<fieldset>
|
||||
<label for="pid-enable">
|
||||
<input type="checkbox" id="pid-enable" name="pid[enable]" value="true">
|
||||
Enable
|
||||
</label>
|
||||
</fieldset>
|
||||
|
||||
<div class="grid">
|
||||
<label for="pid-p-factor">
|
||||
P factor
|
||||
<input type="number" inputmode="numeric" id="pid-p-factor" name="pid[p_factor]" min="0.1" max="1000" step="0.1" required>
|
||||
</label>
|
||||
|
||||
<label for="pid-i-factor">
|
||||
I factor
|
||||
<input type="number" inputmode="numeric" id="pid-i-factor" name="pid[i_factor]" min="0" max="100" step="0.001" required>
|
||||
</label>
|
||||
|
||||
<label for="pid-d-factor">
|
||||
D factor
|
||||
<input type="number" inputmode="numeric" id="pid-d-factor" name="pid[d_factor]" min="0" max="100000" step="1" required>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<label for="pid-dt">
|
||||
DT <small>in seconds</small>
|
||||
<input type="number" inputmode="numeric" id="pid-dt" name="pid[dt]" min="30" max="600" step="1" required>
|
||||
</label>
|
||||
|
||||
<hr />
|
||||
|
||||
<div class="grid">
|
||||
<label for="pid-min-temp">
|
||||
Minimum temperature
|
||||
<input type="number" inputmode="numeric" id="pid-min-temp" name="pid[minTemp]" min="0" max="0" step="1" required>
|
||||
</label>
|
||||
|
||||
<label for="pid-max-temp">
|
||||
Maximum temperature
|
||||
<input type="number" inputmode="numeric" id="pid-max-temp" name="pid[maxTemp]" min="0" max="0" step="1" required>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<button type="submit">Save</button>
|
||||
</form>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<hr />
|
||||
|
||||
<details>
|
||||
<summary><b>OpenTherm settings</b></summary>
|
||||
<div>
|
||||
<div id="opentherm-settings-busy" aria-busy="true"></div>
|
||||
<form action="/api/settings" id="opentherm-settings" class="hidden">
|
||||
<fieldset>
|
||||
<legend>Unit system</legend>
|
||||
|
||||
<label>
|
||||
<input type="radio" id="opentherm-unit-system" name="opentherm[unitSystem]" value="0" />
|
||||
Metric (celsius)
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<input type="radio" id="opentherm-unit-system" name="opentherm[unitSystem]" value="1" />
|
||||
Imperial (fahrenheit)
|
||||
</label>
|
||||
</fieldset>
|
||||
|
||||
<div class="grid">
|
||||
<label for="opentherm-in-gpio">
|
||||
In GPIO
|
||||
<input type="number" inputmode="numeric" id="opentherm-in-gpio" name="opentherm[inGpio]" min="0" max="254" step="1">
|
||||
</label>
|
||||
|
||||
<label for="opentherm-in-gpio">
|
||||
Out GPIO
|
||||
<input type="number" inputmode="numeric" id="opentherm-out-gpio" name="opentherm[outGpio]" min="0" max="254" step="1">
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="grid">
|
||||
<label for="opentherm-rx-led-gpio">
|
||||
RX LED GPIO
|
||||
<input type="number" inputmode="numeric" id="opentherm-rx-led-gpio" name="opentherm[rxLedGpio]" min="0" max="254" step="1">
|
||||
<small>blank - not use</small>
|
||||
</label>
|
||||
|
||||
<label for="opentherm-member-id-code">
|
||||
Master MemberID code
|
||||
<input type="number" inputmode="numeric" id="opentherm-member-id-code" name="opentherm[memberIdCode]" min="0" max="65535" step="1" required>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<fieldset>
|
||||
<legend>Options</legend>
|
||||
<label for="opentherm-dhw-present">
|
||||
<input type="checkbox" id="opentherm-dhw-present" name="opentherm[dhwPresent]" value="true">
|
||||
DHW present
|
||||
</label>
|
||||
|
||||
<label for="opentherm-sw-mode">
|
||||
<input type="checkbox" id="opentherm-sw-mode" name="opentherm[summerWinterMode]" value="true">
|
||||
Summer/winter mode
|
||||
</label>
|
||||
|
||||
<label for="opentherm-heating-ch2-enabled">
|
||||
<input type="checkbox" id="opentherm-heating-ch2-enabled" name="opentherm[heatingCh2Enabled]" value="true">
|
||||
Heating CH2 always enabled
|
||||
</label>
|
||||
|
||||
<label for="opentherm-heating-ch1-to-ch2">
|
||||
<input type="checkbox" id="opentherm-heating-ch1-to-ch2" name="opentherm[heatingCh1ToCh2]" value="true">
|
||||
Duplicate heating CH1 to CH2
|
||||
</label>
|
||||
|
||||
<label for="opentherm-dhw-to-ch2">
|
||||
<input type="checkbox" id="opentherm-dhw-to-ch2" name="opentherm[dhwToCh2]" value="true">
|
||||
Duplicate DHW to CH2
|
||||
</label>
|
||||
|
||||
<label for="opentherm-dhw-blocking">
|
||||
<input type="checkbox" id="opentherm-dhw-blocking" name="opentherm[dhwBlocking]" value="true">
|
||||
DHW blocking
|
||||
</label>
|
||||
|
||||
<label for="opentherm-sync-modulation-with-heating">
|
||||
<input type="checkbox" id="opentherm-sync-modulation-with-heating" name="opentherm[modulationSyncWithHeating]" value="true">
|
||||
Sync modulation with heating
|
||||
</label>
|
||||
|
||||
<label for="opentherm-get-min-max-temp">
|
||||
<input type="checkbox" id="opentherm-get-min-max-temp" name="opentherm[getMinMaxTemp]" value="true">
|
||||
Get min/max temp from boiler
|
||||
</label>
|
||||
</fieldset>
|
||||
|
||||
<button type="submit">Save</button>
|
||||
</form>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<hr />
|
||||
|
||||
<details>
|
||||
<summary><b>MQTT settings</b></summary>
|
||||
<div>
|
||||
<div id="mqtt-settings-busy" aria-busy="true"></div>
|
||||
<form action="/api/settings" id="mqtt-settings" class="hidden">
|
||||
<fieldset>
|
||||
<label for="mqtt-enable">
|
||||
<input type="checkbox" id="mqtt-enable" name="mqtt[enable]" value="true">
|
||||
Enable
|
||||
</label>
|
||||
</fieldset>
|
||||
|
||||
<div class="grid">
|
||||
<label for="mqtt-server">
|
||||
Server
|
||||
<input type="text" id="mqtt-server" name="mqtt[server]" maxlength="80" required>
|
||||
</label>
|
||||
|
||||
<label for="mqtt-port">
|
||||
Port
|
||||
<input type="number" inputmode="numeric" id="mqtt-port" name="mqtt[port]" min="1" max="65535" step="1" required>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="grid">
|
||||
<label for="mqtt-user">
|
||||
User
|
||||
<input type="text" id="mqtt-user" name="mqtt[user]" maxlength="32" required>
|
||||
</label>
|
||||
|
||||
<label for="mqtt-password">
|
||||
Password
|
||||
<input type="password" id="mqtt-password" name="mqtt[password]" maxlength="32">
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="grid">
|
||||
<label for="mqtt-prefix">
|
||||
Prefix
|
||||
<input type="text" id="mqtt-prefix" name="mqtt[prefix]" maxlength="32" required>
|
||||
</label>
|
||||
|
||||
<label for="mqtt-interval">
|
||||
Publish interval <small>(sec)</small>
|
||||
<input type="number" inputmode="numeric" id="mqtt-interval" name="mqtt[interval]" min="3" max="60" step="1" required>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<button type="submit">Save</button>
|
||||
</form>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<hr />
|
||||
|
||||
<details>
|
||||
<summary><b>Outdoor sensor settings</b></summary>
|
||||
<div>
|
||||
<div id="outdoor-sensor-settings-busy" aria-busy="true"></div>
|
||||
<form action="/api/settings" id="outdoor-sensor-settings" class="hidden">
|
||||
<fieldset>
|
||||
<legend>Source type</legend>
|
||||
|
||||
<label>
|
||||
<input type="radio" id="outdoor-sensor-type" name="sensors[outdoor][type]" value="0" />
|
||||
From boiler via OpenTherm
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<input type="radio" id="outdoor-sensor-type" name="sensors[outdoor][type]" value="1" />
|
||||
Manual via MQTT/API
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<input type="radio" id="outdoor-sensor-type" name="sensors[outdoor][type]" value="2" />
|
||||
External (DS18B20)
|
||||
</label>
|
||||
</fieldset>
|
||||
|
||||
<label for="outdoor-sensor-gpio">
|
||||
GPIO
|
||||
<input type="number" inputmode="numeric" id="outdoor-sensor-gpio" name="sensors[outdoor][gpio]" min="0" max="254" step="1">
|
||||
</label>
|
||||
|
||||
<label for="outdoor-sensor-offset">
|
||||
Temp offset (calibration)
|
||||
<input type="number" inputmode="numeric" id="outdoor-sensor-offset" name="sensors[outdoor][offset]" min="-10" max="10" step="0.01" required>
|
||||
</label>
|
||||
|
||||
<button type="submit">Save</button>
|
||||
</form>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<hr />
|
||||
|
||||
<details>
|
||||
<summary><b>Indoor sensor settings</b></summary>
|
||||
<div>
|
||||
<div id="indoor-sensor-settings-busy" aria-busy="true"></div>
|
||||
<form action="/api/settings" id="indoor-sensor-settings" class="hidden">
|
||||
<fieldset>
|
||||
<legend>Source type</legend>
|
||||
|
||||
<label>
|
||||
<input type="radio" id="indoor-sensor-type" name="sensors[indoor][type]" value="1" />
|
||||
Manual via MQTT/API
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<input type="radio" id="indoor-sensor-type" name="sensors[indoor][type]" value="2" />
|
||||
External (DS18B20)
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<input type="radio" id="indoor-sensor-type" name="sensors[indoor][type]" value="3" />
|
||||
BLE device <i>(ONLY for some ESP32 which support BLE)</i>
|
||||
</label>
|
||||
</fieldset>
|
||||
|
||||
<label for="indoor-sensor-gpio">
|
||||
GPIO
|
||||
<input type="number" inputmode="numeric" id="indoor-sensor-gpio" name="sensors[indoor][gpio]" min="0" max="254" step="1">
|
||||
</label>
|
||||
|
||||
<div class="grid">
|
||||
<label for="indoor-sensor-offset">
|
||||
Temp offset (calibration)
|
||||
<input type="number" inputmode="numeric" id="indoor-sensor-offset" name="sensors[indoor][offset]" min="-10" max="10" step="0.01" required>
|
||||
</label>
|
||||
|
||||
<label for="indoor-sensor-ble-addresss">
|
||||
BLE addresss
|
||||
<input type="text" id="indoor-sensor-ble-addresss" name="sensors[indoor][bleAddresss]" pattern="([A-Fa-f0-9]{2}:){5}[A-Fa-f0-9]{2}">
|
||||
<small>ONLY for some ESP32 which support BLE</small>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<button type="submit">Save</button>
|
||||
</form>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<hr />
|
||||
|
||||
<details>
|
||||
<summary><b>External pump settings</b></summary>
|
||||
<div>
|
||||
<div id="extpump-settings-busy" aria-busy="true"></div>
|
||||
<form action="/api/settings" id="extpump-settings" class="hidden">
|
||||
<fieldset>
|
||||
<label for="extpump-use">
|
||||
<input type="checkbox" id="extpump-use" name="externalPump[use]" value="true">
|
||||
Use external pump
|
||||
</label>
|
||||
</fieldset>
|
||||
|
||||
<div class="grid">
|
||||
<label for="extpump-gpio">
|
||||
Relay GPIO
|
||||
<input type="number" inputmode="numeric" id="extpump-gpio" name="externalPump[gpio]" min="0" max="254" step="1">
|
||||
</label>
|
||||
|
||||
<label for="extpump-pc-time">
|
||||
Post circulation time <small>(min)</small>
|
||||
<input type="number" inputmode="numeric" id="extpump-pc-time" name="externalPump[postCirculationTime]" min="1" max="120" step="1" required>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="grid">
|
||||
<label for="extpump-as-interval">
|
||||
Anti stuck interval <small>(days)</small>
|
||||
<input type="number" inputmode="numeric" id="extpump-as-interval" name="externalPump[antiStuckInterval]" min="1" max="366" step="1" required>
|
||||
</label>
|
||||
|
||||
<label for="extpump-as-time">
|
||||
Anti stuck time <small>(min)</small>
|
||||
<input type="number" inputmode="numeric" id="extpump-as-time" name="externalPump[antiStuckTime]" min="1" max="20" step="1" required>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<button type="submit">Save</button>
|
||||
</form>
|
||||
</div>
|
||||
</details>
|
||||
</article>
|
||||
</main>
|
||||
|
||||
<footer class="container">
|
||||
<small>
|
||||
<b>Made by Laxilef</b>
|
||||
• <a href="https://github.com/Laxilef/OTGateway/blob/master/LICENSE" target="_blank" class="secondary">License</a>
|
||||
• <a href="https://github.com/Laxilef/OTGateway/blob/master/" target="_blank" class="secondary">Source code</a>
|
||||
• <a href="https://github.com/Laxilef/OTGateway/wiki" target="_blank" class="secondary">Help</a>
|
||||
• <a href="https://github.com/Laxilef/OTGateway/issues" target="_blank" class="secondary">Issue & questions</a>
|
||||
• <a href="https://github.com/Laxilef/OTGateway/releases" target="_blank" class="secondary">Releases</a>
|
||||
</small>
|
||||
</footer>
|
||||
|
||||
<script src="/static/app.js"></script>
|
||||
<script>
|
||||
window.onload = async function () {
|
||||
const fillData = (data) => {
|
||||
// System
|
||||
setCheckboxValue('#system-debug', data.system.debug);
|
||||
setCheckboxValue('#system-serial-enable', data.system.serial.enable);
|
||||
setInputValue('#system-serial-baudrate', data.system.serial.baudrate);
|
||||
setCheckboxValue('#system-telnet-enable', data.system.telnet.enable);
|
||||
setInputValue('#system-telnet-port', data.system.telnet.port);
|
||||
setRadioValue('#system-unit-system', data.system.unitSystem);
|
||||
setInputValue('#system-status-led-gpio', data.system.statusLedGpio < 255 ? data.system.statusLedGpio : '');
|
||||
setBusy('#system-settings-busy', '#system-settings', false);
|
||||
|
||||
// Portal
|
||||
setCheckboxValue('#portal-auth', data.portal.auth);
|
||||
setInputValue('#portal-login', data.portal.login);
|
||||
setInputValue('#portal-password', data.portal.password);
|
||||
setBusy('#portal-settings-busy', '#portal-settings', false);
|
||||
|
||||
// Opentherm
|
||||
setRadioValue('#opentherm-unit-system', data.opentherm.unitSystem);
|
||||
setInputValue('#opentherm-in-gpio', data.opentherm.inGpio < 255 ? data.opentherm.inGpio : '');
|
||||
setInputValue('#opentherm-out-gpio', data.opentherm.outGpio < 255 ? data.opentherm.outGpio : '');
|
||||
setInputValue('#opentherm-rx-led-gpio', data.opentherm.rxLedGpio < 255 ? data.opentherm.rxLedGpio : '');
|
||||
setInputValue('#opentherm-member-id-code', data.opentherm.memberIdCode);
|
||||
setCheckboxValue('#opentherm-dhw-present', data.opentherm.dhwPresent);
|
||||
setCheckboxValue('#opentherm-sw-mode', data.opentherm.summerWinterMode);
|
||||
setCheckboxValue('#opentherm-heating-ch2-enabled', data.opentherm.heatingCh2Enabled);
|
||||
setCheckboxValue('#opentherm-heating-ch1-to-ch2', data.opentherm.heatingCh1ToCh2);
|
||||
setCheckboxValue('#opentherm-dhw-to-ch2', data.opentherm.dhwToCh2);
|
||||
setCheckboxValue('#opentherm-dhw-blocking', data.opentherm.dhwBlocking);
|
||||
setCheckboxValue('#opentherm-sync-modulation-with-heating', data.opentherm.modulationSyncWithHeating);
|
||||
setCheckboxValue('#opentherm-get-min-max-temp', data.opentherm.getMinMaxTemp);
|
||||
setBusy('#opentherm-settings-busy', '#opentherm-settings', false);
|
||||
|
||||
// MQTT
|
||||
setCheckboxValue('#mqtt-enable', data.mqtt.enable);
|
||||
setInputValue('#mqtt-server', data.mqtt.server);
|
||||
setInputValue('#mqtt-port', data.mqtt.port);
|
||||
setInputValue('#mqtt-user', data.mqtt.user);
|
||||
setInputValue('#mqtt-password', data.mqtt.password);
|
||||
setInputValue('#mqtt-prefix', data.mqtt.prefix);
|
||||
setInputValue('#mqtt-interval', data.mqtt.interval);
|
||||
setBusy('#mqtt-settings-busy', '#mqtt-settings', false);
|
||||
|
||||
// Outdoor sensor
|
||||
setRadioValue('#outdoor-sensor-type', data.sensors.outdoor.type);
|
||||
setInputValue('#outdoor-sensor-gpio', data.sensors.outdoor.gpio < 255 ? data.sensors.outdoor.gpio : '');
|
||||
setInputValue('#outdoor-sensor-offset', data.sensors.outdoor.offset);
|
||||
setBusy('#outdoor-sensor-settings-busy', '#outdoor-sensor-settings', false);
|
||||
|
||||
// Indoor sensor
|
||||
setRadioValue('#indoor-sensor-type', data.sensors.indoor.type);
|
||||
setInputValue('#indoor-sensor-gpio', data.sensors.indoor.gpio < 255 ? data.sensors.indoor.gpio : '');
|
||||
setInputValue('#indoor-sensor-offset', data.sensors.indoor.offset);
|
||||
setInputValue('#indoor-sensor-ble-addresss', data.sensors.indoor.bleAddresss);
|
||||
setBusy('#indoor-sensor-settings-busy', '#indoor-sensor-settings', false);
|
||||
|
||||
// Extpump
|
||||
setCheckboxValue('#extpump-use', data.externalPump.use);
|
||||
setInputValue('#extpump-gpio', data.externalPump.gpio < 255 ? data.externalPump.gpio : '');
|
||||
setInputValue('#extpump-pc-time', data.externalPump.postCirculationTime);
|
||||
setInputValue('#extpump-as-interval', data.externalPump.antiStuckInterval);
|
||||
setInputValue('#extpump-as-time', data.externalPump.antiStuckTime);
|
||||
setBusy('#extpump-settings-busy', '#extpump-settings', false);
|
||||
|
||||
// Heating
|
||||
setInputValue('#heating-min-temp', data.heating.minTemp, {
|
||||
"min": data.system.unitSystem == 0 ? 0 : 32,
|
||||
"max": data.system.unitSystem == 0 ? 99 : 211
|
||||
});
|
||||
setInputValue('#heating-max-temp', data.heating.maxTemp, {
|
||||
"min": data.system.unitSystem == 0 ? 1 : 33,
|
||||
"max": data.system.unitSystem == 0 ? 100 : 212
|
||||
});
|
||||
setInputValue('#heating-hysteresis', data.heating.hysteresis);
|
||||
setInputValue('#heating-max-modulation', data.heating.maxModulation);
|
||||
setBusy('#heating-settings-busy', '#heating-settings', false);
|
||||
|
||||
// DHW
|
||||
setInputValue('#dhw-min-temp', data.dhw.minTemp, {
|
||||
"min": data.system.unitSystem == 0 ? 0 : 32,
|
||||
"max": data.system.unitSystem == 0 ? 99 : 211
|
||||
});
|
||||
setInputValue('#dhw-max-temp', data.dhw.maxTemp, {
|
||||
"min": data.system.unitSystem == 0 ? 1 : 33,
|
||||
"max": data.system.unitSystem == 0 ? 100 : 212
|
||||
});
|
||||
setBusy('#dhw-settings-busy', '#dhw-settings', false);
|
||||
|
||||
// Emergency mode
|
||||
setCheckboxValue('#emergency-enable', data.emergency.enable);
|
||||
setInputValue('#emergency-treshold-time', data.emergency.tresholdTime);
|
||||
setCheckboxValue('#emergency-use-equitherm', data.emergency.useEquitherm);
|
||||
setCheckboxValue('#emergency-use-pid', data.emergency.usePid);
|
||||
setCheckboxValue('#emergency-on-network-fault', data.emergency.onNetworkFault);
|
||||
setCheckboxValue('#emergency-on-mqtt-fault', data.emergency.onMqttFault);
|
||||
setInputValue('#emergency-target', data.emergency.target, {
|
||||
"min": (!data.emergency.useEquitherm && !data.emergency.usePid) ? data.heating.minTemp : 10,
|
||||
"max": (!data.emergency.useEquitherm && !data.emergency.usePid) ? data.heating.maxTemp : 30,
|
||||
});
|
||||
setBusy('#emergency-settings-busy', '#emergency-settings', false);
|
||||
|
||||
// Equitherm
|
||||
setCheckboxValue('#equitherm-enable', data.equitherm.enable);
|
||||
setInputValue('#equitherm-n-factor', data.equitherm.n_factor);
|
||||
setInputValue('#equitherm-k-factor', data.equitherm.k_factor);
|
||||
setInputValue('#equitherm-t-factor', data.equitherm.t_factor);
|
||||
setBusy('#equitherm-settings-busy', '#equitherm-settings', false);
|
||||
|
||||
// PID
|
||||
setCheckboxValue('#pid-enable', data.pid.enable);
|
||||
setInputValue('#pid-p-factor', data.pid.p_factor);
|
||||
setInputValue('#pid-i-factor', data.pid.i_factor);
|
||||
setInputValue('#pid-d-factor', data.pid.d_factor);
|
||||
setInputValue('#pid-dt', data.pid.dt);
|
||||
setInputValue('#pid-min-temp', data.pid.minTemp, {
|
||||
"min": 0,
|
||||
"max": data.system.unitSystem == 0 ? 99 : 211
|
||||
});
|
||||
setInputValue('#pid-max-temp', data.pid.maxTemp, {
|
||||
"min": 1,
|
||||
"max": data.system.unitSystem == 0 ? 100 : 212
|
||||
});
|
||||
setBusy('#pid-settings-busy', '#pid-settings', false);
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/settings', { cache: 'no-cache' });
|
||||
if (!response.ok) {
|
||||
throw new Error('Response not valid');
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
fillData(result);
|
||||
setupForm('#portal-settings', fillData);
|
||||
setupForm('#system-settings', fillData);
|
||||
setupForm('#heating-settings', fillData);
|
||||
setupForm('#dhw-settings', fillData);
|
||||
setupForm('#emergency-settings', fillData);
|
||||
setupForm('#equitherm-settings', fillData);
|
||||
setupForm('#pid-settings', fillData);
|
||||
setupForm('#opentherm-settings', fillData);
|
||||
setupForm('#mqtt-settings', fillData);
|
||||
setupForm('#outdoor-sensor-settings', fillData);
|
||||
setupForm('#indoor-sensor-settings', fillData);
|
||||
setupForm('#extpump-settings', fillData);
|
||||
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -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) {
|
||||
article {
|
||||
--pico-block-spacing-vertical: calc(var(--pico-spacing) * 1.5);
|
||||
--pico-block-spacing-horizontal: calc(var(--pico-spacing) * 1.5);
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1000px;
|
||||
}
|
||||
}
|
||||
|
||||
@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 {
|
||||
max-width: 1000px;
|
||||
}
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
header,
|
||||
main,
|
||||
footer {
|
||||
header, main, footer {
|
||||
padding-top: 1rem !important;
|
||||
padding-bottom: 1rem !important;
|
||||
}
|
||||
@@ -29,6 +54,23 @@ footer {
|
||||
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 {
|
||||
background-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);
|
||||
}
|
||||
|
||||
nav li a:has(> div.logo) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.thermostat {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
function setupForm(formSelector) {
|
||||
function setupForm(formSelector, onResultCallback = null) {
|
||||
const form = document.querySelector(formSelector);
|
||||
if (!form) {
|
||||
return;
|
||||
@@ -27,7 +27,7 @@ function setupForm(formSelector) {
|
||||
button.setAttribute('aria-busy', true);
|
||||
}
|
||||
|
||||
const onSuccess = (response) => {
|
||||
const onSuccess = (result) => {
|
||||
if (button) {
|
||||
button.textContent = 'Saved';
|
||||
button.classList.add('success');
|
||||
@@ -41,7 +41,7 @@ function setupForm(formSelector) {
|
||||
}
|
||||
};
|
||||
|
||||
const onFailed = (response) => {
|
||||
const onFailed = () => {
|
||||
if (button) {
|
||||
button.textContent = 'Error';
|
||||
button.classList.add('failed');
|
||||
@@ -71,15 +71,20 @@ function setupForm(formSelector) {
|
||||
body: form2json(fd)
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
onSuccess(response);
|
||||
if (!response.ok) {
|
||||
throw new Error('Response not valid');
|
||||
}
|
||||
|
||||
} else {
|
||||
onFailed(response);
|
||||
const result = response.status != 204 ? (await response.json()) : null;
|
||||
onSuccess(result);
|
||||
|
||||
if (onResultCallback instanceof Function) {
|
||||
onResultCallback(result);
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
onFailed(false);
|
||||
console.log(err);
|
||||
onFailed();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -410,6 +415,9 @@ function setupUpgradeForm(formSelector) {
|
||||
form.addEventListener('submit', async (event) => {
|
||||
event.preventDefault();
|
||||
|
||||
hide('.upgrade-firmware-result');
|
||||
hide('.upgrade-filesystem-result');
|
||||
|
||||
if (button) {
|
||||
button.textContent = 'Uploading...';
|
||||
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) {
|
||||
let busy = document.querySelector(busySelector);
|
||||
let content = document.querySelector(contentSelector);
|
||||
if (!busy || !content) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!value) {
|
||||
busy.classList.add('hidden');
|
||||
content.classList.remove('hidden');
|
||||
hide(busySelector);
|
||||
show(contentSelector);
|
||||
|
||||
} else {
|
||||
busy.classList.remove('hidden');
|
||||
content.classList.add('hidden');
|
||||
show(busySelector);
|
||||
hide(contentSelector);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -601,12 +467,14 @@ function setState(selector, value) {
|
||||
}
|
||||
|
||||
function setValue(selector, value) {
|
||||
let item = document.querySelector(selector);
|
||||
if (!item) {
|
||||
let items = document.querySelectorAll(selector);
|
||||
if (!items.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
item.innerHTML = value;
|
||||
for (let item of items) {
|
||||
item.innerHTML = value;
|
||||
}
|
||||
}
|
||||
|
||||
function setCheckboxValue(selector, value) {
|
||||
@@ -629,15 +497,106 @@ function setRadioValue(selector, value) {
|
||||
}
|
||||
}
|
||||
|
||||
function setInputValue(selector, value) {
|
||||
let item = document.querySelector(selector);
|
||||
if (!item) {
|
||||
function setInputValue(selector, value, attrs = {}) {
|
||||
let items = document.querySelectorAll(selector);
|
||||
if (!items.length) {
|
||||
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",
|
||||
41: "Italtherm",
|
||||
56: "Baxi Luna Duo-Tec",
|
||||
131: "Nefit",
|
||||
148: "Navien",
|
||||
173: "Intergas",
|
||||
247: "Baxi Ampera",
|
||||
248: "Zota Lux-X"
|
||||
};
|
||||
|
||||
return (memberId in vendorList)
|
||||
? vendorList[memberId]
|
||||
: "unknown vendor";
|
||||
}
|
||||
|
||||
function form2json(data) {
|
||||
let method = function (object, pair) {
|
||||
|
||||
4
src_data/static/pico.min.css
vendored
4
src_data/static/pico.min.css
vendored
File diff suppressed because one or more lines are too long
@@ -36,7 +36,7 @@
|
||||
Settings file:
|
||||
<input type="file" name="settings" id="restore-file" accept=".json">
|
||||
</label>
|
||||
|
||||
|
||||
<div class="grid">
|
||||
<button type="submit">Restore</button>
|
||||
<button type="button" class="secondary" onclick="window.location='/api/backup/save';">Backup</button>
|
||||
@@ -50,7 +50,7 @@
|
||||
<hgroup>
|
||||
<h2>Upgrade</h2>
|
||||
<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.
|
||||
</p>
|
||||
</hgroup>
|
||||
@@ -64,7 +64,7 @@
|
||||
<button type="button" class="upgrade-firmware-result hidden" disabled></button>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
|
||||
<label for="filesystem-file">
|
||||
Filesystem:
|
||||
<div class="grid">
|
||||
@@ -73,7 +73,7 @@
|
||||
</div>
|
||||
</label>
|
||||
</fieldset>
|
||||
|
||||
|
||||
<ul>
|
||||
<li><mark>After a successful upgrade the filesystem, ALL settings will be reset to default values! Save backup before upgrading.</mark></li>
|
||||
<li><mark>After a successful upgrade, the device will automatically reboot after 10 seconds.</mark></li>
|
||||
@@ -1,4 +1,5 @@
|
||||
import shutil
|
||||
import gzip
|
||||
import os
|
||||
Import("env")
|
||||
|
||||
@@ -12,6 +13,23 @@ def post_build(source, target, env):
|
||||
env.Execute("pio run --target buildfs --environment %s" % env["PIOENV"]);
|
||||
|
||||
|
||||
def before_buildfs(source, target, env):
|
||||
src = os.path.join(env["PROJECT_DIR"], "src_data")
|
||||
dst = os.path.join(env["PROJECT_DIR"], "data")
|
||||
|
||||
for root, dirs, files in os.walk(src, topdown=False):
|
||||
for name in files:
|
||||
src_path = os.path.join(root, name)
|
||||
|
||||
with open(src_path, 'rb') as f_in:
|
||||
dst_name = name + ".gz"
|
||||
dst_path = os.path.join(dst, os.path.relpath(root, src), dst_name)
|
||||
|
||||
with gzip.open(dst_path, 'wb', 9) as f_out:
|
||||
shutil.copyfileobj(f_in, f_out)
|
||||
|
||||
print("Compressed '%s' to '%s'" % (src_path, dst_path))
|
||||
|
||||
def after_buildfs(source, target, env):
|
||||
copy_to_build_dir({
|
||||
source[0].get_abspath(): "filesystem_%s_%s.bin" % (env["PIOENV"], env.GetProjectOption("version")),
|
||||
@@ -31,4 +49,6 @@ def copy_to_build_dir(files, build_dir):
|
||||
|
||||
|
||||
env.AddPostAction("buildprog", post_build)
|
||||
env.AddPreAction("$BUILD_DIR/spiffs.bin", before_buildfs)
|
||||
env.AddPreAction("$BUILD_DIR/littlefs.bin", before_buildfs)
|
||||
env.AddPostAction("buildfs", after_buildfs)
|
||||
Reference in New Issue
Block a user