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(/^\/+/, ""); }