* feat: new portal & network manager

* refactor: migrate from PubSubClient to ArduinoMqttClient
* refactor: migrate from EEManager to FileData
* chore: bump ESP Telnet to 2.2
* chore: bump TinyLogger to 1.1.0
This commit is contained in:
Yurii
2024-01-12 18:29:55 +03:00
parent b36e4dca42
commit ab1e9c761f
34 changed files with 4683 additions and 1125 deletions

230
data/index.html Normal file
View File

@@ -0,0 +1,230 @@
<!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="/"><kbd>OpenTherm Gateway</kbd></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"></b> sec.</td>
</tr>
<tr>
<th scope="row">Free memory:</th>
<td><b class="free-heap"></b> of <b class="total-heap"></b> bytes, max block: <b class="max-free-block-heap"></b> bytes</td>
</tr>
<tr>
<th scope="row">Last reset reason:</th>
<td><b class="reset-reason"></b></td>
</tr>
</tbody>
</table>
<div class="grid">
<a href="/settings.html" role="button">Settings</a>
<a href="/upgrade.html" role="button">Upgrade</a>
<a href="/restart.html" role="button" class="secondary restart">Restart</a>
</div>
</div>
</article>
<article>
<div>
<hgroup>
<h2>States and sensors</h2>
<p>More information and settings can be found in your home assistant after setting up network and MQTT</p>
</hgroup>
<div class="ot-busy" aria-busy="true"></div>
<table class="ot-table hidden">
<tbody>
<tr>
<th scope="row">OpenTherm connected:</th>
<td><input type="radio" class="ot-connected" aria-invalid="false" checked disabled /></td>
</tr>
<tr>
<th scope="row">MQTT connected:</th>
<td><input type="radio" class="mqtt-connected" aria-invalid="false" checked disabled /></td>
</tr>
<tr>
<th scope="row">Emergency:</th>
<td><input type="radio" class="ot-emergency" aria-invalid="false" checked disabled /></td>
</tr>
<tr>
<th scope="row">Heating:</th>
<td><input type="radio" class="ot-heating" aria-invalid="false" checked disabled /></td>
</tr>
<tr>
<th scope="row">DHW:</th>
<td><input type="radio" class="ot-dhw" aria-invalid="false" checked disabled /></td>
</tr>
<tr>
<th scope="row">Flame:</th>
<td><input type="radio" class="ot-flame" aria-invalid="false" checked disabled /></td>
</tr>
<tr>
<th scope="row">Fault:</th>
<td><input type="radio" class="ot-fault" aria-invalid="false" checked disabled /></td>
</tr>
<tr>
<th scope="row">Diagnostic:</th>
<td><input type="radio" class="ot-diagnostic" aria-invalid="false" checked disabled /></td>
</tr>
<tr>
<th scope="row">External pump:</th>
<td><input type="radio" class="ot-external-pump" aria-invalid="false" checked disabled /></td>
</tr>
<tr>
<th scope="row">Modulation:</th>
<td><b class="ot-modulation"></b> %</td>
</tr>
<tr>
<th scope="row">Pressure:</th>
<td><b class="ot-pressure"></b> bar</td>
</tr>
<tr>
<th scope="row">DHW flow rate:</th>
<td><b class="ot-dhw-flow-rate"></b> l/min</td>
</tr>
<tr>
<th scope="row">Fault code:</th>
<td><b class="ot-fault-code"></b></td>
</tr>
<tr>
<th scope="row">Indoor temp:</th>
<td><b class="indoor-temp"></b> C</td>
</tr>
<tr>
<th scope="row">Outdoor temp:</th>
<td><b class="outdoor-temp"></b> C</td>
</tr>
<tr>
<th scope="row">Heating temp:</th>
<td><b class="heating-temp"></b> C</td>
</tr>
<tr>
<th scope="row">Heating setpoint temp:</th>
<td><b class="heating-setpoint-temp"></b> C</td>
</tr>
<tr>
<th scope="row">DHW temp:</th>
<td><b class="dhw-temp"></b> C</td>
</tr>
</tbody>
</table>
</div>
</article>
</main>
<footer class="container">
<small>
<b>Made by Laxilef</b>
<a href="https://github.com/Laxilef/OTGateway/blob/master/LICENSE" target="_blank" class="secondary">License</a>
<a href="https://github.com/Laxilef/OTGateway/blob/master/" target="_blank" class="secondary">Source code</a>
<a href="https://github.com/Laxilef/OTGateway/wiki" target="_blank" class="secondary">Help</a>
<a href="https://github.com/Laxilef/OTGateway/issue" target="_blank" class="secondary">Issue & questions</a>
<a href="https://github.com/Laxilef/OTGateway/releases" target="_blank" class="secondary">Releases</a>
</small>
</footer>
<script src="/static/app.js"></script>
<script>
window.onload = async function () {
setTimeout(async function onLoadPage() {
await loadNetworkStatus();
await loadVars();
setTimeout(onLoadPage, 10000);
}, 1000);
};
</script>
</body>
</html>

166
data/network.html Normal file
View File

