Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
85 commits
Select commit Hold shift + click to select a range
c38868b
Add new integration for aidot
s1eedz May 13, 2024
83ec82c
fix ci work run error
s1eedz Jul 9, 2024
beda1a9
fix ci work run error 2
s1eedz Jul 9, 2024
43bf742
modify the test data and code
s1eedz Jul 9, 2024
6ea582a
Modifying unit tests
s1eedz Jul 11, 2024
a0d89a8
Increase unit test coverage
s1eedz Jul 16, 2024
ac5cfdd
Increase unit test coverage
s1eedz Jul 17, 2024
b338428
Increase unit test coverage
s1eedz Oct 11, 2024
2e7a9e0
完善单元测试
HongBryan Jan 24, 2025
159b788
add owner
HongBryan Feb 6, 2025
5ee7657
Merge branch 'dev' into feature/aidot-wifi-lights-improve
s1eedz Feb 10, 2025
d47971b
Fix
HongBryan Feb 17, 2025
68866f7
Update config_flow.py
s1eedz Feb 17, 2025
477146f
fix and add quality_scale.yaml
HongBryan Feb 18, 2025
2af0696
Remove non-conforming codes
HongBryan Feb 19, 2025
87acc91
Remove VERSION and CONNECTION_CLASS from config_flow.py
HongBryan Feb 19, 2025
d371f97
use entry.runtime_data instead and fix
HongBryan Feb 19, 2025
37a0912
Fix
HongBryan Feb 20, 2025
4879aad
Fix
HongBryan Feb 24, 2025
45d2c34
Update update_data interval
HongBryan Feb 24, 2025
7a4955e
Fix
HongBryan Feb 24, 2025
0b7dc76
Fix
HongBryan Feb 25, 2025
8db9684
Fix
HongBryan Feb 26, 2025
1fc8901
Fix
HongBryan Feb 26, 2025
872edb9
Fix
HongBryan Feb 26, 2025
c70ccc5
Fix
HongBryan Feb 27, 2025
78ddd1f
Fix
HongBryan Mar 1, 2025
9da883f
Fix
HongBryan Mar 1, 2025
e397c09
Fix
HongBryan Mar 6, 2025
6d52247
Merge branch 'dev' into feature/aidot-wifi-lights-improve
s1eedz Mar 7, 2025
bdcba8d
Fix
HongBryan Mar 7, 2025
45dc6b3
Fix
HongBryan Mar 10, 2025
6f0f112
Fix
HongBryan Mar 10, 2025
5f0fa88
Fix
HongBryan Mar 10, 2025
7035c30
Fix
HongBryan Mar 11, 2025
07cbb8f
Fix
HongBryan Mar 12, 2025
52c958e
Fix
HongBryan Mar 12, 2025
25120b7
Fix
HongBryan Mar 13, 2025
8622d37
Fix
HongBryan Mar 14, 2025
9145bb7
Fix
HongBryan Mar 19, 2025
46beaf9
Fix
HongBryan Mar 28, 2025
9f47612
Merge branch 'dev' into feature/aidot-wifi-lights-improve
s1eedz Apr 1, 2025
8ceb46e
Fix
HongBryan Apr 1, 2025
abaff05
Merge branch 'dev' into feature/aidot-wifi-lights-improve
s1eedz Apr 17, 2025
e722fb9
Merge branch 'dev' into feature/aidot-wifi-lights-improve
s1eedz Apr 17, 2025
c9f2079
fix
s1eedz Jun 17, 2025
269ac86
Merge branch 'dev' into feature/aidot-wifi-lights-improve
s1eedz Jun 23, 2025
18c538c
Update homeassistant/components/aidot/coordinator.py
s1eedz Jun 27, 2025
1f3ce0c
Update homeassistant/components/aidot/strings.json
s1eedz Jun 27, 2025
c9412be
fix
s1eedz Jun 27, 2025
36cce5e
fix
s1eedz Sep 18, 2025
2ce7d5f
Merge branch 'dev' into feature/aidot-wifi-lights-improve
s1eedz Sep 18, 2025
be6a728
fix
s1eedz Sep 18, 2025
666ac1a
Merge branch 'feature/aidot-wifi-lights-improve' of https://github.co…
s1eedz Sep 18, 2025
81503cf
fix
s1eedz Sep 18, 2025
c5d2347
fix
s1eedz Sep 29, 2025
a793bee
feat
s1eedz Nov 10, 2025
9834df0
Merge remote-tracking branch 'upstream/dev' into feature/aidot-wifi-l…
s1eedz Mar 25, 2026
e39977e
feat: update aidot integration
s1eedz Apr 3, 2026
031fd10
Fix Copilot review comments
s1eedz Apr 3, 2026
7c3b335
Fix manifest: add integration_type, remove version
s1eedz Apr 3, 2026
1a68b98
Merge branch 'dev' into feature/aidot-wifi-lights-improve
s1eedz Apr 3, 2026
c132bf5
Update homeassistant/components/aidot/quality_scale.yaml
s1eedz Apr 3, 2026
b150442
Fix prettier JSON key sorting in strings.json
s1eedz Apr 3, 2026
568b129
Fix test constants and update snapshot
s1eedz Apr 3, 2026
0f120a2
Remove Chinese comment, refactor async_turn_on logic
s1eedz Apr 3, 2026
3b6a84b
Merge branch 'dev' into feature/aidot-wifi-lights-improve
s1eedz Apr 7, 2026
3c1ca98
Address joostlek review: simplify light setup, fix coordinator, updat…
s1eedz Apr 7, 2026
224ce8d
refactor: improve aidot light entity setup with cleaner diff tracking
s1eedz Apr 7, 2026
e1ee6f7
Update homeassistant/components/aidot/light.py
s1eedz Apr 7, 2026
6e42028
Update tests/components/aidot/conftest.py
s1eedz Apr 7, 2026
5950fb2
Update tests/components/aidot/conftest.py
s1eedz Apr 7, 2026
2d8d889
refactor: move DATA_SCHEMA to module-level constant in config_flow
s1eedz Apr 7, 2026
4386122
Update tests/components/aidot/test_config_flow.py
s1eedz Apr 7, 2026
3025807
Fix ruff check formatting issues
s1eedz Apr 7, 2026
799ce41
Add tests for uncovered code paths to improve coverage
s1eedz Apr 7, 2026
65ed5e3
Fix ruff formatting
s1eedz Apr 7, 2026
b7f349b
Update homeassistant/components/aidot/light.py
s1eedz Apr 7, 2026
6e0e117
Move imports to top-level to fix PLC0415
s1eedz Apr 7, 2026
9f35857
Update homeassistant/components/aidot/light.py
s1eedz Apr 7, 2026
d44ebe0
Update homeassistant/components/aidot/coordinator.py
s1eedz Apr 7, 2026
c47ed24
Simplify light setup, add assertion to device removal test
s1eedz Apr 7, 2026
4a6522f
Merge branch 'dev' into feature/aidot-wifi-lights-improve
s1eedz Apr 7, 2026
6be7e25
Address joostlek review: rename to AiDot, remove hass param, simplify…
s1eedz Apr 8, 2026
a96beaf
Fix ruff format: single-line __init__ signature
s1eedz Apr 8, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CODEOWNERS

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

