first commit

This commit is contained in:
2022-12-07 14:22:06 +01:00
commit 2a16f8e5f1
25 changed files with 4890 additions and 0 deletions

135
res/static/index.css Normal file
View File

@@ -0,0 +1,135 @@
* { box-sizing: border-box; }
html, input { font-family: Arial, Helvetica, sans-serif; }
html, body { margin: 0; padding: 0;}
:root {
--color-theme: rgb(3.921%, 39.21%, 68.23%);
--min-size: calc(min(calc(100vw / 0.6), 100vh) / 3);
}
body {
padding: 16px;
}
.widget {
border: solid 1px var(--color-theme);
border-radius: 2px;
margin: 16px;
}
.widget > header {
background-color: var(--color-theme);
color: white;
padding: 10px;
}
.widget > main {
padding: 10px;
}
@media screen and (min-width: 800px) {
body {
display: flex;
flex-flow: row nowrap;
justify-content: center;
}
#left-widget-container {
width: 400px;
}
#right-widget-container {
flex-grow: 1;
}
#login-form .widget {
margin: 16px auto;
width: 400px;
}
}
@media screen and (max-width: 799.99px) {
.widget {
margin: 16px auto;
min-width: 300px;
max-width: 400px;
}
}
.raw-value {
font-family: monospace;
font-weight: bold;
font-size: 1.3em;
color: var(--color-theme);
}
.big-value {
text-align: right;
position: relative;
}
.big-value .name {
position: absolute;
top: 0.2em; left: 0;
}
.big-value .int-value {
font-size: 3.5em;
font-family: monospace;
font-weight: bold;
color: var(--color-theme);
}
.big-value .dec-value {
font-size: 1.6em;
font-family: monospace;
font-weight: bold;
color: var(--color-theme);
}
.big-value .unit {
position: absolute;
top: 0.2em; right: 0;
}
.big-value .meta {
position: absolute;
bottom: 1em; left: 0;
font-size: 0.8em;
}
.live-data-index:not(.index-current) {
opacity: 33%;
}
form input[type="text"],
form input[type="password"],
form input[type="email"],
form input[type="number"],
form input[type="date"],
form input[type="time"],
form textarea,
form select {
display: block;
margin: 5px auto;
border: solid 1px var(--color-theme);
border-radius: 2px;
padding: 5px;
font-size: 1.1em;
width: 100%;
}
form input[type="submit"] {
display: block;
margin: 5px auto;
border: solid 1px var(--color-theme);
border-radius: 2px;
padding: 5px;
font-size: 1.1em;
width: 100%;
}

157
res/static/index.html Normal file
View File

