mirror of
https://github.com/Laxilef/OTGateway.git
synced 2025-12-10 18:24:27 +05:00
499 lines
21 KiB
HTML
499 lines
21 KiB
HTML
<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
<title data-i18n>dashboard.title</title>
|
|
<link rel="stylesheet" href="/static/app.css?{BUILD_TIME}" />
|
|
</head>
|
|
|
|
<body>
|
|
<header class="container">
|
|
<nav>
|
|
<ul>
|
|
<li><a href="/">
|
|
<div class="logo" data-i18n>logo</div>
|
|
</a></li>
|
|
</ul>
|
|
<ul>
|
|
<!--<li><a href="https://github.com/Laxilef/OTGateway/wiki" role="button" class="secondary" target="_blank">Help</a></li>-->
|
|
<li>
|
|
<select id="lang" aria-label="Lang">
|
|
<option value="en" selected>EN</option>
|
|
<option value="ru">RU</option>
|
|
</select>
|
|
</li>
|
|
</ul>
|
|
</nav>
|
|
</header>
|
|
|
|
<main class="container">
|
|
<article>
|
|
<hgroup>
|
|
<h2 data-i18n>dashboard.name</h2>
|
|
<p></p>
|
|
</hgroup>
|
|
|
|
<div id="dashboard-busy" aria-busy="true"></div>
|
|
<div id="dashboard-container" class="hidden">
|
|
<details open>
|
|
<summary><b data-i18n>dashboard.section.control</b></summary>
|
|
<div class="grid">
|
|
<div class="thermostat" id="thermostat-heating">
|
|
<div class="thermostat-header" data-i18n>dashboard.thermostat.heating</div>
|
|
<div class="thermostat-temp">
|
|
<div class="thermostat-temp-target"><span id="tHeatTargetTemp"></span> <span class="tempUnit"></span></div>
|
|
<div class="thermostat-temp-current"><span data-i18n>dashboard.thermostat.temp.current</span>: <span id="tHeatCurrentTemp"></span> <span class="tempUnit"></span></div>
|
|
</div>
|
|
<div class="thermostat-minus"><button id="tHeatActionMinus" class="outline"><i class="icons-down"></i></button></div>
|
|
<div class="thermostat-plus"><button id="tHeatActionPlus" class="outline"><i class="icons-up"></i></button></div>
|
|
<div class="thermostat-control">
|
|
<input type="checkbox" role="switch" id="tHeatEnabled" value="true">
|
|
<label htmlFor="tHeatEnabled" data-i18n>dashboard.thermostat.enable</label>
|
|
|
|
<input type="checkbox" role="switch" id="tHeatTurbo" value="true">
|
|
<label htmlFor="tHeatTurbo" data-i18n>dashboard.thermostat.turbo</label>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="thermostat" id="thermostat-dhw">
|
|
<div class="thermostat-header" data-i18n>dashboard.thermostat.dhw</div>
|
|
<div class="thermostat-temp">
|
|
<div class="thermostat-temp-target"><span id="tDhwTargetTemp"></span> <span class="tempUnit"></span></div>
|
|
<div class="thermostat-temp-current"><span data-i18n>dashboard.thermostat.temp.current</span>: <span id="tDhwCurrentTemp"></span> <span class="tempUnit"></span></div>
|
|
</div>
|
|
<div class="thermostat-minus"><button class="outline" id="tDhwActionMinus"><i class="icons-down"></i></button></div>
|
|
<div class="thermostat-plus"><button class="outline" id="tDhwActionPlus"><i class="icons-up"></i></button></div>
|
|
<div class="thermostat-control">
|
|
<input type="checkbox" role="switch" id="tDhwEnabled" value="true">
|
|
<label htmlFor="tDhwEnabled" data-i18n>dashboard.thermostat.enable</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</details>
|
|
|
|
<hr />
|
|
|
|
<details>
|
|
<summary><b data-i18n>dashboard.section.states</b></summary>
|
|
<table>
|
|
<tbody>
|
|
<tr>
|
|
<th scope="row" data-i18n>dashboard.states.mNetworkConnected</th>
|
|
<td><input type="radio" class="mNetworkConnected" aria-invalid="false" checked disabled /></td>
|
|
</tr>
|
|
<tr>
|
|
<th scope="row" data-i18n>dashboard.states.mMqttConnected</th>
|
|
<td><input type="radio" class="mMqttConnected" aria-invalid="false" checked disabled /></td>
|
|
</tr>
|
|
<tr>
|
|
<th scope="row" data-i18n>dashboard.states.mEmergencyState</th>
|
|
<td><input type="radio" class="mEmergencyState" aria-invalid="false" checked disabled /></td>
|
|
</tr>
|
|
<tr>
|
|
<th scope="row" data-i18n>dashboard.states.mExtPumpState</th>
|
|
<td><input type="radio" class="mExtPumpState" aria-invalid="false" checked disabled /></td>
|
|
</tr>
|
|
<tr>
|
|
<th scope="row" data-i18n>dashboard.states.mCascadeControlInput</th>
|
|
<td><input type="radio" id="mCascadeControlInput" aria-invalid="false" checked disabled /></td>
|
|
</tr>
|
|
<tr>
|
|
<th scope="row" data-i18n>dashboard.states.mCascadeControlOutput</th>
|
|
<td><input type="radio" id="mCascadeControlOutput" aria-invalid="false" checked disabled /></td>
|
|
</tr>
|
|
|
|
|
|
<tr>
|
|
<th scope="row" data-i18n>dashboard.states.sConnected</th>
|
|
<td><input type="radio" class="sConnected" aria-invalid="false" checked disabled /></td>
|
|
</tr>
|
|
<tr>
|
|
<th scope="row" data-i18n>dashboard.states.sFlame</th>
|
|
<td><input type="radio" class="sFlame" aria-invalid="false" checked disabled /></td>
|
|
</tr>
|
|
|
|
|
|
<tr>
|
|
<th scope="row" data-i18n>dashboard.states.sFaultActive</th>
|
|
<td><input type="radio" class="sFaultActive" aria-invalid="false" checked disabled /></td>
|
|
</tr>
|
|
<tr>
|
|
<th scope="row" data-i18n>dashboard.states.sFaultCode</th>
|
|
<td><b class="sFaultCode"></b></td>
|
|
</tr>
|
|
<tr>
|
|
<th scope="row" data-i18n>dashboard.states.sDiagActive</th>
|
|
<td><input type="radio" class="sDiagActive" aria-invalid="false" checked disabled /></td>
|
|
</tr>
|
|
<tr>
|
|
<th scope="row" data-i18n>dashboard.states.sDiagCode</th>
|
|
<td><b class="sDiagCode"></b></td>
|
|
</tr>
|
|
|
|
|
|
<tr>
|
|
<th scope="row" data-i18n>dashboard.states.mHeatEnabled</th>
|
|
<td><input type="radio" class="mHeatEnabled" aria-invalid="false" checked disabled /></td>
|
|
</tr>
|
|
<tr>
|
|
<th scope="row" data-i18n>dashboard.states.mHeatBlocking</th>
|
|
<td><input type="radio" class="mHeatBlocking" aria-invalid="false" checked disabled /></td>
|
|
</tr>
|
|
<tr>
|
|
<th scope="row" data-i18n>dashboard.states.sHeatActive</th>
|
|
<td><input type="radio" class="sHeatActive" aria-invalid="false" checked disabled /></td>
|
|
</tr>
|
|
<tr>
|
|
<th scope="row" data-i18n>dashboard.states.mHeatTargetTemp</th>
|
|
<td><b class="mHeatTargetTemp"></b> <span class="tempUnit"></span></td>
|
|
</tr>
|
|
<tr>
|
|
<th scope="row" data-i18n>dashboard.states.mHeatSetpointTemp</th>
|
|
<td><b class="mHeatSetpointTemp"></b> <span class="tempUnit"></span></td>
|
|
</tr>
|
|
<tr>
|
|
<th scope="row" data-i18n>dashboard.states.mHeatCurrTemp</th>
|
|
<td><b class="mHeatCurrTemp"></b> <span class="tempUnit"></span></td>
|
|
</tr>
|
|
<tr>
|
|
<th scope="row" data-i18n>dashboard.states.mHeatRetTemp</th>
|
|
<td><b class="mHeatRetTemp"></b> <span class="tempUnit"></span></td>
|
|
</tr>
|
|
<tr>
|
|
<th scope="row" data-i18n>dashboard.states.mHeatIndoorTemp</th>
|
|
<td><b class="mHeatIndoorTemp"></b> <span class="tempUnit"></span></td>
|
|
</tr>
|
|
<tr>
|
|
<th scope="row" data-i18n>dashboard.states.mHeatOutdoorTemp</th>
|
|
<td><b class="mHeatOutdoorTemp"></b> <span class="tempUnit"></span></td>
|
|
</tr>
|
|
|
|
|
|
<tr>
|
|
<th scope="row" data-i18n>dashboard.states.mDhwEnabled</th>
|
|
<td><input type="radio" class="mDhwEnabled" aria-invalid="false" checked disabled /></td>
|
|
</tr>
|
|
<tr>
|
|
<th scope="row" data-i18n>dashboard.states.sDhwActive</th>
|
|
<td><input type="radio" class="sDhwActive" aria-invalid="false" checked disabled /></td>
|
|
</tr>
|
|
<tr>
|
|
<th scope="row" data-i18n>dashboard.states.mDhwTargetTemp</th>
|
|
<td><b class="mDhwTargetTemp"></b> <span class="tempUnit"></span></td>
|
|
</tr>
|
|
<tr>
|
|
<th scope="row" data-i18n>dashboard.states.mDhwCurrTemp</th>
|
|
<td><b class="mDhwCurrTemp"></b> <span class="tempUnit"></span></td>
|
|
</tr>
|
|
<tr>
|
|
<th scope="row" data-i18n>dashboard.states.mDhwRetTemp</th>
|
|
<td><b class="mDhwRetTemp"></b> <span class="tempUnit"></span></td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</details>
|
|
|
|
<hr />
|
|
|
|
<details>
|
|
<summary><b data-i18n>dashboard.section.diag</b></summary>
|
|
<pre><b>Vendor:</b> <span class="sVendor"></span>
|
|
<b>Member ID:</b> <span class="sMemberId"></span>
|
|
<b>Flags:</b> <span class="sFlags"></span>
|
|
<b>Type:</b> <span class="sType"></span>
|
|
<b>AppVersion:</b> <span class="sAppVersion"></span>
|
|
<b>OT version:</b> <span class="sProtocolVersion"></span>
|
|
<b>Modulation limits:</b> <span class="sModMin"></span>...<span class="sModMax"></span> %
|
|
<b>Power limits:</b> <span class="sPowerMin"></span>...<span class="sPowerMax"></span> kW
|
|
<b>Heating limits:</b> <span class="sHeatMinTemp"></span>...<span class="sHeatMaxTemp"></span> <span class="tempUnit"></span>
|
|
<b>DHW limits:</b> <span class="sDhwMinTemp"></span>...<span class="sDhwMaxTemp"></span> <span class="tempUnit"></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" data-i18n>nav.license</a>
|
|
• <a href="https://github.com/Laxilef/OTGateway/blob/master/" target="_blank" class="secondary" data-i18n>nav.source</a>
|
|
• <a href="https://github.com/Laxilef/OTGateway/wiki" target="_blank" class="secondary" data-i18n>nav.help</a>
|
|
• <a href="https://github.com/Laxilef/OTGateway/issues" target="_blank" class="secondary" data-i18n>nav.issues</a>
|
|
• <a href="https://github.com/Laxilef/OTGateway/releases" target="_blank" class="secondary" data-i18n>nav.releases</a>
|
|
</small>
|
|
</footer>
|
|
|
|
<script src="/static/app.js?{BUILD_TIME}"></script>
|
|
<script>
|
|
let modifiedTime = null;
|
|
let noRegulators;
|
|
let prevSettings;
|
|
let newSettings = {
|
|
heating: {
|
|
enable: false,
|
|
turbo: false,
|
|
target: 0
|
|
},
|
|
dhw: {
|
|
enable: false,
|
|
target: 0
|
|
}
|
|
};
|
|
|
|
document.addEventListener('DOMContentLoaded', async () => {
|
|
const lang = new Lang(document.getElementById('lang'));
|
|
lang.build();
|
|
|
|
document.querySelector('#tHeatActionMinus').addEventListener('click', (event) => {
|
|
if (!prevSettings) {
|
|
return;
|
|
}
|
|
|
|
newSettings.heating.target -= 0.5;
|
|
modifiedTime = Date.now();
|
|
|
|
let minTemp;
|
|
if (noRegulators) {
|
|
minTemp = prevSettings.heating.minTemp;
|
|
} else {
|
|
minTemp = prevSettings.system.unitSystem == 0 ? 5 : 41;
|
|
}
|
|
|
|
if (prevSettings && newSettings.heating.target < minTemp) {
|
|
newSettings.heating.target = minTemp;
|
|
}
|
|
|
|
setValue('#tHeatTargetTemp', newSettings.heating.target);
|
|
});
|
|
|
|
document.querySelector('#tHeatActionPlus').addEventListener('click', (event) => {
|
|
if (!prevSettings) {
|
|
return;
|
|
}
|
|
|
|
newSettings.heating.target += 0.5;
|
|
modifiedTime = Date.now();
|
|
|
|
let maxTemp;
|
|
if (noRegulators) {
|
|
maxTemp = prevSettings.heating.maxTemp;
|
|
} else {
|
|
maxTemp = prevSettings.system.unitSystem == 0 ? 30 : 86;
|
|
}
|
|
|
|
if (prevSettings && newSettings.heating.target > maxTemp) {
|
|
newSettings.heating.target = maxTemp;
|
|
}
|
|
|
|
setValue('#tHeatTargetTemp', newSettings.heating.target);
|
|
});
|
|
|
|
document.querySelector('#tDhwActionMinus').addEventListener('click', (event) => {
|
|
if (!prevSettings) {
|
|
return;
|
|
}
|
|
|
|
newSettings.dhw.target -= 1.0;
|
|
modifiedTime = Date.now();
|
|
|
|
if (newSettings.dhw.target < prevSettings.dhw.minTemp) {
|
|
newSettings.dhw.target = prevSettings.dhw.minTemp;
|
|
}
|
|
|
|
setValue('#tDhwTargetTemp', newSettings.dhw.target);
|
|
});
|
|
|
|
document.querySelector('#tDhwActionPlus').addEventListener('click', (event) => {
|
|
if (!prevSettings) {
|
|
return;
|
|
}
|
|
|
|
newSettings.dhw.target += 1.0;
|
|
modifiedTime = Date.now();
|
|
|
|
if (newSettings.dhw.target > prevSettings.dhw.maxTemp) {
|
|
newSettings.dhw.target = prevSettings.dhw.maxTemp;
|
|
}
|
|
|
|
setValue('#tDhwTargetTemp', newSettings.dhw.target);
|
|
});
|
|
|
|
document.querySelector('#tHeatEnabled').addEventListener('change', (event) => {
|
|
modifiedTime = Date.now();
|
|
newSettings.heating.enabled = event.currentTarget.checked;
|
|
});
|
|
|
|
document.querySelector('#tHeatTurbo').addEventListener('change', (event) => {
|
|
modifiedTime = Date.now();
|
|
newSettings.heating.turbo = event.currentTarget.checked;
|
|
});
|
|
|
|
document.querySelector('#tDhwEnabled').addEventListener('change', (event) => {
|
|
modifiedTime = Date.now();
|
|
newSettings.dhw.enabled = 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.enabled != newSettings.heating.enabled)
|
|
|| (prevSettings.heating.turbo != newSettings.heating.turbo)
|
|
|| (prevSettings.heating.target != newSettings.heating.target)
|
|
|| (prevSettings.opentherm.dhwPresent && prevSettings.dhw.enabled != newSettings.dhw.enabled)
|
|
|| (prevSettings.opentherm.dhwPresent && prevSettings.dhw.target != newSettings.dhw.target)
|
|
);
|
|
|
|
if (modified) {
|
|
console.log(newSettings);
|
|
}
|
|
|
|
let parameters = { cache: 'no-cache' };
|
|
if (modified) {
|
|
parameters.method = "POST";
|
|
parameters.body = JSON.stringify(newSettings);
|
|
}
|
|
|
|
const response = await fetch('/api/settings', parameters);
|
|
if (!response.ok) {
|
|
throw new Error('Response not valid');
|
|
}
|
|
|
|
const result = await response.json();
|
|
noRegulators = !result.opentherm.nativeHeatingControl && !result.equitherm.enabled && !result.pid.enabled;
|
|
prevSettings = result;
|
|
newSettings.heating.enabled = result.heating.enabled;
|
|
newSettings.heating.turbo = result.heating.turbo;
|
|
newSettings.heating.target = result.heating.target;
|
|
newSettings.dhw.enabled = result.dhw.enabled;
|
|
newSettings.dhw.target = result.dhw.target;
|
|
|
|
if (result.opentherm.dhwPresent) {
|
|
show('#thermostat-dhw');
|
|
} else {
|
|
hide('#thermostat-dhw');
|
|
}
|
|
|
|
setCheckboxValue('#tHeatEnabled', result.heating.enabled);
|
|
setCheckboxValue('#tHeatTurbo', result.heating.turbo);
|
|
setValue('#tHeatTargetTemp', result.heating.target);
|
|
|
|
setCheckboxValue('#tDhwEnabled', result.dhw.enabled);
|
|
setValue('#tDhwTargetTemp', result.dhw.target);
|
|
|
|
setValue('.tempUnit', temperatureUnit(result.system.unitSystem));
|
|
setValue('.pressureUnit', pressureUnit(result.system.unitSystem));
|
|
setValue('.volumeUnit', 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();
|
|
|
|
// Graph
|
|
setValue('#tHeatCurrentTemp', result.master.heating.indoorTempControl
|
|
? result.master.heating.indoorTemp
|
|
: result.master.heating.currentTemp
|
|
);
|
|
setValue('#tDhwCurrentTemp', result.master.dhw.currentTemp);
|
|
|
|
|
|
// SLAVE
|
|
setValue('.sMemberId', result.slave.memberId);
|
|
setValue('.sVendor', memberIdToVendor(result.slave.memberId));
|
|
setValue('.sFlags', result.slave.flags);
|
|
setValue('.sType', result.slave.type);
|
|
setValue('.sAppVersion', result.slave.appVersion);
|
|
setValue('.sProtocolVersion', result.slave.protocolVersion);
|
|
|
|
setState('.sConnected', result.slave.connected);
|
|
setState('.sFlame', result.slave.flame);
|
|
|
|
setValue('.sModMin', result.slave.modulation.min);
|
|
setValue('.sModMax', result.slave.modulation.max);
|
|
|
|
setValue('.sPowerMin', result.slave.power.min);
|
|
setValue('.sPowerMax', result.slave.power.max);
|
|
|
|
setState('.sHeatActive', result.slave.heating.active);
|
|
setValue('.sHeatMinTemp', result.slave.heating.minTemp);
|
|
setValue('.sHeatMaxTemp', result.slave.heating.maxTemp);
|
|
|
|
setState('.sDhwActive', result.slave.dhw.active);
|
|
setValue('.sDhwMinTemp', result.slave.dhw.minTemp);
|
|
setValue('.sDhwMaxTemp', result.slave.dhw.maxTemp);
|
|
|
|
setState('.sFaultActive', result.slave.fault.active);
|
|
setValue(
|
|
'.sFaultCode',
|
|
result.slave.fault.active
|
|
? (result.slave.fault.code + " (0x" + dec2hex(result.slave.fault.code) + ")")
|
|
: "-"
|
|
);
|
|
|
|
setState('.sDiagActive', result.slave.diag.active);
|
|
setValue(
|
|
'.sDiagCode',
|
|
result.slave.diag.active
|
|
? (result.slave.diag.code + " (0x" + dec2hex(result.slave.diag.code) + ")")
|
|
: "-"
|
|
);
|
|
|
|
|
|
// MASTER
|
|
setState('.mHeatEnabled', result.master.heating.enabled);
|
|
setState('.mHeatBlocking', result.master.heating.blocking);
|
|
setState('.mHeatIndoorTempControl', result.master.heating.indoorTempControl);
|
|
setValue('.mHeatSetpointTemp', result.master.heating.setpointTemp);
|
|
setValue('.mHeatTargetTemp', result.master.heating.targetTemp);
|
|
setValue('.mHeatCurrTemp', result.master.heating.currentTemp);
|
|
setValue('.mHeatRetTemp', result.master.heating.returnTemp);
|
|
setValue('.mHeatIndoorTemp', result.master.heating.indoorTemp);
|
|
setValue('.mHeatOutdoorTemp', result.master.heating.outdoorTemp);
|
|
setValue('.mHeatMinTemp', result.master.heating.minTemp);
|
|
setValue('.mHeatMaxTemp', result.master.heating.maxTemp);
|
|
|
|
setState('.mDhwEnabled', result.master.dhw.enabled);
|
|
setValue('.mDhwTargetTemp', result.master.dhw.targetTemp);
|
|
setValue('.mDhwCurrTemp', result.master.dhw.currentTemp);
|
|
setValue('.mDhwRetTemp', result.master.dhw.returnTemp);
|
|
setValue('.mDhwMinTemp', result.master.dhw.minTemp);
|
|
setValue('.mDhwMaxTemp', result.master.dhw.maxTemp);
|
|
|
|
setState('.mNetworkConnected', result.master.network.connected);
|
|
setState('.mMqttConnected', result.master.mqtt.connected);
|
|
setState('.mEmergencyState', result.master.emergency.state);
|
|
setState('.mExtPumpState', result.master.externalPump.state);
|
|
setState('.mCascadeControlInput', result.master.cascadeControl.input);
|
|
setState('.mCascadeControlOutput', result.master.cascadeControl.output);
|
|
|
|
setBusy('#dashboard-busy', '#dashboard-container', false);
|
|
|
|
} catch (error) {
|
|
console.log(error);
|
|
}
|
|
|
|
setTimeout(onLoadPage, 10000);
|
|
}, 1000);
|
|
});
|
|
</script>
|
|
</body>
|
|
</html> |