TKK_E32230469/firmware_iot_esp12f/data/index.html

829 lines
27 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>UPS Dashboard</title>
<link rel="icon" href="favicon.ico">
<style>
body {
font-family: Arial, sans-serif;
padding: 20px;
margin-top: -20px;
background-color: #121212; /* Dark background */
color: #e0e0e0; /* Light text color */
}
hr {
margin-bottom: 20px;
border-color: #333; /* Darker border color */
}
h1 {
font-size: 20px;
font-weight: bold;
margin-bottom: 10px;
color: #ffffff; /* White text for headings */
}
.form-group {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 15px;
}
button {
background-color: #333; /* Dark button background */
color: #e0e0e0; /* Light text color */
border: none;
padding: 5px 10px;
cursor: pointer;
border-radius: 4px;
}
button:hover {
background-color: #444; /* Slightly lighter on hover */
}
button.disabled {
background-color: #0046a3; /* Gray color for disabled button */
color: #a0a0a0;
cursor: default;
pointer-events: none; /* Prevents hover effect */
transition: none; /* Removes transition effect on hover */
}
button[disabled] {
background-color: #333; /* Gray color for disabled button */
color: #555;
cursor: default;
pointer-events: none; /* Prevents hover effect */
transition: none; /* Removes transition effect on hover */
}
button[disabled]:hover {
background-color: #333;
}
label {
color: #e0e0e0; /* Light text color for labels */
}
/* for styled on off using checkbox */
.toggle-switch {
position: relative;
display: inline-block;
width: 48px;
height: 24px;
}
.toggle-switch input {
opacity: 0;
width: 0;
height: 0;
}
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #ccc;
transition: .2s;
border-radius: 24px;
}
.slider:before {
position: absolute;
content: "";
height: 17px;
width: 17px;
left: 4px;
bottom: 4px;
background-color: white;
transition: .2s;
border-radius: 50%;
}
input:checked + .slider {
background-color: #0078d7;
}
input:checked + .slider:before {
transform: translateX(22px);
}
/* Disabled state */
input:disabled + .slider {
background-color: #e6e6e6;
cursor: default;
pointer-events: none; /* Prevents hover effect */
}
input:disabled:checked + .slider {
background-color: #a0d1f6;
}
input:disabled + .slider:before {
background-color: #f5f5f5;
}
/* Optional: Add focus style */
input:focus + .slider {
box-shadow: 0 0 1px #0078d7;
}
/* Selec/dropdown style */
select {
/* width: calc(100% - 22px); */
background-color: #333333;
border: none;
color: #ffffff;
font-size: 16px;
border-radius: 5px;
box-sizing: border-box;
margin-right: 5px;
}
/* Login Overlay Styles */
#login-overlay {
position: fixed;
top: 0; left: 0; right: 0; bottom: 0;
background-color: rgba(18, 18, 18, 0.95);
z-index: 9999;
display: flex;
align-items: center;
justify-content: center;
}
.login-box {
background-color: #242424;
padding: 30px;
border-radius: 8px;
box-shadow: 0 4px 15px rgba(0,0,0,0.5);
text-align: center;
width: 300px;
}
.login-box input {
padding: 10px;
margin-top: 10px;
margin-bottom: 20px;
width: 80%;
border: 1px solid #444;
background-color: #121212;
color: #fff;
border-radius: 4px;
}
.login-box h2 {
margin-top: 0;
color: #e0e0e0;
}
</style>
</head>
<body>
<!-- Login Overlay -->
<div id="login-overlay">
<div class="login-box" onkeydown="if(event.key==='Enter')attemptLogin()">
<h2>UPS Authentication</h2>
<input type="text" id="login-username" placeholder="Username (admin)" value="admin">
<input type="password" id="login-password" placeholder="Password">
<br>
<button onclick="attemptLogin()" style="width: 80%; padding: 10px;">Login</button>
<p id="login-error" style="color: #ff5555; display: none; margin-top: 15px; font-size: 14px;">Invalid username or password</p>
</div>
</div>
<h1>
<label>Smart Network Controlled UPS V1.1</label>
<!-- ⭕Hollow Red Circle⚫Black Circle⚪White Circle · 🟢Green Circle🔵Blue Circle🟡Yellow Circle🟠Orange Circle🟤Brown Circle 🔴Red Circle 🟣 Purple Circle -->
<label id="connect-state-label" title="Unavailable"></label>
<button id="logoutBtn" onclick="logout()" style="position: absolute; right: 20px; top: 20px; padding: 4px 12px; font-size: 12px;">Logout</button>
</h1>
<hr>
<h1>Power Control:</h1>
<div class="form-group">
<label>Auto ON</label>
<!-- <button id="toggleAutoBtn"
onclick="toggleAutoOn()"
style="position: absolute;
left: 140px;width:50px;">OFF</button> -->
<select id="auto-mode" style="position: absolute; left: 150px;width: 150px;">
<option value="0">Disabled</option>
<option value="1">Line Powered</option>
<option value="2">Persistent on Line</option>
</select>
</div>
<!-- <div class="form-group">
<label>State</label>
<button id="switchBtn"
onclick="toggleSwitch()"
style="position: absolute;
left: 140px;width:50px;">OFF</button>
</div> -->
<div class="form-group">
<label>Power</label>
<label class="toggle-switch" style="position: absolute; left: 150px;">
<input type="checkbox" id="switchToggle" disabled >
<span class="slider"></span>
</label>
</div>
<div class="form-group">
<label>Shutdown Cast</label>
<select id="shutcast-mode" style="position: absolute; left: 150px;width: 150px;">
<option value="0">Disabled</option>
<option value="1">On Battery</option>
<option value="2">≤75% Battery</option>
<option value="3">≤50% Battery</option>
<option value="4">≤25% Battery</option>
<option value="5">Battery Critical</option>
</select>
<label id="shutdown-cast-label" style="position: absolute; left: 310px;width: 50px;"> </label>
</div>
<div class="form-group">
<div class="form-group">
<div class="container">
<label style="position: absolute; align-items: center;" title="RestAPI: htttp://<IP>/state?shutdown=<seconds>" >Shutdown</label>
<label id="shutdown-countdown" style="position: absolute; left: 150px; width: 150px;">999</label>
</div>
<div class="container" id="shutdown-form" style="position: absolute; left: 150px; align-items: center;">
<input type="number" id="shut-timer-input" placeholder="0-999" value="120" min="10" max="999" step="1" style="position: absolute; left: 0px; width: 50px; align-items: center;" maxlength="3">
<label style="position: absolute; left: 65px; width: 50px; align-items: center;">sec(s)</label>
<button id="shutdownBtn"
onclick="shutdownTriggered()"
style="position: absolute;
left: 120px;width:64px; align-items:center; padding: 0px 0px; height: 22px; margin: -1px -0px">START</button>
</div>
<!-- <div class="container" id="shutdown-countdown" style="position: absolute; left: 150px; align-items: center;">
<label style="position: absolute; left: 0px; width: 50px;"></label>
</div> -->
</div>
</div>
<hr>
<h1>Charge Control:</h1>
<div class="form-group">
<label>Charge Mode</label>
<select id="charge-mode" style="position: absolute; left: 150px;width: 150px;">
<option value="0">Always Charge</option>
<option value="1">90-100%</option>
<option value="2">85-100%</option>
<option value="3">80-100%</option>
<option value="4">90-95%</option>
<option value="5">85-95%</option>
<option value="6">80-95%</option>
<option value="7">85-90%</option>
<option value="8">80-90%</option>
<option value="9">80-85%</option>
</select>
</div>
<div class="form-group">
<label title="Automatic Voltage Reducer">AVR</label>
<label class="toggle-switch" style="position: absolute; left: 150px;">
<input type="checkbox" id="switchToggleAutoReducer" disabled >
<span class="slider"></span>
</label>
</div>
<div class="form-group">
<label title="Overvoltage Protection">OVP</label>
<label class="toggle-switch" style="position: absolute; left: 150px;">
<input type="checkbox" id="switchToggleOVP" disabled >
<span class="slider"></span>
</label>
</div>
<div class="form-group">
<label title="Full Charge on Power Lost">FCoPL</label>
<select id="fcopl-mode" style="position: absolute; left: 150px;width: 150px;">
<option value="0">Disabled</option>
<option value="1">Always</option>
<option value="2">At Battery ≤95%</option>
<option value="3">At Battery ≤90%</option>
<option value="4">At Battery ≤85%</option>
<option value="5">At Battery ≤80%</option>
</select>
<button id="resetFCoPLBtn"
onclick="resetFCoPLTriggered()"
style="position: absolute;
left: 310px;width:64px; align-items:center; padding: 0px 0px; height: 22px; margin: -1px -0px">Reset</button>
</div>
<hr>
<h1>Monitor:</h1>
<div class="form-group">
<label>Status</label>
<label id="status-label"
style="position: absolute;
left: 150px;width:400px;"
>-</label>
</div>
<div class="form-group">
<label>Power Source</label>
<label id="power-label"
style="position: absolute;
left: 150px;width:400px;"
>-</label>
</div>
<div class="form-group">
<label>Battery Voltage</label>
<label id="input-label"
style="position: absolute;
left: 150px;width:100px;"
>-</label>
</div>
<div class="form-group">
<label>Battery Level</label>
<label id="batt-label"
style="position: absolute;
left: 150px;width:400px;"
>⬛⬛⬛⬛</label>
</div>
<script>
// ⬛ 🟥 (red square), 🟦 (blue square), 🟩 (green square), 🟨 (yellow square), 🟧 (orange square), and 🟫 (brown square)
// ◼️ Black Medium Square: A medium-sized black square.
// ⬛ Black Large Square: A large black square.
// ◾ Black Medium-Small Square: A smaller black square.
// ▪️ Black Small Square: The smallest size of a black square.
// 🔲 Black Square Button: A square version of the radio button emoji
var labelStatus = document.getElementById('status-label');
var labelPower = document.getElementById('power-label');
var labelInput = document.getElementById('input-label');
var labelBatt = document.getElementById('batt-label');
var dropdownAutoOnSelect = document.getElementById('auto-mode');
var toggleSwitch = document.getElementById('switchToggle');
var shutTimerInput = document.getElementById('shut-timer-input');
var shutdownForm = document.getElementById('shutdown-form');
var shutTimer = document.getElementById('shutdown-countdown');
var dropdownShutLevelSelect = document.getElementById('shutcast-mode');
var shutFlagLabel = document.getElementById('shutdown-cast-label');
var connectStateLabel = document.getElementById('connect-state-label');
var dropdownChargeModeSelect = document.getElementById('charge-mode');
var dropdownFullChargeOnPowerLostModeSelect = document.getElementById('fcopl-mode');
var btnResetFCOPL = document.getElementById('resetFCoPLBtn');
var toggleAutomaticVoltageReducer = document.getElementById('switchToggleAutoReducer');
var toggleOvervoltageProtection = document.getElementById('switchToggleOVP');
var lastConnectTick = 0;
var autoOnMode = 0;
var stateTarget = false;
var stateValue = false;
var lineFrom = 0; //0 = None, 1 = AC, 2 = Battery
var onChange = false;
var checkRefresh;
var battLevel = 0;
var battMaxLevel = 0;
var battPercentage = 0;
var isChargingOrBlink = false;
var chargeLevel = 0;
var isBattCritical = false;
var chargeTickTock = false;
var timedShutdown = false;
var timedShutRemaining = 0;
var everReceiveStat = false;
var shutdownSuggestionLevel = 0;
//var shutdownSuggestionLevelPrev = 0;
var shutdownCastFlag = false;
var chargeStatus = 0;
var chargingState = 0;
var chargeMode = 0;
var chargeAutoVoltageReducer = false;
var chargeOvervoltageProtection = false;
var fullChargeOnPowerLostMode = 0;
var fullChargeOnPowerLostState = false;
var targetChargeMode = 0;
var targetFCOPLMode = 0;
var targetOvervoltageProtection = false;
var targetAutomaticVoltageReducer = false;
var authHeader = localStorage.getItem('upsAuth') || "";
function attemptLogin() {
var usr = document.getElementById('login-username').value;
var pwd = document.getElementById('login-password').value;
authHeader = "Basic " + btoa(usr + ":" + pwd);
localStorage.setItem('upsAuth', authHeader);
document.getElementById('login-error').style.display = 'none';
SendState("", 0);
}
function logout() {
authHeader = "";
localStorage.removeItem('upsAuth');
document.getElementById('login-password').value = "";
document.getElementById('login-error').style.display = 'none';
document.getElementById('login-overlay').style.display = 'flex';
fetch('/logout', { method: "GET" }).catch(function(){});
}
function repeatChar(char, times) {
return char.repeat(times);
}
// Disable all elements in shutdown-form
function disableShutdownForm() {
const form = document.getElementById('shutdown-form');
const elements = form.querySelectorAll('input, button, select, textarea');
elements.forEach(element => {
element.disabled = true;
});
//const btn = document.getElementById('shutdownBtn');
//btn.disabled = true;
}
// Enable all elements in shutdown-form
function enableShutdownForm() {
const form = document.getElementById('shutdown-form');
const elements = form.querySelectorAll('input, button, select, textarea');
elements.forEach(element => {
element.disabled = false;
});
//const btn = document.getElementById('shutdownBtn');
//btn.disabled = false;
}
dropdownShutLevelSelect.addEventListener('change', function() {
let targetShutdownLvMode = parseInt(this.value);
let s = 'shutdown suggest cast: ' + targetShutdownLvMode;
console.log(s);
if (targetShutdownLvMode!==shutdownSuggestionLevel){
this.disabled = true;
SendState('shutlv', targetShutdownLvMode);
}
});
dropdownAutoOnSelect.addEventListener('change', function() {
let targetAutoMode = parseInt(this.value);
let s = 'auto on mode: ' + targetAutoMode;
console.log(s);
if (targetAutoMode!==autoOnMode){
this.disabled = true;
SendState('auto', targetAutoMode);
}
});
toggleSwitch.addEventListener('change', function() {
let targetState = toggleSwitch.checked;
let s = 'Toggle switched: ' + (targetState?'ON':'OFF');
console.log(s);
if (targetState!==stateValue){
//Disabled Button
toggleSwitch.disabled = true;
SendState('on', targetState?'1':'0');
}
});
function shutdownTriggered(){
//shutdownForm.style.display = 'none';
let t = parseInt(shutTimerInput.value);
console.log('Send timed shutdown: ' + t);
if (stateValue){
//url: htttp://<IP>/state?shutdown=<seconds>
SendState('shutdown', t);
}
}
function ConnectionRefresh(){
lastConnectTick = performance.now();
connectStateLabel.textContent = ' 🟢';
connectStateLabel.title = 'Connected';
}
dropdownChargeModeSelect.addEventListener('change', function() {
targetChargeMode = parseInt(this.value);
let s = 'charge mode: ' + targetChargeMode;
console.log(s);
if (targetChargeMode!==chargeMode){
this.disabled = true;
SendState('cm', targetChargeMode);
}
});
dropdownFullChargeOnPowerLostModeSelect.addEventListener('change', function() {
targetFCOPLMode = parseInt(this.value);
let s = 'FCOPL mode: ' + targetFCOPLMode;
console.log(s);
if (targetFCOPLMode!==fullChargeOnPowerLostMode){
this.disabled = true;
SendState('coil', targetFCOPLMode);
}
});
function resetFCoPLTriggered(){
btnResetFCOPL.disabled = true;
if (fullChargeOnPowerLostState){
SendState('rofc', 1);
}
}
toggleAutomaticVoltageReducer.addEventListener('change', function() {
targetAutomaticVoltageReducer = toggleAutomaticVoltageReducer.checked;
let s = 'AVR switched: ' + (targetAutomaticVoltageReducer?'ON':'OFF');
console.log(s);
if (targetAutomaticVoltageReducer!==chargeAutoVoltageReducer){
//Disabled Button
toggleAutomaticVoltageReducer.disabled = true;
SendState('car', targetAutomaticVoltageReducer?'1':'0');
}
});
toggleOvervoltageProtection.addEventListener('change', function() {
targetOvervoltageProtection = toggleOvervoltageProtection.checked;
let s = 'OVP switched: ' + (targetOvervoltageProtection?'ON':'OFF');
console.log(s);
if (targetOvervoltageProtection!==chargeOvervoltageProtection){
//Disabled Button
toggleOvervoltageProtection.disabled = true;
SendState('covp', targetOvervoltageProtection?'1':'0');
}
});
function SendState(cmd, value) {
// Clear timeout
clearTimeout(checkRefresh);
if (cmd !== "") {
onChange = true;
const url = '/state?' + cmd + '=' + value;
console.log("Fetch URL:", url);
fetch(url, {
method: "GET",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
"Authorization": authHeader
}
})
.then(response => {
if (response.status === 401) {
document.getElementById('login-overlay').style.display = 'flex';
if (authHeader !== "") document.getElementById('login-error').style.display = 'block';
throw new Error("Unauthorized");
}
if (!response.ok) throw new Error("Failed to update.");
return response.text();
})
.then(data => {
document.getElementById('login-overlay').style.display = 'none';
console.log(`${cmd} updated:`, data);
onChange = false;
if (parseInt(data.success)>0) ConnectionRefresh();
})
.catch(error => {
console.error("Error:", error);
onChange = false;
});
} else {
if (!onChange) {
const url = '/state';
fetch(url, {
method: "GET",
headers: {
"Authorization": authHeader
}
})
.then(response => {
if (response.status === 401) {
document.getElementById('login-overlay').style.display = 'flex';
if (authHeader !== "") document.getElementById('login-error').style.display = 'block';
throw new Error("Unauthorized");
}
if (!response.ok) throw new Error("Failed to fetch data.");
return response.json(); // Already a JSON object, no need for JSON.parse()
})
.then(data => {
document.getElementById('login-overlay').style.display = 'none';
//console.log("Fetched Data:", data);
if (parseInt(data.success)>0) ConnectionRefresh();
if (onChange) return;
//sync display
if (data.success=="2"){
autoOnMode = parseInt(data.auto);
let targetAutoMode = parseInt(dropdownAutoOnSelect.value);
if (targetAutoMode!=autoOnMode){
dropdownAutoOnSelect.value = autoOnMode.toString();
}
if (dropdownAutoOnSelect.disabled){
dropdownAutoOnSelect.disabled = false;
}
//toggleAutoBtn.textContent = autoOn?'ON':'OFF';
stateTarget = parseInt(data.on) > 0;
stateValue = parseInt(data.state) > 0;
if (stateTarget==stateValue){
toggleSwitch.checked = stateTarget;
toggleSwitch.disabled = false;
}
//switchBtn.textContent = stateOn?'ON':'OFF';
lineFrom = parseInt(data.line);
isChargingOrBlink = parseInt(data.battBlink) > 0;
chargeLevel = parseInt(data.chargeLv); //battery level at charges
isBattCritical = parseInt(data.battCritical) > 0;
//Version 1.1
battPercentage = parseInt(data.battP);
chargeStatus = parseInt(data.chgError);
chargingState = parseInt(data.charging);
chargeMode = parseInt(data.chgMode);
chargeAutoVoltageReducer = parseInt(data.chgReducer) > 0;
chargeOvervoltageProtection = parseInt(data.chgOVP) > 0;
fullChargeOnPowerLostMode = parseInt(data.chgFullTrig);
fullChargeOnPowerLostState = parseInt(data.chgOFC) > 0;
//let s = lineFrom==0?'OFF':(lineFrom==1 || isChargingOrBlink)?'⚡️ AC':'🔋 Battery';
let s = lineFrom==0?'OFF':(lineFrom==1 || isChargingOrBlink)?'⚡️ Line':'🔋 Battery';
if (chargingState>0){
if (chargeStatus==0){ //normal state
if (lineFrom==1 && chargingState==2) s += " (Charging)";
if (lineFrom==1 && chargingState==1) s += " (Reduced Charging)";
}else{
//charging while error (retry test)
if (lineFrom==1 && chargingState>0) s += " (Charging)";
}
}
if (lineFrom==2 && isBattCritical) s += " (Critical)";
labelPower.textContent = s;
labelInput.textContent = data.v + " V";
battLevel = parseInt(data.batt);
battMaxLevel = parseInt(data.battMax);
//display V1.1
//charge mode:
if (targetChargeMode!=chargeMode){
dropdownChargeModeSelect.value = chargeMode.toString();
}
if (dropdownChargeModeSelect.disabled){
dropdownChargeModeSelect.disabled = false;
}
//fcopl:
if (targetFCOPLMode!=fullChargeOnPowerLostMode){
dropdownFullChargeOnPowerLostModeSelect.value = fullChargeOnPowerLostMode.toString();
}
if (dropdownFullChargeOnPowerLostModeSelect.disabled){
dropdownFullChargeOnPowerLostModeSelect.disabled = false;
}
//cvopl reset button:
if (fullChargeOnPowerLostState){
if (btnResetFCOPL.disabled || btnResetFCOPL.style.display !== "inline-block"){
btnResetFCOPL.disabled = false;
btnResetFCOPL.style.display = "inline-block";
}
}else{
if (!btnResetFCOPL.disabled || btnResetFCOPL.style.visibility !== "none"){
btnResetFCOPL.disabled = true;
btnResetFCOPL.style.display = "none";
}
}
//AVR:
//if (toggleAutomaticVoltageReducer.checked!=chargeAutoVoltageReducer){
if (toggleAutomaticVoltageReducer.disabled){
toggleAutomaticVoltageReducer.checked = chargeAutoVoltageReducer;
toggleAutomaticVoltageReducer.disabled = false;
}
//}
//OVP:
if (toggleOvervoltageProtection.disabled){
toggleOvervoltageProtection.checked = chargeOvervoltageProtection;
toggleOvervoltageProtection.disabled = false;
}
if (stateTarget==stateValue){
if (stateValue){
enableShutdownForm();
}else{
disableShutdownForm();
}
}else{
disableShutdownForm();
}
timedShutdown = parseInt(data.timedShutdown) > 0;
if (timedShutdown){
timedShutRemaining = parseInt(data.shutdownRemain);
if (timedShutRemaining==0){
shutTimer.textContent = 'TURNING OFF...';
}else{
shutTimer.textContent = timedShutRemaining.toString();
}
shutdownForm.style.display = 'none';
}else{
timedShutRemaining = 0;
shutTimer.textContent = '';
shutdownForm.style.display = 'block';
}
if (!everReceiveStat){
shutTimerInput.value = parseInt(data.shutLastTimer);
}
everReceiveStat = true;
//shutdown suggestion level
let shutLv = parseInt(data.shutdownSuggestMode);
if (shutdownSuggestionLevel!=shutLv){
shutdownSuggestionLevel = shutLv;
dropdownShutLevelSelect.value = shutLv.toString();
}
if (dropdownShutLevelSelect.disabled){
dropdownShutLevelSelect.disabled = false;
}
shutdownCastFlag = parseInt(data.shutdownSuggest) > 0;
shutFlagLabel.textContent = shutdownCastFlag?'✔️':' ';
if (chargeStatus==0){
labelStatus.textContent = "Normal";
}else if (chargeStatus==1){
labelStatus.textContent = "⚠️ Battery Not Detected" + (chargingState==0?"":" (Retry charging...)");
}else if (chargeStatus==2){
labelStatus.textContent = "⛔ Battery Shorted" + (chargingState==0?"":" (Retry charging..)");
}else if (chargeStatus==4){
labelStatus.textContent = "⚠️ Charge Overvoltage" + (chargingState==0?"":" (Retry charging...)");
}
}
})
.catch(error => console.error("Error:", error));
}
}
}
//charging independent animation
function BattDisplayRoutine(){
//connection state update when timeout
if (performance.now() - lastConnectTick >= 3000){
connectStateLabel.textContent = ' ⚫';
connectStateLabel.title = 'Unavailable';
}
if (!isChargingOrBlink){
if (isBattCritical){
chargeTickTock = !chargeTickTock;
let bar = chargeTickTock?0:1;
if (chargeTickTock){
labelBatt.textContent = repeatChar('🟧', bar) + repeatChar('⬛', battMaxLevel-bar) + " " + battPercentage + "%";
}else{
labelBatt.textContent = repeatChar('🟧', bar) + repeatChar('⬛', battMaxLevel-bar) + " " + battPercentage + "%";
}
}else{
chargeTickTock = false;
if (battMaxLevel==0){
labelBatt.textContent = '⬛⬛⬛⬛';//'Unknown';
}else{
labelBatt.textContent = repeatChar('🟩', battLevel) + repeatChar('⬛', battMaxLevel-battLevel) + " " + battPercentage + "%";
}
}
}else{
chargeTickTock = !chargeTickTock;
let bar = chargeTickTock?Math.min(chargeLevel+1, battMaxLevel):Math.min(chargeLevel, battMaxLevel);
if (chargeTickTock){
labelBatt.textContent = repeatChar('🟩', bar) + repeatChar('⬛', battMaxLevel-bar) + " " + battPercentage + "%";
}else{
labelBatt.textContent = repeatChar('🟩', bar) + repeatChar('⬛', battMaxLevel-bar) + " " + battPercentage + "%";
}
}
}
function syncState(){
//checkOnlineState();
if (onChange){
clearTimeout(checkRefresh);
return;
}
checkRefresh = setTimeout(function() {
SendState("", 0);
}, 1000);
}
disableShutdownForm();
// Set an interval to sync display
setInterval(syncState, 500);
SendState("", 0);
syncState();
setInterval(BattDisplayRoutine, 1000);
</script>
</body>
</html>