mirror of
https://github.com/home-assistant/core.git
synced 2025-07-30 08:47:09 +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 = [
|
sensors = [
|
||||||
BroadlinkSensor(device, monitored_condition)
|
BroadlinkSensor(device, monitored_condition)
|
||||||
for monitored_condition in sensor_data
|
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)
|
async_add_entities(sensors)
|
||||||
|
|
||||||
|
@ -117,11 +117,25 @@ class BroadlinkRMUpdateManager(BroadlinkUpdateManager):
|
|||||||
device = self.device
|
device = self.device
|
||||||
|
|
||||||
if hasattr(device.api, "check_sensors"):
|
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)
|
await device.async_request(device.api.update)
|
||||||
return {}
|
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):
|
class BroadlinkSP1UpdateManager(BroadlinkUpdateManager):
|
||||||
"""Manages updates for Broadlink SP1 devices."""
|
"""Manages updates for Broadlink SP1 devices."""
|
||||||
|
@ -6,6 +6,7 @@ from pyclimacell.const import (
|
|||||||
HealthConcernType,
|
HealthConcernType,
|
||||||
PollenIndex,
|
PollenIndex,
|
||||||
PrimaryPollutantType,
|
PrimaryPollutantType,
|
||||||
|
V3PollenIndex,
|
||||||
WeatherCode,
|
WeatherCode,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -307,8 +308,20 @@ CC_V3_SENSOR_TYPES = [
|
|||||||
ATTR_FIELD: CC_V3_ATTR_CHINA_HEALTH_CONCERN,
|
ATTR_FIELD: CC_V3_ATTR_CHINA_HEALTH_CONCERN,
|
||||||
ATTR_NAME: "China MEP 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_TREE,
|
||||||
{ATTR_FIELD: CC_V3_ATTR_POLLEN_GRASS, ATTR_NAME: "Grass Pollen Index"},
|
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"},
|
{ATTR_FIELD: CC_V3_ATTR_FIRE_INDEX, ATTR_NAME: "Fire Index"},
|
||||||
]
|
]
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"name": "ClimaCell",
|
"name": "ClimaCell",
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/climacell",
|
"documentation": "https://www.home-assistant.io/integrations/climacell",
|
||||||
"requirements": ["pyclimacell==0.18.0"],
|
"requirements": ["pyclimacell==0.18.2"],
|
||||||
"codeowners": ["@raman325"],
|
"codeowners": ["@raman325"],
|
||||||
"iot_class": "cloud_polling"
|
"iot_class": "cloud_polling"
|
||||||
}
|
}
|
||||||
|
@ -128,7 +128,7 @@ class BaseClimaCellSensorEntity(ClimaCellEntity, SensorEntity):
|
|||||||
):
|
):
|
||||||
return round(self._state * self.sensor_type[ATTR_METRIC_CONVERSION], 4)
|
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.sensor_type[ATTR_VALUE_MAP](self._state).name.lower()
|
||||||
return self._state
|
return self._state
|
||||||
|
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
"""Demo light platform that implements lights."""
|
"""Demo light platform that implements lights."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import random
|
import random
|
||||||
|
|
||||||
from homeassistant.components.light import (
|
from homeassistant.components.light import (
|
||||||
@ -6,12 +8,13 @@ from homeassistant.components.light import (
|
|||||||
ATTR_COLOR_TEMP,
|
ATTR_COLOR_TEMP,
|
||||||
ATTR_EFFECT,
|
ATTR_EFFECT,
|
||||||
ATTR_HS_COLOR,
|
ATTR_HS_COLOR,
|
||||||
ATTR_WHITE_VALUE,
|
ATTR_RGBW_COLOR,
|
||||||
SUPPORT_BRIGHTNESS,
|
ATTR_RGBWW_COLOR,
|
||||||
SUPPORT_COLOR,
|
COLOR_MODE_COLOR_TEMP,
|
||||||
SUPPORT_COLOR_TEMP,
|
COLOR_MODE_HS,
|
||||||
|
COLOR_MODE_RGBW,
|
||||||
|
COLOR_MODE_RGBWW,
|
||||||
SUPPORT_EFFECT,
|
SUPPORT_EFFECT,
|
||||||
SUPPORT_WHITE_VALUE,
|
|
||||||
LightEntity,
|
LightEntity,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -23,9 +26,7 @@ LIGHT_EFFECT_LIST = ["rainbow", "none"]
|
|||||||
|
|
||||||
LIGHT_TEMPS = [240, 380]
|
LIGHT_TEMPS = [240, 380]
|
||||||
|
|
||||||
SUPPORT_DEMO = (
|
SUPPORT_DEMO = {COLOR_MODE_HS, COLOR_MODE_COLOR_TEMP}
|
||||||
SUPPORT_BRIGHTNESS | SUPPORT_COLOR_TEMP | SUPPORT_COLOR | SUPPORT_WHITE_VALUE
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
|
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(
|
async_add_entities(
|
||||||
[
|
[
|
||||||
DemoLight(
|
DemoLight(
|
||||||
unique_id="light_1",
|
|
||||||
name="Bed Light",
|
|
||||||
state=False,
|
|
||||||
available=True,
|
available=True,
|
||||||
effect_list=LIGHT_EFFECT_LIST,
|
effect_list=LIGHT_EFFECT_LIST,
|
||||||
effect=LIGHT_EFFECT_LIST[0],
|
effect=LIGHT_EFFECT_LIST[0],
|
||||||
|
name="Bed Light",
|
||||||
|
state=False,
|
||||||
|
unique_id="light_1",
|
||||||
),
|
),
|
||||||
DemoLight(
|
DemoLight(
|
||||||
unique_id="light_2",
|
|
||||||
name="Ceiling Lights",
|
|
||||||
state=True,
|
|
||||||
available=True,
|
available=True,
|
||||||
ct=LIGHT_TEMPS[1],
|
ct=LIGHT_TEMPS[1],
|
||||||
|
name="Ceiling Lights",
|
||||||
|
state=True,
|
||||||
|
unique_id="light_2",
|
||||||
),
|
),
|
||||||
DemoLight(
|
DemoLight(
|
||||||
unique_id="light_3",
|
|
||||||
name="Kitchen Lights",
|
|
||||||
state=True,
|
|
||||||
available=True,
|
available=True,
|
||||||
hs_color=LIGHT_COLORS[1],
|
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,
|
name,
|
||||||
state,
|
state,
|
||||||
available=False,
|
available=False,
|
||||||
hs_color=None,
|
|
||||||
ct=None,
|
|
||||||
brightness=180,
|
brightness=180,
|
||||||
white=200,
|
ct=None,
|
||||||
effect_list=None,
|
effect_list=None,
|
||||||
effect=None,
|
effect=None,
|
||||||
|
hs_color=None,
|
||||||
|
rgbw_color=None,
|
||||||
|
rgbww_color=None,
|
||||||
|
supported_color_modes=None,
|
||||||
):
|
):
|
||||||
"""Initialize the light."""
|
"""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._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:
|
if self._effect_list is not None:
|
||||||
self._features |= SUPPORT_EFFECT
|
self._features |= SUPPORT_EFFECT
|
||||||
|
|
||||||
@ -134,24 +164,30 @@ class DemoLight(LightEntity):
|
|||||||
"""Return the brightness of this light between 0..255."""
|
"""Return the brightness of this light between 0..255."""
|
||||||
return self._brightness
|
return self._brightness
|
||||||
|
|
||||||
|
@property
|
||||||
|
def color_mode(self) -> str | None:
|
||||||
|
"""Return the color mode of the light."""
|
||||||
|
return self._color_mode
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def hs_color(self) -> tuple:
|
def hs_color(self) -> tuple:
|
||||||
"""Return the hs color value."""
|
"""Return the hs color value."""
|
||||||
if self._color_mode == "hs":
|
return self._hs_color
|
||||||
return self._hs_color
|
|
||||||
return None
|
@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
|
@property
|
||||||
def color_temp(self) -> int:
|
def color_temp(self) -> int:
|
||||||
"""Return the CT color temperature."""
|
"""Return the CT color temperature."""
|
||||||
if self._color_mode == "ct":
|
return self._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
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def effect_list(self) -> list:
|
def effect_list(self) -> list:
|
||||||
@ -173,24 +209,34 @@ class DemoLight(LightEntity):
|
|||||||
"""Flag supported features."""
|
"""Flag supported features."""
|
||||||
return self._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:
|
async def async_turn_on(self, **kwargs) -> None:
|
||||||
"""Turn the light on."""
|
"""Turn the light on."""
|
||||||
self._state = True
|
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:
|
if ATTR_HS_COLOR in kwargs:
|
||||||
self._color_mode = "hs"
|
self._color_mode = COLOR_MODE_HS
|
||||||
self._hs_color = kwargs[ATTR_HS_COLOR]
|
self._hs_color = kwargs[ATTR_HS_COLOR]
|
||||||
|
|
||||||
if ATTR_COLOR_TEMP in kwargs:
|
if ATTR_COLOR_TEMP in kwargs:
|
||||||
self._color_mode = "ct"
|
self._color_mode = COLOR_MODE_COLOR_TEMP
|
||||||
self._ct = kwargs[ATTR_COLOR_TEMP]
|
self._ct = kwargs[ATTR_COLOR_TEMP]
|
||||||
|
|
||||||
if ATTR_BRIGHTNESS in kwargs:
|
if ATTR_BRIGHTNESS in kwargs:
|
||||||
self._brightness = kwargs[ATTR_BRIGHTNESS]
|
self._brightness = kwargs[ATTR_BRIGHTNESS]
|
||||||
|
|
||||||
if ATTR_WHITE_VALUE in kwargs:
|
|
||||||
self._white = kwargs[ATTR_WHITE_VALUE]
|
|
||||||
|
|
||||||
if ATTR_EFFECT in kwargs:
|
if ATTR_EFFECT in kwargs:
|
||||||
self._effect = kwargs[ATTR_EFFECT]
|
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_ZONE2, DEFAULT_ZONE2),
|
||||||
entry.options.get(CONF_ZONE3, DEFAULT_ZONE3),
|
entry.options.get(CONF_ZONE3, DEFAULT_ZONE3),
|
||||||
lambda: get_async_client(hass),
|
lambda: get_async_client(hass),
|
||||||
entry.state,
|
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
await connect_denonavr.async_connect_receiver()
|
await connect_denonavr.async_connect_receiver()
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"name": "Denon AVR Network Receivers",
|
"name": "Denon AVR Network Receivers",
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/denonavr",
|
"documentation": "https://www.home-assistant.io/integrations/denonavr",
|
||||||
"requirements": ["denonavr==0.10.5"],
|
"requirements": ["denonavr==0.10.6"],
|
||||||
"codeowners": ["@scarface-4711", "@starkillerOG"],
|
"codeowners": ["@scarface-4711", "@starkillerOG"],
|
||||||
"ssdp": [
|
"ssdp": [
|
||||||
{
|
{
|
||||||
|
@ -246,7 +246,6 @@ class DenonDevice(MediaPlayerEntity):
|
|||||||
"manufacturer": self._config_entry.data[CONF_MANUFACTURER],
|
"manufacturer": self._config_entry.data[CONF_MANUFACTURER],
|
||||||
"name": self._config_entry.title,
|
"name": self._config_entry.title,
|
||||||
"model": f"{self._config_entry.data[CONF_MODEL]}-{self._config_entry.data[CONF_TYPE]}",
|
"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
|
return device_info
|
||||||
|
@ -20,7 +20,6 @@ class ConnectDenonAVR:
|
|||||||
zone2: bool,
|
zone2: bool,
|
||||||
zone3: bool,
|
zone3: bool,
|
||||||
async_client_getter: Callable,
|
async_client_getter: Callable,
|
||||||
entry_state: str | None = None,
|
|
||||||
):
|
):
|
||||||
"""Initialize the class."""
|
"""Initialize the class."""
|
||||||
self._async_client_getter = async_client_getter
|
self._async_client_getter = async_client_getter
|
||||||
@ -28,7 +27,6 @@ class ConnectDenonAVR:
|
|||||||
self._host = host
|
self._host = host
|
||||||
self._show_all_inputs = show_all_inputs
|
self._show_all_inputs = show_all_inputs
|
||||||
self._timeout = timeout
|
self._timeout = timeout
|
||||||
self._entry_state = entry_state
|
|
||||||
|
|
||||||
self._zones = {}
|
self._zones = {}
|
||||||
if zone2:
|
if zone2:
|
||||||
|
@ -12,6 +12,7 @@ import voluptuous as vol
|
|||||||
from homeassistant.components import light
|
from homeassistant.components import light
|
||||||
from homeassistant.components.light import (
|
from homeassistant.components.light import (
|
||||||
ATTR_BRIGHTNESS,
|
ATTR_BRIGHTNESS,
|
||||||
|
ATTR_COLOR_MODE,
|
||||||
ATTR_COLOR_TEMP,
|
ATTR_COLOR_TEMP,
|
||||||
ATTR_EFFECT,
|
ATTR_EFFECT,
|
||||||
ATTR_EFFECT_LIST,
|
ATTR_EFFECT_LIST,
|
||||||
@ -19,16 +20,20 @@ from homeassistant.components.light import (
|
|||||||
ATTR_HS_COLOR,
|
ATTR_HS_COLOR,
|
||||||
ATTR_MAX_MIREDS,
|
ATTR_MAX_MIREDS,
|
||||||
ATTR_MIN_MIREDS,
|
ATTR_MIN_MIREDS,
|
||||||
|
ATTR_RGB_COLOR,
|
||||||
|
ATTR_RGBW_COLOR,
|
||||||
|
ATTR_RGBWW_COLOR,
|
||||||
|
ATTR_SUPPORTED_COLOR_MODES,
|
||||||
ATTR_TRANSITION,
|
ATTR_TRANSITION,
|
||||||
ATTR_WHITE_VALUE,
|
ATTR_WHITE_VALUE,
|
||||||
|
ATTR_XY_COLOR,
|
||||||
PLATFORM_SCHEMA,
|
PLATFORM_SCHEMA,
|
||||||
SUPPORT_BRIGHTNESS,
|
|
||||||
SUPPORT_COLOR,
|
|
||||||
SUPPORT_COLOR_TEMP,
|
|
||||||
SUPPORT_EFFECT,
|
SUPPORT_EFFECT,
|
||||||
SUPPORT_FLASH,
|
SUPPORT_FLASH,
|
||||||
SUPPORT_TRANSITION,
|
SUPPORT_TRANSITION,
|
||||||
SUPPORT_WHITE_VALUE,
|
SUPPORT_WHITE_VALUE,
|
||||||
|
color_supported,
|
||||||
|
color_temp_supported,
|
||||||
)
|
)
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_ENTITY_ID,
|
ATTR_ENTITY_ID,
|
||||||
@ -59,13 +64,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
|||||||
)
|
)
|
||||||
|
|
||||||
SUPPORT_GROUP_LIGHT = (
|
SUPPORT_GROUP_LIGHT = (
|
||||||
SUPPORT_BRIGHTNESS
|
SUPPORT_EFFECT | SUPPORT_FLASH | SUPPORT_TRANSITION | SUPPORT_WHITE_VALUE
|
||||||
| SUPPORT_COLOR_TEMP
|
|
||||||
| SUPPORT_EFFECT
|
|
||||||
| SUPPORT_FLASH
|
|
||||||
| SUPPORT_COLOR
|
|
||||||
| SUPPORT_TRANSITION
|
|
||||||
| SUPPORT_WHITE_VALUE
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -89,13 +88,19 @@ class LightGroup(GroupEntity, light.LightEntity):
|
|||||||
self._available = False
|
self._available = False
|
||||||
self._icon = "mdi:lightbulb-group"
|
self._icon = "mdi:lightbulb-group"
|
||||||
self._brightness: int | None = None
|
self._brightness: int | None = None
|
||||||
|
self._color_mode: str | None = None
|
||||||
self._hs_color: tuple[float, float] | 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._color_temp: int | None = None
|
||||||
self._min_mireds: int = 154
|
self._min_mireds: int = 154
|
||||||
self._max_mireds: int = 500
|
self._max_mireds: int = 500
|
||||||
self._white_value: int | None = None
|
self._white_value: int | None = None
|
||||||
self._effect_list: list[str] | None = None
|
self._effect_list: list[str] | None = None
|
||||||
self._effect: str | None = None
|
self._effect: str | None = None
|
||||||
|
self._supported_color_modes: set[str] | None = None
|
||||||
self._supported_features: int = 0
|
self._supported_features: int = 0
|
||||||
|
|
||||||
async def async_added_to_hass(self) -> None:
|
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 the brightness of this light group between 0..255."""
|
||||||
return self._brightness
|
return self._brightness
|
||||||
|
|
||||||
|
@property
|
||||||
|
def color_mode(self) -> str | None:
|
||||||
|
"""Return the color mode of the light."""
|
||||||
|
return self._color_mode
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def hs_color(self) -> tuple[float, float] | None:
|
def hs_color(self) -> tuple[float, float] | None:
|
||||||
"""Return the HS color value [float, float]."""
|
"""Return the HS color value [float, float]."""
|
||||||
return self._hs_color
|
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
|
@property
|
||||||
def color_temp(self) -> int | None:
|
def color_temp(self) -> int | None:
|
||||||
"""Return the CT color value in mireds."""
|
"""Return the CT color value in mireds."""
|
||||||
@ -178,6 +208,11 @@ class LightGroup(GroupEntity, light.LightEntity):
|
|||||||
"""Return the current effect."""
|
"""Return the current effect."""
|
||||||
return self._effect
|
return self._effect
|
||||||
|
|
||||||
|
@property
|
||||||
|
def supported_color_modes(self) -> set | None:
|
||||||
|
"""Flag supported color modes."""
|
||||||
|
return self._supported_color_modes
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def supported_features(self) -> int:
|
def supported_features(self) -> int:
|
||||||
"""Flag supported features."""
|
"""Flag supported features."""
|
||||||
@ -204,6 +239,18 @@ class LightGroup(GroupEntity, light.LightEntity):
|
|||||||
if ATTR_HS_COLOR in kwargs:
|
if ATTR_HS_COLOR in kwargs:
|
||||||
data[ATTR_HS_COLOR] = kwargs[ATTR_HS_COLOR]
|
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:
|
if ATTR_COLOR_TEMP in kwargs:
|
||||||
data[ATTR_COLOR_TEMP] = kwargs[ATTR_COLOR_TEMP]
|
data[ATTR_COLOR_TEMP] = kwargs[ATTR_COLOR_TEMP]
|
||||||
|
|
||||||
@ -215,11 +262,9 @@ class LightGroup(GroupEntity, light.LightEntity):
|
|||||||
state = self.hass.states.get(entity_id)
|
state = self.hass.states.get(entity_id)
|
||||||
if not state:
|
if not state:
|
||||||
continue
|
continue
|
||||||
support = state.attributes.get(ATTR_SUPPORTED_FEATURES)
|
support = state.attributes.get(ATTR_SUPPORTED_COLOR_MODES)
|
||||||
# Only pass color temperature to supported entity_ids
|
# Only pass color temperature to supported entity_ids
|
||||||
if bool(support & SUPPORT_COLOR) and not bool(
|
if color_supported(support) and not color_temp_supported(support):
|
||||||
support & SUPPORT_COLOR_TEMP
|
|
||||||
):
|
|
||||||
emulate_color_temp_entity_ids.append(entity_id)
|
emulate_color_temp_entity_ids.append(entity_id)
|
||||||
updated_entities.remove(entity_id)
|
updated_entities.remove(entity_id)
|
||||||
data[ATTR_ENTITY_ID] = updated_entities
|
data[ATTR_ENTITY_ID] = updated_entities
|
||||||
@ -300,6 +345,16 @@ class LightGroup(GroupEntity, light.LightEntity):
|
|||||||
self._brightness = _reduce_attribute(on_states, ATTR_BRIGHTNESS)
|
self._brightness = _reduce_attribute(on_states, ATTR_BRIGHTNESS)
|
||||||
|
|
||||||
self._hs_color = _reduce_attribute(on_states, ATTR_HS_COLOR, reduce=_mean_tuple)
|
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)
|
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))
|
effects_count = Counter(itertools.chain(all_effects))
|
||||||
self._effect = effects_count.most_common(1)[0][0]
|
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
|
self._supported_features = 0
|
||||||
for support in _find_state_attributes(states, ATTR_SUPPORTED_FEATURES):
|
for support in _find_state_attributes(states, ATTR_SUPPORTED_FEATURES):
|
||||||
# Merge supported features by emulating support for every feature
|
# Merge supported features by emulating support for every feature
|
||||||
|
@ -34,7 +34,7 @@ set:
|
|||||||
object:
|
object:
|
||||||
add_entities:
|
add_entities:
|
||||||
name: 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
|
example: domain.entity_id1, domain.entity_id2
|
||||||
selector:
|
selector:
|
||||||
object:
|
object:
|
||||||
@ -55,5 +55,4 @@ remove:
|
|||||||
required: true
|
required: true
|
||||||
example: "test_group"
|
example: "test_group"
|
||||||
selector:
|
selector:
|
||||||
entity:
|
object:
|
||||||
domain: group
|
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
"""Hue binary sensor entities."""
|
"""Hue binary sensor entities."""
|
||||||
|
|
||||||
from aiohue.sensors import TYPE_ZLL_PRESENCE
|
from aiohue.sensors import TYPE_ZLL_PRESENCE
|
||||||
|
|
||||||
from homeassistant.components.binary_sensor import (
|
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):
|
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||||
"""Defer binary sensor setup to the shared sensor module."""
|
"""Defer binary sensor setup to the shared sensor module."""
|
||||||
await hass.data[HUE_DOMAIN][
|
bridge = hass.data[HUE_DOMAIN][config_entry.entry_id]
|
||||||
config_entry.entry_id
|
|
||||||
].sensor_manager.async_register_component("binary_sensor", async_add_entities)
|
if not bridge.sensor_manager:
|
||||||
|
return
|
||||||
|
|
||||||
|
await bridge.sensor_manager.async_register_component(
|
||||||
|
"binary_sensor", async_add_entities
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class HuePresence(GenericZLLSensor, BinarySensorEntity):
|
class HuePresence(GenericZLLSensor, BinarySensorEntity):
|
||||||
|
@ -102,7 +102,8 @@ class HueBridge:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
self.api = bridge
|
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)
|
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):
|
async def hue_activate_scene(self, data, skip_reload=False, hide_warnings=False):
|
||||||
"""Service to call directly into bridge to set scenes."""
|
"""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]
|
group_name = data[ATTR_GROUP_NAME]
|
||||||
scene_name = data[ATTR_SCENE_NAME]
|
scene_name = data[ATTR_SCENE_NAME]
|
||||||
transition = data.get(ATTR_TRANSITION)
|
transition = data.get(ATTR_TRANSITION)
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"name": "Philips Hue",
|
"name": "Philips Hue",
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/hue",
|
"documentation": "https://www.home-assistant.io/integrations/hue",
|
||||||
"requirements": ["aiohue==2.1.0"],
|
"requirements": ["aiohue==2.3.0"],
|
||||||
"ssdp": [
|
"ssdp": [
|
||||||
{
|
{
|
||||||
"manufacturer": "Royal Philips Electronics",
|
"manufacturer": "Royal Philips Electronics",
|
||||||
|
@ -26,9 +26,12 @@ TEMPERATURE_NAME_FORMAT = "{} temperature"
|
|||||||
|
|
||||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||||
"""Defer sensor setup to the shared sensor module."""
|
"""Defer sensor setup to the shared sensor module."""
|
||||||
await hass.data[HUE_DOMAIN][
|
bridge = hass.data[HUE_DOMAIN][config_entry.entry_id]
|
||||||
config_entry.entry_id
|
|
||||||
].sensor_manager.async_register_component("sensor", async_add_entities)
|
if not bridge.sensor_manager:
|
||||||
|
return
|
||||||
|
|
||||||
|
await bridge.sensor_manager.async_register_component("sensor", async_add_entities)
|
||||||
|
|
||||||
|
|
||||||
class GenericHueGaugeSensorEntity(GenericZLLSensor, SensorEntity):
|
class GenericHueGaugeSensorEntity(GenericZLLSensor, SensorEntity):
|
||||||
|
@ -343,7 +343,7 @@ async def async_setup(hass, config): # noqa: C901
|
|||||||
rgb_color = params.pop(ATTR_RGB_COLOR)
|
rgb_color = params.pop(ATTR_RGB_COLOR)
|
||||||
if COLOR_MODE_RGBW in supported_color_modes:
|
if COLOR_MODE_RGBW in supported_color_modes:
|
||||||
params[ATTR_RGBW_COLOR] = color_util.color_rgb_to_rgbw(*rgb_color)
|
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(
|
params[ATTR_RGBWW_COLOR] = color_util.color_rgb_to_rgbww(
|
||||||
*rgb_color, light.min_mireds, light.max_mireds
|
*rgb_color, light.min_mireds, light.max_mireds
|
||||||
)
|
)
|
||||||
|
@ -344,6 +344,9 @@ class MqttFan(MqttEntity, FanEntity):
|
|||||||
def state_received(msg):
|
def state_received(msg):
|
||||||
"""Handle new received MQTT message."""
|
"""Handle new received MQTT message."""
|
||||||
payload = self._value_templates[CONF_STATE](msg.payload)
|
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"]:
|
if payload == self._payload["STATE_ON"]:
|
||||||
self._state = True
|
self._state = True
|
||||||
elif payload == self._payload["STATE_OFF"]:
|
elif payload == self._payload["STATE_OFF"]:
|
||||||
@ -362,22 +365,27 @@ class MqttFan(MqttEntity, FanEntity):
|
|||||||
def percentage_received(msg):
|
def percentage_received(msg):
|
||||||
"""Handle new received MQTT message for the percentage."""
|
"""Handle new received MQTT message for the percentage."""
|
||||||
numeric_val_str = self._value_templates[ATTR_PERCENTAGE](msg.payload)
|
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:
|
try:
|
||||||
percentage = ranged_value_to_percentage(
|
percentage = ranged_value_to_percentage(
|
||||||
self._speed_range, int(numeric_val_str)
|
self._speed_range, int(numeric_val_str)
|
||||||
)
|
)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
_LOGGER.warning(
|
_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.payload,
|
||||||
msg.topic,
|
msg.topic,
|
||||||
|
numeric_val_str,
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
if percentage < 0 or percentage > 100:
|
if percentage < 0 or percentage > 100:
|
||||||
_LOGGER.warning(
|
_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.payload,
|
||||||
msg.topic,
|
msg.topic,
|
||||||
|
numeric_val_str,
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
self._percentage = percentage
|
self._percentage = percentage
|
||||||
@ -396,11 +404,15 @@ class MqttFan(MqttEntity, FanEntity):
|
|||||||
def preset_mode_received(msg):
|
def preset_mode_received(msg):
|
||||||
"""Handle new received MQTT message for preset mode."""
|
"""Handle new received MQTT message for preset mode."""
|
||||||
preset_mode = self._value_templates[ATTR_PRESET_MODE](msg.payload)
|
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:
|
if preset_mode not in self.preset_modes:
|
||||||
_LOGGER.warning(
|
_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.payload,
|
||||||
msg.topic,
|
msg.topic,
|
||||||
|
preset_mode,
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -436,9 +448,10 @@ class MqttFan(MqttEntity, FanEntity):
|
|||||||
self._speed = speed
|
self._speed = speed
|
||||||
else:
|
else:
|
||||||
_LOGGER.warning(
|
_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.payload,
|
||||||
msg.topic,
|
msg.topic,
|
||||||
|
speed,
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -464,6 +477,9 @@ class MqttFan(MqttEntity, FanEntity):
|
|||||||
def oscillation_received(msg):
|
def oscillation_received(msg):
|
||||||
"""Handle new received MQTT message for the oscillation."""
|
"""Handle new received MQTT message for the oscillation."""
|
||||||
payload = self._value_templates[ATTR_OSCILLATING](msg.payload)
|
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"]:
|
if payload == self._payload["OSCILLATE_ON_PAYLOAD"]:
|
||||||
self._oscillation = True
|
self._oscillation = True
|
||||||
elif payload == self._payload["OSCILLATE_OFF_PAYLOAD"]:
|
elif payload == self._payload["OSCILLATE_OFF_PAYLOAD"]:
|
||||||
|
@ -58,18 +58,23 @@ DEFAULT_TCP_PORT = 5003
|
|||||||
DEFAULT_VERSION = "1.4"
|
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):
|
def has_all_unique_files(value):
|
||||||
"""Validate that all persistence files are unique and set if any is set."""
|
"""Validate that all persistence files are unique and set if any is set."""
|
||||||
persistence_files = [gateway.get(CONF_PERSISTENCE_FILE) for gateway in value]
|
persistence_files = [gateway[CONF_PERSISTENCE_FILE] for gateway in value]
|
||||||
if None in persistence_files and any(
|
schema = vol.Schema(vol.Unique())
|
||||||
name is not None for name in persistence_files
|
schema(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)
|
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
@ -128,7 +133,10 @@ CONFIG_SCHEMA = vol.Schema(
|
|||||||
deprecated(CONF_PERSISTENCE),
|
deprecated(CONF_PERSISTENCE),
|
||||||
{
|
{
|
||||||
vol.Required(CONF_GATEWAYS): vol.All(
|
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_RETAIN, default=True): cv.boolean,
|
||||||
vol.Optional(CONF_VERSION, default=DEFAULT_VERSION): cv.string,
|
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_TOPIC_IN_PREFIX: gw.get(CONF_TOPIC_IN_PREFIX, ""),
|
||||||
CONF_RETAIN: config[CONF_RETAIN],
|
CONF_RETAIN: config[CONF_RETAIN],
|
||||||
CONF_VERSION: config[CONF_VERSION],
|
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.
|
# nodes config ignored at this time. renaming nodes can now be done from the frontend.
|
||||||
}
|
}
|
||||||
for gw in config[CONF_GATEWAYS]
|
for gw in config[CONF_GATEWAYS]
|
||||||
|
@ -324,7 +324,9 @@ class MySensorsConfigFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
except vol.Invalid:
|
except vol.Invalid:
|
||||||
errors[CONF_PERSISTENCE_FILE] = "invalid_persistence_file"
|
errors[CONF_PERSISTENCE_FILE] = "invalid_persistence_file"
|
||||||
else:
|
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]
|
user_input[CONF_PERSISTENCE_FILE]
|
||||||
)
|
)
|
||||||
for other_entry in self.hass.config_entries.async_entries(DOMAIN):
|
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 [
|
return [
|
||||||
home_data.homes[home_id]["id"]
|
home_data.homes[home_id]["id"]
|
||||||
for home_id in home_data.homes
|
for home_id in home_data.homes
|
||||||
if (
|
if "modules" in home_data.homes[home_id]
|
||||||
"therm_schedules" in home_data.homes[home_id]
|
|
||||||
and "modules" in home_data.homes[home_id]
|
|
||||||
)
|
|
||||||
]
|
]
|
||||||
|
@ -10,7 +10,7 @@ from requests.exceptions import RequestException
|
|||||||
|
|
||||||
from homeassistant import exceptions
|
from homeassistant import exceptions
|
||||||
from homeassistant.config_entries import SOURCE_IMPORT
|
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 (
|
from homeassistant.helpers.update_coordinator import (
|
||||||
CoordinatorEntity,
|
CoordinatorEntity,
|
||||||
DataUpdateCoordinator,
|
DataUpdateCoordinator,
|
||||||
@ -22,6 +22,7 @@ from .const import (
|
|||||||
DATA_COORDINATOR,
|
DATA_COORDINATOR,
|
||||||
DATA_LOCKS,
|
DATA_LOCKS,
|
||||||
DATA_OPENERS,
|
DATA_OPENERS,
|
||||||
|
DEFAULT_PORT,
|
||||||
DEFAULT_TIMEOUT,
|
DEFAULT_TIMEOUT,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
ERROR_STATES,
|
ERROR_STATES,
|
||||||
@ -59,11 +60,18 @@ async def async_setup(hass, config):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
for conf in confs:
|
for conf in confs:
|
||||||
hass.async_create_task(
|
if CONF_PLATFORM in conf and conf[CONF_PLATFORM] == DOMAIN:
|
||||||
hass.config_entries.flow.async_init(
|
hass.async_create_task(
|
||||||
DOMAIN, context={"source": SOURCE_IMPORT}, data=conf
|
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
|
return True
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry):
|
|||||||
except CannotConnect as exc:
|
except CannotConnect as exc:
|
||||||
raise ConfigEntryNotReady() from 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:
|
async def cleanup_registry() -> None:
|
||||||
# Get registries
|
# Get registries
|
||||||
@ -71,5 +71,5 @@ async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry):
|
|||||||
config_entry, PLATFORMS
|
config_entry, PLATFORMS
|
||||||
)
|
)
|
||||||
if unload_ok:
|
if unload_ok:
|
||||||
hass.data[DOMAIN].pop(config_entry.unique_id)
|
hass.data[DOMAIN].pop(config_entry.entry_id)
|
||||||
return unload_ok
|
return unload_ok
|
||||||
|
@ -81,7 +81,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
|||||||
"""Set up 1-Wire platform."""
|
"""Set up 1-Wire platform."""
|
||||||
# Only OWServer implementation works with binary sensors
|
# Only OWServer implementation works with binary sensors
|
||||||
if config_entry.data[CONF_TYPE] == CONF_TYPE_OWSERVER:
|
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)
|
entities = await hass.async_add_executor_job(get_entities, onewirehub)
|
||||||
async_add_entities(entities, True)
|
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):
|
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||||
"""Set up 1-Wire platform."""
|
"""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(
|
entities = await hass.async_add_executor_job(
|
||||||
get_entities, onewirehub, config_entry.data
|
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."""
|
"""Set up 1-Wire platform."""
|
||||||
# Only OWServer implementation works with switches
|
# Only OWServer implementation works with switches
|
||||||
if config_entry.data[CONF_TYPE] == CONF_TYPE_OWSERVER:
|
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)
|
entities = await hass.async_add_executor_job(get_entities, onewirehub)
|
||||||
async_add_entities(entities, True)
|
async_add_entities(entities, True)
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
"domain": "recorder",
|
"domain": "recorder",
|
||||||
"name": "Recorder",
|
"name": "Recorder",
|
||||||
"documentation": "https://www.home-assistant.io/integrations/recorder",
|
"documentation": "https://www.home-assistant.io/integrations/recorder",
|
||||||
"requirements": ["sqlalchemy==1.4.11"],
|
"requirements": ["sqlalchemy==1.4.13"],
|
||||||
"codeowners": [],
|
"codeowners": [],
|
||||||
"quality_scale": "internal",
|
"quality_scale": "internal",
|
||||||
"iot_class": "local_push"
|
"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)
|
_async_save_refresh_token(hass, config_entry, api.refresh_token)
|
||||||
|
|
||||||
simplisafe = hass.data[DOMAIN][DATA_CLIENT][config_entry.entry_id] = SimpliSafe(
|
simplisafe = SimpliSafe(hass, api, config_entry)
|
||||||
hass, api, config_entry
|
|
||||||
)
|
|
||||||
await simplisafe.async_init()
|
|
||||||
|
|
||||||
|
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)
|
hass.config_entries.async_setup_platforms(config_entry, PLATFORMS)
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
"domain": "sql",
|
"domain": "sql",
|
||||||
"name": "SQL",
|
"name": "SQL",
|
||||||
"documentation": "https://www.home-assistant.io/integrations/sql",
|
"documentation": "https://www.home-assistant.io/integrations/sql",
|
||||||
"requirements": ["sqlalchemy==1.4.11"],
|
"requirements": ["sqlalchemy==1.4.13"],
|
||||||
"codeowners": ["@dgomes"],
|
"codeowners": ["@dgomes"],
|
||||||
"iot_class": "local_polling"
|
"iot_class": "local_polling"
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,7 @@ from homeassistant.const import (
|
|||||||
HTTP_UNAUTHORIZED,
|
HTTP_UNAUTHORIZED,
|
||||||
)
|
)
|
||||||
from homeassistant.core import callback
|
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 import config_validation as cv
|
||||||
from homeassistant.helpers.httpx_client import SERVER_SOFTWARE, USER_AGENT
|
from homeassistant.helpers.httpx_client import SERVER_SOFTWARE, USER_AGENT
|
||||||
from homeassistant.helpers.update_coordinator import (
|
from homeassistant.helpers.update_coordinator import (
|
||||||
@ -170,6 +170,9 @@ async def async_setup_entry(hass, config_entry):
|
|||||||
except IncompleteCredentials as ex:
|
except IncompleteCredentials as ex:
|
||||||
await async_client.aclose()
|
await async_client.aclose()
|
||||||
raise ConfigEntryAuthFailed from ex
|
raise ConfigEntryAuthFailed from ex
|
||||||
|
except httpx.ConnectTimeout as ex:
|
||||||
|
await async_client.aclose()
|
||||||
|
raise ConfigEntryNotReady from ex
|
||||||
except TeslaException as ex:
|
except TeslaException as ex:
|
||||||
await async_client.aclose()
|
await async_client.aclose()
|
||||||
if ex.code == HTTP_UNAUTHORIZED:
|
if ex.code == HTTP_UNAUTHORIZED:
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"name": "Xiaomi Miio",
|
"name": "Xiaomi Miio",
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/xiaomi_miio",
|
"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"],
|
"codeowners": ["@rytilahti", "@syssi", "@starkillerOG"],
|
||||||
"zeroconf": ["_miio._udp.local."],
|
"zeroconf": ["_miio._udp.local."],
|
||||||
"iot_class": "local_polling"
|
"iot_class": "local_polling"
|
||||||
|
@ -61,7 +61,7 @@ from .core.const import (
|
|||||||
)
|
)
|
||||||
from .core.group import GroupMember
|
from .core.group import GroupMember
|
||||||
from .core.helpers import (
|
from .core.helpers import (
|
||||||
async_input_cluster_exists,
|
async_cluster_exists,
|
||||||
async_is_bindable_target,
|
async_is_bindable_target,
|
||||||
convert_install_code,
|
convert_install_code,
|
||||||
get_matched_clusters,
|
get_matched_clusters,
|
||||||
@ -897,7 +897,7 @@ async def websocket_get_configuration(hass, connection, msg):
|
|||||||
|
|
||||||
data = {"schemas": {}, "data": {}}
|
data = {"schemas": {}, "data": {}}
|
||||||
for section, schema in ZHA_CONFIG_SCHEMAS.items():
|
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
|
hass, IasAce.cluster_id
|
||||||
):
|
):
|
||||||
continue
|
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."""
|
"""Determine if a device containing the specified in cluster is paired."""
|
||||||
zha_gateway = hass.data[DATA_ZHA][DATA_ZHA_GATEWAY]
|
zha_gateway = hass.data[DATA_ZHA][DATA_ZHA_GATEWAY]
|
||||||
zha_devices = zha_gateway.devices.values()
|
zha_devices = zha_gateway.devices.values()
|
||||||
for zha_device in zha_devices:
|
for zha_device in zha_devices:
|
||||||
clusters_by_endpoint = zha_device.async_get_clusters()
|
clusters_by_endpoint = zha_device.async_get_clusters()
|
||||||
for clusters in clusters_by_endpoint.values():
|
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 True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@ -83,7 +83,8 @@ SINGLE_INPUT_CLUSTER_DEVICE_CLASS = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SINGLE_OUTPUT_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()
|
BINDABLE_CLUSTERS = SetRegistry()
|
||||||
|
@ -25,7 +25,7 @@ from homeassistant.components.websocket_api.const import (
|
|||||||
ERR_NOT_SUPPORTED,
|
ERR_NOT_SUPPORTED,
|
||||||
ERR_UNKNOWN_ERROR,
|
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.const import CONF_URL
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.helpers import config_validation as cv
|
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
|
# general API constants
|
||||||
ID = "id"
|
ID = "id"
|
||||||
ENTRY_ID = "entry_id"
|
ENTRY_ID = "entry_id"
|
||||||
|
ERR_NOT_LOADED = "not_loaded"
|
||||||
NODE_ID = "node_id"
|
NODE_ID = "node_id"
|
||||||
COMMAND_CLASS_ID = "command_class_id"
|
COMMAND_CLASS_ID = "command_class_id"
|
||||||
TYPE = "type"
|
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"
|
msg[ID], ERR_NOT_FOUND, f"Config entry {entry_id} not found"
|
||||||
)
|
)
|
||||||
return
|
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]
|
client = hass.data[DOMAIN][entry_id][DATA_CLIENT]
|
||||||
await orig_func(hass, connection, msg, entry, 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
|
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(
|
@websocket_api.websocket_command(
|
||||||
{vol.Required(TYPE): "zwave_js/network_status", vol.Required(ENTRY_ID): str}
|
{vol.Required(TYPE): "zwave_js/network_status", vol.Required(ENTRY_ID): str}
|
||||||
)
|
)
|
||||||
@callback
|
@async_get_entry
|
||||||
def websocket_network_status(
|
async def websocket_network_status(
|
||||||
hass: HomeAssistant, connection: ActiveConnection, msg: dict
|
hass: HomeAssistant,
|
||||||
|
connection: ActiveConnection,
|
||||||
|
msg: dict,
|
||||||
|
entry: ConfigEntry,
|
||||||
|
client: Client,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Get the status of the Z-Wave JS network."""
|
"""Get the status of the Z-Wave JS network."""
|
||||||
entry_id = msg[ENTRY_ID]
|
|
||||||
client = hass.data[DOMAIN][entry_id][DATA_CLIENT]
|
|
||||||
data = {
|
data = {
|
||||||
"client": {
|
"client": {
|
||||||
"ws_server_url": client.ws_server_url,
|
"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(
|
@websocket_api.websocket_command(
|
||||||
{
|
{
|
||||||
vol.Required(TYPE): "zwave_js/node_status",
|
vol.Required(TYPE): "zwave_js/node_status",
|
||||||
@ -173,20 +185,14 @@ def websocket_network_status(
|
|||||||
vol.Required(NODE_ID): int,
|
vol.Required(NODE_ID): int,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@callback
|
@async_get_node
|
||||||
def websocket_node_status(
|
async def websocket_node_status(
|
||||||
hass: HomeAssistant, connection: ActiveConnection, msg: dict
|
hass: HomeAssistant,
|
||||||
|
connection: ActiveConnection,
|
||||||
|
msg: dict,
|
||||||
|
node: Node,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Get the status of a Z-Wave JS node."""
|
"""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 = {
|
data = {
|
||||||
"node_id": node.node_id,
|
"node_id": node.node_id,
|
||||||
"is_routing": node.is_routing,
|
"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(
|
@websocket_api.websocket_command(
|
||||||
{
|
{
|
||||||
vol.Required(TYPE): "zwave_js/get_config_parameters",
|
vol.Required(TYPE): "zwave_js/get_config_parameters",
|
||||||
@ -545,20 +552,11 @@ async def websocket_set_config_parameter(
|
|||||||
vol.Required(NODE_ID): int,
|
vol.Required(NODE_ID): int,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@callback
|
@async_get_node
|
||||||
def websocket_get_config_parameters(
|
async def websocket_get_config_parameters(
|
||||||
hass: HomeAssistant, connection: ActiveConnection, msg: dict
|
hass: HomeAssistant, connection: ActiveConnection, msg: dict, node: Node
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Get a list of configuration parameters for a Z-Wave node."""
|
"""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()
|
values = node.get_configuration_values()
|
||||||
result = {}
|
result = {}
|
||||||
for value_id, zwave_value in values.items():
|
for value_id, zwave_value in values.items():
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
"""Constants used by Home Assistant components."""
|
"""Constants used by Home Assistant components."""
|
||||||
MAJOR_VERSION = 2021
|
MAJOR_VERSION = 2021
|
||||||
MINOR_VERSION = 5
|
MINOR_VERSION = 5
|
||||||
PATCH_VERSION = "0"
|
PATCH_VERSION = "1"
|
||||||
__short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
__short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
||||||
__version__ = f"{__short_version__}.{PATCH_VERSION}"
|
__version__ = f"{__short_version__}.{PATCH_VERSION}"
|
||||||
REQUIRED_PYTHON_VER = (3, 8, 0)
|
REQUIRED_PYTHON_VER = (3, 8, 0)
|
||||||
|
@ -30,7 +30,7 @@ pyyaml==5.4.1
|
|||||||
requests==2.25.1
|
requests==2.25.1
|
||||||
ruamel.yaml==0.15.100
|
ruamel.yaml==0.15.100
|
||||||
scapy==2.4.5
|
scapy==2.4.5
|
||||||
sqlalchemy==1.4.11
|
sqlalchemy==1.4.13
|
||||||
voluptuous-serialize==2.4.0
|
voluptuous-serialize==2.4.0
|
||||||
voluptuous==0.12.1
|
voluptuous==0.12.1
|
||||||
yarl==1.6.3
|
yarl==1.6.3
|
||||||
|
@ -182,7 +182,7 @@ aiohomekit==0.2.61
|
|||||||
aiohttp_cors==0.7.0
|
aiohttp_cors==0.7.0
|
||||||
|
|
||||||
# homeassistant.components.hue
|
# homeassistant.components.hue
|
||||||
aiohue==2.1.0
|
aiohue==2.3.0
|
||||||
|
|
||||||
# homeassistant.components.imap
|
# homeassistant.components.imap
|
||||||
aioimaplib==0.7.15
|
aioimaplib==0.7.15
|
||||||
@ -479,7 +479,7 @@ defusedxml==0.6.0
|
|||||||
deluge-client==1.7.1
|
deluge-client==1.7.1
|
||||||
|
|
||||||
# homeassistant.components.denonavr
|
# homeassistant.components.denonavr
|
||||||
denonavr==0.10.5
|
denonavr==0.10.6
|
||||||
|
|
||||||
# homeassistant.components.devolo_home_control
|
# homeassistant.components.devolo_home_control
|
||||||
devolo-home-control-api==0.17.3
|
devolo-home-control-api==0.17.3
|
||||||
@ -1322,7 +1322,7 @@ pychromecast==9.1.2
|
|||||||
pycketcasts==1.0.0
|
pycketcasts==1.0.0
|
||||||
|
|
||||||
# homeassistant.components.climacell
|
# homeassistant.components.climacell
|
||||||
pyclimacell==0.18.0
|
pyclimacell==0.18.2
|
||||||
|
|
||||||
# homeassistant.components.cmus
|
# homeassistant.components.cmus
|
||||||
pycmus==0.1.1
|
pycmus==0.1.1
|
||||||
@ -1819,7 +1819,7 @@ python-juicenet==1.0.1
|
|||||||
# python-lirc==1.2.3
|
# python-lirc==1.2.3
|
||||||
|
|
||||||
# homeassistant.components.xiaomi_miio
|
# homeassistant.components.xiaomi_miio
|
||||||
python-miio==0.5.5
|
python-miio==0.5.6
|
||||||
|
|
||||||
# homeassistant.components.mpd
|
# homeassistant.components.mpd
|
||||||
python-mpd2==3.0.4
|
python-mpd2==3.0.4
|
||||||
@ -2142,7 +2142,7 @@ spotipy==2.18.0
|
|||||||
|
|
||||||
# homeassistant.components.recorder
|
# homeassistant.components.recorder
|
||||||
# homeassistant.components.sql
|
# homeassistant.components.sql
|
||||||
sqlalchemy==1.4.11
|
sqlalchemy==1.4.13
|
||||||
|
|
||||||
# homeassistant.components.srp_energy
|
# homeassistant.components.srp_energy
|
||||||
srpenergy==1.3.2
|
srpenergy==1.3.2
|
||||||
|
@ -119,7 +119,7 @@ aiohomekit==0.2.61
|
|||||||
aiohttp_cors==0.7.0
|
aiohttp_cors==0.7.0
|
||||||
|
|
||||||
# homeassistant.components.hue
|
# homeassistant.components.hue
|
||||||
aiohue==2.1.0
|
aiohue==2.3.0
|
||||||
|
|
||||||
# homeassistant.components.apache_kafka
|
# homeassistant.components.apache_kafka
|
||||||
aiokafka==0.6.0
|
aiokafka==0.6.0
|
||||||
@ -264,7 +264,7 @@ debugpy==1.2.1
|
|||||||
defusedxml==0.6.0
|
defusedxml==0.6.0
|
||||||
|
|
||||||
# homeassistant.components.denonavr
|
# homeassistant.components.denonavr
|
||||||
denonavr==0.10.5
|
denonavr==0.10.6
|
||||||
|
|
||||||
# homeassistant.components.devolo_home_control
|
# homeassistant.components.devolo_home_control
|
||||||
devolo-home-control-api==0.17.3
|
devolo-home-control-api==0.17.3
|
||||||
@ -720,7 +720,7 @@ pycfdns==1.2.1
|
|||||||
pychromecast==9.1.2
|
pychromecast==9.1.2
|
||||||
|
|
||||||
# homeassistant.components.climacell
|
# homeassistant.components.climacell
|
||||||
pyclimacell==0.18.0
|
pyclimacell==0.18.2
|
||||||
|
|
||||||
# homeassistant.components.comfoconnect
|
# homeassistant.components.comfoconnect
|
||||||
pycomfoconnect==0.4
|
pycomfoconnect==0.4
|
||||||
@ -983,7 +983,7 @@ python-izone==1.1.4
|
|||||||
python-juicenet==1.0.1
|
python-juicenet==1.0.1
|
||||||
|
|
||||||
# homeassistant.components.xiaomi_miio
|
# homeassistant.components.xiaomi_miio
|
||||||
python-miio==0.5.5
|
python-miio==0.5.6
|
||||||
|
|
||||||
# homeassistant.components.nest
|
# homeassistant.components.nest
|
||||||
python-nest==4.1.0
|
python-nest==4.1.0
|
||||||
@ -1144,7 +1144,7 @@ spotipy==2.18.0
|
|||||||
|
|
||||||
# homeassistant.components.recorder
|
# homeassistant.components.recorder
|
||||||
# homeassistant.components.sql
|
# homeassistant.components.sql
|
||||||
sqlalchemy==1.4.11
|
sqlalchemy==1.4.13
|
||||||
|
|
||||||
# homeassistant.components.srp_energy
|
# homeassistant.components.srp_energy
|
||||||
srpenergy==1.3.2
|
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."""
|
"""Test we handle an authorization error in the update step."""
|
||||||
device = get_device("Office")
|
device = get_device("Office")
|
||||||
mock_api = device.get_mock_api()
|
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(
|
with patch.object(
|
||||||
hass.config_entries, "async_forward_entry_setup"
|
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")}
|
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):
|
async def test_rm_mini3_no_sensor(hass):
|
||||||
"""Test we do not set up sensors for RM mini 3."""
|
"""Test we do not set up sensors for RM mini 3."""
|
||||||
device = get_device("Entrance")
|
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_HEALTH_CONCERN, "Good")
|
||||||
check_sensor_state(hass, EPA_PRIMARY_POLLUTANT, "pm25")
|
check_sensor_state(hass, EPA_PRIMARY_POLLUTANT, "pm25")
|
||||||
check_sensor_state(hass, FIRE_INDEX, "9")
|
check_sensor_state(hass, FIRE_INDEX, "9")
|
||||||
check_sensor_state(hass, GRASS_POLLEN, "0")
|
check_sensor_state(hass, GRASS_POLLEN, "minimal_to_none")
|
||||||
check_sensor_state(hass, WEED_POLLEN, "0")
|
check_sensor_state(hass, WEED_POLLEN, "minimal_to_none")
|
||||||
check_sensor_state(hass, TREE_POLLEN, "0")
|
check_sensor_state(hass, TREE_POLLEN, "minimal_to_none")
|
||||||
|
|
||||||
|
|
||||||
async def test_v4_sensor(
|
async def test_v4_sensor(
|
||||||
|
@ -11,7 +11,6 @@ from homeassistant.components.light import (
|
|||||||
ATTR_MAX_MIREDS,
|
ATTR_MAX_MIREDS,
|
||||||
ATTR_MIN_MIREDS,
|
ATTR_MIN_MIREDS,
|
||||||
ATTR_RGB_COLOR,
|
ATTR_RGB_COLOR,
|
||||||
ATTR_WHITE_VALUE,
|
|
||||||
ATTR_XY_COLOR,
|
ATTR_XY_COLOR,
|
||||||
DOMAIN as LIGHT_DOMAIN,
|
DOMAIN as LIGHT_DOMAIN,
|
||||||
SERVICE_TURN_OFF,
|
SERVICE_TURN_OFF,
|
||||||
@ -54,13 +53,11 @@ async def test_state_attributes(hass):
|
|||||||
{
|
{
|
||||||
ATTR_ENTITY_ID: ENTITY_LIGHT,
|
ATTR_ENTITY_ID: ENTITY_LIGHT,
|
||||||
ATTR_RGB_COLOR: (251, 253, 255),
|
ATTR_RGB_COLOR: (251, 253, 255),
|
||||||
ATTR_WHITE_VALUE: 254,
|
|
||||||
},
|
},
|
||||||
blocking=True,
|
blocking=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
state = hass.states.get(ENTITY_LIGHT)
|
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_RGB_COLOR) == (250, 252, 255)
|
||||||
assert state.attributes.get(ATTR_XY_COLOR) == (0.319, 0.326)
|
assert state.attributes.get(ATTR_XY_COLOR) == (0.319, 0.326)
|
||||||
|
|
||||||
|
@ -91,6 +91,8 @@ ENTITY_IDS_BY_NUMBER = {
|
|||||||
"22": "scene.light_on",
|
"22": "scene.light_on",
|
||||||
"23": "scene.light_off",
|
"23": "scene.light_off",
|
||||||
"24": "media_player.kitchen",
|
"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()}
|
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",
|
"type": "action.devices.types.SECURITYSYSTEM",
|
||||||
"willReportState": False,
|
"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
|
import homeassistant.components.group.light as group
|
||||||
from homeassistant.components.light import (
|
from homeassistant.components.light import (
|
||||||
ATTR_BRIGHTNESS,
|
ATTR_BRIGHTNESS,
|
||||||
|
ATTR_COLOR_MODE,
|
||||||
ATTR_COLOR_TEMP,
|
ATTR_COLOR_TEMP,
|
||||||
ATTR_EFFECT,
|
ATTR_EFFECT,
|
||||||
ATTR_EFFECT_LIST,
|
ATTR_EFFECT_LIST,
|
||||||
@ -16,13 +17,24 @@ from homeassistant.components.light import (
|
|||||||
ATTR_MAX_MIREDS,
|
ATTR_MAX_MIREDS,
|
||||||
ATTR_MIN_MIREDS,
|
ATTR_MIN_MIREDS,
|
||||||
ATTR_RGB_COLOR,
|
ATTR_RGB_COLOR,
|
||||||
|
ATTR_RGBW_COLOR,
|
||||||
|
ATTR_RGBWW_COLOR,
|
||||||
|
ATTR_SUPPORTED_COLOR_MODES,
|
||||||
ATTR_TRANSITION,
|
ATTR_TRANSITION,
|
||||||
ATTR_WHITE_VALUE,
|
ATTR_WHITE_VALUE,
|
||||||
ATTR_XY_COLOR,
|
ATTR_XY_COLOR,
|
||||||
|
COLOR_MODE_BRIGHTNESS,
|
||||||
|
COLOR_MODE_COLOR_TEMP,
|
||||||
|
COLOR_MODE_HS,
|
||||||
|
COLOR_MODE_RGBW,
|
||||||
|
COLOR_MODE_RGBWW,
|
||||||
DOMAIN as LIGHT_DOMAIN,
|
DOMAIN as LIGHT_DOMAIN,
|
||||||
SERVICE_TOGGLE,
|
SERVICE_TOGGLE,
|
||||||
SERVICE_TURN_OFF,
|
SERVICE_TURN_OFF,
|
||||||
SERVICE_TURN_ON,
|
SERVICE_TURN_ON,
|
||||||
|
SUPPORT_BRIGHTNESS,
|
||||||
|
SUPPORT_COLOR,
|
||||||
|
SUPPORT_COLOR_TEMP,
|
||||||
)
|
)
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_ENTITY_ID,
|
ATTR_ENTITY_ID,
|
||||||
@ -104,85 +116,281 @@ async def test_state_reporting(hass):
|
|||||||
|
|
||||||
async def test_brightness(hass):
|
async def test_brightness(hass):
|
||||||
"""Test brightness reporting."""
|
"""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,
|
hass,
|
||||||
LIGHT_DOMAIN,
|
LIGHT_DOMAIN,
|
||||||
{
|
{
|
||||||
LIGHT_DOMAIN: {
|
LIGHT_DOMAIN: [
|
||||||
"platform": DOMAIN,
|
{"platform": "test"},
|
||||||
"entities": ["light.test1", "light.test2"],
|
{
|
||||||
}
|
"platform": DOMAIN,
|
||||||
|
"entities": ["light.test1", "light.test2"],
|
||||||
|
},
|
||||||
|
]
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
await hass.async_start()
|
await hass.async_start()
|
||||||
await hass.async_block_till_done()
|
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")
|
state = hass.states.get("light.light_group")
|
||||||
assert state.state == STATE_ON
|
assert state.state == STATE_ON
|
||||||
assert state.attributes[ATTR_SUPPORTED_FEATURES] == 1
|
|
||||||
assert state.attributes[ATTR_BRIGHTNESS] == 255
|
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(
|
await hass.services.async_call(
|
||||||
"light.test2", STATE_ON, {ATTR_BRIGHTNESS: 100, ATTR_SUPPORTED_FEATURES: 1}
|
"light",
|
||||||
|
"turn_on",
|
||||||
|
{"entity_id": [entity1.entity_id], ATTR_BRIGHTNESS: 100},
|
||||||
|
blocking=True,
|
||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
state = hass.states.get("light.light_group")
|
state = hass.states.get("light.light_group")
|
||||||
assert state.state == STATE_ON
|
assert state.state == STATE_ON
|
||||||
assert state.attributes[ATTR_BRIGHTNESS] == 177
|
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(
|
await hass.services.async_call(
|
||||||
"light.test1", STATE_OFF, {ATTR_BRIGHTNESS: 255, ATTR_SUPPORTED_FEATURES: 1}
|
"light",
|
||||||
|
"turn_off",
|
||||||
|
{"entity_id": [entity0.entity_id]},
|
||||||
|
blocking=True,
|
||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
state = hass.states.get("light.light_group")
|
state = hass.states.get("light.light_group")
|
||||||
assert state.state == STATE_ON
|
assert state.state == STATE_ON
|
||||||
assert state.attributes[ATTR_SUPPORTED_FEATURES] == 1
|
|
||||||
assert state.attributes[ATTR_BRIGHTNESS] == 100
|
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):
|
async def test_color_hs(hass):
|
||||||
"""Test RGB reporting."""
|
"""Test hs color 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_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,
|
hass,
|
||||||
LIGHT_DOMAIN,
|
LIGHT_DOMAIN,
|
||||||
{
|
{
|
||||||
LIGHT_DOMAIN: {
|
LIGHT_DOMAIN: [
|
||||||
"platform": DOMAIN,
|
{"platform": "test"},
|
||||||
"entities": ["light.test1", "light.test2"],
|
{
|
||||||
}
|
"platform": DOMAIN,
|
||||||
|
"entities": ["light.test1", "light.test2"],
|
||||||
|
},
|
||||||
|
]
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
await hass.async_start()
|
await hass.async_start()
|
||||||
await hass.async_block_till_done()
|
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")
|
state = hass.states.get("light.light_group")
|
||||||
assert state.state == STATE_ON
|
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_HS_COLOR] == (0, 100)
|
||||||
|
assert state.attributes[ATTR_SUPPORTED_COLOR_MODES] == ["hs"]
|
||||||
|
assert state.attributes[ATTR_SUPPORTED_FEATURES] == 0
|
||||||
|
|
||||||
hass.states.async_set(
|
await hass.services.async_call(
|
||||||
"light.test2", STATE_ON, {ATTR_HS_COLOR: (0, 50), ATTR_SUPPORTED_FEATURES: 16}
|
"light",
|
||||||
|
"turn_on",
|
||||||
|
{"entity_id": [entity1.entity_id], ATTR_HS_COLOR: (0, 50)},
|
||||||
|
blocking=True,
|
||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
state = hass.states.get("light.light_group")
|
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_HS_COLOR] == (0, 75)
|
||||||
|
assert state.attributes[ATTR_SUPPORTED_COLOR_MODES] == ["hs"]
|
||||||
|
assert state.attributes[ATTR_SUPPORTED_FEATURES] == 0
|
||||||
|
|
||||||
hass.states.async_set(
|
await hass.services.async_call(
|
||||||
"light.test1", STATE_OFF, {ATTR_HS_COLOR: (0, 0), ATTR_SUPPORTED_FEATURES: 16}
|
"light",
|
||||||
|
"turn_off",
|
||||||
|
{"entity_id": [entity0.entity_id]},
|
||||||
|
blocking=True,
|
||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
state = hass.states.get("light.light_group")
|
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_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):
|
async def test_white_value(hass):
|
||||||
@ -206,6 +414,7 @@ async def test_white_value(hass):
|
|||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
state = hass.states.get("light.light_group")
|
state = hass.states.get("light.light_group")
|
||||||
|
assert state.attributes[ATTR_SUPPORTED_FEATURES] == 128
|
||||||
assert state.attributes[ATTR_WHITE_VALUE] == 255
|
assert state.attributes[ATTR_WHITE_VALUE] == 255
|
||||||
|
|
||||||
hass.states.async_set(
|
hass.states.async_set(
|
||||||
@ -213,6 +422,7 @@ async def test_white_value(hass):
|
|||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
state = hass.states.get("light.light_group")
|
state = hass.states.get("light.light_group")
|
||||||
|
assert state.attributes[ATTR_SUPPORTED_FEATURES] == 128
|
||||||
assert state.attributes[ATTR_WHITE_VALUE] == 177
|
assert state.attributes[ATTR_WHITE_VALUE] == 177
|
||||||
|
|
||||||
hass.states.async_set(
|
hass.states.async_set(
|
||||||
@ -220,62 +430,36 @@ async def test_white_value(hass):
|
|||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
state = hass.states.get("light.light_group")
|
state = hass.states.get("light.light_group")
|
||||||
|
assert state.attributes[ATTR_SUPPORTED_FEATURES] == 128
|
||||||
assert state.attributes[ATTR_WHITE_VALUE] == 100
|
assert state.attributes[ATTR_WHITE_VALUE] == 100
|
||||||
|
|
||||||
|
|
||||||
async def test_color_temp(hass):
|
async def test_color_temp(hass):
|
||||||
"""Test color temp reporting."""
|
"""Test color temp reporting."""
|
||||||
await async_setup_component(
|
platform = getattr(hass.components, "test.light")
|
||||||
hass,
|
platform.init(empty=True)
|
||||||
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()
|
|
||||||
|
|
||||||
hass.states.async_set(
|
platform.ENTITIES.append(platform.MockLight("test1", STATE_ON))
|
||||||
"light.test1", STATE_ON, {"color_temp": 2, ATTR_SUPPORTED_FEATURES: 2}
|
platform.ENTITIES.append(platform.MockLight("test2", STATE_OFF))
|
||||||
)
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
state = hass.states.get("light.light_group")
|
|
||||||
assert state.attributes[ATTR_COLOR_TEMP] == 2
|
|
||||||
|
|
||||||
hass.states.async_set(
|
entity0 = platform.ENTITIES[0]
|
||||||
"light.test2", STATE_ON, {"color_temp": 1000, ATTR_SUPPORTED_FEATURES: 2}
|
entity0.supported_color_modes = {COLOR_MODE_COLOR_TEMP}
|
||||||
)
|
entity0.color_mode = COLOR_MODE_COLOR_TEMP
|
||||||
await hass.async_block_till_done()
|
entity0.brightness = 255
|
||||||
state = hass.states.get("light.light_group")
|
entity0.color_temp = 2
|
||||||
assert state.attributes[ATTR_COLOR_TEMP] == 501
|
|
||||||
|
|
||||||
hass.states.async_set(
|
entity1 = platform.ENTITIES[1]
|
||||||
"light.test1", STATE_OFF, {"color_temp": 2, ATTR_SUPPORTED_FEATURES: 2}
|
entity1.supported_features = SUPPORT_COLOR_TEMP
|
||||||
)
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
state = hass.states.get("light.light_group")
|
|
||||||
assert state.attributes[ATTR_COLOR_TEMP] == 1000
|
|
||||||
|
|
||||||
|
assert await async_setup_component(
|
||||||
async def test_emulated_color_temp_group(hass):
|
|
||||||
"""Test emulated color temperature in a group."""
|
|
||||||
await async_setup_component(
|
|
||||||
hass,
|
hass,
|
||||||
LIGHT_DOMAIN,
|
LIGHT_DOMAIN,
|
||||||
{
|
{
|
||||||
LIGHT_DOMAIN: [
|
LIGHT_DOMAIN: [
|
||||||
{"platform": "demo"},
|
{"platform": "test"},
|
||||||
{
|
{
|
||||||
"platform": DOMAIN,
|
"platform": DOMAIN,
|
||||||
"entities": [
|
"entities": ["light.test1", "light.test2"],
|
||||||
"light.bed_light",
|
|
||||||
"light.ceiling_lights",
|
|
||||||
"light.kitchen_lights",
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -284,13 +468,78 @@ async def test_emulated_color_temp_group(hass):
|
|||||||
await hass.async_start()
|
await hass.async_start()
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
hass.states.async_set("light.bed_light", STATE_ON, {ATTR_SUPPORTED_FEATURES: 2})
|
state = hass.states.get("light.light_group")
|
||||||
hass.states.async_set(
|
assert state.attributes[ATTR_COLOR_MODE] == "color_temp"
|
||||||
"light.ceiling_lights", STATE_ON, {ATTR_SUPPORTED_FEATURES: 63}
|
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(
|
await hass.async_block_till_done()
|
||||||
"light.kitchen_lights", STATE_ON, {ATTR_SUPPORTED_FEATURES: 61}
|
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.async_block_till_done()
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
LIGHT_DOMAIN,
|
LIGHT_DOMAIN,
|
||||||
@ -300,61 +549,82 @@ async def test_emulated_color_temp_group(hass):
|
|||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
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.state == STATE_ON
|
||||||
assert state.attributes[ATTR_COLOR_TEMP] == 200
|
assert state.attributes[ATTR_COLOR_TEMP] == 200
|
||||||
assert ATTR_HS_COLOR not in state.attributes.keys()
|
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.state == STATE_ON
|
||||||
assert state.attributes[ATTR_COLOR_TEMP] == 200
|
assert state.attributes[ATTR_COLOR_TEMP] == 200
|
||||||
assert ATTR_HS_COLOR not in state.attributes.keys()
|
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.state == STATE_ON
|
||||||
assert state.attributes[ATTR_HS_COLOR] == (27.001, 19.243)
|
assert state.attributes[ATTR_HS_COLOR] == (27.001, 19.243)
|
||||||
|
|
||||||
|
|
||||||
async def test_min_max_mireds(hass):
|
async def test_min_max_mireds(hass):
|
||||||
"""Test min/max mireds reporting."""
|
"""Test min/max mireds reporting.
|
||||||
await async_setup_component(
|
|
||||||
|
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,
|
hass,
|
||||||
LIGHT_DOMAIN,
|
LIGHT_DOMAIN,
|
||||||
{
|
{
|
||||||
LIGHT_DOMAIN: {
|
LIGHT_DOMAIN: [
|
||||||
"platform": DOMAIN,
|
{"platform": "test"},
|
||||||
"entities": ["light.test1", "light.test2"],
|
{
|
||||||
}
|
"platform": DOMAIN,
|
||||||
|
"entities": ["light.test1", "light.test2"],
|
||||||
|
},
|
||||||
|
]
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
await hass.async_start()
|
await hass.async_start()
|
||||||
await hass.async_block_till_done()
|
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()
|
await hass.async_block_till_done()
|
||||||
state = hass.states.get("light.light_group")
|
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] == 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_MAX_MIREDS] == 1234567890
|
assert state.attributes[ATTR_MAX_MIREDS] == 1234567890
|
||||||
|
|
||||||
hass.states.async_set(
|
await hass.services.async_call(
|
||||||
"light.test1",
|
"light",
|
||||||
STATE_OFF,
|
"turn_on",
|
||||||
{ATTR_MIN_MIREDS: 1, ATTR_MAX_MIREDS: 2, ATTR_SUPPORTED_FEATURES: 2},
|
{"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()
|
await hass.async_block_till_done()
|
||||||
state = hass.states.get("light.light_group")
|
state = hass.states.get("light.light_group")
|
||||||
@ -465,6 +735,123 @@ async def test_effect(hass):
|
|||||||
assert state.attributes[ATTR_EFFECT] == "Random"
|
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):
|
async def test_supported_features(hass):
|
||||||
"""Test supported features reporting."""
|
"""Test supported features reporting."""
|
||||||
await async_setup_component(
|
await async_setup_component(
|
||||||
@ -486,20 +873,26 @@ async def test_supported_features(hass):
|
|||||||
state = hass.states.get("light.light_group")
|
state = hass.states.get("light.light_group")
|
||||||
assert state.attributes[ATTR_SUPPORTED_FEATURES] == 0
|
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})
|
hass.states.async_set("light.test2", STATE_ON, {ATTR_SUPPORTED_FEATURES: 2})
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
state = hass.states.get("light.light_group")
|
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})
|
hass.states.async_set("light.test1", STATE_OFF, {ATTR_SUPPORTED_FEATURES: 41})
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
state = hass.states.get("light.light_group")
|
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})
|
hass.states.async_set("light.test2", STATE_OFF, {ATTR_SUPPORTED_FEATURES: 256})
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
state = hass.states.get("light.light_group")
|
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):
|
async def test_service_calls(hass):
|
||||||
@ -629,8 +1022,6 @@ async def test_invalid_service_calls(hass):
|
|||||||
}
|
}
|
||||||
await grouped_light.async_turn_on(**data)
|
await grouped_light.async_turn_on(**data)
|
||||||
data[ATTR_ENTITY_ID] = ["light.test1", "light.test2"]
|
data[ATTR_ENTITY_ID] = ["light.test1", "light.test2"]
|
||||||
data.pop(ATTR_RGB_COLOR)
|
|
||||||
data.pop(ATTR_XY_COLOR)
|
|
||||||
mock_call.assert_called_once_with(
|
mock_call.assert_called_once_with(
|
||||||
LIGHT_DOMAIN, SERVICE_TURN_ON, data, blocking=True, context=None
|
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")
|
_, 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")
|
_, 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):
|
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")
|
state = hass.states.get("fan.test")
|
||||||
assert state.attributes.get(fan.ATTR_PERCENTAGE) == 100
|
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"}')
|
async_fire_mqtt_message(hass, "preset-mode-state-topic", '{"val": "low"}')
|
||||||
assert "not a valid preset mode" in caplog.text
|
assert "not a valid preset mode" in caplog.text
|
||||||
caplog.clear()
|
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")
|
state = hass.states.get("fan.test")
|
||||||
assert state.attributes.get("preset_mode") == "silent"
|
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):
|
async def test_sending_mqtt_commands_and_optimistic(hass, mqtt_mock, caplog):
|
||||||
"""Test optimistic mode without state topic."""
|
"""Test optimistic mode without state topic."""
|
||||||
|
@ -380,6 +380,9 @@ async def test_config_invalid(
|
|||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.mysensors.config_flow.try_connect", return_value=True
|
"homeassistant.components.mysensors.config_flow.try_connect", return_value=True
|
||||||
|
), patch(
|
||||||
|
"homeassistant.components.mysensors.gateway.socket.getaddrinfo",
|
||||||
|
side_effect=OSError,
|
||||||
), patch(
|
), patch(
|
||||||
"homeassistant.components.mysensors.async_setup", return_value=True
|
"homeassistant.components.mysensors.async_setup", return_value=True
|
||||||
) as mock_setup, patch(
|
) as mock_setup, patch(
|
||||||
|
@ -32,7 +32,7 @@ from homeassistant.setup import async_setup_component
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@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,
|
1,
|
||||||
True,
|
True,
|
||||||
{
|
[
|
||||||
CONF_GATEWAY_TYPE: CONF_GATEWAY_TYPE_SERIAL,
|
{
|
||||||
CONF_DEVICE: "COM5",
|
CONF_GATEWAY_TYPE: CONF_GATEWAY_TYPE_SERIAL,
|
||||||
CONF_PERSISTENCE_FILE: "bla.json",
|
CONF_DEVICE: "COM5",
|
||||||
CONF_BAUD_RATE: 57600,
|
CONF_PERSISTENCE_FILE: "bla.json",
|
||||||
CONF_VERSION: "2.3",
|
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,
|
1,
|
||||||
True,
|
True,
|
||||||
{
|
[
|
||||||
CONF_GATEWAY_TYPE: CONF_GATEWAY_TYPE_TCP,
|
{
|
||||||
CONF_DEVICE: "127.0.0.1",
|
CONF_GATEWAY_TYPE: CONF_GATEWAY_TYPE_TCP,
|
||||||
CONF_PERSISTENCE_FILE: "blub.pickle",
|
CONF_DEVICE: "127.0.0.1",
|
||||||
CONF_TCP_PORT: 343,
|
CONF_PERSISTENCE_FILE: "blub.pickle",
|
||||||
CONF_VERSION: "2.4",
|
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,
|
1,
|
||||||
True,
|
True,
|
||||||
{
|
[
|
||||||
CONF_GATEWAY_TYPE: CONF_GATEWAY_TYPE_TCP,
|
{
|
||||||
CONF_DEVICE: "127.0.0.1",
|
CONF_GATEWAY_TYPE: CONF_GATEWAY_TYPE_TCP,
|
||||||
CONF_TCP_PORT: 5003,
|
CONF_DEVICE: "127.0.0.1",
|
||||||
CONF_VERSION: DEFAULT_VERSION,
|
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,
|
1,
|
||||||
True,
|
True,
|
||||||
{
|
[
|
||||||
CONF_GATEWAY_TYPE: CONF_GATEWAY_TYPE_MQTT,
|
{
|
||||||
CONF_DEVICE: "mqtt",
|
CONF_GATEWAY_TYPE: CONF_GATEWAY_TYPE_MQTT,
|
||||||
CONF_VERSION: DEFAULT_VERSION,
|
CONF_DEVICE: "mqtt",
|
||||||
CONF_TOPIC_OUT_PREFIX: "outtopic",
|
CONF_VERSION: DEFAULT_VERSION,
|
||||||
CONF_TOPIC_IN_PREFIX: "intopic",
|
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,
|
0,
|
||||||
True,
|
True,
|
||||||
{},
|
[{}],
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
{
|
{
|
||||||
@ -177,7 +202,30 @@ from homeassistant.setup import async_setup_component
|
|||||||
},
|
},
|
||||||
2,
|
2,
|
||||||
True,
|
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,
|
0,
|
||||||
False,
|
False,
|
||||||
{},
|
[{}],
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
{
|
{
|
||||||
@ -223,7 +271,47 @@ from homeassistant.setup import async_setup_component
|
|||||||
},
|
},
|
||||||
0,
|
0,
|
||||||
True,
|
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,
|
config: ConfigType,
|
||||||
expected_calls: int,
|
expected_calls: int,
|
||||||
expected_to_succeed: bool,
|
expected_to_succeed: bool,
|
||||||
expected_config_flow_user_input: dict[str, Any],
|
expected_config_entry_data: list[dict[str, Any]],
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test importing a gateway."""
|
"""Test importing a gateway."""
|
||||||
await async_setup_component(hass, "persistent_notification", {})
|
await async_setup_component(hass, "persistent_notification", {})
|
||||||
@ -249,8 +337,13 @@ async def test_import(
|
|||||||
|
|
||||||
assert len(mock_setup_entry.mock_calls) == expected_calls
|
assert len(mock_setup_entry.mock_calls) == expected_calls
|
||||||
|
|
||||||
if expected_calls > 0:
|
for idx in range(expected_calls):
|
||||||
config_flow_user_input = mock_setup_entry.mock_calls[0][1][1].data
|
config_entry = mock_setup_entry.mock_calls[idx][1][1]
|
||||||
for key, value in expected_config_flow_user_input.items():
|
expected_persistence_file = expected_config_entry_data[idx].pop(
|
||||||
assert key in config_flow_user_input
|
CONF_PERSISTENCE_FILE
|
||||||
assert config_flow_user_input[key] == value
|
)
|
||||||
|
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_HOST: "1.2.3.4",
|
||||||
CONF_PORT: "1234",
|
CONF_PORT: "1234",
|
||||||
},
|
},
|
||||||
unique_id=f"{CONF_TYPE_OWSERVER}:1.2.3.4:1234",
|
|
||||||
connection_class=CONN_CLASS_LOCAL_POLL,
|
connection_class=CONN_CLASS_LOCAL_POLL,
|
||||||
options={},
|
options={},
|
||||||
entry_id="2",
|
entry_id="2",
|
||||||
@ -65,7 +64,6 @@ async def test_failed_owserver_listing(hass):
|
|||||||
CONF_HOST: "1.2.3.4",
|
CONF_HOST: "1.2.3.4",
|
||||||
CONF_PORT: "1234",
|
CONF_PORT: "1234",
|
||||||
},
|
},
|
||||||
unique_id=f"{CONF_TYPE_OWSERVER}:1.2.3.4:1234",
|
|
||||||
connection_class=CONN_CLASS_LOCAL_POLL,
|
connection_class=CONN_CLASS_LOCAL_POLL,
|
||||||
options={},
|
options={},
|
||||||
entry_id="2",
|
entry_id="2",
|
||||||
|
@ -12,6 +12,7 @@ from homeassistant.components.zwave_js.api import (
|
|||||||
CONFIG,
|
CONFIG,
|
||||||
ENABLED,
|
ENABLED,
|
||||||
ENTRY_ID,
|
ENTRY_ID,
|
||||||
|
ERR_NOT_LOADED,
|
||||||
FILENAME,
|
FILENAME,
|
||||||
FORCE_CONSOLE,
|
FORCE_CONSOLE,
|
||||||
ID,
|
ID,
|
||||||
@ -31,8 +32,8 @@ from homeassistant.components.zwave_js.const import (
|
|||||||
from homeassistant.helpers import device_registry as dr
|
from homeassistant.helpers import device_registry as dr
|
||||||
|
|
||||||
|
|
||||||
async def test_websocket_api(hass, integration, multisensor_6, hass_ws_client):
|
async def test_network_status(hass, integration, hass_ws_client):
|
||||||
"""Test the network and node status websocket commands."""
|
"""Test the network status websocket command."""
|
||||||
entry = integration
|
entry = integration
|
||||||
ws_client = await hass_ws_client(hass)
|
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"]["ws_server_url"] == "ws://test:3000/zjs"
|
||||||
assert result["client"]["server_version"] == "1.0.0"
|
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
|
node = multisensor_6
|
||||||
await ws_client.send_json(
|
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 not result["is_secure"]
|
||||||
assert result["status"] == 1
|
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
|
# Test getting non-existent node fails
|
||||||
await ws_client.send_json(
|
await ws_client.send_json(
|
||||||
{
|
{
|
||||||
ID: 5,
|
ID: 4,
|
||||||
TYPE: "zwave_js/node_status",
|
TYPE: "zwave_js/node_status",
|
||||||
ENTRY_ID: entry.entry_id,
|
ENTRY_ID: entry.entry_id,
|
||||||
NODE_ID: 99999,
|
NODE_ID: 99999,
|
||||||
@ -99,18 +95,22 @@ async def test_websocket_api(hass, integration, multisensor_6, hass_ws_client):
|
|||||||
assert not msg["success"]
|
assert not msg["success"]
|
||||||
assert msg["error"]["code"] == ERR_NOT_FOUND
|
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(
|
await ws_client.send_json(
|
||||||
{
|
{
|
||||||
ID: 6,
|
ID: 5,
|
||||||
TYPE: "zwave_js/get_config_parameters",
|
TYPE: "zwave_js/node_status",
|
||||||
ENTRY_ID: entry.entry_id,
|
ENTRY_ID: entry.entry_id,
|
||||||
NODE_ID: 99999,
|
NODE_ID: node.node_id,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
msg = await ws_client.receive_json()
|
msg = await ws_client.receive_json()
|
||||||
|
|
||||||
assert not msg["success"]
|
assert not msg["success"]
|
||||||
assert msg["error"]["code"] == ERR_NOT_FOUND
|
assert msg["error"]["code"] == ERR_NOT_LOADED
|
||||||
|
|
||||||
|
|
||||||
async def test_add_node(
|
async def test_add_node(
|
||||||
@ -145,6 +145,29 @@ async def test_add_node(
|
|||||||
client.driver.receive_event(nortek_thermostat_added_event)
|
client.driver.receive_event(nortek_thermostat_added_event)
|
||||||
msg = await ws_client.receive_json()
|
msg = await ws_client.receive_json()
|
||||||
assert msg["event"]["event"] == "node added"
|
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):
|
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()
|
msg = await ws_client.receive_json()
|
||||||
assert msg["success"]
|
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(
|
async def test_remove_node(
|
||||||
hass,
|
hass,
|
||||||
@ -226,6 +269,18 @@ async def test_remove_node(
|
|||||||
)
|
)
|
||||||
assert device is None
|
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(
|
async def test_refresh_node_info(
|
||||||
hass, client, integration, hass_ws_client, multisensor_6
|
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()
|
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(
|
async def test_refresh_node_values(
|
||||||
hass, client, integration, hass_ws_client, multisensor_6
|
hass, client, integration, hass_ws_client, multisensor_6
|
||||||
@ -391,6 +476,38 @@ async def test_refresh_node_cc_values(
|
|||||||
assert not msg["success"]
|
assert not msg["success"]
|
||||||
assert msg["error"]["code"] == ERR_NOT_FOUND
|
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(
|
async def test_set_config_parameter(
|
||||||
hass, client, hass_ws_client, multisensor_6, integration
|
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"]["code"] == "unknown_error"
|
||||||
assert msg["error"]["message"] == "test"
|
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):
|
async def test_dump_view(integration, hass_client):
|
||||||
"""Test the HTTP dump view."""
|
"""Test the HTTP dump view."""
|
||||||
@ -571,6 +785,18 @@ async def test_subscribe_logs(hass, integration, client, hass_ws_client):
|
|||||||
"timestamp": "time",
|
"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):
|
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."""
|
"""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"]
|
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):
|
async def test_get_log_config(hass, client, integration, hass_ws_client):
|
||||||
"""Test that the get_log_config WS API call works."""
|
"""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["filename"] == "/test.txt"
|
||||||
assert log_config["force_console"] is False
|
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):
|
async def test_data_collection(hass, client, integration, hass_ws_client):
|
||||||
"""Test that the data collection WS API commands work."""
|
"""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]
|
assert not entry.data[CONF_DATA_COLLECTION_OPTED_IN]
|
||||||
|
|
||||||
client.async_send_command.reset_mock()
|
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):
|
class MockLight(MockToggleEntity, LightEntity):
|
||||||
"""Mock light class."""
|
"""Mock light class."""
|
||||||
|
|
||||||
brightness = None
|
color_mode = None
|
||||||
|
max_mireds = 500
|
||||||
|
min_mireds = 153
|
||||||
supported_color_modes = None
|
supported_color_modes = None
|
||||||
supported_features = 0
|
supported_features = 0
|
||||||
|
|
||||||
color_mode = None
|
brightness = None
|
||||||
|
color_temp = None
|
||||||
hs_color = None
|
hs_color = None
|
||||||
xy_color = None
|
|
||||||
rgb_color = None
|
rgb_color = None
|
||||||
rgbw_color = None
|
rgbw_color = None
|
||||||
rgbww_color = None
|
rgbww_color = None
|
||||||
|
xy_color = None
|
||||||
color_temp = None
|
|
||||||
|
|
||||||
white_value = 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