ctucx.git: smartie-pwa

[js] smarthome web-gui

commit 735b9127def9bb697b21df80398357c73e6ac75a
parent e2c8b96c06e3757757f83494352935ca7def4e1f
Author: Milan Pässler <me@pbb.lc>
Date: Tue, 16 Jul 2019 14:12:54 +0200

adapt to smartied
7 files changed, 163 insertions(+), 128 deletions(-)
M
src/layout.js
|
42
+++++++++++++++++++++++-------------------
D
src/lights.js
|
77
-----------------------------------------------------------------------------
M
src/power-meter/history.js
|
25
++++++++++++++++---------
M
src/power-meter/index.js
|
12
+++++++++---
M
src/power-meter/live.js
|
30
++++++++++++++++++------------
M
src/state.js
|
28
++++++++++++++++++++--------
A
src/switches.js
|
77
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
diff --git a/src/layout.js b/src/layout.js
@@ -1,6 +1,7 @@
 "use strict";
 
 import { LitElement, html, css } from "lit-element";
+import { state } from "./state.js";
 
 import "@authentic/mwc-top-app-bar";
 import "@authentic/mwc-card";

@@ -8,39 +9,42 @@ import "@authentic/mwc-icon-button";
 import "@authentic/mwc-icon";
 import "@authentic/mwc-drawer";
 
-import "./lights.js";
+import "./switches.js";
 import "./power-meter/index.js";
 import "./departures.js";
 import "./settings.js";
 import "./spinner.js";
 import "./row.js";
 
-const netdataRedirect = document.createElement("script");
-netdataRedirect.innerHTML = "window.location = `http://${window.location.host}/netdata/`;";
+const createRedirect = (id) => {
+	const config = state.config.views.filter(view => view.url == id);
+	const redirect = document.createElement("script");
+  redirect.innerHTML = `window.location = "${config.destination}";`;
+};
 
