Add support for LIFX 26"x13" Ceiling (#148459)

Signed-off-by: Avi Miller <me@dje.li>
This commit is contained in:
Avi Miller 2025-07-11 21:42:50 +10:00 committed by GitHub
parent 87aecf0ed9
commit ec5991bc68
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 1437 additions and 1 deletions

View File

@ -70,6 +70,7 @@ INFRARED_BRIGHTNESS_VALUES_MAP = {
}
LIFX_CEILING_PRODUCT_IDS = {176, 177, 201, 202}
LIFX_128ZONE_CEILING_PRODUCT_IDS = {201, 202}
_LOGGER = logging.getLogger(__package__)

View File

@ -41,6 +41,7 @@ from .const import (
DEFAULT_ATTEMPTS,
DOMAIN,
IDENTIFY_WAVEFORM,
LIFX_128ZONE_CEILING_PRODUCT_IDS,
MAX_ATTEMPTS_PER_UPDATE_REQUEST_MESSAGE,
MAX_UPDATE_TIME,
MESSAGE_RETRIES,
@ -183,6 +184,11 @@ class LIFXUpdateCoordinator(DataUpdateCoordinator[None]):
"""Return true if this is a matrix device."""
return bool(lifx_features(self.device)["matrix"])
@cached_property
def is_128zone_matrix(self) -> bool:
"""Return true if this is a 128-zone matrix device."""
return bool(self.device.product in LIFX_128ZONE_CEILING_PRODUCT_IDS)
async def diagnostics(self) -> dict[str, Any]:
"""Return diagnostic information about the device."""
features = lifx_features(self.device)
@ -216,6 +222,16 @@ class LIFXUpdateCoordinator(DataUpdateCoordinator[None]):
"last_result": self.device.last_hev_cycle_result,
}
if features["matrix"] is True:
device_data["matrix"] = {
"effect": self.device.effect,
"chain": self.device.chain,
"chain_length": self.device.chain_length,
"tile_devices": self.device.tile_devices,
"tile_devices_count": self.device.tile_devices_count,
"tile_device_width": self.device.tile_device_width,
}
if features["infrared"] is True:
device_data["infrared"] = {"brightness": self.device.infrared_brightness}
@ -291,6 +307,37 @@ class LIFXUpdateCoordinator(DataUpdateCoordinator[None]):
return calls
@callback
def _async_build_get64_update_requests(self) -> list[Callable]:
"""Build one or more get64 update requests."""
if self.device.tile_device_width == 0:
return []
calls: list[Callable] = []
calls.append(
partial(
self.device.get64,
tile_index=0,
length=1,
x=0,
y=0,
width=self.device.tile_device_width,
)
)
if self.is_128zone_matrix:
# For 128-zone ceiling devices, we need another get64 request for the next set of zones
calls.append(
partial(
self.device.get64,
tile_index=0,
length=1,
x=0,
y=4,
width=self.device.tile_device_width,
)
)
return calls
async def _async_update_data(self) -> None:
"""Fetch all device data from the api."""
device = self.device
@ -312,9 +359,9 @@ class LIFXUpdateCoordinator(DataUpdateCoordinator[None]):
[
self.device.get_tile_effect,
self.device.get_device_chain,
self.device.get64,
]
)
methods.extend(self._async_build_get64_update_requests())
if self.is_extended_multizone:
methods.append(self.device.get_extended_color_zones)
elif self.is_legacy_multizone:
@ -339,6 +386,7 @@ class LIFXUpdateCoordinator(DataUpdateCoordinator[None]):
if self.is_matrix or self.is_extended_multizone or self.is_legacy_multizone:
self.active_effect = FirmwareEffect[self.device.effect.get("effect", "OFF")]
if self.is_legacy_multizone and num_zones != self.get_number_of_zones():
# The number of zones has changed so we need
# to update the zones again. This happens rarely.

View File

