mirror of
https://github.com/Laxilef/OTGateway.git
synced 2025-12-10 18:24:27 +05:00
feat: many features
* added dashboard on portal * added settings for serial port and telnet * added on/off settings for mqtt * added event selection for emergency mode * refactor html & css
This commit is contained in:
387
data/dashboard.html
Normal file
387
data/dashboard.html
Normal file
@@ -0,0 +1,387 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Dashboard - OpenTherm Gateway</title>
|
||||
<link rel="stylesheet" href="/static/pico.min.css">
|
||||
<link rel="stylesheet" href="/static/app.css"/>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<header class="container">
|
||||
<nav>
|
||||
<ul>
|
||||
<li><a href="/"><div class="logo">OpenTherm Gateway</div></a></li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li><a href="https://github.com/Laxilef/OTGateway/wiki" role="button" class="secondary" target="_blank">Help</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main class="container">
|
||||
<article>
|
||||
<hgroup>
|
||||
<h2>Dashboard</h2>
|
||||
<p></p>
|
||||
</hgroup>
|
||||
|
||||
<div id="dashboard-busy" aria-busy="true"></div>
|
||||
<div id="dashboard-container" class="hidden">
|
||||
<details open>
|
||||
<summary><b>Control</b></summary>
|
||||
<div class="grid">
|
||||
<div class="thermostat" id="thermostat-heating">
|
||||
<div class="thermostat-header">Heating</div>
|
||||
<div class="thermostat-temp">
|
||||
<div class="thermostat-temp-target"><span id="thermostat-heating-target"></span> <span class="temp-unit"></span></div>
|
||||
<div class="thermostat-temp-current">Current: <span id="thermostat-heating-current"></span> <span class="temp-unit"></span></div>
|
||||
</div>
|
||||
<div class="thermostat-minus"><button id="thermostat-heating-minus" class="outline"><b>-</b></button></div>
|
||||
<div class="thermostat-plus"><button id="thermostat-heating-plus" class="outline"><b>+</b></button></div>
|
||||
<div class="thermostat-control">
|
||||
<input type="checkbox" role="switch" id="thermostat-heating-enabled" value="true">
|
||||
<label htmlFor="thermostat-heating-enabled">Enable</label>
|
||||
|
||||
<input type="checkbox" role="switch" id="thermostat-heating-turbo" value="true">
|
||||
<label htmlFor="thermostat-heating-turbo">Turbo mode</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="thermostat" id="thermostat-dhw">
|
||||
<div class="thermostat-header">DHW</div>
|
||||
<div class="thermostat-temp">
|
||||
<div class="thermostat-temp-target"><span id="thermostat-dhw-target"></span> <span class="temp-unit"></span></div>
|
||||
<div class="thermostat-temp-current">Current: <span id="thermostat-dhw-current"></span> <span class="temp-unit"></span></div>
|
||||
</div>
|
||||
<div class="thermostat-minus"><button class="outline" id="thermostat-dhw-minus"><b>-</b></button></div>
|
||||
<div class="thermostat-plus"><button class="outline" id="thermostat-dhw-plus"><b>+</b></button></div>
|
||||
<div class="thermostat-control">
|
||||
<input type="checkbox" role="switch" id="thermostat-dhw-enabled" value="true">
|
||||
<label htmlFor="thermostat-dhw-enabled">Enable</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<hr />
|
||||
|
||||
<details>
|
||||
<summary><b>States and sensors</b></summary>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="row">OpenTherm connected:</th>
|
||||
<td><input type="radio" id="ot-connected" aria-invalid="false" checked disabled /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">MQTT connected:</th>
|
||||
<td><input type="radio" id="mqtt-connected" aria-invalid="false" checked disabled /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Emergency:</th>
|
||||
<td><input type="radio" id="ot-emergency" aria-invalid="false" checked disabled /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Heating:</th>
|
||||
<td><input type="radio" id="ot-heating" aria-invalid="false" checked disabled /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">DHW:</th>
|
||||
<td><input type="radio" id="ot-dhw" aria-invalid="false" checked disabled /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Flame:</th>
|
||||
<td><input type="radio" id="ot-flame" aria-invalid="false" checked disabled /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Fault:</th>
|
||||
<td><input type="radio" id="ot-fault" aria-invalid="false" checked disabled /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Diagnostic:</th>
|
||||
<td><input type="radio" id="ot-diagnostic" aria-invalid="false" checked disabled /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">External pump:</th>
|
||||
<td><input type="radio" id="ot-external-pump" aria-invalid="false" checked disabled /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Modulation:</th>
|
||||
<td><b id="ot-modulation"></b> %</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Pressure:</th>
|
||||
<td><b id="ot-pressure"></b> <span class="pressure-unit"></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">DHW flow rate:</th>
|
||||
<td><b id="ot-dhw-flow-rate"></b> <span class="volume-unit"></span>/min</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Fault code:</th>
|
||||
<td><b id="ot-fault-code"></b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Indoor temp:</th>
|
||||
<td><b id="indoor-temp"></b> <span class="temp-unit"></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Outdoor temp:</th>
|
||||
<td><b id="outdoor-temp"></b> <span class="temp-unit"></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Heating temp:</th>
|
||||
<td><b id="heating-temp"></b> <span class="temp-unit"></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Heating setpoint temp:</th>
|
||||
<td><b id="heating-setpoint-temp"></b> <span class="temp-unit"></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Heating return temp:</th>
|
||||
<td><b id="heating-return-temp"></b> <span class="temp-unit"></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">DHW temp:</th>
|
||||
<td><b id="dhw-temp"></b> <span class="temp-unit"></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Exhaust temp:</th>
|
||||
<td><b id="exhaust-temp"></b> <span class="temp-unit"></span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</details>
|
||||
|
||||
<hr />
|
||||
|
||||
<details>
|
||||
<summary><b>OpenTherm diagnostic</b></summary>
|
||||
<pre><b>Vendor:</b> <span id="slave-vendor"></span>
|
||||
<b>Member ID:</b> <span id="slave-member-id"></span>
|
||||
<b>Flags:</b> <span id="slave-flags"></span>
|
||||
<b>Type:</b> <span id="slave-type"></span>
|
||||
<b>Version:</b> <span id="slave-version"></span>
|
||||
<b>OT version:</b> <span id="slave-ot-version"></span>
|
||||
<b>Heating limits:</b> <span id="heating-min-temp"></span>...<span id="heating-max-temp"></span> <span class="temp-unit"></span>
|
||||
<b>DHW limits:</b> <span id="dhw-min-temp"></span>...<span id="dhw-max-temp"></span> <span class="temp-unit"></span></pre>
|
||||
</details>
|
||||
</div>
|
||||
</article>
|
||||
</main>
|
||||
|
||||
<footer class="container">
|
||||
<small>
|
||||
<b>Made by Laxilef</b>
|
||||
• <a href="https://github.com/Laxilef/OTGateway/blob/master/LICENSE" target="_blank" class="secondary">License</a>
|
||||
• <a href="https://github.com/Laxilef/OTGateway/blob/master/" target="_blank" class="secondary">Source code</a>
|
||||
• <a href="https://github.com/Laxilef/OTGateway/wiki" target="_blank" class="secondary">Help</a>
|
||||
• <a href="https://github.com/Laxilef/OTGateway/issues" target="_blank" class="secondary">Issue & questions</a>
|
||||
• <a href="https://github.com/Laxilef/OTGateway/releases" target="_blank" class="secondary">Releases</a>
|
||||
</small>
|
||||
</footer>
|
||||
|
||||
<script src="/static/app.js"></script>
|
||||
<script>
|
||||
let modifiedTime = null;
|
||||
let noRegulators;
|
||||
let prevSettings;
|
||||
let newSettings = {
|
||||
heating: {
|
||||
enable: false,
|
||||
turbo: false,
|
||||
target: 0
|
||||
},
|
||||
dhw: {
|
||||
enable: false,
|
||||
target: 0
|
||||
}
|
||||
};
|
||||
|
||||
window.onload = async function () {
|
||||
document.querySelector('#thermostat-heating-minus').addEventListener('click', (event) => {
|
||||
newSettings.heating.target -= 0.5;
|
||||
modifiedTime = Date.now();
|
||||
|
||||
const minTemp = noRegulators ? prevSettings.heating.minTemp : 5;
|
||||
if (prevSettings && newSettings.heating.target < minTemp) {
|
||||
newSettings.heating.target = minTemp;
|
||||
}
|
||||
|
||||
setValue('#thermostat-heating-target', newSettings.heating.target);
|
||||
});
|
||||
|
||||
document.querySelector('#thermostat-heating-plus').addEventListener('click', (event) => {
|
||||
newSettings.heating.target += 0.5;
|
||||
modifiedTime = Date.now();
|
||||
|
||||
const maxTemp = noRegulators ? prevSettings.heating.maxTemp : 30;
|
||||
if (prevSettings && newSettings.heating.target > maxTemp) {
|
||||
newSettings.heating.target = maxTemp;
|
||||
}
|
||||
|
||||
setValue('#thermostat-heating-target', newSettings.heating.target);
|
||||
});
|
||||
|
||||
document.querySelector('#thermostat-dhw-minus').addEventListener('click', (event) => {
|
||||
newSettings.dhw.target -= 1;
|
||||
modifiedTime = Date.now();
|
||||
|
||||
if (prevSettings && newSettings.dhw.target < prevSettings.dhw.minTemp) {
|
||||
newSettings.dhw.target = prevSettings.dhw.minTemp;
|
||||
}
|
||||
|
||||
setValue('#thermostat-dhw-target', newSettings.dhw.target);
|
||||
});
|
||||
|
||||
document.querySelector('#thermostat-dhw-plus').addEventListener('click', (event) => {
|
||||
newSettings.dhw.target += 1;
|
||||
modifiedTime = Date.now();
|
||||
|
||||
if (prevSettings && newSettings.dhw.target > prevSettings.dhw.maxTemp) {
|
||||
newSettings.dhw.target = prevSettings.dhw.maxTemp;
|
||||
}
|
||||
|
||||
setValue('#thermostat-dhw-target', newSettings.dhw.target);
|
||||
});
|
||||
|
||||
document.querySelector('#thermostat-heating-enabled').addEventListener('change', (event) => {
|
||||
modifiedTime = Date.now();
|
||||
newSettings.heating.enable = event.currentTarget.checked;
|
||||
});
|
||||
|
||||
document.querySelector('#thermostat-heating-turbo').addEventListener('change', (event) => {
|
||||
modifiedTime = Date.now();
|
||||
newSettings.heating.turbo = event.currentTarget.checked;
|
||||
});
|
||||
|
||||
document.querySelector('#thermostat-dhw-enabled').addEventListener('change', (event) => {
|
||||
modifiedTime = Date.now();
|
||||
newSettings.heating.dhw = event.currentTarget.checked;
|
||||
});
|
||||
|
||||
setTimeout(async function onLoadPage() {
|
||||
if (modifiedTime) {
|
||||
if ((Date.now() - modifiedTime) < 5000) {
|
||||
setTimeout(onLoadPage, 1000);
|
||||
return;
|
||||
}
|
||||
|
||||
modifiedTime = null;
|
||||
}
|
||||
|
||||
// settings
|
||||
try {
|
||||
let modified = prevSettings && (
|
||||
(prevSettings.heating.enable != newSettings.heating.enable)
|
||||
|| (prevSettings.heating.turbo != newSettings.heating.turbo)
|
||||
|| (prevSettings.heating.target != newSettings.heating.target)
|
||||
|| (prevSettings.opentherm.dhwPresent && prevSettings.dhw.enable != newSettings.dhw.enable)
|
||||
|| (prevSettings.opentherm.dhwPresent && prevSettings.dhw.target != newSettings.dhw.target)
|
||||
);
|
||||
|
||||
if (modified) {
|
||||
console.log(newSettings);
|
||||
}
|
||||
|
||||
let parameters = {cache: 'no-cache'};
|
||||
if (modified) {
|
||||
parameters.method = "POST";
|
||||
parameters.body = JSON.stringify(newSettings);
|
||||
}
|
||||
|
||||
const response = await fetch('/api/settings', parameters);
|
||||
if (!response.ok) {
|
||||
throw new Error('Response not valid');
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
noRegulators = !result.equitherm.enable && !result.pid.enable;
|
||||
prevSettings = result;
|
||||
newSettings.heating.enable = result.heating.enable;
|
||||
newSettings.heating.turbo = result.heating.turbo;
|
||||
newSettings.heating.target = result.heating.target;
|
||||
newSettings.dhw.enable = result.dhw.enable;
|
||||
newSettings.dhw.target = result.dhw.target;
|
||||
|
||||
if (result.opentherm.dhwPresent) {
|
||||
show('#thermostat-dhw');
|
||||
} else {
|
||||
hide('#thermostat-dhw');
|
||||
}
|
||||
|
||||
setCheckboxValue('#thermostat-heating-enabled', result.heating.enable);
|
||||
setCheckboxValue('#thermostat-heating-turbo', result.heating.turbo);
|
||||
setValue('#thermostat-heating-target', result.heating.target);
|
||||
|
||||
setCheckboxValue('#thermostat-dhw-enabled', result.dhw.enable);
|
||||
setValue('#thermostat-dhw-target', result.dhw.target);
|
||||
|
||||
setValue('.temp-unit', temperatureUnit(result.system.unitSystem));
|
||||
setValue('.pressure-unit', pressureUnit(result.system.unitSystem));
|
||||
setValue('.volume-unit', volumeUnit(result.system.unitSystem));
|
||||
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
|
||||
// vars
|
||||
try {
|
||||
const response = await fetch('/api/vars', { cache: 'no-cache' });
|
||||
if (!response.ok) {
|
||||
throw new Error('Response not valid');
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
setValue('#thermostat-heating-current', noRegulators ? result.temperatures.heating : result.temperatures.indoor);
|
||||
setValue('#thermostat-dhw-current', result.temperatures.dhw);
|
||||
|
||||
setState('#ot-connected', result.states.otStatus);
|
||||
setState('#ot-emergency', result.states.emergency);
|
||||
setState('#ot-heating', result.states.heating);
|
||||
setState('#ot-dhw', result.states.dhw);
|
||||
setState('#ot-flame', result.states.flame);
|
||||
setState('#ot-fault', result.states.fault);
|
||||
setState('#ot-diagnostic', result.states.diagnostic);
|
||||
setState('#ot-external-pump', result.states.externalPump);
|
||||
setState('#mqtt-connected', result.states.mqtt);
|
||||
|
||||
setValue('#ot-modulation', result.sensors.modulation);
|
||||
setValue('#ot-pressure', result.sensors.pressure);
|
||||
setValue('#ot-dhw-flow-rate', result.sensors.dhwFlowRate);
|
||||
setValue('#ot-fault-code', result.sensors.faultCode ? ("E" + result.sensors.faultCode) : "-");
|
||||
|
||||
setValue('#indoor-temp', result.temperatures.indoor);
|
||||
setValue('#outdoor-temp', result.temperatures.outdoor);
|
||||
setValue('#heating-temp', result.temperatures.heating);
|
||||
setValue('#heating-return-temp', result.temperatures.heatingReturn);
|
||||
setValue('#dhw-temp', result.temperatures.dhw);
|
||||
setValue('#exhaust-temp', result.temperatures.exhaust);
|
||||
|
||||
setValue('#heating-min-temp', result.parameters.heatingMinTemp);
|
||||
setValue('#heating-max-temp', result.parameters.heatingMaxTemp);
|
||||
setValue('#heating-setpoint-temp', result.parameters.heatingSetpoint);
|
||||
setValue('#dhw-min-temp', result.parameters.dhwMinTemp);
|
||||
setValue('#dhw-max-temp', result.parameters.dhwMaxTemp);
|
||||
|
||||
setValue('#slave-member-id', result.parameters.slaveMemberId);
|
||||
setValue('#slave-vendor', memberIdToVendor(result.parameters.slaveMemberId));
|
||||
|
||||
setValue('#slave-flags', result.parameters.slaveFlags);
|
||||
setValue('#slave-type', result.parameters.slaveType);
|
||||
setValue('#slave-version', result.parameters.slaveVersion);
|
||||
setValue('#slave-ot-version', result.parameters.slaveOtVersion);
|
||||
setBusy('#dashboard-busy', '#dashboard-container', false);
|
||||
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
|
||||
setTimeout(onLoadPage, 10000);
|
||||
}, 1000);
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
187
data/index.html
187
data/index.html
@@ -28,44 +28,44 @@
|
||||
<p></p>
|
||||
</hgroup>
|
||||
|
||||
<div class="main-busy" aria-busy="true"></div>
|
||||
<table class="main-table hidden">
|
||||
<div id="main-busy" aria-busy="true"></div>
|
||||
<table id="main-table" class="hidden">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="row">Hostname:</th>
|
||||
<td><b class="network-hostname"></b></td>
|
||||
<td><b id="network-hostname"></b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">MAC:</th>
|
||||
<td><b class="network-mac"></b></td>
|
||||
<td><b id="network-mac"></b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Connected:</th>
|
||||
<td><input type="radio" class="network-connected" aria-invalid="false" checked disabled /></td>
|
||||
<td><input type="radio" id="network-connected" aria-invalid="false" checked disabled /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">SSID:</th>
|
||||
<td><b class="network-ssid"></b></td>
|
||||
<td><b id="network-ssid"></b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Signal:</th>
|
||||
<td><b class="network-signal"></b> %</td>
|
||||
<td><b id="network-signal"></b> %</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">IP:</th>
|
||||
<td><b class="network-ip"></b></td>
|
||||
<td><b id="network-ip"></b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Subnet:</th>
|
||||
<td><b class="network-subnet"></b></td>
|
||||
<td><b id="network-subnet"></b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Gateway:</th>
|
||||
<td><b class="network-gateway"></b></td>
|
||||
<td><b id="network-gateway"></b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">DNS:</th>
|
||||
<td><b class="network-dns"></b></td>
|
||||
<td><b id="network-dns"></b></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -82,149 +82,40 @@
|
||||
<p></p>
|
||||
</hgroup>
|
||||
|
||||
<div class="system-busy" aria-busy="true"></div>
|
||||
<table class="system-table hidden">
|
||||
<div id="system-busy" aria-busy="true"></div>
|
||||
<table id="system-table" class="hidden">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="row">Version:</th>
|
||||
<td><b class="version"></b></td>
|
||||
<td><b id="version"></b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Build date:</th>
|
||||
<td><b class="build-date"></b></td>
|
||||
<td><b id="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>
|
||||
<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 class="free-heap"></b> of <b class="total-heap"></b> bytes (min: <b class="min-free-heap"></b> bytes)<br>max free block: <b class="max-free-block-heap"></b> bytes (min: <b class="min-max-free-block-heap"></b> bytes)</td>
|
||||
<td><b id="free-heap"></b> of <b id="total-heap"></b> bytes (min: <b id="min-free-heap"></b> bytes)<br />max free block: <b id="max-free-block-heap"></b> bytes (min: <b id="min-max-free-block-heap"></b> bytes)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Last reset reason:</th>
|
||||
<td><b class="reset-reason"></b></td>
|
||||
<td><b id="reset-reason"></b></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="grid">
|
||||
<div role="group">
|
||||
<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>
|
||||
|
||||
<article>
|
||||
<div class="vars-busy" aria-busy="true"></div>
|
||||
<div class="vars-table hidden">
|
||||
<hgroup>
|
||||
<h2>Info</h2>
|
||||
<p>More information and settings can be found in your Home Assistant after setting up network and MQTT</p>
|
||||
</hgroup>
|
||||
|
||||
<details>
|
||||
<summary role="button" class="secondary">States and sensors</summary>
|
||||
<table>
|
||||
<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> <span class="pressure-unit"></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">DHW flow rate:</th>
|
||||
<td><b class="ot-dhw-flow-rate"></b> <span class="volume-unit"></span>/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> <span class="temp-unit"></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Outdoor temp:</th>
|
||||
<td><b class="outdoor-temp"></b> <span class="temp-unit"></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Heating temp:</th>
|
||||
<td><b class="heating-temp"></b> <span class="temp-unit"></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Heating setpoint temp:</th>
|
||||
<td><b class="heating-setpoint-temp"></b> <span class="temp-unit"></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Heating return temp:</th>
|
||||
<td><b class="heating-return-temp"></b> <span class="temp-unit"></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">DHW temp:</th>
|
||||
<td><b class="dhw-temp"></b> <span class="temp-unit"></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Exhaust temp:</th>
|
||||
<td><b class="exhaust-temp"></b> <span class="temp-unit"></span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary role="button" class="secondary">OpenTherm diagnostic</summary>
|
||||
<pre><b>Vendor:</b> <span class="slave-vendor"></span>
|
||||
<b>Member ID:</b> <span class="slave-member-id"></span>
|
||||
<b>Flags:</b> <span class="slave-flags"></span>
|
||||
<b>Type:</b> <span class="slave-type"></span>
|
||||
<b>Version:</b> <span class="slave-version"></span>
|
||||
<b>OT version:</b> <span class="slave-ot-version"></span>
|
||||
<b>Heating limits:</b> <span class="heating-min-temp"></span>...<span class="heating-max-temp"></span> <span class="temp-unit"></span>
|
||||
<b>DHW limits:</b> <span class="dhw-min-temp"></span>...<span class="dhw-max-temp"></span> <span class="temp-unit"></span></pre>
|
||||
</details>
|
||||
</div>
|
||||
</article>
|
||||
</main>
|
||||
|
||||
<footer class="container">
|
||||
@@ -242,8 +133,42 @@
|
||||
<script>
|
||||
window.onload = async function () {
|
||||
setTimeout(async function onLoadPage() {
|
||||
await loadNetworkStatus();
|
||||
await loadVars();
|
||||
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);
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -29,300 +29,578 @@
|
||||
</hgroup>
|
||||
|
||||
<details>
|
||||
<summary role="button" class="secondary">Portal settings</summary>
|
||||
<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" class="portal-login" name="portal[login]" maxlength="12" required>
|
||||
<input type="text" id="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>
|
||||
<input type="password" id="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 for="portal-auth">
|
||||
<input type="checkbox" id="portal-auth" name="portal[auth]" value="true">
|
||||
Require authentication
|
||||
</label>
|
||||
<br>
|
||||
|
||||
<br />
|
||||
|
||||
<button type="submit">Save</button>
|
||||
</form>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<hr />
|
||||
|
||||
<details>
|
||||
<summary role="button" class="secondary">System settings</summary>
|
||||
<summary><b>System settings</b></summary>
|
||||
<div>
|
||||
<div id="system-settings-busy" aria-busy="true"></div>
|
||||
<form action="/api/settings" id="system-settings" class="hidden">
|
||||
<fieldset>
|
||||
<legend>Unit system</legend>
|
||||
|
||||
<label>
|
||||
<input type="radio" class="system-unit-system" name="system[unitSystem]" value="0" />
|
||||
Metric (celsius)
|
||||
<input type="radio" id="system-unit-system" name="system[unitSystem]" value="0" />
|
||||
Metric (celsius, liters, bar)
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<input type="radio" class="system-unit-system" name="system[unitSystem]" value="1" />
|
||||
Imperial (fahrenheit)
|
||||
<input type="radio" id="system-unit-system" name="system[unitSystem]" value="1" />
|
||||
Imperial (fahrenheit, gallons, psi)
|
||||
</label>
|
||||
</fieldset>
|
||||
|
||||
|
||||
<fieldset>
|
||||
<legend>Serial</legend>
|
||||
|
||||
<label for="system-serial-enable">
|
||||
<input type="checkbox" id="system-serial-enable" name="system[serial][enable]" value="true">
|
||||
Enable
|
||||
</label>
|
||||
|
||||
<label for="system-serial-baudrate">
|
||||
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>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<legend>Telnet</legend>
|
||||
|
||||
<label for="system-telnet-enable">
|
||||
<input type="checkbox" id="system-telnet-enable" name="system[telnet][enable]" value="true">
|
||||
Enable
|
||||
</label>
|
||||
|
||||
<label for="system-telnet-port">
|
||||
Port
|
||||
<input type="number" inputmode="numeric" id="system-telnet-port" name="system[telnet][port]" min="1" max="65535" step="1" required>
|
||||
</label>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<label for="system-debug">
|
||||
<input type="checkbox" class="system-debug" name="system[debug]" value="true">
|
||||
<input type="checkbox" id="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>
|
||||
</details>
|
||||
|
||||
|
||||
<hr />
|
||||
|
||||
<details>
|
||||
<summary role="button" class="secondary">OpenTherm settings</summary>
|
||||
<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>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<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>
|
||||
</fieldset>
|
||||
|
||||
<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>Regulators</legend>
|
||||
|
||||
<label for="emergency-use-equitherm">
|
||||
<input type="checkbox" id="emergency-use-equitherm" name="emergency[useEquitherm]" value="true">
|
||||
Use Equitherm
|
||||
</label>
|
||||
<label for="emergency-use-pid">
|
||||
<input type="checkbox" id="emergency-use-pid" name="emergency[usePid]" value="true">
|
||||
Use PID
|
||||
</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" class="opentherm-unit-system" name="opentherm[unitSystem]" value="0" />
|
||||
<input type="radio" id="opentherm-unit-system" name="opentherm[unitSystem]" value="0" />
|
||||
Metric (celsius)
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<input type="radio" class="opentherm-unit-system" name="opentherm[unitSystem]" value="1" />
|
||||
<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" class="opentherm-in-gpio" name="opentherm[inGpio]" min="0" max="254" step="1">
|
||||
<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" class="opentherm-out-gpio" name="opentherm[outGpio]" min="0" max="254" step="1">
|
||||
<input type="number" inputmode="numeric" id="opentherm-out-gpio" name="opentherm[outGpio]" min="0" max="254" step="1">
|
||||
</label>
|
||||
|
||||
<label 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>
|
||||
<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" class="opentherm-dhw-present" name="opentherm[dhwPresent]" value="true">
|
||||
<input type="checkbox" id="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">
|
||||
<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" class="opentherm-heating-ch2-enabled" name="opentherm[heatingCh2Enabled]" value="true">
|
||||
<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" class="opentherm-heating-ch1-to-ch2" name="opentherm[heatingCh1ToCh2]" value="true">
|
||||
<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" class="opentherm-dhw-to-ch2" name="opentherm[dhwToCh2]" value="true">
|
||||
<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" class="opentherm-dhw-blocking" name="opentherm[dhwBlocking]" value="true">
|
||||
<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" class="opentherm-sync-modulation-with-heating" name="opentherm[modulationSyncWithHeating]" value="true">
|
||||
<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" class="opentherm-get-min-max-temp" name="opentherm[getMinMaxTemp]" value="true">
|
||||
<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 role="button" class="secondary">MQTT settings</summary>
|
||||
<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" class="mqtt-server" name="mqtt[server]" maxlength="80" required>
|
||||
<input type="text" id="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>
|
||||
<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" class="mqtt-user" name="mqtt[user]" maxlength="32" required>
|
||||
<input type="text" id="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">
|
||||
<input type="password" id="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>
|
||||
<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" class="mqtt-interval" name="mqtt[interval]" min="3" max="60" step="1" required>
|
||||
<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 role="button" class="secondary">Outdoor sensor settings</summary>
|
||||
<summary><b>Outdoor sensor settings</b></summary>
|
||||
<div>
|
||||
<div id="outdoor-sensor-settings-busy" aria-busy="true"></div>
|
||||
<form action="/api/settings" id="outdoor-sensor-settings" class="hidden">
|
||||
<fieldset>
|
||||
<legend>Source type</legend>
|
||||
|
||||
<label>
|
||||
<input type="radio" class="outdoor-sensor-type" name="sensors[outdoor][type]" value="0" />
|
||||
<input type="radio" id="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" />
|
||||
<input type="radio" id="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" />
|
||||
<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" class="outdoor-sensor-gpio" name="sensors[outdoor][gpio]" min="0" max="254" step="1">
|
||||
<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" class="outdoor-sensor-offset" name="sensors[outdoor][offset]" min="-20" max="20" step="0.01" required>
|
||||
<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 role="button" class="secondary">Indoor sensor settings</summary>
|
||||
<summary><b>Indoor sensor settings</b></summary>
|
||||
<div>
|
||||
<div id="indoor-sensor-settings-busy" aria-busy="true"></div>
|
||||
<form action="/api/settings" id="indoor-sensor-settings" class="hidden">
|
||||
<fieldset>
|
||||
<legend>Source type</legend>
|
||||
|
||||
<label>
|
||||
<input type="radio" class="indoor-sensor-type" name="sensors[indoor][type]" value="1" />
|
||||
<input type="radio" id="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" />
|
||||
<input type="radio" id="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" />
|
||||
<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" class="indoor-sensor-gpio" name="sensors[indoor][gpio]" min="0" max="254" step="1">
|
||||
<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" class="indoor-sensor-offset" name="sensors[indoor][offset]" min="-20" max="20" step="0.01" required>
|
||||
<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" class="indoor-sensor-ble-addresss" name="sensors[indoor][bleAddresss]" pattern="([A-Fa-f0-9]{2}:){5}[A-Fa-f0-9]{2}">
|
||||
<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 role="button" class="secondary">External pump settings</summary>
|
||||
<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">
|
||||
<label for="extpump-use">
|
||||
<input type="checkbox" class="extpump-use" name="externalPump[use]" value="true">
|
||||
Use external pump
|
||||
</label>
|
||||
<br>
|
||||
|
||||
<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" class="extpump-gpio" name="externalPump[gpio]" min="0" max="254" step="1">
|
||||
<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" class="extpump-pc-time" name="externalPump[postCirculationTime]" min="1" max="120" step="1" required>
|
||||
<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" class="extpump-as-interval" name="externalPump[antiStuckInterval]" min="1" max="366" step="1" required>
|
||||
<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" class="extpump-as-time" name="externalPump[antiStuckTime]" min="1" max="20" step="1" required>
|
||||
<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>
|
||||
@@ -344,15 +622,152 @@
|
||||
<script src="/static/app.js"></script>
|
||||
<script>
|
||||
window.onload = async function () {
|
||||
await loadSettings();
|
||||
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);
|
||||
setBusy('#system-settings-busy', '#system-settings', false);
|
||||
|
||||
setupForm('#portal-settings');
|
||||
setupForm('#opentherm-settings');
|
||||
setupForm('#mqtt-settings');
|
||||
setupForm('#outdoor-sensor-settings');
|
||||
setupForm('#indoor-sensor-settings');
|
||||
setupForm('#extpump-settings');
|
||||
setupForm('#system-settings');
|
||||
// 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-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);
|
||||
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>
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -36,8 +36,8 @@
|
||||
Settings file:
|
||||
<input type="file" name="settings" id="restore-file" accept=".json">
|
||||
</label>
|
||||
|
||||
<div class="grid">
|
||||
|
||||
<div role="group">
|
||||
<button type="submit">Restore</button>
|
||||
<button type="button" class="secondary" onclick="window.location='/api/backup/save';">Backup</button>
|
||||
</div>
|
||||
@@ -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>
|
||||
|
||||
@@ -723,8 +723,8 @@ public:
|
||||
|
||||
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;
|
||||
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);
|
||||
|
||||
@@ -89,16 +89,31 @@ protected:
|
||||
Log.sinfoln(FPSTR(L_MAIN), F("Restart signal received. Restart after 10 sec."));
|
||||
}
|
||||
|
||||
if (network->isConnected()) {
|
||||
vars.sensors.rssi = WiFi.RSSI();
|
||||
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()) {
|
||||
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) {
|
||||
@@ -122,7 +137,7 @@ 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();
|
||||
}
|
||||
|
||||
@@ -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"));
|
||||
@@ -176,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 > EMERGENCY_TIME_TRESHOLD) {
|
||||
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) {
|
||||
|
||||
134
src/PortalTask.h
134
src/PortalTask.h
@@ -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,25 +432,13 @@ protected:
|
||||
this->webServer->on("/api/vars", HTTP_GET, [this]() {
|
||||
JsonDocument doc;
|
||||
varsToJson(vars, doc);
|
||||
|
||||
doc["system"]["unitSystem"] = static_cast<byte>(settings.system.unitSystem);
|
||||
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);
|
||||
}
|
||||
@@ -482,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);
|
||||
});
|
||||
|
||||
|
||||
@@ -560,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() {
|
||||
|
||||
@@ -25,13 +25,22 @@ 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;
|
||||
} system;
|
||||
|
||||
struct {
|
||||
bool useAuth = false;
|
||||
bool auth = false;
|
||||
char login[13] = DEFAULT_PORTAL_LOGIN;
|
||||
char password[33] = DEFAULT_PORTAL_PASSWORD;
|
||||
} portal;
|
||||
@@ -52,6 +61,7 @@ struct Settings {
|
||||
} opentherm;
|
||||
|
||||
struct {
|
||||
bool enable = false;
|
||||
char server[81] = DEFAULT_MQTT_SERVER;
|
||||
unsigned short port = DEFAULT_MQTT_PORT;
|
||||
char user[33] = DEFAULT_MQTT_USER;
|
||||
@@ -65,6 +75,8 @@ struct Settings {
|
||||
float target = 40.0f;
|
||||
bool useEquitherm = false;
|
||||
bool usePid = false;
|
||||
bool onNetworkFault = true;
|
||||
bool onMqttFault = true;
|
||||
} emergency;
|
||||
|
||||
struct {
|
||||
@@ -142,6 +154,7 @@ struct Variables {
|
||||
bool fault = false;
|
||||
bool diagnostic = false;
|
||||
bool externalPump = false;
|
||||
bool mqtt = false;
|
||||
} states;
|
||||
|
||||
struct {
|
||||
|
||||
@@ -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);
|
||||
|
||||
266
src/utils.h
266
src/utils.h
@@ -57,16 +57,8 @@ float convertTemp(float value, const UnitSystem unitFrom, const UnitSystem unitT
|
||||
return value;
|
||||
}
|
||||
|
||||
bool isValidTemp(const float value, UnitSystem unit, const float min = 0.1f, const float max = 99.9f) {
|
||||
if (unit == UnitSystem::METRIC) {
|
||||
return value >= min && value <= max;
|
||||
|
||||
} else if (unit == UnitSystem::IMPERIAL) {
|
||||
return value >= c2f(min) && value <= c2f(max);
|
||||
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
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) {
|
||||
@@ -335,11 +327,13 @@ 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["portal"]["useAuth"] = src.portal.useAuth;
|
||||
dst["portal"]["auth"] = src.portal.auth;
|
||||
dst["portal"]["login"] = src.portal.login;
|
||||
dst["portal"]["password"] = src.portal.password;
|
||||
|
||||
@@ -356,6 +350,7 @@ void settingsToJson(const Settings& src, JsonVariant dst, bool safe = false) {
|
||||
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;
|
||||
@@ -368,6 +363,8 @@ void settingsToJson(const Settings& src, JsonVariant dst, bool safe = false) {
|
||||
dst["emergency"]["target"] = roundd(src.emergency.target, 2);
|
||||
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;
|
||||
@@ -439,16 +436,34 @@ 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;
|
||||
@@ -484,8 +499,8 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false
|
||||
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
@@ -631,6 +646,11 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false
|
||||
|
||||
|
||||
// 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>();
|
||||
|
||||
@@ -693,15 +713,6 @@ 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 (isValidTemp(value, dst.system.unitSystem)) {
|
||||
dst.emergency.target = roundd(value, 2);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (src["emergency"]["useEquitherm"].is<bool>()) {
|
||||
if (dst.sensors.outdoor.type != SensorType::MANUAL) {
|
||||
dst.emergency.useEquitherm = src["emergency"]["useEquitherm"].as<bool>();
|
||||
@@ -732,93 +743,29 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false
|
||||
changed = true;
|
||||
}
|
||||
|
||||
|
||||
// heating
|
||||
if (src["heating"]["enable"].is<bool>()) {
|
||||
dst.heating.enable = src["heating"]["enable"].as<bool>();
|
||||
if (src["emergency"]["onNetworkFault"].is<bool>()) {
|
||||
dst.emergency.onNetworkFault = src["emergency"]["onNetworkFault"].as<bool>();
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (src["heating"]["turbo"].is<bool>()) {
|
||||
dst.heating.turbo = src["heating"]["turbo"].as<bool>();
|
||||
if (src["emergency"]["onMqttFault"].is<bool>()) {
|
||||
dst.emergency.onMqttFault = src["emergency"]["onMqttFault"].as<bool>();
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (!src["heating"]["target"].isNull()) {
|
||||
double value = src["heating"]["target"].as<double>();
|
||||
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 (isValidTemp(value, dst.system.unitSystem)) {
|
||||
dst.heating.target = roundd(value, 2);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!src["heating"]["hysteresis"].isNull()) {
|
||||
double value = src["heating"]["hysteresis"].as<double>();
|
||||
|
||||
if (value >= 0 && value <= 5) {
|
||||
dst.heating.hysteresis = roundd(value, 2);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!src["heating"]["minTemp"].isNull()) {
|
||||
unsigned char value = src["heating"]["minTemp"].as<unsigned char>();
|
||||
|
||||
if (value >= vars.parameters.heatingMinTemp && value <= vars.parameters.heatingMaxTemp) {
|
||||
dst.heating.minTemp = value;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!src["heating"]["maxTemp"].isNull()) {
|
||||
unsigned char value = src["heating"]["maxTemp"].as<unsigned char>();
|
||||
|
||||
if (value >= vars.parameters.heatingMinTemp && value <= vars.parameters.heatingMaxTemp) {
|
||||
dst.heating.maxTemp = value;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!src["heating"]["maxModulation"].isNull()) {
|
||||
unsigned char value = src["heating"]["maxModulation"].as<unsigned char>();
|
||||
|
||||
if (value > 0 && value <= 100) {
|
||||
dst.heating.maxModulation = value;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// dhw
|
||||
if (src["dhw"]["enable"].is<bool>()) {
|
||||
dst.dhw.enable = src["dhw"]["enable"].as<bool>();
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (!src["dhw"]["target"].isNull()) {
|
||||
unsigned char value = src["dhw"]["target"].as<unsigned char>();
|
||||
|
||||
if (isValidTemp(value, dst.system.unitSystem)) {
|
||||
dst.dhw.target = value;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!src["dhw"]["minTemp"].isNull()) {
|
||||
unsigned char value = src["dhw"]["minTemp"].as<unsigned char>();
|
||||
|
||||
if (value >= vars.parameters.dhwMinTemp && value <= vars.parameters.dhwMaxTemp) {
|
||||
dst.dhw.minTemp = value;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!src["dhw"]["maxTemp"].isNull()) {
|
||||
unsigned char value = src["dhw"]["maxTemp"].as<unsigned char>();
|
||||
|
||||
if (value >= vars.parameters.dhwMinTemp && value <= vars.parameters.dhwMaxTemp) {
|
||||
dst.dhw.maxTemp = value;
|
||||
if (valid) {
|
||||
dst.emergency.target = roundd(value, 2);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
@@ -919,6 +866,105 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false
|
||||
}
|
||||
|
||||
|
||||
// heating
|
||||
if (src["heating"]["enable"].is<bool>()) {
|
||||
dst.heating.enable = src["heating"]["enable"].as<bool>();
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (src["heating"]["turbo"].is<bool>()) {
|
||||
dst.heating.turbo = src["heating"]["turbo"].as<bool>();
|
||||
changed = true;
|
||||
}
|
||||
|
||||
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 (valid) {
|
||||
dst.heating.target = roundd(value, 2);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!src["heating"]["hysteresis"].isNull()) {
|
||||
double value = src["heating"]["hysteresis"].as<double>();
|
||||
|
||||
if (value >= 0 && value <= 5) {
|
||||
dst.heating.hysteresis = roundd(value, 2);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!src["heating"]["minTemp"].isNull()) {
|
||||
unsigned char value = src["heating"]["minTemp"].as<unsigned char>();
|
||||
|
||||
if (value >= vars.parameters.heatingMinTemp && value <= vars.parameters.heatingMaxTemp) {
|
||||
dst.heating.minTemp = value;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!src["heating"]["maxTemp"].isNull()) {
|
||||
unsigned char value = src["heating"]["maxTemp"].as<unsigned char>();
|
||||
|
||||
if (value >= vars.parameters.heatingMinTemp && value <= vars.parameters.heatingMaxTemp) {
|
||||
dst.heating.maxTemp = value;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!src["heating"]["maxModulation"].isNull()) {
|
||||
unsigned char value = src["heating"]["maxModulation"].as<unsigned char>();
|
||||
|
||||
if (value > 0 && value <= 100) {
|
||||
dst.heating.maxModulation = value;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// dhw
|
||||
if (src["dhw"]["enable"].is<bool>()) {
|
||||
dst.dhw.enable = src["dhw"]["enable"].as<bool>();
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (!src["dhw"]["target"].isNull()) {
|
||||
unsigned char value = src["dhw"]["target"].as<unsigned char>();
|
||||
|
||||
if (isValidTemp(value, dst.system.unitSystem, dst.dhw.minTemp, dst.dhw.maxTemp, dst.system.unitSystem)) {
|
||||
dst.dhw.target = value;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!src["dhw"]["minTemp"].isNull()) {
|
||||
unsigned char value = src["dhw"]["minTemp"].as<unsigned char>();
|
||||
|
||||
if (value >= vars.parameters.dhwMinTemp && value <= vars.parameters.dhwMaxTemp) {
|
||||
dst.dhw.minTemp = value;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!src["dhw"]["maxTemp"].isNull()) {
|
||||
unsigned char value = src["dhw"]["maxTemp"].as<unsigned char>();
|
||||
|
||||
if (value >= vars.parameters.dhwMinTemp && value <= vars.parameters.dhwMaxTemp) {
|
||||
dst.dhw.maxTemp = value;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// sensors
|
||||
if (!src["sensors"]["outdoor"]["type"].isNull()) {
|
||||
byte value = src["sensors"]["outdoor"]["type"].as<unsigned char>();
|
||||
@@ -974,7 +1020,6 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false
|
||||
if (!src["sensors"]["indoor"]["type"].isNull()) {
|
||||
byte value = src["sensors"]["indoor"]["type"].as<unsigned char>();
|
||||
|
||||
|
||||
switch (value) {
|
||||
case static_cast<byte>(SensorType::BOILER):
|
||||
dst.sensors.indoor.type = SensorType::BOILER;
|
||||
@@ -983,7 +1028,7 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false
|
||||
|
||||
case static_cast<byte>(SensorType::MANUAL):
|
||||
dst.sensors.indoor.type = SensorType::MANUAL;
|
||||
dst.emergency.useEquitherm = false;
|
||||
dst.emergency.usePid = false;
|
||||
changed = true;
|
||||
break;
|
||||
|
||||
@@ -1116,6 +1161,7 @@ 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);
|
||||
|
||||
@@ -41,13 +41,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
header,
|
||||
main,
|
||||
footer {
|
||||
header, main, footer {
|
||||
padding-top: 1rem !important;
|
||||
padding-bottom: 1rem !important;
|
||||
}
|
||||
@@ -60,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);
|
||||
@@ -106,14 +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";
|
||||
|
||||
details > div {
|
||||
padding: 0 var(--pico-form-element-spacing-horizontal);
|
||||
}
|
||||
|
||||
pre {
|
||||
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,199 +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);
|
||||
setRadioValue('.system-unit-system', result.system.unitSystem);
|
||||
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);
|
||||
|
||||
setRadioValue('.opentherm-unit-system', result.opentherm.unitSystem);
|
||||
setInputValue('.opentherm-in-gpio', result.opentherm.inGpio < 255 ? result.opentherm.inGpio : '');
|
||||
setInputValue('.opentherm-out-gpio', result.opentherm.outGpio < 255 ? result.opentherm.outGpio : '');
|
||||
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);
|
||||
setCheckboxValue('.opentherm-get-min-max-temp', result.opentherm.getMinMaxTemp);
|
||||
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-gpio', result.sensors.outdoor.gpio < 255 ? result.sensors.outdoor.gpio : '');
|
||||
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-gpio', result.sensors.indoor.gpio < 255 ? result.sensors.indoor.gpio : '');
|
||||
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-gpio', result.externalPump.gpio < 255 ? result.externalPump.gpio : '');
|
||||
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();
|
||||
|
||||
let tempUnitStr, pressureUnitStr, volumeUnitStr;
|
||||
switch (result.system.unitSystem) {
|
||||
case 0:
|
||||
tempUnitStr = 'C';
|
||||
pressureUnitStr = 'bar';
|
||||
volumeUnitStr = 'L';
|
||||
break;
|
||||
|
||||
case 1:
|
||||
tempUnitStr = 'F';
|
||||
pressureUnitStr = 'psi';
|
||||
volumeUnitStr = 'gal';
|
||||
break;
|
||||
|
||||
default:
|
||||
tempUnitStr = '?';
|
||||
pressureUnitStr = '?';
|
||||
volumeUnitStr = '?';
|
||||
break;
|
||||
}
|
||||
|
||||
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-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('.vars-busy', '.vars-table', false);
|
||||
|
||||
setValue('.temp-unit', tempUnitStr);
|
||||
setValue('.pressure-unit', pressureUnitStr);
|
||||
setValue('.volume-unit', volumeUnitStr);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -673,15 +497,75 @@ 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
|
||||
|
||||
Reference in New Issue
Block a user