commit 735b9127def9bb697b21df80398357c73e6ac75a
parent e2c8b96c06e3757757f83494352935ca7def4e1f
Author: Milan Pässler <me@pbb.lc>
Date: Tue, 16 Jul 2019 14:12:54 +0200
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(-)
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);