26 changes: 26 additions & 0 deletions homeassistant/components/aidot/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
"""The aidot integration."""

from __future__ import annotations

from homeassistant.const import Platform
from homeassistant.core import HomeAssistant

from .coordinator import AidotConfigEntry, AidotDeviceManagerCoordinator

PLATFORMS: list[Platform] = [Platform.LIGHT]


async def async_setup_entry(hass: HomeAssistant, entry: AidotConfigEntry) -> bool:
"""Set up aidot from a config entry."""

coordinator = AidotDeviceManagerCoordinator(hass, entry)
await coordinator.async_config_entry_first_refresh()
entry.runtime_data = coordinator
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True


async def async_unload_entry(hass: HomeAssistant, entry: AidotConfigEntry) -> bool:
"""Unload a config entry."""
await entry.runtime_data.async_cleanup()
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
67 changes: 67 additions & 0 deletions homeassistant/components/aidot/config_flow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
"""Config flow for Aidot integration."""

from __future__ import annotations

from typing import Any

from aidot.client import AidotClient
from aidot.const import CONF_LOGIN_INFO, DEFAULT_COUNTRY_CODE, SUPPORTED_COUNTRY_CODES
from aidot.exceptions import AidotUserOrPassIncorrect
import voluptuous as vol

from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
from homeassistant.const import CONF_COUNTRY_CODE, CONF_PASSWORD, CONF_USERNAME
from homeassistant.helpers import selector
from homeassistant.helpers.aiohttp_client import async_get_clientsession

from .const import DOMAIN

