init
This commit is contained in:
114
custom_components/govee/__init__.py
Normal file
114
custom_components/govee/__init__.py
Normal file
@@ -0,0 +1,114 @@
|
||||
"""The Govee integration."""
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
from govee_api_laggat import Govee
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_API_KEY
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import PlatformNotReady
|
||||
|
||||
from .const import DOMAIN
|
||||
from .learning_storage import GoveeLearningStorage
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
CONFIG_SCHEMA = vol.Schema({DOMAIN: vol.Schema({})}, extra=vol.ALLOW_EXTRA)
|
||||
|
||||
# supported platforms
|
||||
PLATFORMS = ["light"]
|
||||
|
||||
|
||||
def setup(hass, config):
|
||||
"""This setup does nothing, we use the async setup."""
|
||||
hass.states.set("govee.state", "setup called")
|
||||
return True
|
||||
|
||||
|
||||
async def async_setup(hass: HomeAssistant, config: dict):
|
||||
"""Set up the Govee component."""
|
||||
hass.states.async_set("govee.state", "async_setup called")
|
||||
hass.data[DOMAIN] = {}
|
||||
return True
|
||||
|
||||
|
||||
def is_online(online: bool):
|
||||
"""Log online/offline change."""
|
||||
msg = "API is offline."
|
||||
if online:
|
||||
msg = "API is back online."
|
||||
_LOGGER.warning(msg)
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
|
||||
"""Set up Govee from a config entry."""
|
||||
|
||||
# get vars from ConfigFlow/OptionsFlow
|
||||
config = entry.data
|
||||
options = entry.options
|
||||
api_key = options.get(CONF_API_KEY, config.get(CONF_API_KEY, ""))
|
||||
|
||||
# Setup connection with devices/cloud
|
||||
hub = await Govee.create(
|
||||
api_key, learning_storage=GoveeLearningStorage(hass.config.config_dir)
|
||||
)
|
||||
# keep reference for disposing
|
||||
hass.data[DOMAIN] = {}
|
||||
hass.data[DOMAIN]["hub"] = hub
|
||||
|
||||
# inform when api is offline/online
|
||||
hub.events.online += is_online
|
||||
|
||||
# Verify that passed in configuration works
|
||||
_, err = await hub.get_devices()
|
||||
if err:
|
||||
_LOGGER.warning("Could not connect to Govee API: %s", err)
|
||||
await hub.rate_limit_delay()
|
||||
await async_unload_entry(hass, entry)
|
||||
raise PlatformNotReady()
|
||||
|
||||
for component in PLATFORMS:
|
||||
hass.async_create_task(
|
||||
hass.config_entries.async_forward_entry_setup(entry, component)
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):
|
||||
"""Unload a config entry."""
|
||||
|
||||
unload_ok = all(
|
||||
await asyncio.gather(
|
||||
*[
|
||||
_unload_component_entry(hass, entry, component)
|
||||
for component in PLATFORMS
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
if unload_ok:
|
||||
hub = hass.data[DOMAIN].pop("hub")
|
||||
await hub.close()
|
||||
|
||||
return unload_ok
|
||||
|
||||
|
||||
def _unload_component_entry(
|
||||
hass: HomeAssistant, entry: ConfigEntry, component: str
|
||||
) -> bool:
|
||||
"""Unload an entry for a specific component."""
|
||||
success = False
|
||||
try:
|
||||
success = hass.config_entries.async_forward_entry_unload(entry, component)
|
||||
except ValueError:
|
||||
# probably ValueError: Config entry was never loaded!
|
||||
return success
|
||||
except Exception as ex:
|
||||
_LOGGER.warning(
|
||||
"Continuing on exception when unloading %s component's entry: %s",
|
||||
component,
|
||||
ex,
|
||||
)
|
||||
return success
|
||||
BIN
custom_components/govee/__pycache__/__init__.cpython-312.pyc
Normal file
BIN
custom_components/govee/__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
BIN
custom_components/govee/__pycache__/__init__.cpython-313.pyc
Normal file
BIN
custom_components/govee/__pycache__/__init__.cpython-313.pyc
Normal file
Binary file not shown.
BIN
custom_components/govee/__pycache__/config_flow.cpython-312.pyc
Normal file
BIN
custom_components/govee/__pycache__/config_flow.cpython-312.pyc
Normal file
Binary file not shown.
BIN
custom_components/govee/__pycache__/config_flow.cpython-313.pyc
Normal file
BIN
custom_components/govee/__pycache__/config_flow.cpython-313.pyc
Normal file
Binary file not shown.
BIN
custom_components/govee/__pycache__/const.cpython-312.pyc
Normal file
BIN
custom_components/govee/__pycache__/const.cpython-312.pyc
Normal file
Binary file not shown.
BIN
custom_components/govee/__pycache__/const.cpython-313.pyc
Normal file
BIN
custom_components/govee/__pycache__/const.cpython-313.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
custom_components/govee/__pycache__/light.cpython-312.pyc
Normal file
BIN
custom_components/govee/__pycache__/light.cpython-312.pyc
Normal file
Binary file not shown.
BIN
custom_components/govee/__pycache__/light.cpython-313.pyc
Normal file
BIN
custom_components/govee/__pycache__/light.cpython-313.pyc
Normal file
Binary file not shown.
210
custom_components/govee/config_flow.py
Normal file
210
custom_components/govee/config_flow.py
Normal file
@@ -0,0 +1,210 @@
|
||||
"""Config flow for Govee integration."""
|
||||
|
||||
import logging
|
||||
|
||||
from govee_api_laggat import Govee, GoveeNoLearningStorage, GoveeError
|
||||
|
||||
from homeassistant import config_entries, core, exceptions
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.const import CONF_API_KEY, CONF_DELAY
|
||||
from homeassistant.core import callback
|
||||
import voluptuous as vol
|
||||
|
||||
from .const import (
|
||||
CONF_DISABLE_ATTRIBUTE_UPDATES,
|
||||
CONF_OFFLINE_IS_OFF,
|
||||
CONF_USE_ASSUMED_STATE,
|
||||
DOMAIN,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def validate_api_key(hass: core.HomeAssistant, user_input):
|
||||
"""Validate the user input allows us to connect.
|
||||
|
||||
Return info that you want to store in the config entry.
|
||||
"""
|
||||
api_key = user_input[CONF_API_KEY]
|
||||
async with Govee(api_key, learning_storage=GoveeNoLearningStorage()) as hub:
|
||||
_, error = await hub.get_devices()
|
||||
if error:
|
||||
raise CannotConnect(error)
|
||||
|
||||
# Return info that you want to store in the config entry.
|
||||
return user_input
|
||||
|
||||
|
||||
async def validate_disabled_attribute_updates(hass: core.HomeAssistant, user_input):
|
||||
"""Validate format of the ignore_device_attributes parameter string
|
||||
|
||||
Return info that you want to store in the config entry.
|
||||
"""
|
||||
disable_str = user_input[CONF_DISABLE_ATTRIBUTE_UPDATES]
|
||||
if disable_str:
|
||||
# we have something to check, connect without API key
|
||||
async with Govee("", learning_storage=GoveeNoLearningStorage()) as hub:
|
||||
# this will throw an GoveeError if something fails
|
||||
hub.ignore_device_attributes(disable_str)
|
||||
|
||||
# Return info that you want to store in the config entry.
|
||||
return user_input
|
||||
|
||||
|
||||
@config_entries.HANDLERS.register(DOMAIN)
|
||||
class GoveeFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
"""Handle a config flow for Govee."""
|
||||
|
||||
VERSION = 1
|
||||
CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL
|
||||
|
||||
async def async_step_user(self, user_input=None):
|
||||
"""Handle the initial step."""
|
||||
errors = {}
|
||||
if user_input is not None:
|
||||
try:
|
||||
user_input = await validate_api_key(self.hass, user_input)
|
||||
|
||||
except CannotConnect as conn_ex:
|
||||
_LOGGER.exception("Cannot connect: %s", conn_ex)
|
||||
errors[CONF_API_KEY] = "cannot_connect"
|
||||
except GoveeError as govee_ex:
|
||||
_LOGGER.exception("Govee library error: %s", govee_ex)
|
||||
errors["base"] = "govee_ex"
|
||||
except Exception as ex: # pylint: disable=broad-except
|
||||
_LOGGER.exception("Unexpected exception: %s", ex)
|
||||
errors["base"] = "unknown"
|
||||
|
||||
if not errors:
|
||||
return self.async_create_entry(title=DOMAIN, data=user_input)
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="user",
|
||||
data_schema=vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_API_KEY): cv.string,
|
||||
vol.Optional(CONF_DELAY, default=10): cv.positive_int,
|
||||
}
|
||||
),
|
||||
errors=errors,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
@callback
|
||||
def async_get_options_flow(config_entry):
|
||||
"""Get the options flow."""
|
||||
return GoveeOptionsFlowHandler(config_entry)
|
||||
|
||||
|
||||
class GoveeOptionsFlowHandler(config_entries.OptionsFlow):
|
||||
"""Handle options."""
|
||||
|
||||
VERSION = 1
|
||||
|
||||
def __init__(self, config_entry):
|
||||
"""Initialize options flow."""
|
||||
self.config_entry = config_entry
|
||||
self.options = dict(config_entry.options)
|
||||
|
||||
async def async_step_init(self, user_input=None):
|
||||
"""Manage the options."""
|
||||
return await self.async_step_user()
|
||||
|
||||
async def async_step_user(self, user_input=None):
|
||||
"""Manage the options."""
|
||||
# get the current value for API key for comparison and default value
|
||||
old_api_key = self.config_entry.options.get(
|
||||
CONF_API_KEY, self.config_entry.data.get(CONF_API_KEY, "")
|
||||
)
|
||||
|
||||
errors = {}
|
||||
if user_input is not None:
|
||||
# check if API Key changed and is valid
|
||||
try:
|
||||
api_key = user_input[CONF_API_KEY]
|
||||
if old_api_key != api_key:
|
||||
user_input = await validate_api_key(self.hass, user_input)
|
||||
|
||||
except CannotConnect as conn_ex:
|
||||
_LOGGER.exception("Cannot connect: %s", conn_ex)
|
||||
errors[CONF_API_KEY] = "cannot_connect"
|
||||
except GoveeError as govee_ex:
|
||||
_LOGGER.exception("Govee library error: %s", govee_ex)
|
||||
errors["base"] = "govee_ex"
|
||||
except Exception as ex: # pylint: disable=broad-except
|
||||
_LOGGER.exception("Unexpected exception: %s", ex)
|
||||
errors["base"] = "unknown"
|
||||
|
||||
# check validate_disabled_attribute_updates
|
||||
try:
|
||||
user_input = await validate_disabled_attribute_updates(
|
||||
self.hass, user_input
|
||||
)
|
||||
|
||||
# apply settings to the running instance
|
||||
if DOMAIN in self.hass.data and "hub" in self.hass.data[DOMAIN]:
|
||||
hub = self.hass.data[DOMAIN]["hub"]
|
||||
if hub:
|
||||
disable_str = user_input[CONF_DISABLE_ATTRIBUTE_UPDATES]
|
||||
hub.ignore_device_attributes(disable_str)
|
||||
except GoveeError as govee_ex:
|
||||
_LOGGER.exception(
|
||||
"Wrong input format for validate_disabled_attribute_updates: %s",
|
||||
govee_ex,
|
||||
)
|
||||
errors[
|
||||
CONF_DISABLE_ATTRIBUTE_UPDATES
|
||||
] = "disabled_attribute_updates_wrong"
|
||||
|
||||
if not errors:
|
||||
# update options flow values
|
||||
self.options.update(user_input)
|
||||
return await self._update_options()
|
||||
# for later - extend with options you don't want in config but option flow
|
||||
# return await self.async_step_options_2()
|
||||
|
||||
options_schema = vol.Schema(
|
||||
{
|
||||
# to config flow
|
||||
vol.Required(
|
||||
CONF_API_KEY,
|
||||
default=old_api_key,
|
||||
): cv.string,
|
||||
vol.Optional(
|
||||
CONF_DELAY,
|
||||
default=self.config_entry.options.get(
|
||||
CONF_DELAY, self.config_entry.data.get(CONF_DELAY, 10)
|
||||
),
|
||||
): cv.positive_int,
|
||||
# to options flow
|
||||
vol.Required(
|
||||
CONF_USE_ASSUMED_STATE,
|
||||
default=self.config_entry.options.get(CONF_USE_ASSUMED_STATE, True),
|
||||
): cv.boolean,
|
||||
vol.Required(
|
||||
CONF_OFFLINE_IS_OFF,
|
||||
default=self.config_entry.options.get(CONF_OFFLINE_IS_OFF, False),
|
||||
): cv.boolean,
|
||||
# TODO: validator doesn't work, change to list?
|
||||
vol.Optional(
|
||||
CONF_DISABLE_ATTRIBUTE_UPDATES,
|
||||
default=self.config_entry.options.get(
|
||||
CONF_DISABLE_ATTRIBUTE_UPDATES, ""
|
||||
),
|
||||
): cv.string,
|
||||
},
|
||||
)
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="user",
|
||||
data_schema=options_schema,
|
||||
errors=errors,
|
||||
)
|
||||
|
||||
async def _update_options(self):
|
||||
"""Update config entry options."""
|
||||
return self.async_create_entry(title=DOMAIN, data=self.options)
|
||||
|
||||
|
||||
class CannotConnect(exceptions.HomeAssistantError):
|
||||
"""Error to indicate we cannot connect."""
|
||||
10
custom_components/govee/const.py
Normal file
10
custom_components/govee/const.py
Normal file
@@ -0,0 +1,10 @@
|
||||
"""Constants for the Govee LED strips integration."""
|
||||
|
||||
DOMAIN = "govee"
|
||||
|
||||
CONF_DISABLE_ATTRIBUTE_UPDATES = "disable_attribute_updates"
|
||||
CONF_OFFLINE_IS_OFF = "offline_is_off"
|
||||
CONF_USE_ASSUMED_STATE = "use_assumed_state"
|
||||
|
||||
COLOR_TEMP_KELVIN_MIN = 2000
|
||||
COLOR_TEMP_KELVIN_MAX = 9000
|
||||
1
custom_components/govee/govee
Normal file
1
custom_components/govee/govee
Normal file
@@ -0,0 +1 @@
|
||||
/workspaces/hacs-govee/custom_components/govee
|
||||
66
custom_components/govee/learning_storage.py
Normal file
66
custom_components/govee/learning_storage.py
Normal file
@@ -0,0 +1,66 @@
|
||||
"""The Govee learned storage yaml file manager."""
|
||||
|
||||
from dataclasses import asdict
|
||||
import logging
|
||||
|
||||
import dacite
|
||||
from govee_api_laggat import GoveeAbstractLearningStorage, GoveeLearnedInfo
|
||||
import yaml
|
||||
|
||||
from homeassistant.util.yaml import load_yaml, save_yaml
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
LEARNING_STORAGE_YAML = "/govee_learning.yaml"
|
||||
|
||||
|
||||
class GoveeLearningStorage(GoveeAbstractLearningStorage):
|
||||
"""The govee_api_laggat library uses this to store learned information about lights."""
|
||||
|
||||
def __init__(self, config_dir, *args, **kwargs):
|
||||
"""Get the config directory."""
|
||||
super().__init__(*args, **kwargs)
|
||||
self._config_dir = config_dir
|
||||
|
||||
async def read(self):
|
||||
"""Restore from yaml file."""
|
||||
learned_info = {}
|
||||
try:
|
||||
learned_dict = load_yaml(self._config_dir + LEARNING_STORAGE_YAML)
|
||||
learned_info = {
|
||||
device: dacite.from_dict(
|
||||
data_class=GoveeLearnedInfo, data=learned_dict[device]
|
||||
)
|
||||
for device in learned_dict
|
||||
}
|
||||
_LOGGER.info(
|
||||
"Loaded learning information from %s.",
|
||||
self._config_dir + LEARNING_STORAGE_YAML,
|
||||
)
|
||||
except FileNotFoundError:
|
||||
_LOGGER.warning(
|
||||
"There is no %s file containing learned information about your devices. "
|
||||
+ "This is normal for first start of Govee integration.",
|
||||
self._config_dir + LEARNING_STORAGE_YAML,
|
||||
)
|
||||
except (
|
||||
dacite.DaciteError,
|
||||
TypeError,
|
||||
UnicodeDecodeError,
|
||||
yaml.YAMLError,
|
||||
) as ex:
|
||||
_LOGGER.warning(
|
||||
"The %s file containing learned information about your devices is invalid: %s. "
|
||||
+ "Learning starts from scratch.",
|
||||
self._config_dir + LEARNING_STORAGE_YAML,
|
||||
ex,
|
||||
)
|
||||
return learned_info
|
||||
|
||||
async def write(self, learned_info):
|
||||
"""Save to yaml file."""
|
||||
leaned_dict = {device: asdict(learned_info[device]) for device in learned_info}
|
||||
save_yaml(self._config_dir + LEARNING_STORAGE_YAML, leaned_dict)
|
||||
_LOGGER.info(
|
||||
"Stored learning information to %s.",
|
||||
self._config_dir + LEARNING_STORAGE_YAML,
|
||||
)
|
||||
325
custom_components/govee/light.py
Normal file
325
custom_components/govee/light.py
Normal file
@@ -0,0 +1,325 @@
|
||||
"""Govee platform."""
|
||||
|
||||
from datetime import timedelta, datetime
|
||||
import logging
|
||||
|
||||
from govee_api_laggat import Govee, GoveeDevice, GoveeError
|
||||
from govee_api_laggat.govee_dtos import GoveeSource
|
||||
|
||||
from homeassistant.components.light import (
|
||||
ATTR_BRIGHTNESS,
|
||||
ATTR_COLOR_TEMP,
|
||||
ATTR_HS_COLOR,
|
||||
SUPPORT_BRIGHTNESS,
|
||||
SUPPORT_COLOR,
|
||||
SUPPORT_COLOR_TEMP,
|
||||
LightEntity,
|
||||
)
|
||||
from homeassistant.const import CONF_DELAY
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||
from homeassistant.util import color
|
||||
|
||||
from .const import (
|
||||
DOMAIN,
|
||||
CONF_OFFLINE_IS_OFF,
|
||||
CONF_USE_ASSUMED_STATE,
|
||||
COLOR_TEMP_KELVIN_MIN,
|
||||
COLOR_TEMP_KELVIN_MAX,
|
||||
)
|
||||
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def async_setup_entry(hass, entry, async_add_entities):
|
||||
"""Set up the Govee Light platform."""
|
||||
_LOGGER.debug("Setting up Govee lights")
|
||||
config = entry.data
|
||||
options = entry.options
|
||||
hub = hass.data[DOMAIN]["hub"]
|
||||
|
||||
# refresh
|
||||
update_interval = timedelta(
|
||||
seconds=options.get(CONF_DELAY, config.get(CONF_DELAY, 10))
|
||||
)
|
||||
coordinator = GoveeDataUpdateCoordinator(
|
||||
hass, _LOGGER, update_interval=update_interval, config_entry=entry
|
||||
)
|
||||
# Fetch initial data so we have data when entities subscribe
|
||||
hub.events.new_device += lambda device: add_entity(
|
||||
async_add_entities, hub, entry, coordinator, device
|
||||
)
|
||||
await coordinator.async_refresh()
|
||||
|
||||
# Add devices
|
||||
for device in hub.devices:
|
||||
add_entity(async_add_entities, hub, entry, coordinator, device)
|
||||
# async_add_entities(
|
||||
# [
|
||||
# GoveeLightEntity(hub, entry.title, coordinator, device)
|
||||
# for device in hub.devices
|
||||
# ],
|
||||
# update_before_add=False,
|
||||
# )
|
||||
|
||||
|
||||
def add_entity(async_add_entities, hub, entry, coordinator, device):
|
||||
async_add_entities(
|
||||
[GoveeLightEntity(hub, entry.title, coordinator, device)],
|
||||
update_before_add=False,
|
||||
)
|
||||
|
||||
|
||||
class GoveeDataUpdateCoordinator(DataUpdateCoordinator):
|
||||
"""Device state update handler."""
|
||||
|
||||
def __init__(self, hass, logger, update_interval=None, *, config_entry):
|
||||
"""Initialize global data updater."""
|
||||
self._config_entry = config_entry
|
||||
|
||||
super().__init__(
|
||||
hass,
|
||||
logger,
|
||||
name=DOMAIN,
|
||||
update_interval=update_interval,
|
||||
update_method=self._async_update,
|
||||
)
|
||||
|
||||
@property
|
||||
def use_assumed_state(self):
|
||||
"""Use assumed states."""
|
||||
return self._config_entry.options.get(CONF_USE_ASSUMED_STATE, True)
|
||||
|
||||
@property
|
||||
def config_offline_is_off(self):
|
||||
"""Interpret offline led's as off (global config)."""
|
||||
return self._config_entry.options.get(CONF_OFFLINE_IS_OFF, False)
|
||||
|
||||
async def _async_update(self):
|
||||
"""Fetch data."""
|
||||
self.logger.debug("_async_update")
|
||||
if "govee" not in self.hass.data:
|
||||
raise UpdateFailed("Govee instance not available")
|
||||
try:
|
||||
hub = self.hass.data[DOMAIN]["hub"]
|
||||
|
||||
if not hub.online:
|
||||
# when offline, check connection, this will set hub.online
|
||||
await hub.check_connection()
|
||||
|
||||
if hub.online:
|
||||
# set global options to library
|
||||
if self.config_offline_is_off:
|
||||
hub.config_offline_is_off = True
|
||||
else:
|
||||
hub.config_offline_is_off = None # allow override in learning info
|
||||
|
||||
# govee will change this to a single request in 2021
|
||||
device_states = await hub.get_states()
|
||||
for device in device_states:
|
||||
if device.error:
|
||||
self.logger.warning(
|
||||
"update failed for %s: %s", device.device, device.error
|
||||
)
|
||||
return device_states
|
||||
except GoveeError as ex:
|
||||
raise UpdateFailed(f"Exception on getting states: {ex}") from ex
|
||||
|
||||
|
||||
class GoveeLightEntity(LightEntity):
|
||||
"""Representation of a stateful light entity."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
hub: Govee,
|
||||
title: str,
|
||||
coordinator: GoveeDataUpdateCoordinator,
|
||||
device: GoveeDevice,
|
||||
):
|
||||
"""Init a Govee light strip."""
|
||||
self._hub = hub
|
||||
self._title = title
|
||||
self._coordinator = coordinator
|
||||
self._device = device
|
||||
|
||||
@property
|
||||
def entity_registry_enabled_default(self):
|
||||
"""Return if the entity should be enabled when first added to the entity registry."""
|
||||
return True
|
||||
|
||||
async def async_added_to_hass(self):
|
||||
"""Connect to dispatcher listening for entity data notifications."""
|
||||
self._coordinator.async_add_listener(self.async_write_ha_state)
|
||||
|
||||
@property
|
||||
def _state(self):
|
||||
"""Lights internal state."""
|
||||
return self._device # self._hub.state(self._device)
|
||||
|
||||
@property
|
||||
def supported_features(self):
|
||||
"""Flag supported features."""
|
||||
support_flags = 0
|
||||
if self._device.support_brightness:
|
||||
support_flags |= SUPPORT_BRIGHTNESS
|
||||
if self._device.support_color:
|
||||
support_flags |= SUPPORT_COLOR
|
||||
if self._device.support_color_tem:
|
||||
support_flags |= SUPPORT_COLOR_TEMP
|
||||
return support_flags
|
||||
|
||||
async def async_turn_on(self, **kwargs):
|
||||
"""Turn device on."""
|
||||
_LOGGER.debug(
|
||||
"async_turn_on for Govee light %s, kwargs: %s", self._device.device, kwargs
|
||||
)
|
||||
err = None
|
||||
|
||||
just_turn_on = True
|
||||
if ATTR_HS_COLOR in kwargs:
|
||||
hs_color = kwargs.pop(ATTR_HS_COLOR)
|
||||
just_turn_on = False
|
||||
col = color.color_hs_to_RGB(hs_color[0], hs_color[1])
|
||||
_, err = await self._hub.set_color(self._device, col)
|
||||
if ATTR_BRIGHTNESS in kwargs:
|
||||
brightness = kwargs.pop(ATTR_BRIGHTNESS)
|
||||
just_turn_on = False
|
||||
bright_set = brightness - 1
|
||||
_, err = await self._hub.set_brightness(self._device, bright_set)
|
||||
if ATTR_COLOR_TEMP in kwargs:
|
||||
color_temp = kwargs.pop(ATTR_COLOR_TEMP)
|
||||
just_turn_on = False
|
||||
color_temp_kelvin = color.color_temperature_mired_to_kelvin(color_temp)
|
||||
if color_temp_kelvin > COLOR_TEMP_KELVIN_MAX:
|
||||
color_temp_kelvin = COLOR_TEMP_KELVIN_MAX
|
||||
elif color_temp_kelvin < COLOR_TEMP_KELVIN_MIN:
|
||||
color_temp_kelvin = COLOR_TEMP_KELVIN_MIN
|
||||
_, err = await self._hub.set_color_temp(self._device, color_temp_kelvin)
|
||||
|
||||
# if there is no known specific command - turn on
|
||||
if just_turn_on:
|
||||
_, err = await self._hub.turn_on(self._device)
|
||||
# debug log unknown commands
|
||||
if kwargs:
|
||||
_LOGGER.debug(
|
||||
"async_turn_on doesnt know how to handle kwargs: %s", repr(kwargs)
|
||||
)
|
||||
# warn on any error
|
||||
if err:
|
||||
_LOGGER.warning(
|
||||
"async_turn_on failed with '%s' for %s, kwargs: %s",
|
||||
err,
|
||||
self._device.device,
|
||||
kwargs,
|
||||
)
|
||||
|
||||
async def async_turn_off(self, **kwargs):
|
||||
"""Turn device off."""
|
||||
_LOGGER.debug("async_turn_off for Govee light %s", self._device.device)
|
||||
await self._hub.turn_off(self._device)
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return the unique ID."""
|
||||
return f"govee_{self._title}_{self._device.device}"
|
||||
|
||||
@property
|
||||
def device_id(self):
|
||||
"""Return the ID."""
|
||||
return self.unique_id
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name."""
|
||||
return self._device.device_name
|
||||
|
||||
@property
|
||||
def device_info(self):
|
||||
"""Return the device info."""
|
||||
return {
|
||||
"identifiers": {(DOMAIN, self.device_id)},
|
||||
"name": self.name,
|
||||
"manufacturer": "Govee",
|
||||
"model": self._device.model,
|
||||
"via_device": (DOMAIN, "Govee API (cloud)"),
|
||||
}
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return true if device is on."""
|
||||
return self._device.power_state
|
||||
|
||||
@property
|
||||
def assumed_state(self):
|
||||
"""
|
||||
Return true if the state is assumed.
|
||||
|
||||
This can be disabled in options.
|
||||
"""
|
||||
return (
|
||||
self._coordinator.use_assumed_state
|
||||
and self._device.source == GoveeSource.HISTORY
|
||||
)
|
||||
|
||||
@property
|
||||
def available(self):
|
||||
"""Return if light is available."""
|
||||
return self._device.online
|
||||
|
||||
@property
|
||||
def hs_color(self):
|
||||
"""Return the hs color value."""
|
||||
return color.color_RGB_to_hs(
|
||||
self._device.color[0],
|
||||
self._device.color[1],
|
||||
self._device.color[2],
|
||||
)
|
||||
|
||||
@property
|
||||
def rgb_color(self):
|
||||
"""Return the rgb color value."""
|
||||
return [
|
||||
self._device.color[0],
|
||||
self._device.color[1],
|
||||
self._device.color[2],
|
||||
]
|
||||
|
||||
@property
|
||||
def brightness(self):
|
||||
"""Return the brightness value."""
|
||||
# govee is reporting 0 to 254 - home assistant uses 1 to 255
|
||||
return self._device.brightness + 1
|
||||
|
||||
@property
|
||||
def color_temp(self):
|
||||
"""Return the color_temp of the light."""
|
||||
if not self._device.color_temp:
|
||||
return None
|
||||
return color.color_temperature_kelvin_to_mired(self._device.color_temp)
|
||||
|
||||
@property
|
||||
def min_mireds(self):
|
||||
"""Return the coldest color_temp that this light supports."""
|
||||
return color.color_temperature_kelvin_to_mired(COLOR_TEMP_KELVIN_MAX)
|
||||
|
||||
@property
|
||||
def max_mireds(self):
|
||||
"""Return the warmest color_temp that this light supports."""
|
||||
return color.color_temperature_kelvin_to_mired(COLOR_TEMP_KELVIN_MIN)
|
||||
|
||||
@property
|
||||
def extra_state_attributes(self):
|
||||
"""Return the device state attributes."""
|
||||
return {
|
||||
# rate limiting information on Govee API
|
||||
"rate_limit_total": self._hub.rate_limit_total,
|
||||
"rate_limit_remaining": self._hub.rate_limit_remaining,
|
||||
"rate_limit_reset_seconds": round(self._hub.rate_limit_reset_seconds, 2),
|
||||
"rate_limit_reset": datetime.fromtimestamp(
|
||||
self._hub.rate_limit_reset
|
||||
).isoformat(),
|
||||
"rate_limit_on": self._hub.rate_limit_on,
|
||||
# general information
|
||||
"manufacturer": "Govee",
|
||||
"model": self._device.model,
|
||||
}
|
||||
15
custom_components/govee/manifest.json
Normal file
15
custom_components/govee/manifest.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"domain": "govee",
|
||||
"name": "Govee",
|
||||
"codeowners": ["@LaggAt"],
|
||||
"config_flow": true,
|
||||
"dependencies": [],
|
||||
"documentation": "https://github.com/LaggAt/hacs-govee/blob/master/README.md",
|
||||
"homekit": {},
|
||||
"iot_class": "cloud_polling",
|
||||
"issue_tracker": "https://github.com/LaggAt/hacs-govee/issues",
|
||||
"requirements": ["govee-api-laggat==0.2.2", "dacite==1.8.0"],
|
||||
"ssdp": [],
|
||||
"version": "2023.11.1",
|
||||
"zeroconf": []
|
||||
}
|
||||
42
custom_components/govee/strings.json
Normal file
42
custom_components/govee/strings.json
Normal file
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"title": "Govee",
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "Already configured. Only a single configuration possible."
|
||||
},
|
||||
"error": {
|
||||
"cannot_connect": "Cannot connect. Is the API-Key correct and the internet connection working?",
|
||||
"unknown": "Unknown Error."
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"api_key": "API Key",
|
||||
"delay": "Poll Interval"
|
||||
},
|
||||
"title": "",
|
||||
"description": "Get your API Key from the Govee Home App. For Details see https://github.com/LaggAt/hacs-govee/blob/master/README.md"
|
||||
}
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"error": {
|
||||
"cannot_connect": "Cannot connect. Is the API-Key correct and the internet connection working?",
|
||||
"unknown": "Unknown Error.",
|
||||
"disabled_attribute_updates_wrong": "Wrong format, see README above."
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"api_key": "API Key (requires restart)",
|
||||
"delay": "Poll Interval (requires restart)",
|
||||
"use_assumed_state": "Use 'assumed state' (two buttons). Default: True",
|
||||
"offline_is_off": "When a led is offline, show it as off (default doesn't change state). Default: False",
|
||||
"disable_attribute_updates": "DISABLE state updates. Space to disable. Read the README above!"
|
||||
},
|
||||
"title": "Options",
|
||||
"description": "Configure the Govee integration. For Details see https://github.com/LaggAt/hacs-govee/blob/master/README.md"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
41
custom_components/govee/translations/de.json
Normal file
41
custom_components/govee/translations/de.json
Normal file
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"title": "Govee",
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "Bereits eingerichtet. Es ist nur eine Konfiguration möglich."
|
||||
},
|
||||
"error": {
|
||||
"cannot_connect": "Keine Verbindung möglich. Ist der API-Key richtig und die Internet Verbindung in Ordnung?",
|
||||
"unknown": "Unbekannter Fehler."
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"api_key": "API Key",
|
||||
"delay": "Abfrage-Intervall"
|
||||
},
|
||||
"description": "Den API Key bekommen Sie in der Govee Home App. Details dazu hier: https://github.com/LaggAt/hacs-govee/blob/master/README.md"
|
||||
}
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"error": {
|
||||
"cannot_connect": "Keine Verbindung möglich. Ist der API-Key richtig und die Internet Verbindung in Ordnung?",
|
||||
"unknown": "Unbekannter Fehler.",
|
||||
"disabled_attribute_updates_wrong": "Format ist inkorrekt, bitte README lesen."
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"api_key": "API Key (benötigt Neustart)",
|
||||
"delay": "Abfrage-Intervall (benötigt Neustart)",
|
||||
"use_assumed_state": "Verwende 'angenommenen Zustand' (zwei Buttons). Standard: True",
|
||||
"offline_is_off": "Wenn eine LED offline ist, zeige sie als Aus (Standard ändert den Status nicht). Standard: False",
|
||||
"disable_attribute_updates": "Status updates verhindern. Leertaste zum ausschalten. Bitte das README oben dazu lesen."
|
||||
},
|
||||
"title": "Einstellungen",
|
||||
"description": "Einstellen der Govee Integration. Details dazu hier: https://github.com/LaggAt/hacs-govee/blob/master/README.md"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
41
custom_components/govee/translations/en.json
Normal file
41
custom_components/govee/translations/en.json
Normal file
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"title": "Govee",
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "Already configured. Only a single configuration possible."
|
||||
},
|
||||
"error": {
|
||||
"cannot_connect": "Cannot connect. Is the API-Key correct and the internet connection working?",
|
||||
"unknown": "Unknown Error."
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"api_key": "API Key",
|
||||
"delay": "Poll Interval"
|
||||
},
|
||||
"description": "Get your API Key from the Govee Home App. For Details see https://github.com/LaggAt/hacs-govee/blob/master/README.md"
|
||||
}
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"error": {
|
||||
"cannot_connect": "Cannot connect. Is the API-Key correct and the internet connection working?",
|
||||
"unknown": "Unknown Error.",
|
||||
"disabled_attribute_updates_wrong": "Wrong format, see README above."
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"api_key": "API Key (requires restart)",
|
||||
"delay": "Poll Interval (requires restart)",
|
||||
"use_assumed_state": "Use 'assumed state' (two buttons). Default: True",
|
||||
"offline_is_off": "When a led is offline, show it as off (default doesn't change state). Default: False",
|
||||
"disable_attribute_updates": "DISABLE state updates. Space to disable. Read the README above!"
|
||||
},
|
||||
"title": "Options",
|
||||
"description": "Configure the Govee integration. For Details see https://github.com/LaggAt/hacs-govee/blob/master/README.md"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
41
custom_components/govee/translations/fr.json
Normal file
41
custom_components/govee/translations/fr.json
Normal file
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"title": "Govee",
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "Déjà configuré. Une seule configuration possible."
|
||||
},
|
||||
"error": {
|
||||
"cannot_connect": "Impossible de se connecter. La clé d'API est-elle correcte et la connexion Internet fonctionne-t-elle?",
|
||||
"unknown": "Erreure inconnue."
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"api_key": "clé d'API",
|
||||
"delay": "Intervalle d'interrogation"
|
||||
},
|
||||
"description": "Obtenez votre clé API à partir de l'application Govee Home. Pour plus de détails, visitez https://github.com/LaggAt/hacs-govee/blob/master/README.md"
|
||||
}
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"error": {
|
||||
"cannot_connect": "Impossible de se connecter. La clé d'API est-elle correcte et la connexion Internet fonctionne-t-elle?",
|
||||
"unknown": "Erreure inconnue.",
|
||||
"disabled_attribute_updates_wrong": "Format incorrect, voir le 'lisez-moi' ci-dessus."
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"api_key": "Clé d'API (nécessite un redémarrage)",
|
||||
"delay": "Intervalle d'interrogation (nécessite un redémarrage)",
|
||||
"use_assumed_state": "Utiliser 'état supposé' (deux boutons). Par défaut : Vrai",
|
||||
"offline_is_off": "Lorsqu'une DEL est hors ligne, affichez-la comme éteinte (la valeur par défaut ne change pas d'état). Par défaut : Faux",
|
||||
"disable_attribute_updates": "DÉSACTIVER les mises à jour d'état. Espace pour désactiver. Lisez le 'lisez-moi' ci-dessus !"
|
||||
},
|
||||
"title": "Options",
|
||||
"description": "Configurez l'intégration Govee. Pour plus de détails, visitez https://github.com/LaggAt/hacs-govee/blob/master/README.md"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
41
custom_components/govee/translations/pt-BR.json
Normal file
41
custom_components/govee/translations/pt-BR.json
Normal file
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"title": "Govee",
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "Já configurado. Apenas uma única configuração é possível."
|
||||
},
|
||||
"error": {
|
||||
"cannot_connect": "Não pode conectar. A API-Key está correta e a conexão com a Internet está funcionando?",
|
||||
"unknown": "Erro desconhecido."
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"api_key": "Chave de API",
|
||||
"delay": "Intervalo de escaneamento"
|
||||
},
|
||||
"description": "Obtenha sua chave de API do aplicativo Govee Home. Para detalhes consulte https://github.com/LaggAt/hacs-govee/blob/master/README.md"
|
||||
}
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"error": {
|
||||
"cannot_connect": "Não pode conectar. A API-Key está correta e a conexão com a Internet está funcionando?",
|
||||
"unknown": "Erro desconhecido.",
|
||||
"disabled_attribute_updates_wrong": "Formato errado, veja README acima."
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"api_key": "Chave de API (requer reinicialização)",
|
||||
"delay": "Intervalo de escaneamento (requer reinicialização)",
|
||||
"use_assumed_state": "Use 'estado presumido' (dois botões). Padrão: true",
|
||||
"offline_is_off": "Quando um led estiver offline, mostre-o como desligado (o padrão não muda de estado). Padrão: False",
|
||||
"disable_attribute_updates": "DESATIVAR atualizações de estado. Espaço para desativar. Leia o README acima!"
|
||||
},
|
||||
"title": "Opções",
|
||||
"description": "Configure a integração do Govee. Para detalhes consulte https://github.com/LaggAt/hacs-govee/blob/master/README.md"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user