-const pages = {
-	"#/lights": { name: "Lights", content: html`<smarthome-lights></smarthome-lights>`, icon: "lightbulb" },
-	"#/powermeter": { name: "Power Meter", content: html`<smarthome-power-meter></smarthome-power-meter>`, icon: "power" },
-	"#/departures": { name: "Departures", content: html`<smarthome-departures></smarthome-departures>`, icon: "departure_board" },
-	"#/netdata": { name: "netdata", content: html`<smarthome-spinner>Redirecting...</smarthome-spinner> ${netdataRedirect}`, icon: "show_chart" },
-	"#/settings": { name: "Settings", content: html`<smarthome-settings></smarthome-settings>`, icon: "settings" },
+const viewTypes = {
+	"switches": id => html`<smarthome-switches .viewid="${id}"></smarthome-switches>`,
+	"powermeter": id => html`<smarthome-power-meter .viewid="${id}"></smarthome-power-meter>`, icon: "power" ,
+	"departures": id => html`<smarthome-departures .viewid="${id}"></smarthome-departures>`,
+	"redirect": id => html`<smarthome-spinner .viewid="${id}">Redirecting...</smarthome-spinner> ${createRedirect(id)}`,
+	"settings": id => html`<smarthome-settings></smarthome-settings>`,
 };
 
-const notFoundPage = { name: "Sorry", content: html`<h3>The page you tried to access does not exist</h3>` };
-const defaultPage = pages["#/lights"];
+const notFoundView = { name: "Sorry", content: html`<h3>The page you tried to access does not exist</h3>` };
 
 class SmartHomeLayout extends LitElement {
 	constructor() {
 		super(...arguments);
 		window.addEventListener("hashchange", this._handleHashChange.bind(this));
 		this._handleHashChange();
+		state.subscribe(this.requestUpdate.bind(this));
 	}
 
 	async _handleHashChange() {
 		if (!window.location.hash) {
-			this.activePage = defaultPage;
+			this.activeView = state.config.views[0];
 		} else {
-			this.activePage = pages[window.location.hash] || notFoundPage;
+			this.activeView = state.config.views.filter(view => `#/${view.url}` == window.location.hash)[0] || notFoundView;
 		}
 		await this.requestUpdate();
 		const drawer = this.shadowRoot.querySelector("mwc-drawer");

@@ -57,10 +61,10 @@ class SmartHomeLayout extends LitElement {
 		return html`
 			<mwc-drawer type="modal">
 				<div id="drawer-content">
-					${Object.entries(pages).map(([path, page]) => html`
-						<a href="${path}" class="${this.activePage === page ? "active" : "inactive"}">
-							<mwc-icon>${page.icon}</mwc-icon>
-							<span>${page.name}</span>
+					${state.config.views.map(view => html`
+						<a href="#/${view.url}" class="${this.activeView === view ? "active" : "inactive"}">
+							<mwc-icon>${view.icon}</mwc-icon>
+							<span>${view.name}</span>
 							<mwc-ripple></mwc-ripple>
 						</a>
 					`)}

@@ -68,12 +72,12 @@ class SmartHomeLayout extends LitElement {
 			</mwc-drawer>
 			<mwc-top-app-bar type="fixed" @MDCTopAppBar:nav="${this._handleNavEvent}">
 				<div id="title-container" slot="title">
-					<span>${this.activePage.name}</span>
+					<span>${this.activeView.name}</span>
 				</div>
 				<mwc-icon-button id="menu-button" slot="navigationIcon" icon="menu"></mwc-icon-button>
 			</mwc-top-app-bar>
 			<div class="top-app-bar-adjust"></div>
-			${this.activePage.content}
+			${viewTypes[this.activeView.type](this.activeView.url)}
 		`;
 	}
 
diff --git a/src/lights.js b/src/lights.js
@@ -1,77 +0,0 @@
-"use strict";
-
-import { LitElement, html, css } from "lit-element";
-import { state } from "./state.js";
-
-import { Switch } from "@authentic/mwc-switch";
-import "@authentic/mwc-circular-progress";
-import "@authentic/mwc-ripple";
-import "./card.js";
-import "./spinner.js";
-import { Row } from "./row.js";
-
-class Lights extends LitElement {
-	constructor() {
-		super(...arguments);
-		state.subscribe(this.requestUpdate.bind(this));
-	}
-
-	_clickHandler(evt) {
-		evt.preventDefault();
-		const row = evt.composedPath().filter(el => el instanceof Row)[0];
-		if (row.attributes.disabled) return;
-		const sw = row.querySelector("mwc-switch");
-		sw.checked = !sw.checked;
-		state.ws.send(JSON.stringify({
-			type: "SetRelayAction",
-			relay_board: sw.relayboard,
-			relay: sw.relay,
-			value: sw.checked
-		}));
-		if (evt.clientX !== 0 || evt.clientY !== 0) sw.blur();
-		return true;
-	}
-
-	render() {
-		console.log(state);
-		return html`
-			${state.data.devices ? html`
-				${state.data.devices.filter(d => d.type == "RelayBoard").map((device, relayboard) => html`
-					<smarthome-card>
-						${device.state.relays.map((value, relay) => html`
-							<smarthome-row @click="${this._clickHandler}" ?disabled="${!state.connected}">
-								<span slot="left">${device.config.names[relay]}</span>
-								<mwc-ripple slot="center"></mwc-ripple>
-								<mwc-switch slot="right" .relayboard="${relayboard}" .relay="${relay}" ?disabled="${!state.connected}" ?checked="${value}"></mwc-switch>
-							</smarthome-row>
-						`)}
-					</smarthome-card>
-				`)}
-			` : html`
-				<smarthome-spinner>Trying to connect...</smarthome-spinner>
-			`}
-		`;
-	}
-
-	static get styles() {
-		return css`
-			:host {
-				display: flex;
-				flex-direction: column;
-				--mdc-theme-on-primary: white;
-				--mdc-theme-primary: #43a047;
-				--mdc-theme-on-secondary: white;
-				--mdc-theme-secondary: #616161;
-				user-select: none;
-			}
-			smarthome-row[disabled] {
-				color: #999;
-			}
-			smarthome-row[disabled] mwc-ripple {
-				display: none;
-			}
-		`;
-	}
-}
-
-customElements.define("smarthome-lights", Lights);
diff --git a/src/power-meter/history.js b/src/power-meter/history.js
@@ -51,6 +51,7 @@ class PowerMeterHistory extends LitElement {
 	}
 
 	render() {
+		const config = state.config.views.filter(view => view.url == this.viewid)[0];
 		const now = new Date();
 
 		this.data = {};

@@ -60,14 +61,14 @@ class PowerMeterHistory extends LitElement {
 		archive.load("metadata");
 		const years = get(archive.data, [ "metadata", "availableData" ], {});
 		const months = get(archive.data, [ "metadata", "availableData", this.year ], []);
-		const counters = get(archive.data, [ "metadata", "meters" ], {});
 
-		for (let [id, p] of Object.entries(counters)) {
-			archive.load(`${this.type}/${id}_${this.sel}`);
-			const data = get(archive.data, [ `${this.type}/${id}_${this.sel}` ], {});
+		console.log(config)
+		for (let d of config.meters) {
+			archive.load(`${this.type}/${d.device}_${this.sel}`);
+			const data = get(archive.data, [ `${this.type}/${d.device}_${this.sel}` ], {});
 			for (let day of Object.keys(data)) {
 				if (!this.data[day]) this.data[day] = {};
-				this.data[day][id] = data[day].imported;
+				this.data[day][d.device] = data[day].imported;
 			}
 		}
 

@@ -106,11 +107,11 @@ class PowerMeterHistory extends LitElement {
 								<div class="table-column">${formatName(this.type, day)}</table-column>
 							`)}
 						</div>
-						${Object.entries(counters).map(([id, name]) => html`
+						${config.meters.map(d => html`
 							<div class="table-row">
-								<div class="table-column">${name}</div>
-								${Object.entries(this.data).reverse().map(([day, d]) => html`
-									<div class="table-column">${d[id] ? round(d[id], 2) + " kWh" : "-"}</div>
+								<div class="table-column">${d.name}</div>
+								${Object.entries(this.data).reverse().map(([day, data]) => html`
+									<div class="table-column">${data[d.device] ? round(data[d.device], 2) + " kWh" : "-"}</div>
 								`)}
 							</div>
 						`)}

@@ -184,6 +185,12 @@ class PowerMeterHistory extends LitElement {
 			}
 		`;
 	}
+
+	static get properties() {
+		return {
+			viewid: { type: String }
+		};
+	}
 }
 
 customElements.define("smarthome-power-meter-history", PowerMeterHistory);
diff --git a/src/power-meter/index.js b/src/power-meter/index.js
@@ -6,8 +6,8 @@ import "./history.js";
 import "./live.js";
 
 const pages = {
-	"live": { name: "Live", content: html`<smarthome-power-meter-live></smarthome-power-meter-live>` },
-	"history": { name: "History", content: html`<smarthome-power-meter-history></smarthome-power-meter-history>` },
+	"live": { name: "Live", content: id => html`<smarthome-power-meter-live .viewid="${id}"></smarthome-power-meter-live>` },
+	"history": { name: "History", content: id => html`<smarthome-power-meter-history .viewid="${id}"></smarthome-power-meter-history>` },
 };
 
 class PowerMeter extends LitElement {

@@ -32,7 +32,7 @@ class PowerMeter extends LitElement {
 					</a>
 				`)}
 			</div>
-			${this.activePage.content}
+			${this.activePage.content(this.viewid)}
 		`;
 	}
 

@@ -63,6 +63,12 @@ class PowerMeter extends LitElement {
 			}
 		`;
 	}
+
+	static get properties() {
+		return {
+			viewid: { type: String }
+		};
+	}
 }
 
 customElements.define("smarthome-power-meter", PowerMeter);
diff --git a/src/power-meter/live.js b/src/power-meter/live.js
@@ -18,21 +18,21 @@ class PowerMeterLive extends LitElement {
 	}
 
 	getImport(d) {
-		const currentValue = d.state.import;
-		const modbusid = d.config.address;
+		const currentValue = state.data[d.device].import;
 		const now = new Date();
 		console.log(d);
-		const filename = `day/${modbusid}_${now.getFullYear()}_${pad(now.getMonth() + 1, 2)}`;
+		const filename = `day/${d.device}_${now.getFullYear()}_${pad(now.getMonth() + 1, 2)}`;
 		if (this._cachedDayArchive[filename] !== now.getDate()) delete archive.data[filename];
-		archive.load(`day/${modbusid}_${now.getFullYear()}_${pad(now.getMonth() + 1, 2)}`);
+		archive.load(`day/${d.device}_${now.getFullYear()}_${pad(now.getMonth() + 1, 2)}`);
 		this._cachedDayArchive[filename] = now.getDate();
 		const lastValue = get(archive.data, [filename, `${now.getFullYear()}${pad(now.getMonth() + 1, 2)}${pad(now.getDate() - 1, 2)}`, "totalImported" ]);
 		return currentValue && lastValue ? round(currentValue - lastValue, 2) + " kWh" : "-";
 	}
 
 	render() {
+		const config = state.config.views.filter(view => view.url == this.viewid)[0];
 		return html`
-			${state.data.devices ? html`
+			${state.data[config.meters[0].device] ? html`
 				<div id="connection-status">
 					${state.connected ? html`
 						<p><mwc-icon>cloud_queue</mwc-icon> Connected</p>

@@ -52,14 +52,14 @@ class PowerMeterLive extends LitElement {
 							<div class="table-column">Total Import</div>
 							<div class="table-column">Import</div>
 						</div>
-			      ${state.data.devices.filter(d => d.type == "PowerMeter").map(d => html`
+			      ${config.meters.map(d => html`
 							<div class="table-row">
-								<div class="table-column">${d.config.name}</div>
-								<div class="table-column">${round(d.state.voltage, 2)} V</div>
-								<div class="table-column">${round(d.state.power, 2)} W</div>
-								<div class="table-column">${round(d.state.cosphi, 2)}</div>
-								<div class="table-column">${round(d.state.frequency, 2)}</div>
-								<div class="table-column">${round(d.state.import, 2)} kWh</div>
+								<div class="table-column">${d.name}</div>
+								<div class="table-column">${round(state.data[d.device].voltage, 2)} V</div>
+								<div class="table-column">${round(state.data[d.device].power, 2)} W</div>
+								<div class="table-column">${round(state.data[d.device].cosphi, 2)}</div>
+								<div class="table-column">${round(state.data[d.device].frequency, 2)}</div>
+								<div class="table-column">${round(state.data[d.device].import, 2)} kWh</div>
 								<div class="table-column">${this.getImport(d)}</div>
 							</div>
 						`)}

@@ -129,6 +129,12 @@ class PowerMeterLive extends LitElement {
 			}
 		`;
 	}
+
+	static get properties() {
+		return {
+			viewid: { type: String }
+		};
+	}
 }
 
 customElements.define("smarthome-power-meter-live", PowerMeterLive);
diff --git a/src/state.js b/src/state.js
@@ -4,6 +4,7 @@ class State {
 	constructor() {
 		this.connected = false;
 		this.data = JSON.parse(localStorage.getItem("data") || "{}");
+		this.config = JSON.parse(localStorage.getItem("config") || "{}");
 		this.data.lastUpdated = new Date(this.data.lastUpdated);
 		this._subscribers = [];
 		this._initWS();

@@ -15,11 +16,18 @@ class State {
 	}
 
 	_setData(newData) {
-		if (newData.Ok) newData = newData.Ok;
-		if (!newData.devices) return;
-		this.data = newData;
-		this.data.lastUpdated = new Date();
-		localStorage.setItem("data", JSON.stringify(this.data));
+		if (newData.data && newData.data.views) {
+			console.log("received config: ", newData);
+			this.config = newData.data;
+			localStorage.setItem("config", JSON.stringify(this.config));
+		} else if (!newData.status) {
+			this.data = newData;
+			this.data.lastUpdated = new Date();
+			localStorage.setItem("data", JSON.stringify(this.data));
+		} else {
+			console.log("received response: ", newData);
+			return;
+		}
 		this._updateSubscribers();
 	}
 

@@ -30,7 +38,7 @@ class State {
 	}
 
 	_initWS() {
-		this.ws = new WebSocket("ws://192.168.1.1:3002/");
+		this.ws = new WebSocket(`ws://${window.location.hostname}/ws`);
 		this.ws.onclose = () => {
 			this.connected = false;
 			this._initWS();

@@ -39,13 +47,17 @@ class State {
 		this.ws.onopen = () => {
 			this.connected = true;
 			this._updateSubscribers();
+			this.ws.send(JSON.stringify({
+				type: "GetClientConfigAction",
+				configName: "smarthome-pwa"
+			}));
 		};
 		this.ws.onmessage = (msg) => {
-			clearInterval(this._timeout);
+			/*clearInterval(this._timeout);
 			this._timeout = setTimeout(() => {
 				this.ws.close();
 				this.ws.onclose();
-			}, 2000);
+			}, 2000);*/
 			if (!msg.data.length) return; // keepalive
 			this._setData(JSON.parse(msg.data));
 		};
diff --git a/src/switches.js b/src/switches.js
@@ -0,0 +1,77 @@
+"use strict";
+
+import { LitElement, html, css } from "lit-element";
+import { state } from "./state.js";
+
+import { Switch } from "@authentic/mwc-switch";
+import "@authentic/mwc-circular-progress";
+import "@authentic/mwc-ripple";
+import "./card.js";
+import "./spinner.js";
+import { Row } from "./row.js";
+
+class Switches extends LitElement {
+	constructor() {
+		super(...arguments);
+		state.subscribe(this.requestUpdate.bind(this));
+	}
+
+	_clickHandler(evt) {
+		evt.preventDefault();
+		const row = evt.composedPath().filter(el => el instanceof Row)[0];
+		if (row.attributes.disabled) return;
+		const sw = row.querySelector("mwc-switch");
+		sw.checked = !sw.checked;
+		state.ws.send(JSON.stringify({
+			type: "SetRelayAction",
+			setRelayBoard: sw.relayboard,
+			setRelay: sw.relay,
+			setValue: sw.checked
+		}));
+		if (evt.clientX !== 0 || evt.clientY !== 0) sw.blur();
+		return true;
+	}
+
+	render() {
+		const config = state.config.views.filter(view => view.url == this.viewid)[0];
+		return html`
+			<smarthome-card>
+			  ${config.switches.map(sw => html`
+					<smarthome-row @click="${this._clickHandler}" ?disabled="${!state.connected}">
+						<span slot="left">${sw.name}</span>
+						<mwc-ripple slot="center"></mwc-ripple>
+					  <mwc-switch slot="right" .relayboard="${sw.device}" .relay="${sw.relay}" ?disabled="${!state.connected}" ?checked="${state.data[sw.device].relays[sw.relay]}"></mwc-switch>
+					</smarthome-row>
+				`)}
+			</smarthome-card>
+		`;
+	}
+
+	static get styles() {
+		return css`
+			:host {
+				display: flex;
+				flex-direction: column;
+				--mdc-theme-on-primary: white;
+				--mdc-theme-primary: #43a047;
+				--mdc-theme-on-secondary: white;
+				--mdc-theme-secondary: #616161;
+				user-select: none;
+			}
+			smarthome-row[disabled] {
+				color: #999;
+			}
+			smarthome-row[disabled] mwc-ripple {
+				display: none;
+			}
+		`;
+	}
+
+	static get properties() {
+		return {
+			viewid: { type: String }
+		};
+	}
+}
+
+customElements.define("smarthome-switches", Switches);