mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-04 21:58:56 +00:00
feat(backend): LED strips are stored, normalised and transferred (#780)
Stage 1 of #780 — the data model. A space carries an optional `led_strips: [{id, points, marker, active?}]` (custom_components/houseplan/ led_strips.py, pure, strict mypy). - Type schema inside SPACE_SCHEMA: 2–50 finite numeric points (no strings, booleans, NaN or off-canvas values), ≤50 strips per space including hidden shapes, strict boolean `active`, marker = non-empty string or null. - A config-level step after coordinate canonicalisation judges the shape (two distinct points, non-zero length, a closed strip needs three distinct vertices, a hidden shape needs a marker, unique ids per space) and the links in the order the spec fixes: duplicates are rejected before any normalisation (two links to the same missing id still conflict); a link to a marker that is not live becomes an unbound strip (marker null, active true, id and points kept), so a client that does not know strips can delete a bound marker without its save failing; a live marker with an empty space adopts the strip's space; a non-empty foreign space rejects the write. - config/set answers with `led_strips: {unbound, space_adopted}` when the write was normalised, so a new client re-reads; old clients ignore it. - Space import remaps links through the marker id map; a skipped or virtualised duplicate leaves the strip unbound; a coinciding old id never binds. Plan-only export keeps geometry and nulls every link. Import details report `unbound_led_strips`, computed by the server, never read from the file. - Coordinates get JSON-noise cleanup only, like stairs (face contacts are off-lattice), in both canonicalisers with a shared fixture case. - Support package: counters only (total/unbound/hidden), no coordinates or ids. Tests: tests_backend/test_led_strips.py (41, pure), test_ha_import_export (6 cases: full round trip with a hidden shape, orphan count, remap against a coinciding id, skip and virtual duplicates, plan-only), test_ha_websocket (old client deletes a bound marker → save stands, counters, foreign space rejects without a new revision). Full backend with the HA harness: 969 passed. Mutating the shape check, the duplicate check, the orphan normalisation or the space adoption each turns the pure suite red. Issue: #780 User-Visible: no
This commit is contained in:
@@ -96,6 +96,16 @@ function isDecorBoxKind(value: unknown): value is DecorBoxKind {
|
||||
&& (DECOR_BOX_KINDS as readonly string[]).includes(value);
|
||||
}
|
||||
|
||||
function scalarPoints(value: unknown): void {
|
||||
if (!Array.isArray(value)) return;
|
||||
for (const point of value) {
|
||||
if (!Array.isArray(point)) continue;
|
||||
for (let index = 0; index < Math.min(2, point.length); index++) {
|
||||
point[index] = canonicalizeNumber(point[index]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function scalarFields(item: JsonRecord, names: readonly string[]): void {
|
||||
for (const name of names) {
|
||||
if (Object.prototype.hasOwnProperty.call(item, name)) {
|
||||
@@ -382,6 +392,10 @@ export function canonicalizeConfigGeometryInPlace<T>(config: T): T {
|
||||
latticePoint(span.a);
|
||||
latticePoint(span.b);
|
||||
}
|
||||
|
||||
// #780: LED strips snap to physical wall faces like stairs: JSON noise
|
||||
// only, a face contact never moves onto a lattice node.
|
||||
for (const strip of records(space.led_strips)) scalarPoints(strip.points);
|
||||
}
|
||||
|
||||
for (const marker of records(root.markers)) scalarFields(marker, ['angle']);
|
||||
|
||||
Reference in New Issue
Block a user