mirror of
https://github.com/Matysh/houseplan-card
synced 2026-07-31 16:38:31 +00:00
feat v1.2.0: room borders snapped to walls; zigbee LQI under icons + room average in tooltip with red-green gradient; lock icon for devices with lock.* entity (TTLock); show_signal option
This commit is contained in:
File diff suppressed because one or more lines are too long
Vendored
+21
-5
File diff suppressed because one or more lines are too long
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "houseplan-card",
|
"name": "houseplan-card",
|
||||||
"version": "1.1.0",
|
"version": "1.2.0",
|
||||||
"description": "Interactive house plan Lovelace card for Home Assistant (dacha Kirillovskoe)",
|
"description": "Interactive house plan Lovelace card for Home Assistant (dacha Kirillovskoe)",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
Executable
+68
@@ -0,0 +1,68 @@
|
|||||||
|
/** Редактор конфигурации карточки (GUI в Lovelace). */
|
||||||
|
import { LitElement, html, nothing } from 'lit';
|
||||||
|
|
||||||
|
const SCHEMA = [
|
||||||
|
{ name: 'title', selector: { text: {} } },
|
||||||
|
{
|
||||||
|
name: 'default_floor',
|
||||||
|
selector: {
|
||||||
|
select: {
|
||||||
|
mode: 'dropdown',
|
||||||
|
options: [
|
||||||
|
{ value: 'f1', label: '1 этаж' },
|
||||||
|
{ value: 'f2', label: '2 этаж' },
|
||||||
|
{ value: 'yard', label: 'Двор' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ name: 'icon_size', selector: { number: { min: 1, max: 6, step: 0.1, mode: 'box' } } },
|
||||||
|
{ name: 'show_temperature', selector: { boolean: {} } },
|
||||||
|
{ name: 'live_states', selector: { boolean: {} } },
|
||||||
|
{ name: 'show_signal', selector: { boolean: {} } },
|
||||||
|
];
|
||||||
|
|
||||||
|
const LABELS: Record<string, string> = {
|
||||||
|
title: 'Заголовок',
|
||||||
|
default_floor: 'Этаж по умолчанию',
|
||||||
|
icon_size: 'Размер иконок, % ширины плана',
|
||||||
|
show_temperature: 'Показывать температуру',
|
||||||
|
live_states: 'Живые состояния (вкл/выкл, открыто…)',
|
||||||
|
show_signal: 'Показывать сигнал zigbee (LQI)',
|
||||||
|
};
|
||||||
|
|
||||||
|
class HouseplanCardEditor extends LitElement {
|
||||||
|
public hass?: any;
|
||||||
|
private _config?: any;
|
||||||
|
|
||||||
|
static properties = {
|
||||||
|
hass: { attribute: false },
|
||||||
|
_config: { state: true },
|
||||||
|
};
|
||||||
|
|
||||||
|
public setConfig(config: any): void {
|
||||||
|
this._config = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render() {
|
||||||
|
if (!this.hass || !this._config) return nothing;
|
||||||
|
return html`<ha-form
|
||||||
|
.hass=${this.hass}
|
||||||
|
.data=${this._config}
|
||||||
|
.schema=${SCHEMA}
|
||||||
|
.computeLabel=${(s: any) => LABELS[s.name] || s.name}
|
||||||
|
@value-changed=${this._valueChanged}
|
||||||
|
></ha-form>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _valueChanged(ev: CustomEvent): void {
|
||||||
|
const config = { ...this._config, ...ev.detail.value };
|
||||||
|
const e = new Event('config-changed', { bubbles: true, composed: true }) as any;
|
||||||
|
e.detail = { config };
|
||||||
|
this.dispatchEvent(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!customElements.get('houseplan-card-editor')) {
|
||||||
|
customElements.define('houseplan-card-editor', HouseplanCardEditor);
|
||||||
|
}
|
||||||
+46
-46
@@ -7,109 +7,109 @@ export const ROOMS: Room[] = [
|
|||||||
"floor": "f1",
|
"floor": "f1",
|
||||||
"name": "Гостиная",
|
"name": "Гостиная",
|
||||||
"area": "gostinaia_na_dache",
|
"area": "gostinaia_na_dache",
|
||||||
"x": 510,
|
"x": 518,
|
||||||
"y": 205,
|
"y": 226,
|
||||||
"w": 430,
|
"w": 420,
|
||||||
"h": 310
|
"h": 292
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"floor": "f1",
|
"floor": "f1",
|
||||||
"name": "Гостевой с/у",
|
"name": "Гостевой с/у",
|
||||||
"area": "gostevoi_s_u",
|
"area": "gostevoi_s_u",
|
||||||
"x": 600,
|
"x": 615,
|
||||||
"y": 515,
|
"y": 524,
|
||||||
"w": 190,
|
"w": 162,
|
||||||
"h": 170
|
"h": 156
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"floor": "f1",
|
"floor": "f1",
|
||||||
"name": "Котельная",
|
"name": "Котельная",
|
||||||
"area": "kotelnaia",
|
"area": "kotelnaia",
|
||||||
"x": 600,
|
"x": 614,
|
||||||
"y": 690,
|
"y": 686,
|
||||||
"w": 190,
|
"w": 163,
|
||||||
"h": 180
|
"h": 169
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"floor": "f1",
|
"floor": "f1",
|
||||||
"name": "Кладовка уличная",
|
"name": "Кладовка уличная",
|
||||||
"area": "kladovka",
|
"area": "kladovka",
|
||||||
"x": 510,
|
"x": 518,
|
||||||
"y": 690,
|
"y": 686,
|
||||||
"w": 90,
|
"w": 82,
|
||||||
"h": 180
|
"h": 169
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"floor": "f1",
|
"floor": "f1",
|
||||||
"name": "Прихожая",
|
"name": "Прихожая",
|
||||||
"area": "prikhozhaia",
|
"area": "prikhozhaia",
|
||||||
"x": 790,
|
"x": 784,
|
||||||
"y": 600,
|
"y": 600,
|
||||||
"w": 150,
|
"w": 152,
|
||||||
"h": 270
|
"h": 255
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"floor": "f1",
|
"floor": "f1",
|
||||||
"name": "Гостевая спальня",
|
"name": "Гостевая спальня",
|
||||||
"area": "gostevaia_komnata",
|
"area": "gostevaia_komnata",
|
||||||
"x": 945,
|
"x": 949,
|
||||||
"y": 600,
|
"y": 611,
|
||||||
"w": 255,
|
"w": 201,
|
||||||
"h": 270
|
"h": 245
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"floor": "f2",
|
"floor": "f2",
|
||||||
"name": "Детская Майя",
|
"name": "Детская Майя",
|
||||||
"area": "detskaia_maiia",
|
"area": "detskaia_maiia",
|
||||||
"x": 365,
|
"x": 389,
|
||||||
"y": 40,
|
"y": 58,
|
||||||
"w": 435,
|
"w": 424,
|
||||||
"h": 315
|
"h": 297
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"floor": "f2",
|
"floor": "f2",
|
||||||
"name": "Детская Элина",
|
"name": "Детская Элина",
|
||||||
"area": "detskaia_elina",
|
"area": "detskaia_elina",
|
||||||
"x": 810,
|
"x": 830,
|
||||||
"y": 40,
|
"y": 58,
|
||||||
"w": 550,
|
"w": 426,
|
||||||
"h": 315
|
"h": 297
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"floor": "f2",
|
"floor": "f2",
|
||||||
"name": "Детский с/у",
|
"name": "Детский с/у",
|
||||||
"area": "detskii_s_u",
|
"area": "detskii_s_u",
|
||||||
"x": 390,
|
"x": 389,
|
||||||
"y": 360,
|
"y": 360,
|
||||||
"w": 260,
|
"w": 264,
|
||||||
"h": 180
|
"h": 168
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"floor": "f2",
|
"floor": "f2",
|
||||||
"name": "Мастер с/у",
|
"name": "Мастер с/у",
|
||||||
"area": "master_s_u",
|
"area": "master_s_u",
|
||||||
"x": 390,
|
"x": 389,
|
||||||
"y": 540,
|
"y": 545,
|
||||||
"w": 260,
|
"w": 264,
|
||||||
"h": 160
|
"h": 155
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"floor": "f2",
|
"floor": "f2",
|
||||||
"name": "Мастер-спальня",
|
"name": "Мастер-спальня",
|
||||||
"area": "spalnia",
|
"area": "spalnia",
|
||||||
"x": 650,
|
"x": 657,
|
||||||
"y": 600,
|
"y": 607,
|
||||||
"w": 330,
|
"w": 320,
|
||||||
"h": 320
|
"h": 311
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"floor": "f2",
|
"floor": "f2",
|
||||||
"name": "Кабинет",
|
"name": "Кабинет",
|
||||||
"area": "kabinet",
|
"area": "kabinet",
|
||||||
"x": 985,
|
"x": 984,
|
||||||
"y": 600,
|
"y": 607,
|
||||||
"w": 290,
|
"w": 274,
|
||||||
"h": 320
|
"h": 311
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"floor": "yard",
|
"floor": "yard",
|
||||||
|
|||||||
Executable
+63
@@ -0,0 +1,63 @@
|
|||||||
|
/**
|
||||||
|
* Правила прототипа — перенесены 1-в-1 из index.html/build_data.py (см. DOCUMENTATION.md §4.2–4.5).
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** Интеграции-домены, чьи устройства скрываются (курирование). */
|
||||||
|
export const EXCLUDED_DOMAINS = new Set([
|
||||||
|
'hacs', 'sun', 'backup', 'hassio', 'met', 'telegram_bot', 'mobile_app',
|
||||||
|
'systemmonitor', 'better_thermostat', 'adaptive_lighting', 'yandex_pogoda',
|
||||||
|
'upnp_serial_number',
|
||||||
|
]);
|
||||||
|
|
||||||
|
/** Заголовки групп ламп по area_id. */
|
||||||
|
export const GROUP_TITLES: Record<string, string> = {
|
||||||
|
gostinaia_na_dache: 'Настенные лампы',
|
||||||
|
prikhozhaia: 'Свет в прихожей',
|
||||||
|
detskaia_maiia: 'Свет у Майи',
|
||||||
|
detskaia_elina: 'Свет у Элины',
|
||||||
|
};
|
||||||
|
|
||||||
|
const ICON_RULES: Array<[string, string]> = [
|
||||||
|
['протечк', 'mdi:water-alert'],
|
||||||
|
['клапан', 'mdi:pipe-valve'],
|
||||||
|
['дым', 'mdi:smoke-detector'],
|
||||||
|
['термоголов', 'mdi:radiator'],
|
||||||
|
['температ', 'mdi:thermometer'],
|
||||||
|
['qingping|air monitor|молекул', 'mdi:air-filter'],
|
||||||
|
['штор', 'mdi:roller-shade'],
|
||||||
|
['розетк|plug', 'mdi:power-socket-de'],
|
||||||
|
['выключат|switch', 'mdi:light-switch'],
|
||||||
|
['лампа|лампочк|bulb|gx53|светильник|rgb', 'mdi:lightbulb'],
|
||||||
|
['камер|camera', 'mdi:cctv'],
|
||||||
|
['замок|ttlock|lock|sn609|sn9161', 'mdi:lock'],
|
||||||
|
['ворота|garage', 'mdi:garage-variant'],
|
||||||
|
['калитк|door|открыт', 'mdi:door'],
|
||||||
|
['счётчик|счетчик|kws|meter', 'mdi:meter-electric'],
|
||||||
|
['вводный автомат|breaker|wifimcbn', 'mdi:electric-switch'],
|
||||||
|
['myheat|котёл|котел|boiler|отоплен', 'mdi:water-boiler'],
|
||||||
|
['холодильник|fridge', 'mdi:fridge'],
|
||||||
|
['стиральн|washer', 'mdi:washing-machine'],
|
||||||
|
['сушилк|dryer', 'mdi:tumble-dryer'],
|
||||||
|
['пылесос|vacuum|dreame', 'mdi:robot-vacuum'],
|
||||||
|
['soundbar|колонк|станц', 'mdi:soundbar'],
|
||||||
|
['tv|телевизор|hyundaitv|mitv', 'mdi:television'],
|
||||||
|
['keenetic|роутер|router', 'mdi:router-wireless'],
|
||||||
|
['ибп|ups|kirpich', 'mdi:battery-charging-high'],
|
||||||
|
['slzb|координат|zigbee', 'mdi:zigbee'],
|
||||||
|
];
|
||||||
|
|
||||||
|
/** Подбор MDI-иконки по имени/модели устройства. */
|
||||||
|
export function iconFor(name?: string, model?: string): string {
|
||||||
|
const s = ((name || '') + ' ' + (model || '')).toLowerCase();
|
||||||
|
for (const [pat, icon] of ICON_RULES) {
|
||||||
|
if (new RegExp(pat).test(s)) return icon;
|
||||||
|
}
|
||||||
|
return 'mdi:chip';
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Приоритет доменов для выбора «первичной» сущности устройства (more-info). */
|
||||||
|
export const DOMAIN_PRIORITY = [
|
||||||
|
'light', 'switch', 'cover', 'valve', 'lock', 'climate', 'fan',
|
||||||
|
'media_player', 'camera', 'vacuum', 'humidifier', 'water_heater',
|
||||||
|
'alarm_control_panel', 'sensor', 'binary_sensor', 'event', 'button',
|
||||||
|
'number', 'select',
|
||||||
@@ -19,6 +19,7 @@ const SCHEMA = [
|
|||||||
{ name: 'icon_size', selector: { number: { min: 1, max: 6, step: 0.1, mode: 'box' } } },
|
{ name: 'icon_size', selector: { number: { min: 1, max: 6, step: 0.1, mode: 'box' } } },
|
||||||
{ name: 'show_temperature', selector: { boolean: {} } },
|
{ name: 'show_temperature', selector: { boolean: {} } },
|
||||||
{ name: 'live_states', selector: { boolean: {} } },
|
{ name: 'live_states', selector: { boolean: {} } },
|
||||||
|
{ name: 'show_signal', selector: { boolean: {} } },
|
||||||
];
|
];
|
||||||
|
|
||||||
const LABELS: Record<string, string> = {
|
const LABELS: Record<string, string> = {
|
||||||
@@ -27,6 +28,7 @@ const LABELS: Record<string, string> = {
|
|||||||
icon_size: 'Размер иконок, % ширины плана',
|
icon_size: 'Размер иконок, % ширины плана',
|
||||||
show_temperature: 'Показывать температуру',
|
show_temperature: 'Показывать температуру',
|
||||||
live_states: 'Живые состояния (вкл/выкл, открыто…)',
|
live_states: 'Живые состояния (вкл/выкл, открыто…)',
|
||||||
|
show_signal: 'Показывать сигнал zigbee (LQI)',
|
||||||
};
|
};
|
||||||
|
|
||||||
class HouseplanCardEditor extends LitElement {
|
class HouseplanCardEditor extends LitElement {
|
||||||
|
|||||||
+65
-8
@@ -9,7 +9,7 @@ import { FLOOR_BG, FLOOR_BG_RECT } from './data/backgrounds';
|
|||||||
import { EXCLUDED_DOMAINS, GROUP_TITLES, iconFor, DOMAIN_PRIORITY } from './rules';
|
import { EXCLUDED_DOMAINS, GROUP_TITLES, iconFor, DOMAIN_PRIORITY } from './rules';
|
||||||
import './editor';
|
import './editor';
|
||||||
|
|
||||||
const CARD_VERSION = '1.1.0';
|
const CARD_VERSION = '1.2.0';
|
||||||
const LS_KEY = 'houseplan_card_layout_v1';
|
const LS_KEY = 'houseplan_card_layout_v1';
|
||||||
|
|
||||||
interface DevItem {
|
interface DevItem {
|
||||||
@@ -34,9 +34,16 @@ interface CardConfig {
|
|||||||
icon_size?: number;
|
icon_size?: number;
|
||||||
show_temperature?: boolean;
|
show_temperature?: boolean;
|
||||||
live_states?: boolean;
|
live_states?: boolean;
|
||||||
|
show_signal?: boolean;
|
||||||
rooms?: Room[];
|
rooms?: Room[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Цвет LQI: ≤40 — красный, ≥180 — зелёный, между — градиент. */
|
||||||
|
const lqiColor = (lqi: number): string => {
|
||||||
|
const hue = Math.max(0, Math.min(120, ((lqi - 40) / 140) * 120));
|
||||||
|
return `hsl(${Math.round(hue)}, 85%, 55%)`;
|
||||||
|
};
|
||||||
|
|
||||||
const fireEvent = (node: EventTarget, type: string, detail?: unknown) => {
|
const fireEvent = (node: EventTarget, type: string, detail?: unknown) => {
|
||||||
const ev = new Event(type, { bubbles: true, composed: true }) as any;
|
const ev = new Event(type, { bubbles: true, composed: true }) as any;
|
||||||
ev.detail = detail ?? {};
|
ev.detail = detail ?? {};
|
||||||
@@ -70,7 +77,7 @@ class HouseplanCard extends LitElement {
|
|||||||
private _defPos: Record<string, { x: number; y: number }> = {};
|
private _defPos: Record<string, { x: number; y: number }> = {};
|
||||||
private _menuDev: DevItem | null = null;
|
private _menuDev: DevItem | null = null;
|
||||||
private _menuXY = { x: 0, y: 0 };
|
private _menuXY = { x: 0, y: 0 };
|
||||||
private _tip: { x: number; y: number; title: string; meta: string } | null = null;
|
private _tip: { x: number; y: number; title: string; meta: string; lqi?: number | null } | null = null;
|
||||||
private _selId: string | null = null;
|
private _selId: string | null = null;
|
||||||
private _toast = '';
|
private _toast = '';
|
||||||
private _toastTimer?: number;
|
private _toastTimer?: number;
|
||||||
@@ -100,7 +107,7 @@ class HouseplanCard extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public setConfig(config: CardConfig): void {
|
public setConfig(config: CardConfig): void {
|
||||||
this._config = { icon_size: 2.5, show_temperature: true, live_states: true, ...config };
|
this._config = { icon_size: 2.5, show_temperature: true, live_states: true, show_signal: true, ...config };
|
||||||
if (config.default_floor) this._floor = config.default_floor;
|
if (config.default_floor) this._floor = config.default_floor;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -214,6 +221,32 @@ class HouseplanCard extends LitElement {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** LQI zigbee-устройства: среднее по всем *_linkquality сущностям (для группы — по участникам). */
|
||||||
|
private _lqiFor(entIds: string[]): number | null {
|
||||||
|
const vals: number[] = [];
|
||||||
|
for (const eid of entIds) {
|
||||||
|
const st = this.hass.states[eid];
|
||||||
|
const isLqi = /_linkquality$/.test(eid) || (st?.attributes?.unit_of_measurement || '') === 'lqi';
|
||||||
|
if (!isLqi || !st) continue;
|
||||||
|
const v = parseFloat(st.state);
|
||||||
|
if (!isNaN(v)) vals.push(v);
|
||||||
|
}
|
||||||
|
if (!vals.length) return null;
|
||||||
|
return Math.round(vals.reduce((a, b) => a + b, 0) / vals.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Средний LQI zigbee-устройств комнаты. */
|
||||||
|
private _roomLqi(area: string): number | null {
|
||||||
|
const vals: number[] = [];
|
||||||
|
for (const d of this._devices) {
|
||||||
|
if (d.area !== area) continue;
|
||||||
|
const l = this._lqiFor(d.entities);
|
||||||
|
if (l != null) vals.push(l);
|
||||||
|
}
|
||||||
|
if (!vals.length) return null;
|
||||||
|
return Math.round(vals.reduce((a, b) => a + b, 0) / vals.length);
|
||||||
|
}
|
||||||
|
|
||||||
private _tempFor(entIds: string[]): number | null {
|
private _tempFor(entIds: string[]): number | null {
|
||||||
for (const eid of entIds) {
|
for (const eid of entIds) {
|
||||||
if (!this._isTempEntity(eid)) continue;
|
if (!this._isTempEntity(eid)) continue;
|
||||||
@@ -251,7 +284,9 @@ class HouseplanCard extends LitElement {
|
|||||||
const key = name + '|' + area;
|
const key = name + '|' + area;
|
||||||
if (seen[key]) continue;
|
if (seen[key]) continue;
|
||||||
seen[key] = 1;
|
seen[key] = 1;
|
||||||
const icon = iconFor(name, dev.model);
|
let icon = iconFor(name, dev.model);
|
||||||
|
// устройство с сущностью lock.* — всегда «замочек», как бы оно ни называлось
|
||||||
|
if (entIds.some((e) => e.startsWith('lock.'))) icon = 'mdi:lock';
|
||||||
const room = this._areaRoom(area)!;
|
const room = this._areaRoom(area)!;
|
||||||
const item: DevItem = {
|
const item: DevItem = {
|
||||||
id: dev.id,
|
id: dev.id,
|
||||||
@@ -453,9 +488,9 @@ class HouseplanCard extends LitElement {
|
|||||||
}, 3500);
|
}, 3500);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _showTip(ev: MouseEvent, title: string, meta: string): void {
|
private _showTip(ev: MouseEvent, title: string, meta: string, lqi?: number | null): void {
|
||||||
if (this._drag) return;
|
if (this._drag) return;
|
||||||
this._tip = { x: ev.clientX, y: ev.clientY, title, meta };
|
this._tip = { x: ev.clientX, y: ev.clientY, title, meta, lqi };
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------- рендер ----------
|
// ---------- рендер ----------
|
||||||
@@ -510,7 +545,9 @@ class HouseplanCard extends LitElement {
|
|||||||
class="room ${bg ? 'overlay' : 'yard'}"
|
class="room ${bg ? 'overlay' : 'yard'}"
|
||||||
x="${r.x}" y="${r.y}" width="${r.w}" height="${r.h}" rx="8"
|
x="${r.x}" y="${r.y}" width="${r.w}" height="${r.h}" rx="8"
|
||||||
@click=${() => this._clickRoom(r)}
|
@click=${() => this._clickRoom(r)}
|
||||||
@mousemove=${(e: MouseEvent) => this._showTip(e, r.name, 'комната — открыть зону')}
|
@mousemove=${(e: MouseEvent) =>
|
||||||
|
this._showTip(e, r.name, 'комната — открыть зону',
|
||||||
|
this._config?.show_signal ? this._roomLqi(r.area) : null)}
|
||||||
@mouseleave=${() => (this._tip = null)}
|
@mouseleave=${() => (this._tip = null)}
|
||||||
></rect>
|
></rect>
|
||||||
${!bg ? svg`<text class="rlabel" x="${r.x + r.w / 2}" y="${r.y + 26}">${r.name}</text>` : nothing}`,
|
${!bg ? svg`<text class="rlabel" x="${r.x + r.w / 2}" y="${r.y + 26}">${r.name}</text>` : nothing}`,
|
||||||
@@ -526,6 +563,10 @@ class HouseplanCard extends LitElement {
|
|||||||
${this._tip
|
${this._tip
|
||||||
? html`<div class="tip" style="left:${this._tip.x + 12}px;top:${this._tip.y + 12}px">
|
? html`<div class="tip" style="left:${this._tip.x + 12}px;top:${this._tip.y + 12}px">
|
||||||
<b>${this._tip.title}</b>${this._tip.meta ? html`<span class="m">${this._tip.meta}</span>` : nothing}
|
<b>${this._tip.title}</b>${this._tip.meta ? html`<span class="m">${this._tip.meta}</span>` : nothing}
|
||||||
|
${this._tip.lqi != null
|
||||||
|
? html`<span class="m">средний сигнал zigbee:
|
||||||
|
<b style="color:${lqiColor(this._tip.lqi)}">${this._tip.lqi}</b></span>`
|
||||||
|
: nothing}
|
||||||
</div>`
|
</div>`
|
||||||
: nothing}
|
: nothing}
|
||||||
${this._toast ? html`<div class="toast">${this._toast}</div>` : nothing}
|
${this._toast ? html`<div class="toast">${this._toast}</div>` : nothing}
|
||||||
@@ -539,12 +580,14 @@ class HouseplanCard extends LitElement {
|
|||||||
const top = ((p.y - vb[1]) / vb[3]) * 100;
|
const top = ((p.y - vb[1]) / vb[3]) * 100;
|
||||||
const cls = this._stateClass(d);
|
const cls = this._stateClass(d);
|
||||||
const temp = this._liveTemp(d);
|
const temp = this._liveTemp(d);
|
||||||
|
const lqi = this._config?.show_signal ? this._lqiFor(d.entities) : null;
|
||||||
return html`<div
|
return html`<div
|
||||||
class="dev ${cls} ${this._selId === d.id ? 'sel' : ''}"
|
class="dev ${cls} ${this._selId === d.id ? 'sel' : ''}"
|
||||||
style="left:${left}%;top:${top}%"
|
style="left:${left}%;top:${top}%"
|
||||||
@click=${(e: MouseEvent) => this._clickDevice(e, d)}
|
@click=${(e: MouseEvent) => this._clickDevice(e, d)}
|
||||||
@mousemove=${(e: MouseEvent) =>
|
@mousemove=${(e: MouseEvent) =>
|
||||||
this._showTip(e, d.name, d.model + (temp != null ? ' · ' + temp + '°' : ''))}
|
this._showTip(e, d.name,
|
||||||
|
d.model + (temp != null ? ' · ' + temp + '°' : '') + (lqi != null ? ' · LQI ' + lqi : ''))}
|
||||||
@mouseleave=${() => (this._tip = null)}
|
@mouseleave=${() => (this._tip = null)}
|
||||||
@pointerdown=${(e: PointerEvent) => this._pointerDown(e, d)}
|
@pointerdown=${(e: PointerEvent) => this._pointerDown(e, d)}
|
||||||
@pointermove=${(e: PointerEvent) => this._pointerMove(e, d)}
|
@pointermove=${(e: PointerEvent) => this._pointerMove(e, d)}
|
||||||
@@ -552,6 +595,7 @@ class HouseplanCard extends LitElement {
|
|||||||
>
|
>
|
||||||
<ha-icon icon="${d.icon}"></ha-icon>
|
<ha-icon icon="${d.icon}"></ha-icon>
|
||||||
${temp != null ? html`<span class="tval">${temp}°</span>` : nothing}
|
${temp != null ? html`<span class="tval">${temp}°</span>` : nothing}
|
||||||
|
${lqi != null ? html`<span class="lqi" style="color:${lqiColor(lqi)}">${lqi}</span>` : nothing}
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -807,6 +851,19 @@ class HouseplanCard extends LitElement {
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
.dev .lqi {
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
margin-top: calc(var(--icon-size, 2.5cqw) * 0.05);
|
||||||
|
font-size: calc(var(--icon-size, 2.5cqw) * 0.38);
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 1;
|
||||||
|
text-shadow: 0 0 3px rgba(0, 0, 0, 0.9), 0 0 2px rgba(0, 0, 0, 0.9);
|
||||||
|
white-space: nowrap;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
.editbar {
|
.editbar {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
+2
-3
@@ -29,7 +29,7 @@ const ICON_RULES: Array<[string, string]> = [
|
|||||||
['выключат|switch', 'mdi:light-switch'],
|
['выключат|switch', 'mdi:light-switch'],
|
||||||
['лампа|лампочк|bulb|gx53|светильник|rgb', 'mdi:lightbulb'],
|
['лампа|лампочк|bulb|gx53|светильник|rgb', 'mdi:lightbulb'],
|
||||||
['камер|camera', 'mdi:cctv'],
|
['камер|camera', 'mdi:cctv'],
|
||||||
['замок|ttlock|lock', 'mdi:lock-smart'],
|
['замок|ttlock|lock|sn609|sn9161', 'mdi:lock'],
|
||||||
['ворота|garage', 'mdi:garage-variant'],
|
['ворота|garage', 'mdi:garage-variant'],
|
||||||
['калитк|door|открыт', 'mdi:door'],
|
['калитк|door|открыт', 'mdi:door'],
|
||||||
['счётчик|счетчик|kws|meter', 'mdi:meter-electric'],
|
['счётчик|счетчик|kws|meter', 'mdi:meter-electric'],
|
||||||
@@ -60,5 +60,4 @@ export const DOMAIN_PRIORITY = [
|
|||||||
'light', 'switch', 'cover', 'valve', 'lock', 'climate', 'fan',
|
'light', 'switch', 'cover', 'valve', 'lock', 'climate', 'fan',
|
||||||
'media_player', 'camera', 'vacuum', 'humidifier', 'water_heater',
|
'media_player', 'camera', 'vacuum', 'humidifier', 'water_heater',
|
||||||
'alarm_control_panel', 'sensor', 'binary_sensor', 'event', 'button',
|
'alarm_control_panel', 'sensor', 'binary_sensor', 'event', 'button',
|
||||||
'number', 'select', 'update',
|
'number', 'select',
|
||||||
];
|
|
||||||
Reference in New Issue
Block a user