@@ -0,0 +1,166 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Network 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="/"><kbd>OpenTherm Gateway</kbd></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 settings</h2>
<p></p>
</hgroup>
<div id="network-settings-busy" aria-busy="true"></div>
<form action="/api/network/settings" id="network-settings" class="hidden">
<label for="hostname">
Hostname
<input type="text" class="network-hostname" name="hostname" maxlength="24" required>
</label>
<label for="network-use-dhcp">
<input type="checkbox" class="network-use-dhcp" name="useDhcp" value="true">
Use DHCP
</label>
<br>
<hr>
<label for="network-static-ip">
Static IP:
<input type="text" class="network-static-ip" name="staticConfig[ip]" value="true" maxlength="16" required>
</label>
<label for="network-static-gateway">
Static gateway:
<input type="text" class="network-static-gateway" name="staticConfig[gateway]" maxlength="16" required>
</label>
<label for="network-static-subnet">
Static subnet:
<input type="text" class="network-static-subnet" name="staticConfig[subnet]" maxlength="16" required>
</label>
<label for="network-static-dns">
Static DNS:
<input type="text" class="network-static-dns" name="staticConfig[dns]" maxlength="16" required>
</label>
<button type="submit">Save</button>
</form>
</div>
</article>
<article>
<div>
<hgroup>
<h3>Available networks</h3>
<p></p>
</hgroup>
<form action="/api/network/scan" id="network-scan">
<figure style="max-height: 25em;">
<table id="networks" role="grid">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">SSID</th>
<th scope="col">Signal</th>
</tr>
</thead>
<tbody></tbody>
</table>
</figure>
<button type="submit">Refresh</button>
</form>
<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>
</label>
<label for="sta-password">
Password:
<input type="password" class="sta-password" name="sta[password]" maxlength="64" required>
</label>
<label for="sta-channel">
Channel:
<input type="number" class="sta-channel" name="sta[channel]" min="0" max="12" maxlength="2" required>
<small>set 0 for auto select</small>
</label>
<button type="submit">Save</button>
</form>
</div>
</div>
</article>
<article>
<div>
<hgroup>
<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>
</label>
<label for="ap-password">
Password:
<input type="text" class="ap-password" name="ap[password]" maxlength="64" required>
</label>
<label for="ap-channel">
Channel:
<input type="number" class="ap-channel" name="ap[channel]" min="1" max="12" required>
</label>
<button type="submit">Save</button>
</form>
</div>
</article>
</main>
<footer class="container">
<small>
<b>Made by Laxilef</b>
<a href="https://github.com/Laxilef/OTGateway/blob/master/LICENSE" target="_blank" class="secondary">License</a>
<a href="https://github.com/Laxilef/OTGateway/blob/master/" target="_blank" class="secondary">Source code</a>
<a href="https://github.com/Laxilef/OTGateway/wiki" target="_blank" class="secondary">Help</a>
<a href="https://github.com/Laxilef/OTGateway/issue" target="_blank" class="secondary">Issue & questions</a>
<a href="https://github.com/Laxilef/OTGateway/releases" target="_blank" class="secondary">Releases</a>
</small>
</footer>
<script src="/static/app.js"></script>
<script>
window.onload = async function () {
await loadNetworkSettings();
setupForm('#network-settings');
setupNetworkScanForm('#network-scan', '#networks');
setupForm('#sta-settings');
setupForm('#ap-settings');
};
</script>
</body>
</html>

275
data/settings.html Normal file
View File

