827 lines
27 KiB
HTML
827 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 = "";
|
|
|
|
function attemptLogin() {
|
|
var usr = document.getElementById('login-username').value;
|
|
var pwd = document.getElementById('login-password').value;
|
|
authHeader = "Basic " + btoa(usr + ":" + pwd);
|
|
document.getElementById('login-error').style.display = 'none';
|
|
SendState("", 0);
|
|
}
|
|
|
|
function logout() {
|
|
authHeader = "";
|
|
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> |