Merge pull request #50243 from home-assistant/rc

This commit is contained in:
Franck Nijhof 2021-05-07 19:03:00 +02:00 committed by GitHub
commit 808825dc42
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
53 changed files with 1503 additions and 373 deletions

View File

@ -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)

View File

@ -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."""

View File

@ -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"},
]

View File

@ -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"
}

View File

@ -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

View File

@ -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]

View File

@ -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()

View File

@ -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": [
{

View File

@ -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

View File

@ -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:

View File

@ -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

View File

@ -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:

View File

@ -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):

View File

@ -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)

View File

@ -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",

View File

@ -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):

View File

@ -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
)

View File

@ -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"]:

View File

@ -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]

View File

@ -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):

View File

@ -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]
]

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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
)

View File

@ -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)

View File

@ -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"

View File

@ -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

View File

@ -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"
}

View File

@ -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:

View File

@ -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"

View File

@ -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

View File

@ -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

View File

@ -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()

View File

@ -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():

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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"

View File

@ -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")

View File

@ -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(

View File

@ -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)

View File

@ -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()}

View File

@ -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,
},
]

View File

@ -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
)

View File

@ -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):

View File

@ -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."""

View File

@ -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(

View File

@ -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]

View File

@ -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",

View File

@ -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

View File

@ -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)