Files
invertergui/plugins/webui/static/js/controller.js
Nathan Coad a31a0b4829
Some checks failed
build / inverter_gui_pipeline (push) Has been cancelled
Modernize invertergui: MQTT write support, HA integration, UI updates
2026-02-19 12:03:52 +11:00

281 lines
7.5 KiB
JavaScript

var app;
const timeoutMax = 30000;
const timeoutMin = 1000;
var timeout = timeoutMin;
function defaultRemotePanelState() {
return {
writable: false,
mode: "unknown",
current_limit: null,
standby: null,
last_command: "",
last_error: "",
last_updated: ""
};
}
function defaultState() {
return {
output_current: null,
output_voltage: 0,
output_frequency: 0,
output_power: 0,
input_current: 0,
input_voltage: 0,
input_frequency: 0,
input_power: 0,
battery_current: 0,
battery_voltage: 0,
battery_charge: 0,
battery_power: 0,
led_map: {
led_mains: "dot-off",
led_absorb: "dot-off",
led_bulk: "dot-off",
led_float: "dot-off",
led_inverter: "dot-off",
led_overload: "dot-off",
led_bat_low: "dot-off",
led_over_temp: "dot-off"
},
remote_panel: defaultRemotePanelState()
};
}
function loadContent() {
app = new Vue({
el: "#app",
data: {
error: {
has_error: false,
error_message: ""
},
control: {
busy: false,
has_error: false,
message: ""
},
remote_form: {
mode: "on",
current_limit: "",
standby: false
},
state: defaultState()
},
methods: {
syncRemoteFormFromState: function(remoteState) {
if (!remoteState) {
return;
}
if (remoteState.mode && remoteState.mode !== "unknown") {
this.remote_form.mode = remoteState.mode;
}
if (remoteState.current_limit === null || remoteState.current_limit === undefined) {
this.remote_form.current_limit = "";
} else {
this.remote_form.current_limit = String(remoteState.current_limit);
}
if (remoteState.standby === null || remoteState.standby === undefined) {
this.remote_form.standby = false;
} else {
this.remote_form.standby = !!remoteState.standby;
}
},
remoteModeLabel: function(remoteState) {
var mode = (remoteState && remoteState.mode) || "unknown";
if (mode === "charger_only") {
return "Charger Only";
}
if (mode === "inverter_only") {
return "Inverter Only";
}
if (mode === "on") {
return "On";
}
if (mode === "off") {
return "Off";
}
return "Unknown";
},
remoteStandbyLabel: function(remoteState) {
if (!remoteState || remoteState.standby === null || remoteState.standby === undefined) {
return "Unknown";
}
return remoteState.standby ? "Enabled" : "Disabled";
},
refreshRemoteState: function() {
var self = this;
fetch(getAPIURI("api/remote-panel/state"))
.then(function(resp) {
if (!resp.ok) {
throw new Error("Could not load remote panel state.");
}
return resp.json();
})
.then(function(payload) {
self.state.remote_panel = payload;
self.syncRemoteFormFromState(payload);
})
.catch(function(err) {
self.control.has_error = true;
self.control.message = err.message;
});
},
applyRemotePanelState: function() {
var self = this;
if (!self.state.remote_panel.writable) {
return;
}
var body = {
mode: self.remote_form.mode
};
if (self.remote_form.current_limit !== "") {
var parsed = parseFloat(self.remote_form.current_limit);
if (isNaN(parsed)) {
self.control.has_error = true;
self.control.message = "Current limit must be numeric.";
return;
}
body.current_limit = parsed;
}
self.control.busy = true;
self.control.has_error = false;
self.control.message = "";
fetch(getAPIURI("api/remote-panel/state"), {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(body)
})
.then(function(resp) {
if (!resp.ok) {
return resp.text().then(function(text) {
throw new Error(text || "Failed to set remote panel mode/current limit.");
});
}
return resp.json();
})
.then(function(payload) {
self.state.remote_panel = payload;
self.syncRemoteFormFromState(payload);
self.control.has_error = false;
self.control.message = "Remote panel state updated.";
})
.catch(function(err) {
self.control.has_error = true;
self.control.message = err.message;
})
.finally(function() {
self.control.busy = false;
});
},
applyStandby: function() {
var self = this;
if (!self.state.remote_panel.writable) {
return;
}
self.control.busy = true;
self.control.has_error = false;
self.control.message = "";
fetch(getAPIURI("api/remote-panel/standby"), {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
standby: !!self.remote_form.standby
})
})
.then(function(resp) {
if (!resp.ok) {
return resp.text().then(function(text) {
throw new Error(text || "Failed to set standby mode.");
});
}
return resp.json();
})
.then(function(payload) {
self.state.remote_panel = payload;
self.syncRemoteFormFromState(payload);
self.control.has_error = false;
self.control.message = "Standby mode updated.";
})
.catch(function(err) {
self.control.has_error = true;
self.control.message = err.message;
})
.finally(function() {
self.control.busy = false;
});
}
}
});
app.refreshRemoteState();
connect();
}
function connect() {
if (window["WebSocket"]) {
var conn = new WebSocket(getURI());
conn.onclose = function(evt) {
app.error.has_error = true;
app.error.error_message =
"Server not reachable. Trying to reconnect in " +
timeout / 1000 +
" second(s).";
console.log(app.error.error_message, evt.reason);
setTimeout(function() {
connect();
}, timeout);
timeout = timeout * 2;
if (timeout > timeoutMax) {
timeout = timeoutMax;
}
};
conn.onopen = function() {
timeout = timeoutMin;
app.error.has_error = false;
};
conn.onmessage = function(evt) {
var update = JSON.parse(evt.data);
app.state = update;
if (!app.control.busy) {
app.syncRemoteFormFromState(update.remote_panel);
}
};
} else {
app.error.has_error = true;
app.error.error_message = "Our browser does not support WebSockets.";
}
}
function getURI() {
var loc = window.location,
new_uri;
if (loc.protocol === "https:") {
new_uri = "wss:";
} else {
new_uri = "ws:";
}
new_uri += "//" + loc.host;
new_uri += loc.pathname + "ws";
return new_uri;
}
function getAPIURI(path) {
var base = window.location.pathname;
if (base.slice(-1) !== "/") {
base += "/";
}
return base + path.replace(/^\/+/, "");
}