lifx-emulator 2.3.0__tar.gz → 2.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/CLAUDE.md +24 -7
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/PKG-INFO +1 -1
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/docs/advanced/device-management-api.md +2 -2
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/docs/advanced/scenario-api.md +2 -2
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/docs/api/device.md +2 -2
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/docs/api/products.md +2 -2
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/docs/api/protocol.md +2 -2
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/docs/architecture/overview.md +1 -1
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/docs/changelog.md +16 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/docs/getting-started/cli.md +16 -6
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/docs/guide/device-types.md +77 -1
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/docs/guide/products-and-specs.md +10 -10
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/docs/guide/web-interface.md +3 -3
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/docs/index.md +1 -1
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/docs/tutorials/04-advanced-scenarios.md +1 -1
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/pyproject.toml +3 -8
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/src/lifx_emulator/__main__.py +13 -4
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/src/lifx_emulator/api/models.py +2 -2
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/src/lifx_emulator/api/templates/dashboard.html +8 -8
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/src/lifx_emulator/devices/device.py +58 -2
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/src/lifx_emulator/devices/states.py +6 -2
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/src/lifx_emulator/factories/__init__.py +2 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/src/lifx_emulator/factories/builder.py +4 -2
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/src/lifx_emulator/factories/factory.py +35 -4
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/src/lifx_emulator/handlers/light_handlers.py +155 -20
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/src/lifx_emulator/handlers/tile_handlers.py +22 -24
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/src/lifx_emulator/products/generator.py +77 -35
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/src/lifx_emulator/products/registry.py +46 -12
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/src/lifx_emulator/products/specs.py +2 -2
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/src/lifx_emulator/products/specs.yml +46 -12
- lifx_emulator-2.4.0/tests/test_backwards_compatibility.py +943 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/tests/test_cli.py +4 -4
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/tests/test_device.py +1 -1
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/tests/test_integration.py +2 -2
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/tests/test_products_generator.py +9 -10
- lifx_emulator-2.4.0/tests/test_switch_devices.py +335 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/tests/test_tile_handlers_extended.py +2 -2
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/uv.lock +1 -1
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/.github/workflows/ci.yml +0 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/.github/workflows/docs.yml +0 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/.gitignore +0 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/.pre-commit-config.yaml +0 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/LICENSE +0 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/README.md +0 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/docs/advanced/index.md +0 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/docs/advanced/scenarios.md +0 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/docs/advanced/storage.md +0 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/docs/api/factories.md +0 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/docs/api/index.md +0 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/docs/api/server.md +0 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/docs/api/storage.md +0 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/docs/architecture/device-state.md +0 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/docs/architecture/index.md +0 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/docs/architecture/packet-flow.md +0 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/docs/architecture/protocol.md +0 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/docs/assets/favicon.png +0 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/docs/faq.md +0 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/docs/getting-started/index.md +0 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/docs/getting-started/installation.md +0 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/docs/getting-started/quickstart.md +0 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/docs/guide/best-practices.md +0 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/docs/guide/framebuffers.md +0 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/docs/guide/index.md +0 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/docs/guide/integration-testing.md +0 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/docs/guide/testing-scenarios.md +0 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/docs/reference/glossary.md +0 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/docs/reference/troubleshooting.md +0 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/docs/stylesheets/extra.css +0 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/docs/tutorials/01-first-device.md +0 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/docs/tutorials/02-basic.md +0 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/docs/tutorials/03-integration.md +0 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/docs/tutorials/05-cicd.md +0 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/docs/tutorials/index.md +0 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/mkdocs.yml +0 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/renovate.json +0 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/src/lifx_emulator/__init__.py +0 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/src/lifx_emulator/api/__init__.py +0 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/src/lifx_emulator/api/app.py +0 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/src/lifx_emulator/api/mappers/__init__.py +0 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/src/lifx_emulator/api/mappers/device_mapper.py +0 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/src/lifx_emulator/api/routers/__init__.py +0 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/src/lifx_emulator/api/routers/devices.py +0 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/src/lifx_emulator/api/routers/monitoring.py +0 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/src/lifx_emulator/api/routers/scenarios.py +0 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/src/lifx_emulator/api/services/__init__.py +0 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/src/lifx_emulator/api/services/device_service.py +0 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/src/lifx_emulator/constants.py +0 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/src/lifx_emulator/devices/__init__.py +0 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/src/lifx_emulator/devices/manager.py +0 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/src/lifx_emulator/devices/observers.py +0 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/src/lifx_emulator/devices/persistence.py +0 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/src/lifx_emulator/devices/state_restorer.py +0 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/src/lifx_emulator/devices/state_serializer.py +0 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/src/lifx_emulator/factories/default_config.py +0 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/src/lifx_emulator/factories/firmware_config.py +0 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/src/lifx_emulator/factories/serial_generator.py +0 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/src/lifx_emulator/handlers/__init__.py +0 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/src/lifx_emulator/handlers/base.py +0 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/src/lifx_emulator/handlers/device_handlers.py +0 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/src/lifx_emulator/handlers/multizone_handlers.py +0 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/src/lifx_emulator/handlers/registry.py +0 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/src/lifx_emulator/products/__init__.py +0 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/src/lifx_emulator/protocol/__init__.py +0 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/src/lifx_emulator/protocol/base.py +0 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/src/lifx_emulator/protocol/const.py +0 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/src/lifx_emulator/protocol/generator.py +0 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/src/lifx_emulator/protocol/header.py +0 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/src/lifx_emulator/protocol/packets.py +0 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/src/lifx_emulator/protocol/protocol_types.py +0 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/src/lifx_emulator/protocol/serializer.py +0 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/src/lifx_emulator/repositories/__init__.py +0 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/src/lifx_emulator/repositories/device_repository.py +0 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/src/lifx_emulator/repositories/storage_backend.py +0 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/src/lifx_emulator/scenarios/__init__.py +0 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/src/lifx_emulator/scenarios/manager.py +0 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/src/lifx_emulator/scenarios/models.py +0 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/src/lifx_emulator/scenarios/persistence.py +0 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/src/lifx_emulator/server.py +0 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/tests/conftest.py +0 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/tests/test_api.py +0 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/tests/test_api_validation.py +0 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/tests/test_async_storage.py +0 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/tests/test_cli_validation.py +0 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/tests/test_device_edge_cases.py +0 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/tests/test_device_handlers_extended.py +0 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/tests/test_device_manager.py +0 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/tests/test_handler_registry.py +0 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/tests/test_light_handlers_extended.py +0 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/tests/test_multizone_handlers_extended.py +0 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/tests/test_observers.py +0 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/tests/test_products_specs.py +0 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/tests/test_protocol_generator.py +0 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/tests/test_protocol_types_coverage.py +0 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/tests/test_repositories.py +0 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/tests/test_scenario_manager.py +0 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/tests/test_scenario_persistence.py +0 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/tests/test_serializer.py +0 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/tests/test_server.py +0 -0
- {lifx_emulator-2.3.0 → lifx_emulator-2.4.0}/tests/test_state_restorer.py +0 -0
|
@@ -70,7 +70,7 @@ lifx-emulator --bind 192.168.1.100 --port 56700
|
|
|
70
70
|
lifx-emulator --color 2 --multizone 1 --tile 1 --verbose
|
|
71
71
|
|
|
72
72
|
# Create only specific device types
|
|
73
|
-
lifx-emulator --color 0 --infrared 3 --hev 2
|
|
73
|
+
lifx-emulator --color 0 --infrared 3 --hev 2 --switch 2
|
|
74
74
|
|
|
75
75
|
# Mix product IDs with device types
|
|
76
76
|
lifx-emulator --product 27 --color 2 --multizone 1
|
|
@@ -128,8 +128,9 @@ lifx-emulator --help
|
|
|
128
128
|
- `--multizone-extended`: Enable extended multizone support (default: True, use --no-multizone-extended to disable)
|
|
129
129
|
- `--tile`: Number of tile devices
|
|
130
130
|
- `--tile-count`: Tiles per device (uses product default if not specified)
|
|
131
|
-
- `--tile-width`: Width of each tile in
|
|
132
|
-
- `--tile-height`: Height of each tile in
|
|
131
|
+
- `--tile-width`: Width of each tile in zones (uses product default if not specified)
|
|
132
|
+
- `--tile-height`: Height of each tile in zones (uses product default if not specified)
|
|
133
|
+
- `--switch`: Number of LIFX Switch devices (relays, no lighting, default: 0)
|
|
133
134
|
- `--serial-prefix`: serial prefix (6 hex chars, default: d073d5)
|
|
134
135
|
- `--serial-start`: Starting serial suffix (default: 1)
|
|
135
136
|
- `--api`: Enable HTTP API server for monitoring and management (default: False)
|
|
@@ -140,9 +141,9 @@ lifx-emulator --help
|
|
|
140
141
|
**Product Defaults:**
|
|
141
142
|
Device parameters like `--multizone-zones` and `--tile-count` automatically use product-specific defaults from the specs system when not specified:
|
|
142
143
|
- LIFX Beam: Extended multizone support enabled and 80 zones by default
|
|
143
|
-
- LIFX Tile: 5 tiles of 8x8
|
|
144
|
-
- LIFX Candle: 1 tile of 5x6
|
|
145
|
-
- LIFX Ceiling: 1 tile of 8x8
|
|
144
|
+
- LIFX Tile: 5 tiles of 8x8 zones by default
|
|
145
|
+
- LIFX Candle: 1 tile of 5x6 zones by default
|
|
146
|
+
- LIFX Ceiling: 1 tile of 8x8 zones by default
|
|
146
147
|
- These defaults can be overridden with command-line parameters
|
|
147
148
|
|
|
148
149
|
**Firmware Version:**
|
|
@@ -521,7 +522,7 @@ delay = manager.get_response_delay(502, merged) # 1.0s
|
|
|
521
522
|
|
|
522
523
|
**DeviceState** (`src/lifx_emulator/devices/states.py`):
|
|
523
524
|
- Dataclass holding all device state (color, power, zones, tiles, firmware version, etc.)
|
|
524
|
-
- Capability flags: `has_color`, `has_infrared`, `has_multizone`, `has_matrix`, `has_hev`
|
|
525
|
+
- Capability flags: `has_color`, `has_infrared`, `has_multizone`, `has_matrix`, `has_hev`, `has_relays`, `has_buttons`
|
|
525
526
|
- Initialized differently per device type via factory functions
|
|
526
527
|
- **TileFramebuffers**: Internal dataclass for storing non-visible framebuffers (1-7) per tile
|
|
527
528
|
- Provides `get_framebuffer(fb_index, width, height)` for lazy initialization
|
|
@@ -562,6 +563,10 @@ delay = manager.get_response_delay(502, merged) # 1.0s
|
|
|
562
563
|
- Zone count uses product defaults from specs if not specified
|
|
563
564
|
- `create_tile_device(tile_count=None)`: Tile chain (product=55)
|
|
564
565
|
- Tile count and dimensions use product defaults from specs if not specified
|
|
566
|
+
- `create_switch(product_id=70)`: LIFX Switch device (product=70)
|
|
567
|
+
- Has `has_relays=True` and `has_buttons=True` capabilities
|
|
568
|
+
- No lighting control (responds with StateUnhandled to Light/MultiZone/Tile packets)
|
|
569
|
+
- Supports all Device.* packets for basic device information
|
|
565
570
|
- `create_device(product_id, zone_count=None, tile_count=None)`: Universal factory
|
|
566
571
|
- Creates any device by product ID from the registry
|
|
567
572
|
- Automatically uses product defaults from specs system
|
|
@@ -764,6 +769,18 @@ Matrix devices support 8 framebuffers (0-7) for advanced rendering:
|
|
|
764
769
|
- Framebuffers are lazily initialized on first access
|
|
765
770
|
- Non-visible framebuffers are persisted with device state
|
|
766
771
|
|
|
772
|
+
### Switch Handling
|
|
773
|
+
- LIFX Switch devices have `has_relays=True` and `has_buttons=True` capabilities
|
|
774
|
+
- Switches do not support lighting operations (no color, brightness, or zone control)
|
|
775
|
+
- **Capability-based packet filtering**: Switches automatically return `StateUnhandled` (packet 223) for:
|
|
776
|
+
- Light.* packets (types 101-149): GetColor, SetColor, SetWaveform, etc.
|
|
777
|
+
- MultiZone.* packets (types 501-512): GetColorZones, SetColorZones, etc.
|
|
778
|
+
- Tile.* packets (types 701-720): Get64, Set64, GetTileEffect, etc.
|
|
779
|
+
- Switches handle Device.* packets (types 2-59) normally: GetVersion, GetLabel, EchoRequest, etc.
|
|
780
|
+
- StateUnhandled response includes the `unhandled_type` field indicating which packet type was rejected
|
|
781
|
+
- Acknowledgments (packet 45) are still sent if `ack_required=True` flag is set
|
|
782
|
+
- **Note**: Button and relay protocol packets are not currently implemented (requires cloud/Matter infrastructure)
|
|
783
|
+
|
|
767
784
|
### Testing Scenarios
|
|
768
785
|
Configure via ScenarioConfig in HierarchicalScenarioManager:
|
|
769
786
|
- `drop_packets`: Dict mapping packet type to drop rate (0.0-1.0, where 1.0 = always drop)
|
|
@@ -266,8 +266,8 @@ Creates a new emulated device by product ID. The device will be added to the emu
|
|
|
266
266
|
- `serial` (optional): Device serial (auto-generated if not provided)
|
|
267
267
|
- `zone_count` (optional): Number of zones for multizone devices
|
|
268
268
|
- `tile_count` (optional): Number of tiles for matrix devices
|
|
269
|
-
- `tile_width` (optional): Width of each tile in
|
|
270
|
-
- `tile_height` (optional): Height of each tile in
|
|
269
|
+
- `tile_width` (optional): Width of each tile in zones
|
|
270
|
+
- `tile_height` (optional): Height of each tile in zones
|
|
271
271
|
- `firmware_major` (optional): Firmware major version
|
|
272
272
|
- `firmware_minor` (optional): Firmware minor version
|
|
273
273
|
|
|
@@ -788,8 +788,8 @@ jobs:
|
|
|
788
788
|
| 506 | StateMultiZone | Response with zone colors |
|
|
789
789
|
| 512 | ExtendedStateMultiZone | Response with extended zones |
|
|
790
790
|
| 701 | GetDeviceChain | Get tile chain info |
|
|
791
|
-
| 707 | Get64 | Get tile
|
|
792
|
-
| 715 | Set64 | Set tile
|
|
791
|
+
| 707 | Get64 | Get tile zone data |
|
|
792
|
+
| 715 | Set64 | Set tile zone data |
|
|
793
793
|
|
|
794
794
|
## Tips and Best Practices
|
|
795
795
|
|
|
@@ -90,8 +90,8 @@ Dataclass holding all stateful information for an emulated LIFX device.
|
|
|
90
90
|
|
|
91
91
|
- **`tile_count`** (`int` = `0`) - Number of tiles in chain
|
|
92
92
|
- **`tile_devices`** (`list[dict]` = `[]`) - Per-tile state (position, colors)
|
|
93
|
-
- **`tile_width`** (`int` = `8`) - Width of each tile in
|
|
94
|
-
- **`tile_height`** (`int` = `8`) - Height of each tile in
|
|
93
|
+
- **`tile_width`** (`int` = `8`) - Width of each tile in zones
|
|
94
|
+
- **`tile_height`** (`int` = `8`) - Height of each tile in zones
|
|
95
95
|
|
|
96
96
|
#### Effects (Waveforms & Animations)
|
|
97
97
|
|
|
@@ -239,8 +239,8 @@ Get detailed specifications for a product.
|
|
|
239
239
|
- `zone_count`: Number of zones (multizone devices)
|
|
240
240
|
- `extended_multizone`: Extended multizone support flag
|
|
241
241
|
- `tile_count`: Default number of tiles (matrix devices)
|
|
242
|
-
- `tile_width`: Tile width in
|
|
243
|
-
- `tile_height`: Tile height in
|
|
242
|
+
- `tile_width`: Tile width in zones (matrix devices)
|
|
243
|
+
- `tile_height`: Tile height in zones (matrix devices)
|
|
244
244
|
|
|
245
245
|
**Example:**
|
|
246
246
|
```python
|
|
@@ -322,8 +322,8 @@ class TileStateDevice:
|
|
|
322
322
|
accel_meas_z: int
|
|
323
323
|
user_x: float # User-configured X position
|
|
324
324
|
user_y: float # User-configured Y position
|
|
325
|
-
width: int # Tile width in
|
|
326
|
-
height: int # Tile height in
|
|
325
|
+
width: int # Tile width in zones (e.g., 8)
|
|
326
|
+
height: int # Tile height in zones (e.g., 8)
|
|
327
327
|
device_version_vendor: int
|
|
328
328
|
device_version_product: int
|
|
329
329
|
device_version_version: int
|
|
@@ -253,7 +253,7 @@ Devices advertise capabilities through boolean flags:
|
|
|
253
253
|
| `has_infrared` | IR brightness | LIFX A19 Night Vision |
|
|
254
254
|
| `has_multizone` | Linear zones | LIFX Z, LIFX Beam |
|
|
255
255
|
| `has_extended_multizone` | >16 zones | LIFX Beam |
|
|
256
|
-
| `has_matrix` | 2D
|
|
256
|
+
| `has_matrix` | 2D zone grid | LIFX Tile, LIFX Candle |
|
|
257
257
|
| `has_hev` | HEV cleaning | LIFX Clean |
|
|
258
258
|
|
|
259
259
|
## Packet Types
|
|
@@ -2,6 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
<!-- version list -->
|
|
4
4
|
|
|
5
|
+
## v2.4.0 (2025-11-19)
|
|
6
|
+
|
|
7
|
+
### Features
|
|
8
|
+
|
|
9
|
+
- Implement LIFX Switch device emulation with StateUnhandled responses
|
|
10
|
+
([`e2f9114`](https://github.com/Djelibeybi/lifx-emulator/commit/e2f911420db1d27d916247a9b3fdb50f31276b48))
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
## v2.3.1 (2025-11-18)
|
|
14
|
+
|
|
15
|
+
### Bug Fixes
|
|
16
|
+
|
|
17
|
+
- **tile**: Implement backwards compatibility on tile and multizone devices
|
|
18
|
+
([`be473f7`](https://github.com/Djelibeybi/lifx-emulator/commit/be473f74f9a49be7f07082d5bfba16f5d74f46f6))
|
|
19
|
+
|
|
20
|
+
|
|
5
21
|
## v2.3.0 (2025-11-18)
|
|
6
22
|
|
|
7
23
|
### Features
|
|
@@ -197,20 +197,30 @@ Number of tiles per tile device. If not specified, uses product default (5 for L
|
|
|
197
197
|
- **Default:** `None` (uses product defaults)
|
|
198
198
|
- **Example:** `--tile-count 10`
|
|
199
199
|
|
|
200
|
-
### `--tile-width <
|
|
200
|
+
### `--tile-width <zones>`
|
|
201
201
|
|
|
202
|
-
Width of each tile in
|
|
202
|
+
Width of each tile in zones. If not specified, uses product default (typically 8).
|
|
203
203
|
|
|
204
204
|
- **Default:** `None` (uses product defaults)
|
|
205
205
|
- **Example:** `--tile-width 16`
|
|
206
206
|
|
|
207
|
-
### `--tile-height <
|
|
207
|
+
### `--tile-height <zones>`
|
|
208
208
|
|
|
209
|
-
Height of each tile in
|
|
209
|
+
Height of each tile in zones. If not specified, uses product default (typically 8).
|
|
210
210
|
|
|
211
211
|
- **Default:** `None` (uses product defaults)
|
|
212
212
|
- **Example:** `--tile-height 8`
|
|
213
213
|
|
|
214
|
+
### `--switch <COUNT>`
|
|
215
|
+
|
|
216
|
+
Number of LIFX Switch devices to emulate (relay-based switches with no lighting).
|
|
217
|
+
|
|
218
|
+
- **Default:** `0`
|
|
219
|
+
- **Product:** 70 (LIFX Switch)
|
|
220
|
+
- **Example:** `--switch 2`
|
|
221
|
+
|
|
222
|
+
Switch devices have `has_relays=True` and `has_buttons=True` capabilities but do not support Light, MultiZone, or Tile protocol packets. They respond with `StateUnhandled` (packet 223) to unsupported requests.
|
|
223
|
+
|
|
214
224
|
## serial Options
|
|
215
225
|
|
|
216
226
|
### `--serial-prefix <PREFIX>`
|
|
@@ -291,8 +301,8 @@ lifx-emulator --serial-prefix cafe00 --serial-start 100 --color 3
|
|
|
291
301
|
### Only Specific Types
|
|
292
302
|
|
|
293
303
|
```bash
|
|
294
|
-
# No default devices, only infrared and
|
|
295
|
-
lifx-emulator --color 0 --infrared 3 --HEV 2
|
|
304
|
+
# No default devices, only infrared, HEV, and switches
|
|
305
|
+
lifx-emulator --color 0 --infrared 3 --HEV 2 --switch 2
|
|
296
306
|
```
|
|
297
307
|
|
|
298
308
|
### Discovery Testing
|
|
@@ -297,7 +297,7 @@ print(f"Tile height: {device.state.tile_height}") # 8
|
|
|
297
297
|
|
|
298
298
|
# Access tile devices
|
|
299
299
|
for i, tile in enumerate(device.state.tile_devices):
|
|
300
|
-
print(f"Tile {i}: {tile.width}x{tile.height}
|
|
300
|
+
print(f"Tile {i}: {tile.width}x{tile.height} zones")
|
|
301
301
|
```
|
|
302
302
|
|
|
303
303
|
### Matrix Packet Types
|
|
@@ -341,6 +341,82 @@ For large tiles (>64 zones), prepare all zones in a non-visible framebuffer, the
|
|
|
341
341
|
See [Framebuffer Guide](framebuffers.md) for complete documentation and examples.
|
|
342
342
|
|
|
343
343
|
|
|
344
|
+
## Switch Devices
|
|
345
|
+
|
|
346
|
+
LIFX Switch devices are relay-based switches with no lighting capabilities. They respond with `StateUnhandled` (packet 223) to all lighting-related protocol requests.
|
|
347
|
+
|
|
348
|
+
### Example Products
|
|
349
|
+
|
|
350
|
+
- **LIFX Switch** (product IDs 70, 71, 89, 115, 116) - 2 relay switches
|
|
351
|
+
|
|
352
|
+
### Capabilities
|
|
353
|
+
|
|
354
|
+
- **Relays**: Physical relay switches for controlling external loads
|
|
355
|
+
- **Buttons**: Physical buttons for manual control
|
|
356
|
+
- **No lighting**: No color, brightness, or zone control
|
|
357
|
+
- Basic device operations (GetVersion, GetLabel, EchoRequest, etc.)
|
|
358
|
+
|
|
359
|
+
### Factory Function
|
|
360
|
+
|
|
361
|
+
```python
|
|
362
|
+
from lifx_emulator import create_switch
|
|
363
|
+
|
|
364
|
+
# Create LIFX Switch (default product 70)
|
|
365
|
+
switch = create_switch("d073d7000001")
|
|
366
|
+
|
|
367
|
+
# Or specify a different switch product
|
|
368
|
+
switch = create_switch("d073d7000002", product_id=89)
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
### Switch Behavior
|
|
372
|
+
|
|
373
|
+
```python
|
|
374
|
+
switch = create_switch("d073d7000001")
|
|
375
|
+
|
|
376
|
+
# Check capabilities
|
|
377
|
+
print(f"Has relays: {switch.state.has_relays}") # True
|
|
378
|
+
print(f"Has buttons: {switch.state.has_buttons}") # True
|
|
379
|
+
print(f"Has color: {switch.state.has_color}") # False
|
|
380
|
+
print(f"Has multizone: {switch.state.has_multizone}") # False
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
### Packet Handling
|
|
384
|
+
|
|
385
|
+
**Supported (Device.* packets 2-59):**
|
|
386
|
+
- `GetVersion` (32) → `StateVersion` (33)
|
|
387
|
+
- `GetLabel` (23) → `StateLabel` (25)
|
|
388
|
+
- `SetLabel` (24)
|
|
389
|
+
- `EchoRequest` (58) → `EchoResponse` (59)
|
|
390
|
+
- All other Device.* packets
|
|
391
|
+
|
|
392
|
+
**Rejected with StateUnhandled (223):**
|
|
393
|
+
- **Light.* packets (101-149)**: GetColor, SetColor, GetPower, SetPower, etc.
|
|
394
|
+
- **MultiZone.* packets (501-512)**: GetColorZones, SetColorZones, etc.
|
|
395
|
+
- **Tile.* packets (701-720)**: Get64, Set64, GetTileEffect, etc.
|
|
396
|
+
|
|
397
|
+
### StateUnhandled Response
|
|
398
|
+
|
|
399
|
+
When a switch receives an unsupported packet type, it responds with:
|
|
400
|
+
|
|
401
|
+
```python
|
|
402
|
+
# Client sends Light.GetColor (101) to switch
|
|
403
|
+
# Switch responds with:
|
|
404
|
+
# - StateUnhandled (223) with unhandled_type=101
|
|
405
|
+
# - Acknowledgement (45) if ack_required=True
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
The `StateUnhandled` packet includes the rejected packet type in the `unhandled_type` field, allowing clients to detect and handle unsupported operations gracefully.
|
|
409
|
+
|
|
410
|
+
### Limitations
|
|
411
|
+
|
|
412
|
+
**Note**: Button and relay control protocol packets are not currently implemented in the emulator.
|
|
413
|
+
|
|
414
|
+
The switch emulation is primarily for testing client libraries' handling of:
|
|
415
|
+
- Device capability detection
|
|
416
|
+
- StateUnhandled response handling
|
|
417
|
+
- Graceful degradation when lighting features are unavailable
|
|
418
|
+
|
|
419
|
+
|
|
344
420
|
## Using Generic create_device()
|
|
345
421
|
|
|
346
422
|
All factory functions use `create_device()` internally. You can use it directly:
|
|
@@ -31,7 +31,7 @@ Contains product-specific details not available in the upstream catalog:
|
|
|
31
31
|
- Min/max zone counts
|
|
32
32
|
- Default tile counts for matrix devices
|
|
33
33
|
- Min/max tile counts
|
|
34
|
-
- Tile dimensions (width x height in
|
|
34
|
+
- Tile dimensions (width x height in zones)
|
|
35
35
|
- Product-specific notes
|
|
36
36
|
|
|
37
37
|
### `specs.py`
|
|
@@ -81,8 +81,8 @@ products:
|
|
|
81
81
|
default_tile_count: <number> # Typical number of tiles in chain
|
|
82
82
|
min_tile_count: <number> # Minimum tiles supported
|
|
83
83
|
max_tile_count: <number> # Maximum tiles supported
|
|
84
|
-
tile_width: <
|
|
85
|
-
tile_height: <
|
|
84
|
+
tile_width: <zones> # Width of each tile
|
|
85
|
+
tile_height: <zones> # Height of each tile
|
|
86
86
|
notes: "<description>"
|
|
87
87
|
```
|
|
88
88
|
|
|
@@ -95,7 +95,7 @@ products:
|
|
|
95
95
|
max_tile_count: 5
|
|
96
96
|
tile_width: 8
|
|
97
97
|
tile_height: 8
|
|
98
|
-
notes: "LIFX Tile, 8x8
|
|
98
|
+
notes: "LIFX Tile, 8x8 zone matrix, chainable up to 5"
|
|
99
99
|
```
|
|
100
100
|
|
|
101
101
|
**Example - LIFX Candle:**
|
|
@@ -107,7 +107,7 @@ products:
|
|
|
107
107
|
max_tile_count: 1
|
|
108
108
|
tile_width: 5
|
|
109
109
|
tile_height: 6
|
|
110
|
-
notes: "LIFX Candle, 5x6
|
|
110
|
+
notes: "LIFX Candle, 5x6 zone matrix, single unit"
|
|
111
111
|
```
|
|
112
112
|
|
|
113
113
|
**Example - LIFX Ceiling:**
|
|
@@ -119,7 +119,7 @@ products:
|
|
|
119
119
|
max_tile_count: 1
|
|
120
120
|
tile_width: 22
|
|
121
121
|
tile_height: 22
|
|
122
|
-
notes: "LIFX Ceiling, 22x22
|
|
122
|
+
notes: "LIFX Ceiling, 22x22 zone matrix"
|
|
123
123
|
```
|
|
124
124
|
|
|
125
125
|
## How Specifications Are Used
|
|
@@ -149,16 +149,16 @@ When creating a matrix device:
|
|
|
149
149
|
2. **Tile count**: From `specs.yml` if not specified by user
|
|
150
150
|
|
|
151
151
|
```python
|
|
152
|
-
# Uses specification: 5 tiles of 8x8
|
|
152
|
+
# Uses specification: 5 tiles of 8x8 zones
|
|
153
153
|
device = create_device(55)
|
|
154
154
|
|
|
155
155
|
# Custom tile count, specification dimensions
|
|
156
|
-
device = create_device(55, tile_count=3) # 3 tiles of 8x8
|
|
156
|
+
device = create_device(55, tile_count=3) # 3 tiles of 8x8 zones
|
|
157
157
|
|
|
158
|
-
# Candle: 1 tile of 5x5
|
|
158
|
+
# Candle: 1 tile of 5x5 zones (from specification)
|
|
159
159
|
device = create_device(57)
|
|
160
160
|
|
|
161
|
-
# Ceiling: 1 tile of 22x22
|
|
161
|
+
# Ceiling: 1 tile of 22x22 zones (from specification)
|
|
162
162
|
device = create_device(176)
|
|
163
163
|
```
|
|
164
164
|
|
|
@@ -142,7 +142,7 @@ For matrix/tile devices:
|
|
|
142
142
|
```
|
|
143
143
|
▸ Show tiles (5) (click to expand)
|
|
144
144
|
┌──────────┐
|
|
145
|
-
│ T1 │ (8×8
|
|
145
|
+
│ T1 │ (8×8 zone grid)
|
|
146
146
|
│ ████████ │
|
|
147
147
|
│ ████████ │
|
|
148
148
|
│ ████████ │
|
|
@@ -249,8 +249,8 @@ For matrix devices (tiles, candles, ceiling):
|
|
|
249
249
|
|
|
250
250
|
1. Locate device in Devices section
|
|
251
251
|
2. Click "▸ Show tiles" to expand
|
|
252
|
-
3. Grid display shows
|
|
253
|
-
4. Each small square is one
|
|
252
|
+
3. Grid display shows zone colors
|
|
253
|
+
4. Each small square is one zone
|
|
254
254
|
5. Tiles labeled T1, T2, etc.
|
|
255
255
|
6. Click again to collapse
|
|
256
256
|
|
|
@@ -71,7 +71,7 @@ LIFX Emulator is a Python library and CLI tool that creates virtual LIFX devices
|
|
|
71
71
|
| Infrared | LIFX A19 Night Vision | IR brightness control |
|
|
72
72
|
| HEV | LIFX Clean | HEV cleaning cycle |
|
|
73
73
|
| Multizone | LIFX Z, LIFX Beam | Linear zones (up to 82) |
|
|
74
|
-
| Matrix | LIFX Tile, LIFX Candle | 2D
|
|
74
|
+
| Matrix | LIFX Tile, LIFX Candle | 2D zone arrays |
|
|
75
75
|
|
|
76
76
|
## Use Cases
|
|
77
77
|
|
|
@@ -84,7 +84,7 @@ async def main():
|
|
|
84
84
|
# Create a LIFX Tile with 5 tiles in the chain
|
|
85
85
|
device = create_tile_device("d073d9000001", tile_count=5)
|
|
86
86
|
|
|
87
|
-
# Each tile is 8x8
|
|
87
|
+
# Each tile is 8x8 zones (64 zones)
|
|
88
88
|
print(f"Tile device configuration:")
|
|
89
89
|
print(f" Tiles: {len(device.state.tile_devices)}")
|
|
90
90
|
for i, tile in enumerate(device.state.tile_devices):
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "lifx-emulator"
|
|
3
|
-
version = "2.
|
|
3
|
+
version = "2.4.0"
|
|
4
4
|
description = "LIFX Emulator for testing LIFX LAN protocol libraries"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.11"
|
|
@@ -73,7 +73,7 @@ docstring-code-format = true
|
|
|
73
73
|
docstring-code-line-length = "dynamic"
|
|
74
74
|
|
|
75
75
|
[tool.ruff.lint]
|
|
76
|
-
select = ["E", "F", "I", "N", "W", "UP"
|
|
76
|
+
select = ["E", "F", "I", "N", "W", "UP"]
|
|
77
77
|
ignore = []
|
|
78
78
|
|
|
79
79
|
# Complexity and code quality limits
|
|
@@ -86,13 +86,8 @@ max-branches = 12
|
|
|
86
86
|
max-statements = 50
|
|
87
87
|
|
|
88
88
|
[tool.ruff.lint.per-file-ignores]
|
|
89
|
-
"src/lifx_emulator/
|
|
90
|
-
"src/lifx_emulator/devices/state_restorer.py" = ["C901"]
|
|
91
|
-
"src/lifx_emulator/handlers/tile_handlers.py" = ["C901"]
|
|
92
|
-
"src/lifx_emulator/protocol/generator.py" = ["E501", "C901"]
|
|
89
|
+
"src/lifx_emulator/protocol/generator.py" = ["E501"]
|
|
93
90
|
"src/lifx_emulator/protocol/packets.py" = ["E501"]
|
|
94
|
-
"src/lifx_emulator/products/registry.py" = ["C901"]
|
|
95
|
-
"src/lifx_emulator/scenarios/persistence.py" = ["C901"]
|
|
96
91
|
|
|
97
92
|
|
|
98
93
|
[tool.pyright]
|
|
@@ -21,6 +21,7 @@ from lifx_emulator.factories import (
|
|
|
21
21
|
create_hev_light,
|
|
22
22
|
create_infrared_light,
|
|
23
23
|
create_multizone_light,
|
|
24
|
+
create_switch,
|
|
24
25
|
create_tile_device,
|
|
25
26
|
)
|
|
26
27
|
from lifx_emulator.products.registry import get_registry
|
|
@@ -239,6 +240,7 @@ async def run(
|
|
|
239
240
|
hev: Annotated[int, cyclopts.Parameter(group=device_group)] = 0,
|
|
240
241
|
multizone: Annotated[int, cyclopts.Parameter(group=device_group)] = 0,
|
|
241
242
|
tile: Annotated[int, cyclopts.Parameter(group=device_group)] = 0,
|
|
243
|
+
switch: Annotated[int, cyclopts.Parameter(group=device_group)] = 0,
|
|
242
244
|
# Multizone Options
|
|
243
245
|
multizone_zones: Annotated[
|
|
244
246
|
int | None, cyclopts.Parameter(group=multizone_group)
|
|
@@ -284,11 +286,12 @@ async def run(
|
|
|
284
286
|
multizone_extended: Enable extended multizone support (Beam).
|
|
285
287
|
Set --no-multizone-extended for basic multizone (Z) devices.
|
|
286
288
|
tile: Number of tile/matrix chain devices.
|
|
289
|
+
switch: Number of LIFX Switch devices (relays, no lighting).
|
|
287
290
|
tile_count: Number of tiles per device. Uses product defaults if not
|
|
288
291
|
specified (5 for Tile, 1 for Candle/Ceiling).
|
|
289
|
-
tile_width: Width of each tile in
|
|
292
|
+
tile_width: Width of each tile in zones. Uses product defaults if not
|
|
290
293
|
specified (8 for most devices).
|
|
291
|
-
tile_height: Height of each tile in
|
|
294
|
+
tile_height: Height of each tile in zones. Uses product defaults if
|
|
292
295
|
not specified (8 for most devices).
|
|
293
296
|
serial_prefix: Serial number prefix as 6 hex characters.
|
|
294
297
|
serial_start: Starting serial suffix for auto-incrementing device serials.
|
|
@@ -310,7 +313,7 @@ async def run(
|
|
|
310
313
|
lifx-emulator --color 2 --multizone 1 --tile 1 --api --verbose
|
|
311
314
|
|
|
312
315
|
Create only specific device types:
|
|
313
|
-
lifx-emulator --color 0 --infrared 3 --hev 2
|
|
316
|
+
lifx-emulator --color 0 --infrared 3 --hev 2 --switch 2
|
|
314
317
|
|
|
315
318
|
Custom serial prefix:
|
|
316
319
|
lifx-emulator --serial-prefix cafe00 --color 5
|
|
@@ -410,6 +413,7 @@ async def run(
|
|
|
410
413
|
and infrared == 0
|
|
411
414
|
and hev == 0
|
|
412
415
|
and multizone == 0
|
|
416
|
+
and switch == 0
|
|
413
417
|
):
|
|
414
418
|
color = 0
|
|
415
419
|
|
|
@@ -423,6 +427,7 @@ async def run(
|
|
|
423
427
|
and hev == 0
|
|
424
428
|
and multizone == 0
|
|
425
429
|
and tile == 0
|
|
430
|
+
and switch == 0
|
|
426
431
|
):
|
|
427
432
|
color = 0
|
|
428
433
|
|
|
@@ -467,13 +472,17 @@ async def run(
|
|
|
467
472
|
)
|
|
468
473
|
)
|
|
469
474
|
|
|
475
|
+
# Create switch devices
|
|
476
|
+
for _ in range(switch):
|
|
477
|
+
devices.append(create_switch(get_serial(), storage=storage))
|
|
478
|
+
|
|
470
479
|
if not devices:
|
|
471
480
|
if persistent:
|
|
472
481
|
logger.warning("No devices configured. Server will run with no devices.")
|
|
473
482
|
logger.info("Use API (--api) or restart with device flags to add devices.")
|
|
474
483
|
else:
|
|
475
484
|
logger.error(
|
|
476
|
-
"No devices configured. Use --color, --multizone, --tile, "
|
|
485
|
+
"No devices configured. Use --color, --multizone, --tile, --switch, "
|
|
477
486
|
"etc. to add devices."
|
|
478
487
|
)
|
|
479
488
|
return
|
|
@@ -25,10 +25,10 @@ class DeviceCreateRequest(BaseModel):
|
|
|
25
25
|
None, description="Number of tiles for matrix devices", ge=0, le=100
|
|
26
26
|
)
|
|
27
27
|
tile_width: int | None = Field(
|
|
28
|
-
None, description="Width of each tile in
|
|
28
|
+
None, description="Width of each tile in zones", ge=1, le=256
|
|
29
29
|
)
|
|
30
30
|
tile_height: int | None = Field(
|
|
31
|
-
None, description="Height of each tile in
|
|
31
|
+
None, description="Height of each tile in zones", ge=1, le=256
|
|
32
32
|
)
|
|
33
33
|
firmware_major: int | None = Field(
|
|
34
34
|
None, description="Firmware major version", ge=0, le=255
|
|
@@ -163,7 +163,7 @@
|
|
|
163
163
|
gap: 2px;
|
|
164
164
|
margin-top: 4px;
|
|
165
165
|
}
|
|
166
|
-
.tile-
|
|
166
|
+
.tile-zone {
|
|
167
167
|
width: 8px;
|
|
168
168
|
height: 8px;
|
|
169
169
|
border-radius: 1px;
|
|
@@ -644,7 +644,7 @@
|
|
|
644
644
|
`;
|
|
645
645
|
} else if (dev.has_matrix && dev.tile_devices &&
|
|
646
646
|
dev.tile_devices.length > 0) {
|
|
647
|
-
// Render actual tile
|
|
647
|
+
// Render actual tile zones
|
|
648
648
|
const tilesHtml = dev.tile_devices.map((tile, tileIndex) => {
|
|
649
649
|
if (!tile.colors || tile.colors.length === 0) {
|
|
650
650
|
return '<div style="color: #666;">No color data</div>';
|
|
@@ -652,14 +652,14 @@
|
|
|
652
652
|
|
|
653
653
|
const width = tile.width || 8;
|
|
654
654
|
const height = tile.height || 8;
|
|
655
|
-
const
|
|
655
|
+
const totalzones = width * height;
|
|
656
656
|
|
|
657
|
-
// Create grid of
|
|
658
|
-
const slicedColors = tile.colors.slice(0,
|
|
659
|
-
const
|
|
657
|
+
// Create grid of zones
|
|
658
|
+
const slicedColors = tile.colors.slice(0, totalzones);
|
|
659
|
+
const zonesHtml = slicedColors.map(color => {
|
|
660
660
|
const rgb = hsbkToRgb(color);
|
|
661
661
|
const bgStyle = `background: ${rgb};`;
|
|
662
|
-
return `<div class="tile-
|
|
662
|
+
return `<div class="tile-zone" style="${bgStyle}"></div>`;
|
|
663
663
|
}).join('');
|
|
664
664
|
|
|
665
665
|
const labelStyle = (
|
|
@@ -675,7 +675,7 @@
|
|
|
675
675
|
T${tileIndex + 1}
|
|
676
676
|
</div>
|
|
677
677
|
<div class="tile-grid" style="${gridStyle}">
|
|
678
|
-
${
|
|
678
|
+
${zonesHtml}
|
|
679
679
|
</div>
|
|
680
680
|
</div>
|
|
681
681
|
`;
|