v1.44.6: room climate counts only air temperature

After v1.44.5 read the area registry instead of visible icons, every hidden
temperature entity in the area became a candidate, including ones measuring
something other than room air. Verified against a live 60-area install: a NAS
processor temperature, kettle water, a 90 C sauna heater and a virtual
better_thermostat all leaked into room averages.

- areaClimate(): skip entity_category (diagnostic/config), skip EXCLUDED_DOMAINS
  platforms, skip entity ids naming a non-air medium (water/coolant/flow_temp/
  return_temp/target/setpoint/chip/cpu/processor/board/device_temp/batter/
  freezer/fridge/oven/kettle/boiler).
- rules.ts: kettle/thermopot -> mdi:kettle, sauna/harvia -> mdi:hot-tub, so they
  no longer fall through to the generic thermometer rule.
- test: all four real false positives asserted out, one real sensor left.
- docs: CHANGELOG.md + CHANGELOG.ru.md + STATUS.md snapshot.
This commit is contained in:
Matysh
2026-07-27 14:38:50 +03:00
parent 02ba18dc7b
commit ebeaa5c0c6
12 changed files with 129 additions and 35 deletions
+1 -1
View File
@@ -13,7 +13,7 @@ FILES_URL = "/houseplan_files/files"
CONTENT_URL = "/api/houseplan/content"
FILES_DIR = "houseplan/files"
CONF_ADMIN_ONLY = "admin_only"
VERSION = "1.44.5"
VERSION = "1.44.6"
DEFAULT_CONFIG: dict = {
"spaces": [],
+1 -1
View File
@@ -16,5 +16,5 @@
"issue_tracker": "https://github.com/Matysh/houseplan-card/issues",
"requirements": [],
"single_config_entry": true,
"version": "1.44.5"
"version": "1.44.6"
}
File diff suppressed because one or more lines are too long
+13 -13
View File
File diff suppressed because one or more lines are too long
+18
View File
@@ -1,5 +1,23 @@
# Changelog
## v1.44.6 — 2026-07-27
- **Only room *air* counts as room climate.** After v1.44.5 started reading the
area registry instead of the visible icons, every hidden temperature entity in
the area became a candidate — including ones that measure something other than
the air. Three guards now run before averaging: entities marked
diagnostic/config are skipped, entities from curated-out integrations are
skipped, and entity ids naming a non-air medium are skipped
(`water`, `coolant`, `flow_temp`, `return_temp`, `target`, `setpoint`, `chip`,
`cpu`, `processor`, `board`, `device_temp`, `batter`, `freezer`, `fridge`,
`oven`, `kettle`, `boiler`).
On a live 60-area install this removed four real false positives: a NAS
processor temperature, the water in a smart kettle, a 90 °C sauna heater and a
virtual `better_thermostat` duplicating the real sensor.
- **New icon rules:** kettles/thermopots get `mdi:kettle`, saunas
(`sauna`, `harvia`, `парная`) get `mdi:hot-tub` — previously both fell through
to the generic thermometer rule, which is also what made them count as room
climate.
## v1.44.5 — 2026-07-27
- **Room climate now counts every sensor in the area**, including devices that
are not placed on the plan (hidden by curation or by you). Previously the
+17
View File
@@ -6,6 +6,23 @@
> **Правило проекта:** оба файла пополняются в одном коммите с самим
> изменением — как и остальная документация (см. docs/STATUS.md).
## v1.44.6 — 2026-07-27
- **Климатом комнаты считается только температура *воздуха*.** После v1.44.5,
когда данные стали браться из реестра зон, а не с видимых значков,
кандидатами стали все скрытые датчики температуры в зоне — в том числе те,
что меряют вовсе не воздух. Перед усреднением теперь работают три фильтра:
пропускаются сущности с категорией диагностика/настройка, сущности
исключённых интеграций и сущности, в id которых назван не-воздушный носитель
(`water`, `coolant`, `flow_temp`, `return_temp`, `target`, `setpoint`, `chip`,
`cpu`, `processor`, `board`, `device_temp`, `batter`, `freezer`, `fridge`,
`oven`, `kettle`, `boiler`).
На живой установке с 60 зонами это убрало четыре реальных ложных
срабатывания: температуру процессора NAS, воду в умном чайнике, сауну с 90 °C
и виртуальный `better_thermostat`, дублирующий настоящий датчик.
- **Новые правила иконок:** чайники и термопоты получают `mdi:kettle`, сауны
(`sauna`, `harvia`, `парная`) — `mdi:hot-tub`. Раньше и те и другие попадали
под общее правило термометра, из-за чего и учитывались в климате комнаты.
## v1.44.5 — 2026-07-27
- **Климат комнаты считается по всем датчикам зоны**, включая устройства,
которых нет на плане (скрыты курированием или вами). Раньше среднее бралось
+4 -4
View File
@@ -11,18 +11,18 @@
> (versions, publication, infrastructure), DEVELOPMENT.md for new gotchas,
> ARCHITECTURE.md for design changes, ROADMAP.md when plans move.
## Snapshot (2026-07-24)
## Snapshot (2026-07-27)
| Item | State |
|---|---|
| Version | **v1.41.0** everywhere (manifest, const.py, package.json, CARD_VERSION); deployed to the home instance |
| Version | **v1.44.6** everywhere (manifest, const.py, package.json, CARD_VERSION); deployed to the home instance |
| Workflow | Since 2026-07-22: minor changes go to branch **`dev`** (build + smokes → deploy home → commit → push, NO release); releases are batched on the owner's command (merge dev→main, one tag, one release with a summary changelog, CI checked on dev beforehand) |
| GitHub | https://github.com/Matysh/houseplan-card — `main` = releases up to **v1.40.1**; `dev` ahead with v1.40.2+ (speaker icons, kiosk). Push via SSH key `ha_jb` (remote git@github.com:…); API releases via the fine-grained PAT in `~/.git-credentials` (Contents R/W, issued 2026-07-23) |
| CI | validate.yml (hacs + hassfest + frontend + backend) green; release.yml attaches the bundle on release publish |
| HACS | Custom repository works. **Inclusion PR: hacs/default#9004** — open, valid, labeled; ~864 older open PRs but merge rate ≈180/mo; realistic ETA 13 months (checked 2026-07-24) |
| Home instance | ha.jbstudio.pro (SSH port 323, key `ha_jb`), deployed **v1.41.0** via direct copy (HACS custom repo also installed) |
| Home instance | ha.jbstudio.pro (SSH port 323, key `ha_jb`), deployed **v1.44.6** via direct copy (HACS custom repo also installed) |
| Localization | UI en/ru (src/i18n/*.json), everything user-visible localized incl. kiosk popover |
| Tests | 111 frontend (node:test) + 12 pure backend + 12 HA-harness (CI, py3.13); ~30 demo smoke suites (headless chromium) |
| Tests | 121 frontend (node:test) + 12 pure backend + 12 HA-harness (CI, py3.13); ~30 demo smoke suites (headless chromium) |
| Community | **Telegram chat: https://t.me/ha_houseplan** (created 2026-07-27) — the primary user-facing support channel; GitHub issues stay for bugs/features. Link it from any new release notes and posts |
| Product scope | docs/SCOPE.md (2026-07-22) is the feature guard rail — check before accepting any feature |
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "houseplan-card",
"version": "1.44.5",
"version": "1.44.6",
"description": "Interactive house plan Lovelace card for Home Assistant",
"license": "MIT",
"type": "module",
+22 -1
View File
@@ -2,7 +2,7 @@
* Building the device list from HA registries: curation, light groups,
* markers (overrides/virtual). No Lit/DOM only the hass object.
*/
import { iconFor, iconFromDeviceClasses, DOMAIN_PRIORITY, FALLBACK_ICON, type CompiledIconRule } from './rules';
import { iconFor, iconFromDeviceClasses, DOMAIN_PRIORITY, FALLBACK_ICON, type CompiledIconRule, EXCLUDED_DOMAINS } from './rules';
import { averageLqi } from './logic';
import type { DevItem, Marker, ServerConfig } from './types';
@@ -400,6 +400,20 @@ export function areaHum(
return Math.round(vals.reduce((a, b) => a + b, 0) / vals.length);
}
/**
* Entity ids that measure something other than room air, however honest their
* device_class is: water and coolant loops, chip/CPU/board temperatures,
* battery temperature, setpoints (target/external) and the like.
*/
const NON_AIR_RE = new RegExp(
[
'water', 'voda', 'coolant', 'flow_?temp', 'return_?temp', 'target', 'setpoint',
'chip', 'cpu', 'processor', 'board', 'core_temp', 'device_temp',
'batter', 'akkum', 'freezer', 'fridge', 'oven', 'kettle', 'boiler',
].join('|'),
'i',
);
/**
* Room climate from EVERY sensor of the area including devices that are not
* placed on the plan (hidden by curation or by the user). The old helpers read
@@ -420,6 +434,13 @@ export function areaClimate(
const dev = reg.device_id ? hass.devices?.[reg.device_id] : null;
const entArea = reg.area_id || dev?.area_id || null;
if (entArea !== area) continue;
// Not every "temperature" is room air. Real finds on a live install: the
// NAS processor temperature, the water in a smart kettle, a sauna heater at
// 90 C and a virtual better_thermostat duplicating the real sensor (field
// question, 2026-07-27). Three guards, cheapest first:
if (reg.entity_category) continue; // diagnostic/config readings
if (EXCLUDED_DOMAINS.has(reg.platform)) continue; // curated-out integrations
if (NON_AIR_RE.test(eid)) continue; // water/chip/flow/target/...
const key = reg.device_id || eid;
if (!groups.has(key)) {
const st = hass.states?.[eid];
+1 -1
View File
@@ -32,7 +32,7 @@ import './space-card';
import { cardStyles } from './styles';
import { langOf, t, type I18nKey } from './i18n';
const CARD_VERSION = '1.44.5';
const CARD_VERSION = '1.44.6';
const LS_KEY = 'houseplan_card_layout_v1';
const LS_CFG = 'houseplan_card_cfg_v1'; // cache of the server config+layout for instant rendering
const LS_ZOOM = 'houseplan_card_zoom_v1';
+2
View File
@@ -29,6 +29,8 @@ export const DEFAULT_ICON_RULES: IconRule[] = [
{ pattern: 'клапан|valve', icon: 'mdi:pipe-valve' },
{ pattern: 'дым|smoke', icon: 'mdi:smoke-detector' },
{ pattern: 'термоголов|trv|radiator', icon: 'mdi:radiator' },
{ pattern: 'чайник|kettle|термопот', icon: 'mdi:kettle' },
{ pattern: 'сауна|sauna|harvia|парная|парилк', icon: 'mdi:hot-tub' },
{ pattern: 'температ|temperature|climate sensor', icon: 'mdi:thermometer' },
{ pattern: 'qingping|air monitor|молекул|air quality', icon: 'mdi:air-filter' },
{ pattern: 'штор|curtain|blind|shade', icon: 'mdi:roller-shade' },
+36
View File
@@ -400,3 +400,39 @@ test('areaClimate: counts sensors that are NOT on the plan (field report)', () =
};
assert.equal(areaClimate(hass2, 'kitchen', 'temp'), null);
});
test('areaClimate: only ROOM AIR counts (field question, 2026-07-27)', () => {
const hass = {
devices: {
good: { id: 'good', name: 'Датчик температуры спальня', area_id: 'bed' },
kettle: { id: 'kettle', name: 'Polaris PWK-1725CGLD', model: 'Kettle', area_id: 'bed' },
nas: { id: 'nas', name: 'System Monitor', area_id: 'bed' },
sauna: { id: 'sauna', name: 'Сауна Harvia', area_id: 'bed' },
trv: { id: 'trv', name: 'Термоголовка в спальне', area_id: 'bed' },
bt: { id: 'bt', name: 'Спальня better thermostat', area_id: 'bed' },
zb: { id: 'zb', name: 'SLZB-06MU', area_id: 'bed' },
},
entities: {
'sensor.good_t': { device_id: 'good', platform: 'mqtt' },
// вода в чайнике
'sensor.kettle_current_temperature': { device_id: 'kettle', platform: 'syncleo_kettle' },
// температура процессора: и diagnostic, и исключённая интеграция
'sensor.nas_processor_temperature': { device_id: 'nas', platform: 'systemmonitor', entity_category: 'diagnostic' },
'sensor.sauna_temperature': { device_id: 'sauna', platform: 'harvia_sauna' },
'sensor.trv_local_temperature': { device_id: 'trv', platform: 'mqtt' },
'sensor.bt_temperature': { device_id: 'bt', platform: 'better_thermostat' },
'sensor.zb_core_chip_temp': { device_id: 'zb', platform: 'smlight', entity_category: 'diagnostic' },
},
states: {
'sensor.good_t': { state: '22.0', attributes: { device_class: 'temperature', unit_of_measurement: '°C' } },
'sensor.kettle_current_temperature': { state: '95', attributes: { device_class: 'temperature', unit_of_measurement: '°C' } },
'sensor.nas_processor_temperature': { state: '61', attributes: { device_class: 'temperature', unit_of_measurement: '°C' } },
'sensor.sauna_temperature': { state: '90', attributes: { device_class: 'temperature', unit_of_measurement: '°C' } },
'sensor.trv_local_temperature': { state: '24', attributes: { device_class: 'temperature', unit_of_measurement: '°C' } },
'sensor.bt_temperature': { state: '22.0', attributes: { device_class: 'temperature', unit_of_measurement: '°C' } },
'sensor.zb_core_chip_temp': { state: '48', attributes: { device_class: 'temperature', unit_of_measurement: '°C' } },
},
};
// остаётся ровно один настоящий датчик воздуха
assert.equal(areaClimate(hass, 'bed', 'temp'), 22);
});