mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-05 22:29:05 +00:00
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
479 lines
12 KiB
JSON
479 lines
12 KiB
JSON
{
|
|
"decimals": 9,
|
|
"boxKinds": ["rect", "ellipse", "furniture", "image"],
|
|
"configInput": {
|
|
"spaces": [
|
|
{
|
|
"id": "floor",
|
|
"title": "Floor",
|
|
"cell_cm": 5.0000000004,
|
|
"plan_aspect": 1.5000000004,
|
|
"plan_x": 0.1234567894,
|
|
"plan_y": -0.1234567896,
|
|
"plan_scale": 1.0000000001,
|
|
"plan_scale_x": 1.2345678906,
|
|
"plan_scale_y": 0.9876543216,
|
|
"plan_angle": 44.1234567896,
|
|
"view_box": [0.0000000004, -0.0000000004, 1.0000000004, 1.0000000004],
|
|
"settings": {
|
|
"room_opacity": 0.5000000004,
|
|
"temp_min": 18.1234567896,
|
|
"temp_max": 28.1234567896,
|
|
"card_font_scale": 1.0000000004
|
|
},
|
|
"rooms": [
|
|
{
|
|
"id": "poly",
|
|
"name": "Poly",
|
|
"area": null,
|
|
"poly": [
|
|
[0.0000000004, -0.0000000004],
|
|
[0.3000000004, 0.0000000004],
|
|
[0.0000000004, 0.3000000004]
|
|
],
|
|
"settings": {
|
|
"name_scale": 1.0000000004,
|
|
"label_scale": 1.2000000004
|
|
}
|
|
},
|
|
{
|
|
"id": "rect",
|
|
"name": "Rect",
|
|
"area": null,
|
|
"x": 0.4000000004,
|
|
"y": 0.4000000006,
|
|
"w": 0.2000000004,
|
|
"h": 0.2000000006
|
|
}
|
|
],
|
|
"walls": [
|
|
{
|
|
"key": "wall",
|
|
"cm": 15.0000000004,
|
|
"a": [0.0000000004, 0.3000000004],
|
|
"b": [0.3000000006, 0.3000000006]
|
|
}
|
|
],
|
|
"openings": [
|
|
{
|
|
"id": "opening",
|
|
"type": "door",
|
|
"x": 0.5000000004,
|
|
"y": 0.0000000004,
|
|
"angle": -0.0000000004,
|
|
"length": 0.2000000006,
|
|
"host": {
|
|
"kind": "partition",
|
|
"id": "partition",
|
|
"t": 0.5000000004
|
|
}
|
|
}
|
|
],
|
|
"decor": [
|
|
{
|
|
"id": "line",
|
|
"kind": "line",
|
|
"x1": 0.1000000004,
|
|
"y1": 0.1000000006,
|
|
"x2": 0.2000000004,
|
|
"y2": 0.2000000006,
|
|
"width_cm": 2.0000000004,
|
|
"opacity": 0.7000000004
|
|
},
|
|
{
|
|
"id": "rect-decor",
|
|
"kind": "rect",
|
|
"x": 0.1000000004,
|
|
"y": 0.2000000006,
|
|
"w": 0.3000000004,
|
|
"h": 0.4000000006,
|
|
"angle": 12.1234567896,
|
|
"fill_opacity": 0.6000000004
|
|
},
|
|
{
|
|
"id": "ellipse",
|
|
"kind": "ellipse",
|
|
"x": -0.1000000004,
|
|
"y": -0.2000000006,
|
|
"w": 0.3000000006,
|
|
"h": 0.4000000004,
|
|
"angle": -12.1234567896
|
|
},
|
|
{
|
|
"id": "text",
|
|
"kind": "text",
|
|
"x": 0.7000000004,
|
|
"y": 0.8000000006,
|
|
"text": "Text",
|
|
"scale": 1.2345678906,
|
|
"angle": 2.1234567896,
|
|
"size_cm": 25.0000000004
|
|
},
|
|
{
|
|
"id": "chair",
|
|
"kind": "furniture",
|
|
"symbol": "chair",
|
|
"x": 0.6000000004,
|
|
"y": 0.6000000006,
|
|
"w": 0.1000000004,
|
|
"h": 0.1000000006,
|
|
"angle": 90.1234567896
|
|
},
|
|
{
|
|
"id": "image",
|
|
"kind": "image",
|
|
"asset_id": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
|
"x": 0.5000000004,
|
|
"y": 0.5000000006,
|
|
"w": 0.2000000004,
|
|
"h": 0.3000000006,
|
|
"angle": 45.1234567896,
|
|
"opacity": 0.5000000004,
|
|
"flip_h": true,
|
|
"flip_v": false,
|
|
"future": {"numeric": 0.1234567896}
|
|
}
|
|
],
|
|
"room_drafts": [
|
|
{
|
|
"id": "draft",
|
|
"points": [
|
|
[0.1000000004, 0.9000000006],
|
|
[0.2000000006, 0.9000000004],
|
|
[0.3000000004, 0.9000000006]
|
|
],
|
|
"segments": [
|
|
{"cm": 10.0000000004},
|
|
{"cm": 20.0000000004}
|
|
]
|
|
}
|
|
],
|
|
"partitions": [
|
|
{
|
|
"id": "partition",
|
|
"a": [0.0000000004, 0.0000000004],
|
|
"b": [1.0000000004, 0.0000000004],
|
|
"cm": 12.0000000004
|
|
}
|
|
],
|
|
"wall_columns": [
|
|
{
|
|
"id": "column",
|
|
"shape": "square",
|
|
"center": [0.7500000004, 0.7500000006],
|
|
"cm": 30.0000000004,
|
|
"angle": 45.1234567896
|
|
}
|
|
],
|
|
"open_spans": [
|
|
{
|
|
"a": [0.3000000004, 0.3000000006],
|
|
"b": [0.6000000006, 0.3000000004]
|
|
}
|
|
],
|
|
"led_strips": [
|
|
{
|
|
"id": "led-1",
|
|
"points": [[0.3000000004, 0.4166666671], [0.1234567896, -0.0000000004]],
|
|
"marker": null
|
|
}
|
|
],
|
|
"future": {
|
|
"numeric": 0.1234567896
|
|
}
|
|
}
|
|
],
|
|
"markers": [
|
|
{
|
|
"id": "virtual",
|
|
"binding": "virtual",
|
|
"angle": -30.1234567896,
|
|
"size": 1.1000000004,
|
|
"ripple_size": 1.5000000004,
|
|
"glow_radius_cm": 120.0000000004,
|
|
"glow_color": {
|
|
"c": "#112233",
|
|
"bri": 0.5000000004
|
|
},
|
|
"vacuum": {
|
|
"calibration": {
|
|
"map": [
|
|
-0.0017921234567,
|
|
0.0000000004,
|
|
1.0000000004,
|
|
0.0017921234567,
|
|
-0.0000000004,
|
|
2.0000000004
|
|
]
|
|
}
|
|
}
|
|
}
|
|
],
|
|
"settings": {
|
|
"glow_radius_cm": 150.0000000004,
|
|
"fill_colors": {
|
|
"on": {
|
|
"c": "#abcdef",
|
|
"a": 0.5000000004
|
|
}
|
|
},
|
|
"future_numeric": 0.9876543216
|
|
},
|
|
"future_root": {
|
|
"numeric": 0.1234567896
|
|
}
|
|
},
|
|
"configExpected": {
|
|
"spaces": [
|
|
{
|
|
"id": "floor",
|
|
"title": "Floor",
|
|
"cell_cm": 5.0000000004,
|
|
"plan_aspect": 1.5000000004,
|
|
"plan_x": 0.123456789,
|
|
"plan_y": -0.12345679,
|
|
"plan_scale": 1,
|
|
"plan_scale_x": 1.234567891,
|
|
"plan_scale_y": 0.987654322,
|
|
"plan_angle": 44.12345679,
|
|
"view_box": [0.0000000004, -0.0000000004, 1.0000000004, 1.0000000004],
|
|
"settings": {
|
|
"room_opacity": 0.5000000004,
|
|
"temp_min": 18.1234567896,
|
|
"temp_max": 28.1234567896,
|
|
"card_font_scale": 1.0000000004
|
|
},
|
|
"rooms": [
|
|
{
|
|
"id": "poly",
|
|
"name": "Poly",
|
|
"area": null,
|
|
"poly": [
|
|
[0, 0],
|
|
[0.3, 0],
|
|
[0, 0.3]
|
|
],
|
|
"settings": {
|
|
"name_scale": 1.0000000004,
|
|
"label_scale": 1.2000000004
|
|
}
|
|
},
|
|
{
|
|
"id": "rect",
|
|
"name": "Rect",
|
|
"area": null,
|
|
"x": 0.4,
|
|
"y": 0.4,
|
|
"w": 0.2,
|
|
"h": 0.2
|
|
}
|
|
],
|
|
"walls": [
|
|
{
|
|
"key": "wall",
|
|
"cm": 15.0000000004,
|
|
"a": [0, 0.3],
|
|
"b": [0.3, 0.3]
|
|
}
|
|
],
|
|
"openings": [
|
|
{
|
|
"id": "opening",
|
|
"type": "door",
|
|
"x": 0.5,
|
|
"y": 0,
|
|
"angle": 0,
|
|
"length": 0.200000001,
|
|
"host": {
|
|
"kind": "partition",
|
|
"id": "partition",
|
|
"t": 0.5
|
|
}
|
|
}
|
|
],
|
|
"decor": [
|
|
{
|
|
"id": "line",
|
|
"kind": "line",
|
|
"x1": 0.1,
|
|
"y1": 0.1,
|
|
"x2": 0.2,
|
|
"y2": 0.2,
|
|
"width_cm": 2.0000000004,
|
|
"opacity": 0.7000000004
|
|
},
|
|
{
|
|
"id": "rect-decor",
|
|
"kind": "rect",
|
|
"x": 0.1,
|
|
"y": 0.2,
|
|
"w": 0.3,
|
|
"h": 0.4,
|
|
"angle": 12.12345679,
|
|
"fill_opacity": 0.6000000004
|
|
},
|
|
{
|
|
"id": "ellipse",
|
|
"kind": "ellipse",
|
|
"x": -0.1,
|
|
"y": -0.2,
|
|
"w": 0.3,
|
|
"h": 0.4,
|
|
"angle": -12.12345679
|
|
},
|
|
{
|
|
"id": "text",
|
|
"kind": "text",
|
|
"x": 0.7,
|
|
"y": 0.8,
|
|
"text": "Text",
|
|
"scale": 1.234567891,
|
|
"angle": 2.12345679,
|
|
"size_cm": 25.0000000004
|
|
},
|
|
{
|
|
"id": "chair",
|
|
"kind": "furniture",
|
|
"symbol": "chair",
|
|
"x": 0.6,
|
|
"y": 0.6,
|
|
"w": 0.1,
|
|
"h": 0.1,
|
|
"angle": 90.12345679
|
|
},
|
|
{
|
|
"id": "image",
|
|
"kind": "image",
|
|
"asset_id": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
|
"x": 0.5,
|
|
"y": 0.5,
|
|
"w": 0.2,
|
|
"h": 0.3,
|
|
"angle": 45.12345679,
|
|
"opacity": 0.5000000004,
|
|
"flip_h": true,
|
|
"flip_v": false,
|
|
"future": {"numeric": 0.1234567896}
|
|
}
|
|
],
|
|
"room_drafts": [
|
|
{
|
|
"id": "draft",
|
|
"points": [
|
|
[0.1, 0.9],
|
|
[0.2, 0.9],
|
|
[0.3, 0.9]
|
|
],
|
|
"segments": [
|
|
{"cm": 10.0000000004},
|
|
{"cm": 20.0000000004}
|
|
]
|
|
}
|
|
],
|
|
"partitions": [
|
|
{
|
|
"id": "partition",
|
|
"a": [0, 0],
|
|
"b": [1, 0],
|
|
"cm": 12.0000000004
|
|
}
|
|
],
|
|
"wall_columns": [
|
|
{
|
|
"id": "column",
|
|
"shape": "square",
|
|
"center": [0.75, 0.75],
|
|
"cm": 30.0000000004,
|
|
"angle": 45.12345679
|
|
}
|
|
],
|
|
"open_spans": [
|
|
{
|
|
"a": [0.3, 0.3],
|
|
"b": [0.6, 0.3]
|
|
}
|
|
],
|
|
"led_strips": [
|
|
{
|
|
"id": "led-1",
|
|
"points": [[0.3, 0.416666667], [0.12345679, 0]],
|
|
"marker": null
|
|
}
|
|
],
|
|
"future": {
|
|
"numeric": 0.1234567896
|
|
}
|
|
}
|
|
],
|
|
"markers": [
|
|
{
|
|
"id": "virtual",
|
|
"binding": "virtual",
|
|
"angle": -30.12345679,
|
|
"size": 1.1000000004,
|
|
"ripple_size": 1.5000000004,
|
|
"glow_radius_cm": 120.0000000004,
|
|
"glow_color": {
|
|
"c": "#112233",
|
|
"bri": 0.5000000004
|
|
},
|
|
"vacuum": {
|
|
"calibration": {
|
|
"map": [
|
|
-0.0017921234567,
|
|
0.0000000004,
|
|
1.0000000004,
|
|
0.0017921234567,
|
|
-0.0000000004,
|
|
2.0000000004
|
|
]
|
|
}
|
|
}
|
|
}
|
|
],
|
|
"settings": {
|
|
"glow_radius_cm": 150.0000000004,
|
|
"fill_colors": {
|
|
"on": {
|
|
"c": "#abcdef",
|
|
"a": 0.5000000004
|
|
}
|
|
},
|
|
"future_numeric": 0.9876543216
|
|
},
|
|
"future_root": {
|
|
"numeric": 0.1234567896
|
|
}
|
|
},
|
|
"layoutInput": {
|
|
"virtual": {
|
|
"s": "floor",
|
|
"x": 0.1234567894,
|
|
"y": -0.1234567896,
|
|
"k": 1.2000000004,
|
|
"future_numeric": 0.9876543216
|
|
},
|
|
"rl:poly": {
|
|
"s": "floor",
|
|
"x": -0.0000000004,
|
|
"y": 0.7500000006,
|
|
"k": 1.5000000004
|
|
}
|
|
},
|
|
"layoutExpected": {
|
|
"virtual": {
|
|
"s": "floor",
|
|
"x": 0.123456789,
|
|
"y": -0.12345679,
|
|
"k": 1.2000000004,
|
|
"future_numeric": 0.9876543216
|
|
},
|
|
"rl:poly": {
|
|
"s": "floor",
|
|
"x": 0,
|
|
"y": 0.75,
|
|
"k": 1.5000000004
|
|
}
|
|
}
|
|
}
|