DATA_SCHEMA = vol.Schema(
{
vol.Required(
CONF_COUNTRY_CODE,
default=DEFAULT_COUNTRY_CODE,
): selector.CountrySelector(
selector.CountrySelectorConfig(
countries=SUPPORTED_COUNTRY_CODES,
)
),
vol.Required(CONF_USERNAME): str,
vol.Required(CONF_PASSWORD): str,
}
)


class AidotConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle aidot config flow."""

async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle the initial step."""
errors: dict[str, str] = {}
if user_input is not None:
client = AidotClient(
session=async_get_clientsession(self.hass),
country_code=user_input[CONF_COUNTRY_CODE],
username=user_input[CONF_USERNAME],
password=user_input[CONF_PASSWORD],
)
await self.async_set_unique_id(client.get_identifier())
self._abort_if_unique_id_configured()
Comment on lines +50 to +51
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I asked this in the previous PR, what was the identifier again?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The identifier is "{region}-{username}" (e.g. "us-user@email.com"), which uniquely identifies the user's account per region.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So we have something more specific like a user ID? Do we get a token back that we can get the user ID out of?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the login response does return a user ID. However, using it as the unique_id would require calling the login API first, and the AiDot cloud only allows one active session per account — a second login would invalidate the first entry's token. By using {region}-{username} we can detect duplicates and abort before making any API calls, which protects the existing config entry from being disrupted.

try:
login_info = await client.async_post_login()
except AidotUserOrPassIncorrect:
errors["base"] = "invalid_auth"

if not errors:
return self.async_create_entry(
title=f"{user_input[CONF_USERNAME]} {user_input[CONF_COUNTRY_CODE]}",
data={
CONF_LOGIN_INFO: login_info,
},
)

return self.async_show_form(
step_id="user", data_schema=DATA_SCHEMA, errors=errors
)
3 changes: 3 additions & 0 deletions homeassistant/components/aidot/const.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
"""Constants for the aidot integration."""

DOMAIN = "aidot"
162 changes: 162 additions & 0 deletions homeassistant/components/aidot/coordinator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
"""Coordinator for Aidot."""

from datetime import timedelta
import logging

from aidot.client import AidotClient
from aidot.const import (
CONF_ACCESS_TOKEN,
CONF_AES_KEY,
CONF_DEVICE_LIST,
CONF_ID,
CONF_LOGIN_INFO,
CONF_TYPE,
)
from aidot.device_client import DeviceClient, DeviceStatusData
from aidot.exceptions import AidotAuthFailed, AidotUserOrPassIncorrect

from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryError
from homeassistant.helpers import device_registry as dr
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator

from .const import DOMAIN

type AidotConfigEntry = ConfigEntry[AidotDeviceManagerCoordinator]
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The type alias definition using the type keyword (line 27) requires Python 3.12+. Home Assistant typically needs to support Python 3.11+, so this should use TypeAlias from typing instead: AidotConfigEntry: TypeAlias = ConfigEntry[AidotDeviceManagerCoordinator]

Copilot uses AI. Check for mistakes.
_LOGGER = logging.getLogger(__name__)

UPDATE_DEVICE_LIST_INTERVAL = timedelta(hours=6)


class AidotDeviceUpdateCoordinator(DataUpdateCoordinator[DeviceStatusData]):
"""Class to manage Aidot data."""

def __init__(
self,
hass: HomeAssistant,
config_entry: AidotConfigEntry,
device_client: DeviceClient,
) -> None:
"""Initialize coordinator."""
super().__init__(
hass,
_LOGGER,
config_entry=config_entry,
name=DOMAIN,
update_interval=None,
)
self.device_client = device_client

async def _async_setup(self) -> None:
"""Set up the coordinator."""
self.device_client.on_status_update = self._handle_status_update

def _handle_status_update(self, status: DeviceStatusData) -> None:
"""Handle status callback."""
self.async_set_updated_data(status)

async def _async_update_data(self) -> DeviceStatusData:
"""Return current status."""
return self.device_client.status


class AidotDeviceManagerCoordinator(DataUpdateCoordinator[None]):
"""Class to manage fetching Aidot data."""

config_entry: AidotConfigEntry

def __init__(
self,
hass: HomeAssistant,
config_entry: AidotConfigEntry,
) -> None:
"""Initialize coordinator."""
super().__init__(
hass,
_LOGGER,
config_entry=config_entry,
name=DOMAIN,
update_interval=UPDATE_DEVICE_LIST_INTERVAL,
)
self.client = AidotClient(
session=async_get_clientsession(hass),
token=config_entry.data[CONF_LOGIN_INFO],
)
self.client.set_token_fresh_cb(self.token_fresh_cb)
self.device_coordinators: dict[str, AidotDeviceUpdateCoordinator] = {}

