Mercurial > code > home > repos > front-door-display
view src/FdElectricity.ts @ 21:a90cb6927c7d default tip
fix countdown queries. Display "now" instead of "In -0.4 hours"
author | drewp@bigasterisk.com |
---|---|
date | Sat, 07 Sep 2024 17:47:36 -0700 |
parents | e8c90d893919 |
children |
line wrap: on
line source
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 = [ shared, css` :host { font-size: 14px; } .unit { color: #6c7d6c; font-family: monospace; /* for dollar signs */ } :host > table { margin-top: 10px; } th, td { padding: 0 0; white-space: nowrap; } th { text-align: left; } td { text-align: right; } tr.total td, tr.total th { border-top: 1px solid #6c7d6c; } .bar { background: yellow; display: inline-block; height: 10px; margin-left: 5px; } .bar > div { transition: width 3s ease-out; } .total .bar { background: pink; } table { border-collapse: collapse; } .summary tr:first-of-type { opacity: .7; } .summary td { padding: 2px 6px; } .summary td, .summary th { border: 1px solid #2f2f2f; } .summary td.odometer { width: 3.4em; text-align: left; } `, ]; @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), 8 * 1000); setInterval(this.computeLiveState.bind(this), 500); } async load() { const seriesData = new Map(); const base = "https://bigasterisk.com/m/vmselect/select/0/prometheus/api/v1/query"; const { t, day0, day1, hoursSinceMidnight, hours } = this.timeMath(); const seriesUrls = [ { 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) { 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; // todo: no peak on weekends 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 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 / 2500; 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>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>today</th> <td>${disp0(this.seriesData.get("day1_avg_w"))} <span class="unit">W avg</span></td> <td class="odometer">${dispDollar(this.today_charge, 4)}</td> <td>${dispDollar(this.today_peak_penalty)} peak penalty</td> </tr> </table>`; } }