@@ -0,0 +1,157 @@
<!DOCTYPE html>
<html lang="fr" dir="ltr">
<head>
<meta charset="utf-8">
<title>Domotique</title>
<link rel="stylesheet" type="text/css" href="index.css" media="screen"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<div id="logged-in-content" style="display: none;">
<div id="left-widget-container">
<div class="widget">
<header>Puissances</header>
<main>
<div class="big-value">
<span class="name">Courante</span>
<span class="int-value" id="live-data-papp"></span><span style="visibility: hidden;">.<span class="dec-value">&nbsp;&nbsp;</span></span>
<span class="unit">VA</span>
</div>
<div class="big-value">
<span class="name">Coupure</span>
<span class="int-value" id="live-data-pcoup"></span><span style="visibility: hidden;">.<span class="dec-value">&nbsp;&nbsp;</span></span>
<span class="unit">VA</span>
</div>
<div class="big-value">
<span class="name">Référence (souscrite)</span>
<span class="int-value" id="live-data-pref"></span><span style="visibility: hidden;">.<span class="dec-value">&nbsp;&nbsp;</span></span>
<span class="unit">VA</span>
</div>
<div class="big-value">
<span class="name">Max aujourdhui</span>
<span class="meta" id="live-data-pj-time">--:--</span>
<span class="int-value" id="live-data-pj"></span><span style="visibility: hidden;">.<span class="dec-value">&nbsp;&nbsp;</span></span>
<span class="unit">VA</span>
</div>
<div class="big-value">
<span class="name">Max hier</span>
<span class="meta" id="live-data-pj-1-time">--:--</span>
<span class="int-value" id="live-data-pj-1"></span><span style="visibility: hidden;">.<span class="dec-value">&nbsp;&nbsp;</span></span>
<span class="unit">VA</span>
</div>
</main>
</div>
<div class="widget">
<header>Voltage</header>
<main>
<div class="big-value">
<span class="name">U<sub>eff</sub></span>
<span class="int-value" id="live-data-urms"></span><span style="visibility: hidden;">.<span class="dec-value">&nbsp;</span></span>
<span class="unit">V</span>
</div>
</main>
</div>
<div class="widget">
<header>Indexes cumulés</header>
<main id="live-data-indexes">
<div id="live-data-indexes-template" class="big-value live-data-index" style="display: none;">
<span class="name"></span>
<span class="int-value"></span>.<span class="dec-value">&nbsp;&nbsp;&nbsp;</span>
<span class="unit">kWh</span>
</div>
</main>
</div>
<div class="widget">
<header>Indexes jour</header>
<main id="live-data-indexes-day">
<div id="live-data-indexes-day-template" class="big-value" style="display: none;">
<span class="name"></span>
<span class="meta"></span>
<span class="int-value"></span>.<span class="dec-value"></span>
<span class="unit">kWh</span>
</div>
</main>
</div>
<div class="widget">
<header>Couts depuis minuit</header>
<main id="live-data-price-day">
<div id="live-data-price-day-template" class="big-value" style="display: none;">
<span class="name"></span>
<span class="meta"><span class="live-data-price-per-kwh"></span> € / kWh</span>
<span class="int-value"></span>.<span class="dec-value"></span>
<span class="unit"></span>
</div>
<div class="big-value">
<span class="name">Total</span>
<span class="int-value" id="live-data-price-total-int"></span>.<span class="dec-value" id="live-data-price-total-dec"></span>
<span class="unit"></span>
</div>
<div class="big-value">
<span class="name">Abonnement</span>
<span class="meta"><span id="live-data-price-sub-per-month"></span> € / mois</span>
<span class="int-value" id="live-data-price-sub-int"></span>.<span class="dec-value" id="live-data-price-sub-dec"></span>
<span class="unit"></span>
</div>
</main>
</div>
<div class="widget">
<header>Infos compteur</header>
<main>
Num série : <span class="raw-value" id="live-data-serial"></span><br>
PRM/<span title="Référence Point De Livraison">PDL</span> : <span class="raw-value" id="live-data-prm"></span><br>
Tarif : <span class="raw-value" id="live-data-OPTARIF"></span><br>
<span class="raw-value" id="live-data-status"></span>
</main>
</div>
<div class="widget">
<header>Air</header>
<main id="live-data-sensors">
<div id="live-data-sensors-template" class="big-value" style="display: none;">
<span class="name"></span>
<span class="meta"><span class="live-data-sensor-hum"></span> % Hum.</span>
<span class="int-value"></span>.<span class="dec-value"></span>
<span class="unit">°C</span>
</div>
</main>
</div>
<div class="widget">
<header>Connexion</header>
<main>
<form method="get" action="/_logout" target="login_frame">
<input type="submit" value="Se déconnecter"/>
</form>
</main>
</div>
</div>
<!-- <div id="right-widget-container">
<div class="widget">
<header>Widget 1</header>
<main>Du contenu</main>
</div>
<div class="widget">
<header>Widget 2</header>
<main>Encore<br>du<br>contenu</main>
</div>
<div class="widget">
<header>Widget 3</header>
<main>Encore<br>du contenu</main>
</div>
</div> -->
</div>
<div id="login-form" style="display: none;">
<div class="widget">
<header>Connexion</header>
<main>
<form method="post" action="/_login" onsubmit="setHTML('live-login-status', 'Connexion...');" target="login_frame">
<input type="text" name="username" placeholder="Pseudo"/>
<input type="password" name="password" placeholder="Mot de passe"/>
<input type="submit" value="Connexion"/>
<span class="raw-value" id="live-login-status"></span>
</form>
<iframe id="login_frame" name="login_frame" style="display: none;"></iframe>
</main>
</div>
</div>
</body>
</html>
<script src="index.js"></script>