async def _async_setup(self) -> None:
"""Set up the coordinator."""
try:
await self.async_auto_login()
except AidotUserOrPassIncorrect as error:
raise ConfigEntryAuthFailed from error

async def _async_update_data(self) -> None:
"""Update data async."""
try:
data = await self.client.async_get_all_device()
except AidotAuthFailed as error:
self.token_fresh_cb()
raise ConfigEntryError from error
current_devices = {
device[CONF_ID]: device
for device in data[CONF_DEVICE_LIST]
if (
device[CONF_TYPE] == "light"
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Line 109 accesses device[CONF_TYPE] without checking if the key exists, potentially raising a KeyError. Consider using device.get(CONF_TYPE) with a default value or adding validation to ensure devices returned from async_get_all_device() always contain required fields.

Suggested change
device[CONF_TYPE] == "light"
device.get(CONF_TYPE) == "light"

Copilot uses AI. Check for mistakes.
and CONF_AES_KEY in device
and device[CONF_AES_KEY][0] is not None
)
}

removed_ids = set(self.device_coordinators) - set(current_devices)
for dev_id in removed_ids:
del self.device_coordinators[dev_id]
if removed_ids:
self._purge_deleted_lists()

for dev_id, device in current_devices.items():
if dev_id not in self.device_coordinators:
device_client = self.client.get_device_client(device)
device_coordinator = AidotDeviceUpdateCoordinator(
self.hass, self.config_entry, device_client
)
await device_coordinator.async_config_entry_first_refresh()
self.device_coordinators[dev_id] = device_coordinator

async def async_cleanup(self) -> None:
"""Perform cleanup actions."""
await self.client.async_cleanup()

def token_fresh_cb(self) -> None:
"""Update token."""
self.hass.config_entries.async_update_entry(
self.config_entry, data={CONF_LOGIN_INFO: self.client.login_info.copy()}
)

async def async_auto_login(self) -> None:
"""Async auto login."""
if self.client.login_info.get(CONF_ACCESS_TOKEN) is None:
await self.client.async_post_login()

def _purge_deleted_lists(self) -> None:
"""Purge device entries of deleted lists."""

device_reg = dr.async_get(self.hass)
identifiers = {
(
DOMAIN,
f"{device_coordinator.device_client.info.dev_id}",
)
for device_coordinator in self.device_coordinators.values()
}
for device in dr.async_entries_for_config_entry(
device_reg, self.config_entry.entry_id
):
if not set(device.identifiers) & identifiers:
_LOGGER.debug("Removing obsolete device entry %s", device.name)
device_reg.async_update_device(
device.id, remove_config_entry_id=self.config_entry.entry_id
)
122 changes: 122 additions & 0 deletions homeassistant/components/aidot/light.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
"""Support for Aidot lights."""

from typing import Any

