Modernize invertergui: MQTT write support, HA integration, UI updates
Some checks failed
build / inverter_gui_pipeline (push) Has been cancelled

This commit is contained in:
2026-02-19 12:03:52 +11:00
parent 959d1e3c1f
commit a31a0b4829
460 changed files with 19655 additions and 40205 deletions

View File

@@ -3,6 +3,46 @@ 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",
@@ -11,33 +51,172 @@ function loadContent() {
has_error: false,
error_message: ""
},
state: {
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" }
]
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();
}
@@ -61,7 +240,7 @@ function connect() {
}
};
conn.onopen = function(evt) {
conn.onopen = function() {
timeout = timeoutMin;
app.error.has_error = false;
};
@@ -69,6 +248,9 @@ function connect() {
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;
@@ -88,3 +270,11 @@ function getURI() {
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(/^\/+/, "");
}