Mercurial > code > home > repos > front-door-display
changeset 18:499996b8224a
more electricity summary info
author | drewp@bigasterisk.com |
---|---|
date | Wed, 19 Jun 2024 18:35:12 -0700 |
parents | 472003015880 |
children | 6960c172f1a9 |
files | src/FdElectricity.ts src/main.css |
diffstat | 2 files changed, 147 insertions(+), 57 deletions(-) [+] |
line wrap: on
line diff
--- a/src/FdElectricity.ts Fri Jun 07 21:22:37 2024 -0700 +++ b/src/FdElectricity.ts Wed Jun 19 18:35:12 2024 -0700 @@ -1,7 +1,11 @@ -import { LitElement, PropertyValues, css, html } from "lit"; -import { customElement, property } from "lit/decorators.js"; +import { LitElement, PropertyValues, TemplateResult, css, html } from "lit"; +import { customElement, state } from "lit/decorators.js"; import { shared } from "./shared"; +const HR = 3600; +const off_peak_wh_cost = 0.27 / 1000; +const on_peak_wh_cost = 0.312 / 1000; + @customElement("fd-electricity") export class FdElectricity extends LitElement { static styles = [ @@ -10,7 +14,10 @@ :host { font-size: 14px; } - + .unit { + color: #6c7d6c; + font-family: monospace; /* for dollar signs */ + } :host > table { margin-top: 10px; } @@ -26,89 +33,172 @@ } tr.total td, tr.total th { - border-top: 1px solid white; + border-top: 1px solid #6c7d6c; } .bar { background: yellow; display: inline-block; height: 10px; } + .bar > div { + transition: width 3s ease-out; + } .total .bar { background: pink; } + table { + border-collapse: collapse; + } + .summary td { + padding: 2px 6px; + } + .summary td, + .summary th { + border: 1px solid #2f2f2f; + } + .summary td.odometer { + width: 4.7em; + text-align: left; + } `, ]; - @property() seriesData: Map<string, number> = new Map(); + @state() seriesData: Map<string, number> = new Map(); + @state() isPeak = false; + @state() yesterday_charge = 0; // $$ + @state() today_charge = 0; // $$ + @state() today_peak_penalty = 0; // $$ + @state() yesterday_peak_penalty = 0; // $$ + @state() lastLoad = 0; // unix secs + constructor() { super(); this.load(); - setInterval(this.load.bind(this), 10 * 1000); + setInterval(this.load.bind(this), 8 * 1000); + setInterval(this.computeLiveState.bind(this), 60); } + async load() { - this.seriesData = new Map(); + const seriesData = new Map(); const base = "https://bigasterisk.com/m/vmselect/select/0/prometheus/api/v1/query"; - const now = Date.now() / 1000 - 10; + const { t, day0, day1, hoursSinceMidnight, hours } = this.timeMath(); + const seriesUrls = [ - { url: base + `?query=powermeter_w&time=${now}&step=725ms`, label: "powermeter" }, - { url: base + `?query=(sum%20by%20%20(s)%20(house_power_w))%20-%20(sum%20by(s)%20(powermeter_w))&time=${now}&step=725ms`, label: "unmetered" }, - { url: base + `?query=house_power_w&time=${now}&step=725ms`, label: "total" }, + { url: base + `?query=powermeter_w&time=${t}`, label: "powermeter" }, + { url: base + `?query=sum (powermeter_w{sensor=~".*_fridge"})&time=${t}`, label: "fridges" }, + { url: base + `?query=sum (powermeter_w{sensor=~"ws_.*"})&time=${t}`, label: "ws" }, + { url: base + `?query=(sum%20by%20%20(s)%20(house_power_w))%20-%20(sum%20by(s)%20(powermeter_w))&time=${t}`, label: "unmetered" }, + { url: base + `?query=house_power_w&time=${t}`, label: "total" }, + + { url: base + `?query=avg_over_time(house_power_w[17h]) @ ${day0 + 17 * HR}`, label: "day0_off_0" }, + { url: base + `?query=avg_over_time(house_power_w[3h]) @ ${day0 + 20 * HR}`, label: "day0_on_0" }, + { url: base + `?query=avg_over_time(house_power_w[4h]) @ ${day1}`, label: "day0_off_1" }, + { url: base + `?query=avg_over_time(house_power_w[24h]) @ ${day1}`, label: "day0_avg_w" }, + + { url: base + `?query=avg_over_time(house_power_w[17h]) @ ${day1 + 17 * HR}`, label: "day1_off_0" }, + { url: base + `?query=avg_over_time(house_power_w[3h]) @ ${day1 + 20 * HR}`, label: "day1_on_0" }, + { url: base + `?query=avg_over_time(house_power_w[4h]) @ ${day1 + 24 * HR}`, label: "day1_off_1" }, + { url: base + `?query=avg_over_time(house_power_w[${hoursSinceMidnight}h]) @ ${t}`, label: "day1_avg_w" }, ]; for (const series of seriesUrls) { const response = await fetch(series.url); const data = await response.json(); for (let row of data.data.result) { - this.seriesData.set(series.label + (row.metric.sensor ? "-" + row.metric.sensor : ""), parseFloat(row.value[1])); + const key = series.label + (row.metric.sensor ? "-" + row.metric.sensor : ""); + const value = parseFloat(row.value[1]); + seriesData.set(key, value); } } + + this.lastLoad = t; + this.isPeak = hours >= 17 && hours < 20; + this.seriesData = seriesData; this.update(new Map() as PropertyValues); } + + private timeMath() { + const now = new Date(); + + const t = now.getTime() / 1000; + + const hours = now.getHours(); + const minutes = now.getMinutes(); + const seconds = now.getSeconds(); + const hoursSinceMidnight = (hours * 60 * 60 + minutes * 60 + seconds) / HR; + + const lastMidnight = new Date(now); + lastMidnight.setHours(0, 0, 0, 0); + const day1 = lastMidnight.getTime() / 1000; + const day0 = day1 - 86400; + + return { t, day0, day1, hoursSinceMidnight, hours }; + } + + private computeLiveState() { + const data = (label: string): number => this.seriesData.get(label) || 0; + + const day0_off_peak_wh = data("day0_off_0") * 17 + data("day0_off_1") * 4; + const day0_on_peak_wh = data("day0_on_0") * 3; + this.yesterday_charge = off_peak_wh_cost * day0_off_peak_wh + on_peak_wh_cost * day0_on_peak_wh; + + const day1_off_peak_wh = data("day1_off_0") * 17 + data("day1_off_1") * 4; + const day1_on_peak_wh = data("day1_on_0") * 3; + this.today_charge = off_peak_wh_cost * day1_off_peak_wh + on_peak_wh_cost * day1_on_peak_wh + this.costSinceLastLoad(); + + this.yesterday_peak_penalty = this.yesterday_charge - off_peak_wh_cost * (day0_off_peak_wh + day0_on_peak_wh); + this.today_peak_penalty = this.today_charge - off_peak_wh_cost * (day1_off_peak_wh + day1_on_peak_wh); + } + + private costSinceLastLoad() { + const t = Date.now() / 1000; + const hr_since_last_load = (t - this.lastLoad) / HR; + const wh_since_last_load = (this.seriesData.get("total") || 0) * hr_since_last_load; + const cost_since_load = (this.isPeak ? on_peak_wh_cost : off_peak_wh_cost) * wh_since_last_load; + return cost_since_load; + } + render() { - const disp = (n: number | undefined) => (n ? `${n.toFixed(1)}` : ""); - const pxPerWatt = 150 / this.seriesData.get("total")!; - const bar = (n: number | undefined) => (n ? html`<div style="width: ${(n * pxPerWatt).toFixed(1)}px"></div>` : ""); - return html`<h1 data-text="Electricity">Electricity</h1> + const disp0 = (n: number | undefined) => (n ? `${n.toFixed(0)}` : "0"); + const disp1 = (n: number | undefined) => (n ? `${n.toFixed(1)}` : "0.0"); + const dispDollar = (n: number | undefined, places = 2) => (n ? html`<span class="unit">$</span>${n.toFixed(places)}` : ""); + const pxPerWatt = 150 / 1500; + const bar = (n: number | undefined) => (n ? html`<div style="width: ${Math.ceil(n * pxPerWatt).toFixed(1)}px"></div>` : ""); + + const seriesRow = (label: string, value: number | undefined, unitColumn: TemplateResult | string) => html` + <tr> + <th>${label}</th> + <td>${disp1(value)}</td> + <td>${unitColumn}</td> + <td class="bar">${bar(value)}</td> + </tr> + `; + + return html`<h1 data-text="Electricity">Electricity (now is ${this.isPeak ? "Peak" : "Off-Peak"})</h1> <table> + ${seriesRow("washer", this.seriesData.get("powermeter-ga_washer"), html`<span class="unit">W</span>`)} + ${seriesRow("theater console", this.seriesData.get("powermeter-tt_console"), "")} ${seriesRow("workshop", this.seriesData.get("ws"), "")} + ${seriesRow("dash", this.seriesData.get("powermeter-do_r"), "")} ${seriesRow("fridges", this.seriesData.get("fridges"), "")} + ${seriesRow("server closet", this.seriesData.get("powermeter-st_wall"), "")} ${seriesRow("(unmetered)", this.seriesData.get("unmetered"), "")} + <tr class="total"> + <th>Total</th> + <td>${(this.seriesData.get("total") || 0).toFixed(1)}</td> + <td></td> + <td class="bar">${bar(this.seriesData.get("total"))}</td> + </tr> + </table> + <table class="summary"> <tr> - <th>(unmetered)</th> - <td>${disp(this.seriesData.get("unmetered"))}</td> - <td class="bar">${bar(this.seriesData.get("unmetered"))}</td> - </tr> - <tr> - <th>dash</th> - <td>${disp(this.seriesData.get("powermeter-do_r"))}</td> - <td class="bar">${bar(this.seriesData.get("powermeter-do_r"))}</td> - </tr> - <tr> - <th>washer</th> - <td>${disp(this.seriesData.get("powermeter-ga_washer"))}</td> - <td class="bar">${bar(this.seriesData.get("powermeter-ga_washer"))}</td> + <th>yesterday</th> + <td>${disp0(this.seriesData.get("day0_avg_w"))} <span class="unit">W avg</span></td> + <td class="odometer">${dispDollar(this.yesterday_charge)}</td> + <td>${dispDollar(this.yesterday_peak_penalty)} peak penalty</td> </tr> <tr> - <th>fridge</th> - <td>${disp(this.seriesData.get("powermeter-ki_fridge"))}</td> - <td class="bar">${bar(this.seriesData.get("powermeter-ki_fridge"))}</td> - </tr> - <tr> - <th>server closet</th> - <td>${disp(this.seriesData.get("powermeter-st_wall"))}</td> - <td class="bar">${bar(this.seriesData.get("powermeter-st_wall"))}</td> + <th>today</th> + <td>${disp0(this.seriesData.get("day1_avg_w"))} <span class="unit">W avg</span></td> + <td class="odometer">${dispDollar(this.today_charge, 6)}</td> + <td>${dispDollar(this.today_peak_penalty)} peak penalty</td> </tr> - <tr> - <th>theater console</th> - <td>${disp(this.seriesData.get("powermeter-tt_console"))}</td> - <td class="bar">${bar(this.seriesData.get("powermeter-tt_console"))}</td> - </tr> - <tr> - <th>workshop desk</th> - <td>${disp(this.seriesData.get("powermeter-ws_desk"))}</td> - <td class="bar">${bar(this.seriesData.get("powermeter-ws_desk"))}</td> - </tr> - <tr class="total"> - <th>Total</th> - <td>${this.seriesData.get("total")} W</td> - <td class="bar">${bar(this.seriesData.get("total"))}</td> - </tr> - </table> `; + </table>`; } }
--- a/src/main.css Fri Jun 07 21:22:37 2024 -0700 +++ b/src/main.css Wed Jun 19 18:35:12 2024 -0700 @@ -21,11 +21,11 @@ #grid { display: grid; - grid-template-rows: 54px 206px 1fr; + grid-template-rows: 54px 165px 1fr; grid-template-columns: 1.7fr 1fr 80px; } -.debug #grid>* { +.debug #grid > * { outline: 1px rgb(68, 68, 68) solid; } @@ -42,9 +42,9 @@ } fd-countdown { - grid-area: 2/2/2/4 + grid-area: 2/2/2/4; } fd-electricity { grid-area: 3/2/3/4; -} \ No newline at end of file +}