mirror of
https://github.com/home-assistant/core.git
synced 2025-07-29 16:17:20 +00:00
Merge pull request #50243 from home-assistant/rc
This commit is contained in:
commit
808825dc42
@ -51,7 +51,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
sensors = [
|
||||
BroadlinkSensor(device, monitored_condition)
|
||||
for monitored_condition in sensor_data
|
||||
if sensor_data[monitored_condition] or device.api.type == "A1"
|
||||
if sensor_data[monitored_condition] != 0 or device.api.type == "A1"
|
||||
]
|
||||
async_add_entities(sensors)
|
||||
|
||||
|
@ -117,11 +117,25 @@ class BroadlinkRMUpdateManager(BroadlinkUpdateManager):
|
||||
device = self.device
|
||||
|
||||
if hasattr(device.api, "check_sensors"):
|
||||
return await device.async_request(device.api.check_sensors)
|
||||
data = await device.async_request(device.api.check_sensors)
|
||||
return self.normalize(data, self.coordinator.data)
|
||||
|
||||
await device.async_request(device.api.update)
|
||||
return {}
|
||||
|
||||
@staticmethod
|
||||
def normalize(data, previous_data):
|
||||
"""Fix firmware issue.
|
||||
|
||||
See https://github.com/home-assistant/core/issues/42100.
|
||||
"""
|
||||
if data["temperature"] == -7:
|
||||
if previous_data is None or previous_data["temperature"] is None:
|
||||
data["temperature"] = None
|
||||
elif abs(previous_data["temperature"] - data["temperature"]) > 3:
|
||||
data["temperature"] = previous_data["temperature"]
|
||||
return data
|
||||
|
||||
|
||||
class BroadlinkSP1UpdateManager(BroadlinkUpdateManager):
|
||||
"""Manages updates for Broadlink SP1 devices."""
|
||||
|
@ -6,6 +6,7 @@ from pyclimacell.const import (
|
||||
HealthConcernType,
|
||||
PollenIndex,
|
||||
PrimaryPollutantType,
|
||||
V3PollenIndex,
|
||||
WeatherCode,
|
||||
)
|
||||
|
||||
@ -307,8 +308,20 @@ CC_V3_SENSOR_TYPES = [
|
||||
ATTR_FIELD: CC_V3_ATTR_CHINA_HEALTH_CONCERN,
|
||||
ATTR_NAME: "China MEP Health Concern",
|
||||
},
|
||||
{ATTR_FIELD: CC_V3_ATTR_POLLEN_TREE, ATTR_NAME: "Tree Pollen Index"},
|
||||
{ATTR_FIELD: CC_V3_ATTR_POLLEN_WEED, ATTR_NAME: "Weed Pollen Index"},
|
||||
{ATTR_FIELD: CC_V3_ATTR_POLLEN_GRASS, ATTR_NAME: "Grass Pollen Index"},
|
||||
{
|
||||
ATTR_FIELD: CC_V3_ATTR_POLLEN_TREE,
|
||||
ATTR_NAME: "Tree Pollen Index",
|
||||
ATTR_VALUE_MAP: V3PollenIndex,
|
||||
},
|
||||
{
|
||||
ATTR_FIELD: CC_V3_ATTR_POLLEN_WEED,
|
||||
ATTR_NAME: "Weed Pollen Index",
|
||||
ATTR_VALUE_MAP: V3PollenIndex,
|
||||
},
|
||||
{
|
||||
ATTR_FIELD: CC_V3_ATTR_POLLEN_GRASS,
|
||||
ATTR_NAME: "Grass Pollen Index",
|
||||
ATTR_VALUE_MAP: V3PollenIndex,
|
||||
},
|
||||
{ATTR_FIELD: CC_V3_ATTR_FIRE_INDEX, ATTR_NAME: "Fire Index"},
|
||||
]
|
||||
|
@ -3,7 +3,7 @@
|
||||
"name": "ClimaCell",
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/climacell",
|
||||
"requirements": ["pyclimacell==0.18.0"],
|
||||
"requirements": ["pyclimacell==0.18.2"],
|
||||
"codeowners": ["@raman325"],
|
||||
"iot_class": "cloud_polling"
|
||||
}
|
||||
|
@ -128,7 +128,7 @@ class BaseClimaCellSensorEntity(ClimaCellEntity, SensorEntity):
|
||||
):
|
||||
return round(self._state * self.sensor_type[ATTR_METRIC_CONVERSION], 4)
|
||||
|
||||
if ATTR_VALUE_MAP in self.sensor_type:
|
||||
if ATTR_VALUE_MAP in self.sensor_type and self._state is not None:
|
||||
return self.sensor_type[ATTR_VALUE_MAP](self._state).name.lower()
|
||||
return self._state
|
||||
|
||||
|
@ -1,4 +1,6 @@
|
||||
"""Demo light platform that implements lights."""
|
||||
from __future__ import annotations
|
||||
|
||||
import random
|
||||
|
||||
from homeassistant.components.light import (
|
||||
@ -6,12 +8,13 @@ from homeassistant.components.light import (
|
||||
ATTR_COLOR_TEMP,
|
||||
ATTR_EFFECT,
|
||||
ATTR_HS_COLOR,
|
||||
ATTR_WHITE_VALUE,
|
||||
SUPPORT_BRIGHTNESS,
|
||||
SUPPORT_COLOR,
|
||||
SUPPORT_COLOR_TEMP,
|
||||
ATTR_RGBW_COLOR,
|
||||
ATTR_RGBWW_COLOR,
|
||||
COLOR_MODE_COLOR_TEMP,
|
||||
COLOR_MODE_HS,
|
||||
COLOR_MODE_RGBW,
|
||||
COLOR_MODE_RGBWW,
|
||||
SUPPORT_EFFECT,
|
||||
SUPPORT_WHITE_VALUE,
|
||||
LightEntity,
|
||||
)
|
||||
|
||||
@ -23,9 +26,7 @@ LIGHT_EFFECT_LIST = ["rainbow", "none"]
|
||||
|
||||
LIGHT_TEMPS = [240, 380]
|
||||
|
||||
SUPPORT_DEMO = (
|
||||
SUPPORT_BRIGHTNESS | SUPPORT_COLOR_TEMP | SUPPORT_COLOR | SUPPORT_WHITE_VALUE
|
||||
)
|
||||
SUPPORT_DEMO = {COLOR_MODE_HS, COLOR_MODE_COLOR_TEMP}
|
||||
|
||||
|
||||
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
|
||||
@ -33,27 +34,43 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
|
||||
async_add_entities(
|
||||
[
|
||||
DemoLight(
|
||||
unique_id="light_1",
|
||||
name="Bed Light",
|
||||
state=False,
|
||||
available=True,
|
||||
effect_list=LIGHT_EFFECT_LIST,
|
||||
effect=LIGHT_EFFECT_LIST[0],
|
||||
name="Bed Light",
|
||||
state=False,
|
||||
unique_id="light_1",
|
||||
),
|
||||
DemoLight(
|
||||
unique_id="light_2",
|
||||
name="Ceiling Lights",
|
||||
state=True,
|
||||
available=True,
|
||||
ct=LIGHT_TEMPS[1],
|
||||
name="Ceiling Lights",
|
||||
state=True,
|
||||
unique_id="light_2",
|
||||
),
|
||||
DemoLight(
|
||||
unique_id="light_3",
|
||||
name="Kitchen Lights",
|
||||
state=True,
|
||||
available=True,
|
||||
hs_color=LIGHT_COLORS[1],
|
||||
ct=LIGHT_TEMPS[0],
|
||||
name="Kitchen Lights",
|
||||
state=True,
|
||||
unique_id="light_3",
|
||||
),
|
||||
DemoLight(
|
||||
available=True,
|
||||
ct=LIGHT_TEMPS[1],
|
||||
name="Office RGBW Lights",
|
||||
rgbw_color=(255, 0, 0, 255),
|
||||
state=True,
|
||||
supported_color_modes={COLOR_MODE_RGBW},
|
||||
unique_id="light_4",
|
||||
),
|
||||
DemoLight(
|
||||
available=True,
|
||||
name="Living Room RGBWW Lights",
|
||||
rgbww_color=(255, 0, 0, 255, 0),
|
||||
state=True,
|
||||
supported_color_modes={COLOR_MODE_RGBWW},
|
||||
unique_id="light_5",
|
||||
),
|
||||
]
|
||||
)
|
||||
@ -73,26 +90,39 @@ class DemoLight(LightEntity):
|
||||
name,
|
||||
state,
|
||||
available=False,
|
||||
hs_color=None,
|
||||
ct=None,
|
||||
brightness=180,
|
||||
white=200,
|
||||
ct=None,
|
||||
effect_list=None,
|
||||
effect=None,
|
||||
hs_color=None,
|
||||
rgbw_color=None,
|
||||
rgbww_color=None,
|
||||
supported_color_modes=None,
|
||||
):
|
||||
"""Initialize the light."""
|
||||
self._unique_id = unique_id
|
||||
self._name = name
|
||||
self._state = state
|
||||
self._hs_color = hs_color
|
||||
self._ct = ct or random.choice(LIGHT_TEMPS)
|
||||
self._brightness = brightness
|
||||
self._white = white
|
||||
self._features = SUPPORT_DEMO
|
||||
self._effect_list = effect_list
|
||||
self._effect = effect
|
||||
self._available = True
|
||||
self._color_mode = "ct" if ct is not None and hs_color is None else "hs"
|
||||
self._brightness = brightness
|
||||
self._ct = ct or random.choice(LIGHT_TEMPS)
|
||||
self._effect = effect
|
||||
self._effect_list = effect_list
|
||||
self._features = 0
|
||||
self._hs_color = hs_color
|
||||
self._name = name
|
||||
self._rgbw_color = rgbw_color
|
||||
self._rgbww_color = rgbww_color
|
||||
self._state = state
|
||||
self._unique_id = unique_id
|
||||
if hs_color:
|
||||
self._color_mode = COLOR_MODE_HS
|
||||
elif rgbw_color:
|
||||
self._color_mode = COLOR_MODE_RGBW
|
||||
elif rgbww_color:
|
||||
self._color_mode = COLOR_MODE_RGBWW
|
||||
else:
|
||||
self._color_mode = COLOR_MODE_COLOR_TEMP
|
||||
if not supported_color_modes:
|
||||
supported_color_modes = SUPPORT_DEMO
|
||||
self._color_modes = supported_color_modes
|
||||
if self._effect_list is not None:
|
||||
self._features |= SUPPORT_EFFECT
|
||||
|
||||
@ -134,24 +164,30 @@ class DemoLight(LightEntity):
|
||||
"""Return the brightness of this light between 0..255."""
|
||||
return self._brightness
|
||||
|
||||
@property
|
||||
def color_mode(self) -> str | None:
|
||||
"""Return the color mode of the light."""
|
||||
return self._color_mode
|
||||
|
||||
@property
|
||||
def hs_color(self) -> tuple:
|
||||
"""Return the hs color value."""
|
||||
if self._color_mode == "hs":
|
||||
return self._hs_color
|
||||
return None
|
||||
return self._hs_color
|
||||
|
||||
@property
|
||||
def rgbw_color(self) -> tuple:
|
||||
"""Return the rgbw color value."""
|
||||
return self._rgbw_color
|
||||
|
||||
@property
|
||||
def rgbww_color(self) -> tuple:
|
||||
"""Return the rgbww color value."""
|
||||
return self._rgbww_color
|
||||
|
||||
@property
|
||||
def color_temp(self) -> int:
|
||||
"""Return the CT color temperature."""
|
||||
if self._color_mode == "ct":
|
||||
return self._ct
|
||||
return None
|
||||
|
||||
@property
|
||||
def white_value(self) -> int:
|
||||
"""Return the white value of this light between 0..255."""
|
||||
return self._white
|
||||
return self._ct
|
||||
|
||||
@property
|
||||
def effect_list(self) -> list:
|
||||
@ -173,24 +209,34 @@ class DemoLight(LightEntity):
|
||||
"""Flag supported features."""
|
||||
return self._features
|
||||
|
||||
@property
|
||||
def supported_color_modes(self) -> set | None:
|
||||
"""Flag supported color modes."""
|
||||
return self._color_modes
|
||||
|
||||
async def async_turn_on(self, **kwargs) -> None:
|
||||
"""Turn the light on."""
|
||||
self._state = True
|
||||
|
||||
if ATTR_RGBW_COLOR in kwargs:
|
||||
self._color_mode = COLOR_MODE_RGBW
|
||||
self._rgbw_color = kwargs[ATTR_RGBW_COLOR]
|
||||
|
||||
if ATTR_RGBWW_COLOR in kwargs:
|
||||
self._color_mode = COLOR_MODE_RGBWW
|
||||
self._rgbww_color = kwargs[ATTR_RGBWW_COLOR]
|
||||
|
||||
if ATTR_HS_COLOR in kwargs:
|
||||
self._color_mode = "hs"
|
||||
self._color_mode = COLOR_MODE_HS
|
||||
self._hs_color = kwargs[ATTR_HS_COLOR]
|
||||
|
||||
if ATTR_COLOR_TEMP in kwargs:
|
||||
self._color_mode = "ct"
|
||||
self._color_mode = COLOR_MODE_COLOR_TEMP
|
||||
self._ct = kwargs[ATTR_COLOR_TEMP]
|
||||
|
||||
if ATTR_BRIGHTNESS in kwargs:
|
||||
self._brightness = kwargs[ATTR_BRIGHTNESS]
|
||||
|
||||
if ATTR_WHITE_VALUE in kwargs:
|
||||
self._white = kwargs[ATTR_WHITE_VALUE]
|
||||
|
||||
if ATTR_EFFECT in kwargs:
|
||||
self._effect = kwargs[ATTR_EFFECT]
|
||||
|
||||
|
@ -42,7 +42,6 @@ async def async_setup_entry(
|
||||
entry.options.get(CONF_ZONE2, DEFAULT_ZONE2),
|
||||
entry.options.get(CONF_ZONE3, DEFAULT_ZONE3),
|
||||
lambda: get_async_client(hass),
|
||||
entry.state,
|
||||
)
|
||||
try:
|
||||
await connect_denonavr.async_connect_receiver()
|
||||
|
@ -3,7 +3,7 @@
|
||||
"name": "Denon AVR Network Receivers",
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/denonavr",
|
||||
"requirements": ["denonavr==0.10.5"],
|
||||
"requirements": ["denonavr==0.10.6"],
|
||||
"codeowners": ["@scarface-4711", "@starkillerOG"],
|
||||
"ssdp": [
|
||||
{
|
||||
|
@ -246,7 +246,6 @@ class DenonDevice(MediaPlayerEntity):
|
||||
"manufacturer": self._config_entry.data[CONF_MANUFACTURER],
|
||||
"name": self._config_entry.title,
|
||||
"model": f"{self._config_entry.data[CONF_MODEL]}-{self._config_entry.data[CONF_TYPE]}",
|
||||
"serial_number": self._config_entry.data[CONF_SERIAL_NUMBER],
|
||||
}
|
||||
|
||||
return device_info
|
||||
|
@ -20,7 +20,6 @@ class ConnectDenonAVR:
|
||||
zone2: bool,
|
||||
zone3: bool,
|
||||
async_client_getter: Callable,
|
||||
entry_state: str | None = None,
|
||||
):
|
||||
"""Initialize the class."""
|
||||
self._async_client_getter = async_client_getter
|
||||
@ -28,7 +27,6 @@ class ConnectDenonAVR:
|
||||
self._host = host
|
||||
self._show_all_inputs = show_all_inputs
|
||||
self._timeout = timeout
|
||||
self._entry_state = entry_state
|
||||
|
||||
self._zones = {}
|
||||
if zone2:
|
||||
|
@ -12,6 +12,7 @@ import voluptuous as vol
|
||||
from homeassistant.components import light
|
||||
from homeassistant.components.light import (
|
||||
ATTR_BRIGHTNESS,
|
||||
ATTR_COLOR_MODE,
|
||||
ATTR_COLOR_TEMP,
|
||||
ATTR_EFFECT,
|
||||
ATTR_EFFECT_LIST,
|
||||
@ -19,16 +20,20 @@ from homeassistant.components.light import (
|
||||
ATTR_HS_COLOR,
|
||||
ATTR_MAX_MIREDS,
|
||||
ATTR_MIN_MIREDS,
|
||||
ATTR_RGB_COLOR,
|
||||
ATTR_RGBW_COLOR,
|
||||
ATTR_RGBWW_COLOR,
|
||||
ATTR_SUPPORTED_COLOR_MODES,
|
||||
ATTR_TRANSITION,
|
||||
ATTR_WHITE_VALUE,
|
||||
ATTR_XY_COLOR,
|
||||
PLATFORM_SCHEMA,
|
||||
SUPPORT_BRIGHTNESS,
|
||||
SUPPORT_COLOR,
|
||||
SUPPORT_COLOR_TEMP,
|
||||
SUPPORT_EFFECT,
|
||||
SUPPORT_FLASH,
|
||||
SUPPORT_TRANSITION,
|
||||
SUPPORT_WHITE_VALUE,
|
||||
color_supported,
|
||||
color_temp_supported,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID,
|
||||
@ -59,13 +64,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
||||
)
|
||||
|
||||
SUPPORT_GROUP_LIGHT = (
|
||||
SUPPORT_BRIGHTNESS
|
||||
| SUPPORT_COLOR_TEMP
|
||||
| SUPPORT_EFFECT
|
||||
| SUPPORT_FLASH
|
||||
| SUPPORT_COLOR
|
||||
| SUPPORT_TRANSITION
|
||||
| SUPPORT_WHITE_VALUE
|
||||
SUPPORT_EFFECT | SUPPORT_FLASH | SUPPORT_TRANSITION | SUPPORT_WHITE_VALUE
|
||||
)
|
||||
|
||||
|
||||
@ -89,13 +88,19 @@ class LightGroup(GroupEntity, light.LightEntity):
|
||||
self._available = False
|
||||
self._icon = "mdi:lightbulb-group"
|
||||
self._brightness: int | None = None
|
||||
self._color_mode: str | None = None
|
||||
self._hs_color: tuple[float, float] | None = None
|
||||
self._rgb_color: tuple[int, int, int] | None = None
|
||||
self._rgbw_color: tuple[int, int, int, int] | None = None
|
||||
self._rgbww_color: tuple[int, int, int, int, int] | None = None
|
||||
self._xy_color: tuple[float, float] | None = None
|
||||
self._color_temp: int | None = None
|
||||
self._min_mireds: int = 154
|
||||
self._max_mireds: int = 500
|
||||
self._white_value: int | None = None
|
||||
self._effect_list: list[str] | None = None
|
||||
self._effect: str | None = None
|
||||
self._supported_color_modes: set[str] | None = None
|
||||
self._supported_features: int = 0
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
@ -143,11 +148,36 @@ class LightGroup(GroupEntity, light.LightEntity):
|
||||
"""Return the brightness of this light group between 0..255."""
|
||||
return self._brightness
|
||||
|
||||
@property
|
||||
def color_mode(self) -> str | None:
|
||||
"""Return the color mode of the light."""
|
||||
return self._color_mode
|
||||
|
||||
@property
|
||||
def hs_color(self) -> tuple[float, float] | None:
|
||||
"""Return the HS color value [float, float]."""
|
||||
return self._hs_color
|
||||
|
||||
@property
|
||||
def rgb_color(self) -> tuple[int, int, int] | None:
|
||||
"""Return the rgb color value [int, int, int]."""
|
||||
return self._rgb_color
|
||||
|
||||
@property
|
||||
def rgbw_color(self) -> tuple[int, int, int, int] | None:
|
||||
"""Return the rgbw color value [int, int, int, int]."""
|
||||
return self._rgbw_color
|
||||
|
||||
@property
|
||||
def rgbww_color(self) -> tuple[int, int, int, int, int] | None:
|
||||
"""Return the rgbww color value [int, int, int, int, int]."""
|
||||
return self._rgbww_color
|
||||
|
||||
@property
|
||||
def xy_color(self) -> tuple[float, float] | None:
|
||||
"""Return the xy color value [float, float]."""
|
||||
return self._xy_color
|
||||
|
||||
@property
|
||||
def color_temp(self) -> int | None:
|
||||
"""Return the CT color value in mireds."""
|
||||
@ -178,6 +208,11 @@ class LightGroup(GroupEntity, light.LightEntity):
|
||||
"""Return the current effect."""
|
||||
return self._effect
|
||||
|
||||
@property
|
||||
def supported_color_modes(self) -> set | None:
|
||||
"""Flag supported color modes."""
|
||||
return self._supported_color_modes
|
||||
|
||||
@property
|
||||
def supported_features(self) -> int:
|
||||
"""Flag supported features."""
|
||||
@ -204,6 +239,18 @@ class LightGroup(GroupEntity, light.LightEntity):
|
||||
if ATTR_HS_COLOR in kwargs:
|
||||
data[ATTR_HS_COLOR] = kwargs[ATTR_HS_COLOR]
|
||||
|
||||
if ATTR_RGB_COLOR in kwargs:
|
||||
data[ATTR_RGB_COLOR] = kwargs[ATTR_RGB_COLOR]
|
||||
|
||||
if ATTR_RGBW_COLOR in kwargs:
|
||||
data[ATTR_RGBW_COLOR] = kwargs[ATTR_RGBW_COLOR]
|
||||
|
||||
if ATTR_RGBWW_COLOR in kwargs:
|
||||
data[ATTR_RGBWW_COLOR] = kwargs[ATTR_RGBWW_COLOR]
|
||||
|
||||
if ATTR_XY_COLOR in kwargs:
|
||||
data[ATTR_XY_COLOR] = kwargs[ATTR_XY_COLOR]
|
||||
|
||||
if ATTR_COLOR_TEMP in kwargs:
|
||||
data[ATTR_COLOR_TEMP] = kwargs[ATTR_COLOR_TEMP]
|
||||
|
||||
@ -215,11 +262,9 @@ class LightGroup(GroupEntity, light.LightEntity):
|
||||
state = self.hass.states.get(entity_id)
|
||||
if not state:
|
||||
continue
|
||||
support = state.attributes.get(ATTR_SUPPORTED_FEATURES)
|
||||
support = state.attributes.get(ATTR_SUPPORTED_COLOR_MODES)
|
||||
# Only pass color temperature to supported entity_ids
|
||||
if bool(support & SUPPORT_COLOR) and not bool(
|
||||
support & SUPPORT_COLOR_TEMP
|
||||
):
|
||||
if color_supported(support) and not color_temp_supported(support):
|
||||
emulate_color_temp_entity_ids.append(entity_id)
|
||||
updated_entities.remove(entity_id)
|
||||
data[ATTR_ENTITY_ID] = updated_entities
|
||||
@ -300,6 +345,16 @@ class LightGroup(GroupEntity, light.LightEntity):
|
||||
self._brightness = _reduce_attribute(on_states, ATTR_BRIGHTNESS)
|
||||
|
||||
self._hs_color = _reduce_attribute(on_states, ATTR_HS_COLOR, reduce=_mean_tuple)
|
||||
self._rgb_color = _reduce_attribute(
|
||||
on_states, ATTR_RGB_COLOR, reduce=_mean_tuple
|
||||
)
|
||||
self._rgbw_color = _reduce_attribute(
|
||||
on_states, ATTR_RGBW_COLOR, reduce=_mean_tuple
|
||||
)
|
||||
self._rgbww_color = _reduce_attribute(
|
||||
on_states, ATTR_RGBWW_COLOR, reduce=_mean_tuple
|
||||
)
|
||||
self._xy_color = _reduce_attribute(on_states, ATTR_XY_COLOR, reduce=_mean_tuple)
|
||||
|
||||
self._white_value = _reduce_attribute(on_states, ATTR_WHITE_VALUE)
|
||||
|
||||
@ -324,6 +379,21 @@ class LightGroup(GroupEntity, light.LightEntity):
|
||||
effects_count = Counter(itertools.chain(all_effects))
|
||||
self._effect = effects_count.most_common(1)[0][0]
|
||||
|
||||
self._color_mode = None
|
||||
all_color_modes = list(_find_state_attributes(on_states, ATTR_COLOR_MODE))
|
||||
if all_color_modes:
|
||||
# Report the most common color mode.
|
||||
color_mode_count = Counter(itertools.chain(all_color_modes))
|
||||
self._color_mode = color_mode_count.most_common(1)[0][0]
|
||||
|
||||
self._supported_color_modes = None
|
||||
all_supported_color_modes = list(
|
||||
_find_state_attributes(states, ATTR_SUPPORTED_COLOR_MODES)
|
||||
)
|
||||
if all_supported_color_modes:
|
||||
# Merge all color modes.
|
||||
self._supported_color_modes = set().union(*all_supported_color_modes)
|
||||
|
||||
self._supported_features = 0
|
||||
for support in _find_state_attributes(states, ATTR_SUPPORTED_FEATURES):
|
||||
# Merge supported features by emulating support for every feature
|
||||
|
@ -34,7 +34,7 @@ set:
|
||||
object:
|
||||
add_entities:
|
||||
name: Add Entities
|
||||
description: List of members they will change on group listening.
|
||||
description: List of members that will change on group listening.
|
||||
example: domain.entity_id1, domain.entity_id2
|
||||
selector:
|
||||
object:
|
||||
@ -55,5 +55,4 @@ remove:
|
||||
required: true
|
||||
example: "test_group"
|
||||
selector:
|
||||
entity:
|
||||
domain: group
|
||||
object:
|
||||
|
@ -1,5 +1,4 @@
|
||||
"""Hue binary sensor entities."""
|
||||
|
||||
from aiohue.sensors import TYPE_ZLL_PRESENCE
|
||||
|
||||
from homeassistant.components.binary_sensor import (
|
||||
@ -15,9 +14,14 @@ PRESENCE_NAME_FORMAT = "{} motion"
|
||||
|
||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
"""Defer binary sensor setup to the shared sensor module."""
|
||||
await hass.data[HUE_DOMAIN][
|
||||
config_entry.entry_id
|
||||
].sensor_manager.async_register_component("binary_sensor", async_add_entities)
|
||||
bridge = hass.data[HUE_DOMAIN][config_entry.entry_id]
|
||||
|
||||
if not bridge.sensor_manager:
|
||||
return
|
||||
|
||||
await bridge.sensor_manager.async_register_component(
|
||||
"binary_sensor", async_add_entities
|
||||
)
|
||||
|
||||
|
||||
class HuePresence(GenericZLLSensor, BinarySensorEntity):
|
||||
|
@ -102,7 +102,8 @@ class HueBridge:
|
||||
return False
|
||||
|
||||
self.api = bridge
|
||||
self.sensor_manager = SensorManager(self)
|
||||
if bridge.sensors is not None:
|
||||
self.sensor_manager = SensorManager(self)
|
||||
|
||||
hass.config_entries.async_setup_platforms(self.config_entry, PLATFORMS)
|
||||
|
||||
@ -178,6 +179,10 @@ class HueBridge:
|
||||
|
||||
async def hue_activate_scene(self, data, skip_reload=False, hide_warnings=False):
|
||||
"""Service to call directly into bridge to set scenes."""
|
||||
if self.api.scenes is None:
|
||||
_LOGGER.warning("Hub %s does not support scenes", self.api.host)
|
||||
return
|
||||
|
||||
group_name = data[ATTR_GROUP_NAME]
|
||||
scene_name = data[ATTR_SCENE_NAME]
|
||||
transition = data.get(ATTR_TRANSITION)
|
||||
|
@ -3,7 +3,7 @@
|
||||
"name": "Philips Hue",
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/hue",
|
||||
"requirements": ["aiohue==2.1.0"],
|
||||
"requirements": ["aiohue==2.3.0"],
|
||||
"ssdp": [
|
||||
{
|
||||
"manufacturer": "Royal Philips Electronics",
|
||||
|
@ -26,9 +26,12 @@ TEMPERATURE_NAME_FORMAT = "{} temperature"
|
||||
|
||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
"""Defer sensor setup to the shared sensor module."""
|
||||
await hass.data[HUE_DOMAIN][
|
||||
config_entry.entry_id
|
||||
].sensor_manager.async_register_component("sensor", async_add_entities)
|
||||
bridge = hass.data[HUE_DOMAIN][config_entry.entry_id]
|
||||
|
||||
if not bridge.sensor_manager:
|
||||
return
|
||||
|
||||
await bridge.sensor_manager.async_register_component("sensor", async_add_entities)
|
||||
|
||||
|
||||
class GenericHueGaugeSensorEntity(GenericZLLSensor, SensorEntity):
|
||||
|
@ -343,7 +343,7 @@ async def async_setup(hass, config): # noqa: C901
|
||||
rgb_color = params.pop(ATTR_RGB_COLOR)
|
||||
if COLOR_MODE_RGBW in supported_color_modes:
|
||||
params[ATTR_RGBW_COLOR] = color_util.color_rgb_to_rgbw(*rgb_color)
|
||||
if COLOR_MODE_RGBWW in supported_color_modes:
|
||||
elif COLOR_MODE_RGBWW in supported_color_modes:
|
||||
params[ATTR_RGBWW_COLOR] = color_util.color_rgb_to_rgbww(
|
||||
*rgb_color, light.min_mireds, light.max_mireds
|
||||
)
|
||||
|
@ -344,6 +344,9 @@ class MqttFan(MqttEntity, FanEntity):
|
||||
def state_received(msg):
|
||||
"""Handle new received MQTT message."""
|
||||
payload = self._value_templates[CONF_STATE](msg.payload)
|
||||
if not payload:
|
||||
_LOGGER.debug("Ignoring empty state from '%s'", msg.topic)
|
||||
return
|
||||
if payload == self._payload["STATE_ON"]:
|
||||
self._state = True
|
||||
elif payload == self._payload["STATE_OFF"]:
|
||||
@ -362,22 +365,27 @@ class MqttFan(MqttEntity, FanEntity):
|
||||
def percentage_received(msg):
|
||||
"""Handle new received MQTT message for the percentage."""
|
||||
numeric_val_str = self._value_templates[ATTR_PERCENTAGE](msg.payload)
|
||||
if not numeric_val_str:
|
||||
_LOGGER.debug("Ignoring empty speed from '%s'", msg.topic)
|
||||
return
|
||||
try:
|
||||
percentage = ranged_value_to_percentage(
|
||||
self._speed_range, int(numeric_val_str)
|
||||
)
|
||||
except ValueError:
|
||||
_LOGGER.warning(
|
||||
"'%s' received on topic %s is not a valid speed within the speed range",
|
||||
"'%s' received on topic %s. '%s' is not a valid speed within the speed range",
|
||||
msg.payload,
|
||||
msg.topic,
|
||||
numeric_val_str,
|
||||
)
|
||||
return
|
||||
if percentage < 0 or percentage > 100:
|
||||
_LOGGER.warning(
|
||||
"'%s' received on topic %s is not a valid speed within the speed range",
|
||||
"'%s' received on topic %s. '%s' is not a valid speed within the speed range",
|
||||
msg.payload,
|
||||
msg.topic,
|
||||
numeric_val_str,
|
||||
)
|
||||
return
|
||||
self._percentage = percentage
|
||||
@ -396,11 +404,15 @@ class MqttFan(MqttEntity, FanEntity):
|
||||
def preset_mode_received(msg):
|
||||
"""Handle new received MQTT message for preset mode."""
|
||||
preset_mode = self._value_templates[ATTR_PRESET_MODE](msg.payload)
|
||||
if not preset_mode:
|
||||
_LOGGER.debug("Ignoring empty preset_mode from '%s'", msg.topic)
|
||||
return
|
||||
if preset_mode not in self.preset_modes:
|
||||
_LOGGER.warning(
|
||||
"'%s' received on topic %s is not a valid preset mode",
|
||||
"'%s' received on topic %s. '%s' is not a valid preset mode",
|
||||
msg.payload,
|
||||
msg.topic,
|
||||
preset_mode,
|
||||
)
|
||||
return
|
||||
|
||||
@ -436,9 +448,10 @@ class MqttFan(MqttEntity, FanEntity):
|
||||
self._speed = speed
|
||||
else:
|
||||
_LOGGER.warning(
|
||||
"'%s' received on topic %s is not a valid speed",
|
||||
"'%s' received on topic %s. '%s' is not a valid speed",
|
||||
msg.payload,
|
||||
msg.topic,
|
||||
speed,
|
||||
)
|
||||
return
|
||||
|
||||
@ -464,6 +477,9 @@ class MqttFan(MqttEntity, FanEntity):
|
||||
def oscillation_received(msg):
|
||||
"""Handle new received MQTT message for the oscillation."""
|
||||
payload = self._value_templates[ATTR_OSCILLATING](msg.payload)
|
||||
if not payload:
|
||||
_LOGGER.debug("Ignoring empty oscillation from '%s'", msg.topic)
|
||||
return
|
||||
if payload == self._payload["OSCILLATE_ON_PAYLOAD"]:
|
||||
self._oscillation = True
|
||||
elif payload == self._payload["OSCILLATE_OFF_PAYLOAD"]:
|
||||
|
@ -58,18 +58,23 @@ DEFAULT_TCP_PORT = 5003
|
||||
DEFAULT_VERSION = "1.4"
|
||||
|
||||
|
||||
def set_default_persistence_file(value: dict) -> dict:
|
||||
"""Set default persistence file."""
|
||||
for idx, gateway in enumerate(value):
|
||||
fil = gateway.get(CONF_PERSISTENCE_FILE)
|
||||
if fil is not None:
|
||||
continue
|
||||
new_name = f"mysensors{idx + 1}.pickle"
|
||||
gateway[CONF_PERSISTENCE_FILE] = new_name
|
||||
|
||||
return value
|
||||
|
||||
|
||||
def has_all_unique_files(value):
|
||||
"""Validate that all persistence files are unique and set if any is set."""
|
||||
persistence_files = [gateway.get(CONF_PERSISTENCE_FILE) for gateway in value]
|
||||
if None in persistence_files and any(
|
||||
name is not None for name in persistence_files
|
||||
):
|
||||
raise vol.Invalid(
|
||||
"persistence file name of all devices must be set if any is set"
|
||||
)
|
||||
if not all(name is None for name in persistence_files):
|
||||
schema = vol.Schema(vol.Unique())
|
||||
schema(persistence_files)
|
||||
persistence_files = [gateway[CONF_PERSISTENCE_FILE] for gateway in value]
|
||||
schema = vol.Schema(vol.Unique())
|
||||
schema(persistence_files)
|
||||
return value
|
||||
|
||||
|
||||
@ -128,7 +133,10 @@ CONFIG_SCHEMA = vol.Schema(
|
||||
deprecated(CONF_PERSISTENCE),
|
||||
{
|
||||
vol.Required(CONF_GATEWAYS): vol.All(
|
||||
cv.ensure_list, has_all_unique_files, [GATEWAY_SCHEMA]
|
||||
cv.ensure_list,
|
||||
set_default_persistence_file,
|
||||
has_all_unique_files,
|
||||
[GATEWAY_SCHEMA],
|
||||
),
|
||||
vol.Optional(CONF_RETAIN, default=True): cv.boolean,
|
||||
vol.Optional(CONF_VERSION, default=DEFAULT_VERSION): cv.string,
|
||||
@ -159,7 +167,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
CONF_TOPIC_IN_PREFIX: gw.get(CONF_TOPIC_IN_PREFIX, ""),
|
||||
CONF_RETAIN: config[CONF_RETAIN],
|
||||
CONF_VERSION: config[CONF_VERSION],
|
||||
CONF_PERSISTENCE_FILE: gw.get(CONF_PERSISTENCE_FILE)
|
||||
CONF_PERSISTENCE_FILE: gw[CONF_PERSISTENCE_FILE]
|
||||
# nodes config ignored at this time. renaming nodes can now be done from the frontend.
|
||||
}
|
||||
for gw in config[CONF_GATEWAYS]
|
||||
|
@ -324,7 +324,9 @@ class MySensorsConfigFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
except vol.Invalid:
|
||||
errors[CONF_PERSISTENCE_FILE] = "invalid_persistence_file"
|
||||
else:
|
||||
real_persistence_path = self._normalize_persistence_file(
|
||||
real_persistence_path = user_input[
|
||||
CONF_PERSISTENCE_FILE
|
||||
] = self._normalize_persistence_file(
|
||||
user_input[CONF_PERSISTENCE_FILE]
|
||||
)
|
||||
for other_entry in self.hass.config_entries.async_entries(DOMAIN):
|
||||
|
@ -651,8 +651,5 @@ def get_all_home_ids(home_data: pyatmo.HomeData) -> list[str]:
|
||||
return [
|
||||
home_data.homes[home_id]["id"]
|
||||
for home_id in home_data.homes
|
||||
if (
|
||||
"therm_schedules" in home_data.homes[home_id]
|
||||
and "modules" in home_data.homes[home_id]
|
||||
)
|
||||
if "modules" in home_data.homes[home_id]
|
||||
]
|
||||
|
@ -10,7 +10,7 @@ from requests.exceptions import RequestException
|
||||
|
||||
from homeassistant import exceptions
|
||||
from homeassistant.config_entries import SOURCE_IMPORT
|
||||
from homeassistant.const import CONF_HOST, CONF_PORT, CONF_TOKEN
|
||||
from homeassistant.const import CONF_HOST, CONF_PLATFORM, CONF_PORT, CONF_TOKEN
|
||||
from homeassistant.helpers.update_coordinator import (
|
||||
CoordinatorEntity,
|
||||
DataUpdateCoordinator,
|
||||
@ -22,6 +22,7 @@ from .const import (
|
||||
DATA_COORDINATOR,
|
||||
DATA_LOCKS,
|
||||
DATA_OPENERS,
|
||||
DEFAULT_PORT,
|
||||
DEFAULT_TIMEOUT,
|
||||
DOMAIN,
|
||||
ERROR_STATES,
|
||||
@ -59,11 +60,18 @@ async def async_setup(hass, config):
|
||||
continue
|
||||
|
||||
for conf in confs:
|
||||
hass.async_create_task(
|
||||
hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": SOURCE_IMPORT}, data=conf
|
||||
if CONF_PLATFORM in conf and conf[CONF_PLATFORM] == DOMAIN:
|
||||
hass.async_create_task(
|
||||
hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": SOURCE_IMPORT},
|
||||
data={
|
||||
CONF_HOST: conf[CONF_HOST],
|
||||
CONF_PORT: conf.get(CONF_PORT, DEFAULT_PORT),
|
||||
CONF_TOKEN: conf[CONF_TOKEN],
|
||||
},
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
|
@ -23,7 +23,7 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry):
|
||||
except CannotConnect as exc:
|
||||
raise ConfigEntryNotReady() from exc
|
||||
|
||||
hass.data[DOMAIN][config_entry.unique_id] = onewirehub
|
||||
hass.data[DOMAIN][config_entry.entry_id] = onewirehub
|
||||
|
||||
async def cleanup_registry() -> None:
|
||||
# Get registries
|
||||
@ -71,5 +71,5 @@ async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry):
|
||||
config_entry, PLATFORMS
|
||||
)
|
||||
if unload_ok:
|
||||
hass.data[DOMAIN].pop(config_entry.unique_id)
|
||||
hass.data[DOMAIN].pop(config_entry.entry_id)
|
||||
return unload_ok
|
||||
|
@ -81,7 +81,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
"""Set up 1-Wire platform."""
|
||||
# Only OWServer implementation works with binary sensors
|
||||
if config_entry.data[CONF_TYPE] == CONF_TYPE_OWSERVER:
|
||||
onewirehub = hass.data[DOMAIN][config_entry.unique_id]
|
||||
onewirehub = hass.data[DOMAIN][config_entry.entry_id]
|
||||
|
||||
entities = await hass.async_add_executor_job(get_entities, onewirehub)
|
||||
async_add_entities(entities, True)
|
||||
|
@ -256,7 +256,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
|
||||
|
||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
"""Set up 1-Wire platform."""
|
||||
onewirehub = hass.data[DOMAIN][config_entry.unique_id]
|
||||
onewirehub = hass.data[DOMAIN][config_entry.entry_id]
|
||||
entities = await hass.async_add_executor_job(
|
||||
get_entities, onewirehub, config_entry.data
|
||||
)
|
||||
|
@ -144,7 +144,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
"""Set up 1-Wire platform."""
|
||||
# Only OWServer implementation works with switches
|
||||
if config_entry.data[CONF_TYPE] == CONF_TYPE_OWSERVER:
|
||||
onewirehub = hass.data[DOMAIN][config_entry.unique_id]
|
||||
onewirehub = hass.data[DOMAIN][config_entry.entry_id]
|
||||
|
||||
entities = await hass.async_add_executor_job(get_entities, onewirehub)
|
||||
async_add_entities(entities, True)
|
||||
|
@ -2,7 +2,7 @@
|
||||
"domain": "recorder",
|
||||
"name": "Recorder",
|
||||
"documentation": "https://www.home-assistant.io/integrations/recorder",
|
||||
"requirements": ["sqlalchemy==1.4.11"],
|
||||
"requirements": ["sqlalchemy==1.4.13"],
|
||||
"codeowners": [],
|
||||
"quality_scale": "internal",
|
||||
"iot_class": "local_push"
|
||||
|
@ -213,11 +213,14 @@ async def async_setup_entry(hass, config_entry): # noqa: C901
|
||||
|
||||
_async_save_refresh_token(hass, config_entry, api.refresh_token)
|
||||
|
||||
simplisafe = hass.data[DOMAIN][DATA_CLIENT][config_entry.entry_id] = SimpliSafe(
|
||||
hass, api, config_entry
|
||||
)
|
||||
await simplisafe.async_init()
|
||||
simplisafe = SimpliSafe(hass, api, config_entry)
|
||||
|
||||
try:
|
||||
await simplisafe.async_init()
|
||||
except SimplipyError as err:
|
||||
raise ConfigEntryNotReady from err
|
||||
|
||||
hass.data[DOMAIN][DATA_CLIENT][config_entry.entry_id] = simplisafe
|
||||
hass.config_entries.async_setup_platforms(config_entry, PLATFORMS)
|
||||
|
||||
@callback
|
||||
|
@ -2,7 +2,7 @@
|
||||
"domain": "sql",
|
||||
"name": "SQL",
|
||||
"documentation": "https://www.home-assistant.io/integrations/sql",
|
||||
"requirements": ["sqlalchemy==1.4.11"],
|
||||
"requirements": ["sqlalchemy==1.4.13"],
|
||||
"codeowners": ["@dgomes"],
|
||||
"iot_class": "local_polling"
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ from homeassistant.const import (
|
||||
HTTP_UNAUTHORIZED,
|
||||
)
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.exceptions import ConfigEntryAuthFailed
|
||||
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.httpx_client import SERVER_SOFTWARE, USER_AGENT
|
||||
from homeassistant.helpers.update_coordinator import (
|
||||
@ -170,6 +170,9 @@ async def async_setup_entry(hass, config_entry):
|
||||
except IncompleteCredentials as ex:
|
||||
await async_client.aclose()
|
||||
raise ConfigEntryAuthFailed from ex
|
||||
except httpx.ConnectTimeout as ex:
|
||||
await async_client.aclose()
|
||||
raise ConfigEntryNotReady from ex
|
||||
except TeslaException as ex:
|
||||
await async_client.aclose()
|
||||
if ex.code == HTTP_UNAUTHORIZED:
|
||||
|
@ -3,7 +3,7 @@
|
||||
"name": "Xiaomi Miio",
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/xiaomi_miio",
|
||||
"requirements": ["construct==2.10.56", "python-miio==0.5.5"],
|
||||
"requirements": ["construct==2.10.56", "python-miio==0.5.6"],
|
||||
"codeowners": ["@rytilahti", "@syssi", "@starkillerOG"],
|
||||
"zeroconf": ["_miio._udp.local."],
|
||||
"iot_class": "local_polling"
|
||||
|
@ -61,7 +61,7 @@ from .core.const import (
|
||||
)
|
||||
from .core.group import GroupMember
|
||||
from .core.helpers import (
|
||||
async_input_cluster_exists,
|
||||
async_cluster_exists,
|
||||
async_is_bindable_target,
|
||||
convert_install_code,
|
||||
get_matched_clusters,
|
||||
@ -897,7 +897,7 @@ async def websocket_get_configuration(hass, connection, msg):
|
||||
|
||||
data = {"schemas": {}, "data": {}}
|
||||
for section, schema in ZHA_CONFIG_SCHEMAS.items():
|
||||
if section == ZHA_ALARM_OPTIONS and not async_input_cluster_exists(
|
||||
if section == ZHA_ALARM_OPTIONS and not async_cluster_exists(
|
||||
hass, IasAce.cluster_id
|
||||
):
|
||||
continue
|
||||
|
@ -139,14 +139,17 @@ def async_get_zha_config_value(config_entry, section, config_key, default):
|
||||
)
|
||||
|
||||
|
||||
def async_input_cluster_exists(hass, cluster_id):
|
||||
def async_cluster_exists(hass, cluster_id):
|
||||
"""Determine if a device containing the specified in cluster is paired."""
|
||||
zha_gateway = hass.data[DATA_ZHA][DATA_ZHA_GATEWAY]
|
||||
zha_devices = zha_gateway.devices.values()
|
||||
for zha_device in zha_devices:
|
||||
clusters_by_endpoint = zha_device.async_get_clusters()
|
||||
for clusters in clusters_by_endpoint.values():
|
||||
if cluster_id in clusters[CLUSTER_TYPE_IN]:
|
||||
if (
|
||||
cluster_id in clusters[CLUSTER_TYPE_IN]
|
||||
or cluster_id in clusters[CLUSTER_TYPE_OUT]
|
||||
):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
@ -83,7 +83,8 @@ SINGLE_INPUT_CLUSTER_DEVICE_CLASS = {
|
||||
}
|
||||
|
||||
SINGLE_OUTPUT_CLUSTER_DEVICE_CLASS = {
|
||||
zcl.clusters.general.OnOff.cluster_id: BINARY_SENSOR
|
||||
zcl.clusters.general.OnOff.cluster_id: BINARY_SENSOR,
|
||||
zcl.clusters.security.IasAce.cluster_id: ALARM,
|
||||
}
|
||||
|
||||
BINDABLE_CLUSTERS = SetRegistry()
|
||||
|
@ -25,7 +25,7 @@ from homeassistant.components.websocket_api.const import (
|
||||
ERR_NOT_SUPPORTED,
|
||||
ERR_UNKNOWN_ERROR,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.config_entries import ENTRY_STATE_LOADED, ConfigEntry
|
||||
from homeassistant.const import CONF_URL
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
@ -44,6 +44,7 @@ from .helpers import async_enable_statistics, update_data_collection_preference
|
||||
# general API constants
|
||||
ID = "id"
|
||||
ENTRY_ID = "entry_id"
|
||||
ERR_NOT_LOADED = "not_loaded"
|
||||
NODE_ID = "node_id"
|
||||
COMMAND_CLASS_ID = "command_class_id"
|
||||
TYPE = "type"
|
||||
@ -83,6 +84,13 @@ def async_get_entry(orig_func: Callable) -> Callable:
|
||||
msg[ID], ERR_NOT_FOUND, f"Config entry {entry_id} not found"
|
||||
)
|
||||
return
|
||||
|
||||
if entry.state != ENTRY_STATE_LOADED:
|
||||
connection.send_error(
|
||||
msg[ID], ERR_NOT_LOADED, f"Config entry {entry_id} not loaded"
|
||||
)
|
||||
return
|
||||
|
||||
client = hass.data[DOMAIN][entry_id][DATA_CLIENT]
|
||||
await orig_func(hass, connection, msg, entry, client)
|
||||
|
||||
@ -137,17 +145,20 @@ def async_register_api(hass: HomeAssistant) -> None:
|
||||
hass.http.register_view(DumpView) # type: ignore
|
||||
|
||||
|
||||
@websocket_api.require_admin
|
||||
@websocket_api.require_admin # type: ignore
|
||||
@websocket_api.async_response
|
||||
@websocket_api.websocket_command(
|
||||
{vol.Required(TYPE): "zwave_js/network_status", vol.Required(ENTRY_ID): str}
|
||||
)
|
||||
@callback
|
||||
def websocket_network_status(
|
||||
hass: HomeAssistant, connection: ActiveConnection, msg: dict
|
||||
@async_get_entry
|
||||
async def websocket_network_status(
|
||||
hass: HomeAssistant,
|
||||
connection: ActiveConnection,
|
||||
msg: dict,
|
||||
entry: ConfigEntry,
|
||||
client: Client,
|
||||
) -> None:
|
||||
"""Get the status of the Z-Wave JS network."""
|
||||
entry_id = msg[ENTRY_ID]
|
||||
client = hass.data[DOMAIN][entry_id][DATA_CLIENT]
|
||||
data = {
|
||||
"client": {
|
||||
"ws_server_url": client.ws_server_url,
|
||||
@ -166,6 +177,7 @@ def websocket_network_status(
|
||||
)
|
||||
|
||||
|
||||
@websocket_api.async_response # type: ignore
|
||||
@websocket_api.websocket_command(
|
||||
{
|
||||
vol.Required(TYPE): "zwave_js/node_status",
|
||||
@ -173,20 +185,14 @@ def websocket_network_status(
|
||||
vol.Required(NODE_ID): int,
|
||||
}
|
||||
)
|
||||
@callback
|
||||
def websocket_node_status(
|
||||
hass: HomeAssistant, connection: ActiveConnection, msg: dict
|
||||
@async_get_node
|
||||
async def websocket_node_status(
|
||||
hass: HomeAssistant,
|
||||
connection: ActiveConnection,
|
||||
msg: dict,
|
||||
node: Node,
|
||||
) -> None:
|
||||
"""Get the status of a Z-Wave JS node."""
|
||||
entry_id = msg[ENTRY_ID]
|
||||
client = hass.data[DOMAIN][entry_id][DATA_CLIENT]
|
||||
node_id = msg[NODE_ID]
|
||||
node = client.driver.controller.nodes.get(node_id)
|
||||
|
||||
if node is None:
|
||||
connection.send_error(msg[ID], ERR_NOT_FOUND, f"Node {node_id} not found")
|
||||
return
|
||||
|
||||
data = {
|
||||
"node_id": node.node_id,
|
||||
"is_routing": node.is_routing,
|
||||
@ -537,7 +543,8 @@ async def websocket_set_config_parameter(
|
||||
)
|
||||
|
||||
|
||||
@websocket_api.require_admin
|
||||
@websocket_api.require_admin # type: ignore
|
||||
@websocket_api.async_response
|
||||
@websocket_api.websocket_command(
|
||||
{
|
||||
vol.Required(TYPE): "zwave_js/get_config_parameters",
|
||||
@ -545,20 +552,11 @@ async def websocket_set_config_parameter(
|
||||
vol.Required(NODE_ID): int,
|
||||
}
|
||||
)
|
||||
@callback
|
||||
def websocket_get_config_parameters(
|
||||
hass: HomeAssistant, connection: ActiveConnection, msg: dict
|
||||
@async_get_node
|
||||
async def websocket_get_config_parameters(
|
||||
hass: HomeAssistant, connection: ActiveConnection, msg: dict, node: Node
|
||||
) -> None:
|
||||
"""Get a list of configuration parameters for a Z-Wave node."""
|
||||
entry_id = msg[ENTRY_ID]
|
||||
node_id = msg[NODE_ID]
|
||||
client = hass.data[DOMAIN][entry_id][DATA_CLIENT]
|
||||
node = client.driver.controller.nodes.get(node_id)
|
||||
|
||||
if node is None:
|
||||
connection.send_error(msg[ID], ERR_NOT_FOUND, f"Node {node_id} not found")
|
||||
return
|
||||
|
||||
values = node.get_configuration_values()
|
||||
result = {}
|
||||
for value_id, zwave_value in values.items():
|
||||
|
@ -1,7 +1,7 @@
|
||||
"""Constants used by Home Assistant components."""
|
||||
MAJOR_VERSION = 2021
|
||||
MINOR_VERSION = 5
|
||||
PATCH_VERSION = "0"
|
||||
PATCH_VERSION = "1"
|
||||
__short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
||||
__version__ = f"{__short_version__}.{PATCH_VERSION}"
|
||||
REQUIRED_PYTHON_VER = (3, 8, 0)
|
||||
|
@ -30,7 +30,7 @@ pyyaml==5.4.1
|
||||
requests==2.25.1
|
||||
ruamel.yaml==0.15.100
|
||||
scapy==2.4.5
|
||||
sqlalchemy==1.4.11
|
||||
sqlalchemy==1.4.13
|
||||
voluptuous-serialize==2.4.0
|
||||
voluptuous==0.12.1
|
||||
yarl==1.6.3
|
||||
|
@ -182,7 +182,7 @@ aiohomekit==0.2.61
|
||||
aiohttp_cors==0.7.0
|
||||
|
||||
# homeassistant.components.hue
|
||||
aiohue==2.1.0
|
||||
aiohue==2.3.0
|
||||
|
||||
# homeassistant.components.imap
|
||||
aioimaplib==0.7.15
|
||||
@ -479,7 +479,7 @@ defusedxml==0.6.0
|
||||
deluge-client==1.7.1
|
||||
|
||||
# homeassistant.components.denonavr
|
||||
denonavr==0.10.5
|
||||
denonavr==0.10.6
|
||||
|
||||
# homeassistant.components.devolo_home_control
|
||||
devolo-home-control-api==0.17.3
|
||||
@ -1322,7 +1322,7 @@ pychromecast==9.1.2
|
||||
pycketcasts==1.0.0
|
||||
|
||||
# homeassistant.components.climacell
|
||||
pyclimacell==0.18.0
|
||||
pyclimacell==0.18.2
|
||||
|
||||
# homeassistant.components.cmus
|
||||
pycmus==0.1.1
|
||||
@ -1819,7 +1819,7 @@ python-juicenet==1.0.1
|
||||
# python-lirc==1.2.3
|
||||
|
||||
# homeassistant.components.xiaomi_miio
|
||||
python-miio==0.5.5
|
||||
python-miio==0.5.6
|
||||
|
||||
# homeassistant.components.mpd
|
||||
python-mpd2==3.0.4
|
||||
@ -2142,7 +2142,7 @@ spotipy==2.18.0
|
||||
|
||||
# homeassistant.components.recorder
|
||||
# homeassistant.components.sql
|
||||
sqlalchemy==1.4.11
|
||||
sqlalchemy==1.4.13
|
||||
|
||||
# homeassistant.components.srp_energy
|
||||
srpenergy==1.3.2
|
||||
|
@ -119,7 +119,7 @@ aiohomekit==0.2.61
|
||||
aiohttp_cors==0.7.0
|
||||
|
||||
# homeassistant.components.hue
|
||||
aiohue==2.1.0
|
||||
aiohue==2.3.0
|
||||
|
||||
# homeassistant.components.apache_kafka
|
||||
aiokafka==0.6.0
|
||||
@ -264,7 +264,7 @@ debugpy==1.2.1
|
||||
defusedxml==0.6.0
|
||||
|
||||
# homeassistant.components.denonavr
|
||||
denonavr==0.10.5
|
||||
denonavr==0.10.6
|
||||
|
||||
# homeassistant.components.devolo_home_control
|
||||
devolo-home-control-api==0.17.3
|
||||
@ -720,7 +720,7 @@ pycfdns==1.2.1
|
||||
pychromecast==9.1.2
|
||||
|
||||
# homeassistant.components.climacell
|
||||
pyclimacell==0.18.0
|
||||
pyclimacell==0.18.2
|
||||
|
||||
# homeassistant.components.comfoconnect
|
||||
pycomfoconnect==0.4
|
||||
@ -983,7 +983,7 @@ python-izone==1.1.4
|
||||
python-juicenet==1.0.1
|
||||
|
||||
# homeassistant.components.xiaomi_miio
|
||||
python-miio==0.5.5
|
||||
python-miio==0.5.6
|
||||
|
||||
# homeassistant.components.nest
|
||||
python-nest==4.1.0
|
||||
@ -1144,7 +1144,7 @@ spotipy==2.18.0
|
||||
|
||||
# homeassistant.components.recorder
|
||||
# homeassistant.components.sql
|
||||
sqlalchemy==1.4.11
|
||||
sqlalchemy==1.4.13
|
||||
|
||||
# homeassistant.components.srp_energy
|
||||
srpenergy==1.3.2
|
||||
|
@ -144,7 +144,10 @@ async def test_device_setup_update_authorization_error(hass):
|
||||
"""Test we handle an authorization error in the update step."""
|
||||
device = get_device("Office")
|
||||
mock_api = device.get_mock_api()
|
||||
mock_api.check_sensors.side_effect = (blke.AuthorizationError(), None)
|
||||
mock_api.check_sensors.side_effect = (
|
||||
blke.AuthorizationError(),
|
||||
{"temperature": 30},
|
||||
)
|
||||
|
||||
with patch.object(
|
||||
hass.config_entries, "async_forward_entry_setup"
|
||||
|
@ -143,6 +143,38 @@ async def test_rm_pro_sensor_update(hass):
|
||||
assert sensors_and_states == {(f"{device.name} Temperature", "25.8")}
|
||||
|
||||
|
||||
async def test_rm_pro_filter_crazy_temperature(hass):
|
||||
"""Test we filter a crazy temperature variation.
|
||||
|
||||
Firmware issue. See https://github.com/home-assistant/core/issues/42100.
|
||||
"""
|
||||
device = get_device("Office")
|
||||
mock_api = device.get_mock_api()
|
||||
mock_api.check_sensors.return_value = {"temperature": 22.9}
|
||||
|
||||
device_registry = mock_device_registry(hass)
|
||||
entity_registry = mock_registry(hass)
|
||||
|
||||
mock_api, mock_entry = await device.setup_entry(hass, mock_api=mock_api)
|
||||
|
||||
device_entry = device_registry.async_get_device({(DOMAIN, mock_entry.unique_id)})
|
||||
entries = async_entries_for_device(entity_registry, device_entry.id)
|
||||
sensors = {entry for entry in entries if entry.domain == SENSOR_DOMAIN}
|
||||
assert len(sensors) == 1
|
||||
|
||||
mock_api.check_sensors.return_value = {"temperature": -7}
|
||||
await hass.helpers.entity_component.async_update_entity(
|
||||
next(iter(sensors)).entity_id
|
||||
)
|
||||
assert mock_api.check_sensors.call_count == 2
|
||||
|
||||
sensors_and_states = {
|
||||
(sensor.original_name, hass.states.get(sensor.entity_id).state)
|
||||
for sensor in sensors
|
||||
}
|
||||
assert sensors_and_states == {(f"{device.name} Temperature", "22.9")}
|
||||
|
||||
|
||||
async def test_rm_mini3_no_sensor(hass):
|
||||
"""Test we do not set up sensors for RM mini 3."""
|
||||
device = get_device("Entrance")
|
||||
|
@ -119,9 +119,9 @@ async def test_v3_sensor(
|
||||
check_sensor_state(hass, EPA_HEALTH_CONCERN, "Good")
|
||||
check_sensor_state(hass, EPA_PRIMARY_POLLUTANT, "pm25")
|
||||
check_sensor_state(hass, FIRE_INDEX, "9")
|
||||
check_sensor_state(hass, GRASS_POLLEN, "0")
|
||||
check_sensor_state(hass, WEED_POLLEN, "0")
|
||||
check_sensor_state(hass, TREE_POLLEN, "0")
|
||||
check_sensor_state(hass, GRASS_POLLEN, "minimal_to_none")
|
||||
check_sensor_state(hass, WEED_POLLEN, "minimal_to_none")
|
||||
check_sensor_state(hass, TREE_POLLEN, "minimal_to_none")
|
||||
|
||||
|
||||
async def test_v4_sensor(
|
||||
|
@ -11,7 +11,6 @@ from homeassistant.components.light import (
|
||||
ATTR_MAX_MIREDS,
|
||||
ATTR_MIN_MIREDS,
|
||||
ATTR_RGB_COLOR,
|
||||
ATTR_WHITE_VALUE,
|
||||
ATTR_XY_COLOR,
|
||||
DOMAIN as LIGHT_DOMAIN,
|
||||
SERVICE_TURN_OFF,
|
||||
@ -54,13 +53,11 @@ async def test_state_attributes(hass):
|
||||
{
|
||||
ATTR_ENTITY_ID: ENTITY_LIGHT,
|
||||
ATTR_RGB_COLOR: (251, 253, 255),
|
||||
ATTR_WHITE_VALUE: 254,
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
state = hass.states.get(ENTITY_LIGHT)
|
||||
assert state.attributes.get(ATTR_WHITE_VALUE) == 254
|
||||
assert state.attributes.get(ATTR_RGB_COLOR) == (250, 252, 255)
|
||||
assert state.attributes.get(ATTR_XY_COLOR) == (0.319, 0.326)
|
||||
|
||||
|
@ -91,6 +91,8 @@ ENTITY_IDS_BY_NUMBER = {
|
||||
"22": "scene.light_on",
|
||||
"23": "scene.light_off",
|
||||
"24": "media_player.kitchen",
|
||||
"25": "light.office_rgbw_lights",
|
||||
"26": "light.living_room_rgbww_lights",
|
||||
}
|
||||
|
||||
ENTITY_NUMBERS_BY_ID = {v: k for k, v in ENTITY_IDS_BY_NUMBER.items()}
|
||||
|
@ -380,4 +380,26 @@ DEMO_DEVICES = [
|
||||
"type": "action.devices.types.SECURITYSYSTEM",
|
||||
"willReportState": False,
|
||||
},
|
||||
{
|
||||
"id": "light.living_room_rgbww_lights",
|
||||
"name": {"name": "Living Room RGBWW Lights"},
|
||||
"traits": [
|
||||
"action.devices.traits.OnOff",
|
||||
"action.devices.traits.Brightness",
|
||||
"action.devices.traits.ColorSetting",
|
||||
],
|
||||
"type": "action.devices.types.LIGHT",
|
||||
"willReportState": False,
|
||||
},
|
||||
{
|
||||
"id": "light.office_rgbw_lights",
|
||||
"name": {"name": "Office RGBW Lights"},
|
||||
"traits": [
|
||||
"action.devices.traits.OnOff",
|
||||
"action.devices.traits.Brightness",
|
||||
"action.devices.traits.ColorSetting",
|
||||
],
|
||||
"type": "action.devices.types.LIGHT",
|
||||
"willReportState": False,
|
||||
},
|
||||
]
|
||||
|
@ -8,6 +8,7 @@ from homeassistant.components.group import DOMAIN, SERVICE_RELOAD
|
||||
import homeassistant.components.group.light as group
|
||||
from homeassistant.components.light import (
|
||||
ATTR_BRIGHTNESS,
|
||||
ATTR_COLOR_MODE,
|
||||
ATTR_COLOR_TEMP,
|
||||
ATTR_EFFECT,
|
||||
ATTR_EFFECT_LIST,
|
||||
@ -16,13 +17,24 @@ from homeassistant.components.light import (
|
||||
ATTR_MAX_MIREDS,
|
||||
ATTR_MIN_MIREDS,
|
||||
ATTR_RGB_COLOR,
|
||||
ATTR_RGBW_COLOR,
|
||||
ATTR_RGBWW_COLOR,
|
||||
ATTR_SUPPORTED_COLOR_MODES,
|
||||
ATTR_TRANSITION,
|
||||
ATTR_WHITE_VALUE,
|
||||
ATTR_XY_COLOR,
|
||||
COLOR_MODE_BRIGHTNESS,
|
||||
COLOR_MODE_COLOR_TEMP,
|
||||
COLOR_MODE_HS,
|
||||
COLOR_MODE_RGBW,
|
||||
COLOR_MODE_RGBWW,
|
||||
DOMAIN as LIGHT_DOMAIN,
|
||||
SERVICE_TOGGLE,
|
||||
SERVICE_TURN_OFF,
|
||||
SERVICE_TURN_ON,
|
||||
SUPPORT_BRIGHTNESS,
|
||||
SUPPORT_COLOR,
|
||||
SUPPORT_COLOR_TEMP,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID,
|
||||
@ -104,85 +116,281 @@ async def test_state_reporting(hass):
|
||||
|
||||
async def test_brightness(hass):
|
||||
"""Test brightness reporting."""
|
||||
await async_setup_component(
|
||||
platform = getattr(hass.components, "test.light")
|
||||
platform.init(empty=True)
|
||||
|
||||
platform.ENTITIES.append(platform.MockLight("test1", STATE_ON))
|
||||
platform.ENTITIES.append(platform.MockLight("test2", STATE_OFF))
|
||||
|
||||
entity0 = platform.ENTITIES[0]
|
||||
entity0.supported_color_modes = {COLOR_MODE_BRIGHTNESS}
|
||||
entity0.color_mode = COLOR_MODE_BRIGHTNESS
|
||||
entity0.brightness = 255
|
||||
|
||||
entity1 = platform.ENTITIES[1]
|
||||
entity1.supported_features = SUPPORT_BRIGHTNESS
|
||||
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
LIGHT_DOMAIN,
|
||||
{
|
||||
LIGHT_DOMAIN: {
|
||||
"platform": DOMAIN,
|
||||
"entities": ["light.test1", "light.test2"],
|
||||
}
|
||||
LIGHT_DOMAIN: [
|
||||
{"platform": "test"},
|
||||
{
|
||||
"platform": DOMAIN,
|
||||
"entities": ["light.test1", "light.test2"],
|
||||
},
|
||||
]
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
await hass.async_start()
|
||||
await hass.async_block_till_done()
|
||||
|
||||
hass.states.async_set(
|
||||
"light.test1", STATE_ON, {ATTR_BRIGHTNESS: 255, ATTR_SUPPORTED_FEATURES: 1}
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
state = hass.states.get("light.light_group")
|
||||
assert state.state == STATE_ON
|
||||
assert state.attributes[ATTR_SUPPORTED_FEATURES] == 1
|
||||
assert state.attributes[ATTR_BRIGHTNESS] == 255
|
||||
assert state.attributes[ATTR_COLOR_MODE] == "brightness"
|
||||
assert state.attributes[ATTR_SUPPORTED_FEATURES] == 0
|
||||
assert state.attributes[ATTR_SUPPORTED_COLOR_MODES] == ["brightness"]
|
||||
|
||||
hass.states.async_set(
|
||||
"light.test2", STATE_ON, {ATTR_BRIGHTNESS: 100, ATTR_SUPPORTED_FEATURES: 1}
|
||||
await hass.services.async_call(
|
||||
"light",
|
||||
"turn_on",
|
||||
{"entity_id": [entity1.entity_id], ATTR_BRIGHTNESS: 100},
|
||||
blocking=True,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
state = hass.states.get("light.light_group")
|
||||
assert state.state == STATE_ON
|
||||
assert state.attributes[ATTR_BRIGHTNESS] == 177
|
||||
assert state.attributes[ATTR_COLOR_MODE] == "brightness"
|
||||
assert state.attributes[ATTR_SUPPORTED_FEATURES] == 0
|
||||
assert state.attributes[ATTR_SUPPORTED_COLOR_MODES] == ["brightness"]
|
||||
|
||||
hass.states.async_set(
|
||||
"light.test1", STATE_OFF, {ATTR_BRIGHTNESS: 255, ATTR_SUPPORTED_FEATURES: 1}
|
||||
await hass.services.async_call(
|
||||
"light",
|
||||
"turn_off",
|
||||
{"entity_id": [entity0.entity_id]},
|
||||
blocking=True,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
state = hass.states.get("light.light_group")
|
||||
assert state.state == STATE_ON
|
||||
assert state.attributes[ATTR_SUPPORTED_FEATURES] == 1
|
||||
assert state.attributes[ATTR_BRIGHTNESS] == 100
|
||||
assert state.attributes[ATTR_COLOR_MODE] == "brightness"
|
||||
assert state.attributes[ATTR_SUPPORTED_FEATURES] == 0
|
||||
assert state.attributes[ATTR_SUPPORTED_COLOR_MODES] == ["brightness"]
|
||||
|
||||
|
||||
async def test_color(hass):
|
||||
"""Test RGB reporting."""
|
||||
await async_setup_component(
|
||||
async def test_color_hs(hass):
|
||||
"""Test hs color reporting."""
|
||||
platform = getattr(hass.components, "test.light")
|
||||
platform.init(empty=True)
|
||||
|
||||
platform.ENTITIES.append(platform.MockLight("test1", STATE_ON))
|
||||
platform.ENTITIES.append(platform.MockLight("test2", STATE_OFF))
|
||||
|
||||
entity0 = platform.ENTITIES[0]
|
||||
entity0.supported_color_modes = {COLOR_MODE_HS}
|
||||
entity0.color_mode = COLOR_MODE_HS
|
||||
entity0.brightness = 255
|
||||
entity0.hs_color = (0, 100)
|
||||
|
||||
entity1 = platform.ENTITIES[1]
|
||||
entity1.supported_features = SUPPORT_COLOR
|
||||
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
LIGHT_DOMAIN,
|
||||
{
|
||||
LIGHT_DOMAIN: {
|
||||
"platform": DOMAIN,
|
||||
"entities": ["light.test1", "light.test2"],
|
||||
}
|
||||
LIGHT_DOMAIN: [
|
||||
{"platform": "test"},
|
||||
{
|
||||
"platform": DOMAIN,
|
||||
"entities": ["light.test1", "light.test2"],
|
||||
},
|
||||
]
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
await hass.async_start()
|
||||
await hass.async_block_till_done()
|
||||
|
||||
hass.states.async_set(
|
||||
"light.test1", STATE_ON, {ATTR_HS_COLOR: (0, 100), ATTR_SUPPORTED_FEATURES: 16}
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
state = hass.states.get("light.light_group")
|
||||
assert state.state == STATE_ON
|
||||
assert state.attributes[ATTR_SUPPORTED_FEATURES] == 16
|
||||
assert state.attributes[ATTR_COLOR_MODE] == "hs"
|
||||
assert state.attributes[ATTR_HS_COLOR] == (0, 100)
|
||||
assert state.attributes[ATTR_SUPPORTED_COLOR_MODES] == ["hs"]
|
||||
assert state.attributes[ATTR_SUPPORTED_FEATURES] == 0
|
||||
|
||||
hass.states.async_set(
|
||||
"light.test2", STATE_ON, {ATTR_HS_COLOR: (0, 50), ATTR_SUPPORTED_FEATURES: 16}
|
||||
await hass.services.async_call(
|
||||
"light",
|
||||
"turn_on",
|
||||
{"entity_id": [entity1.entity_id], ATTR_HS_COLOR: (0, 50)},
|
||||
blocking=True,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
state = hass.states.get("light.light_group")
|
||||
assert state.attributes[ATTR_COLOR_MODE] == "hs"
|
||||
assert state.attributes[ATTR_HS_COLOR] == (0, 75)
|
||||
assert state.attributes[ATTR_SUPPORTED_COLOR_MODES] == ["hs"]
|
||||
assert state.attributes[ATTR_SUPPORTED_FEATURES] == 0
|
||||
|
||||
hass.states.async_set(
|
||||
"light.test1", STATE_OFF, {ATTR_HS_COLOR: (0, 0), ATTR_SUPPORTED_FEATURES: 16}
|
||||
await hass.services.async_call(
|
||||
"light",
|
||||
"turn_off",
|
||||
{"entity_id": [entity0.entity_id]},
|
||||
blocking=True,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
state = hass.states.get("light.light_group")
|
||||
assert state.attributes[ATTR_COLOR_MODE] == "hs"
|
||||
assert state.attributes[ATTR_HS_COLOR] == (0, 50)
|
||||
assert state.attributes[ATTR_SUPPORTED_COLOR_MODES] == ["hs"]
|
||||
assert state.attributes[ATTR_SUPPORTED_FEATURES] == 0
|
||||
|
||||
|
||||
async def test_color_rgbw(hass):
|
||||
"""Test rgbw color reporting."""
|
||||
platform = getattr(hass.components, "test.light")
|
||||
platform.init(empty=True)
|
||||
|
||||
platform.ENTITIES.append(platform.MockLight("test1", STATE_ON))
|
||||
platform.ENTITIES.append(platform.MockLight("test2", STATE_OFF))
|
||||
|
||||
entity0 = platform.ENTITIES[0]
|
||||
entity0.supported_color_modes = {COLOR_MODE_RGBW}
|
||||
entity0.color_mode = COLOR_MODE_RGBW
|
||||
entity0.brightness = 255
|
||||
entity0.rgbw_color = (0, 64, 128, 255)
|
||||
|
||||
entity1 = platform.ENTITIES[1]
|
||||
entity1.supported_color_modes = {COLOR_MODE_RGBW}
|
||||
entity1.color_mode = COLOR_MODE_RGBW
|
||||
entity1.brightness = 255
|
||||
entity1.rgbw_color = (255, 128, 64, 0)
|
||||
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
LIGHT_DOMAIN,
|
||||
{
|
||||
LIGHT_DOMAIN: [
|
||||
{"platform": "test"},
|
||||
{
|
||||
"platform": DOMAIN,
|
||||
"entities": ["light.test1", "light.test2"],
|
||||
},
|
||||
]
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
await hass.async_start()
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("light.light_group")
|
||||
assert state.state == STATE_ON
|
||||
assert state.attributes[ATTR_COLOR_MODE] == "rgbw"
|
||||
assert state.attributes[ATTR_RGBW_COLOR] == (0, 64, 128, 255)
|
||||
assert state.attributes[ATTR_SUPPORTED_COLOR_MODES] == ["rgbw"]
|
||||
assert state.attributes[ATTR_SUPPORTED_FEATURES] == 0
|
||||
|
||||
await hass.services.async_call(
|
||||
"light",
|
||||
"turn_on",
|
||||
{"entity_id": [entity1.entity_id]},
|
||||
blocking=True,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
state = hass.states.get("light.light_group")
|
||||
assert state.attributes[ATTR_COLOR_MODE] == "rgbw"
|
||||
assert state.attributes[ATTR_RGBW_COLOR] == (127, 96, 96, 127)
|
||||
assert state.attributes[ATTR_SUPPORTED_COLOR_MODES] == ["rgbw"]
|
||||
assert state.attributes[ATTR_SUPPORTED_FEATURES] == 0
|
||||
|
||||
await hass.services.async_call(
|
||||
"light",
|
||||
"turn_off",
|
||||
{"entity_id": [entity0.entity_id]},
|
||||
blocking=True,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
state = hass.states.get("light.light_group")
|
||||
assert state.attributes[ATTR_COLOR_MODE] == "rgbw"
|
||||
assert state.attributes[ATTR_RGBW_COLOR] == (255, 128, 64, 0)
|
||||
assert state.attributes[ATTR_SUPPORTED_COLOR_MODES] == ["rgbw"]
|
||||
assert state.attributes[ATTR_SUPPORTED_FEATURES] == 0
|
||||
|
||||
|
||||
async def test_color_rgbww(hass):
|
||||
"""Test rgbww color reporting."""
|
||||
platform = getattr(hass.components, "test.light")
|
||||
platform.init(empty=True)
|
||||
|
||||
platform.ENTITIES.append(platform.MockLight("test1", STATE_ON))
|
||||
platform.ENTITIES.append(platform.MockLight("test2", STATE_OFF))
|
||||
|
||||
entity0 = platform.ENTITIES[0]
|
||||
entity0.supported_color_modes = {COLOR_MODE_RGBWW}
|
||||
entity0.color_mode = COLOR_MODE_RGBWW
|
||||
entity0.brightness = 255
|
||||
entity0.rgbww_color = (0, 32, 64, 128, 255)
|
||||
|
||||
entity1 = platform.ENTITIES[1]
|
||||
entity1.supported_color_modes = {COLOR_MODE_RGBWW}
|
||||
entity1.color_mode = COLOR_MODE_RGBWW
|
||||
entity1.brightness = 255
|
||||
entity1.rgbww_color = (255, 128, 64, 32, 0)
|
||||
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
LIGHT_DOMAIN,
|
||||
{
|
||||
LIGHT_DOMAIN: [
|
||||
{"platform": "test"},
|
||||
{
|
||||
"platform": DOMAIN,
|
||||
"entities": ["light.test1", "light.test2"],
|
||||
},
|
||||
]
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
await hass.async_start()
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("light.light_group")
|
||||
assert state.state == STATE_ON
|
||||
assert state.attributes[ATTR_COLOR_MODE] == "rgbww"
|
||||
assert state.attributes[ATTR_RGBWW_COLOR] == (0, 32, 64, 128, 255)
|
||||
assert state.attributes[ATTR_SUPPORTED_COLOR_MODES] == ["rgbww"]
|
||||
assert state.attributes[ATTR_SUPPORTED_FEATURES] == 0
|
||||
|
||||
await hass.services.async_call(
|
||||
"light",
|
||||
"turn_on",
|
||||
{"entity_id": [entity1.entity_id]},
|
||||
blocking=True,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
state = hass.states.get("light.light_group")
|
||||
assert state.attributes[ATTR_COLOR_MODE] == "rgbww"
|
||||
assert state.attributes[ATTR_RGBWW_COLOR] == (127, 80, 64, 80, 127)
|
||||
assert state.attributes[ATTR_SUPPORTED_COLOR_MODES] == ["rgbww"]
|
||||
assert state.attributes[ATTR_SUPPORTED_FEATURES] == 0
|
||||
|
||||
await hass.services.async_call(
|
||||
"light",
|
||||
"turn_off",
|
||||
{"entity_id": [entity0.entity_id]},
|
||||
blocking=True,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
state = hass.states.get("light.light_group")
|
||||
assert state.attributes[ATTR_COLOR_MODE] == "rgbww"
|
||||
assert state.attributes[ATTR_RGBWW_COLOR] == (255, 128, 64, 32, 0)
|
||||
assert state.attributes[ATTR_SUPPORTED_COLOR_MODES] == ["rgbww"]
|
||||
assert state.attributes[ATTR_SUPPORTED_FEATURES] == 0
|
||||
|
||||
|
||||
async def test_white_value(hass):
|
||||
@ -206,6 +414,7 @@ async def test_white_value(hass):
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
state = hass.states.get("light.light_group")
|
||||
assert state.attributes[ATTR_SUPPORTED_FEATURES] == 128
|
||||
assert state.attributes[ATTR_WHITE_VALUE] == 255
|
||||
|
||||
hass.states.async_set(
|
||||
@ -213,6 +422,7 @@ async def test_white_value(hass):
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
state = hass.states.get("light.light_group")
|
||||
assert state.attributes[ATTR_SUPPORTED_FEATURES] == 128
|
||||
assert state.attributes[ATTR_WHITE_VALUE] == 177
|
||||
|
||||
hass.states.async_set(
|
||||
@ -220,62 +430,36 @@ async def test_white_value(hass):
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
state = hass.states.get("light.light_group")
|
||||
assert state.attributes[ATTR_SUPPORTED_FEATURES] == 128
|
||||
assert state.attributes[ATTR_WHITE_VALUE] == 100
|
||||
|
||||
|
||||
async def test_color_temp(hass):
|
||||
"""Test color temp reporting."""
|
||||
await async_setup_component(
|
||||
hass,
|
||||
LIGHT_DOMAIN,
|
||||
{
|
||||
LIGHT_DOMAIN: {
|
||||
"platform": DOMAIN,
|
||||
"entities": ["light.test1", "light.test2"],
|
||||
}
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
await hass.async_start()
|
||||
await hass.async_block_till_done()
|
||||
platform = getattr(hass.components, "test.light")
|
||||
platform.init(empty=True)
|
||||
|
||||
hass.states.async_set(
|
||||
"light.test1", STATE_ON, {"color_temp": 2, ATTR_SUPPORTED_FEATURES: 2}
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
state = hass.states.get("light.light_group")
|
||||
assert state.attributes[ATTR_COLOR_TEMP] == 2
|
||||
platform.ENTITIES.append(platform.MockLight("test1", STATE_ON))
|
||||
platform.ENTITIES.append(platform.MockLight("test2", STATE_OFF))
|
||||
|
||||
hass.states.async_set(
|
||||
"light.test2", STATE_ON, {"color_temp": 1000, ATTR_SUPPORTED_FEATURES: 2}
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
state = hass.states.get("light.light_group")
|
||||
assert state.attributes[ATTR_COLOR_TEMP] == 501
|
||||
entity0 = platform.ENTITIES[0]
|
||||
entity0.supported_color_modes = {COLOR_MODE_COLOR_TEMP}
|
||||
entity0.color_mode = COLOR_MODE_COLOR_TEMP
|
||||
entity0.brightness = 255
|
||||
entity0.color_temp = 2
|
||||
|
||||
hass.states.async_set(
|
||||
"light.test1", STATE_OFF, {"color_temp": 2, ATTR_SUPPORTED_FEATURES: 2}
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
state = hass.states.get("light.light_group")
|
||||
assert state.attributes[ATTR_COLOR_TEMP] == 1000
|
||||
entity1 = platform.ENTITIES[1]
|
||||
entity1.supported_features = SUPPORT_COLOR_TEMP
|
||||
|
||||
|
||||
async def test_emulated_color_temp_group(hass):
|
||||
"""Test emulated color temperature in a group."""
|
||||
await async_setup_component(
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
LIGHT_DOMAIN,
|
||||
{
|
||||
LIGHT_DOMAIN: [
|
||||
{"platform": "demo"},
|
||||
{"platform": "test"},
|
||||
{
|
||||
"platform": DOMAIN,
|
||||
"entities": [
|
||||
"light.bed_light",
|
||||
"light.ceiling_lights",
|
||||
"light.kitchen_lights",
|
||||
],
|
||||
"entities": ["light.test1", "light.test2"],
|
||||
},
|
||||
]
|
||||
},
|
||||
@ -284,13 +468,78 @@ async def test_emulated_color_temp_group(hass):
|
||||
await hass.async_start()
|
||||
await hass.async_block_till_done()
|
||||
|
||||
hass.states.async_set("light.bed_light", STATE_ON, {ATTR_SUPPORTED_FEATURES: 2})
|
||||
hass.states.async_set(
|
||||
"light.ceiling_lights", STATE_ON, {ATTR_SUPPORTED_FEATURES: 63}
|
||||
state = hass.states.get("light.light_group")
|
||||
assert state.attributes[ATTR_COLOR_MODE] == "color_temp"
|
||||
assert state.attributes[ATTR_COLOR_TEMP] == 2
|
||||
assert state.attributes[ATTR_SUPPORTED_FEATURES] == 0
|
||||
assert state.attributes[ATTR_SUPPORTED_COLOR_MODES] == ["color_temp"]
|
||||
|
||||
await hass.services.async_call(
|
||||
"light",
|
||||
"turn_on",
|
||||
{"entity_id": [entity1.entity_id], ATTR_COLOR_TEMP: 1000},
|
||||
blocking=True,
|
||||
)
|
||||
hass.states.async_set(
|
||||
"light.kitchen_lights", STATE_ON, {ATTR_SUPPORTED_FEATURES: 61}
|
||||
await hass.async_block_till_done()
|
||||
state = hass.states.get("light.light_group")
|
||||
assert state.attributes[ATTR_COLOR_MODE] == "color_temp"
|
||||
assert state.attributes[ATTR_COLOR_TEMP] == 501
|
||||
assert state.attributes[ATTR_SUPPORTED_FEATURES] == 0
|
||||
assert state.attributes[ATTR_SUPPORTED_COLOR_MODES] == ["color_temp"]
|
||||
|
||||
await hass.services.async_call(
|
||||
"light",
|
||||
"turn_off",
|
||||
{"entity_id": [entity0.entity_id]},
|
||||
blocking=True,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
state = hass.states.get("light.light_group")
|
||||
assert state.attributes[ATTR_COLOR_MODE] == "color_temp"
|
||||
assert state.attributes[ATTR_COLOR_TEMP] == 1000
|
||||
assert state.attributes[ATTR_SUPPORTED_FEATURES] == 0
|
||||
assert state.attributes[ATTR_SUPPORTED_COLOR_MODES] == ["color_temp"]
|
||||
|
||||
|
||||
async def test_emulated_color_temp_group(hass):
|
||||
"""Test emulated color temperature in a group."""
|
||||
platform = getattr(hass.components, "test.light")
|
||||
platform.init(empty=True)
|
||||
|
||||
platform.ENTITIES.append(platform.MockLight("test1", STATE_ON))
|
||||
platform.ENTITIES.append(platform.MockLight("test2", STATE_OFF))
|
||||
platform.ENTITIES.append(platform.MockLight("test3", STATE_OFF))
|
||||
|
||||
entity0 = platform.ENTITIES[0]
|
||||
entity0.supported_color_modes = {COLOR_MODE_COLOR_TEMP}
|
||||
entity0.color_mode = COLOR_MODE_COLOR_TEMP
|
||||
|
||||
entity1 = platform.ENTITIES[1]
|
||||
entity1.supported_color_modes = {COLOR_MODE_COLOR_TEMP, COLOR_MODE_HS}
|
||||
entity1.color_mode = COLOR_MODE_COLOR_TEMP
|
||||
|
||||
entity2 = platform.ENTITIES[2]
|
||||
entity2.supported_color_modes = {COLOR_MODE_HS}
|
||||
entity2.color_mode = COLOR_MODE_HS
|
||||
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
LIGHT_DOMAIN,
|
||||
{
|
||||
LIGHT_DOMAIN: [
|
||||
{"platform": "test"},
|
||||
{
|
||||
"platform": DOMAIN,
|
||||
"entities": ["light.test1", "light.test2", "light.test3"],
|
||||
},
|
||||
]
|
||||
},
|
||||
)
|
||||
|
||||
await hass.async_block_till_done()
|
||||
await hass.async_start()
|
||||
await hass.async_block_till_done()
|
||||
|
||||
await hass.async_block_till_done()
|
||||
await hass.services.async_call(
|
||||
LIGHT_DOMAIN,
|
||||
@ -300,61 +549,82 @@ async def test_emulated_color_temp_group(hass):
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("light.bed_light")
|
||||
state = hass.states.get("light.test1")
|
||||
assert state.state == STATE_ON
|
||||
assert state.attributes[ATTR_COLOR_TEMP] == 200
|
||||
assert ATTR_HS_COLOR not in state.attributes.keys()
|
||||
|
||||
state = hass.states.get("light.ceiling_lights")
|
||||
state = hass.states.get("light.test2")
|
||||
assert state.state == STATE_ON
|
||||
assert state.attributes[ATTR_COLOR_TEMP] == 200
|
||||
assert ATTR_HS_COLOR not in state.attributes.keys()
|
||||
|
||||
state = hass.states.get("light.kitchen_lights")
|
||||
state = hass.states.get("light.test3")
|
||||
assert state.state == STATE_ON
|
||||
assert state.attributes[ATTR_HS_COLOR] == (27.001, 19.243)
|
||||
|
||||
|
||||
async def test_min_max_mireds(hass):
|
||||
"""Test min/max mireds reporting."""
|
||||
await async_setup_component(
|
||||
"""Test min/max mireds reporting.
|
||||
|
||||
min/max mireds is reported both when light is on and off
|
||||
"""
|
||||
platform = getattr(hass.components, "test.light")
|
||||
platform.init(empty=True)
|
||||
|
||||
platform.ENTITIES.append(platform.MockLight("test1", STATE_ON))
|
||||
platform.ENTITIES.append(platform.MockLight("test2", STATE_OFF))
|
||||
|
||||
entity0 = platform.ENTITIES[0]
|
||||
entity0.supported_color_modes = {COLOR_MODE_COLOR_TEMP}
|
||||
entity0.color_mode = COLOR_MODE_COLOR_TEMP
|
||||
entity0.color_temp = 2
|
||||
entity0.min_mireds = 2
|
||||
entity0.max_mireds = 5
|
||||
|
||||
entity1 = platform.ENTITIES[1]
|
||||
entity1.supported_features = SUPPORT_COLOR_TEMP
|
||||
entity1.min_mireds = 1
|
||||
entity1.max_mireds = 1234567890
|
||||
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
LIGHT_DOMAIN,
|
||||
{
|
||||
LIGHT_DOMAIN: {
|
||||
"platform": DOMAIN,
|
||||
"entities": ["light.test1", "light.test2"],
|
||||
}
|
||||
LIGHT_DOMAIN: [
|
||||
{"platform": "test"},
|
||||
{
|
||||
"platform": DOMAIN,
|
||||
"entities": ["light.test1", "light.test2"],
|
||||
},
|
||||
]
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
await hass.async_start()
|
||||
await hass.async_block_till_done()
|
||||
|
||||
hass.states.async_set(
|
||||
"light.test1",
|
||||
STATE_ON,
|
||||
{ATTR_MIN_MIREDS: 2, ATTR_MAX_MIREDS: 5, ATTR_SUPPORTED_FEATURES: 2},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
state = hass.states.get("light.light_group")
|
||||
assert state.attributes[ATTR_MIN_MIREDS] == 2
|
||||
assert state.attributes[ATTR_MAX_MIREDS] == 5
|
||||
|
||||
hass.states.async_set(
|
||||
"light.test2",
|
||||
STATE_ON,
|
||||
{ATTR_MIN_MIREDS: 7, ATTR_MAX_MIREDS: 1234567890, ATTR_SUPPORTED_FEATURES: 2},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
state = hass.states.get("light.light_group")
|
||||
assert state.attributes[ATTR_MIN_MIREDS] == 2
|
||||
assert state.attributes[ATTR_MIN_MIREDS] == 1
|
||||
assert state.attributes[ATTR_MAX_MIREDS] == 1234567890
|
||||
|
||||
hass.states.async_set(
|
||||
"light.test1",
|
||||
STATE_OFF,
|
||||
{ATTR_MIN_MIREDS: 1, ATTR_MAX_MIREDS: 2, ATTR_SUPPORTED_FEATURES: 2},
|
||||
await hass.services.async_call(
|
||||
"light",
|
||||
"turn_on",
|
||||
{"entity_id": [entity0.entity_id]},
|
||||
blocking=True,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
state = hass.states.get("light.light_group")
|
||||
assert state.attributes[ATTR_MIN_MIREDS] == 1
|
||||
assert state.attributes[ATTR_MAX_MIREDS] == 1234567890
|
||||
|
||||
await hass.services.async_call(
|
||||
"light",
|
||||
"turn_off",
|
||||
{"entity_id": [entity0.entity_id]},
|
||||
blocking=True,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
state = hass.states.get("light.light_group")
|
||||
@ -465,6 +735,123 @@ async def test_effect(hass):
|
||||
assert state.attributes[ATTR_EFFECT] == "Random"
|
||||
|
||||
|
||||
async def test_supported_color_modes(hass):
|
||||
"""Test supported_color_modes reporting."""
|
||||
platform = getattr(hass.components, "test.light")
|
||||
platform.init(empty=True)
|
||||
|
||||
platform.ENTITIES.append(platform.MockLight("test1", STATE_ON))
|
||||
platform.ENTITIES.append(platform.MockLight("test2", STATE_OFF))
|
||||
platform.ENTITIES.append(platform.MockLight("test3", STATE_OFF))
|
||||
|
||||
entity0 = platform.ENTITIES[0]
|
||||
entity0.supported_color_modes = {COLOR_MODE_COLOR_TEMP, COLOR_MODE_HS}
|
||||
|
||||
entity1 = platform.ENTITIES[1]
|
||||
entity1.supported_color_modes = {COLOR_MODE_RGBW, COLOR_MODE_RGBWW}
|
||||
|
||||
entity2 = platform.ENTITIES[2]
|
||||
entity2.supported_features = SUPPORT_BRIGHTNESS
|
||||
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
LIGHT_DOMAIN,
|
||||
{
|
||||
LIGHT_DOMAIN: [
|
||||
{"platform": "test"},
|
||||
{
|
||||
"platform": DOMAIN,
|
||||
"entities": ["light.test1", "light.test2", "light.test3"],
|
||||
},
|
||||
]
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
await hass.async_start()
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("light.light_group")
|
||||
assert set(state.attributes[ATTR_SUPPORTED_COLOR_MODES]) == {
|
||||
"brightness",
|
||||
"color_temp",
|
||||
"hs",
|
||||
"rgbw",
|
||||
"rgbww",
|
||||
}
|
||||
|
||||
|
||||
async def test_color_mode(hass):
|
||||
"""Test color_mode reporting."""
|
||||
platform = getattr(hass.components, "test.light")
|
||||
platform.init(empty=True)
|
||||
|
||||
platform.ENTITIES.append(platform.MockLight("test1", STATE_ON))
|
||||
platform.ENTITIES.append(platform.MockLight("test2", STATE_OFF))
|
||||
platform.ENTITIES.append(platform.MockLight("test3", STATE_OFF))
|
||||
|
||||
entity0 = platform.ENTITIES[0]
|
||||
entity0.supported_color_modes = {COLOR_MODE_COLOR_TEMP, COLOR_MODE_HS}
|
||||
entity0.color_mode = COLOR_MODE_COLOR_TEMP
|
||||
|
||||
entity1 = platform.ENTITIES[1]
|
||||
entity1.supported_color_modes = {COLOR_MODE_COLOR_TEMP, COLOR_MODE_HS}
|
||||
entity1.color_mode = COLOR_MODE_COLOR_TEMP
|
||||
|
||||
entity2 = platform.ENTITIES[2]
|
||||
entity2.supported_color_modes = {COLOR_MODE_COLOR_TEMP, COLOR_MODE_HS}
|
||||
entity2.color_mode = COLOR_MODE_HS
|
||||
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
LIGHT_DOMAIN,
|
||||
{
|
||||
LIGHT_DOMAIN: [
|
||||
{"platform": "test"},
|
||||
{
|
||||
"platform": DOMAIN,
|
||||
"entities": ["light.test1", "light.test2", "light.test3"],
|
||||
},
|
||||
]
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
await hass.async_start()
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("light.light_group")
|
||||
assert state.attributes[ATTR_COLOR_MODE] == COLOR_MODE_COLOR_TEMP
|
||||
|
||||
await hass.services.async_call(
|
||||
"light",
|
||||
"turn_on",
|
||||
{"entity_id": [entity1.entity_id]},
|
||||
blocking=True,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
state = hass.states.get("light.light_group")
|
||||
assert state.attributes[ATTR_COLOR_MODE] == COLOR_MODE_COLOR_TEMP
|
||||
|
||||
await hass.services.async_call(
|
||||
"light",
|
||||
"turn_on",
|
||||
{"entity_id": [entity2.entity_id]},
|
||||
blocking=True,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
state = hass.states.get("light.light_group")
|
||||
assert state.attributes[ATTR_COLOR_MODE] == COLOR_MODE_COLOR_TEMP
|
||||
|
||||
await hass.services.async_call(
|
||||
"light",
|
||||
"turn_off",
|
||||
{"entity_id": [entity0.entity_id, entity1.entity_id]},
|
||||
blocking=True,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
state = hass.states.get("light.light_group")
|
||||
assert state.attributes[ATTR_COLOR_MODE] == COLOR_MODE_HS
|
||||
|
||||
|
||||
async def test_supported_features(hass):
|
||||
"""Test supported features reporting."""
|
||||
await async_setup_component(
|
||||
@ -486,20 +873,26 @@ async def test_supported_features(hass):
|
||||
state = hass.states.get("light.light_group")
|
||||
assert state.attributes[ATTR_SUPPORTED_FEATURES] == 0
|
||||
|
||||
# SUPPORT_COLOR_TEMP = 2
|
||||
# SUPPORT_COLOR_TEMP = 2 will be blocked in favour of COLOR_MODE_COLOR_TEMP
|
||||
hass.states.async_set("light.test2", STATE_ON, {ATTR_SUPPORTED_FEATURES: 2})
|
||||
await hass.async_block_till_done()
|
||||
state = hass.states.get("light.light_group")
|
||||
assert state.attributes[ATTR_SUPPORTED_FEATURES] == 2
|
||||
assert state.attributes[ATTR_SUPPORTED_FEATURES] == 0
|
||||
|
||||
# SUPPORT_TRANSITION | SUPPORT_FLASH | SUPPORT_BRIGHTNESS = 41
|
||||
# SUPPORT_BRIGHTNESS = 1 will be translated to COLOR_MODE_BRIGHTNESS
|
||||
hass.states.async_set("light.test1", STATE_OFF, {ATTR_SUPPORTED_FEATURES: 41})
|
||||
await hass.async_block_till_done()
|
||||
state = hass.states.get("light.light_group")
|
||||
assert state.attributes[ATTR_SUPPORTED_FEATURES] == 43
|
||||
# SUPPORT_TRANSITION | SUPPORT_FLASH = 40
|
||||
assert state.attributes[ATTR_SUPPORTED_FEATURES] == 40
|
||||
|
||||
# Test that unknown feature 256 is blocked
|
||||
hass.states.async_set("light.test2", STATE_OFF, {ATTR_SUPPORTED_FEATURES: 256})
|
||||
await hass.async_block_till_done()
|
||||
state = hass.states.get("light.light_group")
|
||||
assert state.attributes[ATTR_SUPPORTED_FEATURES] == 41
|
||||
assert state.attributes[ATTR_SUPPORTED_FEATURES] == 40
|
||||
|
||||
|
||||
async def test_service_calls(hass):
|
||||
@ -629,8 +1022,6 @@ async def test_invalid_service_calls(hass):
|
||||
}
|
||||
await grouped_light.async_turn_on(**data)
|
||||
data[ATTR_ENTITY_ID] = ["light.test1", "light.test2"]
|
||||
data.pop(ATTR_RGB_COLOR)
|
||||
data.pop(ATTR_XY_COLOR)
|
||||
mock_call.assert_called_once_with(
|
||||
LIGHT_DOMAIN, SERVICE_TURN_ON, data, blocking=True, context=None
|
||||
)
|
||||
|
@ -748,9 +748,9 @@ async def test_light_brightness_step(hass):
|
||||
)
|
||||
|
||||
_, data = entity0.last_call("turn_on")
|
||||
assert data["brightness"] == 126 # 100 + (255 * 0.10)
|
||||
assert data["brightness"] == 116 # 90 + (255 * 0.10)
|
||||
_, data = entity1.last_call("turn_on")
|
||||
assert data["brightness"] == 76 # 50 + (255 * 0.10)
|
||||
assert data["brightness"] == 66 # 40 + (255 * 0.10)
|
||||
|
||||
|
||||
async def test_light_brightness_pct_conversion(hass):
|
||||
|
@ -408,6 +408,10 @@ async def test_controlling_state_via_topic_and_json_message(hass, mqtt_mock, cap
|
||||
state = hass.states.get("fan.test")
|
||||
assert state.attributes.get(fan.ATTR_PERCENTAGE) == 100
|
||||
|
||||
async_fire_mqtt_message(hass, "percentage-state-topic", '{"otherval": 100}')
|
||||
assert "Ignoring empty speed from" in caplog.text
|
||||
caplog.clear()
|
||||
|
||||
async_fire_mqtt_message(hass, "preset-mode-state-topic", '{"val": "low"}')
|
||||
assert "not a valid preset mode" in caplog.text
|
||||
caplog.clear()
|
||||
@ -424,6 +428,99 @@ async def test_controlling_state_via_topic_and_json_message(hass, mqtt_mock, cap
|
||||
state = hass.states.get("fan.test")
|
||||
assert state.attributes.get("preset_mode") == "silent"
|
||||
|
||||
async_fire_mqtt_message(hass, "preset-mode-state-topic", '{"otherval": 100}')
|
||||
assert "Ignoring empty preset_mode from" in caplog.text
|
||||
caplog.clear()
|
||||
|
||||
|
||||
async def test_controlling_state_via_topic_and_json_message_shared_topic(
|
||||
hass, mqtt_mock, caplog
|
||||
):
|
||||
"""Test the controlling state via topic and JSON message using a shared topic."""
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
fan.DOMAIN,
|
||||
{
|
||||
fan.DOMAIN: {
|
||||
"platform": "mqtt",
|
||||
"name": "test",
|
||||
"state_topic": "shared-state-topic",
|
||||
"command_topic": "command-topic",
|
||||
"oscillation_state_topic": "shared-state-topic",
|
||||
"oscillation_command_topic": "oscillation-command-topic",
|
||||
"percentage_state_topic": "shared-state-topic",
|
||||
"percentage_command_topic": "percentage-command-topic",
|
||||
"preset_mode_state_topic": "shared-state-topic",
|
||||
"preset_mode_command_topic": "preset-mode-command-topic",
|
||||
"preset_modes": [
|
||||
"auto",
|
||||
"smart",
|
||||
"whoosh",
|
||||
"eco",
|
||||
"breeze",
|
||||
"silent",
|
||||
],
|
||||
"state_value_template": "{{ value_json.state }}",
|
||||
"oscillation_value_template": "{{ value_json.oscillation }}",
|
||||
"percentage_value_template": "{{ value_json.percentage }}",
|
||||
"preset_mode_value_template": "{{ value_json.preset_mode }}",
|
||||
"speed_range_min": 1,
|
||||
"speed_range_max": 100,
|
||||
}
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("fan.test")
|
||||
assert state.state == STATE_OFF
|
||||
assert not state.attributes.get(ATTR_ASSUMED_STATE)
|
||||
|
||||
async_fire_mqtt_message(
|
||||
hass,
|
||||
"shared-state-topic",
|
||||
'{"state":"ON","preset_mode":"eco","oscillation":"oscillate_on","percentage": 50}',
|
||||
)
|
||||
state = hass.states.get("fan.test")
|
||||
assert state.state == STATE_ON
|
||||
assert state.attributes.get("oscillating") is True
|
||||
assert state.attributes.get(fan.ATTR_PERCENTAGE) == 50
|
||||
assert state.attributes.get("preset_mode") == "eco"
|
||||
|
||||
async_fire_mqtt_message(
|
||||
hass,
|
||||
"shared-state-topic",
|
||||
'{"state":"ON","preset_mode":"auto","oscillation":"oscillate_off","percentage": 10}',
|
||||
)
|
||||
state = hass.states.get("fan.test")
|
||||
assert state.state == STATE_ON
|
||||
assert state.attributes.get("oscillating") is False
|
||||
assert state.attributes.get(fan.ATTR_PERCENTAGE) == 10
|
||||
assert state.attributes.get("preset_mode") == "auto"
|
||||
|
||||
async_fire_mqtt_message(
|
||||
hass,
|
||||
"shared-state-topic",
|
||||
'{"state":"OFF","preset_mode":"auto","oscillation":"oscillate_off","percentage": 0}',
|
||||
)
|
||||
state = hass.states.get("fan.test")
|
||||
assert state.state == STATE_OFF
|
||||
assert state.attributes.get("oscillating") is False
|
||||
assert state.attributes.get(fan.ATTR_PERCENTAGE) == 0
|
||||
assert state.attributes.get("preset_mode") == "auto"
|
||||
|
||||
async_fire_mqtt_message(
|
||||
hass,
|
||||
"shared-state-topic",
|
||||
'{"percentage": 100}',
|
||||
)
|
||||
state = hass.states.get("fan.test")
|
||||
assert state.attributes.get(fan.ATTR_PERCENTAGE) == 100
|
||||
assert state.attributes.get("preset_mode") == "auto"
|
||||
assert "Ignoring empty preset_mode from" in caplog.text
|
||||
assert "Ignoring empty state from" in caplog.text
|
||||
assert "Ignoring empty oscillation from" in caplog.text
|
||||
caplog.clear()
|
||||
|
||||
|
||||
async def test_sending_mqtt_commands_and_optimistic(hass, mqtt_mock, caplog):
|
||||
"""Test optimistic mode without state topic."""
|
||||
|
@ -380,6 +380,9 @@ async def test_config_invalid(
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.mysensors.config_flow.try_connect", return_value=True
|
||||
), patch(
|
||||
"homeassistant.components.mysensors.gateway.socket.getaddrinfo",
|
||||
side_effect=OSError,
|
||||
), patch(
|
||||
"homeassistant.components.mysensors.async_setup", return_value=True
|
||||
) as mock_setup, patch(
|
||||
|
@ -32,7 +32,7 @@ from homeassistant.setup import async_setup_component
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"config, expected_calls, expected_to_succeed, expected_config_flow_user_input",
|
||||
"config, expected_calls, expected_to_succeed, expected_config_entry_data",
|
||||
[
|
||||
(
|
||||
{
|
||||
@ -52,13 +52,19 @@ from homeassistant.setup import async_setup_component
|
||||
},
|
||||
1,
|
||||
True,
|
||||
{
|
||||
CONF_GATEWAY_TYPE: CONF_GATEWAY_TYPE_SERIAL,
|
||||
CONF_DEVICE: "COM5",
|
||||
CONF_PERSISTENCE_FILE: "bla.json",
|
||||
CONF_BAUD_RATE: 57600,
|
||||
CONF_VERSION: "2.3",
|
||||
},
|
||||
[
|
||||
{
|
||||
CONF_GATEWAY_TYPE: CONF_GATEWAY_TYPE_SERIAL,
|
||||
CONF_DEVICE: "COM5",
|
||||
CONF_PERSISTENCE_FILE: "bla.json",
|
||||
CONF_BAUD_RATE: 57600,
|
||||
CONF_VERSION: "2.3",
|
||||
CONF_TCP_PORT: 5003,
|
||||
CONF_TOPIC_IN_PREFIX: "",
|
||||
CONF_TOPIC_OUT_PREFIX: "",
|
||||
CONF_RETAIN: True,
|
||||
}
|
||||
],
|
||||
),
|
||||
(
|
||||
{
|
||||
@ -78,13 +84,19 @@ from homeassistant.setup import async_setup_component
|
||||
},
|
||||
1,
|
||||
True,
|
||||
{
|
||||
CONF_GATEWAY_TYPE: CONF_GATEWAY_TYPE_TCP,
|
||||
CONF_DEVICE: "127.0.0.1",
|
||||
CONF_PERSISTENCE_FILE: "blub.pickle",
|
||||
CONF_TCP_PORT: 343,
|
||||
CONF_VERSION: "2.4",
|
||||
},
|
||||
[
|
||||
{
|
||||
CONF_GATEWAY_TYPE: CONF_GATEWAY_TYPE_TCP,
|
||||
CONF_DEVICE: "127.0.0.1",
|
||||
CONF_PERSISTENCE_FILE: "blub.pickle",
|
||||
CONF_TCP_PORT: 343,
|
||||
CONF_VERSION: "2.4",
|
||||
CONF_BAUD_RATE: 115200,
|
||||
CONF_TOPIC_IN_PREFIX: "",
|
||||
CONF_TOPIC_OUT_PREFIX: "",
|
||||
CONF_RETAIN: False,
|
||||
}
|
||||
],
|
||||
),
|
||||
(
|
||||
{
|
||||
@ -100,12 +112,19 @@ from homeassistant.setup import async_setup_component
|
||||
},
|
||||
1,
|
||||
True,
|
||||
{
|
||||
CONF_GATEWAY_TYPE: CONF_GATEWAY_TYPE_TCP,
|
||||
CONF_DEVICE: "127.0.0.1",
|
||||
CONF_TCP_PORT: 5003,
|
||||
CONF_VERSION: DEFAULT_VERSION,
|
||||
},
|
||||
[
|
||||
{
|
||||
CONF_GATEWAY_TYPE: CONF_GATEWAY_TYPE_TCP,
|
||||
CONF_DEVICE: "127.0.0.1",
|
||||
CONF_TCP_PORT: 5003,
|
||||
CONF_VERSION: DEFAULT_VERSION,
|
||||
CONF_BAUD_RATE: 115200,
|
||||
CONF_TOPIC_IN_PREFIX: "",
|
||||
CONF_TOPIC_OUT_PREFIX: "",
|
||||
CONF_RETAIN: False,
|
||||
CONF_PERSISTENCE_FILE: "mysensors1.pickle",
|
||||
}
|
||||
],
|
||||
),
|
||||
(
|
||||
{
|
||||
@ -125,13 +144,19 @@ from homeassistant.setup import async_setup_component
|
||||
},
|
||||
1,
|
||||
True,
|
||||
{
|
||||
CONF_GATEWAY_TYPE: CONF_GATEWAY_TYPE_MQTT,
|
||||
CONF_DEVICE: "mqtt",
|
||||
CONF_VERSION: DEFAULT_VERSION,
|
||||
CONF_TOPIC_OUT_PREFIX: "outtopic",
|
||||
CONF_TOPIC_IN_PREFIX: "intopic",
|
||||
},
|
||||
[
|
||||
{
|
||||
CONF_GATEWAY_TYPE: CONF_GATEWAY_TYPE_MQTT,
|
||||
CONF_DEVICE: "mqtt",
|
||||
CONF_VERSION: DEFAULT_VERSION,
|
||||
CONF_BAUD_RATE: 115200,
|
||||
CONF_TCP_PORT: 5003,
|
||||
CONF_TOPIC_OUT_PREFIX: "outtopic",
|
||||
CONF_TOPIC_IN_PREFIX: "intopic",
|
||||
CONF_RETAIN: False,
|
||||
CONF_PERSISTENCE_FILE: "mysensors1.pickle",
|
||||
}
|
||||
],
|
||||
),
|
||||
(
|
||||
{
|
||||
@ -149,7 +174,7 @@ from homeassistant.setup import async_setup_component
|
||||
},
|
||||
0,
|
||||
True,
|
||||
{},
|
||||
[{}],
|
||||
),
|
||||
(
|
||||
{
|
||||
@ -177,7 +202,30 @@ from homeassistant.setup import async_setup_component
|
||||
},
|
||||
2,
|
||||
True,
|
||||
{},
|
||||
[
|
||||
{
|
||||
CONF_DEVICE: "mqtt",
|
||||
CONF_PERSISTENCE_FILE: "bla.json",
|
||||
CONF_TOPIC_OUT_PREFIX: "out",
|
||||
CONF_TOPIC_IN_PREFIX: "in",
|
||||
CONF_BAUD_RATE: 115200,
|
||||
CONF_TCP_PORT: 5003,
|
||||
CONF_VERSION: "2.4",
|
||||
CONF_RETAIN: False,
|
||||
CONF_GATEWAY_TYPE: CONF_GATEWAY_TYPE_MQTT,
|
||||
},
|
||||
{
|
||||
CONF_DEVICE: "COM6",
|
||||
CONF_PERSISTENCE_FILE: "bla2.json",
|
||||
CONF_TOPIC_OUT_PREFIX: "",
|
||||
CONF_TOPIC_IN_PREFIX: "",
|
||||
CONF_BAUD_RATE: 115200,
|
||||
CONF_TCP_PORT: 5003,
|
||||
CONF_VERSION: "2.4",
|
||||
CONF_RETAIN: False,
|
||||
CONF_GATEWAY_TYPE: CONF_GATEWAY_TYPE_SERIAL,
|
||||
},
|
||||
],
|
||||
),
|
||||
(
|
||||
{
|
||||
@ -203,7 +251,7 @@ from homeassistant.setup import async_setup_component
|
||||
},
|
||||
0,
|
||||
False,
|
||||
{},
|
||||
[{}],
|
||||
),
|
||||
(
|
||||
{
|
||||
@ -223,7 +271,47 @@ from homeassistant.setup import async_setup_component
|
||||
},
|
||||
0,
|
||||
True,
|
||||
{},
|
||||
[{}],
|
||||
),
|
||||
(
|
||||
{
|
||||
DOMAIN: {
|
||||
CONF_GATEWAYS: [
|
||||
{
|
||||
CONF_DEVICE: "COM1",
|
||||
},
|
||||
{
|
||||
CONF_DEVICE: "COM2",
|
||||
},
|
||||
],
|
||||
}
|
||||
},
|
||||
2,
|
||||
True,
|
||||
[
|
||||
{
|
||||
CONF_DEVICE: "COM1",
|
||||
CONF_PERSISTENCE_FILE: "mysensors1.pickle",
|
||||
CONF_TOPIC_OUT_PREFIX: "",
|
||||
CONF_TOPIC_IN_PREFIX: "",
|
||||
CONF_BAUD_RATE: 115200,
|
||||
CONF_TCP_PORT: 5003,
|
||||
CONF_VERSION: "1.4",
|
||||
CONF_RETAIN: True,
|
||||
CONF_GATEWAY_TYPE: CONF_GATEWAY_TYPE_SERIAL,
|
||||
},
|
||||
{
|
||||
CONF_DEVICE: "COM2",
|
||||
CONF_PERSISTENCE_FILE: "mysensors2.pickle",
|
||||
CONF_TOPIC_OUT_PREFIX: "",
|
||||
CONF_TOPIC_IN_PREFIX: "",
|
||||
CONF_BAUD_RATE: 115200,
|
||||
CONF_TCP_PORT: 5003,
|
||||
CONF_VERSION: "1.4",
|
||||
CONF_RETAIN: True,
|
||||
CONF_GATEWAY_TYPE: CONF_GATEWAY_TYPE_SERIAL,
|
||||
},
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
@ -233,7 +321,7 @@ async def test_import(
|
||||
config: ConfigType,
|
||||
expected_calls: int,
|
||||
expected_to_succeed: bool,
|
||||
expected_config_flow_user_input: dict[str, Any],
|
||||
expected_config_entry_data: list[dict[str, Any]],
|
||||
) -> None:
|
||||
"""Test importing a gateway."""
|
||||
await async_setup_component(hass, "persistent_notification", {})
|
||||
@ -249,8 +337,13 @@ async def test_import(
|
||||
|
||||
assert len(mock_setup_entry.mock_calls) == expected_calls
|
||||
|
||||
if expected_calls > 0:
|
||||
config_flow_user_input = mock_setup_entry.mock_calls[0][1][1].data
|
||||
for key, value in expected_config_flow_user_input.items():
|
||||
assert key in config_flow_user_input
|
||||
assert config_flow_user_input[key] == value
|
||||
for idx in range(expected_calls):
|
||||
config_entry = mock_setup_entry.mock_calls[idx][1][1]
|
||||
expected_persistence_file = expected_config_entry_data[idx].pop(
|
||||
CONF_PERSISTENCE_FILE
|
||||
)
|
||||
expected_persistence_path = hass.config.path(expected_persistence_file)
|
||||
config_entry_data = dict(config_entry.data)
|
||||
persistence_path = config_entry_data.pop(CONF_PERSISTENCE_FILE)
|
||||
assert persistence_path == expected_persistence_path
|
||||
assert config_entry_data == expected_config_entry_data[idx]
|
||||
|
@ -36,7 +36,6 @@ async def test_owserver_connect_failure(hass):
|
||||
CONF_HOST: "1.2.3.4",
|
||||
CONF_PORT: "1234",
|
||||
},
|
||||
unique_id=f"{CONF_TYPE_OWSERVER}:1.2.3.4:1234",
|
||||
connection_class=CONN_CLASS_LOCAL_POLL,
|
||||
options={},
|
||||
entry_id="2",
|
||||
@ -65,7 +64,6 @@ async def test_failed_owserver_listing(hass):
|
||||
CONF_HOST: "1.2.3.4",
|
||||
CONF_PORT: "1234",
|
||||
},
|
||||
unique_id=f"{CONF_TYPE_OWSERVER}:1.2.3.4:1234",
|
||||
connection_class=CONN_CLASS_LOCAL_POLL,
|
||||
options={},
|
||||
entry_id="2",
|
||||
|
@ -12,6 +12,7 @@ from homeassistant.components.zwave_js.api import (
|
||||
CONFIG,
|
||||
ENABLED,
|
||||
ENTRY_ID,
|
||||
ERR_NOT_LOADED,
|
||||
FILENAME,
|
||||
FORCE_CONSOLE,
|
||||
ID,
|
||||
@ -31,8 +32,8 @@ from homeassistant.components.zwave_js.const import (
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
|
||||
|
||||
async def test_websocket_api(hass, integration, multisensor_6, hass_ws_client):
|
||||
"""Test the network and node status websocket commands."""
|
||||
async def test_network_status(hass, integration, hass_ws_client):
|
||||
"""Test the network status websocket command."""
|
||||
entry = integration
|
||||
ws_client = await hass_ws_client(hass)
|
||||
|
||||
@ -45,6 +46,24 @@ async def test_websocket_api(hass, integration, multisensor_6, hass_ws_client):
|
||||
assert result["client"]["ws_server_url"] == "ws://test:3000/zjs"
|
||||
assert result["client"]["server_version"] == "1.0.0"
|
||||
|
||||
# Test sending command with not loaded entry fails
|
||||
await hass.config_entries.async_unload(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
await ws_client.send_json(
|
||||
{ID: 3, TYPE: "zwave_js/network_status", ENTRY_ID: entry.entry_id}
|
||||
)
|
||||
msg = await ws_client.receive_json()
|
||||
|
||||
assert not msg["success"]
|
||||
assert msg["error"]["code"] == ERR_NOT_LOADED
|
||||
|
||||
|
||||
async def test_node_status(hass, integration, multisensor_6, hass_ws_client):
|
||||
"""Test the node status websocket command."""
|
||||
entry = integration
|
||||
ws_client = await hass_ws_client(hass)
|
||||
|
||||
node = multisensor_6
|
||||
await ws_client.send_json(
|
||||
{
|
||||
@ -63,33 +82,10 @@ async def test_websocket_api(hass, integration, multisensor_6, hass_ws_client):
|
||||
assert not result["is_secure"]
|
||||
assert result["status"] == 1
|
||||
|
||||
# Test getting configuration parameter values
|
||||
await ws_client.send_json(
|
||||
{
|
||||
ID: 4,
|
||||
TYPE: "zwave_js/get_config_parameters",
|
||||
ENTRY_ID: entry.entry_id,
|
||||
NODE_ID: node.node_id,
|
||||
}
|
||||
)
|
||||
msg = await ws_client.receive_json()
|
||||
result = msg["result"]
|
||||
|
||||
assert len(result) == 61
|
||||
key = "52-112-0-2"
|
||||
assert result[key]["property"] == 2
|
||||
assert result[key]["property_key"] is None
|
||||
assert result[key]["metadata"]["type"] == "number"
|
||||
assert result[key]["configuration_value_type"] == "enumerated"
|
||||
assert result[key]["metadata"]["states"]
|
||||
|
||||
key = "52-112-0-201-255"
|
||||
assert result[key]["property_key"] == 255
|
||||
|
||||
# Test getting non-existent node fails
|
||||
await ws_client.send_json(
|
||||
{
|
||||
ID: 5,
|
||||
ID: 4,
|
||||
TYPE: "zwave_js/node_status",
|
||||
ENTRY_ID: entry.entry_id,
|
||||
NODE_ID: 99999,
|
||||
@ -99,18 +95,22 @@ async def test_websocket_api(hass, integration, multisensor_6, hass_ws_client):
|
||||
assert not msg["success"]
|
||||
assert msg["error"]["code"] == ERR_NOT_FOUND
|
||||
|
||||
# Test getting non-existent node config params fails
|
||||
# Test sending command with not loaded entry fails
|
||||
await hass.config_entries.async_unload(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
await ws_client.send_json(
|
||||
{
|
||||
ID: 6,
|
||||
TYPE: "zwave_js/get_config_parameters",
|
||||
ID: 5,
|
||||
TYPE: "zwave_js/node_status",
|
||||
ENTRY_ID: entry.entry_id,
|
||||
NODE_ID: 99999,
|
||||
NODE_ID: node.node_id,
|
||||
}
|
||||
)
|
||||
msg = await ws_client.receive_json()
|
||||
|
||||
assert not msg["success"]
|
||||
assert msg["error"]["code"] == ERR_NOT_FOUND
|
||||
assert msg["error"]["code"] == ERR_NOT_LOADED
|
||||
|
||||
|
||||
async def test_add_node(
|
||||
@ -145,6 +145,29 @@ async def test_add_node(
|
||||
client.driver.receive_event(nortek_thermostat_added_event)
|
||||
msg = await ws_client.receive_json()
|
||||
assert msg["event"]["event"] == "node added"
|
||||
node_details = {
|
||||
"node_id": 53,
|
||||
"status": 0,
|
||||
"ready": False,
|
||||
}
|
||||
assert msg["event"]["node"] == node_details
|
||||
|
||||
msg = await ws_client.receive_json()
|
||||
assert msg["event"]["event"] == "device registered"
|
||||
# Check the keys of the device item
|
||||
assert list(msg["event"]["device"]) == ["name", "id"]
|
||||
|
||||
# Test sending command with not loaded entry fails
|
||||
await hass.config_entries.async_unload(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
await ws_client.send_json(
|
||||
{ID: 4, TYPE: "zwave_js/add_node", ENTRY_ID: entry.entry_id}
|
||||
)
|
||||
msg = await ws_client.receive_json()
|
||||
|
||||
assert not msg["success"]
|
||||
assert msg["error"]["code"] == ERR_NOT_LOADED
|
||||
|
||||
|
||||
async def test_cancel_inclusion_exclusion(hass, integration, client, hass_ws_client):
|
||||
@ -168,6 +191,26 @@ async def test_cancel_inclusion_exclusion(hass, integration, client, hass_ws_cli
|
||||
msg = await ws_client.receive_json()
|
||||
assert msg["success"]
|
||||
|
||||
# Test sending command with not loaded entry fails
|
||||
await hass.config_entries.async_unload(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
await ws_client.send_json(
|
||||
{ID: 6, TYPE: "zwave_js/stop_inclusion", ENTRY_ID: entry.entry_id}
|
||||
)
|
||||
msg = await ws_client.receive_json()
|
||||
|
||||
assert not msg["success"]
|
||||
assert msg["error"]["code"] == ERR_NOT_LOADED
|
||||
|
||||
await ws_client.send_json(
|
||||
{ID: 7, TYPE: "zwave_js/stop_exclusion", ENTRY_ID: entry.entry_id}
|
||||
)
|
||||
msg = await ws_client.receive_json()
|
||||
|
||||
assert not msg["success"]
|
||||
assert msg["error"]["code"] == ERR_NOT_LOADED
|
||||
|
||||
|
||||
async def test_remove_node(
|
||||
hass,
|
||||
@ -226,6 +269,18 @@ async def test_remove_node(
|
||||
)
|
||||
assert device is None
|
||||
|
||||
# Test sending command with not loaded entry fails
|
||||
await hass.config_entries.async_unload(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
await ws_client.send_json(
|
||||
{ID: 4, TYPE: "zwave_js/remove_node", ENTRY_ID: entry.entry_id}
|
||||
)
|
||||
msg = await ws_client.receive_json()
|
||||
|
||||
assert not msg["success"]
|
||||
assert msg["error"]["code"] == ERR_NOT_LOADED
|
||||
|
||||
|
||||
async def test_refresh_node_info(
|
||||
hass, client, integration, hass_ws_client, multisensor_6
|
||||
@ -295,6 +350,36 @@ async def test_refresh_node_info(
|
||||
|
||||
client.async_send_command_no_wait.reset_mock()
|
||||
|
||||
# Test getting non-existent node fails
|
||||
await ws_client.send_json(
|
||||
{
|
||||
ID: 2,
|
||||
TYPE: "zwave_js/refresh_node_info",
|
||||
ENTRY_ID: entry.entry_id,
|
||||
NODE_ID: 9999,
|
||||
}
|
||||
)
|
||||
msg = await ws_client.receive_json()
|
||||
assert not msg["success"]
|
||||
assert msg["error"]["code"] == ERR_NOT_FOUND
|
||||
|
||||
# Test sending command with not loaded entry fails
|
||||
await hass.config_entries.async_unload(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
await ws_client.send_json(
|
||||
{
|
||||
ID: 3,
|
||||
TYPE: "zwave_js/refresh_node_info",
|
||||
ENTRY_ID: entry.entry_id,
|
||||
NODE_ID: 52,
|
||||
}
|
||||
)
|
||||
msg = await ws_client.receive_json()
|
||||
|
||||
assert not msg["success"]
|
||||
assert msg["error"]["code"] == ERR_NOT_LOADED
|
||||
|
||||
|
||||
async def test_refresh_node_values(
|
||||
hass, client, integration, hass_ws_client, multisensor_6
|
||||
@ -391,6 +476,38 @@ async def test_refresh_node_cc_values(
|
||||
assert not msg["success"]
|
||||
assert msg["error"]["code"] == ERR_NOT_FOUND
|
||||
|
||||
# Test getting non-existent node fails
|
||||
await ws_client.send_json(
|
||||
{
|
||||
ID: 3,
|
||||
TYPE: "zwave_js/refresh_node_cc_values",
|
||||
ENTRY_ID: entry.entry_id,
|
||||
NODE_ID: 9999,
|
||||
COMMAND_CLASS_ID: 112,
|
||||
}
|
||||
)
|
||||
msg = await ws_client.receive_json()
|
||||
assert not msg["success"]
|
||||
assert msg["error"]["code"] == ERR_NOT_FOUND
|
||||
|
||||
# Test sending command with not loaded entry fails
|
||||
await hass.config_entries.async_unload(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
await ws_client.send_json(
|
||||
{
|
||||
ID: 4,
|
||||
TYPE: "zwave_js/refresh_node_cc_values",
|
||||
ENTRY_ID: entry.entry_id,
|
||||
NODE_ID: 52,
|
||||
COMMAND_CLASS_ID: 112,
|
||||
}
|
||||
)
|
||||
msg = await ws_client.receive_json()
|
||||
|
||||
assert not msg["success"]
|
||||
assert msg["error"]["code"] == ERR_NOT_LOADED
|
||||
|
||||
|
||||
async def test_set_config_parameter(
|
||||
hass, client, hass_ws_client, multisensor_6, integration
|
||||
@ -510,6 +627,103 @@ async def test_set_config_parameter(
|
||||
assert msg["error"]["code"] == "unknown_error"
|
||||
assert msg["error"]["message"] == "test"
|
||||
|
||||
# Test getting non-existent node fails
|
||||
await ws_client.send_json(
|
||||
{
|
||||
ID: 5,
|
||||
TYPE: "zwave_js/set_config_parameter",
|
||||
ENTRY_ID: entry.entry_id,
|
||||
NODE_ID: 9999,
|
||||
PROPERTY: 102,
|
||||
PROPERTY_KEY: 1,
|
||||
VALUE: 1,
|
||||
}
|
||||
)
|
||||
msg = await ws_client.receive_json()
|
||||
assert not msg["success"]
|
||||
assert msg["error"]["code"] == ERR_NOT_FOUND
|
||||
|
||||
# Test sending command with not loaded entry fails
|
||||
await hass.config_entries.async_unload(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
await ws_client.send_json(
|
||||
{
|
||||
ID: 6,
|
||||
TYPE: "zwave_js/set_config_parameter",
|
||||
ENTRY_ID: entry.entry_id,
|
||||
NODE_ID: 52,
|
||||
PROPERTY: 102,
|
||||
PROPERTY_KEY: 1,
|
||||
VALUE: 1,
|
||||
}
|
||||
)
|
||||
|
||||
msg = await ws_client.receive_json()
|
||||
|
||||
assert not msg["success"]
|
||||
assert msg["error"]["code"] == ERR_NOT_LOADED
|
||||
|
||||
|
||||
async def test_get_config_parameters(hass, integration, multisensor_6, hass_ws_client):
|
||||
"""Test the get config parameters websocket command."""
|
||||
entry = integration
|
||||
ws_client = await hass_ws_client(hass)
|
||||
node = multisensor_6
|
||||
|
||||
# Test getting configuration parameter values
|
||||
await ws_client.send_json(
|
||||
{
|
||||
ID: 4,
|
||||
TYPE: "zwave_js/get_config_parameters",
|
||||
ENTRY_ID: entry.entry_id,
|
||||
NODE_ID: node.node_id,
|
||||
}
|
||||
)
|
||||
msg = await ws_client.receive_json()
|
||||
result = msg["result"]
|
||||
|
||||
assert len(result) == 61
|
||||
key = "52-112-0-2"
|
||||
assert result[key]["property"] == 2
|
||||
assert result[key]["property_key"] is None
|
||||
assert result[key]["metadata"]["type"] == "number"
|
||||
assert result[key]["configuration_value_type"] == "enumerated"
|
||||
assert result[key]["metadata"]["states"]
|
||||
|
||||
key = "52-112-0-201-255"
|
||||
assert result[key]["property_key"] == 255
|
||||
|
||||
# Test getting non-existent node config params fails
|
||||
await ws_client.send_json(
|
||||
{
|
||||
ID: 5,
|
||||
TYPE: "zwave_js/get_config_parameters",
|
||||
ENTRY_ID: entry.entry_id,
|
||||
NODE_ID: 99999,
|
||||
}
|
||||
)
|
||||
msg = await ws_client.receive_json()
|
||||
assert not msg["success"]
|
||||
assert msg["error"]["code"] == ERR_NOT_FOUND
|
||||
|
||||
# Test sending command with not loaded entry fails
|
||||
await hass.config_entries.async_unload(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
await ws_client.send_json(
|
||||
{
|
||||
ID: 6,
|
||||
TYPE: "zwave_js/get_config_parameters",
|
||||
ENTRY_ID: entry.entry_id,
|
||||
NODE_ID: node.node_id,
|
||||
}
|
||||
)
|
||||
msg = await ws_client.receive_json()
|
||||
|
||||
assert not msg["success"]
|
||||
assert msg["error"]["code"] == ERR_NOT_LOADED
|
||||
|
||||
|
||||
async def test_dump_view(integration, hass_client):
|
||||
"""Test the HTTP dump view."""
|
||||
@ -571,6 +785,18 @@ async def test_subscribe_logs(hass, integration, client, hass_ws_client):
|
||||
"timestamp": "time",
|
||||
}
|
||||
|
||||
# Test sending command with not loaded entry fails
|
||||
await hass.config_entries.async_unload(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
await ws_client.send_json(
|
||||
{ID: 2, TYPE: "zwave_js/subscribe_logs", ENTRY_ID: entry.entry_id}
|
||||
)
|
||||
msg = await ws_client.receive_json()
|
||||
|
||||
assert not msg["success"]
|
||||
assert msg["error"]["code"] == ERR_NOT_LOADED
|
||||
|
||||
|
||||
async def test_update_log_config(hass, client, integration, hass_ws_client):
|
||||
"""Test that the update_log_config WS API call works and that schema validation works."""
|
||||
@ -691,6 +917,23 @@ async def test_update_log_config(hass, client, integration, hass_ws_client):
|
||||
and "must be provided if logging to file" in msg["error"]["message"]
|
||||
)
|
||||
|
||||
# Test sending command with not loaded entry fails
|
||||
await hass.config_entries.async_unload(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
await ws_client.send_json(
|
||||
{
|
||||
ID: 7,
|
||||
TYPE: "zwave_js/update_log_config",
|
||||
ENTRY_ID: entry.entry_id,
|
||||
CONFIG: {LEVEL: "Error"},
|
||||
}
|
||||
)
|
||||
msg = await ws_client.receive_json()
|
||||
|
||||
assert not msg["success"]
|
||||
assert msg["error"]["code"] == ERR_NOT_LOADED
|
||||
|
||||
|
||||
async def test_get_log_config(hass, client, integration, hass_ws_client):
|
||||
"""Test that the get_log_config WS API call works."""
|
||||
@ -726,6 +969,22 @@ async def test_get_log_config(hass, client, integration, hass_ws_client):
|
||||
assert log_config["filename"] == "/test.txt"
|
||||
assert log_config["force_console"] is False
|
||||
|
||||
# Test sending command with not loaded entry fails
|
||||
await hass.config_entries.async_unload(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
await ws_client.send_json(
|
||||
{
|
||||
ID: 2,
|
||||
TYPE: "zwave_js/get_log_config",
|
||||
ENTRY_ID: entry.entry_id,
|
||||
}
|
||||
)
|
||||
msg = await ws_client.receive_json()
|
||||
|
||||
assert not msg["success"]
|
||||
assert msg["error"]["code"] == ERR_NOT_LOADED
|
||||
|
||||
|
||||
async def test_data_collection(hass, client, integration, hass_ws_client):
|
||||
"""Test that the data collection WS API commands work."""
|
||||
@ -794,3 +1053,32 @@ async def test_data_collection(hass, client, integration, hass_ws_client):
|
||||
assert not entry.data[CONF_DATA_COLLECTION_OPTED_IN]
|
||||
|
||||
client.async_send_command.reset_mock()
|
||||
|
||||
# Test sending command with not loaded entry fails
|
||||
await hass.config_entries.async_unload(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
await ws_client.send_json(
|
||||
{
|
||||
ID: 4,
|
||||
TYPE: "zwave_js/data_collection_status",
|
||||
ENTRY_ID: entry.entry_id,
|
||||
}
|
||||
)
|
||||
msg = await ws_client.receive_json()
|
||||
|
||||
assert not msg["success"]
|
||||
assert msg["error"]["code"] == ERR_NOT_LOADED
|
||||
|
||||
await ws_client.send_json(
|
||||
{
|
||||
ID: 5,
|
||||
TYPE: "zwave_js/update_data_collection_preference",
|
||||
ENTRY_ID: entry.entry_id,
|
||||
OPTED_IN: True,
|
||||
}
|
||||
)
|
||||
msg = await ws_client.receive_json()
|
||||
|
||||
assert not msg["success"]
|
||||
assert msg["error"]["code"] == ERR_NOT_LOADED
|
||||
|
@ -36,18 +36,33 @@ async def async_setup_platform(
|
||||
class MockLight(MockToggleEntity, LightEntity):
|
||||
"""Mock light class."""
|
||||
|
||||
brightness = None
|
||||
color_mode = None
|
||||
max_mireds = 500
|
||||
min_mireds = 153
|
||||
supported_color_modes = None
|
||||
supported_features = 0
|
||||
|
||||
color_mode = None
|
||||
|
||||
brightness = None
|
||||
color_temp = None
|
||||
hs_color = None
|
||||
xy_color = None
|
||||
rgb_color = None
|
||||
rgbw_color = None
|
||||
rgbww_color = None
|
||||
|
||||
color_temp = None
|
||||
|
||||
xy_color = None
|
||||
white_value = None
|
||||
|
||||
def turn_on(self, **kwargs):
|
||||
"""Turn the entity on."""
|
||||
super().turn_on(**kwargs)
|
||||
for key, value in kwargs.items():
|
||||
if key in [
|
||||
"brightness",
|
||||
"hs_color",
|
||||
"xy_color",
|
||||
"rgb_color",
|
||||
"rgbw_color",
|
||||
"rgbww_color",
|
||||
"color_temp",
|
||||
"white_value",
|
||||
]:
|
||||
setattr(self, key, value)
|
||||
|
Loading…
x
Reference in New Issue
Block a user