tesla-charge-controller/webapp/index.html

376 lines
11 KiB
HTML
Raw Normal View History

<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>Tesla Charge Control</title>
2024-01-07 10:33:32 +11:00
<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>">
2024-01-19 10:28:00 +11:00
<style>
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;
}
.loading,
.loading * {
cursor: progress;
}
.disabled,
.disabled * {
cursor: not-allowed;
}
2024-01-19 10:28:00 +11:00
.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;
}
2024-01-19 10:28:00 +11:00
input[type=radio]:checked:disabled+label {
background-color: #ddd;
}
input[type=radio]:disabled+label {
color: #666;
2024-01-19 10:28:00 +11:00
}
</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;
};
2024-01-07 11:21:33 +11:00
refresh_interval = register();
2024-01-22 12:40:29 +11:00
refresh_gains();
2024-01-07 11:21:33 +11:00
refresh();
document.addEventListener("visibilitychange", () => {
if (document.hidden) {
clearInterval(refresh_interval);
} else {
2024-01-22 12:40:29 +11:00
refresh_gains();
2024-01-07 11:21:33 +11:00
refresh();
refresh_interval = register();
}
});
2024-01-07 10:33:32 +11:00
function register() {
2024-01-19 10:28:00 +11:00
return setInterval(refresh, 5000);
2024-01-07 10:33:32 +11:00
}
function flash() {
2024-01-17 09:18:49 +11:00
fetch(api_url + "/flash", { method: "POST" });
}
2024-01-19 10:28:00 +11:00
var is_automatic_control;
2024-01-21 15:25:28 +11:00
var current_min_rate;
var current_max_rate;
2024-01-19 10:28:00 +11:00
2024-01-21 15:25:28 +11:00
const delay = (time) => {
return new Promise(resolve => setTimeout(resolve, time));
2024-01-19 10:28:00 +11:00
};
2024-01-21 15:25:28 +11:00
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);
}
2024-01-22 12:40:29 +11:00
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();
});
}
}
2024-01-16 11:00:11 +11:00
function disable_automatic_control() {
2024-01-19 10:28:00 +11:00
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) => {
2024-01-21 15:25:28 +11:00
let delayres = await delay(1000);
2024-01-19 10:28:00 +11:00
refresh_buttons();
});
}
2024-01-16 11:00:11 +11:00
}
function enable_automatic_control() {
2024-01-19 10:28:00 +11:00
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) => {
2024-01-21 15:25:28 +11:00
let delayres = await delay(1000);
2024-01-19 10:28:00 +11:00
refresh_buttons();
});
}
2024-01-16 11:00:11 +11:00
}
function update_control_buttons(data) {
2024-01-21 15:25:28 +11:00
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);
2024-01-19 10:28:00 +11:00
document.body.classList.remove("loading");
is_automatic_control = data.control_enable;
2024-01-16 11:00:11 +11:00
if (data.control_enable) {
2024-01-19 10:28:00 +11:00
document.getElementById('control-enabled').checked = true;
2024-01-16 11:00:11 +11:00
} else {
2024-01-19 10:28:00 +11:00
document.getElementById('control-disabled').checked = true;
2024-01-16 11:00:11 +11:00
}
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();
}
2024-01-16 11:00:11 +11:00
}
2024-01-22 12:40:29 +11:00
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;
}
2024-01-16 11:00:11 +11:00
function refresh_buttons() {
fetch(api_url + "/control-state")
.then((response) => response.json())
.then((json) => update_control_buttons(json));
}
function refresh() {
2024-01-07 10:33:32 +11:00
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>");
2024-01-19 10:28:00 +11:00
refresh_buttons();
2024-01-22 12:40:29 +11:00
fetch(api_url + "/car-state")
.then((response) => response.json())
.then((json) => update_state(json));
}
function update_state(state) {
2024-01-07 10:33:32 +11:00
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>");
2024-01-07 10:33:32 +11:00
var info_div = document.getElementById("info");
while (info_div.childElementCount > 0) { info_div.removeChild(info_div.firstChild) }
2024-01-07 10:33:32 +11:00
el = document.createElement('p');
state_json = document.createElement('pre');
2024-01-19 10:28:00 +11:00
state_json.appendChild(document.createTextNode(JSON.stringify(state, null, '\t')));
2024-01-07 10:33:32 +11:00
el.appendChild(state_json);
info_div.appendChild(el);
2024-01-08 12:00:09 +11:00
}
2024-01-07 10:33:32 +11:00
function get_emoji(charge_state) {
if (charge_state == null) {
return "🤨";
}
else if (charge_state.charge_rate > 0) {
2024-01-07 10:33:32 +11:00
return "🔌";
} else if (charge_state.battery_level < 60) {
return "🪫"
} else return "🔋";
}
</script>
</head>
2024-01-19 10:28:00 +11:00
<body>
<div class="container">
<h3>Automatic control:</h3>
<div class="selector disabled" id="control-selector">
<input id="control-enabled" type="radio" name="control" onclick="enable_automatic_control()" disabled>
2024-01-19 10:28:00 +11:00
<label for="control-enabled">enabled</label>
<input id="control-disabled" type="radio" name="control" onclick="disable_automatic_control()" disabled>
2024-01-19 10:28:00 +11:00
<label for="control-disabled">disabled</label>
</div>
2024-01-21 15:25:28 +11:00
<div id="rate-control">
<h3>Charge rate:</h3>
<input type="number" id="max-rate" max="15" min="3" onchange="change_max()" autocomplete="off" />
<button id="set-maximum" onclick="set_maximum()" disabled>Set maximum</button>
<br><br>
<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>
</div>
2024-01-22 12:40:29 +11:00
<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>
2024-01-19 10:28:00 +11:00
<br>
<button onclick="flash()">flash</button>
<div id="info"></div>
<div>
<h3>
<a href="/grafana">Grafana</a>
</h3>
</div>
</div>
</body>
</html>