Add Govee local light integration (#106697)

* Added govee_local_api

* Code cleanup

* Fix discovery

* Add missing supported device

* Fix autodiscovery

* Add missing quality scale in manifest.json

* QA

* QA: Moved coordinator creation to __init__.py

* QA

* Fix typo and update test

* QA

* Removed unecessary code

* Fix typo

* Fix typo

* QA, typing and strings

* Removed unsed logger in __init__.py

* QA, using ColorMode for lights capabilities

* Bump govee_local_api to 1.4.0

Moved capabilities to library.

* Update requirements

* Update library to 1.4.1 with unsupported dvice warning

* Fix tests after library update

* QA

* Add test for retry config

* Update integration name and domain

* Update homeassistant/components/govee_light_local/light.py

---------

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
This commit is contained in:
Galorhallen
2024-01-16 20:00:29 +01:00
committed by GitHub
parent 95ed1ada50
commit ddaf194f91
17 changed files with 868 additions and 5 deletions

View File

@ -495,6 +495,8 @@ build.json @home-assistant/supervisor
/tests/components/google_travel_time/ @eifinger
/homeassistant/components/govee_ble/ @bdraco @PierreAronnax
/tests/components/govee_ble/ @bdraco @PierreAronnax
/homeassistant/components/govee_light_local/ @Galorhallen
/tests/components/govee_light_local/ @Galorhallen
/homeassistant/components/gpsd/ @fabaff
/homeassistant/components/gree/ @cmroche
/tests/components/gree/ @cmroche

View File

@ -0,0 +1,5 @@
{
"domain": "govee",
"name": "Govee",
"integrations": ["govee_ble", "govee_light_local"]
}

View File

@ -0,0 +1,44 @@
"""The Govee Light local integration."""
from __future__ import annotations
import asyncio
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady
from .const import DOMAIN
from .coordinator import GoveeLocalApiCoordinator
PLATFORMS: list[Platform] = [Platform.LIGHT]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up Govee light local from a config entry."""
coordinator: GoveeLocalApiCoordinator = GoveeLocalApiCoordinator(hass=hass)
entry.async_on_unload(coordinator.cleanup)
await coordinator.start()
await coordinator.async_config_entry_first_refresh()
try:
async with asyncio.timeout(delay=5):
while not coordinator.devices:
await asyncio.sleep(delay=1)
except asyncio.TimeoutError as ex:
raise ConfigEntryNotReady from ex
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
hass.data[DOMAIN].pop(entry.entry_id)
return unload_ok

View File

@ -0,0 +1,58 @@
"""Config flow for Govee light local."""
from __future__ import annotations
import asyncio
import logging
from govee_local_api import GoveeController
from homeassistant.components import network
from homeassistant.core import HomeAssistant
from homeassistant.helpers import config_entry_flow
from .const import (
CONF_LISTENING_PORT_DEFAULT,
CONF_MULTICAST_ADDRESS_DEFAULT,
CONF_TARGET_PORT_DEFAULT,
DOMAIN,
)
_LOGGER = logging.getLogger(__name__)
async def _async_has_devices(hass: HomeAssistant) -> bool:
"""Return if there are devices that can be discovered."""
adapter = await network.async_get_source_ip(hass, network.PUBLIC_TARGET_IP)
controller: GoveeController = GoveeController(
loop=hass.loop,
logger=_LOGGER,
listening_address=adapter,
broadcast_address=CONF_MULTICAST_ADDRESS_DEFAULT,
broadcast_port=CONF_TARGET_PORT_DEFAULT,
listening_port=CONF_LISTENING_PORT_DEFAULT,
discovery_enabled=True,
discovery_interval=1,
update_enabled=False,
)
await controller.start()
try:
async with asyncio.timeout(delay=5):
while not controller.devices:
await asyncio.sleep(delay=1)
except asyncio.TimeoutError:
_LOGGER.debug("No devices found")
devices_count = len(controller.devices)
controller.cleanup()
return devices_count > 0
config_entry_flow.register_discovery_flow(
DOMAIN, "Govee light local", _async_has_devices
)

View File

@ -0,0 +1,13 @@
"""Constants for the Govee light local integration."""
from datetime import timedelta
DOMAIN = "govee_light_local"
MANUFACTURER = "Govee"
CONF_MULTICAST_ADDRESS_DEFAULT = "239.255.255.250"
CONF_TARGET_PORT_DEFAULT = 4001
CONF_LISTENING_PORT_DEFAULT = 4002
CONF_DISCOVERY_INTERVAL_DEFAULT = 60
SCAN_INTERVAL = timedelta(seconds=30)

View File

@ -0,0 +1,90 @@
"""Coordinator for Govee light local."""
from collections.abc import Callable
import logging
from govee_local_api import GoveeController, GoveeDevice
from homeassistant.core import HomeAssistant
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from .const import (
CONF_DISCOVERY_INTERVAL_DEFAULT,
CONF_LISTENING_PORT_DEFAULT,
CONF_MULTICAST_ADDRESS_DEFAULT,
CONF_TARGET_PORT_DEFAULT,
SCAN_INTERVAL,
)
_LOGGER = logging.getLogger(__name__)
class GoveeLocalApiCoordinator(DataUpdateCoordinator[list[GoveeDevice]]):
"""Govee light local coordinator."""
def __init__(self, hass: HomeAssistant) -> None:
"""Initialize my coordinator."""
super().__init__(
hass=hass,
logger=_LOGGER,
name="GoveeLightLocalApi",
update_interval=SCAN_INTERVAL,
)
self._controller = GoveeController(
loop=hass.loop,
logger=_LOGGER,
broadcast_address=CONF_MULTICAST_ADDRESS_DEFAULT,
broadcast_port=CONF_TARGET_PORT_DEFAULT,
listening_port=CONF_LISTENING_PORT_DEFAULT,
discovery_enabled=True,
discovery_interval=CONF_DISCOVERY_INTERVAL_DEFAULT,
discovered_callback=None,
update_enabled=False,
)
async def start(self) -> None:
"""Start the Govee coordinator."""
await self._controller.start()
self._controller.send_update_message()
async def set_discovery_callback(
self, callback: Callable[[GoveeDevice, bool], bool]
) -> None:
"""Set discovery callback for automatic Govee light discovery."""
self._controller.set_device_discovered_callback(callback)
def cleanup(self) -> None:
"""Stop and cleanup the cooridinator."""
self._controller.cleanup()
async def turn_on(self, device: GoveeDevice) -> None:
"""Turn on the light."""
await device.turn_on()
async def turn_off(self, device: GoveeDevice) -> None:
"""Turn off the light."""
await device.turn_off()
async def set_brightness(self, device: GoveeDevice, brightness: int) -> None:
"""Set light brightness."""
await device.set_brightness(brightness)
async def set_rgb_color(
self, device: GoveeDevice, red: int, green: int, blue: int
) -> None:
"""Set light RGB color."""
await device.set_rgb_color(red, green, blue)
async def set_temperature(self, device: GoveeDevice, temperature: int) -> None:
"""Set light color in kelvin."""
await device.set_temperature(temperature)
@property
def devices(self) -> list[GoveeDevice]:
"""Return a list of discovered Govee devices."""
return self._controller.devices
async def _async_update_data(self) -> list[GoveeDevice]:
self._controller.send_update_message()
return self._controller.devices

View File

@ -0,0 +1,160 @@
"""Govee light local."""
from __future__ import annotations
import logging
from typing import Any
from govee_local_api import GoveeDevice, GoveeLightCapability
from homeassistant.components.light import (
ATTR_BRIGHTNESS,
ATTR_COLOR_TEMP_KELVIN,
ATTR_RGB_COLOR,
ColorMode,
LightEntity,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC, DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import DOMAIN, MANUFACTURER
from .coordinator import GoveeLocalApiCoordinator
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Govee light setup."""
coordinator: GoveeLocalApiCoordinator = hass.data[DOMAIN][config_entry.entry_id]
def discovery_callback(device: GoveeDevice, is_new: bool) -> bool:
if is_new:
async_add_entities([GoveeLight(coordinator, device)])
return True
async_add_entities(
GoveeLight(coordinator, device) for device in coordinator.devices
)
await coordinator.set_discovery_callback(discovery_callback)
class GoveeLight(CoordinatorEntity[GoveeLocalApiCoordinator], LightEntity):
"""Govee Light."""
_attr_has_entity_name = True
_attr_name = None
def __init__(
self,
coordinator: GoveeLocalApiCoordinator,
device: GoveeDevice,
) -> None:
"""Govee Light constructor."""
super().__init__(coordinator)
self._device = device
device.set_update_callback(self._update_callback)
self._attr_unique_id = device.fingerprint
capabilities = device.capabilities
color_modes = set()
if capabilities:
if GoveeLightCapability.COLOR_RGB in capabilities:
color_modes.add(ColorMode.RGB)
if GoveeLightCapability.COLOR_KELVIN_TEMPERATURE in capabilities:
color_modes.add(ColorMode.COLOR_TEMP)
self._attr_max_color_temp_kelvin = 9000
self._attr_min_color_temp_kelvin = 2000
if GoveeLightCapability.BRIGHTNESS in capabilities:
color_modes.add(ColorMode.BRIGHTNESS)
else:
color_modes.add(ColorMode.ONOFF)
self._attr_supported_color_modes = color_modes
self._attr_device_info = DeviceInfo(
identifiers={
# Serial numbers are unique identifiers within a specific domain
(DOMAIN, device.fingerprint)
},
name=device.sku,
manufacturer=MANUFACTURER,
model=device.sku,
connections={(CONNECTION_NETWORK_MAC, device.fingerprint)},
)
@property
def is_on(self) -> bool:
"""Return true if device is on (brightness above 0)."""
return self._device.on
@property
def brightness(self) -> int:
"""Return the brightness of this light between 0..255."""
return int((self._device.brightness / 100.0) * 255.0)
@property
def color_temp_kelvin(self) -> int | None:
"""Return the color temperature in Kelvin."""
return self._device.temperature_color
@property
def rgb_color(self) -> tuple[int, int, int] | None:
"""Return the rgb color."""
return self._device.rgb_color
@property
def color_mode(self) -> ColorMode | str | None:
"""Return the color mode."""
if (
self._device.temperature_color is not None
and self._device.temperature_color > 0
):
return ColorMode.COLOR_TEMP
if self._device.rgb_color is not None and any(self._device.rgb_color):
return ColorMode.RGB
if (
self._attr_supported_color_modes
and ColorMode.BRIGHTNESS in self._attr_supported_color_modes
):
return ColorMode.BRIGHTNESS
return ColorMode.ONOFF
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn the device on."""
if not self.is_on or not kwargs:
await self.coordinator.turn_on(self._device)
if ATTR_BRIGHTNESS in kwargs:
brightness: int = int((float(kwargs[ATTR_BRIGHTNESS]) / 255.0) * 100.0)
await self.coordinator.set_brightness(self._device, brightness)
if ATTR_RGB_COLOR in kwargs:
self._attr_color_mode = ColorMode.RGB
red, green, blue = kwargs[ATTR_RGB_COLOR]
await self.coordinator.set_rgb_color(self._device, red, green, blue)
elif ATTR_COLOR_TEMP_KELVIN in kwargs:
self._attr_color_mode = ColorMode.COLOR_TEMP
temperature: float = kwargs[ATTR_COLOR_TEMP_KELVIN]
await self.coordinator.set_temperature(self._device, int(temperature))
self.async_write_ha_state()
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn the device off."""
await self.coordinator.turn_off(self._device)
self.async_write_ha_state()
@callback
def _update_callback(self, device: GoveeDevice) -> None:
self.async_write_ha_state()

View File

@ -0,0 +1,10 @@
{
"domain": "govee_light_local",
"name": "Govee lights local",
"codeowners": ["@Galorhallen"],
"config_flow": true,
"dependencies": ["network"],
"documentation": "https://www.home-assistant.io/integrations/govee_light_local",
"iot_class": "local_push",
"requirements": ["govee-local-api==1.4.1"]
}

View File

@ -0,0 +1,13 @@
{
"config": {
"step": {
"confirm": {
"description": "[%key:common::config_flow::description::confirm_setup%]"
}
},
"abort": {
"single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]",
"no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]"
}
}
}

View File

@ -197,6 +197,7 @@ FLOWS = {
"google_translate",
"google_travel_time",
"govee_ble",
"govee_light_local",
"gpslogger",
"gree",
"growatt_server",

View File

@ -2266,11 +2266,22 @@
}
}
},
"govee_ble": {
"name": "Govee Bluetooth",
"integration_type": "hub",
"config_flow": true,
"iot_class": "local_push"
"govee": {
"name": "Govee",
"integrations": {
"govee_ble": {
"integration_type": "hub",
"config_flow": true,
"iot_class": "local_push",
"name": "Govee Bluetooth"
},
"govee_light_local": {
"integration_type": "hub",
"config_flow": true,
"iot_class": "local_push",
"name": "Govee lights local"
}
}
},
"gpsd": {
"name": "GPSD",

View File

@ -954,6 +954,9 @@ gotailwind==0.2.2
# homeassistant.components.govee_ble
govee-ble==0.27.3
# homeassistant.components.govee_light_local
govee-local-api==1.4.1
# homeassistant.components.remote_rpi_gpio
gpiozero==1.6.2

View File

@ -771,6 +771,9 @@ gotailwind==0.2.2
# homeassistant.components.govee_ble
govee-ble==0.27.3
# homeassistant.components.govee_light_local
govee-local-api==1.4.1
# homeassistant.components.gree
greeclimate==1.4.1

View File

@ -0,0 +1 @@
"""Tests for the Govee Light local integration."""

View File

@ -0,0 +1,37 @@
"""Tests configuration for Govee Local API."""
from collections.abc import Generator
from unittest.mock import AsyncMock, patch
from govee_local_api import GoveeLightCapability
import pytest
from homeassistant.components.govee_light_local.coordinator import GoveeController
@pytest.fixture(name="mock_govee_api")
def fixture_mock_govee_api():
"""Set up Govee Local API fixture."""
mock_api = AsyncMock(spec=GoveeController)
mock_api.start = AsyncMock()
mock_api.turn_on_off = AsyncMock()
mock_api.set_brightness = AsyncMock()
mock_api.set_color = AsyncMock()
mock_api._async_update_data = AsyncMock()
return mock_api
@pytest.fixture(name="mock_setup_entry")
def fixture_mock_setup_entry() -> Generator[AsyncMock, None, None]:
"""Override async_setup_entry."""
with patch(
"homeassistant.components.govee_light_local.async_setup_entry",
return_value=True,
) as mock_setup_entry:
yield mock_setup_entry
DEFAULT_CAPABILITEIS: set[GoveeLightCapability] = {
GoveeLightCapability.COLOR_RGB,
GoveeLightCapability.COLOR_KELVIN_TEMPERATURE,
GoveeLightCapability.BRIGHTNESS,
}

View File

@ -0,0 +1,74 @@
"""Test Govee light local config flow."""
from unittest.mock import AsyncMock, patch
from govee_local_api import GoveeDevice
from homeassistant import config_entries, data_entry_flow
from homeassistant.components.govee_light_local.const import DOMAIN
from homeassistant.core import HomeAssistant
from .conftest import DEFAULT_CAPABILITEIS
async def test_creating_entry_has_no_devices(
hass: HomeAssistant, mock_setup_entry: AsyncMock, mock_govee_api: AsyncMock
) -> None:
"""Test setting up Govee with no devices."""
mock_govee_api.devices = []
with patch(
"homeassistant.components.govee_light_local.config_flow.GoveeController",
return_value=mock_govee_api,
):
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
# Confirmation form
assert result["type"] == data_entry_flow.FlowResultType.FORM
result = await hass.config_entries.flow.async_configure(result["flow_id"], {})
assert result["type"] == data_entry_flow.FlowResultType.ABORT
await hass.async_block_till_done()
mock_govee_api.start.assert_awaited_once()
mock_setup_entry.assert_not_called()
async def test_creating_entry_has_with_devices(
hass: HomeAssistant,
mock_setup_entry: AsyncMock,
mock_govee_api: AsyncMock,
) -> None:
"""Test setting up Govee with devices."""
mock_govee_api.devices = [
GoveeDevice(
controller=mock_govee_api,
ip="192.168.1.100",
fingerprint="asdawdqwdqwd1",
sku="H615A",
capabilities=DEFAULT_CAPABILITEIS,
)
]
with patch(
"homeassistant.components.govee_light_local.config_flow.GoveeController",
return_value=mock_govee_api,
):
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
# Confirmation form
assert result["type"] == data_entry_flow.FlowResultType.FORM
result = await hass.config_entries.flow.async_configure(result["flow_id"], {})
assert result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY
await hass.async_block_till_done()
mock_govee_api.start.assert_awaited_once()
mock_setup_entry.assert_awaited_once()

View File

@ -0,0 +1,338 @@
"""Test Govee light local."""
import asyncio
from unittest.mock import AsyncMock, MagicMock, patch
from govee_local_api import GoveeDevice
from homeassistant.components.govee_light_local.const import DOMAIN
from homeassistant.components.light import ATTR_SUPPORTED_COLOR_MODES, ColorMode
from homeassistant.config_entries import ConfigEntryState
from homeassistant.core import HomeAssistant
from .conftest import DEFAULT_CAPABILITEIS
from tests.common import MockConfigEntry
async def test_light_known_device(
hass: HomeAssistant, mock_govee_api: AsyncMock
) -> None:
"""Test adding a known device."""
mock_govee_api.devices = [
GoveeDevice(
controller=mock_govee_api,
ip="192.168.1.100",
fingerprint="asdawdqwdqwd",
sku="H615A",
capabilities=DEFAULT_CAPABILITEIS,
)
]
with patch(
"homeassistant.components.govee_light_local.coordinator.GoveeController",
return_value=mock_govee_api,
):
entry = MockConfigEntry(domain=DOMAIN)
entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
assert len(hass.states.async_all()) == 1
light = hass.states.get("light.H615A")
assert light is not None
color_modes = light.attributes[ATTR_SUPPORTED_COLOR_MODES]
assert ColorMode.RGB in color_modes
assert ColorMode.BRIGHTNESS in color_modes
assert ColorMode.COLOR_TEMP in color_modes
# Remove
assert await hass.config_entries.async_remove(entry.entry_id)
await hass.async_block_till_done()
assert hass.states.get("light.H615A") is None
async def test_light_unknown_device(
hass: HomeAssistant, mock_govee_api: AsyncMock
) -> None:
"""Test adding an unknown device."""
mock_govee_api.devices = [
GoveeDevice(
controller=mock_govee_api,
ip="192.168.1.101",
fingerprint="unkown_device",
sku="XYZK",
capabilities=None,
)
]
with patch(
"homeassistant.components.govee_light_local.coordinator.GoveeController",
return_value=mock_govee_api,
):
entry = MockConfigEntry(domain=DOMAIN)
entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
assert len(hass.states.async_all()) == 1
light = hass.states.get("light.XYZK")
assert light is not None
assert light.attributes[ATTR_SUPPORTED_COLOR_MODES] == [ColorMode.ONOFF]
async def test_light_remove(hass: HomeAssistant, mock_govee_api: AsyncMock) -> None:
"""Test adding a known device."""
mock_govee_api.devices = [
GoveeDevice(
controller=mock_govee_api,
ip="192.168.1.100",
fingerprint="asdawdqwdqwd1",
sku="H615A",
capabilities=DEFAULT_CAPABILITEIS,
)
]
with patch(
"homeassistant.components.govee_light_local.coordinator.GoveeController",
return_value=mock_govee_api,
):
entry = MockConfigEntry(domain=DOMAIN)
entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
assert hass.states.get("light.H615A") is not None
# Remove 1
assert await hass.config_entries.async_remove(entry.entry_id)
await hass.async_block_till_done()
assert len(hass.states.async_all()) == 0
async def test_light_setup_retry(
hass: HomeAssistant, mock_govee_api: AsyncMock
) -> None:
"""Test adding an unknown device."""
mock_govee_api.devices = []
with patch(
"homeassistant.components.govee_light_local.coordinator.GoveeController",
return_value=mock_govee_api,
):
entry = MockConfigEntry(domain=DOMAIN)
entry.add_to_hass(hass)
with patch(
"homeassistant.components.govee_light_local.asyncio.timeout",
side_effect=asyncio.TimeoutError,
):
await hass.config_entries.async_setup(entry.entry_id)
assert entry.state is ConfigEntryState.SETUP_RETRY
async def test_light_on_off(hass: HomeAssistant, mock_govee_api: MagicMock) -> None:
"""Test adding a known device."""
mock_govee_api.devices = [
GoveeDevice(
controller=mock_govee_api,
ip="192.168.1.100",
fingerprint="asdawdqwdqwd",
sku="H615A",
capabilities=DEFAULT_CAPABILITEIS,
)
]
with patch(
"homeassistant.components.govee_light_local.coordinator.GoveeController",
return_value=mock_govee_api,
):
entry = MockConfigEntry(domain=DOMAIN)
entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
assert len(hass.states.async_all()) == 1
light = hass.states.get("light.H615A")
assert light is not None
assert light.state == "off"
await hass.services.async_call(
"light",
"turn_on",
{"entity_id": light.entity_id},
blocking=True,
)
await hass.async_block_till_done()
light = hass.states.get("light.H615A")
assert light is not None
assert light.state == "on"
mock_govee_api.turn_on_off.assert_awaited_with(mock_govee_api.devices[0], True)
# Turn off
await hass.services.async_call(
"light",
"turn_off",
{"entity_id": light.entity_id},
blocking=True,
)
await hass.async_block_till_done()
light = hass.states.get("light.H615A")
assert light is not None
assert light.state == "off"
mock_govee_api.turn_on_off.assert_awaited_with(mock_govee_api.devices[0], False)
async def test_light_brightness(hass: HomeAssistant, mock_govee_api: MagicMock) -> None:
"""Test changing brightness."""
mock_govee_api.devices = [
GoveeDevice(
controller=mock_govee_api,
ip="192.168.1.100",
fingerprint="asdawdqwdqwd",
sku="H615A",
capabilities=DEFAULT_CAPABILITEIS,
)
]
with patch(
"homeassistant.components.govee_light_local.coordinator.GoveeController",
return_value=mock_govee_api,
):
entry = MockConfigEntry(domain=DOMAIN)
entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
assert len(hass.states.async_all()) == 1
light = hass.states.get("light.H615A")
assert light is not None
assert light.state == "off"
await hass.services.async_call(
"light",
"turn_on",
{"entity_id": light.entity_id, "brightness_pct": 50},
blocking=True,
)
await hass.async_block_till_done()
light = hass.states.get("light.H615A")
assert light is not None
assert light.state == "on"
mock_govee_api.set_brightness.assert_awaited_with(mock_govee_api.devices[0], 50)
assert light.attributes["brightness"] == 127
await hass.services.async_call(
"light",
"turn_on",
{"entity_id": light.entity_id, "brightness": 255},
blocking=True,
)
await hass.async_block_till_done()
light = hass.states.get("light.H615A")
assert light is not None
assert light.state == "on"
assert light.attributes["brightness"] == 255
mock_govee_api.set_brightness.assert_awaited_with(
mock_govee_api.devices[0], 100
)
await hass.services.async_call(
"light",
"turn_on",
{"entity_id": light.entity_id, "brightness": 255},
blocking=True,
)
await hass.async_block_till_done()
light = hass.states.get("light.H615A")
assert light is not None
assert light.state == "on"
assert light.attributes["brightness"] == 255
mock_govee_api.set_brightness.assert_awaited_with(
mock_govee_api.devices[0], 100
)
async def test_light_color(hass: HomeAssistant, mock_govee_api: MagicMock) -> None:
"""Test changing brightness."""
mock_govee_api.devices = [
GoveeDevice(
controller=mock_govee_api,
ip="192.168.1.100",
fingerprint="asdawdqwdqwd",
sku="H615A",
capabilities=DEFAULT_CAPABILITEIS,
)
]
with patch(
"homeassistant.components.govee_light_local.coordinator.GoveeController",
return_value=mock_govee_api,
):
entry = MockConfigEntry(domain=DOMAIN)
entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
assert len(hass.states.async_all()) == 1
light = hass.states.get("light.H615A")
assert light is not None
assert light.state == "off"
await hass.services.async_call(
"light",
"turn_on",
{"entity_id": light.entity_id, "rgb_color": [100, 255, 50]},
blocking=True,
)
await hass.async_block_till_done()
light = hass.states.get("light.H615A")
assert light is not None
assert light.state == "on"
assert light.attributes["rgb_color"] == (100, 255, 50)
assert light.attributes["color_mode"] == ColorMode.RGB
mock_govee_api.set_color.assert_awaited_with(
mock_govee_api.devices[0], rgb=(100, 255, 50), temperature=None
)
await hass.services.async_call(
"light",
"turn_on",
{"entity_id": light.entity_id, "kelvin": 4400},
blocking=True,
)
await hass.async_block_till_done()
light = hass.states.get("light.H615A")
assert light is not None
assert light.state == "on"
assert light.attributes["color_temp_kelvin"] == 4400
assert light.attributes["color_mode"] == ColorMode.COLOR_TEMP
mock_govee_api.set_color.assert_awaited_with(
mock_govee_api.devices[0], rgb=None, temperature=4400
)