mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 05:07:41 +00:00
Merge pull request #55870 from home-assistant/rc
This commit is contained in:
commit
f0649855f9
@ -67,13 +67,13 @@ class IncomfortWaterHeater(IncomfortEntity, WaterHeaterEntity):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def min_temp(self) -> float:
|
def min_temp(self) -> float:
|
||||||
"""Return max valid temperature that can be set."""
|
"""Return min valid temperature that can be set."""
|
||||||
return 80.0
|
return 30.0
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def max_temp(self) -> float:
|
def max_temp(self) -> float:
|
||||||
"""Return max valid temperature that can be set."""
|
"""Return max valid temperature that can be set."""
|
||||||
return 30.0
|
return 80.0
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def temperature_unit(self) -> str:
|
def temperature_unit(self) -> str:
|
||||||
|
@ -110,16 +110,10 @@ class IntegrationSensor(RestoreEntity, SensorEntity):
|
|||||||
self._method = integration_method
|
self._method = integration_method
|
||||||
|
|
||||||
self._name = name if name is not None else f"{source_entity} integral"
|
self._name = name if name is not None else f"{source_entity} integral"
|
||||||
|
self._unit_template = (
|
||||||
if unit_of_measurement is None:
|
f"{'' if unit_prefix is None else unit_prefix}{{}}{unit_time}"
|
||||||
self._unit_template = (
|
)
|
||||||
f"{'' if unit_prefix is None else unit_prefix}{{}}{unit_time}"
|
self._unit_of_measurement = unit_of_measurement
|
||||||
)
|
|
||||||
# we postpone the definition of unit_of_measurement to later
|
|
||||||
self._unit_of_measurement = None
|
|
||||||
else:
|
|
||||||
self._unit_of_measurement = unit_of_measurement
|
|
||||||
|
|
||||||
self._unit_prefix = UNIT_PREFIXES[unit_prefix]
|
self._unit_prefix = UNIT_PREFIXES[unit_prefix]
|
||||||
self._unit_time = UNIT_TIME[unit_time]
|
self._unit_time = UNIT_TIME[unit_time]
|
||||||
self._attr_state_class = STATE_CLASS_TOTAL_INCREASING
|
self._attr_state_class = STATE_CLASS_TOTAL_INCREASING
|
||||||
@ -135,10 +129,10 @@ class IntegrationSensor(RestoreEntity, SensorEntity):
|
|||||||
_LOGGER.warning("Could not restore last state: %s", err)
|
_LOGGER.warning("Could not restore last state: %s", err)
|
||||||
else:
|
else:
|
||||||
self._attr_device_class = state.attributes.get(ATTR_DEVICE_CLASS)
|
self._attr_device_class = state.attributes.get(ATTR_DEVICE_CLASS)
|
||||||
|
if self._unit_of_measurement is None:
|
||||||
self._unit_of_measurement = state.attributes.get(
|
self._unit_of_measurement = state.attributes.get(
|
||||||
ATTR_UNIT_OF_MEASUREMENT
|
ATTR_UNIT_OF_MEASUREMENT
|
||||||
)
|
)
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def calc_integration(event):
|
def calc_integration(event):
|
||||||
|
@ -48,7 +48,7 @@ from homeassistant.helpers.integration_platform import (
|
|||||||
from homeassistant.loader import bind_hass
|
from homeassistant.loader import bind_hass
|
||||||
import homeassistant.util.dt as dt_util
|
import homeassistant.util.dt as dt_util
|
||||||
|
|
||||||
ENTITY_ID_JSON_TEMPLATE = '"entity_id": ?"{}"'
|
ENTITY_ID_JSON_TEMPLATE = '"entity_id":"{}"'
|
||||||
ENTITY_ID_JSON_EXTRACT = re.compile('"entity_id": ?"([^"]+)"')
|
ENTITY_ID_JSON_EXTRACT = re.compile('"entity_id": ?"([^"]+)"')
|
||||||
DOMAIN_JSON_EXTRACT = re.compile('"domain": ?"([^"]+)"')
|
DOMAIN_JSON_EXTRACT = re.compile('"domain": ?"([^"]+)"')
|
||||||
ICON_JSON_EXTRACT = re.compile('"icon": ?"([^"]+)"')
|
ICON_JSON_EXTRACT = re.compile('"icon": ?"([^"]+)"')
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"name": "Mazda Connected Services",
|
"name": "Mazda Connected Services",
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/mazda",
|
"documentation": "https://www.home-assistant.io/integrations/mazda",
|
||||||
"requirements": ["pymazda==0.2.0"],
|
"requirements": ["pymazda==0.2.1"],
|
||||||
"codeowners": ["@bdr99"],
|
"codeowners": ["@bdr99"],
|
||||||
"quality_scale": "platinum",
|
"quality_scale": "platinum",
|
||||||
"iot_class": "cloud_polling"
|
"iot_class": "cloud_polling"
|
||||||
|
@ -25,9 +25,11 @@ from homeassistant.const import (
|
|||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
CONF_DATA_TYPE,
|
CONF_DATA_TYPE,
|
||||||
|
CONF_INPUT_TYPE,
|
||||||
CONF_SWAP,
|
CONF_SWAP,
|
||||||
CONF_SWAP_BYTE,
|
CONF_SWAP_BYTE,
|
||||||
CONF_SWAP_NONE,
|
CONF_SWAP_NONE,
|
||||||
|
CONF_WRITE_TYPE,
|
||||||
DATA_TYPE_CUSTOM,
|
DATA_TYPE_CUSTOM,
|
||||||
DATA_TYPE_FLOAT,
|
DATA_TYPE_FLOAT,
|
||||||
DATA_TYPE_FLOAT16,
|
DATA_TYPE_FLOAT16,
|
||||||
@ -212,6 +214,10 @@ def duplicate_entity_validator(config: dict) -> dict:
|
|||||||
for index, entry in enumerate(hub[conf_key]):
|
for index, entry in enumerate(hub[conf_key]):
|
||||||
name = entry[CONF_NAME]
|
name = entry[CONF_NAME]
|
||||||
addr = str(entry[CONF_ADDRESS])
|
addr = str(entry[CONF_ADDRESS])
|
||||||
|
if CONF_INPUT_TYPE in entry:
|
||||||
|
addr += "_" + str(entry[CONF_INPUT_TYPE])
|
||||||
|
elif CONF_WRITE_TYPE in entry:
|
||||||
|
addr += "_" + str(entry[CONF_WRITE_TYPE])
|
||||||
if CONF_COMMAND_ON in entry:
|
if CONF_COMMAND_ON in entry:
|
||||||
addr += "_" + str(entry[CONF_COMMAND_ON])
|
addr += "_" + str(entry[CONF_COMMAND_ON])
|
||||||
if CONF_COMMAND_OFF in entry:
|
if CONF_COMMAND_OFF in entry:
|
||||||
@ -242,7 +248,10 @@ def duplicate_modbus_validator(config: list) -> list:
|
|||||||
errors = []
|
errors = []
|
||||||
for index, hub in enumerate(config):
|
for index, hub in enumerate(config):
|
||||||
name = hub.get(CONF_NAME, DEFAULT_HUB)
|
name = hub.get(CONF_NAME, DEFAULT_HUB)
|
||||||
host = hub[CONF_PORT] if hub[CONF_TYPE] == SERIAL else hub[CONF_HOST]
|
if hub[CONF_TYPE] == SERIAL:
|
||||||
|
host = hub[CONF_PORT]
|
||||||
|
else:
|
||||||
|
host = f"{hub[CONF_HOST]}_{hub[CONF_PORT]}"
|
||||||
if host in hosts:
|
if host in hosts:
|
||||||
err = f"Modbus {name} contains duplicate host/port {host}, not loaded!"
|
err = f"Modbus {name} contains duplicate host/port {host}, not loaded!"
|
||||||
_LOGGER.warning(err)
|
_LOGGER.warning(err)
|
||||||
|
@ -75,7 +75,7 @@ class RfxtrxSensorEntityDescription(SensorEntityDescription):
|
|||||||
|
|
||||||
SENSOR_TYPES = (
|
SENSOR_TYPES = (
|
||||||
RfxtrxSensorEntityDescription(
|
RfxtrxSensorEntityDescription(
|
||||||
key="Barameter",
|
key="Barometer",
|
||||||
device_class=DEVICE_CLASS_PRESSURE,
|
device_class=DEVICE_CLASS_PRESSURE,
|
||||||
state_class=STATE_CLASS_MEASUREMENT,
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
native_unit_of_measurement=PRESSURE_HPA,
|
native_unit_of_measurement=PRESSURE_HPA,
|
||||||
|
@ -144,7 +144,7 @@ class SurePetcareAPI:
|
|||||||
"""Get the latest data from Sure Petcare."""
|
"""Get the latest data from Sure Petcare."""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.states = await self.surepy.get_entities()
|
self.states = await self.surepy.get_entities(refresh=True)
|
||||||
except SurePetcareError as error:
|
except SurePetcareError as error:
|
||||||
_LOGGER.error("Unable to fetch data: %s", error)
|
_LOGGER.error("Unable to fetch data: %s", error)
|
||||||
return
|
return
|
||||||
|
@ -80,7 +80,7 @@ class ThinkingCleanerSwitch(SwitchEntity):
|
|||||||
self.last_lock_time = None
|
self.last_lock_time = None
|
||||||
self.graceful_state = False
|
self.graceful_state = False
|
||||||
|
|
||||||
self._attr_name = f"{tc_object} {description.name}"
|
self._attr_name = f"{tc_object.name} {description.name}"
|
||||||
|
|
||||||
def lock_update(self):
|
def lock_update(self):
|
||||||
"""Lock the update since TC clean takes some time to update."""
|
"""Lock the update since TC clean takes some time to update."""
|
||||||
|
@ -116,6 +116,13 @@ MODEL_AIRQUALITYMONITOR_B1 = "cgllc.airmonitor.b1"
|
|||||||
MODEL_AIRQUALITYMONITOR_S1 = "cgllc.airmonitor.s1"
|
MODEL_AIRQUALITYMONITOR_S1 = "cgllc.airmonitor.s1"
|
||||||
MODEL_AIRQUALITYMONITOR_CGDN1 = "cgllc.airm.cgdn1"
|
MODEL_AIRQUALITYMONITOR_CGDN1 = "cgllc.airm.cgdn1"
|
||||||
|
|
||||||
|
MODELS_AIR_QUALITY_MONITOR = [
|
||||||
|
MODEL_AIRQUALITYMONITOR_V1,
|
||||||
|
MODEL_AIRQUALITYMONITOR_B1,
|
||||||
|
MODEL_AIRQUALITYMONITOR_S1,
|
||||||
|
MODEL_AIRQUALITYMONITOR_CGDN1,
|
||||||
|
]
|
||||||
|
|
||||||
# Light Models
|
# Light Models
|
||||||
MODELS_LIGHT_EYECARE = ["philips.light.sread1"]
|
MODELS_LIGHT_EYECARE = ["philips.light.sread1"]
|
||||||
MODELS_LIGHT_CEILING = ["philips.light.ceiling", "philips.light.zyceiling"]
|
MODELS_LIGHT_CEILING = ["philips.light.ceiling", "philips.light.zyceiling"]
|
||||||
|
@ -405,36 +405,42 @@ class XiaomiAirPurifier(XiaomiGenericDevice):
|
|||||||
self._preset_modes = PRESET_MODES_AIRPURIFIER_PRO
|
self._preset_modes = PRESET_MODES_AIRPURIFIER_PRO
|
||||||
self._supported_features = SUPPORT_PRESET_MODE
|
self._supported_features = SUPPORT_PRESET_MODE
|
||||||
self._speed_count = 1
|
self._speed_count = 1
|
||||||
|
self._operation_mode_class = AirpurifierOperationMode
|
||||||
elif self._model == MODEL_AIRPURIFIER_PRO_V7:
|
elif self._model == MODEL_AIRPURIFIER_PRO_V7:
|
||||||
self._device_features = FEATURE_FLAGS_AIRPURIFIER_PRO_V7
|
self._device_features = FEATURE_FLAGS_AIRPURIFIER_PRO_V7
|
||||||
self._available_attributes = AVAILABLE_ATTRIBUTES_AIRPURIFIER_PRO_V7
|
self._available_attributes = AVAILABLE_ATTRIBUTES_AIRPURIFIER_PRO_V7
|
||||||
self._preset_modes = PRESET_MODES_AIRPURIFIER_PRO_V7
|
self._preset_modes = PRESET_MODES_AIRPURIFIER_PRO_V7
|
||||||
self._supported_features = SUPPORT_PRESET_MODE
|
self._supported_features = SUPPORT_PRESET_MODE
|
||||||
self._speed_count = 1
|
self._speed_count = 1
|
||||||
|
self._operation_mode_class = AirpurifierOperationMode
|
||||||
elif self._model in [MODEL_AIRPURIFIER_2S, MODEL_AIRPURIFIER_2H]:
|
elif self._model in [MODEL_AIRPURIFIER_2S, MODEL_AIRPURIFIER_2H]:
|
||||||
self._device_features = FEATURE_FLAGS_AIRPURIFIER_2S
|
self._device_features = FEATURE_FLAGS_AIRPURIFIER_2S
|
||||||
self._available_attributes = AVAILABLE_ATTRIBUTES_AIRPURIFIER_COMMON
|
self._available_attributes = AVAILABLE_ATTRIBUTES_AIRPURIFIER_COMMON
|
||||||
self._preset_modes = PRESET_MODES_AIRPURIFIER_2S
|
self._preset_modes = PRESET_MODES_AIRPURIFIER_2S
|
||||||
self._supported_features = SUPPORT_PRESET_MODE
|
self._supported_features = SUPPORT_PRESET_MODE
|
||||||
self._speed_count = 1
|
self._speed_count = 1
|
||||||
|
self._operation_mode_class = AirpurifierOperationMode
|
||||||
elif self._model in MODELS_PURIFIER_MIOT:
|
elif self._model in MODELS_PURIFIER_MIOT:
|
||||||
self._device_features = FEATURE_FLAGS_AIRPURIFIER_MIOT
|
self._device_features = FEATURE_FLAGS_AIRPURIFIER_MIOT
|
||||||
self._available_attributes = AVAILABLE_ATTRIBUTES_AIRPURIFIER_MIOT
|
self._available_attributes = AVAILABLE_ATTRIBUTES_AIRPURIFIER_MIOT
|
||||||
self._preset_modes = PRESET_MODES_AIRPURIFIER_MIOT
|
self._preset_modes = PRESET_MODES_AIRPURIFIER_MIOT
|
||||||
self._supported_features = SUPPORT_SET_SPEED | SUPPORT_PRESET_MODE
|
self._supported_features = SUPPORT_SET_SPEED | SUPPORT_PRESET_MODE
|
||||||
self._speed_count = 3
|
self._speed_count = 3
|
||||||
|
self._operation_mode_class = AirpurifierMiotOperationMode
|
||||||
elif self._model == MODEL_AIRPURIFIER_V3:
|
elif self._model == MODEL_AIRPURIFIER_V3:
|
||||||
self._device_features = FEATURE_FLAGS_AIRPURIFIER_V3
|
self._device_features = FEATURE_FLAGS_AIRPURIFIER_V3
|
||||||
self._available_attributes = AVAILABLE_ATTRIBUTES_AIRPURIFIER_V3
|
self._available_attributes = AVAILABLE_ATTRIBUTES_AIRPURIFIER_V3
|
||||||
self._preset_modes = PRESET_MODES_AIRPURIFIER_V3
|
self._preset_modes = PRESET_MODES_AIRPURIFIER_V3
|
||||||
self._supported_features = SUPPORT_PRESET_MODE
|
self._supported_features = SUPPORT_PRESET_MODE
|
||||||
self._speed_count = 1
|
self._speed_count = 1
|
||||||
|
self._operation_mode_class = AirpurifierOperationMode
|
||||||
else:
|
else:
|
||||||
self._device_features = FEATURE_FLAGS_AIRPURIFIER_MIIO
|
self._device_features = FEATURE_FLAGS_AIRPURIFIER_MIIO
|
||||||
self._available_attributes = AVAILABLE_ATTRIBUTES_AIRPURIFIER
|
self._available_attributes = AVAILABLE_ATTRIBUTES_AIRPURIFIER
|
||||||
self._preset_modes = PRESET_MODES_AIRPURIFIER
|
self._preset_modes = PRESET_MODES_AIRPURIFIER
|
||||||
self._supported_features = SUPPORT_PRESET_MODE
|
self._supported_features = SUPPORT_PRESET_MODE
|
||||||
self._speed_count = 1
|
self._speed_count = 1
|
||||||
|
self._operation_mode_class = AirpurifierOperationMode
|
||||||
|
|
||||||
self._state_attrs.update(
|
self._state_attrs.update(
|
||||||
{attribute: None for attribute in self._available_attributes}
|
{attribute: None for attribute in self._available_attributes}
|
||||||
@ -446,7 +452,7 @@ class XiaomiAirPurifier(XiaomiGenericDevice):
|
|||||||
def preset_mode(self):
|
def preset_mode(self):
|
||||||
"""Get the active preset mode."""
|
"""Get the active preset mode."""
|
||||||
if self._state:
|
if self._state:
|
||||||
preset_mode = AirpurifierOperationMode(self._state_attrs[ATTR_MODE]).name
|
preset_mode = self._operation_mode_class(self._mode).name
|
||||||
return preset_mode if preset_mode in self._preset_modes else None
|
return preset_mode if preset_mode in self._preset_modes else None
|
||||||
|
|
||||||
return None
|
return None
|
||||||
@ -455,7 +461,7 @@ class XiaomiAirPurifier(XiaomiGenericDevice):
|
|||||||
def percentage(self):
|
def percentage(self):
|
||||||
"""Return the current percentage based speed."""
|
"""Return the current percentage based speed."""
|
||||||
if self._state:
|
if self._state:
|
||||||
mode = AirpurifierOperationMode(self._state_attrs[ATTR_MODE])
|
mode = self._operation_mode_class(self._state_attrs[ATTR_MODE])
|
||||||
if mode in self.REVERSE_SPEED_MODE_MAPPING:
|
if mode in self.REVERSE_SPEED_MODE_MAPPING:
|
||||||
return ranged_value_to_percentage(
|
return ranged_value_to_percentage(
|
||||||
(1, self._speed_count), self.REVERSE_SPEED_MODE_MAPPING[mode]
|
(1, self._speed_count), self.REVERSE_SPEED_MODE_MAPPING[mode]
|
||||||
@ -479,7 +485,7 @@ class XiaomiAirPurifier(XiaomiGenericDevice):
|
|||||||
await self._try_command(
|
await self._try_command(
|
||||||
"Setting operation mode of the miio device failed.",
|
"Setting operation mode of the miio device failed.",
|
||||||
self._device.set_mode,
|
self._device.set_mode,
|
||||||
AirpurifierOperationMode(self.SPEED_MODE_MAPPING[speed_mode]),
|
self._operation_mode_class(self.SPEED_MODE_MAPPING[speed_mode]),
|
||||||
)
|
)
|
||||||
|
|
||||||
async def async_set_preset_mode(self, preset_mode: str) -> None:
|
async def async_set_preset_mode(self, preset_mode: str) -> None:
|
||||||
@ -490,11 +496,13 @@ class XiaomiAirPurifier(XiaomiGenericDevice):
|
|||||||
if preset_mode not in self.preset_modes:
|
if preset_mode not in self.preset_modes:
|
||||||
_LOGGER.warning("'%s'is not a valid preset mode", preset_mode)
|
_LOGGER.warning("'%s'is not a valid preset mode", preset_mode)
|
||||||
return
|
return
|
||||||
await self._try_command(
|
if await self._try_command(
|
||||||
"Setting operation mode of the miio device failed.",
|
"Setting operation mode of the miio device failed.",
|
||||||
self._device.set_mode,
|
self._device.set_mode,
|
||||||
self.PRESET_MODE_MAPPING[preset_mode],
|
self.PRESET_MODE_MAPPING[preset_mode],
|
||||||
)
|
):
|
||||||
|
self._mode = self._operation_mode_class[preset_mode].value
|
||||||
|
self.async_write_ha_state()
|
||||||
|
|
||||||
async def async_set_extra_features(self, features: int = 1):
|
async def async_set_extra_features(self, features: int = 1):
|
||||||
"""Set the extra features."""
|
"""Set the extra features."""
|
||||||
@ -538,15 +546,6 @@ class XiaomiAirPurifierMiot(XiaomiAirPurifier):
|
|||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@property
|
|
||||||
def preset_mode(self):
|
|
||||||
"""Get the active preset mode."""
|
|
||||||
if self._state:
|
|
||||||
preset_mode = AirpurifierMiotOperationMode(self._mode).name
|
|
||||||
return preset_mode if preset_mode in self._preset_modes else None
|
|
||||||
|
|
||||||
return None
|
|
||||||
|
|
||||||
async def async_set_percentage(self, percentage: int) -> None:
|
async def async_set_percentage(self, percentage: int) -> None:
|
||||||
"""Set the percentage of the fan.
|
"""Set the percentage of the fan.
|
||||||
|
|
||||||
|
@ -210,7 +210,7 @@ class XiaomiAirHumidifier(XiaomiGenericHumidifier, HumidifierEntity):
|
|||||||
self._available_modes = AVAILABLE_MODES_MJJSQ
|
self._available_modes = AVAILABLE_MODES_MJJSQ
|
||||||
self._min_humidity = 30
|
self._min_humidity = 30
|
||||||
self._max_humidity = 80
|
self._max_humidity = 80
|
||||||
self._humidity_steps = 10
|
self._humidity_steps = 100
|
||||||
else:
|
else:
|
||||||
self._available_modes = AVAILABLE_MODES_OTHER
|
self._available_modes = AVAILABLE_MODES_OTHER
|
||||||
self._min_humidity = 30
|
self._min_humidity = 30
|
||||||
|
@ -66,6 +66,7 @@ from .const import (
|
|||||||
MODEL_FAN_ZA1,
|
MODEL_FAN_ZA1,
|
||||||
MODEL_FAN_ZA3,
|
MODEL_FAN_ZA3,
|
||||||
MODEL_FAN_ZA4,
|
MODEL_FAN_ZA4,
|
||||||
|
MODELS_AIR_QUALITY_MONITOR,
|
||||||
MODELS_HUMIDIFIER_MIIO,
|
MODELS_HUMIDIFIER_MIIO,
|
||||||
MODELS_HUMIDIFIER_MIOT,
|
MODELS_HUMIDIFIER_MIOT,
|
||||||
MODELS_HUMIDIFIER_MJJSQ,
|
MODELS_HUMIDIFIER_MJJSQ,
|
||||||
@ -371,23 +372,11 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
|||||||
host = config_entry.data[CONF_HOST]
|
host = config_entry.data[CONF_HOST]
|
||||||
token = config_entry.data[CONF_TOKEN]
|
token = config_entry.data[CONF_TOKEN]
|
||||||
model = config_entry.data[CONF_MODEL]
|
model = config_entry.data[CONF_MODEL]
|
||||||
device = hass.data[DOMAIN][config_entry.entry_id].get(KEY_DEVICE)
|
|
||||||
sensors = []
|
|
||||||
if model in (MODEL_FAN_ZA1, MODEL_FAN_ZA3, MODEL_FAN_ZA4, MODEL_FAN_P5):
|
if model in (MODEL_FAN_ZA1, MODEL_FAN_ZA3, MODEL_FAN_ZA4, MODEL_FAN_P5):
|
||||||
return
|
return
|
||||||
if model in MODEL_TO_SENSORS_MAP:
|
|
||||||
sensors = MODEL_TO_SENSORS_MAP[model]
|
if model in MODELS_AIR_QUALITY_MONITOR:
|
||||||
elif model in MODELS_HUMIDIFIER_MIOT:
|
|
||||||
sensors = HUMIDIFIER_MIOT_SENSORS
|
|
||||||
elif model in MODELS_HUMIDIFIER_MJJSQ:
|
|
||||||
sensors = HUMIDIFIER_MJJSQ_SENSORS
|
|
||||||
elif model in MODELS_HUMIDIFIER_MIIO:
|
|
||||||
sensors = HUMIDIFIER_MIIO_SENSORS
|
|
||||||
elif model in MODELS_PURIFIER_MIIO:
|
|
||||||
sensors = PURIFIER_MIIO_SENSORS
|
|
||||||
elif model in MODELS_PURIFIER_MIOT:
|
|
||||||
sensors = PURIFIER_MIOT_SENSORS
|
|
||||||
else:
|
|
||||||
unique_id = config_entry.unique_id
|
unique_id = config_entry.unique_id
|
||||||
name = config_entry.title
|
name = config_entry.title
|
||||||
_LOGGER.debug("Initializing with host %s (token %s...)", host, token[:5])
|
_LOGGER.debug("Initializing with host %s (token %s...)", host, token[:5])
|
||||||
@ -399,19 +388,35 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
|||||||
name, device, config_entry, unique_id, description
|
name, device, config_entry, unique_id, description
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
for sensor, description in SENSOR_TYPES.items():
|
else:
|
||||||
if sensor not in sensors:
|
device = hass.data[DOMAIN][config_entry.entry_id][KEY_DEVICE]
|
||||||
continue
|
sensors = []
|
||||||
entities.append(
|
if model in MODEL_TO_SENSORS_MAP:
|
||||||
XiaomiGenericSensor(
|
sensors = MODEL_TO_SENSORS_MAP[model]
|
||||||
f"{config_entry.title} {description.name}",
|
elif model in MODELS_HUMIDIFIER_MIOT:
|
||||||
device,
|
sensors = HUMIDIFIER_MIOT_SENSORS
|
||||||
config_entry,
|
elif model in MODELS_HUMIDIFIER_MJJSQ:
|
||||||
f"{sensor}_{config_entry.unique_id}",
|
sensors = HUMIDIFIER_MJJSQ_SENSORS
|
||||||
hass.data[DOMAIN][config_entry.entry_id][KEY_COORDINATOR],
|
elif model in MODELS_HUMIDIFIER_MIIO:
|
||||||
description,
|
sensors = HUMIDIFIER_MIIO_SENSORS
|
||||||
|
elif model in MODELS_PURIFIER_MIIO:
|
||||||
|
sensors = PURIFIER_MIIO_SENSORS
|
||||||
|
elif model in MODELS_PURIFIER_MIOT:
|
||||||
|
sensors = PURIFIER_MIOT_SENSORS
|
||||||
|
|
||||||
|
for sensor, description in SENSOR_TYPES.items():
|
||||||
|
if sensor not in sensors:
|
||||||
|
continue
|
||||||
|
entities.append(
|
||||||
|
XiaomiGenericSensor(
|
||||||
|
f"{config_entry.title} {description.name}",
|
||||||
|
device,
|
||||||
|
config_entry,
|
||||||
|
f"{sensor}_{config_entry.unique_id}",
|
||||||
|
hass.data[DOMAIN][config_entry.entry_id][KEY_COORDINATOR],
|
||||||
|
description,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
|
||||||
|
|
||||||
async_add_entities(entities)
|
async_add_entities(entities)
|
||||||
|
|
||||||
|
@ -108,7 +108,7 @@ class ZhaFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
self._abort_if_unique_id_configured(
|
self._abort_if_unique_id_configured(
|
||||||
updates={
|
updates={
|
||||||
CONF_DEVICE: {
|
CONF_DEVICE: {
|
||||||
**current_entry.data[CONF_DEVICE],
|
**current_entry.data.get(CONF_DEVICE, {}),
|
||||||
CONF_DEVICE_PATH: dev_path,
|
CONF_DEVICE_PATH: dev_path,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -172,7 +172,7 @@ class ZhaFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
self._abort_if_unique_id_configured(
|
self._abort_if_unique_id_configured(
|
||||||
updates={
|
updates={
|
||||||
CONF_DEVICE: {
|
CONF_DEVICE: {
|
||||||
**current_entry.data[CONF_DEVICE],
|
**current_entry.data.get(CONF_DEVICE, {}),
|
||||||
CONF_DEVICE_PATH: device_path,
|
CONF_DEVICE_PATH: device_path,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ import logging
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from zwave_js_server.client import Client as ZwaveClient
|
from zwave_js_server.client import Client as ZwaveClient
|
||||||
from zwave_js_server.const.command_class.barrior_operator import BarrierState
|
from zwave_js_server.const.command_class.barrier_operator import BarrierState
|
||||||
from zwave_js_server.model.value import Value as ZwaveValue
|
from zwave_js_server.model.value import Value as ZwaveValue
|
||||||
|
|
||||||
from homeassistant.components.cover import (
|
from homeassistant.components.cover import (
|
||||||
|
@ -32,8 +32,8 @@ from zwave_js_server.const.command_class.multilevel_sensor import (
|
|||||||
)
|
)
|
||||||
from zwave_js_server.model.node import Node as ZwaveNode
|
from zwave_js_server.model.node import Node as ZwaveNode
|
||||||
from zwave_js_server.model.value import Value as ZwaveValue, get_value_id
|
from zwave_js_server.model.value import Value as ZwaveValue, get_value_id
|
||||||
from zwave_js_server.util.command_class import (
|
from zwave_js_server.util.command_class.meter import get_meter_scale_type
|
||||||
get_meter_scale_type,
|
from zwave_js_server.util.command_class.multilevel_sensor import (
|
||||||
get_multilevel_sensor_type,
|
get_multilevel_sensor_type,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"name": "Z-Wave JS",
|
"name": "Z-Wave JS",
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/zwave_js",
|
"documentation": "https://www.home-assistant.io/integrations/zwave_js",
|
||||||
"requirements": ["zwave-js-server-python==0.29.1"],
|
"requirements": ["zwave-js-server-python==0.30.0"],
|
||||||
"codeowners": ["@home-assistant/z-wave"],
|
"codeowners": ["@home-assistant/z-wave"],
|
||||||
"dependencies": ["usb", "http", "websocket_api"],
|
"dependencies": ["usb", "http", "websocket_api"],
|
||||||
"iot_class": "local_push",
|
"iot_class": "local_push",
|
||||||
|
@ -15,7 +15,7 @@ from zwave_js_server.const.command_class.meter import (
|
|||||||
)
|
)
|
||||||
from zwave_js_server.model.node import Node as ZwaveNode
|
from zwave_js_server.model.node import Node as ZwaveNode
|
||||||
from zwave_js_server.model.value import ConfigurationValue
|
from zwave_js_server.model.value import ConfigurationValue
|
||||||
from zwave_js_server.util.command_class import get_meter_type
|
from zwave_js_server.util.command_class.meter import get_meter_type
|
||||||
|
|
||||||
from homeassistant.components.sensor import (
|
from homeassistant.components.sensor import (
|
||||||
DEVICE_CLASS_ENERGY,
|
DEVICE_CLASS_ENERGY,
|
||||||
|
@ -5,7 +5,7 @@ import logging
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from zwave_js_server.client import Client as ZwaveClient
|
from zwave_js_server.client import Client as ZwaveClient
|
||||||
from zwave_js_server.const.command_class.barrior_operator import (
|
from zwave_js_server.const.command_class.barrier_operator import (
|
||||||
BarrierEventSignalingSubsystemState,
|
BarrierEventSignalingSubsystemState,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ from typing import Final
|
|||||||
|
|
||||||
MAJOR_VERSION: Final = 2021
|
MAJOR_VERSION: Final = 2021
|
||||||
MINOR_VERSION: Final = 9
|
MINOR_VERSION: Final = 9
|
||||||
PATCH_VERSION: Final = "3"
|
PATCH_VERSION: Final = "4"
|
||||||
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
||||||
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
|
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
|
||||||
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 8, 0)
|
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 8, 0)
|
||||||
|
@ -1599,7 +1599,7 @@ pymailgunner==1.4
|
|||||||
pymata-express==1.19
|
pymata-express==1.19
|
||||||
|
|
||||||
# homeassistant.components.mazda
|
# homeassistant.components.mazda
|
||||||
pymazda==0.2.0
|
pymazda==0.2.1
|
||||||
|
|
||||||
# homeassistant.components.mediaroom
|
# homeassistant.components.mediaroom
|
||||||
pymediaroom==0.6.4.1
|
pymediaroom==0.6.4.1
|
||||||
@ -2489,4 +2489,4 @@ zigpy==0.37.1
|
|||||||
zm-py==0.5.2
|
zm-py==0.5.2
|
||||||
|
|
||||||
# homeassistant.components.zwave_js
|
# homeassistant.components.zwave_js
|
||||||
zwave-js-server-python==0.29.1
|
zwave-js-server-python==0.30.0
|
||||||
|
@ -921,7 +921,7 @@ pymailgunner==1.4
|
|||||||
pymata-express==1.19
|
pymata-express==1.19
|
||||||
|
|
||||||
# homeassistant.components.mazda
|
# homeassistant.components.mazda
|
||||||
pymazda==0.2.0
|
pymazda==0.2.1
|
||||||
|
|
||||||
# homeassistant.components.melcloud
|
# homeassistant.components.melcloud
|
||||||
pymelcloud==2.5.3
|
pymelcloud==2.5.3
|
||||||
@ -1400,4 +1400,4 @@ zigpy-znp==0.5.4
|
|||||||
zigpy==0.37.1
|
zigpy==0.37.1
|
||||||
|
|
||||||
# homeassistant.components.zwave_js
|
# homeassistant.components.zwave_js
|
||||||
zwave-js-server-python==0.29.1
|
zwave-js-server-python==0.30.0
|
||||||
|
@ -1289,6 +1289,45 @@ async def test_logbook_entity_matches_only(hass, hass_client):
|
|||||||
assert json_dict[1]["context_user_id"] == "9400facee45711eaa9308bfd3d19e474"
|
assert json_dict[1]["context_user_id"] == "9400facee45711eaa9308bfd3d19e474"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_custom_log_entry_discoverable_via_entity_matches_only(hass, hass_client):
|
||||||
|
"""Test if a custom log entry is later discoverable via entity_matches_only."""
|
||||||
|
await hass.async_add_executor_job(init_recorder_component, hass)
|
||||||
|
await async_setup_component(hass, "logbook", {})
|
||||||
|
await hass.async_add_executor_job(hass.data[recorder.DATA_INSTANCE].block_till_done)
|
||||||
|
|
||||||
|
logbook.async_log_entry(
|
||||||
|
hass,
|
||||||
|
"Alarm",
|
||||||
|
"is triggered",
|
||||||
|
"switch",
|
||||||
|
"switch.test_switch",
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
await hass.async_add_executor_job(trigger_db_commit, hass)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
await hass.async_add_executor_job(hass.data[recorder.DATA_INSTANCE].block_till_done)
|
||||||
|
|
||||||
|
client = await hass_client()
|
||||||
|
|
||||||
|
# Today time 00:00:00
|
||||||
|
start = dt_util.utcnow().date()
|
||||||
|
start_date = datetime(start.year, start.month, start.day)
|
||||||
|
|
||||||
|
# Test today entries with filter by end_time
|
||||||
|
end_time = start + timedelta(hours=24)
|
||||||
|
response = await client.get(
|
||||||
|
f"/api/logbook/{start_date.isoformat()}?end_time={end_time.isoformat()}&entity=switch.test_switch&entity_matches_only"
|
||||||
|
)
|
||||||
|
assert response.status == 200
|
||||||
|
json_dict = await response.json()
|
||||||
|
|
||||||
|
assert len(json_dict) == 1
|
||||||
|
|
||||||
|
assert json_dict[0]["name"] == "Alarm"
|
||||||
|
assert json_dict[0]["message"] == "is triggered"
|
||||||
|
assert json_dict[0]["entity_id"] == "switch.test_switch"
|
||||||
|
|
||||||
|
|
||||||
async def test_logbook_entity_matches_only_multiple(hass, hass_client):
|
async def test_logbook_entity_matches_only_multiple(hass, hass_client):
|
||||||
"""Test the logbook view with a multiple entities and entity_matches_only."""
|
"""Test the logbook view with a multiple entities and entity_matches_only."""
|
||||||
await hass.async_add_executor_job(init_recorder_component, hass)
|
await hass.async_add_executor_job(init_recorder_component, hass)
|
||||||
|
@ -7,12 +7,16 @@ from surepy import MESTART_RESOURCE
|
|||||||
from . import MOCK_API_DATA
|
from . import MOCK_API_DATA
|
||||||
|
|
||||||
|
|
||||||
|
async def _mock_call(method, resource):
|
||||||
|
if method == "GET" and resource == MESTART_RESOURCE:
|
||||||
|
return {"data": MOCK_API_DATA}
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
async def surepetcare():
|
async def surepetcare():
|
||||||
"""Mock the SurePetcare for easier testing."""
|
"""Mock the SurePetcare for easier testing."""
|
||||||
with patch("surepy.SureAPIClient", autospec=True) as mock_client_class, patch(
|
with patch("surepy.SureAPIClient", autospec=True) as mock_client_class:
|
||||||
"surepy.find_token"
|
|
||||||
):
|
|
||||||
client = mock_client_class.return_value
|
client = mock_client_class.return_value
|
||||||
client.resources = {MESTART_RESOURCE: {"data": MOCK_API_DATA}}
|
client.resources = {}
|
||||||
|
client.call = _mock_call
|
||||||
yield client
|
yield client
|
||||||
|
@ -12,7 +12,7 @@ EXPECTED_ENTITY_IDS = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async def test_binary_sensors(hass, surepetcare) -> None:
|
async def test_sensors(hass, surepetcare) -> None:
|
||||||
"""Test the generation of unique ids."""
|
"""Test the generation of unique ids."""
|
||||||
assert await async_setup_component(hass, DOMAIN, MOCK_CONFIG)
|
assert await async_setup_component(hass, DOMAIN, MOCK_CONFIG)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
@ -113,6 +113,34 @@ async def test_discovery_via_zeroconf_ip_change(detect_mock, hass):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@patch("homeassistant.components.zha.async_setup_entry", AsyncMock(return_value=True))
|
||||||
|
@patch("zigpy_znp.zigbee.application.ControllerApplication.probe", return_value=True)
|
||||||
|
async def test_discovery_via_zeroconf_ip_change_ignored(detect_mock, hass):
|
||||||
|
"""Test zeroconf flow that was ignored gets updated."""
|
||||||
|
entry = MockConfigEntry(
|
||||||
|
domain=DOMAIN,
|
||||||
|
unique_id="tube_zb_gw_cc2652p2_poe",
|
||||||
|
source=config_entries.SOURCE_IGNORE,
|
||||||
|
)
|
||||||
|
entry.add_to_hass(hass)
|
||||||
|
|
||||||
|
service_info = {
|
||||||
|
"host": "192.168.1.22",
|
||||||
|
"port": 6053,
|
||||||
|
"hostname": "tube_zb_gw_cc2652p2_poe.local.",
|
||||||
|
"properties": {"address": "tube_zb_gw_cc2652p2_poe.local"},
|
||||||
|
}
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
"zha", context={"source": SOURCE_ZEROCONF}, data=service_info
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == RESULT_TYPE_ABORT
|
||||||
|
assert result["reason"] == "already_configured"
|
||||||
|
assert entry.data[CONF_DEVICE] == {
|
||||||
|
CONF_DEVICE_PATH: "socket://192.168.1.22:6638",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@patch("zigpy_znp.zigbee.application.ControllerApplication.probe", return_value=True)
|
@patch("zigpy_znp.zigbee.application.ControllerApplication.probe", return_value=True)
|
||||||
async def test_discovery_via_usb(detect_mock, hass):
|
async def test_discovery_via_usb(detect_mock, hass):
|
||||||
"""Test usb flow -- radio detected."""
|
"""Test usb flow -- radio detected."""
|
||||||
@ -317,6 +345,37 @@ async def test_discovery_via_usb_deconz_ignored(detect_mock, hass):
|
|||||||
assert result["step_id"] == "confirm"
|
assert result["step_id"] == "confirm"
|
||||||
|
|
||||||
|
|
||||||
|
@patch("zigpy_znp.zigbee.application.ControllerApplication.probe", return_value=True)
|
||||||
|
async def test_discovery_via_usb_zha_ignored_updates(detect_mock, hass):
|
||||||
|
"""Test usb flow that was ignored gets updated."""
|
||||||
|
entry = MockConfigEntry(
|
||||||
|
domain=DOMAIN,
|
||||||
|
source=config_entries.SOURCE_IGNORE,
|
||||||
|
data={},
|
||||||
|
unique_id="AAAA:AAAA_1234_test_zigbee radio",
|
||||||
|
)
|
||||||
|
entry.add_to_hass(hass)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
discovery_info = {
|
||||||
|
"device": "/dev/ttyZIGBEE",
|
||||||
|
"pid": "AAAA",
|
||||||
|
"vid": "AAAA",
|
||||||
|
"serial_number": "1234",
|
||||||
|
"description": "zigbee radio",
|
||||||
|
"manufacturer": "test",
|
||||||
|
}
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
"zha", context={"source": SOURCE_USB}, data=discovery_info
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert result["type"] == RESULT_TYPE_ABORT
|
||||||
|
assert result["reason"] == "already_configured"
|
||||||
|
assert entry.data[CONF_DEVICE] == {
|
||||||
|
CONF_DEVICE_PATH: "/dev/ttyZIGBEE",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@patch("homeassistant.components.zha.async_setup_entry", AsyncMock(return_value=True))
|
@patch("homeassistant.components.zha.async_setup_entry", AsyncMock(return_value=True))
|
||||||
@patch("zigpy_znp.zigbee.application.ControllerApplication.probe", return_value=True)
|
@patch("zigpy_znp.zigbee.application.ControllerApplication.probe", return_value=True)
|
||||||
async def test_discovery_already_setup(detect_mock, hass):
|
async def test_discovery_already_setup(detect_mock, hass):
|
||||||
|
@ -338,7 +338,7 @@ async def test_add_node_secure(
|
|||||||
assert len(client.async_send_command.call_args_list) == 1
|
assert len(client.async_send_command.call_args_list) == 1
|
||||||
assert client.async_send_command.call_args[0][0] == {
|
assert client.async_send_command.call_args[0][0] == {
|
||||||
"command": "controller.begin_inclusion",
|
"command": "controller.begin_inclusion",
|
||||||
"options": {"inclusionStrategy": InclusionStrategy.SECURITY_S0},
|
"options": {"strategy": InclusionStrategy.SECURITY_S0},
|
||||||
}
|
}
|
||||||
|
|
||||||
client.async_send_command.reset_mock()
|
client.async_send_command.reset_mock()
|
||||||
@ -363,7 +363,7 @@ async def test_add_node(
|
|||||||
assert len(client.async_send_command.call_args_list) == 1
|
assert len(client.async_send_command.call_args_list) == 1
|
||||||
assert client.async_send_command.call_args[0][0] == {
|
assert client.async_send_command.call_args[0][0] == {
|
||||||
"command": "controller.begin_inclusion",
|
"command": "controller.begin_inclusion",
|
||||||
"options": {"inclusionStrategy": InclusionStrategy.INSECURE},
|
"options": {"strategy": InclusionStrategy.INSECURE},
|
||||||
}
|
}
|
||||||
|
|
||||||
event = Event(
|
event = Event(
|
||||||
@ -671,7 +671,7 @@ async def test_replace_failed_node_secure(
|
|||||||
assert client.async_send_command.call_args[0][0] == {
|
assert client.async_send_command.call_args[0][0] == {
|
||||||
"command": "controller.replace_failed_node",
|
"command": "controller.replace_failed_node",
|
||||||
"nodeId": nortek_thermostat.node_id,
|
"nodeId": nortek_thermostat.node_id,
|
||||||
"options": {"inclusionStrategy": InclusionStrategy.SECURITY_S0},
|
"options": {"strategy": InclusionStrategy.SECURITY_S0},
|
||||||
}
|
}
|
||||||
|
|
||||||
client.async_send_command.reset_mock()
|
client.async_send_command.reset_mock()
|
||||||
@ -720,7 +720,7 @@ async def test_replace_failed_node(
|
|||||||
assert client.async_send_command.call_args[0][0] == {
|
assert client.async_send_command.call_args[0][0] == {
|
||||||
"command": "controller.replace_failed_node",
|
"command": "controller.replace_failed_node",
|
||||||
"nodeId": nortek_thermostat.node_id,
|
"nodeId": nortek_thermostat.node_id,
|
||||||
"options": {"inclusionStrategy": InclusionStrategy.INSECURE},
|
"options": {"strategy": InclusionStrategy.INSECURE},
|
||||||
}
|
}
|
||||||
|
|
||||||
client.async_send_command.reset_mock()
|
client.async_send_command.reset_mock()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user