docs(hass): Quick 面板 card-mod 等高 + 饼图图例右侧 + WS 客户端脚本

- 安装 card-mod (HACS),用 CSS `ha-card { height: Npx }` 强制等高
- 饼图+人体感应 520px,温湿度双图 520px
- 饼图图例从底部移到右侧 (legend position: right),高度 510→520
- 关键认知:apexcharts chart.height 含图例,timescale-plotly height 是纯绘图区
- 提交 scripts/ha-ws-client.py (纯 stdlib WS 客户端,文档已引用)
This commit is contained in:
windyboy
2026-09-21 19:57:35 +08:00
parent e08e5d50de
commit 812a1eb77d
2 changed files with 345 additions and 14 deletions
+173 -14
View File
@@ -305,14 +305,58 @@ supervisor 代理 WS 被拒(loop prevention),用
0.589 元/kWh、15 个 csg_* 数值齐全、回归值不变(14.09/198.65/331.22/
304.99/181.89)、automations on、`ha core check` OK、日志无 template 错误。
**CSG 长期归档(W1N-243, 2026-08-29:** scribe 库新增 `csg_history`
表(逐日 usage/cost/ladder/balance + 逐月累计;2026-07-01 起回填,永久),
由 TimescaleDB 每日任务 **1008** `csg_daily_snapshot()`22:30
Asia/Shanghai**TS job 非 pg_cron**upsert 维护。日费用在原生
`latest_day_cost` 缺失时回退 = 昨日用电 × 当前档费率(模板
`csg_current_ladder_tariff` 0.639);月费用回退模板
`csg_this_month_ladder_cost`。**语义**day 行 usage/cost 为该日值,
ladder/balance 为 22:30 快照值。详见 [hosts/pgdb.md](../hosts/pgdb.md)。
**CSG 长期归档(W1N-243, 2026-08-29;任务改号 2026-09-21:** scribe 库新增
`csg_history` 表(逐日 usage/cost/ladder/balance + 逐月累计;2026-07-01 起回填,
永久),由 TimescaleDB 每日任务 **1010**(原 10082026-09-21 重建)
`csg_daily_snapshot()`**14:30 UTC = 22:30 Asia/Shanghai****TS job 非 pg_cron**
upsert 维护;计费用 `csg_ladder_cost(kwh, month)`。日行 usage 取集成
`yesterday_kwh`,日费用在原生 `latest_day_cost` 缺失时回退 = 昨日用电 ×
当前档费率(模板 `csg_current_ladder_tariff`);月行由阶梯助手重算。**语义**
day 行 usage/cost 为该日值,ladder/balance 为快照值。详见
[hosts/pgdb.md](../hosts/pgdb.md) § Known issues 2026-09-21(含原 1008 从未成功
的根因与回填)。
**CSG 预测值复核(2026-09-21,只读):**
`sensor.csg_*` 全部 14 个派生值用独立 Python 实现按同一输入重算,逐个与 HA
live state 完全一致(`csg_30d_avg` 9.07 / `csg_predicted_usage` 268.45 /
`csg_predicted_cost_ladder` 158.54 / `csg_this_month_ladder_cost` 99.35 /
`csg_mom_change` -21.6 等)。同时把 9 月逐日口径对回 scribe:集成
`this_month_by_day`09-01..09-1919 天,和 168.68)与 `states_raw`
`yesterday_kwh` 观测值逐日一致(唯一差异 09-06 = 9.29 是 scribe 漏采、集成有值),
确认输入数据无缺口。**因此「预测本月电费」不是算错,而是估计器的窗口问题**:
`csg_predicted_usage = 本月累计 + avg × 剩余天数`,旧版 `avg = csg_30d_avg`
`history_data[-30:]` 的**普通 30 日均值、跨月重叠**)→ 月初被上月(更热)拖高
09-02 实测 317.93 kWh / 190.16 元),随本月凉快日置换回落到 268.45 / 158.54
一个月内摆动 **-17%**。**另注**:集成原生
`latest_day_cost` / `this_month_total_cost` / `last_month_total_cost`
2026-08-29 起**恒为 unknown**`states_raw` 无一条有值),故面板上所有「元」
均为模板阶梯估算值,从未与账单口径对账。
**`csg_sensors.yaml` 预测速率 v22026-09-21,已实施):** 只改
`csg_predicted_usage` 的速率项,`csg_30d_avg` 语义不动(面板「近30日平均」照旧)。
新公式:`avg = mtd × w + recent7 × (1 w)`,其中 `mtd = 本月累计 / 已过天数`
`recent7` = `history_data` 末 7 条均值、`w = min(已过天数, 14) / 14`;两者都取不到
才退回 `csg_30d_avg`。**动机**:本月至今速率按天数爬升可信度,避免月初被上月污染;
近 7 日速率兜底,因为月初唯一的新数据就是上月末。Block C 触发器加
`sensor.csg_history`。**19 天回测**(基准 = 第 19 天本月至今速率外推,逐日重放
集成 `this_month_by_day` + `last_month_by_day`):
| 速率方案 | 预测电费摆动 | 最大偏差 | 平均绝对偏差 |
|---|---|---|---|
| 旧:30 日均值 | 21.1% | 35.99 元 | 14.04 元 |
| v2K=7 | 14.6% | 16.81 元 | 5.65 元 |
| **v2K=14(采用)** | **13.9%** | **15.33 元** | **4.94 元** |
| v2K=14 但兜底用 30 日均值 | 23.3% | 33.12 元 | 6.28 元 |
(末行证实病根就是那个跨月的 30 日均值兜底。)备份
`/homeassistant/.csg-backups/csg_sensors.yaml.bak-20260921-pre-predictor`
(改前)与 `…-pre-k14`K=7 中间态)。**验证**`ha core check` OKCore 重启两次
trigger 模板块不吃 `template.reload`W1N-114 先例);15 个 `csg_*` 全部数值、
无 template 错误;当天 `n=19 ≥ 14` 故两版同值,面板读回
`csg_predicted_usage` 266.34 kWh / `csg_predicted_cost_ladder` 157.19 元 /
`csg_prediction_progress` 63.3%;新值已进 scribe 归档
`states_raw` 08:17:04Z 158.54 → 08:17:10Z 157.19)。**注意**`w` 的爬升窗口
(14 天)是回测选出来的单月参数,属弱证据,换季/换季后值得复看。
> **Seasonal gauge switch (W1N-240 已知事项):** 每年 **11-01** 把
> `power-monitor` 视图「本月累计」gauge 切到冬季 `max:450` /
@@ -563,13 +607,128 @@ manual copies): reader `remmob/timescale_database_reader` **v1.1.0** (main
各 timescale 卡可选档上调——大功率电器/常驻负载 `…,24h``+3d,7d`
环境 `6h,12h,24h,48h``+7d,14d,30d`;人体感应 `…,24h``+3d,7d`
用电量(按插座) `energy_time_ranges` `today,week,month,custom``+3mo`
**默认档未改**6h / 6h / today / 24h / 12h)。卡片 JS 只接受
`<n>m|<n>h|<n>d``parseDurationToMs` 正则 `/^(\d+)(m|h|d)$/`
仅 m/h/d,无 w)与命名档 `today|week|month|3mo|6mo|year|years|custom`
`energy_mode` 卡必须用后者。**数据下界注意**:scribe `sensor_minute`
目前最早只到 **2026-08-29**,所以 >15d 的档(14d 边缘、30d 明显)前半段
会是空白,等归档继续累积才好看。备份
**默认档未改**6h / 6h / today / 24h / 12h)。备份
`.lovelace-backups/dashboard-quick-20260913-190912-pre-timerange.json`
⚠️ **本条的 `d` 档当天其实是坏的**——见下方 2026-09-21 修复;`energy_mode`
命名档(today/week/month/3mo)当时就是好的。卡片命名档只能是
`today|week|month|3mo|6mo|year|years|custom`。**数据下界注意**scribe
`sensor_minute` 最早只到 **2026-08-29**,所以 >15d 的档(14d 边缘、30d 明显)
前半段仍是空白,等归档继续累积才好看。
- **Quick 面板改造 + timescale 卡两个真 bug 修复 (2026-09-21)**:起因是用户
觉得「面板标题 + 统计方式」不对劲。只读审查查出三个问题并全部修掉:
**① 卡 JS`<n>d` 档实际渲染成 n 小时(VPS-92 的扩容其实没生效)**。
非 energy 档走 `const hours = parseInt(this._selectedRange) || 24;`
`parseInt("7d") === 7`。全卡唯一能解析 `d``parseDurationToMs()`
(正则 `/^(\d+)(m|h|d)$/`**只被 `resolveEnergyWindow()` 调用一次**
即只服务 energy 卡。后果:`3d``24h` 还短(3h),`14d`→14h、`30d`→30h。
**修复**:改用 `Math.max(1, parseDurationToMs(...)/3600000)`;顺带把采样密度
目标 80 → 160 点(7d 档从 2.1h/桶降到 1.05h/桶)。备份
`.lovelace-backups/timescale-plotly-card.js.bak-20260921-pre-dayfix`
实测(本地 node 跑卡里的 `parseDurationToMs`):`3d→72h / 7d→168h /
14d→336h / 30d→720h`,各档均 ~160 点;`node --check` JS 语法 OK。
**资源 URL 已加 `?v=20260921` 强制浏览器重取**(原来无 hacstag,缓存问题会
让修复「看不见」)。
**② 功率卡画的是瞬时采样而非统计量**。reader 对 `table=sensor_minute`
(时间列名 `minute`)默认 `downsample_method = "last"`
`custom_components/timescale_database_reader/__init__.py` L385-389),
即每桶取「末一分钟的瞬时值」。实测空调 1 小时桶:03:00 真实均值 241.6 W /
峰值 1128 W,图上只画 15719:00 真实均值 97.6 / 峰值 676,图上画 **13**
(看起来像停机)。因 `sensor_minute` 已是均匀 1 分钟 LOCF 序列,`avg()`
才是正确时间均值。**修复**:两张功率卡加 `downsample_method: "avg"`
**用电量卡不用改**——累计计数器配 `last()` 就是桶末读数,卡片再 diff
本来就是对的;人体感应卡显式 `last` 对分类信号也对。
**③ 标题与分组名不副实**。按 7d 实测(峰值/均值)重分组并改名:
| 位置 | 改前 | 改后 |
|---|---|---|
| 分区 | 大功率电器 | **峰值负载**(厨房电器 1839 / 空调 1128 / 电水壶 957 / 咖啡机 882 / 电脑 363 W |
| 分区 | 常驻负载 | **基础负载**(主网络 75 / 冰箱 137 / 大房 290 / 水设备 171 / 无线AP 976* W |
| 分区 | 用电 | **今日用电** |
| 分区 | 灯 + 开关(分开在最顶和最底) | **灯与开关**(合并置顶,可操作项集中) |
| 分区(新) | — | **此刻耗电** |
| 徽标 | 总功率 | **插座合计**(只是 9 个插座之和,不含灯/换气) |
| 实体 | 迷你 / mini数显 / 数显 | **卫生间 / 客厅 / 卧室**(HA area 本来就有,之前用的是设备型号名) |
| 实体 | 电池(移动) | **厨房(电池)**(该设备 area 也是厨房) |
删除 3 张与分区标题重复的卡 title(用电量(按插座) / 室内外温湿度 / 活动状态)。
\* 无线AP 7 天里只有 2 分钟 >150 W 却记到 976 W —— 单点毛刺,会把 y 轴顶飞,
待查。
**④ 新展示**:新增「此刻耗电」区 = `custom:apexcharts-card` **donut**11 个
插座当前功率(`graph_span: 10min` + `update_interval: 1min`),回答「现在什么
在吃电」;环境区拆成**室内外温差**(新模板传感器 `sensor.quick_wen_cha` =
3 个室内温度均值 − 室外,画零线上下 fill)+ **湿度**4 条),4 个温度进徽标。
传感器加在 `templates/quick_sensors.yaml`(W1N-238 约定的落点,顶层列表,
`quick_` 前缀 + unique_id),非 trigger 型模板卡,**`template.reload` 即可生效,
无需重启 Core**(实测 `POST /core/api/services/template/reload` → 200
`sensor.quick_wen_cha` = 3.9 °C)。备份
`templates/quick_sensors.yaml.bak-20260921-pre-wencha`
最终分区顺序(按「可变性」):灯与开关 → 此刻耗电 → 今日用电 →
峰值负载(2) → 基础负载(2) → 环境 → 人体感应。
**⑤ Quick 布局重构 (2026-09-21 晚)**:用户反馈饼图(apexcharts donut)撑满
整行(span 4)且不满、马桶换气开关 tile `columns: "full"` 占满整行太宽、整体
布局需要重新思考。改动:
**布局重排**(阅读流:控制 → 此刻 → 趋势 → 今日 → 环境):
| 行 | 左 (span 2) | 右 (span 2) | 主题 |
|---|---|---|---|
| 1 | 灯与开关 (span 4) | | 可操作项置顶 |
| 2 | 实时功率 (donut) | 人体感应 | 「此刻状态」 |
| 3 | 峰值负载 | 基础负载 | 「功率趋势」(原配对保留) |
| 4 | 今日用电 (span 4) | | 11 条堆叠柱需要宽度 |
| 5 | 温湿度 (span 4) | | 温差+湿度两图并排 |
**标题改名**:此刻耗电→**实时功率**(与峰值负载对仗)、环境→**温湿度**
(更具体)。删饼图卡内冗余标题 "各插座当前功率 (W)"。
**开关收窄**:马桶换气电源 tile 从独立 `columns: "full"`48 列全宽)→ 移入
灯的 2 列嵌套网格第 5 格,与灯卡等宽(半行)。
**饼图高度**`apex_config.chart.height` 280→35011 项底部图例占 ~100px
280 时饼图本体太扁)。
**⑥ card-mod 等高 (2026-09-21 晚)**:不同卡类型(apexcharts vs timescale-plotly
`height` 渲染比例不同(apexcharts 含图例/内边距,timescale 是纯绘图区),
直接对齐数字无法视觉等高。安装 **card-mod**HACS → card-mod + card-mod-studio
后用 CSS 强制 `ha-card` 高度:
```yaml
card_mod:
style: "ha-card { height: 520px !important; }"
```
**最终高度**:饼图+人体感应 = **520px**,温湿度双图 = **520px**。其余图表
(峰值/基础负载 280、今日用电 320)保持各自原生高度,不强制统一——不同图表
类型适合不同高度,强行统一反而让简单图表显得太空。
**关键认知**apexcharts `chart.height` 包含图例区域,timescale-plotly `height`
是纯绘图区。同一数字渲染出来 apexcharts 视觉上更矮。card-mod 直接控制
`ha-card` 容器高度,绕过内部渲染差异。
备份:`.lovelace-backups/dashboard-quick-20260921-pre-titles.json`(同日早期
备份,覆盖标题+布局全部改动的回滚)。
**验证**`ha core check` OKWS 读回与写入 payload **逐字节相同**
新配置引用的 **40 个实体全部存在**且无 unknown/unavailable
卡 JS `node --check` 通过、时长解析实测正确。
**改法(可复用)**:写 Lovelace 配置必须走 WS
`lovelace/config/save`**勿直改 `.storage/`**)。本机 python/HA 容器内经
supervisor 代理连 WS 会被 loop-prevention 拒;可行路径是
`docker run --rm -i --network host -e SUPERVISOR_TOKEN --entrypoint python3
r.hassbus.com/home-assistant/aarch64-hassio-supervisor:<ver> -c "..."`
跑本仓库的 [`scripts/ha-ws-client.py`](../scripts/ha-ws-client.py)(纯 stdlib
不依赖 websockets/aiohttp,也不依赖 `-v` 挂载——`-v /tmp/...` 会落到 Docker 宿主
而不是 SSH add-on 的 `/tmp`)。读配置:`{"action":"get","url_path":"dashboard-quick"}`
写:`{"action":"save",...,"config":{...}}`。备份
`.lovelace-backups/dashboard-quick-20260921-pre-titles.json`。
**回滚**:还原 `dashboard-quick-20260921-pre-titles.json`WS save+
`timescale-plotly-card.js.bak-20260921-pre-dayfix` + 删 `quick_wen_cha`
(还原 `quick_sensors.yaml.bak-20260921-pre-wencha` + `template.reload`)。
改了卡 JS 就要把资源 URL 的 `?v=` 再 bump 一次。
### 地图仪表盘:CARTO keyed tiles via `custom:map-card` (verified 2026-08-30, W1N-261)