commit 444a385c4cb118568fd5d808dce07a6d46e4ad9d
parent e17a1a3f32d4f36e93fc78be56d00b5f6dcc0b2e
Author: Milan Pässler <me@pbb.lc>
Date: Mon, 20 May 2019 00:20:55 +0200
parent e17a1a3f32d4f36e93fc78be56d00b5f6dcc0b2e
Author: Milan Pässler <me@pbb.lc>
Date: Mon, 20 May 2019 00:20:55 +0200
add menu and power meter
7 files changed, 435 insertions(+), 22 deletions(-)
M
|
191
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
diff --git a/package.json b/package.json @@ -9,6 +9,10 @@ "dependencies": { "@authentic/mwc-card": "^0.5.0", "@authentic/mwc-circular-progress": "^0.5.0", + "@authentic/mwc-drawer": "^0.5.0", + "@authentic/mwc-icon": "^0.5.0", + "@authentic/mwc-icon-button": "^0.5.0", + "@authentic/mwc-ripple": "^0.5.0", "@authentic/mwc-switch": "^0.5.0", "@authentic/mwc-top-app-bar": "^0.5.0", "lit-element": "^2.1.0"
diff --git a/src/index.js b/src/index.js @@ -2,15 +2,18 @@ import { LitElement, html, css } from "lit-element"; import "@authentic/mwc-top-app-bar"; import "@authentic/mwc-card"; +import "@authentic/mwc-icon-button"; +import "@authentic/mwc-icon"; +import "@authentic/mwc-drawer"; import "./lights.js"; import "./power-meter.js"; import "./settings.js"; const pages = { - "#/lights": { name: "Lights", content: html`<smarthome-lights></smarthome-lights>`, path: "/lights" }, - "#/powermeter": { name: "Power Meter", content: html`<smarthome-power-meter></smarthome-power-meter>` }, - "#/settings": { name: "Settings", content: html`<smarthome-settings></smarthome-settings>` }, + "#/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" }, + "#/settings": { name: "Settings", content: html`<smarthome-settings></smarthome-settings>`, icon: "settings" }, }; const notFoundPage = { name: "Sorry", content: html`<h3>The page you tried to access does not exist</h3>` }; @@ -23,21 +26,40 @@ class SmartHomeLayout extends LitElement { this._handleHashChange(); } - _handleHashChange() { + async _handleHashChange() { if (!window.location.hash) { this.activePage = defaultPage; } else { this.activePage = pages[window.location.hash] || notFoundPage; } - this.requestUpdate(); + await this.requestUpdate(); + const drawer = this.shadowRoot.querySelector("mwc-drawer"); + drawer.open = false; + } + + _handleNavEvent(evt) { + const drawer = this.shadowRoot.querySelector("mwc-drawer"); + drawer.open = !drawer.open; } render() { return html` - <mwc-top-app-bar type="fixed"> + <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> + <mwc-ripple></mwc-ripple> + </a> + `)} + </div> + </mwc-drawer> + <mwc-top-app-bar type="fixed" @MDCTopAppBar:nav="${this._handleNavEvent}"> <div id="title-container" slot="title"> <span>${this.activePage.name}</span> </div> + <mwc-icon-button slot="navigationIcon" icon="menu"></mwc-icon-button> </mwc-top-app-bar> <div class="top-app-bar-adjust">${this.activePage.content}</div> `; @@ -51,7 +73,7 @@ class SmartHomeLayout extends LitElement { --mdc-theme-primary: #43a047; --mdc-theme-on-secondary: white; --mdc-theme-secondary: #616161; - background-color: #f5f5f5; + background-color: #ccc; } mwc-top-app-bar { position: fixed; @@ -67,9 +89,35 @@ class SmartHomeLayout extends LitElement { width: 100vw; padding-left: calc((100% - 450px) / 2 + 20px); } + mwc-icon-button { + position: absolute; + padding-left: calc(((100% - 450px) / 2) - 75px); + z-index: 10; + } + } + + #drawer-content { + display: flex; + flex-direction: column; + } + #drawer-content a { + text-decoration: none; + color: black; + display: flex; + flex-direction: row; + margin: 0; + align-items: center; + border-bottom: 1px solid #ccc; + } + #drawer-content a mwc-icon { + padding: .7em; + } + + #drawer-content a.active { + background-color: #ddd; } `; } } -customElements.define('smarthome-layout', SmartHomeLayout); +customElements.define("smarthome-layout", SmartHomeLayout);
diff --git a/src/lights.js b/src/lights.js @@ -2,6 +2,7 @@ import { LitElement, html, css } from "lit-element"; import { Switch } from "@authentic/mwc-switch"; import "@authentic/mwc-circular-progress"; +import "@authentic/mwc-ripple"; class Lights extends LitElement { constructor() { @@ -12,7 +13,7 @@ class Lights extends LitElement { } _initWebSocket() { - this.ws = new WebSocket("ws://192.168.1.1:8080/"); + this.ws = new WebSocket(`ws://${window.location.host}/relay/ws`); this.ws.onopen = async () => { this.connected = true; this.requestUpdate(); @@ -34,8 +35,12 @@ class Lights extends LitElement { } _clickHandler(evt) { - const sw = evt.composedPath().filter(el => el instanceof Switch)[0]; - this.ws.send(`set ${Number(sw.id)} ${!sw.checked ? "on" : "off"}`); + evt.preventDefault(); + const p = evt.composedPath().filter(el => el instanceof HTMLParagraphElement)[0]; + const sw = p.querySelector("mwc-switch"); + sw.checked = !sw.checked; + this.ws.send(`set ${Number(sw.id)} ${sw.checked ? "on" : "off"}`); + return true; } render() { @@ -43,9 +48,10 @@ class Lights extends LitElement { ${this.connected ? html` <div class="card"> ${this.switches.map(sw => html` - <p> + <p @click="${this._clickHandler}"> <span>${sw.name}</span> - <mwc-switch .id="${sw.id}" ?disabled="${!this.connected}" ?checked="${sw.value}" @click="${this._clickHandler}"></mwc-switch> + <mwc-ripple></mwc-ripple> + <mwc-switch .id="${sw.id}" ?disabled="${!this.connected}" ?checked="${sw.value}"></mwc-switch> </p> `)} </div> @@ -67,6 +73,7 @@ class Lights extends LitElement { --mdc-theme-primary: #43a047; --mdc-theme-on-secondary: white; --mdc-theme-secondary: #616161; + user-select: none; } p { display: flex; @@ -84,21 +91,24 @@ class Lights extends LitElement { } .card { background-color: #fff; - box-shadow: rgba(0, 0, 0, 0.2) 0px 2px 1px -1px, rgba(0, 0, 0, 0.14) 0px 1px 1px 0px, rgba(0, 0, 0, 0.12) 0px 1px 3px 0px; - border-radius: 4px; margin-left: auto; margin-right: auto; width: 100%; overflow: hidden; + border-top: 1px solid #ccc; + border-bottom: 1px solid #ccc; } @media (min-width: 600px) { .card { max-width: 450px; margin-top: 20px; + border: none; + border-radius: 4px; + box-shadow: 0 4px 5px 0 rgba(0,0,0,0.14), 0 1px 10px 0 rgba(0,0,0,0.12), 0 2px 4px -1px rgba(0,0,0,0.3); } } `; } } -customElements.define('smarthome-lights', Lights); +customElements.define("smarthome-lights", Lights);
diff --git a/src/power-meter.js b/src/power-meter.js @@ -0,0 +1,191 @@ +import { LitElement, html, css } from "lit-element"; + +import "@authentic/mwc-circular-progress"; +import "@authentic/mwc-icon"; + +const counters = [ + { id: 60, name: "Kueche" }, + { id: 50, name: "Sonstiges" }, +]; + +const round = (num, places) => { + const factor = Math.pow(10, places); + return Math.round(num * factor) / factor; +}; + +class PowerMeter extends LitElement { + constructor() { + super(...arguments); + this.loaded = false; + this.connected = false; + this._loadData(); + this._initWebSocket(); + } + + _initWebSocket() { + this.ws = new WebSocket(`ws://${window.location.host}/gosdm/ws`); + this.ws.onopen = async () => { + this.connected = true; + this.requestUpdate(); + }; + this.ws.onclose = () => { + this.connected = false; + this.requestUpdate(); + this._initWebSocket(); + }; + this.ws.onmessage = (msg) => { + clearInterval(this.timeout); + this.timeout = setTimeout(() => { + this.ws.onclose(); + }, 2000); + + const m = JSON.parse(msg.data); + if (!m.IEC61850) return; + + const d = this.data[m.DeviceId]; + + if (m.IEC61850 === "Frequency") { + d.Frequency = m.Value; + } else if (m.IEC61850 === "VoltageL1") { + d.Voltage.L1 = m.Value; + } else if (m.IEC61850 === "CosphiL1") { + d.Cosphi.L1 = m.Value; + } else if (m.IEC61850 === "PowerL1") { + d.Power.L1 = m.Value; + } else if (m.IEC61850 === "Import") { + d.TotalImport = m.Value; + } + + this.requestUpdate(); + }; + } + + async _loadData() { + const newData = {}; + await Promise.all(counters.map(async (counter) => { + const d = await fetch(`http://192.168.1.1:8070/last/${counter.id}`) + .then(resp => resp.json()); + + newData[d.ModbusDeviceId] = { + ...d, + name: counter.name, + }; + })); + + this.data = newData; + this.loaded = true; + this.requestUpdate(); + } + + render() { + return html` + ${this.loaded ? html` + <div id="connection-status"> + ${this.connected ? html` + <mwc-icon>cloud_queue</mwc-icon> Connected + ` : html` + <mwc-icon>cloud_off</mwc-icon> Disconnected + `} + </div> + <div class="card table"> + <div class="table-row table-head"> + <div class="table-column">Name</div> + <div class="table-column">Voltage</div> + <div class="table-column">Power</div> + <div class="table-column">Power Factor</div> + <div class="table-column">Frequency</div> + <div class="table-column">Import</div> + </div> + ${Object.entries(this.data).map(([_, d]) => html` + <div class="table-row"> + <div class="table-column">${d.name}</div> + <div class="table-column">${round(d.Voltage.L1, 2)} V</div> + <div class="table-column">${round(d.Power.L1, 2)} W</div> + <div class="table-column">${round(d.Cosphi.L1, 2)}</div> + <div class="table-column">${round(d.Frequency, 2)}</div> + <div class="table-column">${round(d.TotalImport, 2)} kWh</div> + </div> + `)} + </div> + ` : html` + <div id="loading"> + <h3>Trying to connect...</h3> + <mwc-circular-progress></mwc-circular-progress> + </div> + `} + `; + } + + 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; + } + #loading { + display: flex; + flex-direction: column; + align-items: center; + } + #connection-status mwc-icon { + padding: .5em; + } + #connection-status { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + } + .card { + background-color: #fff; + margin-left: auto; + margin-right: auto; + width: 100%; + overflow: hidden; + border-top: 1px solid #ccc; + border-bottom: 1px solid #ccc; + } + .table { + display: flex; + flex-direction: row; + } + .table-row { + display: flex; + flex-direction: column; + width: 100%; + } + .table-column { + padding: 1em; + height: 1em; + display: flex; + flex-direction: column; + justify-content: center; + } + + .table-column { + background-color: #ddd; + } + .table-column:nth-child(2n) { + background-color: #fff; + } + @media (min-width: 600px) { + .card { + max-width: 450px; + margin-top: 20px; + box-shadow: 0 4px 5px 0 rgba(0,0,0,0.14), 0 1px 10px 0 rgba(0,0,0,0.12), 0 2px 4px -1px rgba(0,0,0,0.3); + border: none; + border-radius: 4px; + } + #connection-status { + margin-top: 20px; + } + } + `; + } +} + +customElements.define("smarthome-power-meter", PowerMeter);
diff --git a/src/settings.js b/src/settings.js @@ -0,0 +1,65 @@ +import { LitElement, html, css } from "lit-element"; + +import "@authentic/mwc-ripple"; + +class Settings extends LitElement { + _forceUpdate(evt) { + } + + render() { + return html` + <div class="card"> + <a href="https://www.gnu.org/licenses/agpl-3.0-standalone.html"> + <span>License</span> + <mwc-ripple></mwc-ripple> + </a> + <p @click="${this._forceUpdate}"> + <span>Force update</span> + <mwc-ripple></mwc-ripple> + </p> + </div> + `; + } + + 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; + } + p, a { + text-decoration: none; + color: black; + display: flex; + flex-direction: row; + justify-content: space-between; + padding: 1em; + margin: 0; + height: 14px; + border-bottom: 1px solid #ccc; + } + .card { + background-color: #fff; + box-shadow: rgba(0, 0, 0, 0.2) 0px 2px 1px -1px, rgba(0, 0, 0, 0.14) 0px 1px 1px 0px, rgba(0, 0, 0, 0.12) 0px 1px 3px 0px; + border-radius: 4px; + margin-left: auto; + margin-right: auto; + width: 100%; + overflow: hidden; + } + @media (min-width: 600px) { + .card { + max-width: 450px; + margin-top: 20px; + } + } + `; + } +} + +customElements.define("smarthome-settings", Settings);
diff --git a/style.css b/style.css @@ -1,3 +0,0 @@ -body { - background: linear-gradient(#f4f4f4, #cfcfcf) fixed; -}
diff --git a/yarn.lock b/yarn.lock @@ -35,6 +35,26 @@ lit-element "^2.1.0" lit-html "^1.0.0" +"@authentic/mwc-drawer@^0.5.0": + version "0.5.0" + resolved "https://registry.yarnpkg.com/@authentic/mwc-drawer/-/mwc-drawer-0.5.0.tgz#e33598b150ec260cf48299ab03ef5a1bf7f8d7da" + integrity sha512-0fUtRno1phJimvE+9Y2unY2kS/bbfUgKNvEVemDHgaz7JvGHRddqyS0JnTCT8A0AqRFfuovlXzzjmuUDzO+uGw== + dependencies: + "@authentic/mwc-base" "^0.5.0" + "@material/drawer" "^2.0.0" + blocking-elements "^0.0.2" + wicg-inert "^1.1.6" + +"@authentic/mwc-icon-button@^0.5.0": + version "0.5.0" + resolved "https://registry.yarnpkg.com/@authentic/mwc-icon-button/-/mwc-icon-button-0.5.0.tgz#9ba5056ea7b256f783773a8000a0226713fcc2c3" + integrity sha512-UbjEWER/AX3FNBbJwVTdpH0pvW7INjdGJC8nN37hbl6I9wYU53IW8K05BtTzlc6SOP1cOEYr9B5u+6Qxw1TAtg== + dependencies: + "@authentic/mwc-base" "^0.5.0" + "@authentic/mwc-icon" "^0.5.0" + "@authentic/mwc-ripple" "^0.5.0" + "@material/icon-button" "^2.0.0" + "@authentic/mwc-icon@^0.5.0": version "0.5.0" resolved "https://registry.yarnpkg.com/@authentic/mwc-icon/-/mwc-icon-0.5.0.tgz#ab443a880f53a2b4258c2b4432d3fac4970ea8fb" @@ -125,6 +145,23 @@ dependencies: tslib "^1.9.3" +"@material/drawer@^2.0.0": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@material/drawer/-/drawer-2.1.1.tgz#ccdc3404bba60f3f72b153d04f9a8967c06a7df6" + integrity sha512-z0q2Kwb79ZMng4Kb7u2UgIxQJPNLcTq/Bj/J7EJlTlUPSMXe4N/geAhSFHyGBG/iXQupdQw5E2Wu7Fo2sfx7sg== + dependencies: + "@material/animation" "^1.0.0" + "@material/base" "^1.0.0" + "@material/elevation" "^1.1.0" + "@material/list" "^2.1.1" + "@material/ripple" "^2.1.1" + "@material/rtl" "^0.42.0" + "@material/shape" "^1.1.1" + "@material/theme" "^1.1.0" + "@material/typography" "^1.0.0" + focus-trap "^5.0.0" + tslib "^1.9.3" + "@material/elevation@^1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@material/elevation/-/elevation-1.1.0.tgz#def23c360ae067b43c1632a331b9883b9f679cc5" @@ -139,6 +176,32 @@ resolved "https://registry.yarnpkg.com/@material/feature-targeting/-/feature-targeting-0.44.1.tgz#afafc80294e5efab94bee31a187273d43d34979a" integrity sha512-90cc7njn4aHbH9UxY8qgZth1W5JgOgcEdWdubH1t7sFkwqFxS5g3zgxSBt46TygFBVIXNZNq35Xmg80wgqO7Pg== +"@material/icon-button@^2.0.0": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@material/icon-button/-/icon-button-2.1.1.tgz#574842abc808da774e77c097dfca2d7e74ec23f5" + integrity sha512-WUp06uqG458bWCp66hyutNvwiuptbE7vuOtVukG/DmxoGWLDXSRStcUzOXYYElZsmIR14SPOTD4694CaaqHz9w== + dependencies: + "@material/base" "^1.0.0" + "@material/feature-targeting" "^0.44.1" + "@material/ripple" "^2.1.1" + "@material/theme" "^1.1.0" + tslib "^1.9.3" + +"@material/list@^2.1.1": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@material/list/-/list-2.1.1.tgz#fac8fc886d72c33ffb0f91abbc2483570cda4674" + integrity sha512-yZFNLQ6+nTGTJsVuqbYh9gSC+aUh3hC/0aJZzAjVCCY0qSi1vWDHcYdgmgDr4THznlcPr9TxAi6IBiJ/fHXjZg== + dependencies: + "@material/base" "^1.0.0" + "@material/dom" "^1.1.0" + "@material/feature-targeting" "^0.44.1" + "@material/ripple" "^2.1.1" + "@material/rtl" "^0.42.0" + "@material/shape" "^1.1.1" + "@material/theme" "^1.1.0" + "@material/typography" "^1.0.0" + tslib "^1.9.3" + "@material/ripple@^2.0.0", "@material/ripple@^2.1.1": version "2.1.1" resolved "https://registry.yarnpkg.com/@material/ripple/-/ripple-2.1.1.tgz#850e44bafe9db962f400c7e067eb28c1cb11010b" @@ -279,6 +342,11 @@ base@^0.11.1: mixin-deep "^1.2.0" pascalcase "^0.1.1" +blocking-elements@^0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/blocking-elements/-/blocking-elements-0.0.2.tgz#fd1fb73c090415039e7ad497879decc7c54f20bf" + integrity sha512-sMYXYkCAAV4hBrKGZ8ylp761A02uRDjpueW23W43/YvYR6gFD/Z7cIHWvJbSUDmnpzws9VQxSMHdm4/UbL4PKg== + braces@^2.3.1: version "2.3.2" resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729" @@ -413,6 +481,11 @@ define-property@^2.0.2: is-descriptor "^1.0.2" isobject "^3.0.1" +dom-matches@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/dom-matches/-/dom-matches-2.0.0.tgz#d2728b416a87533980eb089b848d253cf23a758c" + integrity sha1-0nKLQWqHUzmA6wibhI0lPPI6dYw= + escape-string-regexp@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" @@ -480,6 +553,14 @@ fill-range@^4.0.0: repeat-string "^1.6.1" to-regex-range "^2.1.0" +focus-trap@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/focus-trap/-/focus-trap-5.0.1.tgz#285f9df2cd9f5ef82dd1abb5d8a70e66cd4f99e3" + integrity sha512-vU7zEdL3y+kfkuwBbT9456JH8QfyemdcdZ2gKMfmgLyAs9NQAkSVQBSZmb9nlb1cVMo+iCsddqeGJog00pd2EQ== + dependencies: + tabbable "^4.0.0" + xtend "^4.0.1" + for-in@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" @@ -880,9 +961,9 @@ rollup-pluginutils@^2.7.0: micromatch "^3.1.10" rollup@^1.12.2: - version "1.12.2" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-1.12.2.tgz#a7c34a4bef71feb43e3ae69f0b26ae683e75db44" - integrity sha512-ePehZfVMIE4eO0/LV6VaMY8kp0D9sbziUabpBeJbHAHa2WJPxuS0lYLmiLamb02e098RIRyq1F2yjM4O08dQVA== + version "1.12.3" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-1.12.3.tgz#068b1957d5bebf6c0a758cfe42609b512add35a9" + integrity sha512-ueWhPijWN+GaPgD3l77hXih/gcDXmYph6sWeQegwBYtaqAE834e8u+MC2wT6FKIUsz1DBOyOXAQXUZB+rjWDoQ== dependencies: "@types/estree" "0.0.39" "@types/node" "^12.0.2" @@ -1025,6 +1106,11 @@ supports-color@^6.1.0: dependencies: has-flag "^3.0.0" +tabbable@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/tabbable/-/tabbable-4.0.0.tgz#5bff1d1135df1482cf0f0206434f15eadbeb9261" + integrity sha512-H1XoH1URcBOa/rZZWxLxHCtOdVUEev+9vo5YdYhC9tCY4wnybX+VQrCYuy9ubkg69fCBxCONJOSLGfw0DWMffQ== + terser@^3.14.1: version "3.17.0" resolved "https://registry.yarnpkg.com/terser/-/terser-3.17.0.tgz#f88ffbeda0deb5637f9d24b0da66f4e15ab10cb2" @@ -1096,3 +1182,15 @@ util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= + +wicg-inert@^1.1.6: + version "1.1.6" + resolved "https://registry.yarnpkg.com/wicg-inert/-/wicg-inert-1.1.6.tgz#1d7703bc2f84acc0ea4de01a4c8a5cfcc1fd8a8a" + integrity sha512-svnNP2bUZc1luu0erL2Y25Iyxsm0SUk9wNq3FbgTgxcrqG3YAZBPYonRNRGgpveeEqRAnNE5yNcIdEd/F86tbw== + dependencies: + dom-matches "^2.0.0" + +xtend@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" + integrity sha1-pcbVMr5lbiPbgg77lDofBJmNY68=