nicer webapp
This commit is contained in:
parent
647583ec5b
commit
09887c2a09
|
@ -6,334 +6,11 @@
|
||||||
<title>Tesla Charge Control</title>
|
<title>Tesla Charge Control</title>
|
||||||
<link id="favicon" rel="icon"
|
<link id="favicon" rel="icon"
|
||||||
href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>⏳</text></svg>">
|
href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>⏳</text></svg>">
|
||||||
<style>
|
<link rel="stylesheet" href="style.css">
|
||||||
body {
|
<script src="script.js"></script>
|
||||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
|
||||||
background-color: #666;
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
|
||||||
max-width: 40em;
|
|
||||||
margin: auto;
|
|
||||||
padding: 0.5em 2em;
|
|
||||||
border-radius: 10px;
|
|
||||||
background-color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loading,
|
|
||||||
.loading * {
|
|
||||||
cursor: progress;
|
|
||||||
}
|
|
||||||
|
|
||||||
.disabled,
|
|
||||||
.disabled * {
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selector {
|
|
||||||
padding: 1em;
|
|
||||||
background-color: gray;
|
|
||||||
width: max-content;
|
|
||||||
border: 0.2em;
|
|
||||||
border-radius: 6px;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
label {
|
|
||||||
/* display: block; */
|
|
||||||
padding: 0.5em 1em;
|
|
||||||
margin: 0.5em;
|
|
||||||
font-weight: bold;
|
|
||||||
transition: all .2s 0s ease;
|
|
||||||
border-radius: 4px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type=radio] {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type=radio]:checked+label {
|
|
||||||
background-color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type=radio]:checked:disabled+label {
|
|
||||||
background-color: #ddd;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type=radio]:disabled+label {
|
|
||||||
color: #666;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<script type="text/javascript">
|
|
||||||
const api_url = window.location.protocol + "//" + window.location.hostname + ":" + window.location.port;
|
|
||||||
|
|
||||||
Object.prototype.disable = function () {
|
|
||||||
var that = this;
|
|
||||||
for (var i = 0, len = that.length; i < len; i++) {
|
|
||||||
that[i].disabled = true;
|
|
||||||
}
|
|
||||||
return that;
|
|
||||||
};
|
|
||||||
|
|
||||||
Object.prototype.enable = function () {
|
|
||||||
var that = this;
|
|
||||||
for (var i = 0, len = that.length; i < len; i++) {
|
|
||||||
that[i].disabled = false;
|
|
||||||
}
|
|
||||||
return that;
|
|
||||||
};
|
|
||||||
|
|
||||||
refresh_interval = register();
|
|
||||||
|
|
||||||
refresh_gains();
|
|
||||||
refresh();
|
|
||||||
document.addEventListener("visibilitychange", () => {
|
|
||||||
if (document.hidden) {
|
|
||||||
clearInterval(refresh_interval);
|
|
||||||
} else {
|
|
||||||
refresh_gains();
|
|
||||||
refresh();
|
|
||||||
refresh_interval = register();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
function register() {
|
|
||||||
return setInterval(refresh, 5000);
|
|
||||||
}
|
|
||||||
|
|
||||||
function flash() {
|
|
||||||
fetch(api_url + "/flash", { method: "POST" });
|
|
||||||
}
|
|
||||||
|
|
||||||
var is_automatic_control;
|
|
||||||
var current_min_rate;
|
|
||||||
var current_max_rate;
|
|
||||||
|
|
||||||
const delay = (time) => {
|
|
||||||
return new Promise(resolve => setTimeout(resolve, time));
|
|
||||||
};
|
|
||||||
|
|
||||||
function set_minimum() {
|
|
||||||
var set_button = document.getElementById("set-minimum");
|
|
||||||
var number_input = document.getElementById("min-rate");
|
|
||||||
if (!isNaN(number_input.value)) {
|
|
||||||
set_button.disabled = true;
|
|
||||||
number_input.disabled = true;
|
|
||||||
fetch(api_url + "/set-min/" + number_input.value, { method: "POST" })
|
|
||||||
.then(async (response) => {
|
|
||||||
let delayres = await delay(100);
|
|
||||||
refresh_buttons();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function change_min() {
|
|
||||||
var set_button = document.getElementById("set-minimum");
|
|
||||||
var number_input = document.getElementById("min-rate");
|
|
||||||
set_button.disabled = (number_input.value == current_min_rate);
|
|
||||||
}
|
|
||||||
|
|
||||||
function set_maximum() {
|
|
||||||
var set_button = document.getElementById("set-maximum");
|
|
||||||
var number_input = document.getElementById("max-rate");
|
|
||||||
if (!isNaN(number_input.value)) {
|
|
||||||
set_button.disabled = true;
|
|
||||||
number_input.disabled = true;
|
|
||||||
fetch(api_url + "/set-max/" + number_input.value, { method: "POST" })
|
|
||||||
.then(async (response) => {
|
|
||||||
let delayres = await delay(100);
|
|
||||||
refresh_buttons();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function change_max() {
|
|
||||||
var set_button = document.getElementById("set-maximum");
|
|
||||||
var number_input = document.getElementById("max-rate");
|
|
||||||
set_button.disabled = (number_input.value == current_max_rate);
|
|
||||||
}
|
|
||||||
|
|
||||||
function set_proportional() {
|
|
||||||
var set_button = document.getElementById("set-proportional");
|
|
||||||
var number_input = document.getElementById("proportional-gain");
|
|
||||||
if (!isNaN(number_input.value)) {
|
|
||||||
set_button.disabled = true;
|
|
||||||
number_input.disabled = true;
|
|
||||||
fetch(api_url + "/pid-settings/proportional/" + number_input.value, { method: "POST" })
|
|
||||||
.then(async (response) => {
|
|
||||||
let delayres = await delay(100);
|
|
||||||
refresh_gains();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function set_derivative() {
|
|
||||||
var set_button = document.getElementById("set-derivative");
|
|
||||||
var number_input = document.getElementById("derivative-gain");
|
|
||||||
if (!isNaN(number_input.value)) {
|
|
||||||
set_button.disabled = true;
|
|
||||||
number_input.disabled = true;
|
|
||||||
fetch(api_url + "/pid-settings/derivative/" + number_input.value, { method: "POST" })
|
|
||||||
.then(async (response) => {
|
|
||||||
let delayres = await delay(100);
|
|
||||||
refresh_gains();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function set_pid_length() {
|
|
||||||
var set_button = document.getElementById("set-pid-length");
|
|
||||||
var number_input = document.getElementById("pid-length");
|
|
||||||
if (!isNaN(number_input.value)) {
|
|
||||||
set_button.disabled = true;
|
|
||||||
number_input.disabled = true;
|
|
||||||
fetch(api_url + "/pid-settings/loop_time_seconds/" + number_input.value, { method: "POST" })
|
|
||||||
.then(async (response) => {
|
|
||||||
let delayres = await delay(100);
|
|
||||||
refresh_gains();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function disable_automatic_control() {
|
|
||||||
if (is_automatic_control) {
|
|
||||||
document.getElementById('control-disabled').checked = true;
|
|
||||||
document.body.classList.add("loading");
|
|
||||||
|
|
||||||
fetch(api_url + "/disable-control", { method: "POST" })
|
|
||||||
.then(async (response) => {
|
|
||||||
let delayres = await delay(1000);
|
|
||||||
refresh_buttons();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function enable_automatic_control() {
|
|
||||||
if (!is_automatic_control) {
|
|
||||||
document.getElementById('control-enabled').checked = true;
|
|
||||||
document.body.classList.add("loading");
|
|
||||||
fetch(api_url + "/enable-control", { method: "POST" })
|
|
||||||
.then(async (response) => {
|
|
||||||
let delayres = await delay(1000);
|
|
||||||
refresh_buttons();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function update_control_buttons(data) {
|
|
||||||
current_max_rate = data.max_rate;
|
|
||||||
current_min_rate = data.min_rate;
|
|
||||||
|
|
||||||
var number_input_min = document.getElementById("min-rate");
|
|
||||||
if (number_input_min.disabled || number_input_min.value == "") {
|
|
||||||
number_input_min.value = data.min_rate;
|
|
||||||
number_input_min.disabled = false;
|
|
||||||
}
|
|
||||||
document.getElementById("set-minimum").disabled = (number_input_min.value == current_min_rate);
|
|
||||||
|
|
||||||
var number_input_max = document.getElementById("max-rate");
|
|
||||||
if (number_input_max.disabled || number_input_max.value == "") {
|
|
||||||
number_input_max.value = data.max_rate;
|
|
||||||
number_input_max.disabled = false;
|
|
||||||
}
|
|
||||||
document.getElementById("set-maximum").disabled = (number_input_max.value == current_max_rate);
|
|
||||||
|
|
||||||
document.body.classList.remove("loading");
|
|
||||||
is_automatic_control = data.control_enable;
|
|
||||||
if (data.control_enable) {
|
|
||||||
document.getElementById('control-enabled').checked = true;
|
|
||||||
} else {
|
|
||||||
document.getElementById('control-disabled').checked = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
var control_selector = document.getElementById("control-selector");
|
|
||||||
if (data.is_charging_at_home) {
|
|
||||||
if (control_selector.classList.contains('disabled')) {
|
|
||||||
control_selector.classList.remove('disabled');
|
|
||||||
}
|
|
||||||
document.getElementsByName('control').enable();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (!control_selector.classList.contains('disabled')) {
|
|
||||||
control_selector.classList.add('disabled');
|
|
||||||
}
|
|
||||||
document.getElementsByName('control').disable();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function refresh_gains() {
|
|
||||||
fetch(api_url + "/pid-settings/status")
|
|
||||||
.then((response) => response.json())
|
|
||||||
.then((json) => update_gains(json));
|
|
||||||
}
|
|
||||||
|
|
||||||
function update_gains(data) {
|
|
||||||
var proportional_set_button = document.getElementById("set-proportional");
|
|
||||||
var proportional_number_input = document.getElementById("proportional-gain");
|
|
||||||
proportional_set_button.disabled = false;
|
|
||||||
proportional_number_input.disabled = false;
|
|
||||||
proportional_number_input.value = data.proportional_gain;
|
|
||||||
var derivative_set_button = document.getElementById("set-derivative");
|
|
||||||
var derivative_number_input = document.getElementById("derivative-gain");
|
|
||||||
derivative_set_button.disabled = false;
|
|
||||||
derivative_number_input.disabled = false;
|
|
||||||
derivative_number_input.value = data.derivative_gain;
|
|
||||||
var pid_length_button = document.getElementById("set-pid-length");
|
|
||||||
var pid_length_input = document.getElementById("pid-length");
|
|
||||||
pid_length_button.disabled = false;
|
|
||||||
pid_length_input.disabled = false;
|
|
||||||
pid_length_input.value = data.loop_time_seconds;
|
|
||||||
}
|
|
||||||
|
|
||||||
function refresh_buttons() {
|
|
||||||
fetch(api_url + "/control-state")
|
|
||||||
.then((response) => response.json())
|
|
||||||
.then((json) => update_control_buttons(json));
|
|
||||||
}
|
|
||||||
|
|
||||||
function refresh() {
|
|
||||||
let favicon = document.getElementById("favicon");
|
|
||||||
|
|
||||||
favicon.setAttribute("href", "data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>" + "⏳" + "</text></svg>");
|
|
||||||
|
|
||||||
refresh_buttons();
|
|
||||||
|
|
||||||
fetch(api_url + "/car-state")
|
|
||||||
.then((response) => response.json())
|
|
||||||
.then((json) => update_state(json));
|
|
||||||
}
|
|
||||||
|
|
||||||
function update_state(state) {
|
|
||||||
let favicon = document.getElementById("favicon");
|
|
||||||
|
|
||||||
favicon.setAttribute("href", "data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>" + get_emoji(state.charge_state) + "</text></svg>");
|
|
||||||
|
|
||||||
var info_div = document.getElementById("info");
|
|
||||||
while (info_div.childElementCount > 0) { info_div.removeChild(info_div.firstChild) }
|
|
||||||
|
|
||||||
el = document.createElement('p');
|
|
||||||
|
|
||||||
state_json = document.createElement('pre');
|
|
||||||
state_json.appendChild(document.createTextNode(JSON.stringify(state, null, '\t')));
|
|
||||||
el.appendChild(state_json);
|
|
||||||
info_div.appendChild(el);
|
|
||||||
}
|
|
||||||
|
|
||||||
function get_emoji(charge_state) {
|
|
||||||
if (charge_state == null) {
|
|
||||||
return "🤨";
|
|
||||||
}
|
|
||||||
else if (charge_state.charge_rate > 0) {
|
|
||||||
return "🔌";
|
|
||||||
} else if (charge_state.battery_level < 60) {
|
|
||||||
return "🪫"
|
|
||||||
} else return "🔋";
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body onload="init_main()">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<h3>Automatic control:</h3>
|
<h3>Automatic control:</h3>
|
||||||
<div class="selector disabled" id="control-selector">
|
<div class="selector disabled" id="control-selector">
|
||||||
|
@ -351,25 +28,13 @@
|
||||||
<input type="number" id="min-rate" max="15" min="3" onchange="change_min()" autocomplete="off" />
|
<input type="number" id="min-rate" max="15" min="3" onchange="change_min()" autocomplete="off" />
|
||||||
<button id="set-minimum" onclick="set_minimum()" disabled>Set minimum</button>
|
<button id="set-minimum" onclick="set_minimum()" disabled>Set minimum</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="pid-control">
|
|
||||||
<h3>PID:</h3>
|
|
||||||
<input type="number" id="proportional-gain" step="0.1" autocomplete="off" />
|
|
||||||
<button id="set-proportional" onclick="set_proportional()">Set proportional gain</button>
|
|
||||||
<br><br>
|
|
||||||
<input type="number" id="derivative-gain" step="0.1" autocomplete="off" />
|
|
||||||
<button id="set-derivative" onclick="set_derivative()">Set derivative gain</button>
|
|
||||||
<br><br>
|
|
||||||
<input type="number" id="pid-length" step="1" autocomplete="off" />
|
|
||||||
<button id="set-pid-length" onclick="set_pid_length()">Set pid length</button>
|
|
||||||
</div>
|
|
||||||
<br>
|
|
||||||
<button onclick="flash()">flash</button>
|
|
||||||
<div id="info"></div>
|
<div id="info"></div>
|
||||||
<div>
|
<button onclick="flash()">flash</button>
|
||||||
<h3>
|
<p>
|
||||||
<a href="/grafana">Grafana</a>
|
<a class="outlink" href="/grafana">Grafana→</a>
|
||||||
</h3>
|
<a class="outlink" href="/info">Car state info→</a>
|
||||||
</div>
|
<a class="outlink" href="/pid">PID control variables→</a>
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
|
|
22
webapp/info/index.html
Normal file
22
webapp/info/index.html
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<title>Tesla Charge Control</title>
|
||||||
|
<link id="favicon" rel="icon"
|
||||||
|
href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>⏳</text></svg>">
|
||||||
|
<link rel="stylesheet" href="style.css">
|
||||||
|
<script src="script.js"></script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body onload="init_info()">
|
||||||
|
<div class="container">
|
||||||
|
<div id="info"></div>
|
||||||
|
<p>
|
||||||
|
<a class="outlink" href="/">←Home</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
32
webapp/pid/index.html
Normal file
32
webapp/pid/index.html
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<title>Tesla Charge Control</title>
|
||||||
|
<link id="favicon" rel="icon"
|
||||||
|
href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🎚️</text></svg>">
|
||||||
|
<link rel="stylesheet" href="style.css">
|
||||||
|
<script src="script.js"></script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body onload="init_pid()">
|
||||||
|
<div class="container">
|
||||||
|
<p id="pid-control">
|
||||||
|
<h3>PID:</h3>
|
||||||
|
<input type="number" id="proportional-gain" step="0.1" autocomplete="off" />
|
||||||
|
<button id="set-proportional" onclick="set_proportional()">Set proportional gain</button>
|
||||||
|
<br><br>
|
||||||
|
<input type="number" id="derivative-gain" step="0.1" autocomplete="off" />
|
||||||
|
<button id="set-derivative" onclick="set_derivative()">Set derivative gain</button>
|
||||||
|
<br><br>
|
||||||
|
<input type="number" id="pid-length" step="1" autocomplete="off" />
|
||||||
|
<button id="set-pid-length" onclick="set_pid_length()">Set pid length</button>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<a class="outlink" href="/">←Home</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
334
webapp/script.js
Normal file
334
webapp/script.js
Normal file
|
@ -0,0 +1,334 @@
|
||||||
|
const api_url = window.location.protocol + "//" + window.location.hostname + ":" + window.location.port;
|
||||||
|
|
||||||
|
Object.prototype.disable = function () {
|
||||||
|
var that = this;
|
||||||
|
for (var i = 0, len = that.length; i < len; i++) {
|
||||||
|
that[i].disabled = true;
|
||||||
|
}
|
||||||
|
return that;
|
||||||
|
};
|
||||||
|
|
||||||
|
Object.prototype.enable = function () {
|
||||||
|
var that = this;
|
||||||
|
for (var i = 0, len = that.length; i < len; i++) {
|
||||||
|
that[i].disabled = false;
|
||||||
|
}
|
||||||
|
return that;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
function init_main() {
|
||||||
|
refresh_interval = register(refresh);
|
||||||
|
|
||||||
|
refresh();
|
||||||
|
document.addEventListener("visibilitychange", () => {
|
||||||
|
if (document.hidden) {
|
||||||
|
clearInterval(refresh_interval);
|
||||||
|
} else {
|
||||||
|
refresh();
|
||||||
|
refresh_interval = register(refresh);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function init_pid() {
|
||||||
|
refresh_gains();
|
||||||
|
document.addEventListener("visibilitychange", () => {
|
||||||
|
if (!document.hidden) {
|
||||||
|
refresh_gains();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function init_info() {
|
||||||
|
refresh_interval = register(refresh_info);
|
||||||
|
|
||||||
|
refresh_info();
|
||||||
|
document.addEventListener("visibilitychange", () => {
|
||||||
|
if (document.hidden) {
|
||||||
|
clearInterval(refresh_interval);
|
||||||
|
} else {
|
||||||
|
refresh_info();
|
||||||
|
refresh_interval = register(refresh_info);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function register(func) {
|
||||||
|
return setInterval(func, 5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
function flash() {
|
||||||
|
fetch(api_url + "/flash", { method: "POST" });
|
||||||
|
}
|
||||||
|
|
||||||
|
var is_automatic_control;
|
||||||
|
var current_min_rate;
|
||||||
|
var current_max_rate;
|
||||||
|
|
||||||
|
const delay = (time) => {
|
||||||
|
return new Promise(resolve => setTimeout(resolve, time));
|
||||||
|
};
|
||||||
|
|
||||||
|
function set_minimum() {
|
||||||
|
var set_button = document.getElementById("set-minimum");
|
||||||
|
var number_input = document.getElementById("min-rate");
|
||||||
|
if (!isNaN(number_input.value)) {
|
||||||
|
set_button.disabled = true;
|
||||||
|
number_input.disabled = true;
|
||||||
|
fetch(api_url + "/set-min/" + number_input.value, { method: "POST" })
|
||||||
|
.then(async (response) => {
|
||||||
|
let delayres = await delay(100);
|
||||||
|
refresh_buttons();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function change_min() {
|
||||||
|
var set_button = document.getElementById("set-minimum");
|
||||||
|
var number_input = document.getElementById("min-rate");
|
||||||
|
set_button.disabled = (number_input.value == current_min_rate);
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_maximum() {
|
||||||
|
var set_button = document.getElementById("set-maximum");
|
||||||
|
var number_input = document.getElementById("max-rate");
|
||||||
|
if (!isNaN(number_input.value)) {
|
||||||
|
set_button.disabled = true;
|
||||||
|
number_input.disabled = true;
|
||||||
|
fetch(api_url + "/set-max/" + number_input.value, { method: "POST" })
|
||||||
|
.then(async (response) => {
|
||||||
|
let delayres = await delay(100);
|
||||||
|
refresh_buttons();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function change_max() {
|
||||||
|
var set_button = document.getElementById("set-maximum");
|
||||||
|
var number_input = document.getElementById("max-rate");
|
||||||
|
set_button.disabled = (number_input.value == current_max_rate);
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_proportional() {
|
||||||
|
var set_button = document.getElementById("set-proportional");
|
||||||
|
var number_input = document.getElementById("proportional-gain");
|
||||||
|
if (!isNaN(number_input.value)) {
|
||||||
|
set_button.disabled = true;
|
||||||
|
number_input.disabled = true;
|
||||||
|
fetch(api_url + "/pid-settings/proportional/" + number_input.value, { method: "POST" })
|
||||||
|
.then(async (response) => {
|
||||||
|
let delayres = await delay(100);
|
||||||
|
refresh_gains();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_derivative() {
|
||||||
|
var set_button = document.getElementById("set-derivative");
|
||||||
|
var number_input = document.getElementById("derivative-gain");
|
||||||
|
if (!isNaN(number_input.value)) {
|
||||||
|
set_button.disabled = true;
|
||||||
|
number_input.disabled = true;
|
||||||
|
fetch(api_url + "/pid-settings/derivative/" + number_input.value, { method: "POST" })
|
||||||
|
.then(async (response) => {
|
||||||
|
let delayres = await delay(100);
|
||||||
|
refresh_gains();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_pid_length() {
|
||||||
|
var set_button = document.getElementById("set-pid-length");
|
||||||
|
var number_input = document.getElementById("pid-length");
|
||||||
|
if (!isNaN(number_input.value)) {
|
||||||
|
set_button.disabled = true;
|
||||||
|
number_input.disabled = true;
|
||||||
|
fetch(api_url + "/pid-settings/loop_time_seconds/" + number_input.value, { method: "POST" })
|
||||||
|
.then(async (response) => {
|
||||||
|
let delayres = await delay(100);
|
||||||
|
refresh_gains();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function disable_automatic_control() {
|
||||||
|
if (is_automatic_control) {
|
||||||
|
document.getElementById('control-disabled').checked = true;
|
||||||
|
document.body.classList.add("loading");
|
||||||
|
|
||||||
|
fetch(api_url + "/disable-control", { method: "POST" })
|
||||||
|
.then(async (response) => {
|
||||||
|
let delayres = await delay(1000);
|
||||||
|
refresh_buttons();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function enable_automatic_control() {
|
||||||
|
if (!is_automatic_control) {
|
||||||
|
document.getElementById('control-enabled').checked = true;
|
||||||
|
document.body.classList.add("loading");
|
||||||
|
fetch(api_url + "/enable-control", { method: "POST" })
|
||||||
|
.then(async (response) => {
|
||||||
|
let delayres = await delay(1000);
|
||||||
|
refresh_buttons();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function update_control_buttons(data) {
|
||||||
|
current_max_rate = data.max_rate;
|
||||||
|
current_min_rate = data.min_rate;
|
||||||
|
|
||||||
|
var number_input_min = document.getElementById("min-rate");
|
||||||
|
if (number_input_min.disabled || number_input_min.value == "") {
|
||||||
|
number_input_min.value = data.min_rate;
|
||||||
|
number_input_min.disabled = false;
|
||||||
|
}
|
||||||
|
document.getElementById("set-minimum").disabled = (number_input_min.value == current_min_rate);
|
||||||
|
|
||||||
|
var number_input_max = document.getElementById("max-rate");
|
||||||
|
if (number_input_max.disabled || number_input_max.value == "") {
|
||||||
|
number_input_max.value = data.max_rate;
|
||||||
|
number_input_max.disabled = false;
|
||||||
|
}
|
||||||
|
document.getElementById("set-maximum").disabled = (number_input_max.value == current_max_rate);
|
||||||
|
|
||||||
|
document.body.classList.remove("loading");
|
||||||
|
is_automatic_control = data.control_enable;
|
||||||
|
if (data.control_enable) {
|
||||||
|
document.getElementById('control-enabled').checked = true;
|
||||||
|
} else {
|
||||||
|
document.getElementById('control-disabled').checked = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var control_selector = document.getElementById("control-selector");
|
||||||
|
if (data.is_charging_at_home) {
|
||||||
|
if (control_selector.classList.contains('disabled')) {
|
||||||
|
control_selector.classList.remove('disabled');
|
||||||
|
}
|
||||||
|
document.getElementsByName('control').enable();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (!control_selector.classList.contains('disabled')) {
|
||||||
|
control_selector.classList.add('disabled');
|
||||||
|
}
|
||||||
|
document.getElementsByName('control').disable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function refresh_gains() {
|
||||||
|
fetch(api_url + "/pid-settings/status")
|
||||||
|
.then((response) => response.json())
|
||||||
|
.then((json) => update_gains(json));
|
||||||
|
}
|
||||||
|
|
||||||
|
function update_gains(data) {
|
||||||
|
var proportional_set_button = document.getElementById("set-proportional");
|
||||||
|
var proportional_number_input = document.getElementById("proportional-gain");
|
||||||
|
proportional_set_button.disabled = false;
|
||||||
|
proportional_number_input.disabled = false;
|
||||||
|
proportional_number_input.value = data.proportional_gain;
|
||||||
|
var derivative_set_button = document.getElementById("set-derivative");
|
||||||
|
var derivative_number_input = document.getElementById("derivative-gain");
|
||||||
|
derivative_set_button.disabled = false;
|
||||||
|
derivative_number_input.disabled = false;
|
||||||
|
derivative_number_input.value = data.derivative_gain;
|
||||||
|
var pid_length_button = document.getElementById("set-pid-length");
|
||||||
|
var pid_length_input = document.getElementById("pid-length");
|
||||||
|
pid_length_button.disabled = false;
|
||||||
|
pid_length_input.disabled = false;
|
||||||
|
pid_length_input.value = data.loop_time_seconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
function refresh_buttons() {
|
||||||
|
fetch(api_url + "/control-state")
|
||||||
|
.then((response) => response.json())
|
||||||
|
.then((json) => update_control_buttons(json));
|
||||||
|
}
|
||||||
|
|
||||||
|
function refresh() {
|
||||||
|
set_favicon(null);
|
||||||
|
|
||||||
|
refresh_buttons();
|
||||||
|
|
||||||
|
fetch(api_url + "/car-state")
|
||||||
|
.then((response) => response.json())
|
||||||
|
.then((json) => update_state(json));
|
||||||
|
}
|
||||||
|
|
||||||
|
function refresh_info() {
|
||||||
|
set_favicon(null);
|
||||||
|
|
||||||
|
fetch(api_url + "/car-state")
|
||||||
|
.then((response) => response.json())
|
||||||
|
.then((json) => update_info(json));
|
||||||
|
}
|
||||||
|
|
||||||
|
function update_info(state) {
|
||||||
|
set_favicon(state.charge_state);
|
||||||
|
|
||||||
|
var info_div = document.getElementById("info");
|
||||||
|
while (info_div.childElementCount > 0) { info_div.removeChild(info_div.firstChild) }
|
||||||
|
|
||||||
|
el = document.createElement('p');
|
||||||
|
|
||||||
|
state_json = document.createElement('pre');
|
||||||
|
state_json.appendChild(document.createTextNode(JSON.stringify(state, null, '\t')));
|
||||||
|
el.appendChild(state_json);
|
||||||
|
info_div.appendChild(el);
|
||||||
|
}
|
||||||
|
|
||||||
|
function update_state(state) {
|
||||||
|
set_favicon(state.charge_state);
|
||||||
|
|
||||||
|
var info_div = document.getElementById("info");
|
||||||
|
while (info_div.childElementCount > 0) { info_div.removeChild(info_div.firstChild) }
|
||||||
|
|
||||||
|
el = document.createElement('p');
|
||||||
|
|
||||||
|
var charging_state_info = "";
|
||||||
|
|
||||||
|
switch (state.charge_state.charging_state) {
|
||||||
|
case "Charging":
|
||||||
|
charging_state_info = "charging at " + state.charge_state.charge_rate + "A";
|
||||||
|
break;
|
||||||
|
case "Stopped":
|
||||||
|
charging_state_info = "charge stopped";
|
||||||
|
break;
|
||||||
|
case "Disconnected":
|
||||||
|
charging_state_info = "disconnected"
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.location_data.home) {
|
||||||
|
if (state.charge_state.charging_state == "Charging") {
|
||||||
|
charging_state_info = "set at " + state.charge_state.charge_current_request + "A, " + charging_state_info;
|
||||||
|
}
|
||||||
|
charging_state_info = "At home; " + charging_state_info;
|
||||||
|
} else {
|
||||||
|
charging_state_info = "Not home; " + charging_state_info;
|
||||||
|
}
|
||||||
|
|
||||||
|
el.appendChild(document.createTextNode(charging_state_info));
|
||||||
|
|
||||||
|
info_div.appendChild(el);
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_favicon(charge_state) {
|
||||||
|
let favicon = document.getElementById("favicon");
|
||||||
|
|
||||||
|
favicon.setAttribute("href", "data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>" + get_emoji(charge_state) + "</text></svg>");
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_emoji(charge_state) {
|
||||||
|
if (charge_state == null) {
|
||||||
|
return "⏳";
|
||||||
|
}
|
||||||
|
else if (charge_state.charge_rate > 0) {
|
||||||
|
return "🔌";
|
||||||
|
} else if (charge_state.battery_level < 60) {
|
||||||
|
return "🪫"
|
||||||
|
} else return "🔋";
|
||||||
|
}
|
72
webapp/style.css
Normal file
72
webapp/style.css
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
||||||
|
background-color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
max-width: 40em;
|
||||||
|
margin: auto;
|
||||||
|
padding: 0.5em 2em;
|
||||||
|
border-radius: 10px;
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.outlink {
|
||||||
|
display: block;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-top: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.outlink {
|
||||||
|
text-decoration: none;
|
||||||
|
color: rgb(52, 52, 246);
|
||||||
|
}
|
||||||
|
|
||||||
|
a.outlink:hover {
|
||||||
|
color: rgb(100, 90, 255);
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading,
|
||||||
|
.loading * {
|
||||||
|
cursor: progress;
|
||||||
|
}
|
||||||
|
|
||||||
|
.disabled,
|
||||||
|
.disabled * {
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selector {
|
||||||
|
padding: 1em;
|
||||||
|
background-color: gray;
|
||||||
|
width: max-content;
|
||||||
|
border: 0.2em;
|
||||||
|
border-radius: 6px;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
/* display: block; */
|
||||||
|
padding: 0.5em 1em;
|
||||||
|
margin: 0.5em;
|
||||||
|
font-weight: bold;
|
||||||
|
transition: all .2s 0s ease;
|
||||||
|
border-radius: 4px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=radio] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=radio]:checked+label {
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=radio]:checked:disabled+label {
|
||||||
|
background-color: #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=radio]:disabled+label {
|
||||||
|
color: #666;
|
||||||
|
}
|
Loading…
Reference in a new issue