233
res/static/index.js Normal file
View File

@@ -0,0 +1,233 @@
let subscriptionMonth = 10.60;
let indexPrices = [0.1423, 0, 0, 0, 0, 0, 0, 0, 0, 0];
let currentData = {};
function setHTML(id, html) {
document.getElementById(id).innerHTML = html;
}
function leftPadStr(value, pad, count) {
value = value + '';
while (value.length < count) {
value = pad + value;
}
return value;
}
function onLiveDataReceived(data) {
if (data.serialNumber != currentData.serialNumber) {
setHTML("live-data-serial", data.serialNumber);
}
if (data.prm != currentData.prm) {
setHTML("live-data-prm", data.prm);
}
if (data.subscribedOption != currentData.subscribedOption) {
setHTML("live-data-OPTARIF", data.subscribedOption);
}
if (data.refPower != currentData.refPower) {
setHTML("live-data-pref", data.refPower);
}
if (data.cutPower != currentData.cutPower) {
setHTML("live-data-pcoup", data.cutPower);
}
if (data.maxPowerToday != currentData.maxPowerToday) {
setHTML("live-data-pj", data.maxPowerToday);
var d = new Date(data.maxPowerTimeToday);
setHTML("live-data-pj-time", d.toLocaleTimeString());
}
if (data.maxPowerYesterday != currentData.maxPowerYesterday) {
setHTML("live-data-pj-1", data.maxPowerYesterday);
var d = new Date(data.maxPowerTimeYesterday);
setHTML("live-data-pj-1-time", d.toLocaleTimeString());
}
if (data.appPower != currentData.appPower) {
setHTML("live-data-papp", data.appPower);
}
if (data.rmsVoltage != currentData.rmsVoltage) {
setHTML("live-data-urms", data.rmsVoltage);
}
if (data.indexes != currentData.indexes) {
var parent = document.getElementById('live-data-indexes');
var template = document.getElementById('live-data-indexes-template');
for (var i = 0; i < data.indexes.length; i++) {
var value = data.indexes[i];
var name = data.indexNames[i] || ('Index ' + (i + 1));
if (value <= 0)
continue;
var el = document.getElementById('live-data-index-' + i);
if (el == null) {
el = template.cloneNode(true);
el.id = 'live-data-index-' + i;
el.style.display = "";
if (data.currIndex == i)
el.classList.add('index-current');
el = parent.appendChild(el);
var nameEl = el.querySelector('.name');
nameEl.innerHTML = name;
}
var kWhEl = el.querySelector('.int-value');
var whEl = el.querySelector('.dec-value');
kWhEl.innerHTML = Math.floor(value / 1000);
whEl.innerHTML = leftPadStr(value % 1000, '0', 3);
}
}
if (data.currIndex != currentData.currIndex) {
var oldEl = document.getElementById('live-data-index-' + currentData.currIndex);
if (oldEl != null) {
oldEl.classList.remove('index-current');
}
var newEl = document.getElementById('live-data-index-' + data.currIndex);
if (newEl != null) {
newEl.classList.add('index-current');
}
}
var dayPriceSum = 0.0;
if (data.indexesMidnight != currentData.indexesMidnight) {
var parentIndex = document.getElementById('live-data-indexes-day');
var templateIndex = document.getElementById('live-data-indexes-day-template');
var parentPrice = document.getElementById('live-data-price-day');
var templatePrice = document.getElementById('live-data-price-day-template');
for (var i = 0; i < data.indexesMidnight.length; i++) {
var value = data.indexes[i] - data.indexesMidnight[i];
var name = data.indexNames[i] || ('Index ' + (i + 1));
if (value <= 0)
continue;
var el = document.getElementById('live-data-index-day-' + i);
if (el == null) {
el = templateIndex.cloneNode(true);
el.id = 'live-data-index-day-' + i;
el.style.display = "";
el = parentIndex.appendChild(el);
var nameEl = el.querySelector('.name');
nameEl.innerHTML = name;
}
var kWhEl = el.querySelector('.int-value');
var whEl = el.querySelector('.dec-value');
kWhEl.innerHTML = Math.floor(value / 1000);
whEl.innerHTML = leftPadStr(value % 1000, '0', 3);
el = document.getElementById('live-data-price-day-' + i);
if (el == null) {
el = templatePrice.cloneNode(true);
el.id = 'live-data-price-day-' + i;
el.style.display = "";
el = parentPrice.appendChild(el);
var nameEl = el.querySelector('.name');
nameEl.innerHTML = name;
}
var price = value * (indexPrices[i] / 1000);
dayPriceSum += price;
var euroEl = el.querySelector('.int-value');
var centsEl = el.querySelector('.dec-value');
var perkWh = el.querySelector('.live-data-price-per-kwh');
euroEl.innerHTML = Math.floor(price);
centsEl.innerHTML = leftPadStr(Math.floor(price * 10000) % 10000, '0', 4);
perkWh.innerHTML = indexPrices[i];
}
}
var dayPrice = subscriptionMonth / data.nbDayThisMonth;
var currDayPrice = dayPrice * ((data.date - data.getDayStartTime) / 86400000);
dayPriceSum += currDayPrice;
setHTML('live-data-price-sub-int', Math.floor(currDayPrice));
setHTML('live-data-price-sub-dec', leftPadStr(Math.floor(currDayPrice * 10000) % 10000, '0', 4));
setHTML('live-data-price-total-int', Math.floor(dayPriceSum));
setHTML('live-data-price-total-dec', leftPadStr(Math.floor(dayPriceSum * 10000) % 10000, '0', 4));
setHTML('live-data-price-sub-per-month', subscriptionMonth);
if (data.sensorsData != currentData.sensorsData) {
var parent = document.getElementById('live-data-sensors');
var template = document.getElementById('live-data-sensors-template');
for (var name in data.sensorsData) {
var temp = data.sensorsData[name].temp;
var hum = data.sensorsData[name].hum;
var el = document.getElementById('live-data-sensors-' + name);
if (el == null) {
el = template.cloneNode(true);
el.id = 'live-data-sensors-' + name;
el.style.display = "";
el = parent.appendChild(el);
var nameEl = el.querySelector('.name');
nameEl.innerHTML = name;
}
var intEl = el.querySelector('.int-value');
var decEl = el.querySelector('.dec-value');
var humEl = el.querySelector('.live-data-sensor-hum');
intEl.innerHTML = Math.floor(temp);
decEl.innerHTML = leftPadStr(Math.floor(temp * 10) % 10, '0', 1);
humEl.innerHTML = Math.floor(hum);
}
}
currentData = data;
}
function loginSuccess() {
document.getElementById("logged-in-content").style.display = "";
document.getElementById("login-form").style.display = "none";
setHTML("live-login-status", "");
}
function loginFail(message) {
document.getElementById("logged-in-content").style.display = "none";
document.getElementById("login-form").style.display = "";
setHTML("live-login-status", message);
}
function updateLiveData(afterLogin = false) {
var xhr = new XMLHttpRequest();
xhr.onload = function() {
if (xhr.readyState == 4) {
if (xhr.status == 403) {
loginFail(afterLogin ? "Mauvais identifiants" : "");
return;
}
loginSuccess();
if (xhr.status == 200) {
setHTML("live-data-status", "");
var jsonObj = JSON.parse(xhr.responseText);
onLiveDataReceived(jsonObj.data);
var delay = jsonObj.avgUpdateInterval - (jsonObj.now - jsonObj.lastUpdate) + 100;
if (delay < 200) // dont spam if data source is too late than usual
delay = 200;
setTimeout(function() { updateLiveData(false); }, delay);
}
else {
setHTML("live-data-status", "Erreur de connexion (backend offline)");
setTimeout(function() { updateLiveData(false); }, 5000);
}
}
}
xhr.ontimeout = function() {
setHTML("live-data-status", "Erreur de connexion (timeout)");
setTimeout(function() { updateLiveData(false); }, 5000);
}
xhr.onerror = function() {
setHTML("live-data-status", "Erreur de connexion");
setTimeout(function() { updateLiveData(false); }, 5000);
}
xhr.timeout = 5000;
xhr.open("GET", "/rest/currentData", true);
xhr.send();
}
document.getElementById("login_frame").onload = function() {
updateLiveData(true);
}
updateLiveData(false);