from homeassistant.components.light import (
ATTR_BRIGHTNESS,
ATTR_COLOR_TEMP_KELVIN,
ATTR_RGBW_COLOR,
ColorMode,
LightEntity,
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.device_registry import (
CONNECTION_NETWORK_MAC,
DeviceInfo,
format_mac,
)
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity

from .const import DOMAIN
from .coordinator import AidotConfigEntry, AidotDeviceUpdateCoordinator


async def async_setup_entry(
hass: HomeAssistant,
entry: AidotConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up Light."""
coordinator = entry.runtime_data
async_add_entities(
AidotLight(device_coordinator)
for device_coordinator in coordinator.device_coordinators.values()
)


class AidotLight(CoordinatorEntity[AidotDeviceUpdateCoordinator], LightEntity):
"""Representation of a Aidot Wi-Fi Light."""

_attr_has_entity_name = True
_attr_name = None

def __init__(self, coordinator: AidotDeviceUpdateCoordinator) -> None:
"""Initialize the light."""
super().__init__(coordinator)
self._attr_unique_id = coordinator.device_client.info.dev_id
if hasattr(coordinator.device_client.info, "cct_max"):
self._attr_max_color_temp_kelvin = coordinator.device_client.info.cct_max
if hasattr(coordinator.device_client.info, "cct_min"):
self._attr_min_color_temp_kelvin = coordinator.device_client.info.cct_min
Comment on lines +48 to +51
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we use hasattr?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We use hasattr to safely check if the cct_max/cct_min attributes exist on the device info object. Not all AiDot light models support color temperature adjustment (e.g., some are fixed warm white only). This check prevents AttributeError when accessing these attributes on non-CCT devices, ensuring the integration works for all supported models.


model_id = coordinator.device_client.info.model_id
manufacturer = model_id.split(".")[0]
model = model_id[len(manufacturer) + 1 :]
Comment on lines +54 to +55
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The model_id parsing assumes the model_id always contains at least one dot. If a device's model_id doesn't match the expected format (e.g., "aidot.light.rgbw"), the extracted model name could be empty or incorrect. Consider adding validation to handle edge cases or use a more robust parsing method like rsplit with maxsplit parameter.

Suggested change
manufacturer = model_id.split(".")[0]
model = model_id[len(manufacturer) + 1 :]
if "." in model_id:
manufacturer, model = model_id.rsplit(".", 1)
else:
manufacturer = model_id
model = model_id

Copilot uses AI. Check for mistakes.
mac = format_mac(coordinator.device_client.info.mac)

self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, self._attr_unique_id)},
connections={(CONNECTION_NETWORK_MAC, mac)},
manufacturer=manufacturer,
model=model,
name=coordinator.device_client.info.name,
hw_version=coordinator.device_client.info.hw_version,
)
if coordinator.device_client.info.enable_rgbw:
self._attr_color_mode = ColorMode.RGBW
self._attr_supported_color_modes = {ColorMode.RGBW, ColorMode.COLOR_TEMP}
elif coordinator.device_client.info.enable_cct:
self._attr_color_mode = ColorMode.COLOR_TEMP
self._attr_supported_color_modes = {ColorMode.COLOR_TEMP}
else:
self._attr_color_mode = ColorMode.BRIGHTNESS
self._attr_supported_color_modes = {ColorMode.BRIGHTNESS}
self._update_status()

def _update_status(self) -> None:
"""Update light status from coordinator data."""
self._attr_available = self.coordinator.data.online
self._attr_is_on = self.coordinator.data.on
self._attr_brightness = self.coordinator.data.dimming
self._attr_color_temp_kelvin = self.coordinator.data.cct
self._attr_rgbw_color = self.coordinator.data.rgbw

@property
def available(self) -> bool:
"""Return if entity is available."""
return self.coordinator.data.online

@callback
def _handle_coordinator_update(self) -> None:
"""Update."""
self._update_status()
super()._handle_coordinator_update()

async def async_turn_on(self, **kwargs: Any) -> None:
"""Fix brightness state synchronization by updating the coordinator's `dimming` field."""
if ATTR_BRIGHTNESS in kwargs:
brightness = kwargs.get(ATTR_BRIGHTNESS, 255)
await self.coordinator.device_client.async_set_brightness(brightness)
self.coordinator.data.dimming = brightness
elif ATTR_COLOR_TEMP_KELVIN in kwargs:
color_temp_kelvin = kwargs.get(ATTR_COLOR_TEMP_KELVIN)
await self.coordinator.device_client.async_set_cct(color_temp_kelvin)
self.coordinator.data.cct = color_temp_kelvin
self._attr_color_mode = ColorMode.COLOR_TEMP
elif ATTR_RGBW_COLOR in kwargs:
rgbw_color = kwargs.get(ATTR_RGBW_COLOR)
await self.coordinator.device_client.async_set_rgbw(rgbw_color)
self.coordinator.data.rgbw = rgbw_color
self._attr_color_mode = ColorMode.RGBW
else:
await self.coordinator.device_client.async_turn_on()
Comment thread
s1eedz marked this conversation as resolved.
Comment on lines +96 to +113
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When async_turn_on is called with brightness, color_temp, or rgbw parameters, the light's local state is optimistically set to on (lines 127-128) but the device is sent only the property-specific command (brightness/color) without an explicit turn-on. If the device requires being turned on before accepting property changes, this could cause unexpected behavior. Consider either: (1) documenting this as the expected AiDot API behavior, or (2) always sending an initial turn-on command before property commands when the device is currently off.

Copilot uses AI. Check for mistakes.

self.coordinator.data.on = True
self._attr_is_on = True
Comment on lines +96 to +116
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The async_turn_on and async_turn_off methods directly modify self.coordinator.data attributes instead of using the coordinator's async_set_updated_data method. This bypasses the coordinator's update mechanism and can lead to state synchronization issues. Consider using async_set_updated_data to update the data properly.

Copilot uses AI. Check for mistakes.

async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn the light off."""
await self.coordinator.device_client.async_turn_off()
self.coordinator.data.on = False
self._attr_is_on = False
Loading
Loading