@@ -0,0 +1,275 @@
<!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="/"><kbd>OpenTherm Gateway</kbd></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" class="opentherm-in-pin" name="opentherm[inPin]" maxlength="2" required>
</label>
<label for="opentherm-in-pin">
OUT gpio
<input type="number" class="opentherm-out-pin" name="opentherm[outPin]" maxlength="2" required>
</label>
<label for="opentherm-member-id-code">
Master MemberID code
<input type="number" class="opentherm-member-id-code" name="opentherm[memberIdCode]" maxlength="2" required>
</label>
</div>
<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" class="mqtt-port" name="mqtt[port]" maxlength="5" 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" class="mqtt-interval" name="mqtt[interval]" maxlength="2" required>
</label>
</div>
<button type="submit">Save</button>
</form>
</div>
</article>
<article>
<div>
<hgroup>
<h2>Sensor settings</h2>
<p></p>
</hgroup>
<div id="sensors-settings-busy" aria-busy="true"></div>
<form action="/api/settings" id="sensors-settings" class="hidden">
<div class="grid">
<label for="sensors-outdoor-pin">
Outdoor GPIO
<input type="number" class="sensors-outdoor-pin" name="sensors[outdoor][pin]" maxlength="2" required>
</label>
<label for="sensors-outdoor-offset">
Outdoor offset
<input type="number" class="sensors-outdoor-offset" name="sensors[outdoor][offset]" maxlength="2" required>
</label>
</div>
<hr><br>
<div class="grid">
<label for="sensors-indoor-pin">
Indoor GPIO
<input type="number" class="sensors-indoor-pin" name="sensors[indoor][pin]" maxlength="2" required>
</label>
<label for="sensors-indoor-offset">
Indoor offset
<input type="number" class="sensors-indoor-offset" name="sensors[indoor][offset]" maxlength="2" required>
</label>
</div>
<label for="sensors-indoor-ble-addresss">
BLE addresss
<input type="text" class="sensors-indoor-ble-addresss" name="sensors[indoor][bleAddresss]" maxlength="17">
<small>ONLY for ESP32</small>
</label>
<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" class="extpump-pin" name="externalPump[pin]" maxlength="2" required>
</label>
<label for="extpump-pc-time">
Post circulation time <small>(min)</small>
<input type="number" class="extpump-pc-time" name="externalPump[postCirculationTime]" maxlength="2" required>
</label>
</div>
<div class="grid">
<label for="extpump-as-interval">
Anti stuck interval <small>(days)</small>
<input type="number" class="extpump-as-interval" name="externalPump[antiStuckInterval]" maxlength="3" required>
</label>
<label for="extpump-as-time">
Anti stuck time <small>(min)</small>
<input type="number" class="extpump-as-time" name="externalPump[antiStuckTime]" maxlength="2" required>
</label>
</div>
<button type="submit">Save</button>
</form>
</div>
</article>
</main>
<footer class="container">
<small>
<b>Made by Laxilef</b>
<a href="https://github.com/Laxilef/OTGateway/blob/master/LICENSE" target="_blank" class="secondary">License</a>
<a href="https://github.com/Laxilef/OTGateway/blob/master/" target="_blank" class="secondary">Source code</a>
<a href="https://github.com/Laxilef/OTGateway/wiki" target="_blank" class="secondary">Help</a>
<a href="https://github.com/Laxilef/OTGateway/issue" target="_blank" class="secondary">Issue & questions</a>
<a href="https://github.com/Laxilef/OTGateway/releases" target="_blank" class="secondary">Releases</a>
</small>
</footer>
<script src="/static/app.js"></script>
<script>
window.onload = async function () {
await loadSettings();
setupForm('#portal-settings');
setupForm('#opentherm-settings');
setupForm('#mqtt-settings');
setupForm('#sensors-settings');
setupForm('#extpump-settings');
};
</script>
</body>
</html>

BIN
data/static/app.css.gz Normal file

Binary file not shown.

BIN
data/static/app.js.gz Normal file

Binary file not shown.

BIN
data/static/favicon.ico.gz Normal file

Binary file not shown.

BIN
data/static/pico.min.css.gz Normal file

Binary file not shown.

108
data/upgrade.html Normal file
View File

@@ -0,0 +1,108 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Upgrade - 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="/"><kbd>OpenTherm Gateway</kbd></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>Backup & restore</h2>
<p>
In this section you can save and restore a backup of ALL settings.
</p>
</hgroup>
<form action="/api/backup/restore" id="restore">
<label for="restore-file">
Settings file:
<input type="file" name="settings" id="restore-file" accept="application/JSON">
</label>
<div class="grid">
<button type="submit">Restore</button>
<button type="button" class="secondary" onclick="window.location='/api/backup/save';">Backup</button>
</div>
</form>
</div>
</article>
<article>
<div>
<hgroup>
<h2>Upgrade</h2>
<p>
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>
<form action="/api/upgrade" id="upgrade">
<fieldset class="primary">
<label for="firmware-file">
Firmware:
<div class="grid">
<input type="file" name="firmware" id="firmware-file" accept="application/x-binary">
<button type="button" class="upgrade-firmware-result hidden" disabled></button>
</div>
</label>
<label for="filesystem-file">
Filesystem:
<div class="grid">
<input type="file" name="filesystem" id="filesystem-file" accept="application/x-binary">
<button type="button" class="upgrade-filesystem-result hidden" disabled></button>
</div>
</label>
</fieldset>
<ul>
<li><mark>After a successful upgrade the filesystem, ALL settings will be reset to default values! Save them before upgrading.</mark></li>
<li><mark>After a successful upgrade, the device will automatically reboot after 10 seconds.</mark></li>
</ul>
<button type="submit">Upgrade</button>
</form>
</div>
</article>
</main>
<footer class="container">
<small>
<b>Made by Laxilef</b>
<a href="https://github.com/Laxilef/OTGateway/blob/master/LICENSE" target="_blank" class="secondary">License</a>
<a href="https://github.com/Laxilef/OTGateway/blob/master/" target="_blank" class="secondary">Source code</a>
<a href="https://github.com/Laxilef/OTGateway/wiki" target="_blank" class="secondary">Help</a>
<a href="https://github.com/Laxilef/OTGateway/issue" target="_blank" class="secondary">Issue & questions</a>
<a href="https://github.com/Laxilef/OTGateway/releases" target="_blank" class="secondary">Releases</a>
</small>
</footer>
<script src="/static/app.js"></script>
<script>
window.onload = async function () {
setupRestoreBackupForm('#restore');
setupUpgradeForm('#upgrade');
};
</script>
</body>
</html>