mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-29 03:09:36 +00:00
407 lines
15 KiB
TypeScript
407 lines
15 KiB
TypeScript
/** Shared types of the House Plan card. */
|
|
|
|
import type { HaBindingStatus } from './ha-binding-status';
|
|
|
|
export interface RoomCfg {
|
|
/** Deprecated pre-v9 room links used only by legacy compatibility readers. */
|
|
open_to?: string[] | null;
|
|
/** Room-level settings (tier 3 of 4: global > space > ROOM > device). */
|
|
settings?: {
|
|
/** Fill override; unset = inherit the space fill mode. */
|
|
/** `glow` is a permanent legacy read token. */
|
|
fill_mode?: 'none' | 'lqi' | 'light' | 'temp' | 'custom' | 'glow' | null;
|
|
/** Explicit custom fill; absent/null uses the space custom fill. */
|
|
custom_fill?: { c: string; a: number } | null;
|
|
/** Independent per-room Glow override; null/absent inherits the space. */
|
|
glow?: boolean | null;
|
|
/** 'device:<id>' or 'entity:<eid>'; unset = average over the room sensors. */
|
|
temp_source?: string | null;
|
|
hum_source?: string | null;
|
|
/** Room comfort bounds; each absent/null side inherits from the space. */
|
|
temp_min?: number | null;
|
|
temp_max?: number | null;
|
|
/** Font multipliers for THIS room's card (0.5-3, unset = 1). */
|
|
name_scale?: number | null;
|
|
label_scale?: number | null;
|
|
} | null;
|
|
id?: string;
|
|
name: string;
|
|
area: string | null;
|
|
x?: number;
|
|
y?: number;
|
|
w?: number;
|
|
h?: number;
|
|
poly?: number[][]; // polygon in render units (model) / normalized (config)
|
|
/** v8: one stable contour-wall id for every edge in `poly`. */
|
|
wall_ids?: string[];
|
|
}
|
|
|
|
/** Wall thickness entry (docs/WALL-THICKNESS.md) — always centimetres in config. */
|
|
export interface WallEntry {
|
|
key: string;
|
|
cm: number;
|
|
/** Exact interval endpoints in normalized config coordinates (new writes). */
|
|
a?: number[];
|
|
b?: number[];
|
|
}
|
|
|
|
/** Canonical v8 contour-wall atom. `cm: 0` is a thin contour, not masonry. */
|
|
export interface WallSegmentEntry {
|
|
id: string;
|
|
a: number[];
|
|
b: number[];
|
|
cm: number;
|
|
[key: string]: unknown;
|
|
}
|
|
|
|
// #33: exported list drives the schema parity test; the type derives from it
|
|
// so the two can never diverge.
|
|
export const ZERO_WALL_STYLES = ['dashed', 'solid'] as const;
|
|
export type ZeroWallStyle = (typeof ZERO_WALL_STYLES)[number];
|
|
|
|
/** A single independent physical wall which never owns or splits a room. */
|
|
export interface PartitionCfg {
|
|
id: string;
|
|
a: number[];
|
|
b: number[];
|
|
cm: number;
|
|
}
|
|
|
|
/** A physical column. `cm` is the outer side/diameter. */
|
|
export type WallColumnCfg =
|
|
| { id: string; shape: 'square'; center: number[]; cm: number; angle?: number }
|
|
| { id: string; shape: 'circle'; center: number[]; cm: number };
|
|
|
|
export interface SpaceModel {
|
|
id: string;
|
|
title: string;
|
|
/** Canonical centimetres represented by one stored grid cell. */
|
|
cellCm?: number;
|
|
vb: number[]; // render units
|
|
bg: { href: string; x: number; y: number; w: number; h: number; angle?: number } | null;
|
|
rooms: RoomCfg[]; // render units
|
|
/** Canonical contour-wall atoms in render units. */
|
|
wall_segments: WallSegmentEntry[];
|
|
partitions: PartitionCfg[];
|
|
wall_columns: WallColumnCfg[];
|
|
}
|
|
|
|
export interface PdfRef {
|
|
name: string;
|
|
url: string;
|
|
}
|
|
|
|
export type ValueBadgePosition = 'right' | 'bottom' | 'left' | 'top';
|
|
|
|
export type ValueBadgeSource =
|
|
| { kind: 'entity_state'; entity_id: string }
|
|
| { kind: 'entity_attribute'; entity_id: string; attribute: string }
|
|
| { kind: 'derived_lqi' }
|
|
| { kind: 'derived_marker_state'; ref: `marker:${string}` };
|
|
|
|
export interface MarkerValueBadge {
|
|
enabled: boolean;
|
|
source?: ValueBadgeSource | null;
|
|
position: ValueBadgePosition;
|
|
}
|
|
|
|
export type RadarProfile = 'esphome_ld2450_v1' | 'cartesian_v1' | 'polar_v1'
|
|
| 'range_v1' | 'zones_v1' | 'presence_v1';
|
|
export type RadarLengthUnit = 'mm' | 'cm' | 'm' | 'in' | 'ft';
|
|
export interface RadarPoint { x: number; y: number }
|
|
export interface RadarCartesianSlot {
|
|
id: string; x_entity: string; y_entity: string; unit: RadarLengthUnit;
|
|
swap_xy?: boolean; x_sign?: 1 | -1; y_sign?: 1 | -1; presence_entity?: string;
|
|
}
|
|
export interface RadarPolarSlot {
|
|
id: string; distance_entity: string; angle_entity: string; unit: RadarLengthUnit;
|
|
angle_unit: 'degrees' | 'radians'; angle_zero: 'forward' | 'right';
|
|
angle_clockwise: boolean; presence_entity?: string;
|
|
}
|
|
export interface RadarRangeSource {
|
|
id: string; entity_id: string; unit: RadarLengthUnit; presence_entity?: string;
|
|
}
|
|
export interface RadarZoneSource {
|
|
id: string; kind: 'occupancy' | 'count'; entity_id: string;
|
|
}
|
|
export interface MarkerRadar {
|
|
version: 1;
|
|
enabled: boolean;
|
|
show_live?: boolean;
|
|
profile: RadarProfile;
|
|
sources: {
|
|
slots?: (RadarCartesianSlot | RadarPolarSlot)[];
|
|
ranges?: RadarRangeSource[];
|
|
zones?: RadarZoneSource[];
|
|
occupancy_entity?: string;
|
|
count_entity?: string;
|
|
availability_entity?: string;
|
|
};
|
|
mount: {
|
|
installation_id: string; x: number; y: number; heading_deg: number;
|
|
range_cm?: number; fov_deg?: number;
|
|
};
|
|
room_id: string;
|
|
calibration: {
|
|
method: 'manual' | 'two_point' | 'not_required'; mirror: boolean; cell_cm: number;
|
|
refs?: { plan: RadarPoint; local_cm: RadarPoint }[]; rms_cm?: number;
|
|
};
|
|
allowed_room_ids?: string[];
|
|
[key: string]: unknown;
|
|
}
|
|
|
|
/** Config marker: edits/augments an auto-discovered device OR describes a manual/virtual icon. */
|
|
export const VACUUM_TRAIL_MODES = ['never', 'cleaning', 'always'] as const; // #33 parity
|
|
export type VacuumTrailMode = (typeof VACUUM_TRAIL_MODES)[number];
|
|
|
|
export interface Marker {
|
|
id: string;
|
|
binding: string; // 'device:<id>' | 'entity:<eid>' | 'virtual'
|
|
space?: string | null;
|
|
area?: string | null;
|
|
hidden?: boolean;
|
|
/** Tombstone: excluded from every plan renderer/aggregate, but its binding
|
|
* remains available in the Add-device picker. */
|
|
removed?: boolean;
|
|
name?: string | null;
|
|
icon?: string | null;
|
|
model?: string | null;
|
|
link?: string | null;
|
|
description?: string | null;
|
|
pdfs?: PdfRef[];
|
|
/** Per-device override. `cover` is legacy read/import compatibility only;
|
|
* the current editor writes `toggle`. */
|
|
tap_action?: string | null;
|
|
/** 'run' target: automation./script./scene. entity id. */
|
|
tap_target?: string | null;
|
|
/** Ask before toggle/run — accidental-tap guard (owner's spec). */
|
|
tap_confirm?: boolean | null;
|
|
/** live robot vacuum (docs/VACUUM.md); absent on non-vacuum markers */
|
|
vacuum?: {
|
|
live?: boolean | null;
|
|
trail?: boolean | null; // legacy bool; trail_mode wins
|
|
trail_mode?: VacuumTrailMode | null;
|
|
room_highlight?: boolean | null;
|
|
source?: string | null;
|
|
/** Legacy-read after #162: `map_id -> matrix`, no source, no space. */
|
|
calibration?: Record<string, number[]>;
|
|
segment_map?: Record<string, string>;
|
|
/** Canonical map->space routing (#162, docs/VACUUM.md). */
|
|
map_routes?: VacuumMapRoute[] | null;
|
|
} | null;
|
|
/** Optional #485 presence-radar setup. Unknown future versions remain inert. */
|
|
radar?: MarkerRadar | Record<string, unknown> | null;
|
|
/** Manual placement into a House Plan room without an HA Area. Room-aware
|
|
* consumers resolve it together with `space`; visual placement is not its
|
|
* only use. */
|
|
room_id?: string | null;
|
|
/** `ripple` is legacy read compatibility; UI maps it to icon_ripple. */
|
|
display?: 'badge' | 'ripple' | 'icon_ripple' | 'value' | 'static_icon' | null; // how the device is drawn
|
|
ripple_color?: string | null;
|
|
ripple_size?: number | null; // max ring diameter, in icon diameters (default 1.5)
|
|
size?: number | null; // icon size multiplier (default 1)
|
|
angle?: number | null; // icon rotation, degrees
|
|
/** Entities this icon toggles as a group (wall switch → its lights). */
|
|
controls?: string[] | null;
|
|
/** Per-source glow radius in cm (glow fill); null = the global default. */
|
|
glow_radius_cm?: number | null;
|
|
/**
|
|
* Optional visual override for this source's Glow. Missing/null = use the
|
|
* live light colour and brightness; `bri` omitted/null keeps live brightness.
|
|
*/
|
|
glow_color?: { c: string; bri?: number | null } | null;
|
|
/**
|
|
* Light-source role: true = always use the marker's own controllable entity,
|
|
* false = never use its own entity, null/undefined = automatic discovery.
|
|
* External `controls` remain independent room-light votes in every mode.
|
|
*/
|
|
is_light?: boolean | null;
|
|
/** Explicit leading controllable entity for an Always source. Missing keeps
|
|
* the compatibility fallback (entity binding -> primary -> first control). */
|
|
light_entity?: string | null;
|
|
/** Exact own light/switch selected for Toggle. Missing/null keeps the
|
|
* historical action resolver and external-only controls groups. */
|
|
toggle_entity?: string | null;
|
|
/** Optional, user-controlled value satellite around the device face.
|
|
* Missing keeps the legacy temperature/humidity compatibility heuristic. */
|
|
value_badge?: MarkerValueBadge | null;
|
|
/** Explicit source for the `value` display face. Missing/null keeps the
|
|
* historical automatic entity-state resolver. */
|
|
value_source?: ValueBadgeSource | null;
|
|
/**
|
|
* Climate devices (AC, thermostat) know the room temperature
|
|
* (attributes.current_temperature). Opt-in per marker: show it as a badge
|
|
* next to the icon and include it in the room average. null/absent = off —
|
|
* nothing changes without an explicit tick (owner's spec, 2026-08-03).
|
|
*/
|
|
use_climate_temp?: boolean | null;
|
|
}
|
|
|
|
/** One saved answer to "this exact map of this exact source lives here" (#162). */
|
|
export interface VacuumMapRoute {
|
|
id: string;
|
|
source: string;
|
|
map_id: string;
|
|
space: string;
|
|
calibration?: [number, number, number, number, number, number] | null;
|
|
}
|
|
|
|
/** A door, window, gate or open passage: plan geometry (normalized coords). */
|
|
export interface PartitionOpeningHost {
|
|
kind: 'partition';
|
|
/** Stable id of one saved independent wall segment in the same space. */
|
|
id: string;
|
|
/** Centre position along the directed partition a -> b. */
|
|
t: number;
|
|
}
|
|
|
|
export interface WallOpeningHost {
|
|
kind: 'wall';
|
|
/** Stable id of one authoritative contour-wall atom in the same space. */
|
|
id: string;
|
|
/** Centre position along the canonical stored segment a -> b. */
|
|
t: number;
|
|
}
|
|
|
|
export type OpeningHost = PartitionOpeningHost | WallOpeningHost;
|
|
|
|
export const OPENING_TYPES = ['door', 'window', 'gate', 'passage'] as const; // #33 parity
|
|
export type OpeningType = (typeof OPENING_TYPES)[number];
|
|
|
|
export interface OpeningCfg {
|
|
id: string;
|
|
type: OpeningType;
|
|
x: number; // center, normalized by plan width
|
|
y: number; // center, normalized by plan height
|
|
angle: number; // wall angle, degrees
|
|
length: number; // along the wall, normalized by plan width
|
|
contact?: string | null; // binary_sensor / cover driving open-closed
|
|
lock?: string | null; // lock entity (door-like openings: doors and gates)
|
|
invert?: boolean;
|
|
flip_h?: boolean; // hinge on the other jamb
|
|
flip_v?: boolean; // opens to the other side of the wall
|
|
/** Explicit owner for an opening cut into an independent wall. */
|
|
host?: OpeningHost;
|
|
}
|
|
|
|
export interface ServerConfig {
|
|
model_version?: number;
|
|
spaces: any[];
|
|
markers: Marker[];
|
|
settings: {
|
|
exclude_integrations?: string[];
|
|
group_lights?: boolean;
|
|
show_all?: boolean; // legacy: removed when the config is materialised
|
|
/** The filter flags are materialised into markers (docs/FILTERING.md). */
|
|
filter_seeded?: boolean;
|
|
known_devices?: string[];
|
|
new_device_ids?: string[];
|
|
/** Internal registry-Area provenance; not a user-facing setting. */
|
|
marker_area_snapshot?: MarkerAreaSnapshot;
|
|
icon_rules?: { pattern: string; icon: string }[];
|
|
/** Only an explicit false hides the room hover information window. */
|
|
show_room_tooltip?: boolean;
|
|
/** Opt-in, admin-only contextual Zigbee diagnostics (#54). */
|
|
zigbee_topology?: { enabled?: boolean; z2m_base_topics?: string[] };
|
|
/** Read-only grouped values shown over the plan (#437). */
|
|
summary_panel?: SummaryPanelConfig;
|
|
radar?: {
|
|
version?: 1;
|
|
show_live?: boolean;
|
|
[key: string]: unknown;
|
|
};
|
|
};
|
|
}
|
|
|
|
export type SummaryPanelSystemKey = 'device_count' | 'total_area' | 'datetime';
|
|
export type SummaryPanelSource =
|
|
| ({ type: 'entity'; entity_id: string } & Record<string, unknown>)
|
|
| ({ type: 'system'; key: SummaryPanelSystemKey } & Record<string, unknown>);
|
|
|
|
export type SummaryPanelScope =
|
|
| ({ type: 'all' } & Record<string, unknown>)
|
|
| ({ type: 'space'; space_id: string } & Record<string, unknown>);
|
|
|
|
export interface SummaryPanelValue extends Record<string, unknown> {
|
|
id: string;
|
|
label: string;
|
|
source: SummaryPanelSource;
|
|
}
|
|
|
|
export interface SummaryPanelBlock extends Record<string, unknown> {
|
|
id: string;
|
|
title: string;
|
|
visible: boolean;
|
|
scope: SummaryPanelScope;
|
|
values: SummaryPanelValue[];
|
|
}
|
|
|
|
export interface SummaryPanelConfig extends Record<string, unknown> {
|
|
version: 1;
|
|
title: string;
|
|
show_on_mobile: boolean;
|
|
blocks: SummaryPanelBlock[];
|
|
}
|
|
|
|
export type MarkerAreaBinding = `device:${string}` | `entity:${string}`;
|
|
|
|
export interface MarkerAreaSnapshotEntry {
|
|
binding: MarkerAreaBinding;
|
|
area: string;
|
|
}
|
|
|
|
export type MarkerAreaSnapshot = Record<string, MarkerAreaSnapshotEntry>;
|
|
|
|
export interface DevItem {
|
|
id: string;
|
|
name: string;
|
|
model: string;
|
|
area: string;
|
|
space: string;
|
|
/** Effective presentation hide: explicit user hide OR HA-disabled. */
|
|
hidden?: boolean;
|
|
/** Persisted user choice only; never inferred from HA runtime status. */
|
|
userHidden?: boolean;
|
|
/** Central registry decision. Virtual/legacy items are active. */
|
|
bindingStatus?: HaBindingStatus;
|
|
icon: string;
|
|
/** Active runtime entities only. */
|
|
entities: string[];
|
|
/** Registry metadata only; never use for states/actions/aggregates. */
|
|
allEntities?: string[];
|
|
primary?: string;
|
|
temp?: number | null;
|
|
hum?: number | null;
|
|
virtual?: boolean;
|
|
marker?: Marker; // linked config marker (metadata, overrides)
|
|
/** Runtime-effective controls. The marker retains the user's complete
|
|
* persisted list so transient tombstones cannot erase it on the next save. */
|
|
controls?: string[];
|
|
bindingKind?: 'device' | 'entity' | 'virtual';
|
|
bindingRef?: string; // device_id / entity_id
|
|
link?: string | null;
|
|
description?: string | null;
|
|
pdfs?: PdfRef[];
|
|
tapAction?: string | null; // from the marker override
|
|
}
|
|
|
|
export interface CardConfig {
|
|
type: string;
|
|
title?: string;
|
|
floor?: string | number;
|
|
default_floor?: string;
|
|
icon_size?: number;
|
|
show_temperature?: boolean;
|
|
live_states?: boolean;
|
|
show_signal?: boolean;
|
|
/** Registry language code, or empty/absent for the Home Assistant profile. */
|
|
language?: string;
|
|
/** @deprecated Ignored since v1.38.1 — per-marker `tap_action` only.
|
|
* Kept so old YAML does not break; no runtime action reads this field. */
|
|
tap_action?: string;
|
|
/** Wall-device (kiosk) mode: no header, no editors, swipe between spaces. */
|
|
kiosk?: boolean;
|
|
/** Kiosk auto-carousel: seconds between space switches, 0/undefined = off. */
|
|
cycle?: number;
|
|
}
|