commit fa14ce28daa297884371c213ae10e67e31d9a736
parent 3d0277f80c9b4d74d36621b6de79c65f2d1065bc
Author: Milan Pässler <me@pbb.lc>
Date: Sun, 21 Jul 2019 22:14:01 +0200
parent 3d0277f80c9b4d74d36621b6de79c65f2d1065bc
Author: Milan Pässler <me@pbb.lc>
Date: Sun, 21 Jul 2019 22:14:01 +0200
add temperature page
6 files changed, 422 insertions(+), 9 deletions(-)
A
|
196
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
|
74
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
|
132
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
diff --git a/src/layout.js b/src/layout.js @@ -11,20 +11,23 @@ import "@authentic/mwc-drawer"; import "./switches.js"; import "./power-meter/index.js"; +import "./temperature/index.js"; import "./departures.js"; import "./settings.js"; import "./spinner.js"; import "./row.js"; const createRedirect = (id) => { - const config = state.config.views.filter(view => view.url == id)[0]; + const config = state.config.views.filter(view => view.url == id)[0] || state.config.views[0]; const redirect = document.createElement("script"); - redirect.innerHTML = `window.location = "${config.destination}";`; + redirect.innerHTML = `window.location.href = "${config.destination}"; console.log(window.location.href)`; }; const viewTypes = { + "noconfig": id => html`<smarthome-spinner>Waiting for initial connection...</smarthome-spinner>`, "switches": id => html`<smarthome-switches .viewid="${id}"></smarthome-switches>`, "powermeter": id => html`<smarthome-power-meter .viewid="${id}"></smarthome-power-meter>`, icon: "power" , + "temperature": id => html`<smarthome-temperature .viewid="${id}"></smarthome-temperature>`, icon: "thermometer" , "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>`, @@ -41,11 +44,7 @@ class SmartHomeLayout extends LitElement { } async _handleHashChange() { - if (!window.location.hash) { - this.activeView = state.config.views[0]; - } else { - this.activeView = state.config.views.filter(view => `#/${view.url}` == window.location.hash)[0] || notFoundView; - } + this.activeView = state.config.views.filter(view => `#/${view.url}` == window.location.hash)[0] || state.config.views[0]; await this.requestUpdate(); const drawer = this.shadowRoot.querySelector("mwc-drawer"); drawer.open = false; @@ -58,6 +57,9 @@ class SmartHomeLayout extends LitElement { } render() { + if (this.activeView.type === "noconfig") { + window.location.hash = `#/${state.config.views[0].url}`; + } return html` <mwc-drawer type="modal"> <div id="drawer-content">
diff --git a/src/power-meter/live.js b/src/power-meter/live.js @@ -52,7 +52,7 @@ class PowerMeterLive extends LitElement { <div class="table-column">Total Import</div> <div class="table-column">Import</div> </div> - ${config.meters.map(d => html` + ${config.meters.map(d => html` <div class="table-row"> <div class="table-column">${d.name}</div> <div class="table-column">${round(state.data[d.device].voltage, 2)} V</div>
diff --git a/src/state.js b/src/state.js @@ -4,7 +4,16 @@ class State { constructor() { this.connected = false; this.data = JSON.parse(localStorage.getItem("data") || "{}"); - this.config = JSON.parse(localStorage.getItem("config") || "{}"); + this.config = JSON.parse(localStorage.getItem("config") || JSON.stringify({ + views: [ + { + type: "noconfig", + url: "noconfig", + name: "Start", + icon: "settings" + } + ] + })); this.data.lastUpdated = new Date(this.data.lastUpdated); this._subscribers = []; this._initWS();
diff --git a/src/temperature/history.js b/src/temperature/history.js @@ -0,0 +1,196 @@ +"use strict"; + +import { LitElement, html, css } from "lit-element"; +import { state } from "../state.js"; +import { pad, round, weekdays, months, get } from "../util.js"; +import { archive } from "../archive.js"; + +import "@authentic/mwc-icon"; +import "@authentic/mwc-select"; +import "@authentic/mwc-menu"; +import "@authentic/mwc-list"; +import "../card.js"; +import "../spinner.js"; + +const formatName = (type, name) => { + if (type == "day") { + const date = new Date(`${name.substr(0, 4)}-${name.substr(4, 2)}-${name.substr(6, 2)}`); + return `${weekdays[date.getDay()]} ${date.getDate()}th`; + } else if (type == "week") { + let res = ""; + const date = new Date(name.substr(0, 4), 0, 1 + 7 * (Number(name.substr(4, 2)) - 1)); + date.setDate(date.getDate() + 1 - date.getDay()); + res += `${date.getDate()}.${date.getMonth()+1}.`; + date.setDate(date.getDate() + 7 - date.getDay()); + res += ` - ${date.getDate()}.${date.getMonth()+1}.`; + return res; + } else if (type == "month") { + const date = new Date(`${name.substr(0, 4)}-${name.substr(4, 2)}-01`); + return months[date.getMonth()]; + } +}; + +class TemperatureHistory extends LitElement { + constructor() { + super(...arguments); + this.type = "day"; + const now = new Date(); + this.year = now.getFullYear(); + this.month = pad(now.getMonth() + 1, 2); + this.data = {}; + + state.subscribe(this.requestUpdate.bind(this)); + archive.subscribe(this.requestUpdate.bind(this)); + } + + handleSelected(field) { + return (evt) => { + this[field] = evt.detail.item.value; + this.requestUpdate(); + }; + } + + render() { + const config = state.config.views.filter(view => view.url == this.viewid)[0]; + const now = new Date(); + + this.data = {}; + this.sel = this.year; + if (this.type === "day") this.sel += "_" + this.month; + + archive.load("metadata"); + const years = get(archive.data, [ "metadata", "availableData" ], {}); + const months = get(archive.data, [ "metadata", "availableData", this.year ], []); + + 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][d.device] = data[day].imported; + } + } + + return html` + <div id="selection" class="${Object.keys(years).length > 0 ? "" : "hidden"}"> + <mwc-select label="Type"> + <mwc-menu slot="menu" class="type-sel" @MDCMenu:selected=${this.handleSelected("type")}> + <mwc-list> + <mwc-list-item value="day">Days</mwc-list-item> + <mwc-list-item value="week">Weeks</mwc-list-item> + <mwc-list-item value="month">Months</mwc-list-item> + </mwc-list> + </mwc-menu> + </mwc-select> + <mwc-select label="Year"> + <mwc-menu slot="menu" class="year-sel" @MDCMenu:selected=${this.handleSelected("year")}> + <mwc-list> + ${Object.keys(years).map(m => html`<mwc-list-item value="${m}">${m}</mwc-list-item>`)} + </mwc-list> + </mwc-menu> + </mwc-select> + <mwc-select label="Month" class="${this.type == "day" ? "" : "hidden"}"> + <mwc-menu slot="menu" class="month-sel" @MDCMenu:selected=${this.handleSelected("month")}> + <mwc-list> + ${months.map(m => html`<mwc-list-item value="${m}">${Number(m)}</mwc-list-item>`)} + </mwc-list> + </mwc-menu> + </mwc-select> + </div> + ${archive.finishedLoading() ? html` + <smarthome-card> + <div class="table"> + <div class="table-row table-head"> + <div class="table-column">Name</div> + ${Object.keys(this.data).reverse().map((day) => html` + <div class="table-column">${formatName(this.type, day)}</table-column> + `)} + </div> + ${config.meters.map(d => html` + <div class="table-row"> + <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> + `)} + </div> + </smarthome-card> + ` : html` + <smarthome-spinner>Loading...</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; + } + .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; + } + + .hidden { + display: none; + } + #selection { + justify-content: center; + display: flex; + flex-wrap: wrap; + flex-direction: row; + width: 450px; + max-width: 100%; + margin-left: auto; + margin-right: auto; + margin-top: 20px; + margin-bottom: 0px; + /*justify-content: space-between;*/ + } + mwc-select { + width: 100px; + margin-right: 10px; + margin-left: 10px; + } + @media (min-width: 600px) { + #selection { + margin-bottom: -20px; + } + } + `; + } + + static get properties() { + return { + viewid: { type: String } + }; + } +} + +customElements.define("smarthome-temperature-history", TemperatureHistory);
diff --git a/src/temperature/index.js b/src/temperature/index.js @@ -0,0 +1,74 @@ +"use strict"; + +import { LitElement, html, css } from "lit-element"; + +import "./history.js"; +import "./live.js"; + +const pages = { + "live": { name: "Live", content: id => html`<smarthome-temperature-live .viewid="${id}"></smarthome-temperature-live>` }, + //"history": { name: "History", content: id => html`<smarthome-temperature-history .viewid="${id}"></smarthome-temperature-history>` }, +}; + +class Temperature extends LitElement { + constructor() { + super(...arguments); + this.activePage = pages["live"]; + } + + _setPage(path) { + return () => { + this.activePage = pages[path]; + this.requestUpdate(); + }; + } + + render() { + return html` + <div id="pages"> + ${Object.entries(pages).map(([path, page]) => html` + <a href="#/temperature" class="page-link ${this.activePage == page ? "active" : "inactive"}" @click=${this._setPage(path)}> + ${page.name} + </a> + `)} + </div> + ${this.activePage.content(this.viewid)} + `; + } + + 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; + } + + #pages { + display: flex; + flex-direction: row; + justify-content: center; + } + .page-link { + padding: .5em; + margin: .5em; + color: black; + text-decoration: none; + } + .page-link.active { + border-bottom: 4px solid #43a047; + } + `; + } + + static get properties() { + return { + viewid: { type: String } + }; + } +} + +customElements.define("smarthome-temperature", Temperature);
diff --git a/src/temperature/live.js b/src/temperature/live.js @@ -0,0 +1,132 @@ +"use strict"; + +import { LitElement, html, css } from "lit-element"; +import { state } from "../state.js"; +import { pad, formatDate, round, get } from "../util.js"; +import { archive } from "../archive.js"; + +import "@authentic/mwc-icon"; +import "../card.js"; +import "../spinner.js"; + +class TemperatureLive extends LitElement { + constructor() { + super(...arguments); + this._cachedDayArchive = {}; + state.subscribe(this.requestUpdate.bind(this)); + archive.subscribe(this.requestUpdate.bind(this)); + } + + getImport(d) { + const currentValue = state.data[d.device].import; + const now = new Date(); + console.log(d); + 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/${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[config.sensors[0].device] ? html` + <div id="connection-status"> + ${state.connected ? html` + <p><mwc-icon>cloud_queue</mwc-icon> Connected</p> + ` : html` + <p><mwc-icon>cloud_off</mwc-icon> Disconnected</p> + <p class="lastupdate">last updated ${formatDate(state.data.lastUpdated)}</p> + `} + </div> + <smarthome-card> + <div class="table"> + <div class="table-row table-head"> + <div class="table-column">Name</div> + <div class="table-column">Temperature</div> + <div class="table-column">Humidity</div> + </div> + ${config.sensors.map(d => html` + <div class="table-row"> + <div class="table-column">${d.name}</div> + <div class="table-column">${round(state.data[d.device].temperature, 2)}°C</div> + <div class="table-column">${round(state.data[d.device].humidity, 2)}%</div> + </div> + `)} + </div> + </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; + } + #connection-status mwc-icon { + padding-right: .5em; + } + #connection-status { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + } + #connection-status>p { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + } + #connection-status>p.lastupdate { + font-size: 1em; + } + .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) { + #connection-status { + margin-top: 20px; + } + } + `; + } + + static get properties() { + return { + viewid: { type: String } + }; + } +} + +customElements.define("smarthome-temperature-live", TemperatureLive);