mirror of
https://github.com/home-assistant/core.git
synced 2025-07-19 03:07:37 +00:00
2023.8.3 (#98623)
This commit is contained in:
commit
bdd202b873
@ -11,7 +11,7 @@ DEFAULT_ALLOW_SERVICE_CALLS = True
|
|||||||
DEFAULT_NEW_CONFIG_ALLOW_ALLOW_SERVICE_CALLS = False
|
DEFAULT_NEW_CONFIG_ALLOW_ALLOW_SERVICE_CALLS = False
|
||||||
|
|
||||||
|
|
||||||
STABLE_BLE_VERSION_STR = "2023.6.0"
|
STABLE_BLE_VERSION_STR = "2023.8.0"
|
||||||
STABLE_BLE_VERSION = AwesomeVersion(STABLE_BLE_VERSION_STR)
|
STABLE_BLE_VERSION = AwesomeVersion(STABLE_BLE_VERSION_STR)
|
||||||
PROJECT_URLS = {
|
PROJECT_URLS = {
|
||||||
"esphome.bluetooth-proxy": "https://esphome.github.io/bluetooth-proxies/",
|
"esphome.bluetooth-proxy": "https://esphome.github.io/bluetooth-proxies/",
|
||||||
|
@ -54,5 +54,5 @@
|
|||||||
"iot_class": "local_push",
|
"iot_class": "local_push",
|
||||||
"loggers": ["flux_led"],
|
"loggers": ["flux_led"],
|
||||||
"quality_scale": "platinum",
|
"quality_scale": "platinum",
|
||||||
"requirements": ["flux-led==1.0.1"]
|
"requirements": ["flux-led==1.0.2"]
|
||||||
}
|
}
|
||||||
|
@ -20,5 +20,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/frontend",
|
"documentation": "https://www.home-assistant.io/integrations/frontend",
|
||||||
"integration_type": "system",
|
"integration_type": "system",
|
||||||
"quality_scale": "internal",
|
"quality_scale": "internal",
|
||||||
"requirements": ["home-assistant-frontend==20230802.0"]
|
"requirements": ["home-assistant-frontend==20230802.1"]
|
||||||
}
|
}
|
||||||
|
@ -113,9 +113,10 @@ class GoGoGate2Entity(CoordinatorEntity[DeviceDataUpdateCoordinator]):
|
|||||||
def device_info(self) -> DeviceInfo:
|
def device_info(self) -> DeviceInfo:
|
||||||
"""Device info for the controller."""
|
"""Device info for the controller."""
|
||||||
data = self.coordinator.data
|
data = self.coordinator.data
|
||||||
configuration_url = (
|
if data.remoteaccessenabled:
|
||||||
f"https://{data.remoteaccess}" if data.remoteaccess else None
|
configuration_url = f"https://{data.remoteaccess}"
|
||||||
)
|
else:
|
||||||
|
configuration_url = f"http://{self._config_entry.data[CONF_IP_ADDRESS]}"
|
||||||
return DeviceInfo(
|
return DeviceInfo(
|
||||||
configuration_url=configuration_url,
|
configuration_url=configuration_url,
|
||||||
identifiers={(DOMAIN, str(self._config_entry.unique_id))},
|
identifiers={(DOMAIN, str(self._config_entry.unique_id))},
|
||||||
|
@ -14,6 +14,6 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/homekit_controller",
|
"documentation": "https://www.home-assistant.io/integrations/homekit_controller",
|
||||||
"iot_class": "local_push",
|
"iot_class": "local_push",
|
||||||
"loggers": ["aiohomekit", "commentjson"],
|
"loggers": ["aiohomekit", "commentjson"],
|
||||||
"requirements": ["aiohomekit==2.6.15"],
|
"requirements": ["aiohomekit==2.6.16"],
|
||||||
"zeroconf": ["_hap._tcp.local.", "_hap._udp.local."]
|
"zeroconf": ["_hap._tcp.local.", "_hap._udp.local."]
|
||||||
}
|
}
|
||||||
|
@ -146,13 +146,13 @@ class HoneywellUSThermostat(ClimateEntity):
|
|||||||
| ClimateEntityFeature.TARGET_TEMPERATURE_RANGE
|
| ClimateEntityFeature.TARGET_TEMPERATURE_RANGE
|
||||||
)
|
)
|
||||||
|
|
||||||
if device._data["canControlHumidification"]:
|
if device._data.get("canControlHumidification"):
|
||||||
self._attr_supported_features |= ClimateEntityFeature.TARGET_HUMIDITY
|
self._attr_supported_features |= ClimateEntityFeature.TARGET_HUMIDITY
|
||||||
|
|
||||||
if device.raw_ui_data["SwitchEmergencyHeatAllowed"]:
|
if device.raw_ui_data.get("SwitchEmergencyHeatAllowed"):
|
||||||
self._attr_supported_features |= ClimateEntityFeature.AUX_HEAT
|
self._attr_supported_features |= ClimateEntityFeature.AUX_HEAT
|
||||||
|
|
||||||
if not device._data["hasFan"]:
|
if not device._data.get("hasFan"):
|
||||||
return
|
return
|
||||||
|
|
||||||
# not all honeywell fans support all modes
|
# not all honeywell fans support all modes
|
||||||
|
@ -20,6 +20,7 @@ from homeassistant.helpers.entity import DeviceInfo
|
|||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
from homeassistant.helpers.typing import StateType
|
from homeassistant.helpers.typing import StateType
|
||||||
|
|
||||||
|
from . import HoneywellData
|
||||||
from .const import DOMAIN, HUMIDITY_STATUS_KEY, TEMPERATURE_STATUS_KEY
|
from .const import DOMAIN, HUMIDITY_STATUS_KEY, TEMPERATURE_STATUS_KEY
|
||||||
|
|
||||||
|
|
||||||
@ -71,7 +72,7 @@ async def async_setup_entry(
|
|||||||
async_add_entities: AddEntitiesCallback,
|
async_add_entities: AddEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up the Honeywell thermostat."""
|
"""Set up the Honeywell thermostat."""
|
||||||
data = hass.data[DOMAIN][config_entry.entry_id]
|
data: HoneywellData = hass.data[DOMAIN][config_entry.entry_id]
|
||||||
sensors = []
|
sensors = []
|
||||||
|
|
||||||
for device in data.devices.values():
|
for device in data.devices.values():
|
||||||
|
@ -8,6 +8,6 @@
|
|||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"loggers": ["deepmerge", "pyipp"],
|
"loggers": ["deepmerge", "pyipp"],
|
||||||
"quality_scale": "platinum",
|
"quality_scale": "platinum",
|
||||||
"requirements": ["pyipp==0.14.2"],
|
"requirements": ["pyipp==0.14.3"],
|
||||||
"zeroconf": ["_ipps._tcp.local.", "_ipp._tcp.local."]
|
"zeroconf": ["_ipps._tcp.local.", "_ipp._tcp.local."]
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,12 @@ from homeassistant.components.climate import (
|
|||||||
HVACMode,
|
HVACMode,
|
||||||
)
|
)
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import ATTR_TEMPERATURE
|
from homeassistant.const import (
|
||||||
|
ATTR_TEMPERATURE,
|
||||||
|
PRECISION_HALVES,
|
||||||
|
PRECISION_WHOLE,
|
||||||
|
UnitOfTemperature,
|
||||||
|
)
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
from homeassistant.helpers import entity_platform
|
from homeassistant.helpers import entity_platform
|
||||||
@ -113,7 +118,6 @@ async def async_setup_entry(
|
|||||||
),
|
),
|
||||||
location,
|
location,
|
||||||
device,
|
device,
|
||||||
hass.config.units.temperature_unit,
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -140,10 +144,15 @@ class LyricClimate(LyricDeviceEntity, ClimateEntity):
|
|||||||
description: ClimateEntityDescription,
|
description: ClimateEntityDescription,
|
||||||
location: LyricLocation,
|
location: LyricLocation,
|
||||||
device: LyricDevice,
|
device: LyricDevice,
|
||||||
temperature_unit: str,
|
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize Honeywell Lyric climate entity."""
|
"""Initialize Honeywell Lyric climate entity."""
|
||||||
self._temperature_unit = temperature_unit
|
# Use the native temperature unit from the device settings
|
||||||
|
if device.units == "Fahrenheit":
|
||||||
|
self._attr_temperature_unit = UnitOfTemperature.FAHRENHEIT
|
||||||
|
self._attr_precision = PRECISION_WHOLE
|
||||||
|
else:
|
||||||
|
self._attr_temperature_unit = UnitOfTemperature.CELSIUS
|
||||||
|
self._attr_precision = PRECISION_HALVES
|
||||||
|
|
||||||
# Setup supported hvac modes
|
# Setup supported hvac modes
|
||||||
self._attr_hvac_modes = [HVACMode.OFF]
|
self._attr_hvac_modes = [HVACMode.OFF]
|
||||||
@ -176,11 +185,6 @@ class LyricClimate(LyricDeviceEntity, ClimateEntity):
|
|||||||
return SUPPORT_FLAGS_LCC
|
return SUPPORT_FLAGS_LCC
|
||||||
return SUPPORT_FLAGS_TCC
|
return SUPPORT_FLAGS_TCC
|
||||||
|
|
||||||
@property
|
|
||||||
def temperature_unit(self) -> str:
|
|
||||||
"""Return the unit of measurement."""
|
|
||||||
return self._temperature_unit
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_temperature(self) -> float | None:
|
def current_temperature(self) -> float | None:
|
||||||
"""Return the current temperature."""
|
"""Return the current temperature."""
|
||||||
|
@ -17,7 +17,7 @@ from homeassistant.components.sensor import (
|
|||||||
SensorStateClass,
|
SensorStateClass,
|
||||||
)
|
)
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import PERCENTAGE
|
from homeassistant.const import PERCENTAGE, UnitOfTemperature
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
from homeassistant.helpers.typing import StateType
|
from homeassistant.helpers.typing import StateType
|
||||||
@ -76,6 +76,11 @@ async def async_setup_entry(
|
|||||||
for location in coordinator.data.locations:
|
for location in coordinator.data.locations:
|
||||||
for device in location.devices:
|
for device in location.devices:
|
||||||
if device.indoorTemperature:
|
if device.indoorTemperature:
|
||||||
|
if device.units == "Fahrenheit":
|
||||||
|
native_temperature_unit = UnitOfTemperature.FAHRENHEIT
|
||||||
|
else:
|
||||||
|
native_temperature_unit = UnitOfTemperature.CELSIUS
|
||||||
|
|
||||||
entities.append(
|
entities.append(
|
||||||
LyricSensor(
|
LyricSensor(
|
||||||
coordinator,
|
coordinator,
|
||||||
@ -84,7 +89,7 @@ async def async_setup_entry(
|
|||||||
name="Indoor Temperature",
|
name="Indoor Temperature",
|
||||||
device_class=SensorDeviceClass.TEMPERATURE,
|
device_class=SensorDeviceClass.TEMPERATURE,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
native_unit_of_measurement=hass.config.units.temperature_unit,
|
native_unit_of_measurement=native_temperature_unit,
|
||||||
value=lambda device: device.indoorTemperature,
|
value=lambda device: device.indoorTemperature,
|
||||||
),
|
),
|
||||||
location,
|
location,
|
||||||
@ -108,6 +113,11 @@ async def async_setup_entry(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
if device.outdoorTemperature:
|
if device.outdoorTemperature:
|
||||||
|
if device.units == "Fahrenheit":
|
||||||
|
native_temperature_unit = UnitOfTemperature.FAHRENHEIT
|
||||||
|
else:
|
||||||
|
native_temperature_unit = UnitOfTemperature.CELSIUS
|
||||||
|
|
||||||
entities.append(
|
entities.append(
|
||||||
LyricSensor(
|
LyricSensor(
|
||||||
coordinator,
|
coordinator,
|
||||||
@ -116,7 +126,7 @@ async def async_setup_entry(
|
|||||||
name="Outdoor Temperature",
|
name="Outdoor Temperature",
|
||||||
device_class=SensorDeviceClass.TEMPERATURE,
|
device_class=SensorDeviceClass.TEMPERATURE,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
native_unit_of_measurement=hass.config.units.temperature_unit,
|
native_unit_of_measurement=native_temperature_unit,
|
||||||
value=lambda device: device.outdoorTemperature,
|
value=lambda device: device.outdoorTemperature,
|
||||||
),
|
),
|
||||||
location,
|
location,
|
||||||
|
@ -48,10 +48,12 @@ from .const import (
|
|||||||
CONF_MIN_VALUE,
|
CONF_MIN_VALUE,
|
||||||
CONF_PRECISION,
|
CONF_PRECISION,
|
||||||
CONF_SCALE,
|
CONF_SCALE,
|
||||||
|
CONF_SLAVE_COUNT,
|
||||||
CONF_STATE_OFF,
|
CONF_STATE_OFF,
|
||||||
CONF_STATE_ON,
|
CONF_STATE_ON,
|
||||||
CONF_SWAP,
|
CONF_SWAP,
|
||||||
CONF_SWAP_BYTE,
|
CONF_SWAP_BYTE,
|
||||||
|
CONF_SWAP_NONE,
|
||||||
CONF_SWAP_WORD,
|
CONF_SWAP_WORD,
|
||||||
CONF_SWAP_WORD_BYTE,
|
CONF_SWAP_WORD_BYTE,
|
||||||
CONF_VERIFY,
|
CONF_VERIFY,
|
||||||
@ -155,15 +157,25 @@ class BaseStructPlatform(BasePlatform, RestoreEntity):
|
|||||||
"""Initialize the switch."""
|
"""Initialize the switch."""
|
||||||
super().__init__(hub, config)
|
super().__init__(hub, config)
|
||||||
self._swap = config[CONF_SWAP]
|
self._swap = config[CONF_SWAP]
|
||||||
|
if self._swap == CONF_SWAP_NONE:
|
||||||
|
self._swap = None
|
||||||
self._data_type = config[CONF_DATA_TYPE]
|
self._data_type = config[CONF_DATA_TYPE]
|
||||||
self._structure: str = config[CONF_STRUCTURE]
|
self._structure: str = config[CONF_STRUCTURE]
|
||||||
self._precision = config[CONF_PRECISION]
|
self._precision = config[CONF_PRECISION]
|
||||||
self._scale = config[CONF_SCALE]
|
self._scale = config[CONF_SCALE]
|
||||||
self._offset = config[CONF_OFFSET]
|
self._offset = config[CONF_OFFSET]
|
||||||
self._count = config[CONF_COUNT]
|
self._slave_count = config.get(CONF_SLAVE_COUNT, 0)
|
||||||
|
self._slave_size = self._count = config[CONF_COUNT]
|
||||||
|
|
||||||
def _swap_registers(self, registers: list[int]) -> list[int]:
|
def _swap_registers(self, registers: list[int], slave_count: int) -> list[int]:
|
||||||
"""Do swap as needed."""
|
"""Do swap as needed."""
|
||||||
|
if slave_count:
|
||||||
|
swapped = []
|
||||||
|
for i in range(0, self._slave_count + 1):
|
||||||
|
inx = i * self._slave_size
|
||||||
|
inx2 = inx + self._slave_size
|
||||||
|
swapped.extend(self._swap_registers(registers[inx:inx2], 0))
|
||||||
|
return swapped
|
||||||
if self._swap in (CONF_SWAP_BYTE, CONF_SWAP_WORD_BYTE):
|
if self._swap in (CONF_SWAP_BYTE, CONF_SWAP_WORD_BYTE):
|
||||||
# convert [12][34] --> [21][43]
|
# convert [12][34] --> [21][43]
|
||||||
for i, register in enumerate(registers):
|
for i, register in enumerate(registers):
|
||||||
@ -191,7 +203,8 @@ class BaseStructPlatform(BasePlatform, RestoreEntity):
|
|||||||
def unpack_structure_result(self, registers: list[int]) -> str | None:
|
def unpack_structure_result(self, registers: list[int]) -> str | None:
|
||||||
"""Convert registers to proper result."""
|
"""Convert registers to proper result."""
|
||||||
|
|
||||||
registers = self._swap_registers(registers)
|
if self._swap:
|
||||||
|
registers = self._swap_registers(registers, self._slave_count)
|
||||||
byte_string = b"".join([x.to_bytes(2, byteorder="big") for x in registers])
|
byte_string = b"".join([x.to_bytes(2, byteorder="big") for x in registers])
|
||||||
if self._data_type == DataType.STRING:
|
if self._data_type == DataType.STRING:
|
||||||
return byte_string.decode()
|
return byte_string.decode()
|
||||||
|
@ -206,7 +206,7 @@ class ModbusThermostat(BaseStructPlatform, RestoreEntity, ClimateEntity):
|
|||||||
int.from_bytes(as_bytes[i : i + 2], "big")
|
int.from_bytes(as_bytes[i : i + 2], "big")
|
||||||
for i in range(0, len(as_bytes), 2)
|
for i in range(0, len(as_bytes), 2)
|
||||||
]
|
]
|
||||||
registers = self._swap_registers(raw_regs)
|
registers = self._swap_registers(raw_regs, 0)
|
||||||
|
|
||||||
if self._data_type in (
|
if self._data_type in (
|
||||||
DataType.INT16,
|
DataType.INT16,
|
||||||
|
@ -68,7 +68,7 @@ class ModbusRegisterSensor(BaseStructPlatform, RestoreSensor, SensorEntity):
|
|||||||
"""Initialize the modbus register sensor."""
|
"""Initialize the modbus register sensor."""
|
||||||
super().__init__(hub, entry)
|
super().__init__(hub, entry)
|
||||||
if slave_count:
|
if slave_count:
|
||||||
self._count = self._count * slave_count
|
self._count = self._count * (slave_count + 1)
|
||||||
self._coordinator: DataUpdateCoordinator[list[int] | None] | None = None
|
self._coordinator: DataUpdateCoordinator[list[int] | None] | None = None
|
||||||
self._attr_native_unit_of_measurement = entry.get(CONF_UNIT_OF_MEASUREMENT)
|
self._attr_native_unit_of_measurement = entry.get(CONF_UNIT_OF_MEASUREMENT)
|
||||||
self._attr_state_class = entry.get(CONF_STATE_CLASS)
|
self._attr_state_class = entry.get(CONF_STATE_CLASS)
|
||||||
@ -132,10 +132,7 @@ class ModbusRegisterSensor(BaseStructPlatform, RestoreSensor, SensorEntity):
|
|||||||
self._coordinator.async_set_updated_data(None)
|
self._coordinator.async_set_updated_data(None)
|
||||||
else:
|
else:
|
||||||
self._attr_native_value = result
|
self._attr_native_value = result
|
||||||
if self._attr_native_value is None:
|
self._attr_available = self._attr_native_value is not None
|
||||||
self._attr_available = False
|
|
||||||
else:
|
|
||||||
self._attr_available = True
|
|
||||||
self._lazy_errors = self._lazy_error_count
|
self._lazy_errors = self._lazy_error_count
|
||||||
self.async_write_ha_state()
|
self.async_write_ha_state()
|
||||||
|
|
||||||
|
@ -65,25 +65,25 @@ def struct_validator(config: dict[str, Any]) -> dict[str, Any]:
|
|||||||
name = config[CONF_NAME]
|
name = config[CONF_NAME]
|
||||||
structure = config.get(CONF_STRUCTURE)
|
structure = config.get(CONF_STRUCTURE)
|
||||||
slave_count = config.get(CONF_SLAVE_COUNT, 0) + 1
|
slave_count = config.get(CONF_SLAVE_COUNT, 0) + 1
|
||||||
swap_type = config.get(CONF_SWAP)
|
slave = config.get(CONF_SLAVE, 0)
|
||||||
|
swap_type = config.get(CONF_SWAP, CONF_SWAP_NONE)
|
||||||
|
if (
|
||||||
|
slave_count > 1
|
||||||
|
and count > 1
|
||||||
|
and data_type not in (DataType.CUSTOM, DataType.STRING)
|
||||||
|
):
|
||||||
|
error = f"{name} {CONF_COUNT} cannot be mixed with {data_type}"
|
||||||
|
raise vol.Invalid(error)
|
||||||
if config[CONF_DATA_TYPE] != DataType.CUSTOM:
|
if config[CONF_DATA_TYPE] != DataType.CUSTOM:
|
||||||
if structure:
|
if structure:
|
||||||
error = f"{name} structure: cannot be mixed with {data_type}"
|
error = f"{name} structure: cannot be mixed with {data_type}"
|
||||||
raise vol.Invalid(error)
|
|
||||||
if data_type not in DEFAULT_STRUCT_FORMAT:
|
|
||||||
error = f"Error in sensor {name}. data_type `{data_type}` not supported"
|
|
||||||
raise vol.Invalid(error)
|
|
||||||
|
|
||||||
structure = f">{DEFAULT_STRUCT_FORMAT[data_type].struct_id}"
|
if config[CONF_DATA_TYPE] == DataType.CUSTOM:
|
||||||
if CONF_COUNT not in config:
|
if slave or slave_count > 1:
|
||||||
config[CONF_COUNT] = DEFAULT_STRUCT_FORMAT[data_type].register_count
|
error = f"{name}: `{CONF_STRUCTURE}` illegal with `{CONF_SLAVE_COUNT}` / `{CONF_SLAVE}`"
|
||||||
if slave_count > 1:
|
raise vol.Invalid(error)
|
||||||
structure = f">{slave_count}{DEFAULT_STRUCT_FORMAT[data_type].struct_id}"
|
if swap_type != CONF_SWAP_NONE:
|
||||||
else:
|
error = f"{name}: `{CONF_STRUCTURE}` illegal with `{CONF_SWAP}`"
|
||||||
structure = f">{DEFAULT_STRUCT_FORMAT[data_type].struct_id}"
|
|
||||||
else:
|
|
||||||
if slave_count > 1:
|
|
||||||
error = f"{name} structure: cannot be mixed with {CONF_SLAVE_COUNT}"
|
|
||||||
raise vol.Invalid(error)
|
raise vol.Invalid(error)
|
||||||
if not structure:
|
if not structure:
|
||||||
error = (
|
error = (
|
||||||
@ -102,19 +102,37 @@ def struct_validator(config: dict[str, Any]) -> dict[str, Any]:
|
|||||||
f"Structure request {size} bytes, "
|
f"Structure request {size} bytes, "
|
||||||
f"but {count} registers have a size of {bytecount} bytes"
|
f"but {count} registers have a size of {bytecount} bytes"
|
||||||
)
|
)
|
||||||
|
return {
|
||||||
|
**config,
|
||||||
|
CONF_STRUCTURE: structure,
|
||||||
|
CONF_SWAP: swap_type,
|
||||||
|
}
|
||||||
|
if data_type not in DEFAULT_STRUCT_FORMAT:
|
||||||
|
error = f"Error in sensor {name}. data_type `{data_type}` not supported"
|
||||||
|
raise vol.Invalid(error)
|
||||||
|
if slave_count > 1 and data_type == DataType.STRING:
|
||||||
|
error = f"{name}: `{data_type}` illegal with `{CONF_SLAVE_COUNT}`"
|
||||||
|
raise vol.Invalid(error)
|
||||||
|
|
||||||
if swap_type != CONF_SWAP_NONE:
|
if CONF_COUNT not in config:
|
||||||
if swap_type == CONF_SWAP_BYTE:
|
config[CONF_COUNT] = DEFAULT_STRUCT_FORMAT[data_type].register_count
|
||||||
regs_needed = 1
|
if swap_type != CONF_SWAP_NONE:
|
||||||
else: # CONF_SWAP_WORD_BYTE, CONF_SWAP_WORD
|
if swap_type == CONF_SWAP_BYTE:
|
||||||
regs_needed = 2
|
regs_needed = 1
|
||||||
if count < regs_needed or (count % regs_needed) != 0:
|
else: # CONF_SWAP_WORD_BYTE, CONF_SWAP_WORD
|
||||||
raise vol.Invalid(
|
regs_needed = 2
|
||||||
f"Error in sensor {name} swap({swap_type}) "
|
count = config[CONF_COUNT]
|
||||||
"not possible due to the registers "
|
if count < regs_needed or (count % regs_needed) != 0:
|
||||||
f"count: {count}, needed: {regs_needed}"
|
raise vol.Invalid(
|
||||||
)
|
f"Error in sensor {name} swap({swap_type}) "
|
||||||
|
"not possible due to the registers "
|
||||||
|
f"count: {count}, needed: {regs_needed}"
|
||||||
|
)
|
||||||
|
structure = f">{DEFAULT_STRUCT_FORMAT[data_type].struct_id}"
|
||||||
|
if slave_count > 1:
|
||||||
|
structure = f">{slave_count}{DEFAULT_STRUCT_FORMAT[data_type].struct_id}"
|
||||||
|
else:
|
||||||
|
structure = f">{DEFAULT_STRUCT_FORMAT[data_type].struct_id}"
|
||||||
return {
|
return {
|
||||||
**config,
|
**config,
|
||||||
CONF_STRUCTURE: structure,
|
CONF_STRUCTURE: structure,
|
||||||
|
@ -3,7 +3,7 @@ from collections import namedtuple
|
|||||||
import datetime
|
import datetime
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from nessclient import ArmingState, Client
|
from nessclient import ArmingMode, ArmingState, Client
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.components.binary_sensor import (
|
from homeassistant.components.binary_sensor import (
|
||||||
@ -136,9 +136,11 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
|||||||
hass, SIGNAL_ZONE_CHANGED, ZoneChangedData(zone_id=zone_id, state=state)
|
hass, SIGNAL_ZONE_CHANGED, ZoneChangedData(zone_id=zone_id, state=state)
|
||||||
)
|
)
|
||||||
|
|
||||||
def on_state_change(arming_state: ArmingState):
|
def on_state_change(arming_state: ArmingState, arming_mode: ArmingMode | None):
|
||||||
"""Receives and propagates arming state updates."""
|
"""Receives and propagates arming state updates."""
|
||||||
async_dispatcher_send(hass, SIGNAL_ARMING_STATE_CHANGED, arming_state)
|
async_dispatcher_send(
|
||||||
|
hass, SIGNAL_ARMING_STATE_CHANGED, arming_state, arming_mode
|
||||||
|
)
|
||||||
|
|
||||||
client.on_zone_change(on_zone_change)
|
client.on_zone_change(on_zone_change)
|
||||||
client.on_state_change(on_state_change)
|
client.on_state_change(on_state_change)
|
||||||
|
@ -3,12 +3,15 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from nessclient import ArmingState, Client
|
from nessclient import ArmingMode, ArmingState, Client
|
||||||
|
|
||||||
import homeassistant.components.alarm_control_panel as alarm
|
import homeassistant.components.alarm_control_panel as alarm
|
||||||
from homeassistant.components.alarm_control_panel import AlarmControlPanelEntityFeature
|
from homeassistant.components.alarm_control_panel import AlarmControlPanelEntityFeature
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
STATE_ALARM_ARMED_AWAY,
|
STATE_ALARM_ARMED_AWAY,
|
||||||
|
STATE_ALARM_ARMED_HOME,
|
||||||
|
STATE_ALARM_ARMED_NIGHT,
|
||||||
|
STATE_ALARM_ARMED_VACATION,
|
||||||
STATE_ALARM_ARMING,
|
STATE_ALARM_ARMING,
|
||||||
STATE_ALARM_DISARMED,
|
STATE_ALARM_DISARMED,
|
||||||
STATE_ALARM_PENDING,
|
STATE_ALARM_PENDING,
|
||||||
@ -23,6 +26,15 @@ from . import DATA_NESS, SIGNAL_ARMING_STATE_CHANGED
|
|||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
ARMING_MODE_TO_STATE = {
|
||||||
|
ArmingMode.ARMED_AWAY: STATE_ALARM_ARMED_AWAY,
|
||||||
|
ArmingMode.ARMED_HOME: STATE_ALARM_ARMED_HOME,
|
||||||
|
ArmingMode.ARMED_DAY: STATE_ALARM_ARMED_AWAY, # no applicable state, fallback to away
|
||||||
|
ArmingMode.ARMED_NIGHT: STATE_ALARM_ARMED_NIGHT,
|
||||||
|
ArmingMode.ARMED_VACATION: STATE_ALARM_ARMED_VACATION,
|
||||||
|
ArmingMode.ARMED_HIGHEST: STATE_ALARM_ARMED_AWAY, # no applicable state, fallback to away
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_platform(
|
async def async_setup_platform(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
@ -79,7 +91,9 @@ class NessAlarmPanel(alarm.AlarmControlPanelEntity):
|
|||||||
await self._client.panic(code)
|
await self._client.panic(code)
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _handle_arming_state_change(self, arming_state: ArmingState) -> None:
|
def _handle_arming_state_change(
|
||||||
|
self, arming_state: ArmingState, arming_mode: ArmingMode | None
|
||||||
|
) -> None:
|
||||||
"""Handle arming state update."""
|
"""Handle arming state update."""
|
||||||
|
|
||||||
if arming_state == ArmingState.UNKNOWN:
|
if arming_state == ArmingState.UNKNOWN:
|
||||||
@ -91,7 +105,9 @@ class NessAlarmPanel(alarm.AlarmControlPanelEntity):
|
|||||||
elif arming_state == ArmingState.EXIT_DELAY:
|
elif arming_state == ArmingState.EXIT_DELAY:
|
||||||
self._attr_state = STATE_ALARM_ARMING
|
self._attr_state = STATE_ALARM_ARMING
|
||||||
elif arming_state == ArmingState.ARMED:
|
elif arming_state == ArmingState.ARMED:
|
||||||
self._attr_state = STATE_ALARM_ARMED_AWAY
|
self._attr_state = ARMING_MODE_TO_STATE.get(
|
||||||
|
arming_mode, STATE_ALARM_ARMED_AWAY
|
||||||
|
)
|
||||||
elif arming_state == ArmingState.ENTRY_DELAY:
|
elif arming_state == ArmingState.ENTRY_DELAY:
|
||||||
self._attr_state = STATE_ALARM_PENDING
|
self._attr_state = STATE_ALARM_PENDING
|
||||||
elif arming_state == ArmingState.TRIGGERED:
|
elif arming_state == ArmingState.TRIGGERED:
|
||||||
|
@ -5,5 +5,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/ness_alarm",
|
"documentation": "https://www.home-assistant.io/integrations/ness_alarm",
|
||||||
"iot_class": "local_push",
|
"iot_class": "local_push",
|
||||||
"loggers": ["nessclient"],
|
"loggers": ["nessclient"],
|
||||||
"requirements": ["nessclient==0.10.0"]
|
"requirements": ["nessclient==1.0.0"]
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ from opower import (
|
|||||||
InvalidAuth,
|
InvalidAuth,
|
||||||
MeterType,
|
MeterType,
|
||||||
Opower,
|
Opower,
|
||||||
|
ReadResolution,
|
||||||
)
|
)
|
||||||
|
|
||||||
from homeassistant.components.recorder import get_instance
|
from homeassistant.components.recorder import get_instance
|
||||||
@ -177,44 +178,55 @@ class OpowerCoordinator(DataUpdateCoordinator[dict[str, Forecast]]):
|
|||||||
"""Get all cost reads since account activation but at different resolutions depending on age.
|
"""Get all cost reads since account activation but at different resolutions depending on age.
|
||||||
|
|
||||||
- month resolution for all years (since account activation)
|
- month resolution for all years (since account activation)
|
||||||
- day resolution for past 3 years
|
- day resolution for past 3 years (if account's read resolution supports it)
|
||||||
- hour resolution for past 2 months, only for electricity, not gas
|
- hour resolution for past 2 months (if account's read resolution supports it)
|
||||||
"""
|
"""
|
||||||
cost_reads = []
|
cost_reads = []
|
||||||
|
|
||||||
start = None
|
start = None
|
||||||
end = datetime.now() - timedelta(days=3 * 365)
|
end = datetime.now()
|
||||||
|
if account.read_resolution != ReadResolution.BILLING:
|
||||||
|
end -= timedelta(days=3 * 365)
|
||||||
cost_reads += await self.api.async_get_cost_reads(
|
cost_reads += await self.api.async_get_cost_reads(
|
||||||
account, AggregateType.BILL, start, end
|
account, AggregateType.BILL, start, end
|
||||||
)
|
)
|
||||||
|
if account.read_resolution == ReadResolution.BILLING:
|
||||||
|
return cost_reads
|
||||||
|
|
||||||
start = end if not cost_reads else cost_reads[-1].end_time
|
start = end if not cost_reads else cost_reads[-1].end_time
|
||||||
end = (
|
end = datetime.now()
|
||||||
datetime.now() - timedelta(days=2 * 30)
|
if account.read_resolution != ReadResolution.DAY:
|
||||||
if account.meter_type == MeterType.ELEC
|
end -= timedelta(days=2 * 30)
|
||||||
else datetime.now()
|
|
||||||
)
|
|
||||||
cost_reads += await self.api.async_get_cost_reads(
|
cost_reads += await self.api.async_get_cost_reads(
|
||||||
account, AggregateType.DAY, start, end
|
account, AggregateType.DAY, start, end
|
||||||
)
|
)
|
||||||
if account.meter_type == MeterType.ELEC:
|
if account.read_resolution == ReadResolution.DAY:
|
||||||
start = end if not cost_reads else cost_reads[-1].end_time
|
return cost_reads
|
||||||
end = datetime.now()
|
|
||||||
cost_reads += await self.api.async_get_cost_reads(
|
start = end if not cost_reads else cost_reads[-1].end_time
|
||||||
account, AggregateType.HOUR, start, end
|
end = datetime.now()
|
||||||
)
|
cost_reads += await self.api.async_get_cost_reads(
|
||||||
|
account, AggregateType.HOUR, start, end
|
||||||
|
)
|
||||||
return cost_reads
|
return cost_reads
|
||||||
|
|
||||||
async def _async_get_recent_cost_reads(
|
async def _async_get_recent_cost_reads(
|
||||||
self, account: Account, last_stat_time: float
|
self, account: Account, last_stat_time: float
|
||||||
) -> list[CostRead]:
|
) -> list[CostRead]:
|
||||||
"""Get cost reads within the past 30 days to allow corrections in data from utilities.
|
"""Get cost reads within the past 30 days to allow corrections in data from utilities."""
|
||||||
|
if account.read_resolution in [
|
||||||
Hourly for electricity, daily for gas.
|
ReadResolution.HOUR,
|
||||||
"""
|
ReadResolution.HALF_HOUR,
|
||||||
|
ReadResolution.QUARTER_HOUR,
|
||||||
|
]:
|
||||||
|
aggregate_type = AggregateType.HOUR
|
||||||
|
elif account.read_resolution == ReadResolution.DAY:
|
||||||
|
aggregate_type = AggregateType.DAY
|
||||||
|
else:
|
||||||
|
aggregate_type = AggregateType.BILL
|
||||||
return await self.api.async_get_cost_reads(
|
return await self.api.async_get_cost_reads(
|
||||||
account,
|
account,
|
||||||
AggregateType.HOUR
|
aggregate_type,
|
||||||
if account.meter_type == MeterType.ELEC
|
|
||||||
else AggregateType.DAY,
|
|
||||||
datetime.fromtimestamp(last_stat_time) - timedelta(days=30),
|
datetime.fromtimestamp(last_stat_time) - timedelta(days=30),
|
||||||
datetime.now(),
|
datetime.now(),
|
||||||
)
|
)
|
||||||
|
@ -6,5 +6,5 @@
|
|||||||
"dependencies": ["recorder"],
|
"dependencies": ["recorder"],
|
||||||
"documentation": "https://www.home-assistant.io/integrations/opower",
|
"documentation": "https://www.home-assistant.io/integrations/opower",
|
||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"requirements": ["opower==0.0.26"]
|
"requirements": ["opower==0.0.29"]
|
||||||
}
|
}
|
||||||
|
@ -6,5 +6,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/rainbird",
|
"documentation": "https://www.home-assistant.io/integrations/rainbird",
|
||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"loggers": ["pyrainbird"],
|
"loggers": ["pyrainbird"],
|
||||||
"requirements": ["pyrainbird==3.0.0"]
|
"requirements": ["pyrainbird==4.0.0"]
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ from collections.abc import Callable
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
|
||||||
from reolink_aio.api import (
|
from reolink_aio.api import (
|
||||||
|
DUAL_LENS_DUAL_MOTION_MODELS,
|
||||||
FACE_DETECTION_TYPE,
|
FACE_DETECTION_TYPE,
|
||||||
PERSON_DETECTION_TYPE,
|
PERSON_DETECTION_TYPE,
|
||||||
PET_DETECTION_TYPE,
|
PET_DETECTION_TYPE,
|
||||||
@ -128,6 +129,9 @@ class ReolinkBinarySensorEntity(ReolinkChannelCoordinatorEntity, BinarySensorEnt
|
|||||||
super().__init__(reolink_data, channel)
|
super().__init__(reolink_data, channel)
|
||||||
self.entity_description = entity_description
|
self.entity_description = entity_description
|
||||||
|
|
||||||
|
if self._host.api.model in DUAL_LENS_DUAL_MOTION_MODELS:
|
||||||
|
self._attr_name = f"{entity_description.name} lens {self._channel}"
|
||||||
|
|
||||||
self._attr_unique_id = (
|
self._attr_unique_id = (
|
||||||
f"{self._host.unique_id}_{self._channel}_{entity_description.key}"
|
f"{self._host.unique_id}_{self._channel}_{entity_description.key}"
|
||||||
)
|
)
|
||||||
|
@ -163,7 +163,7 @@ class ReolinkHost:
|
|||||||
else:
|
else:
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
"Camera model %s most likely does not push its initial state"
|
"Camera model %s most likely does not push its initial state"
|
||||||
"upon ONVIF subscription, do not check",
|
" upon ONVIF subscription, do not check",
|
||||||
self._api.model,
|
self._api.model,
|
||||||
)
|
)
|
||||||
self._cancel_onvif_check = async_call_later(
|
self._cancel_onvif_check = async_call_later(
|
||||||
|
@ -18,5 +18,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/reolink",
|
"documentation": "https://www.home-assistant.io/integrations/reolink",
|
||||||
"iot_class": "local_push",
|
"iot_class": "local_push",
|
||||||
"loggers": ["reolink_aio"],
|
"loggers": ["reolink_aio"],
|
||||||
"requirements": ["reolink-aio==0.7.6"]
|
"requirements": ["reolink-aio==0.7.7"]
|
||||||
}
|
}
|
||||||
|
@ -6,5 +6,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/roborock",
|
"documentation": "https://www.home-assistant.io/integrations/roborock",
|
||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"loggers": ["roborock"],
|
"loggers": ["roborock"],
|
||||||
"requirements": ["python-roborock==0.32.2"]
|
"requirements": ["python-roborock==0.32.3"]
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"loggers": ["rokuecp"],
|
"loggers": ["rokuecp"],
|
||||||
"quality_scale": "silver",
|
"quality_scale": "silver",
|
||||||
"requirements": ["rokuecp==0.18.0"],
|
"requirements": ["rokuecp==0.18.1"],
|
||||||
"ssdp": [
|
"ssdp": [
|
||||||
{
|
{
|
||||||
"st": "roku:ecp",
|
"st": "roku:ecp",
|
||||||
|
@ -533,7 +533,8 @@ RPC_SENSORS: Final = {
|
|||||||
native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
|
native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
|
||||||
device_class=SensorDeviceClass.CURRENT,
|
device_class=SensorDeviceClass.CURRENT,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
available=lambda status: status["n_current"] is not None,
|
available=lambda status: (status and status["n_current"]) is not None,
|
||||||
|
removal_condition=lambda _config, status, _key: "n_current" not in status,
|
||||||
entity_registry_enabled_default=False,
|
entity_registry_enabled_default=False,
|
||||||
),
|
),
|
||||||
"total_current": RpcSensorDescription(
|
"total_current": RpcSensorDescription(
|
||||||
|
@ -163,11 +163,12 @@ class TadoConnector:
|
|||||||
|
|
||||||
def setup(self):
|
def setup(self):
|
||||||
"""Connect to Tado and fetch the zones."""
|
"""Connect to Tado and fetch the zones."""
|
||||||
self.tado = Tado(self._username, self._password, None, True)
|
self.tado = Tado(self._username, self._password)
|
||||||
|
self.tado.setDebugging(True)
|
||||||
# Load zones and devices
|
# Load zones and devices
|
||||||
self.zones = self.tado.get_zones()
|
self.zones = self.tado.getZones()
|
||||||
self.devices = self.tado.get_devices()
|
self.devices = self.tado.getDevices()
|
||||||
tado_home = self.tado.get_me()["homes"][0]
|
tado_home = self.tado.getMe()["homes"][0]
|
||||||
self.home_id = tado_home["id"]
|
self.home_id = tado_home["id"]
|
||||||
self.home_name = tado_home["name"]
|
self.home_name = tado_home["name"]
|
||||||
|
|
||||||
@ -180,7 +181,7 @@ class TadoConnector:
|
|||||||
|
|
||||||
def update_devices(self):
|
def update_devices(self):
|
||||||
"""Update the device data from Tado."""
|
"""Update the device data from Tado."""
|
||||||
devices = self.tado.get_devices()
|
devices = self.tado.getDevices()
|
||||||
for device in devices:
|
for device in devices:
|
||||||
device_short_serial_no = device["shortSerialNo"]
|
device_short_serial_no = device["shortSerialNo"]
|
||||||
_LOGGER.debug("Updating device %s", device_short_serial_no)
|
_LOGGER.debug("Updating device %s", device_short_serial_no)
|
||||||
@ -189,7 +190,7 @@ class TadoConnector:
|
|||||||
INSIDE_TEMPERATURE_MEASUREMENT
|
INSIDE_TEMPERATURE_MEASUREMENT
|
||||||
in device["characteristics"]["capabilities"]
|
in device["characteristics"]["capabilities"]
|
||||||
):
|
):
|
||||||
device[TEMP_OFFSET] = self.tado.get_device_info(
|
device[TEMP_OFFSET] = self.tado.getDeviceInfo(
|
||||||
device_short_serial_no, TEMP_OFFSET
|
device_short_serial_no, TEMP_OFFSET
|
||||||
)
|
)
|
||||||
except RuntimeError:
|
except RuntimeError:
|
||||||
@ -217,7 +218,7 @@ class TadoConnector:
|
|||||||
def update_zones(self):
|
def update_zones(self):
|
||||||
"""Update the zone data from Tado."""
|
"""Update the zone data from Tado."""
|
||||||
try:
|
try:
|
||||||
zone_states = self.tado.get_zone_states()["zoneStates"]
|
zone_states = self.tado.getZoneStates()["zoneStates"]
|
||||||
except RuntimeError:
|
except RuntimeError:
|
||||||
_LOGGER.error("Unable to connect to Tado while updating zones")
|
_LOGGER.error("Unable to connect to Tado while updating zones")
|
||||||
return
|
return
|
||||||
@ -229,7 +230,7 @@ class TadoConnector:
|
|||||||
"""Update the internal data from Tado."""
|
"""Update the internal data from Tado."""
|
||||||
_LOGGER.debug("Updating zone %s", zone_id)
|
_LOGGER.debug("Updating zone %s", zone_id)
|
||||||
try:
|
try:
|
||||||
data = self.tado.get_zone_state(zone_id)
|
data = self.tado.getZoneState(zone_id)
|
||||||
except RuntimeError:
|
except RuntimeError:
|
||||||
_LOGGER.error("Unable to connect to Tado while updating zone %s", zone_id)
|
_LOGGER.error("Unable to connect to Tado while updating zone %s", zone_id)
|
||||||
return
|
return
|
||||||
@ -250,8 +251,8 @@ class TadoConnector:
|
|||||||
def update_home(self):
|
def update_home(self):
|
||||||
"""Update the home data from Tado."""
|
"""Update the home data from Tado."""
|
||||||
try:
|
try:
|
||||||
self.data["weather"] = self.tado.get_weather()
|
self.data["weather"] = self.tado.getWeather()
|
||||||
self.data["geofence"] = self.tado.get_home_state()
|
self.data["geofence"] = self.tado.getHomeState()
|
||||||
dispatcher_send(
|
dispatcher_send(
|
||||||
self.hass,
|
self.hass,
|
||||||
SIGNAL_TADO_UPDATE_RECEIVED.format(self.home_id, "home", "data"),
|
SIGNAL_TADO_UPDATE_RECEIVED.format(self.home_id, "home", "data"),
|
||||||
@ -264,15 +265,15 @@ class TadoConnector:
|
|||||||
|
|
||||||
def get_capabilities(self, zone_id):
|
def get_capabilities(self, zone_id):
|
||||||
"""Return the capabilities of the devices."""
|
"""Return the capabilities of the devices."""
|
||||||
return self.tado.get_capabilities(zone_id)
|
return self.tado.getCapabilities(zone_id)
|
||||||
|
|
||||||
def get_auto_geofencing_supported(self):
|
def get_auto_geofencing_supported(self):
|
||||||
"""Return whether the Tado Home supports auto geofencing."""
|
"""Return whether the Tado Home supports auto geofencing."""
|
||||||
return self.tado.get_auto_geofencing_supported()
|
return self.tado.getAutoGeofencingSupported()
|
||||||
|
|
||||||
def reset_zone_overlay(self, zone_id):
|
def reset_zone_overlay(self, zone_id):
|
||||||
"""Reset the zone back to the default operation."""
|
"""Reset the zone back to the default operation."""
|
||||||
self.tado.reset_zone_overlay(zone_id)
|
self.tado.resetZoneOverlay(zone_id)
|
||||||
self.update_zone(zone_id)
|
self.update_zone(zone_id)
|
||||||
|
|
||||||
def set_presence(
|
def set_presence(
|
||||||
@ -281,11 +282,11 @@ class TadoConnector:
|
|||||||
):
|
):
|
||||||
"""Set the presence to home, away or auto."""
|
"""Set the presence to home, away or auto."""
|
||||||
if presence == PRESET_AWAY:
|
if presence == PRESET_AWAY:
|
||||||
self.tado.set_away()
|
self.tado.setAway()
|
||||||
elif presence == PRESET_HOME:
|
elif presence == PRESET_HOME:
|
||||||
self.tado.set_home()
|
self.tado.setHome()
|
||||||
elif presence == PRESET_AUTO:
|
elif presence == PRESET_AUTO:
|
||||||
self.tado.set_auto()
|
self.tado.setAuto()
|
||||||
|
|
||||||
# Update everything when changing modes
|
# Update everything when changing modes
|
||||||
self.update_zones()
|
self.update_zones()
|
||||||
@ -319,7 +320,7 @@ class TadoConnector:
|
|||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.tado.set_zone_overlay(
|
self.tado.setZoneOverlay(
|
||||||
zone_id,
|
zone_id,
|
||||||
overlay_mode,
|
overlay_mode,
|
||||||
temperature,
|
temperature,
|
||||||
@ -339,7 +340,7 @@ class TadoConnector:
|
|||||||
def set_zone_off(self, zone_id, overlay_mode, device_type="HEATING"):
|
def set_zone_off(self, zone_id, overlay_mode, device_type="HEATING"):
|
||||||
"""Set a zone to off."""
|
"""Set a zone to off."""
|
||||||
try:
|
try:
|
||||||
self.tado.set_zone_overlay(
|
self.tado.setZoneOverlay(
|
||||||
zone_id, overlay_mode, None, None, device_type, "OFF"
|
zone_id, overlay_mode, None, None, device_type, "OFF"
|
||||||
)
|
)
|
||||||
except RequestException as exc:
|
except RequestException as exc:
|
||||||
@ -350,6 +351,6 @@ class TadoConnector:
|
|||||||
def set_temperature_offset(self, device_id, offset):
|
def set_temperature_offset(self, device_id, offset):
|
||||||
"""Set temperature offset of device."""
|
"""Set temperature offset of device."""
|
||||||
try:
|
try:
|
||||||
self.tado.set_temp_offset(device_id, offset)
|
self.tado.setTempOffset(device_id, offset)
|
||||||
except RequestException as exc:
|
except RequestException as exc:
|
||||||
_LOGGER.error("Could not set temperature offset: %s", exc)
|
_LOGGER.error("Could not set temperature offset: %s", exc)
|
||||||
|
@ -14,5 +14,5 @@
|
|||||||
},
|
},
|
||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"loggers": ["PyTado"],
|
"loggers": ["PyTado"],
|
||||||
"requirements": ["python-tado==0.16.0"]
|
"requirements": ["python-tado==0.15.0"]
|
||||||
}
|
}
|
||||||
|
@ -50,7 +50,6 @@ ENERGY_SENSORS: tuple[TPLinkSensorEntityDescription, ...] = (
|
|||||||
native_unit_of_measurement=UnitOfPower.WATT,
|
native_unit_of_measurement=UnitOfPower.WATT,
|
||||||
device_class=SensorDeviceClass.POWER,
|
device_class=SensorDeviceClass.POWER,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
name="Current Consumption",
|
|
||||||
emeter_attr="power",
|
emeter_attr="power",
|
||||||
precision=1,
|
precision=1,
|
||||||
),
|
),
|
||||||
@ -60,7 +59,6 @@ ENERGY_SENSORS: tuple[TPLinkSensorEntityDescription, ...] = (
|
|||||||
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||||
device_class=SensorDeviceClass.ENERGY,
|
device_class=SensorDeviceClass.ENERGY,
|
||||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||||
name="Total Consumption",
|
|
||||||
emeter_attr="total",
|
emeter_attr="total",
|
||||||
precision=3,
|
precision=3,
|
||||||
),
|
),
|
||||||
@ -70,7 +68,6 @@ ENERGY_SENSORS: tuple[TPLinkSensorEntityDescription, ...] = (
|
|||||||
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||||
device_class=SensorDeviceClass.ENERGY,
|
device_class=SensorDeviceClass.ENERGY,
|
||||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||||
name="Today's Consumption",
|
|
||||||
precision=3,
|
precision=3,
|
||||||
),
|
),
|
||||||
TPLinkSensorEntityDescription(
|
TPLinkSensorEntityDescription(
|
||||||
|
@ -20,16 +20,19 @@ ENTITY_LEGACY_PROVIDER_GROUP = "entity_or_legacy_provider"
|
|||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
PLATFORM_SCHEMA = vol.All(
|
||||||
{
|
cv.has_at_least_one_key(CONF_TTS_SERVICE, CONF_ENTITY_ID),
|
||||||
vol.Required(CONF_NAME): cv.string,
|
PLATFORM_SCHEMA.extend(
|
||||||
vol.Exclusive(CONF_TTS_SERVICE, ENTITY_LEGACY_PROVIDER_GROUP): cv.entity_id,
|
{
|
||||||
vol.Exclusive(CONF_ENTITY_ID, ENTITY_LEGACY_PROVIDER_GROUP): cv.entities_domain(
|
vol.Required(CONF_NAME): cv.string,
|
||||||
DOMAIN
|
vol.Exclusive(CONF_TTS_SERVICE, ENTITY_LEGACY_PROVIDER_GROUP): cv.entity_id,
|
||||||
),
|
vol.Exclusive(
|
||||||
vol.Required(CONF_MEDIA_PLAYER): cv.entity_id,
|
CONF_ENTITY_ID, ENTITY_LEGACY_PROVIDER_GROUP
|
||||||
vol.Optional(ATTR_LANGUAGE): cv.string,
|
): cv.entities_domain(DOMAIN),
|
||||||
}
|
vol.Required(CONF_MEDIA_PLAYER): cv.entity_id,
|
||||||
|
vol.Optional(ATTR_LANGUAGE): cv.string,
|
||||||
|
}
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -83,13 +83,16 @@ class VerisureDataUpdateCoordinator(DataUpdateCoordinator):
|
|||||||
raise UpdateFailed("Could not read overview") from err
|
raise UpdateFailed("Could not read overview") from err
|
||||||
|
|
||||||
def unpack(overview: list, value: str) -> dict | list:
|
def unpack(overview: list, value: str) -> dict | list:
|
||||||
return next(
|
return (
|
||||||
(
|
next(
|
||||||
item["data"]["installation"][value]
|
(
|
||||||
for item in overview
|
item["data"]["installation"][value]
|
||||||
if value in item.get("data", {}).get("installation", {})
|
for item in overview
|
||||||
),
|
if value in item.get("data", {}).get("installation", {})
|
||||||
[],
|
),
|
||||||
|
[],
|
||||||
|
)
|
||||||
|
or []
|
||||||
)
|
)
|
||||||
|
|
||||||
# Store data in a way Home Assistant can easily consume it
|
# Store data in a way Home Assistant can easily consume it
|
||||||
|
@ -438,22 +438,16 @@ class DataManager:
|
|||||||
async def async_get_sleep_summary(self) -> dict[Measurement, Any]:
|
async def async_get_sleep_summary(self) -> dict[Measurement, Any]:
|
||||||
"""Get the sleep summary data."""
|
"""Get the sleep summary data."""
|
||||||
_LOGGER.debug("Updating withing sleep summary")
|
_LOGGER.debug("Updating withing sleep summary")
|
||||||
now = dt_util.utcnow()
|
now = dt_util.now()
|
||||||
yesterday = now - datetime.timedelta(days=1)
|
yesterday = now - datetime.timedelta(days=1)
|
||||||
yesterday_noon = datetime.datetime(
|
yesterday_noon = dt_util.start_of_local_day(yesterday) + datetime.timedelta(
|
||||||
yesterday.year,
|
hours=12
|
||||||
yesterday.month,
|
|
||||||
yesterday.day,
|
|
||||||
12,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
datetime.UTC,
|
|
||||||
)
|
)
|
||||||
|
yesterday_noon_utc = dt_util.as_utc(yesterday_noon)
|
||||||
|
|
||||||
def get_sleep_summary() -> SleepGetSummaryResponse:
|
def get_sleep_summary() -> SleepGetSummaryResponse:
|
||||||
return self._api.sleep_get_summary(
|
return self._api.sleep_get_summary(
|
||||||
lastupdate=yesterday_noon,
|
lastupdate=yesterday_noon_utc,
|
||||||
data_fields=[
|
data_fields=[
|
||||||
GetSleepSummaryField.BREATHING_DISTURBANCES_INTENSITY,
|
GetSleepSummaryField.BREATHING_DISTURBANCES_INTENSITY,
|
||||||
GetSleepSummaryField.DEEP_SLEEP_DURATION,
|
GetSleepSummaryField.DEEP_SLEEP_DURATION,
|
||||||
|
@ -238,7 +238,7 @@ def _parse_custom_effects(effects_config) -> dict[str, dict[str, Any]]:
|
|||||||
def _async_cmd(func):
|
def _async_cmd(func):
|
||||||
"""Define a wrapper to catch exceptions from the bulb."""
|
"""Define a wrapper to catch exceptions from the bulb."""
|
||||||
|
|
||||||
async def _async_wrap(self: YeelightGenericLight, *args, **kwargs):
|
async def _async_wrap(self: YeelightBaseLight, *args, **kwargs):
|
||||||
for attempts in range(2):
|
for attempts in range(2):
|
||||||
try:
|
try:
|
||||||
_LOGGER.debug("Calling %s with %s %s", func, args, kwargs)
|
_LOGGER.debug("Calling %s with %s %s", func, args, kwargs)
|
||||||
@ -403,8 +403,8 @@ def _async_setup_services(hass: HomeAssistant):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class YeelightGenericLight(YeelightEntity, LightEntity):
|
class YeelightBaseLight(YeelightEntity, LightEntity):
|
||||||
"""Representation of a Yeelight generic light."""
|
"""Abstract Yeelight light."""
|
||||||
|
|
||||||
_attr_color_mode = ColorMode.BRIGHTNESS
|
_attr_color_mode = ColorMode.BRIGHTNESS
|
||||||
_attr_supported_color_modes = {ColorMode.BRIGHTNESS}
|
_attr_supported_color_modes = {ColorMode.BRIGHTNESS}
|
||||||
@ -861,7 +861,13 @@ class YeelightGenericLight(YeelightEntity, LightEntity):
|
|||||||
await self._bulb.async_set_scene(scene_class, *args)
|
await self._bulb.async_set_scene(scene_class, *args)
|
||||||
|
|
||||||
|
|
||||||
class YeelightColorLightSupport(YeelightGenericLight):
|
class YeelightGenericLight(YeelightBaseLight):
|
||||||
|
"""Representation of a generic Yeelight."""
|
||||||
|
|
||||||
|
_attr_name = None
|
||||||
|
|
||||||
|
|
||||||
|
class YeelightColorLightSupport(YeelightBaseLight):
|
||||||
"""Representation of a Color Yeelight light support."""
|
"""Representation of a Color Yeelight light support."""
|
||||||
|
|
||||||
_attr_supported_color_modes = {ColorMode.COLOR_TEMP, ColorMode.HS, ColorMode.RGB}
|
_attr_supported_color_modes = {ColorMode.COLOR_TEMP, ColorMode.HS, ColorMode.RGB}
|
||||||
@ -884,7 +890,7 @@ class YeelightColorLightSupport(YeelightGenericLight):
|
|||||||
return YEELIGHT_COLOR_EFFECT_LIST
|
return YEELIGHT_COLOR_EFFECT_LIST
|
||||||
|
|
||||||
|
|
||||||
class YeelightWhiteTempLightSupport(YeelightGenericLight):
|
class YeelightWhiteTempLightSupport(YeelightBaseLight):
|
||||||
"""Representation of a White temp Yeelight light."""
|
"""Representation of a White temp Yeelight light."""
|
||||||
|
|
||||||
_attr_name = None
|
_attr_name = None
|
||||||
@ -904,7 +910,7 @@ class YeelightNightLightSupport:
|
|||||||
return PowerMode.NORMAL
|
return PowerMode.NORMAL
|
||||||
|
|
||||||
|
|
||||||
class YeelightWithoutNightlightSwitchMixIn(YeelightGenericLight):
|
class YeelightWithoutNightlightSwitchMixIn(YeelightBaseLight):
|
||||||
"""A mix-in for yeelights without a nightlight switch."""
|
"""A mix-in for yeelights without a nightlight switch."""
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -940,7 +946,7 @@ class YeelightColorLightWithoutNightlightSwitchLight(
|
|||||||
|
|
||||||
|
|
||||||
class YeelightColorLightWithNightlightSwitch(
|
class YeelightColorLightWithNightlightSwitch(
|
||||||
YeelightNightLightSupport, YeelightColorLightSupport, YeelightGenericLight
|
YeelightNightLightSupport, YeelightColorLightSupport, YeelightBaseLight
|
||||||
):
|
):
|
||||||
"""Representation of a Yeelight with rgb support and nightlight.
|
"""Representation of a Yeelight with rgb support and nightlight.
|
||||||
|
|
||||||
@ -964,7 +970,7 @@ class YeelightWhiteTempWithoutNightlightSwitch(
|
|||||||
|
|
||||||
|
|
||||||
class YeelightWithNightLight(
|
class YeelightWithNightLight(
|
||||||
YeelightNightLightSupport, YeelightWhiteTempLightSupport, YeelightGenericLight
|
YeelightNightLightSupport, YeelightWhiteTempLightSupport, YeelightBaseLight
|
||||||
):
|
):
|
||||||
"""Representation of a Yeelight with temp only support and nightlight.
|
"""Representation of a Yeelight with temp only support and nightlight.
|
||||||
|
|
||||||
@ -979,7 +985,7 @@ class YeelightWithNightLight(
|
|||||||
return super().is_on and not self.device.is_nightlight_enabled
|
return super().is_on and not self.device.is_nightlight_enabled
|
||||||
|
|
||||||
|
|
||||||
class YeelightNightLightMode(YeelightGenericLight):
|
class YeelightNightLightMode(YeelightBaseLight):
|
||||||
"""Representation of a Yeelight when in nightlight mode."""
|
"""Representation of a Yeelight when in nightlight mode."""
|
||||||
|
|
||||||
_attr_color_mode = ColorMode.BRIGHTNESS
|
_attr_color_mode = ColorMode.BRIGHTNESS
|
||||||
|
@ -7,7 +7,7 @@ from typing import Final
|
|||||||
APPLICATION_NAME: Final = "HomeAssistant"
|
APPLICATION_NAME: Final = "HomeAssistant"
|
||||||
MAJOR_VERSION: Final = 2023
|
MAJOR_VERSION: Final = 2023
|
||||||
MINOR_VERSION: Final = 8
|
MINOR_VERSION: Final = 8
|
||||||
PATCH_VERSION: Final = "2"
|
PATCH_VERSION: Final = "3"
|
||||||
__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, 11, 0)
|
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 11, 0)
|
||||||
|
@ -22,7 +22,7 @@ ha-av==10.1.1
|
|||||||
hass-nabucasa==0.69.0
|
hass-nabucasa==0.69.0
|
||||||
hassil==1.2.5
|
hassil==1.2.5
|
||||||
home-assistant-bluetooth==1.10.2
|
home-assistant-bluetooth==1.10.2
|
||||||
home-assistant-frontend==20230802.0
|
home-assistant-frontend==20230802.1
|
||||||
home-assistant-intents==2023.8.2
|
home-assistant-intents==2023.8.2
|
||||||
httpx==0.24.1
|
httpx==0.24.1
|
||||||
ifaddr==0.2.0
|
ifaddr==0.2.0
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
[build-system]
|
[build-system]
|
||||||
requires = ["setuptools~=68.0", "wheel~=0.40.0"]
|
requires = ["setuptools==68.0.0", "wheel~=0.40.0"]
|
||||||
build-backend = "setuptools.build_meta"
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "homeassistant"
|
name = "homeassistant"
|
||||||
version = "2023.8.2"
|
version = "2023.8.3"
|
||||||
license = {text = "Apache-2.0"}
|
license = {text = "Apache-2.0"}
|
||||||
description = "Open-source home automation platform running on Python 3."
|
description = "Open-source home automation platform running on Python 3."
|
||||||
readme = "README.rst"
|
readme = "README.rst"
|
||||||
|
@ -249,7 +249,7 @@ aioguardian==2022.07.0
|
|||||||
aioharmony==0.2.10
|
aioharmony==0.2.10
|
||||||
|
|
||||||
# homeassistant.components.homekit_controller
|
# homeassistant.components.homekit_controller
|
||||||
aiohomekit==2.6.15
|
aiohomekit==2.6.16
|
||||||
|
|
||||||
# homeassistant.components.emulated_hue
|
# homeassistant.components.emulated_hue
|
||||||
# homeassistant.components.http
|
# homeassistant.components.http
|
||||||
@ -800,7 +800,7 @@ fjaraskupan==2.2.0
|
|||||||
flipr-api==1.5.0
|
flipr-api==1.5.0
|
||||||
|
|
||||||
# homeassistant.components.flux_led
|
# homeassistant.components.flux_led
|
||||||
flux-led==1.0.1
|
flux-led==1.0.2
|
||||||
|
|
||||||
# homeassistant.components.homekit
|
# homeassistant.components.homekit
|
||||||
# homeassistant.components.recorder
|
# homeassistant.components.recorder
|
||||||
@ -988,7 +988,7 @@ hole==0.8.0
|
|||||||
holidays==0.28
|
holidays==0.28
|
||||||
|
|
||||||
# homeassistant.components.frontend
|
# homeassistant.components.frontend
|
||||||
home-assistant-frontend==20230802.0
|
home-assistant-frontend==20230802.1
|
||||||
|
|
||||||
# homeassistant.components.conversation
|
# homeassistant.components.conversation
|
||||||
home-assistant-intents==2023.8.2
|
home-assistant-intents==2023.8.2
|
||||||
@ -1243,7 +1243,7 @@ nad-receiver==0.3.0
|
|||||||
ndms2-client==0.1.2
|
ndms2-client==0.1.2
|
||||||
|
|
||||||
# homeassistant.components.ness_alarm
|
# homeassistant.components.ness_alarm
|
||||||
nessclient==0.10.0
|
nessclient==1.0.0
|
||||||
|
|
||||||
# homeassistant.components.netdata
|
# homeassistant.components.netdata
|
||||||
netdata==1.1.0
|
netdata==1.1.0
|
||||||
@ -1368,7 +1368,7 @@ openwrt-luci-rpc==1.1.16
|
|||||||
openwrt-ubus-rpc==0.0.2
|
openwrt-ubus-rpc==0.0.2
|
||||||
|
|
||||||
# homeassistant.components.opower
|
# homeassistant.components.opower
|
||||||
opower==0.0.26
|
opower==0.0.29
|
||||||
|
|
||||||
# homeassistant.components.oralb
|
# homeassistant.components.oralb
|
||||||
oralb-ble==0.17.6
|
oralb-ble==0.17.6
|
||||||
@ -1746,7 +1746,7 @@ pyintesishome==1.8.0
|
|||||||
pyipma==3.0.6
|
pyipma==3.0.6
|
||||||
|
|
||||||
# homeassistant.components.ipp
|
# homeassistant.components.ipp
|
||||||
pyipp==0.14.2
|
pyipp==0.14.3
|
||||||
|
|
||||||
# homeassistant.components.iqvia
|
# homeassistant.components.iqvia
|
||||||
pyiqvia==2022.04.0
|
pyiqvia==2022.04.0
|
||||||
@ -1952,7 +1952,7 @@ pyqwikswitch==0.93
|
|||||||
pyrail==0.0.3
|
pyrail==0.0.3
|
||||||
|
|
||||||
# homeassistant.components.rainbird
|
# homeassistant.components.rainbird
|
||||||
pyrainbird==3.0.0
|
pyrainbird==4.0.0
|
||||||
|
|
||||||
# homeassistant.components.recswitch
|
# homeassistant.components.recswitch
|
||||||
pyrecswitch==1.0.2
|
pyrecswitch==1.0.2
|
||||||
@ -2150,7 +2150,7 @@ python-qbittorrent==0.4.3
|
|||||||
python-ripple-api==0.0.3
|
python-ripple-api==0.0.3
|
||||||
|
|
||||||
# homeassistant.components.roborock
|
# homeassistant.components.roborock
|
||||||
python-roborock==0.32.2
|
python-roborock==0.32.3
|
||||||
|
|
||||||
# homeassistant.components.smarttub
|
# homeassistant.components.smarttub
|
||||||
python-smarttub==0.0.33
|
python-smarttub==0.0.33
|
||||||
@ -2159,7 +2159,7 @@ python-smarttub==0.0.33
|
|||||||
python-songpal==0.15.2
|
python-songpal==0.15.2
|
||||||
|
|
||||||
# homeassistant.components.tado
|
# homeassistant.components.tado
|
||||||
python-tado==0.16.0
|
python-tado==0.15.0
|
||||||
|
|
||||||
# homeassistant.components.telegram_bot
|
# homeassistant.components.telegram_bot
|
||||||
python-telegram-bot==13.1
|
python-telegram-bot==13.1
|
||||||
@ -2278,7 +2278,7 @@ renault-api==0.1.13
|
|||||||
renson-endura-delta==1.5.0
|
renson-endura-delta==1.5.0
|
||||||
|
|
||||||
# homeassistant.components.reolink
|
# homeassistant.components.reolink
|
||||||
reolink-aio==0.7.6
|
reolink-aio==0.7.7
|
||||||
|
|
||||||
# homeassistant.components.idteck_prox
|
# homeassistant.components.idteck_prox
|
||||||
rfk101py==0.0.1
|
rfk101py==0.0.1
|
||||||
@ -2299,7 +2299,7 @@ rjpl==0.3.6
|
|||||||
rocketchat-API==0.6.1
|
rocketchat-API==0.6.1
|
||||||
|
|
||||||
# homeassistant.components.roku
|
# homeassistant.components.roku
|
||||||
rokuecp==0.18.0
|
rokuecp==0.18.1
|
||||||
|
|
||||||
# homeassistant.components.roomba
|
# homeassistant.components.roomba
|
||||||
roombapy==1.6.8
|
roombapy==1.6.8
|
||||||
|
@ -227,7 +227,7 @@ aioguardian==2022.07.0
|
|||||||
aioharmony==0.2.10
|
aioharmony==0.2.10
|
||||||
|
|
||||||
# homeassistant.components.homekit_controller
|
# homeassistant.components.homekit_controller
|
||||||
aiohomekit==2.6.15
|
aiohomekit==2.6.16
|
||||||
|
|
||||||
# homeassistant.components.emulated_hue
|
# homeassistant.components.emulated_hue
|
||||||
# homeassistant.components.http
|
# homeassistant.components.http
|
||||||
@ -628,7 +628,7 @@ fjaraskupan==2.2.0
|
|||||||
flipr-api==1.5.0
|
flipr-api==1.5.0
|
||||||
|
|
||||||
# homeassistant.components.flux_led
|
# homeassistant.components.flux_led
|
||||||
flux-led==1.0.1
|
flux-led==1.0.2
|
||||||
|
|
||||||
# homeassistant.components.homekit
|
# homeassistant.components.homekit
|
||||||
# homeassistant.components.recorder
|
# homeassistant.components.recorder
|
||||||
@ -774,7 +774,7 @@ hole==0.8.0
|
|||||||
holidays==0.28
|
holidays==0.28
|
||||||
|
|
||||||
# homeassistant.components.frontend
|
# homeassistant.components.frontend
|
||||||
home-assistant-frontend==20230802.0
|
home-assistant-frontend==20230802.1
|
||||||
|
|
||||||
# homeassistant.components.conversation
|
# homeassistant.components.conversation
|
||||||
home-assistant-intents==2023.8.2
|
home-assistant-intents==2023.8.2
|
||||||
@ -957,7 +957,7 @@ mutesync==0.0.1
|
|||||||
ndms2-client==0.1.2
|
ndms2-client==0.1.2
|
||||||
|
|
||||||
# homeassistant.components.ness_alarm
|
# homeassistant.components.ness_alarm
|
||||||
nessclient==0.10.0
|
nessclient==1.0.0
|
||||||
|
|
||||||
# homeassistant.components.nmap_tracker
|
# homeassistant.components.nmap_tracker
|
||||||
netmap==0.7.0.2
|
netmap==0.7.0.2
|
||||||
@ -1037,7 +1037,7 @@ openerz-api==0.2.0
|
|||||||
openhomedevice==2.2.0
|
openhomedevice==2.2.0
|
||||||
|
|
||||||
# homeassistant.components.opower
|
# homeassistant.components.opower
|
||||||
opower==0.0.26
|
opower==0.0.29
|
||||||
|
|
||||||
# homeassistant.components.oralb
|
# homeassistant.components.oralb
|
||||||
oralb-ble==0.17.6
|
oralb-ble==0.17.6
|
||||||
@ -1292,7 +1292,7 @@ pyinsteon==1.4.3
|
|||||||
pyipma==3.0.6
|
pyipma==3.0.6
|
||||||
|
|
||||||
# homeassistant.components.ipp
|
# homeassistant.components.ipp
|
||||||
pyipp==0.14.2
|
pyipp==0.14.3
|
||||||
|
|
||||||
# homeassistant.components.iqvia
|
# homeassistant.components.iqvia
|
||||||
pyiqvia==2022.04.0
|
pyiqvia==2022.04.0
|
||||||
@ -1453,7 +1453,7 @@ pyps4-2ndscreen==1.3.1
|
|||||||
pyqwikswitch==0.93
|
pyqwikswitch==0.93
|
||||||
|
|
||||||
# homeassistant.components.rainbird
|
# homeassistant.components.rainbird
|
||||||
pyrainbird==3.0.0
|
pyrainbird==4.0.0
|
||||||
|
|
||||||
# homeassistant.components.risco
|
# homeassistant.components.risco
|
||||||
pyrisco==0.5.7
|
pyrisco==0.5.7
|
||||||
@ -1579,7 +1579,7 @@ python-picnic-api==1.1.0
|
|||||||
python-qbittorrent==0.4.3
|
python-qbittorrent==0.4.3
|
||||||
|
|
||||||
# homeassistant.components.roborock
|
# homeassistant.components.roborock
|
||||||
python-roborock==0.32.2
|
python-roborock==0.32.3
|
||||||
|
|
||||||
# homeassistant.components.smarttub
|
# homeassistant.components.smarttub
|
||||||
python-smarttub==0.0.33
|
python-smarttub==0.0.33
|
||||||
@ -1588,7 +1588,7 @@ python-smarttub==0.0.33
|
|||||||
python-songpal==0.15.2
|
python-songpal==0.15.2
|
||||||
|
|
||||||
# homeassistant.components.tado
|
# homeassistant.components.tado
|
||||||
python-tado==0.16.0
|
python-tado==0.15.0
|
||||||
|
|
||||||
# homeassistant.components.telegram_bot
|
# homeassistant.components.telegram_bot
|
||||||
python-telegram-bot==13.1
|
python-telegram-bot==13.1
|
||||||
@ -1674,7 +1674,7 @@ renault-api==0.1.13
|
|||||||
renson-endura-delta==1.5.0
|
renson-endura-delta==1.5.0
|
||||||
|
|
||||||
# homeassistant.components.reolink
|
# homeassistant.components.reolink
|
||||||
reolink-aio==0.7.6
|
reolink-aio==0.7.7
|
||||||
|
|
||||||
# homeassistant.components.rflink
|
# homeassistant.components.rflink
|
||||||
rflink==0.0.65
|
rflink==0.0.65
|
||||||
@ -1683,7 +1683,7 @@ rflink==0.0.65
|
|||||||
ring-doorbell==0.7.2
|
ring-doorbell==0.7.2
|
||||||
|
|
||||||
# homeassistant.components.roku
|
# homeassistant.components.roku
|
||||||
rokuecp==0.18.0
|
rokuecp==0.18.1
|
||||||
|
|
||||||
# homeassistant.components.roomba
|
# homeassistant.components.roomba
|
||||||
roombapy==1.6.8
|
roombapy==1.6.8
|
||||||
|
@ -77,7 +77,7 @@ def _mocked_ismartgate_closed_door_response():
|
|||||||
ismartgatename="ismartgatename0",
|
ismartgatename="ismartgatename0",
|
||||||
model="ismartgatePRO",
|
model="ismartgatePRO",
|
||||||
apiversion="",
|
apiversion="",
|
||||||
remoteaccessenabled=False,
|
remoteaccessenabled=True,
|
||||||
remoteaccess="abc321.blah.blah",
|
remoteaccess="abc321.blah.blah",
|
||||||
firmwareversion="555",
|
firmwareversion="555",
|
||||||
pin=123,
|
pin=123,
|
||||||
|
@ -340,6 +340,7 @@ async def test_device_info_ismartgate(
|
|||||||
assert device.name == "mycontroller"
|
assert device.name == "mycontroller"
|
||||||
assert device.model == "ismartgatePRO"
|
assert device.model == "ismartgatePRO"
|
||||||
assert device.sw_version == "555"
|
assert device.sw_version == "555"
|
||||||
|
assert device.configuration_url == "https://abc321.blah.blah"
|
||||||
|
|
||||||
|
|
||||||
@patch("homeassistant.components.gogogate2.common.GogoGate2Api")
|
@patch("homeassistant.components.gogogate2.common.GogoGate2Api")
|
||||||
@ -375,3 +376,4 @@ async def test_device_info_gogogate2(
|
|||||||
assert device.name == "mycontroller"
|
assert device.name == "mycontroller"
|
||||||
assert device.model == "gogogate2"
|
assert device.model == "gogogate2"
|
||||||
assert device.sw_version == "222"
|
assert device.sw_version == "222"
|
||||||
|
assert device.configuration_url == "http://127.0.0.1"
|
||||||
|
@ -48,13 +48,13 @@ FAN_ACTION = "fan_action"
|
|||||||
PRESET_HOLD = "Hold"
|
PRESET_HOLD = "Hold"
|
||||||
|
|
||||||
|
|
||||||
async def test_no_thermostats(
|
async def test_no_thermostat_options(
|
||||||
hass: HomeAssistant, device: MagicMock, config_entry: MagicMock
|
hass: HomeAssistant, device: MagicMock, config_entry: MagicMock
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test the setup of the climate entities when there are no appliances available."""
|
"""Test the setup of the climate entities when there are no additional options available."""
|
||||||
device._data = {}
|
device._data = {}
|
||||||
await init_integration(hass, config_entry)
|
await init_integration(hass, config_entry)
|
||||||
assert len(hass.states.async_all()) == 0
|
assert len(hass.states.async_all()) == 1
|
||||||
|
|
||||||
|
|
||||||
async def test_static_attributes(
|
async def test_static_attributes(
|
||||||
|
@ -163,7 +163,6 @@ async def test_number_validator() -> None:
|
|||||||
CONF_COUNT: 2,
|
CONF_COUNT: 2,
|
||||||
CONF_DATA_TYPE: DataType.CUSTOM,
|
CONF_DATA_TYPE: DataType.CUSTOM,
|
||||||
CONF_STRUCTURE: ">i",
|
CONF_STRUCTURE: ">i",
|
||||||
CONF_SWAP: CONF_SWAP_BYTE,
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
@ -221,6 +220,22 @@ async def test_ok_struct_validator(do_config) -> None:
|
|||||||
CONF_STRUCTURE: ">f",
|
CONF_STRUCTURE: ">f",
|
||||||
CONF_SLAVE_COUNT: 5,
|
CONF_SLAVE_COUNT: 5,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
CONF_NAME: TEST_ENTITY_NAME,
|
||||||
|
CONF_DATA_TYPE: DataType.STRING,
|
||||||
|
CONF_SLAVE_COUNT: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
CONF_NAME: TEST_ENTITY_NAME,
|
||||||
|
CONF_DATA_TYPE: DataType.INT16,
|
||||||
|
CONF_SWAP: CONF_SWAP_WORD,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
CONF_NAME: TEST_ENTITY_NAME,
|
||||||
|
CONF_COUNT: 2,
|
||||||
|
CONF_SLAVE_COUNT: 2,
|
||||||
|
CONF_DATA_TYPE: DataType.INT32,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
async def test_exception_struct_validator(do_config) -> None:
|
async def test_exception_struct_validator(do_config) -> None:
|
||||||
|
@ -243,7 +243,7 @@ async def test_config_sensor(hass: HomeAssistant, mock_modbus) -> None:
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
f"Error in sensor {TEST_ENTITY_NAME} swap(word) not possible due to the registers count: 1, needed: 2",
|
f"{TEST_ENTITY_NAME}: `structure` illegal with `swap`",
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
@ -603,9 +603,7 @@ async def test_all_sensor(hass: HomeAssistant, mock_do_cycle, expected) -> None:
|
|||||||
CONF_ADDRESS: 51,
|
CONF_ADDRESS: 51,
|
||||||
CONF_INPUT_TYPE: CALL_TYPE_REGISTER_HOLDING,
|
CONF_INPUT_TYPE: CALL_TYPE_REGISTER_HOLDING,
|
||||||
CONF_DATA_TYPE: DataType.UINT32,
|
CONF_DATA_TYPE: DataType.UINT32,
|
||||||
CONF_SCALE: 1,
|
CONF_SCAN_INTERVAL: 1,
|
||||||
CONF_OFFSET: 0,
|
|
||||||
CONF_PRECISION: 0,
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@ -677,17 +675,184 @@ async def test_all_sensor(hass: HomeAssistant, mock_do_cycle, expected) -> None:
|
|||||||
)
|
)
|
||||||
async def test_slave_sensor(hass: HomeAssistant, mock_do_cycle, expected) -> None:
|
async def test_slave_sensor(hass: HomeAssistant, mock_do_cycle, expected) -> None:
|
||||||
"""Run test for sensor."""
|
"""Run test for sensor."""
|
||||||
assert hass.states.get(ENTITY_ID).state == expected[0]
|
|
||||||
entity_registry = er.async_get(hass)
|
entity_registry = er.async_get(hass)
|
||||||
|
for i in range(0, len(expected)):
|
||||||
for i in range(1, len(expected)):
|
entity_id = f"{SENSOR_DOMAIN}.{TEST_ENTITY_NAME}".replace(" ", "_")
|
||||||
entity_id = f"{SENSOR_DOMAIN}.{TEST_ENTITY_NAME}_{i}".replace(" ", "_")
|
unique_id = f"{SLAVE_UNIQUE_ID}"
|
||||||
assert hass.states.get(entity_id).state == expected[i]
|
if i:
|
||||||
unique_id = f"{SLAVE_UNIQUE_ID}_{i}"
|
entity_id = f"{entity_id}_{i}"
|
||||||
|
unique_id = f"{unique_id}_{i}"
|
||||||
entry = entity_registry.async_get(entity_id)
|
entry = entity_registry.async_get(entity_id)
|
||||||
|
state = hass.states.get(entity_id).state
|
||||||
|
assert state == expected[i]
|
||||||
assert entry.unique_id == unique_id
|
assert entry.unique_id == unique_id
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"do_config",
|
||||||
|
[
|
||||||
|
{
|
||||||
|
CONF_SENSORS: [
|
||||||
|
{
|
||||||
|
CONF_NAME: TEST_ENTITY_NAME,
|
||||||
|
CONF_ADDRESS: 51,
|
||||||
|
CONF_INPUT_TYPE: CALL_TYPE_REGISTER_HOLDING,
|
||||||
|
CONF_SCAN_INTERVAL: 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
)
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("config_addon", "register_words", "do_exception", "expected"),
|
||||||
|
[
|
||||||
|
(
|
||||||
|
{
|
||||||
|
CONF_SLAVE_COUNT: 0,
|
||||||
|
CONF_UNIQUE_ID: SLAVE_UNIQUE_ID,
|
||||||
|
CONF_SWAP: CONF_SWAP_BYTE,
|
||||||
|
CONF_DATA_TYPE: DataType.UINT16,
|
||||||
|
},
|
||||||
|
[0x0102],
|
||||||
|
False,
|
||||||
|
[str(int(0x0201))],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
{
|
||||||
|
CONF_SLAVE_COUNT: 0,
|
||||||
|
CONF_UNIQUE_ID: SLAVE_UNIQUE_ID,
|
||||||
|
CONF_SWAP: CONF_SWAP_WORD,
|
||||||
|
CONF_DATA_TYPE: DataType.UINT32,
|
||||||
|
},
|
||||||
|
[0x0102, 0x0304],
|
||||||
|
False,
|
||||||
|
[str(int(0x03040102))],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
{
|
||||||
|
CONF_SLAVE_COUNT: 0,
|
||||||
|
CONF_UNIQUE_ID: SLAVE_UNIQUE_ID,
|
||||||
|
CONF_SWAP: CONF_SWAP_WORD,
|
||||||
|
CONF_DATA_TYPE: DataType.UINT64,
|
||||||
|
},
|
||||||
|
[0x0102, 0x0304, 0x0506, 0x0708],
|
||||||
|
False,
|
||||||
|
[str(int(0x0708050603040102))],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
{
|
||||||
|
CONF_SLAVE_COUNT: 1,
|
||||||
|
CONF_UNIQUE_ID: SLAVE_UNIQUE_ID,
|
||||||
|
CONF_DATA_TYPE: DataType.UINT16,
|
||||||
|
CONF_SWAP: CONF_SWAP_BYTE,
|
||||||
|
},
|
||||||
|
[0x0102, 0x0304],
|
||||||
|
False,
|
||||||
|
[str(int(0x0201)), str(int(0x0403))],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
{
|
||||||
|
CONF_SLAVE_COUNT: 1,
|
||||||
|
CONF_UNIQUE_ID: SLAVE_UNIQUE_ID,
|
||||||
|
CONF_DATA_TYPE: DataType.UINT32,
|
||||||
|
CONF_SWAP: CONF_SWAP_WORD,
|
||||||
|
},
|
||||||
|
[0x0102, 0x0304, 0x0506, 0x0708],
|
||||||
|
False,
|
||||||
|
[str(int(0x03040102)), str(int(0x07080506))],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
{
|
||||||
|
CONF_SLAVE_COUNT: 1,
|
||||||
|
CONF_UNIQUE_ID: SLAVE_UNIQUE_ID,
|
||||||
|
CONF_DATA_TYPE: DataType.UINT64,
|
||||||
|
CONF_SWAP: CONF_SWAP_WORD,
|
||||||
|
},
|
||||||
|
[0x0102, 0x0304, 0x0506, 0x0708, 0x0901, 0x0902, 0x0903, 0x0904],
|
||||||
|
False,
|
||||||
|
[str(int(0x0708050603040102)), str(int(0x0904090309020901))],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
{
|
||||||
|
CONF_SLAVE_COUNT: 3,
|
||||||
|
CONF_UNIQUE_ID: SLAVE_UNIQUE_ID,
|
||||||
|
CONF_DATA_TYPE: DataType.UINT16,
|
||||||
|
CONF_SWAP: CONF_SWAP_BYTE,
|
||||||
|
},
|
||||||
|
[0x0102, 0x0304, 0x0506, 0x0708],
|
||||||
|
False,
|
||||||
|
[str(int(0x0201)), str(int(0x0403)), str(int(0x0605)), str(int(0x0807))],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
{
|
||||||
|
CONF_SLAVE_COUNT: 3,
|
||||||
|
CONF_UNIQUE_ID: SLAVE_UNIQUE_ID,
|
||||||
|
CONF_DATA_TYPE: DataType.UINT32,
|
||||||
|
CONF_SWAP: CONF_SWAP_WORD,
|
||||||
|
},
|
||||||
|
[
|
||||||
|
0x0102,
|
||||||
|
0x0304,
|
||||||
|
0x0506,
|
||||||
|
0x0708,
|
||||||
|
0x090A,
|
||||||
|
0x0B0C,
|
||||||
|
0x0D0E,
|
||||||
|
0x0F00,
|
||||||
|
],
|
||||||
|
False,
|
||||||
|
[
|
||||||
|
str(int(0x03040102)),
|
||||||
|
str(int(0x07080506)),
|
||||||
|
str(int(0x0B0C090A)),
|
||||||
|
str(int(0x0F000D0E)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
{
|
||||||
|
CONF_SLAVE_COUNT: 3,
|
||||||
|
CONF_UNIQUE_ID: SLAVE_UNIQUE_ID,
|
||||||
|
CONF_DATA_TYPE: DataType.UINT64,
|
||||||
|
CONF_SWAP: CONF_SWAP_WORD,
|
||||||
|
},
|
||||||
|
[
|
||||||
|
0x0601,
|
||||||
|
0x0602,
|
||||||
|
0x0603,
|
||||||
|
0x0604,
|
||||||
|
0x0701,
|
||||||
|
0x0702,
|
||||||
|
0x0703,
|
||||||
|
0x0704,
|
||||||
|
0x0801,
|
||||||
|
0x0802,
|
||||||
|
0x0803,
|
||||||
|
0x0804,
|
||||||
|
0x0901,
|
||||||
|
0x0902,
|
||||||
|
0x0903,
|
||||||
|
0x0904,
|
||||||
|
],
|
||||||
|
False,
|
||||||
|
[
|
||||||
|
str(int(0x0604060306020601)),
|
||||||
|
str(int(0x0704070307020701)),
|
||||||
|
str(int(0x0804080308020801)),
|
||||||
|
str(int(0x0904090309020901)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_slave_swap_sensor(hass: HomeAssistant, mock_do_cycle, expected) -> None:
|
||||||
|
"""Run test for sensor."""
|
||||||
|
for i in range(0, len(expected)):
|
||||||
|
entity_id = f"{SENSOR_DOMAIN}.{TEST_ENTITY_NAME}".replace(" ", "_")
|
||||||
|
if i:
|
||||||
|
entity_id = f"{entity_id}_{i}"
|
||||||
|
state = hass.states.get(entity_id).state
|
||||||
|
assert state == expected[i]
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"do_config",
|
"do_config",
|
||||||
[
|
[
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
"""Tests for the ness_alarm component."""
|
"""Tests for the ness_alarm component."""
|
||||||
from enum import Enum
|
|
||||||
from unittest.mock import MagicMock, patch
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
|
from nessclient import ArmingMode, ArmingState
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from homeassistant.components import alarm_control_panel
|
from homeassistant.components import alarm_control_panel
|
||||||
@ -24,6 +24,8 @@ from homeassistant.const import (
|
|||||||
SERVICE_ALARM_DISARM,
|
SERVICE_ALARM_DISARM,
|
||||||
SERVICE_ALARM_TRIGGER,
|
SERVICE_ALARM_TRIGGER,
|
||||||
STATE_ALARM_ARMED_AWAY,
|
STATE_ALARM_ARMED_AWAY,
|
||||||
|
STATE_ALARM_ARMED_HOME,
|
||||||
|
STATE_ALARM_ARMED_NIGHT,
|
||||||
STATE_ALARM_ARMING,
|
STATE_ALARM_ARMING,
|
||||||
STATE_ALARM_DISARMED,
|
STATE_ALARM_DISARMED,
|
||||||
STATE_ALARM_PENDING,
|
STATE_ALARM_PENDING,
|
||||||
@ -84,7 +86,7 @@ async def test_dispatch_state_change(hass: HomeAssistant, mock_nessclient) -> No
|
|||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
on_state_change = mock_nessclient.on_state_change.call_args[0][0]
|
on_state_change = mock_nessclient.on_state_change.call_args[0][0]
|
||||||
on_state_change(MockArmingState.ARMING)
|
on_state_change(ArmingState.ARMING, None)
|
||||||
|
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert hass.states.is_state("alarm_control_panel.alarm_panel", STATE_ALARM_ARMING)
|
assert hass.states.is_state("alarm_control_panel.alarm_panel", STATE_ALARM_ARMING)
|
||||||
@ -174,13 +176,16 @@ async def test_dispatch_zone_change(hass: HomeAssistant, mock_nessclient) -> Non
|
|||||||
async def test_arming_state_change(hass: HomeAssistant, mock_nessclient) -> None:
|
async def test_arming_state_change(hass: HomeAssistant, mock_nessclient) -> None:
|
||||||
"""Test arming state change handing."""
|
"""Test arming state change handing."""
|
||||||
states = [
|
states = [
|
||||||
(MockArmingState.UNKNOWN, STATE_UNKNOWN),
|
(ArmingState.UNKNOWN, None, STATE_UNKNOWN),
|
||||||
(MockArmingState.DISARMED, STATE_ALARM_DISARMED),
|
(ArmingState.DISARMED, None, STATE_ALARM_DISARMED),
|
||||||
(MockArmingState.ARMING, STATE_ALARM_ARMING),
|
(ArmingState.ARMING, None, STATE_ALARM_ARMING),
|
||||||
(MockArmingState.EXIT_DELAY, STATE_ALARM_ARMING),
|
(ArmingState.EXIT_DELAY, None, STATE_ALARM_ARMING),
|
||||||
(MockArmingState.ARMED, STATE_ALARM_ARMED_AWAY),
|
(ArmingState.ARMED, None, STATE_ALARM_ARMED_AWAY),
|
||||||
(MockArmingState.ENTRY_DELAY, STATE_ALARM_PENDING),
|
(ArmingState.ARMED, ArmingMode.ARMED_AWAY, STATE_ALARM_ARMED_AWAY),
|
||||||
(MockArmingState.TRIGGERED, STATE_ALARM_TRIGGERED),
|
(ArmingState.ARMED, ArmingMode.ARMED_HOME, STATE_ALARM_ARMED_HOME),
|
||||||
|
(ArmingState.ARMED, ArmingMode.ARMED_NIGHT, STATE_ALARM_ARMED_NIGHT),
|
||||||
|
(ArmingState.ENTRY_DELAY, None, STATE_ALARM_PENDING),
|
||||||
|
(ArmingState.TRIGGERED, None, STATE_ALARM_TRIGGERED),
|
||||||
]
|
]
|
||||||
|
|
||||||
await async_setup_component(hass, DOMAIN, VALID_CONFIG)
|
await async_setup_component(hass, DOMAIN, VALID_CONFIG)
|
||||||
@ -188,24 +193,12 @@ async def test_arming_state_change(hass: HomeAssistant, mock_nessclient) -> None
|
|||||||
assert hass.states.is_state("alarm_control_panel.alarm_panel", STATE_UNKNOWN)
|
assert hass.states.is_state("alarm_control_panel.alarm_panel", STATE_UNKNOWN)
|
||||||
on_state_change = mock_nessclient.on_state_change.call_args[0][0]
|
on_state_change = mock_nessclient.on_state_change.call_args[0][0]
|
||||||
|
|
||||||
for arming_state, expected_state in states:
|
for arming_state, arming_mode, expected_state in states:
|
||||||
on_state_change(arming_state)
|
on_state_change(arming_state, arming_mode)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert hass.states.is_state("alarm_control_panel.alarm_panel", expected_state)
|
assert hass.states.is_state("alarm_control_panel.alarm_panel", expected_state)
|
||||||
|
|
||||||
|
|
||||||
class MockArmingState(Enum):
|
|
||||||
"""Mock nessclient.ArmingState enum."""
|
|
||||||
|
|
||||||
UNKNOWN = "UNKNOWN"
|
|
||||||
DISARMED = "DISARMED"
|
|
||||||
ARMING = "ARMING"
|
|
||||||
EXIT_DELAY = "EXIT_DELAY"
|
|
||||||
ARMED = "ARMED"
|
|
||||||
ENTRY_DELAY = "ENTRY_DELAY"
|
|
||||||
TRIGGERED = "TRIGGERED"
|
|
||||||
|
|
||||||
|
|
||||||
class MockClient:
|
class MockClient:
|
||||||
"""Mock nessclient.Client stub."""
|
"""Mock nessclient.Client stub."""
|
||||||
|
|
||||||
@ -253,10 +246,5 @@ def mock_nessclient():
|
|||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.ness_alarm.Client", new=_mock_factory, create=True
|
"homeassistant.components.ness_alarm.Client", new=_mock_factory, create=True
|
||||||
), patch(
|
|
||||||
"homeassistant.components.ness_alarm.ArmingState", new=MockArmingState
|
|
||||||
), patch(
|
|
||||||
"homeassistant.components.ness_alarm.alarm_control_panel.ArmingState",
|
|
||||||
new=MockArmingState,
|
|
||||||
):
|
):
|
||||||
yield _mock_instance
|
yield _mock_instance
|
||||||
|
@ -68,6 +68,21 @@ async def test_setup_platform(hass: HomeAssistant) -> None:
|
|||||||
assert hass.services.has_service(notify.DOMAIN, "tts_test")
|
assert hass.services.has_service(notify.DOMAIN, "tts_test")
|
||||||
|
|
||||||
|
|
||||||
|
async def test_setup_platform_missing_key(hass: HomeAssistant) -> None:
|
||||||
|
"""Test platform without required tts_service or entity_id key."""
|
||||||
|
config = {
|
||||||
|
notify.DOMAIN: {
|
||||||
|
"platform": "tts",
|
||||||
|
"name": "tts_test",
|
||||||
|
"media_player": "media_player.demo",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
with assert_setup_component(0, notify.DOMAIN):
|
||||||
|
assert await async_setup_component(hass, notify.DOMAIN, config)
|
||||||
|
|
||||||
|
assert not hass.services.has_service(notify.DOMAIN, "tts_test")
|
||||||
|
|
||||||
|
|
||||||
async def test_setup_legacy_service(hass: HomeAssistant) -> None:
|
async def test_setup_legacy_service(hass: HomeAssistant) -> None:
|
||||||
"""Set up the demo platform and call service."""
|
"""Set up the demo platform and call service."""
|
||||||
calls = async_mock_service(hass, DOMAIN_MP, SERVICE_PLAY_MEDIA)
|
calls = async_mock_service(hass, DOMAIN_MP, SERVICE_PLAY_MEDIA)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user