@ -199,6 +199,17 @@ def _mocked_ceiling() -> Light:
return bulb
def _mocked_128zone_ceiling() -> Light:
bulb = _mocked_bulb()
bulb.product = 201 # LIFX 26"x13" Ceiling
bulb.effect = {"effect": "OFF"}
bulb.get_tile_effect = MockLifxCommand(bulb)
bulb.set_tile_effect = MockLifxCommand(bulb)
bulb.get64 = MockLifxCommand(bulb)
bulb.get_device_chain = MockLifxCommand(bulb)
return bulb
def _mocked_bulb_old_firmware() -> Light:
bulb = _mocked_bulb()
bulb.host_firmware_version = "2.77"

File diff suppressed because it is too large Load Diff

View File

@ -12,7 +12,9 @@ from . import (
IP_ADDRESS,
SERIAL,
MockLifxCommand,
_mocked_128zone_ceiling,
_mocked_bulb,
_mocked_ceiling,
_mocked_clean_bulb,
_mocked_infrared_bulb,
_mocked_light_strip,
@ -209,3 +211,101 @@ async def test_multizone_bulb_diagnostics(
diag = await get_diagnostics_for_config_entry(hass, hass_client, config_entry)
assert diag == snapshot
async def test_matrix_diagnostics(
hass: HomeAssistant,
hass_client: ClientSessionGenerator,
snapshot: SnapshotAssertion,
) -> None:
"""Test diagnostics for a standard bulb."""
config_entry = MockConfigEntry(
domain=lifx.DOMAIN,
title=DEFAULT_ENTRY_TITLE,
data={CONF_HOST: IP_ADDRESS},
unique_id=SERIAL,
)
config_entry.add_to_hass(hass)
bulb = _mocked_ceiling()
bulb.effect = {"effect": "OFF"}
bulb.tile_devices_count = 1
bulb.tile_device_width = 8
bulb.tile_devices = [
{
"accel_meas_x": 0,
"accel_meas_y": 0,
"accel_meas_z": 2000,
"user_x": 0.0,
"user_y": 0.0,
"width": 8,
"height": 8,
"supported_frame_buffers": 5,
"device_version_vendor": 1,
"device_version_product": 176,
"firmware_build": 1729829374000000000,
"firmware_version_minor": 10,
"firmware_version_major": 4,
}
]
bulb.chain = {0: [(0, 0, 0, 3500)] * 64}
bulb.chain_length = 1
with (
_patch_discovery(device=bulb),
_patch_config_flow_try_connect(device=bulb),
_patch_device(device=bulb),
):
await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}})
await hass.async_block_till_done()
diag = await get_diagnostics_for_config_entry(hass, hass_client, config_entry)
assert diag == snapshot
async def test_128zone_matrix_diagnostics(
hass: HomeAssistant,
hass_client: ClientSessionGenerator,
snapshot: SnapshotAssertion,
) -> None:
"""Test diagnostics for a standard bulb."""
config_entry = MockConfigEntry(
domain=lifx.DOMAIN,
title=DEFAULT_ENTRY_TITLE,
data={CONF_HOST: IP_ADDRESS},
unique_id=SERIAL,
)
config_entry.add_to_hass(hass)
bulb = _mocked_128zone_ceiling()
bulb.effect = {"effect": "OFF"}
bulb.tile_devices_count = 1
bulb.tile_device_width = 16
bulb.tile_devices = [
{
"accel_meas_x": 0,
"accel_meas_y": 0,
"accel_meas_z": 2000,
"user_x": 0.0,
"user_y": 0.0,
"width": 8,
"height": 16,
"supported_frame_buffers": 5,
"device_version_vendor": 1,
"device_version_product": 201,
"firmware_build": 1729829374000000000,
"firmware_version_minor": 10,
"firmware_version_major": 4,
}
]
bulb.chain = {0: [(0, 0, 0, 3500)] * 128}
bulb.chain_length = 1
with (
_patch_discovery(device=bulb),
_patch_config_flow_try_connect(device=bulb),
_patch_device(device=bulb),
):
await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}})
await hass.async_block_till_done()
diag = await get_diagnostics_for_config_entry(hass, hass_client, config_entry)
assert diag == snapshot