Merge pull request #73821 from home-assistant/rc

This commit is contained in:
Franck Nijhof 2022-06-22 13:22:39 +02:00 committed by GitHub
commit f97e95134b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
58 changed files with 773 additions and 312 deletions

View File

@ -5,7 +5,7 @@ set_comfort_mode:
description: >
Enable comfort mode on your AC.
fields:
Name:
name:
description: >
String with device name.
required: true
@ -18,14 +18,14 @@ send_comfort_feedback:
description: >
Send feedback for comfort mode.
fields:
Name:
name:
description: >
String with device name.
required: true
example: Bedroom
selector:
text:
Value:
value:
description: >
Send any of the following comfort values: too_hot, too_warm, bit_warm, comfortable, bit_cold, too_cold, freezing
required: true
@ -38,14 +38,14 @@ set_temperature_mode:
description: >
Enable temperature mode on your AC.
fields:
Name:
name:
description: >
String with device name.
required: true
example: Bedroom
selector:
text:
Value:
value:
description: >
Target value in celsius
required: true

View File

@ -3,7 +3,7 @@
"name": "Big Ass Fans",
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/baf",
"requirements": ["aiobafi6==0.5.0"],
"requirements": ["aiobafi6==0.6.0"],
"codeowners": ["@bdraco", "@jfroy"],
"iot_class": "local_push",
"zeroconf": [

View File

@ -83,8 +83,7 @@ class BrotherConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
self, discovery_info: zeroconf.ZeroconfServiceInfo
) -> FlowResult:
"""Handle zeroconf discovery."""
# Hostname is format: brother.local.
self.host = discovery_info.hostname.rstrip(".")
self.host = discovery_info.host
# Do not probe the device if the host is already configured
self._async_abort_entries_match({CONF_HOST: self.host})
@ -102,7 +101,7 @@ class BrotherConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
# Check if already configured
await self.async_set_unique_id(self.brother.serial.lower())
self._abort_if_unique_id_configured()
self._abort_if_unique_id_configured({CONF_HOST: self.host})
self.context.update(
{

View File

@ -96,7 +96,7 @@ SENSOR_TYPES: Final[tuple[FritzSensorEntityDescription, ...]] = (
device_class=SensorDeviceClass.VOLTAGE,
state_class=SensorStateClass.MEASUREMENT,
suitable=lambda device: device.has_powermeter, # type: ignore[no-any-return]
native_value=lambda device: device.voltage / 1000
native_value=lambda device: device.voltage
if getattr(device, "voltage", None)
else 0.0,
),
@ -107,7 +107,7 @@ SENSOR_TYPES: Final[tuple[FritzSensorEntityDescription, ...]] = (
device_class=SensorDeviceClass.CURRENT,
state_class=SensorStateClass.MEASUREMENT,
suitable=lambda device: device.has_powermeter, # type: ignore[no-any-return]
native_value=lambda device: device.power / device.voltage
native_value=lambda device: device.power / device.voltage / 1000
if device.power and getattr(device, "voltage", None)
else 0.0,
),

View File

@ -3,7 +3,7 @@
"name": "Growatt",
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/growatt_server/",
"requirements": ["growattServer==1.1.0"],
"requirements": ["growattServer==1.2.2"],
"codeowners": ["@indykoning", "@muppet3000", "@JasperPlant"],
"iot_class": "cloud_polling",
"loggers": ["growattServer"]

View File

@ -99,8 +99,6 @@ def get_properties(device: Device, show_advanced=False):
continue
prop_schema = get_schema(prop, name, device.groups)
if name == "momentary_delay":
print(prop_schema)
if prop_schema is None:
continue
schema[name] = prop_schema
@ -216,7 +214,7 @@ async def websocket_write_properties(
result = await device.async_write_config()
await devices.async_save(workdir=hass.config.config_dir)
if result != ResponseStatus.SUCCESS:
if result not in [ResponseStatus.SUCCESS, ResponseStatus.RUN_ON_WAKE]:
connection.send_message(
websocket_api.error_message(
msg[ID], "write_failed", "properties not written to device"
@ -244,9 +242,10 @@ async def websocket_load_properties(
notify_device_not_found(connection, msg, INSTEON_DEVICE_NOT_FOUND)
return
result, _ = await device.async_read_config(read_aldb=False)
result = await device.async_read_config(read_aldb=False)
await devices.async_save(workdir=hass.config.config_dir)
if result != ResponseStatus.SUCCESS:
if result not in [ResponseStatus.SUCCESS, ResponseStatus.RUN_ON_WAKE]:
connection.send_message(
websocket_api.error_message(
msg[ID], "load_failed", "properties not loaded from device"

View File

@ -4,8 +4,8 @@
"documentation": "https://www.home-assistant.io/integrations/insteon",
"dependencies": ["http", "websocket_api"],
"requirements": [
"pyinsteon==1.1.0",
"insteon-frontend-home-assistant==0.1.0"
"pyinsteon==1.1.1",
"insteon-frontend-home-assistant==0.1.1"
],
"codeowners": ["@teharris1"],
"dhcp": [

View File

@ -196,9 +196,8 @@ def async_register_services(hass):
for address in devices:
device = devices[address]
if device != devices.modem and device.cat != 0x03:
await device.aldb.async_load(
refresh=reload, callback=async_srv_save_devices
)
await device.aldb.async_load(refresh=reload)
await async_srv_save_devices()
async def async_srv_save_devices():
"""Write the Insteon device configuration to file."""

View File

@ -7,6 +7,7 @@ import datapoint
from homeassistant.helpers.update_coordinator import UpdateFailed
from homeassistant.util.dt import utcnow
from .const import MODE_3HOURLY
from .data import MetOfficeData
_LOGGER = logging.getLogger(__name__)
@ -39,6 +40,9 @@ def fetch_data(connection: datapoint.Manager, site, mode) -> MetOfficeData:
for day in forecast.days
for timestep in day.timesteps
if timestep.date > time_now
and (
mode == MODE_3HOURLY or timestep.date.hour > 6
) # ensures only one result per day in MODE_DAILY
],
site,
)

View File

@ -47,7 +47,11 @@ from .client import ( # noqa: F401
publish,
subscribe,
)
from .config import CONFIG_SCHEMA_BASE, DEFAULT_VALUES, DEPRECATED_CONFIG_KEYS
from .config_integration import (
CONFIG_SCHEMA_BASE,
DEFAULT_VALUES,
DEPRECATED_CONFIG_KEYS,
)
from .const import ( # noqa: F401
ATTR_PAYLOAD,
ATTR_QOS,
@ -76,6 +80,7 @@ from .const import ( # noqa: F401
MQTT_DISCONNECTED,
MQTT_RELOADED,
PLATFORMS,
RELOADABLE_PLATFORMS,
)
from .models import ( # noqa: F401
MqttCommandTemplate,
@ -378,7 +383,7 @@ async def async_setup_entry( # noqa: C901
# Setup reload service. Once support for legacy config is removed in 2022.9, we
# should no longer call async_setup_reload_service but instead implement a custom
# service
await async_setup_reload_service(hass, DOMAIN, PLATFORMS)
await async_setup_reload_service(hass, DOMAIN, RELOADABLE_PLATFORMS)
async def _async_reload_platforms(_: Event | None) -> None:
"""Discover entities for a platform."""

View File

@ -148,7 +148,7 @@ async def async_setup_entry(
"""Set up MQTT alarm control panel through configuration.yaml and dynamically through MQTT discovery."""
# load and initialize platform config from configuration.yaml
config_entry.async_on_unload(
await async_setup_platform_discovery(hass, alarm.DOMAIN, PLATFORM_SCHEMA_MODERN)
await async_setup_platform_discovery(hass, alarm.DOMAIN)
)
# setup for discovery
setup = functools.partial(

View File

@ -103,9 +103,7 @@ async def async_setup_entry(
"""Set up MQTT binary sensor through configuration.yaml and dynamically through MQTT discovery."""
# load and initialize platform config from configuration.yaml
config_entry.async_on_unload(
await async_setup_platform_discovery(
hass, binary_sensor.DOMAIN, PLATFORM_SCHEMA_MODERN
)
await async_setup_platform_discovery(hass, binary_sensor.DOMAIN)
)
# setup for discovery
setup = functools.partial(

View File

@ -83,9 +83,7 @@ async def async_setup_entry(
"""Set up MQTT button through configuration.yaml and dynamically through MQTT discovery."""
# load and initialize platform config from configuration.yaml
config_entry.async_on_unload(
await async_setup_platform_discovery(
hass, button.DOMAIN, PLATFORM_SCHEMA_MODERN
)
await async_setup_platform_discovery(hass, button.DOMAIN)
)
# setup for discovery
setup = functools.partial(

View File

@ -81,9 +81,7 @@ async def async_setup_entry(
"""Set up MQTT camera through configuration.yaml and dynamically through MQTT discovery."""
# load and initialize platform config from configuration.yaml
config_entry.async_on_unload(
await async_setup_platform_discovery(
hass, camera.DOMAIN, PLATFORM_SCHEMA_MODERN
)
await async_setup_platform_discovery(hass, camera.DOMAIN)
)
# setup for discovery
setup = functools.partial(

View File

@ -392,9 +392,7 @@ async def async_setup_entry(
"""Set up MQTT climate device through configuration.yaml and dynamically through MQTT discovery."""
# load and initialize platform config from configuration.yaml
config_entry.async_on_unload(
await async_setup_platform_discovery(
hass, climate.DOMAIN, PLATFORM_SCHEMA_MODERN
)
await async_setup_platform_discovery(hass, climate.DOMAIN)
)
# setup for discovery
setup = functools.partial(

View File

@ -3,126 +3,21 @@ from __future__ import annotations
import voluptuous as vol
from homeassistant.const import (
CONF_CLIENT_ID,
CONF_DISCOVERY,
CONF_PASSWORD,
CONF_PORT,
CONF_PROTOCOL,
CONF_USERNAME,
CONF_VALUE_TEMPLATE,
)
from homeassistant.const import CONF_VALUE_TEMPLATE
from homeassistant.helpers import config_validation as cv
from .const import (
ATTR_PAYLOAD,
ATTR_QOS,
ATTR_RETAIN,
ATTR_TOPIC,
CONF_BIRTH_MESSAGE,
CONF_BROKER,
CONF_CERTIFICATE,
CONF_CLIENT_CERT,
CONF_CLIENT_KEY,
CONF_COMMAND_TOPIC,
CONF_DISCOVERY_PREFIX,
CONF_ENCODING,
CONF_KEEPALIVE,
CONF_QOS,
CONF_RETAIN,
CONF_STATE_TOPIC,
CONF_TLS_INSECURE,
CONF_TLS_VERSION,
CONF_WILL_MESSAGE,
DEFAULT_BIRTH,
DEFAULT_DISCOVERY,
DEFAULT_ENCODING,
DEFAULT_PREFIX,
DEFAULT_QOS,
DEFAULT_RETAIN,
DEFAULT_WILL,
PLATFORMS,
PROTOCOL_31,
PROTOCOL_311,
)
from .util import _VALID_QOS_SCHEMA, valid_publish_topic, valid_subscribe_topic
DEFAULT_PORT = 1883
DEFAULT_KEEPALIVE = 60
DEFAULT_PROTOCOL = PROTOCOL_311
DEFAULT_TLS_PROTOCOL = "auto"
DEFAULT_VALUES = {
CONF_BIRTH_MESSAGE: DEFAULT_BIRTH,
CONF_DISCOVERY: DEFAULT_DISCOVERY,
CONF_PORT: DEFAULT_PORT,
CONF_TLS_VERSION: DEFAULT_TLS_PROTOCOL,
CONF_WILL_MESSAGE: DEFAULT_WILL,
}
CLIENT_KEY_AUTH_MSG = (
"client_key and client_cert must both be present in "
"the MQTT broker configuration"
)
MQTT_WILL_BIRTH_SCHEMA = vol.Schema(
{
vol.Inclusive(ATTR_TOPIC, "topic_payload"): valid_publish_topic,
vol.Inclusive(ATTR_PAYLOAD, "topic_payload"): cv.string,
vol.Optional(ATTR_QOS, default=DEFAULT_QOS): _VALID_QOS_SCHEMA,
vol.Optional(ATTR_RETAIN, default=DEFAULT_RETAIN): cv.boolean,
},
required=True,
)
PLATFORM_CONFIG_SCHEMA_BASE = vol.Schema(
{vol.Optional(platform.value): cv.ensure_list for platform in PLATFORMS}
)
CONFIG_SCHEMA_BASE = PLATFORM_CONFIG_SCHEMA_BASE.extend(
{
vol.Optional(CONF_CLIENT_ID): cv.string,
vol.Optional(CONF_KEEPALIVE, default=DEFAULT_KEEPALIVE): vol.All(
vol.Coerce(int), vol.Range(min=15)
),
vol.Optional(CONF_BROKER): cv.string,
vol.Optional(CONF_PORT): cv.port,
vol.Optional(CONF_USERNAME): cv.string,
vol.Optional(CONF_PASSWORD): cv.string,
vol.Optional(CONF_CERTIFICATE): vol.Any("auto", cv.isfile),
vol.Inclusive(
CONF_CLIENT_KEY, "client_key_auth", msg=CLIENT_KEY_AUTH_MSG
): cv.isfile,
vol.Inclusive(
CONF_CLIENT_CERT, "client_key_auth", msg=CLIENT_KEY_AUTH_MSG
): cv.isfile,
vol.Optional(CONF_TLS_INSECURE): cv.boolean,
vol.Optional(CONF_TLS_VERSION): vol.Any("auto", "1.0", "1.1", "1.2"),
vol.Optional(CONF_PROTOCOL, default=DEFAULT_PROTOCOL): vol.All(
cv.string, vol.In([PROTOCOL_31, PROTOCOL_311])
),
vol.Optional(CONF_WILL_MESSAGE): MQTT_WILL_BIRTH_SCHEMA,
vol.Optional(CONF_BIRTH_MESSAGE): MQTT_WILL_BIRTH_SCHEMA,
vol.Optional(CONF_DISCOVERY): cv.boolean,
# discovery_prefix must be a valid publish topic because if no
# state topic is specified, it will be created with the given prefix.
vol.Optional(
CONF_DISCOVERY_PREFIX, default=DEFAULT_PREFIX
): valid_publish_topic,
}
)
DEPRECATED_CONFIG_KEYS = [
CONF_BIRTH_MESSAGE,
CONF_BROKER,
CONF_DISCOVERY,
CONF_PASSWORD,
CONF_PORT,
CONF_TLS_VERSION,
CONF_USERNAME,
CONF_WILL_MESSAGE,
]
SCHEMA_BASE = {
vol.Optional(CONF_QOS, default=DEFAULT_QOS): _VALID_QOS_SCHEMA,
vol.Optional(CONF_ENCODING, default=DEFAULT_ENCODING): cv.string,

View File

@ -0,0 +1,193 @@
"""Support for MQTT platform config setup."""
from __future__ import annotations
import voluptuous as vol
from homeassistant.const import (
CONF_CLIENT_ID,
CONF_DISCOVERY,
CONF_PASSWORD,
CONF_PORT,
CONF_PROTOCOL,
CONF_USERNAME,
Platform,
)
from homeassistant.helpers import config_validation as cv
from . import (
alarm_control_panel as alarm_control_panel_platform,
binary_sensor as binary_sensor_platform,
button as button_platform,
camera as camera_platform,
climate as climate_platform,
cover as cover_platform,
device_tracker as device_tracker_platform,
fan as fan_platform,
humidifier as humidifier_platform,
light as light_platform,
lock as lock_platform,
number as number_platform,
scene as scene_platform,
select as select_platform,
sensor as sensor_platform,
siren as siren_platform,
switch as switch_platform,
vacuum as vacuum_platform,
)
from .const import (
ATTR_PAYLOAD,
ATTR_QOS,
ATTR_RETAIN,
ATTR_TOPIC,
CONF_BIRTH_MESSAGE,
CONF_BROKER,
CONF_CERTIFICATE,
CONF_CLIENT_CERT,
CONF_CLIENT_KEY,
CONF_DISCOVERY_PREFIX,
CONF_KEEPALIVE,
CONF_TLS_INSECURE,
CONF_TLS_VERSION,
CONF_WILL_MESSAGE,
DEFAULT_BIRTH,
DEFAULT_DISCOVERY,
DEFAULT_PREFIX,
DEFAULT_QOS,
DEFAULT_RETAIN,
DEFAULT_WILL,
PROTOCOL_31,
PROTOCOL_311,
)
from .util import _VALID_QOS_SCHEMA, valid_publish_topic
DEFAULT_PORT = 1883
DEFAULT_KEEPALIVE = 60
DEFAULT_PROTOCOL = PROTOCOL_311
DEFAULT_TLS_PROTOCOL = "auto"
DEFAULT_VALUES = {
CONF_BIRTH_MESSAGE: DEFAULT_BIRTH,
CONF_DISCOVERY: DEFAULT_DISCOVERY,
CONF_PORT: DEFAULT_PORT,
CONF_TLS_VERSION: DEFAULT_TLS_PROTOCOL,
CONF_WILL_MESSAGE: DEFAULT_WILL,
}
PLATFORM_CONFIG_SCHEMA_BASE = vol.Schema(
{
Platform.ALARM_CONTROL_PANEL.value: vol.All(
cv.ensure_list, [alarm_control_panel_platform.PLATFORM_SCHEMA_MODERN] # type: ignore[has-type]
),
Platform.BINARY_SENSOR.value: vol.All(
cv.ensure_list, [binary_sensor_platform.PLATFORM_SCHEMA_MODERN] # type: ignore[has-type]
),
Platform.BUTTON.value: vol.All(
cv.ensure_list, [button_platform.PLATFORM_SCHEMA_MODERN] # type: ignore[has-type]
),
Platform.CAMERA.value: vol.All(
cv.ensure_list, [camera_platform.PLATFORM_SCHEMA_MODERN] # type: ignore[has-type]
),
Platform.CLIMATE.value: vol.All(
cv.ensure_list, [climate_platform.PLATFORM_SCHEMA_MODERN] # type: ignore[has-type]
),
Platform.COVER.value: vol.All(
cv.ensure_list, [cover_platform.PLATFORM_SCHEMA_MODERN] # type: ignore[has-type]
),
Platform.DEVICE_TRACKER.value: vol.All(
cv.ensure_list, [device_tracker_platform.PLATFORM_SCHEMA_MODERN] # type: ignore[has-type]
),
Platform.FAN.value: vol.All(
cv.ensure_list, [fan_platform.PLATFORM_SCHEMA_MODERN] # type: ignore[has-type]
),
Platform.HUMIDIFIER.value: vol.All(
cv.ensure_list, [humidifier_platform.PLATFORM_SCHEMA_MODERN] # type: ignore[has-type]
),
Platform.LOCK.value: vol.All(
cv.ensure_list, [lock_platform.PLATFORM_SCHEMA_MODERN] # type: ignore[has-type]
),
Platform.LIGHT.value: vol.All(
cv.ensure_list, [light_platform.PLATFORM_SCHEMA_MODERN] # type: ignore[has-type]
),
Platform.NUMBER.value: vol.All(
cv.ensure_list, [number_platform.PLATFORM_SCHEMA_MODERN] # type: ignore[has-type]
),
Platform.SCENE.value: vol.All(
cv.ensure_list, [scene_platform.PLATFORM_SCHEMA_MODERN] # type: ignore[has-type]
),
Platform.SELECT.value: vol.All(
cv.ensure_list, [select_platform.PLATFORM_SCHEMA_MODERN] # type: ignore[has-type]
),
Platform.SENSOR.value: vol.All(
cv.ensure_list, [sensor_platform.PLATFORM_SCHEMA_MODERN] # type: ignore[has-type]
),
Platform.SIREN.value: vol.All(
cv.ensure_list, [siren_platform.PLATFORM_SCHEMA_MODERN] # type: ignore[has-type]
),
Platform.SWITCH.value: vol.All(
cv.ensure_list, [switch_platform.PLATFORM_SCHEMA_MODERN] # type: ignore[has-type]
),
Platform.VACUUM.value: vol.All(
cv.ensure_list, [vacuum_platform.PLATFORM_SCHEMA_MODERN] # type: ignore[has-type]
),
}
)
CLIENT_KEY_AUTH_MSG = (
"client_key and client_cert must both be present in "
"the MQTT broker configuration"
)
MQTT_WILL_BIRTH_SCHEMA = vol.Schema(
{
vol.Inclusive(ATTR_TOPIC, "topic_payload"): valid_publish_topic,
vol.Inclusive(ATTR_PAYLOAD, "topic_payload"): cv.string,
vol.Optional(ATTR_QOS, default=DEFAULT_QOS): _VALID_QOS_SCHEMA,
vol.Optional(ATTR_RETAIN, default=DEFAULT_RETAIN): cv.boolean,
},
required=True,
)
CONFIG_SCHEMA_BASE = PLATFORM_CONFIG_SCHEMA_BASE.extend(
{
vol.Optional(CONF_CLIENT_ID): cv.string,
vol.Optional(CONF_KEEPALIVE, default=DEFAULT_KEEPALIVE): vol.All(
vol.Coerce(int), vol.Range(min=15)
),
vol.Optional(CONF_BROKER): cv.string,
vol.Optional(CONF_PORT): cv.port,
vol.Optional(CONF_USERNAME): cv.string,
vol.Optional(CONF_PASSWORD): cv.string,
vol.Optional(CONF_CERTIFICATE): vol.Any("auto", cv.isfile),
vol.Inclusive(
CONF_CLIENT_KEY, "client_key_auth", msg=CLIENT_KEY_AUTH_MSG
): cv.isfile,
vol.Inclusive(
CONF_CLIENT_CERT, "client_key_auth", msg=CLIENT_KEY_AUTH_MSG
): cv.isfile,
vol.Optional(CONF_TLS_INSECURE): cv.boolean,
vol.Optional(CONF_TLS_VERSION): vol.Any("auto", "1.0", "1.1", "1.2"),
vol.Optional(CONF_PROTOCOL, default=DEFAULT_PROTOCOL): vol.All(
cv.string, vol.In([PROTOCOL_31, PROTOCOL_311])
),
vol.Optional(CONF_WILL_MESSAGE): MQTT_WILL_BIRTH_SCHEMA,
vol.Optional(CONF_BIRTH_MESSAGE): MQTT_WILL_BIRTH_SCHEMA,
vol.Optional(CONF_DISCOVERY): cv.boolean,
# discovery_prefix must be a valid publish topic because if no
# state topic is specified, it will be created with the given prefix.
vol.Optional(
CONF_DISCOVERY_PREFIX, default=DEFAULT_PREFIX
): valid_publish_topic,
}
)
DEPRECATED_CONFIG_KEYS = [
CONF_BIRTH_MESSAGE,
CONF_BROKER,
CONF_DISCOVERY,
CONF_PASSWORD,
CONF_PORT,
CONF_TLS_VERSION,
CONF_USERNAME,
CONF_WILL_MESSAGE,
]

View File

@ -92,3 +92,23 @@ PLATFORMS = [
Platform.SWITCH,
Platform.VACUUM,
]
RELOADABLE_PLATFORMS = [
Platform.ALARM_CONTROL_PANEL,
Platform.BINARY_SENSOR,
Platform.BUTTON,
Platform.CAMERA,
Platform.CLIMATE,
Platform.COVER,
Platform.FAN,
Platform.HUMIDIFIER,
Platform.LIGHT,
Platform.LOCK,
Platform.NUMBER,
Platform.SELECT,
Platform.SCENE,
Platform.SENSOR,
Platform.SIREN,
Platform.SWITCH,
Platform.VACUUM,
]

View File

@ -241,7 +241,7 @@ async def async_setup_entry(
"""Set up MQTT cover through configuration.yaml and dynamically through MQTT discovery."""
# load and initialize platform config from configuration.yaml
config_entry.async_on_unload(
await async_setup_platform_discovery(hass, cover.DOMAIN, PLATFORM_SCHEMA_MODERN)
await async_setup_platform_discovery(hass, cover.DOMAIN)
)
# setup for discovery
setup = functools.partial(

View File

@ -4,6 +4,7 @@ import voluptuous as vol
from homeassistant.components import device_tracker
from ..mixins import warn_for_legacy_schema
from .schema_discovery import PLATFORM_SCHEMA_MODERN # noqa: F401
from .schema_discovery import async_setup_entry_from_discovery
from .schema_yaml import PLATFORM_SCHEMA_YAML, async_setup_scanner_from_yaml

View File

@ -54,7 +54,7 @@ async def async_setup_entry_from_discovery(hass, config_entry, async_add_entitie
*(
_async_setup_entity(hass, async_add_entities, config, config_entry)
for config in await async_get_platform_config_from_yaml(
hass, device_tracker.DOMAIN, PLATFORM_SCHEMA_MODERN
hass, device_tracker.DOMAIN
)
)
)

View File

@ -231,9 +231,7 @@ async def async_setup_entry(
) -> None:
"""Set up MQTT fan through configuration.yaml and dynamically through MQTT discovery."""
# load and initialize platform config from configuration.yaml
config_entry.async_on_unload(
await async_setup_platform_discovery(hass, fan.DOMAIN, PLATFORM_SCHEMA_MODERN)
)
config_entry.async_on_unload(await async_setup_platform_discovery(hass, fan.DOMAIN))
# setup for discovery
setup = functools.partial(
_async_setup_entity, hass, async_add_entities, config_entry=config_entry

View File

@ -188,9 +188,7 @@ async def async_setup_entry(
"""Set up MQTT humidifier through configuration.yaml and dynamically through MQTT discovery."""
# load and initialize platform config from configuration.yaml
config_entry.async_on_unload(
await async_setup_platform_discovery(
hass, humidifier.DOMAIN, PLATFORM_SCHEMA_MODERN
)
await async_setup_platform_discovery(hass, humidifier.DOMAIN)
)
# setup for discovery
setup = functools.partial(

View File

@ -112,7 +112,7 @@ async def async_setup_entry(
"""Set up MQTT lights configured under the light platform key (deprecated)."""
# load and initialize platform config from configuration.yaml
config_entry.async_on_unload(
await async_setup_platform_discovery(hass, light.DOMAIN, PLATFORM_SCHEMA_MODERN)
await async_setup_platform_discovery(hass, light.DOMAIN)
)
# setup for discovery
setup = functools.partial(

View File

@ -103,7 +103,7 @@ async def async_setup_entry(
"""Set up MQTT lock through configuration.yaml and dynamically through MQTT discovery."""
# load and initialize platform config from configuration.yaml
config_entry.async_on_unload(
await async_setup_platform_discovery(hass, lock.DOMAIN, PLATFORM_SCHEMA_MODERN)
await async_setup_platform_discovery(hass, lock.DOMAIN)
)
# setup for discovery
setup = functools.partial(

View File

@ -10,7 +10,6 @@ from typing import Any, Protocol, cast, final
import voluptuous as vol
from homeassistant.config import async_log_exception
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
ATTR_CONFIGURATION_URL,
@ -263,7 +262,7 @@ class SetupEntity(Protocol):
async def async_setup_platform_discovery(
hass: HomeAssistant, platform_domain: str, schema: vol.Schema
hass: HomeAssistant, platform_domain: str
) -> CALLBACK_TYPE:
"""Set up platform discovery for manual config."""
@ -282,7 +281,7 @@ async def async_setup_platform_discovery(
*(
discovery.async_load_platform(hass, platform_domain, DOMAIN, config, {})
for config in await async_get_platform_config_from_yaml(
hass, platform_domain, schema, config_yaml
hass, platform_domain, config_yaml
)
)
)
@ -295,32 +294,17 @@ async def async_setup_platform_discovery(
async def async_get_platform_config_from_yaml(
hass: HomeAssistant,
platform_domain: str,
schema: vol.Schema,
config_yaml: ConfigType = None,
) -> list[ConfigType]:
"""Return a list of validated configurations for the domain."""
def async_validate_config(
hass: HomeAssistant,
config: list[ConfigType],
) -> list[ConfigType]:
"""Validate config."""
validated_config = []
for config_item in config:
try:
validated_config.append(schema(config_item))
except vol.MultipleInvalid as err:
async_log_exception(err, platform_domain, config_item, hass)
return validated_config
if config_yaml is None:
config_yaml = hass.data.get(DATA_MQTT_CONFIG)
if not config_yaml:
return []
if not (platform_configs := config_yaml.get(platform_domain)):
return []
return async_validate_config(hass, platform_configs)
return platform_configs
async def async_setup_entry_helper(hass, domain, async_setup, schema):

View File

@ -134,9 +134,7 @@ async def async_setup_entry(
"""Set up MQTT number through configuration.yaml and dynamically through MQTT discovery."""
# load and initialize platform config from configuration.yaml
config_entry.async_on_unload(
await async_setup_platform_discovery(
hass, number.DOMAIN, PLATFORM_SCHEMA_MODERN
)
await async_setup_platform_discovery(hass, number.DOMAIN)
)
# setup for discovery
setup = functools.partial(

View File

@ -80,7 +80,7 @@ async def async_setup_entry(
"""Set up MQTT scene through configuration.yaml and dynamically through MQTT discovery."""
# load and initialize platform config from configuration.yaml
config_entry.async_on_unload(
await async_setup_platform_discovery(hass, scene.DOMAIN, PLATFORM_SCHEMA_MODERN)
await async_setup_platform_discovery(hass, scene.DOMAIN)
)
# setup for discovery
setup = functools.partial(

View File

@ -95,9 +95,7 @@ async def async_setup_entry(
"""Set up MQTT select through configuration.yaml and dynamically through MQTT discovery."""
# load and initialize platform config from configuration.yaml
config_entry.async_on_unload(
await async_setup_platform_discovery(
hass, select.DOMAIN, PLATFORM_SCHEMA_MODERN
)
await async_setup_platform_discovery(hass, select.DOMAIN)
)
# setup for discovery
setup = functools.partial(

View File

@ -148,9 +148,7 @@ async def async_setup_entry(
"""Set up MQTT sensor through configuration.yaml and dynamically through MQTT discovery."""
# load and initialize platform config from configuration.yaml
config_entry.async_on_unload(
await async_setup_platform_discovery(
hass, sensor.DOMAIN, PLATFORM_SCHEMA_MODERN
)
await async_setup_platform_discovery(hass, sensor.DOMAIN)
)
# setup for discovery
setup = functools.partial(

View File

@ -144,7 +144,7 @@ async def async_setup_entry(
"""Set up MQTT siren through configuration.yaml and dynamically through MQTT discovery."""
# load and initialize platform config from configuration.yaml
config_entry.async_on_unload(
await async_setup_platform_discovery(hass, siren.DOMAIN, PLATFORM_SCHEMA_MODERN)
await async_setup_platform_discovery(hass, siren.DOMAIN)
)
# setup for discovery
setup = functools.partial(

View File

@ -98,9 +98,7 @@ async def async_setup_entry(
"""Set up MQTT switch through configuration.yaml and dynamically through MQTT discovery."""
# load and initialize platform config from configuration.yaml
config_entry.async_on_unload(
await async_setup_platform_discovery(
hass, switch.DOMAIN, PLATFORM_SCHEMA_MODERN
)
await async_setup_platform_discovery(hass, switch.DOMAIN)
)
# setup for discovery
setup = functools.partial(

View File

@ -92,9 +92,7 @@ async def async_setup_entry(
"""Set up MQTT vacuum through configuration.yaml and dynamically through MQTT discovery."""
# load and initialize platform config from configuration.yaml
config_entry.async_on_unload(
await async_setup_platform_discovery(
hass, vacuum.DOMAIN, PLATFORM_SCHEMA_MODERN
)
await async_setup_platform_discovery(hass, vacuum.DOMAIN)
)
# setup for discovery
setup = functools.partial(

View File

@ -391,7 +391,9 @@ class NexiaZone(NexiaThermostatZoneEntity, ClimateEntity):
async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
"""Set the system mode (Auto, Heat_Cool, Cool, Heat, etc)."""
if hvac_mode == HVACMode.AUTO:
if hvac_mode == HVACMode.OFF:
await self._zone.call_permanent_off()
elif hvac_mode == HVACMode.AUTO:
await self._zone.call_return_to_schedule()
await self._zone.set_mode(mode=OPERATION_MODE_AUTO)
else:

View File

@ -1,7 +1,7 @@
{
"domain": "nexia",
"name": "Nexia/American Standard/Trane",
"requirements": ["nexia==1.0.1"],
"requirements": ["nexia==1.0.2"],
"codeowners": ["@bdraco"],
"documentation": "https://www.home-assistant.io/integrations/nexia",
"config_flow": true,

View File

@ -3,6 +3,7 @@ from __future__ import annotations
from typing import Any
from nexia.const import OPERATION_MODE_OFF
from nexia.home import NexiaHome
from nexia.thermostat import NexiaThermostat
from nexia.zone import NexiaThermostatZone
@ -58,7 +59,10 @@ class NexiaHoldSwitch(NexiaThermostatZoneEntity, SwitchEntity):
async def async_turn_on(self, **kwargs: Any) -> None:
"""Enable permanent hold."""
await self._zone.call_permanent_hold()
if self._zone.get_current_mode() == OPERATION_MODE_OFF:
await self._zone.call_permanent_off()
else:
await self._zone.call_permanent_hold()
self._signal_zone_update()
async def async_turn_off(self, **kwargs: Any) -> None:

View File

@ -3,6 +3,7 @@ from __future__ import annotations
from aiooncue import OncueDevice, OncueSensor
from homeassistant.const import ATTR_CONNECTIONS
from homeassistant.helpers import device_registry as dr
from homeassistant.helpers.entity import DeviceInfo, Entity, EntityDescription
from homeassistant.helpers.update_coordinator import (
@ -30,16 +31,21 @@ class OncueEntity(CoordinatorEntity, Entity):
self._device_id = device_id
self._attr_unique_id = f"{device_id}_{description.key}"
self._attr_name = f"{device.name} {sensor.display_name}"
mac_address_hex = hex(int(device.sensors["MacAddress"].value))[2:]
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, device_id)},
connections={(dr.CONNECTION_NETWORK_MAC, mac_address_hex)},
name=device.name,
hw_version=device.hardware_version,
sw_version=device.sensors["FirmwareVersion"].display_value,
model=device.sensors["GensetModelNumberSelect"].display_value,
manufacturer="Kohler",
)
try:
mac_address_hex = hex(int(device.sensors["MacAddress"].value))[2:]
except ValueError: # MacAddress may be invalid if the gateway is offline
return
self._attr_device_info[ATTR_CONNECTIONS] = {
(dr.CONNECTION_NETWORK_MAC, mac_address_hex)
}
@property
def _oncue_value(self) -> str:

View File

@ -1,4 +1,6 @@
"""ONVIF event parsers."""
from __future__ import annotations
from collections.abc import Callable, Coroutine
import datetime
from typing import Any
@ -12,16 +14,16 @@ from .models import Event
PARSERS: Registry[str, Callable[[str, Any], Coroutine[Any, Any, Event]]] = Registry()
def datetime_or_zero(value: str) -> datetime:
"""Convert strings to datetimes, if invalid, return datetime.min."""
def local_datetime_or_none(value: str) -> datetime.datetime | None:
"""Convert strings to datetimes, if invalid, return None."""
# To handle cameras that return times like '0000-00-00T00:00:00Z' (e.g. hikvision)
try:
ret = dt_util.parse_datetime(value)
except ValueError:
return datetime.datetime.min
if ret is None:
return datetime.datetime.min
return ret
return None
if ret is not None:
return dt_util.as_local(ret)
return None
@PARSERS.register("tns1:VideoSource/MotionAlarm")
@ -394,14 +396,16 @@ async def async_parse_last_reboot(uid: str, msg) -> Event:
Topic: tns1:Monitoring/OperatingTime/LastReboot
"""
try:
date_time = datetime_or_zero(msg.Message._value_1.Data.SimpleItem[0].Value)
date_time = local_datetime_or_none(
msg.Message._value_1.Data.SimpleItem[0].Value
)
return Event(
f"{uid}_{msg.Topic._value_1}",
"Last Reboot",
"sensor",
"timestamp",
None,
dt_util.as_local(date_time),
date_time,
EntityCategory.DIAGNOSTIC,
)
except (AttributeError, KeyError):
@ -416,14 +420,16 @@ async def async_parse_last_reset(uid: str, msg) -> Event:
Topic: tns1:Monitoring/OperatingTime/LastReset
"""
try:
date_time = datetime_or_zero(msg.Message._value_1.Data.SimpleItem[0].Value)
date_time = local_datetime_or_none(
msg.Message._value_1.Data.SimpleItem[0].Value
)
return Event(
f"{uid}_{msg.Topic._value_1}",
"Last Reset",
"sensor",
"timestamp",
None,
dt_util.as_local(date_time),
date_time,
EntityCategory.DIAGNOSTIC,
entity_enabled=False,
)
@ -440,14 +446,16 @@ async def async_parse_backup_last(uid: str, msg) -> Event:
"""
try:
date_time = datetime_or_zero(msg.Message._value_1.Data.SimpleItem[0].Value)
date_time = local_datetime_or_none(
msg.Message._value_1.Data.SimpleItem[0].Value
)
return Event(
f"{uid}_{msg.Topic._value_1}",
"Last Backup",
"sensor",
"timestamp",
None,
dt_util.as_local(date_time),
date_time,
EntityCategory.DIAGNOSTIC,
entity_enabled=False,
)
@ -463,14 +471,16 @@ async def async_parse_last_clock_sync(uid: str, msg) -> Event:
Topic: tns1:Monitoring/OperatingTime/LastClockSynchronization
"""
try:
date_time = datetime_or_zero(msg.Message._value_1.Data.SimpleItem[0].Value)
date_time = local_datetime_or_none(
msg.Message._value_1.Data.SimpleItem[0].Value
)
return Event(
f"{uid}_{msg.Topic._value_1}",
"Last Clock Synchronization",
"sensor",
"timestamp",
None,
dt_util.as_local(date_time),
date_time,
EntityCategory.DIAGNOSTIC,
entity_enabled=False,
)

View File

@ -113,7 +113,7 @@ def migrate_sensor_entities(
# Migrating opentherm_outdoor_temperature to opentherm_outdoor_air_temperature sensor
for device_id, device in coordinator.data.devices.items():
if device["dev_class"] != "heater_central":
if device.get("dev_class") != "heater_central":
continue
old_unique_id = f"{device_id}-outdoor_temperature"

View File

@ -5,6 +5,7 @@ import logging
from sense_energy import (
ASyncSenseable,
SenseAPIException,
SenseAuthenticationException,
SenseMFARequiredException,
)
@ -84,6 +85,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
raise ConfigEntryNotReady(
str(err) or "Timed out during authentication"
) from err
except SenseAPIException as err:
raise ConfigEntryNotReady(str(err)) from err
sense_devices_data = SenseDevicesData()
try:

View File

@ -351,7 +351,7 @@ class Scanner:
async def async_start(self) -> None:
"""Start the scanners."""
session = async_get_clientsession(self.hass)
session = async_get_clientsession(self.hass, verify_ssl=False)
requester = AiohttpSessionRequester(session, True, 10)
self._description_cache = DescriptionCache(requester)

View File

@ -108,10 +108,8 @@ class TwitchSensor(SensorEntity):
def update(self) -> None:
"""Update device state."""
followers = self._client.get_users_follows(to_id=self.unique_id)["total"]
channel = self._client.get_users(user_ids=[self.unique_id])["data"][0]
self._attr_extra_state_attributes = {
ATTR_FOLLOWING: followers,
ATTR_VIEWS: channel["view_count"],
@ -151,8 +149,13 @@ class TwitchSensor(SensorEntity):
self._attr_extra_state_attributes[ATTR_GAME] = stream["game_name"]
self._attr_extra_state_attributes[ATTR_TITLE] = stream["title"]
self._attr_entity_picture = stream["thumbnail_url"]
if self._attr_entity_picture is not None:
self._attr_entity_picture = self._attr_entity_picture.format(
height=24,
width=24,
)
else:
self._attr_native_value = STATE_OFFLINE
self._attr_extra_state_attributes[ATTR_GAME] = None
self._attr_extra_state_attributes[ATTR_TITLE] = None
self._attr_entity_picture = channel["offline_image_url"]
self._attr_entity_picture = channel["profile_image_url"]

View File

@ -3,7 +3,7 @@
"name": "UniFi Network",
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/unifi",
"requirements": ["aiounifi==31"],
"requirements": ["aiounifi==32"],
"codeowners": ["@Kane610"],
"quality_scale": "platinum",
"ssdp": [

View File

@ -48,7 +48,7 @@ async def async_get_mac_address_from_host(hass: HomeAssistant, host: str) -> str
async def async_create_device(hass: HomeAssistant, ssdp_location: str) -> Device:
"""Create UPnP/IGD device."""
session = async_get_clientsession(hass)
session = async_get_clientsession(hass, verify_ssl=False)
requester = AiohttpSessionRequester(session, with_sleep=True, timeout=20)
factory = UpnpFactory(requester, disable_state_variable_validation=True)

View File

@ -3,7 +3,7 @@
"name": "MusicCast",
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/yamaha_musiccast",
"requirements": ["aiomusiccast==0.14.3"],
"requirements": ["aiomusiccast==0.14.4"],
"ssdp": [
{
"manufacturer": "Yamaha Corporation"

View File

@ -7,7 +7,7 @@ from .backports.enum import StrEnum
MAJOR_VERSION: Final = 2022
MINOR_VERSION: Final = 6
PATCH_VERSION: Final = "6"
PATCH_VERSION: Final = "7"
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 9, 0)

View File

@ -122,7 +122,7 @@ aioasuswrt==1.4.0
aioazuredevops==1.3.5
# homeassistant.components.baf
aiobafi6==0.5.0
aiobafi6==0.6.0
# homeassistant.components.aws
aiobotocore==2.1.0
@ -196,7 +196,7 @@ aiolyric==1.0.8
aiomodernforms==0.1.8
# homeassistant.components.yamaha_musiccast
aiomusiccast==0.14.3
aiomusiccast==0.14.4
# homeassistant.components.nanoleaf
aionanoleaf==0.2.0
@ -256,7 +256,7 @@ aiosyncthing==0.5.1
aiotractive==0.5.4
# homeassistant.components.unifi
aiounifi==31
aiounifi==32
# homeassistant.components.vlc_telnet
aiovlc==0.1.0
@ -768,7 +768,7 @@ greenwavereality==0.5.1
gridnet==4.0.0
# homeassistant.components.growatt_server
growattServer==1.1.0
growattServer==1.2.2
# homeassistant.components.gstreamer
gstreamer-player==1.1.2
@ -885,7 +885,7 @@ influxdb-client==1.24.0
influxdb==5.3.1
# homeassistant.components.insteon
insteon-frontend-home-assistant==0.1.0
insteon-frontend-home-assistant==0.1.1
# homeassistant.components.intellifire
intellifire4py==1.0.2
@ -1080,7 +1080,7 @@ nettigo-air-monitor==1.2.4
neurio==0.3.1
# homeassistant.components.nexia
nexia==1.0.1
nexia==1.0.2
# homeassistant.components.nextcloud
nextcloudmonitor==1.1.0
@ -1556,7 +1556,7 @@ pyialarmxr-homeassistant==1.0.18
pyicloud==1.0.0
# homeassistant.components.insteon
pyinsteon==1.1.0
pyinsteon==1.1.1
# homeassistant.components.intesishome
pyintesishome==1.7.6

View File

@ -109,7 +109,7 @@ aioasuswrt==1.4.0
aioazuredevops==1.3.5
# homeassistant.components.baf
aiobafi6==0.5.0
aiobafi6==0.6.0
# homeassistant.components.aws
aiobotocore==2.1.0
@ -168,7 +168,7 @@ aiolyric==1.0.8
aiomodernforms==0.1.8
# homeassistant.components.yamaha_musiccast
aiomusiccast==0.14.3
aiomusiccast==0.14.4
# homeassistant.components.nanoleaf
aionanoleaf==0.2.0
@ -225,7 +225,7 @@ aiosyncthing==0.5.1
aiotractive==0.5.4
# homeassistant.components.unifi
aiounifi==31
aiounifi==32
# homeassistant.components.vlc_telnet
aiovlc==0.1.0
@ -550,7 +550,7 @@ greeneye_monitor==3.0.3
gridnet==4.0.0
# homeassistant.components.growatt_server
growattServer==1.1.0
growattServer==1.2.2
# homeassistant.components.profiler
guppy3==3.1.2
@ -628,7 +628,7 @@ influxdb-client==1.24.0
influxdb==5.3.1
# homeassistant.components.insteon
insteon-frontend-home-assistant==0.1.0
insteon-frontend-home-assistant==0.1.1
# homeassistant.components.intellifire
intellifire4py==1.0.2
@ -745,7 +745,7 @@ netmap==0.7.0.2
nettigo-air-monitor==1.2.4
# homeassistant.components.nexia
nexia==1.0.1
nexia==1.0.2
# homeassistant.components.discord
nextcord==2.0.0a8
@ -1044,7 +1044,7 @@ pyialarmxr-homeassistant==1.0.18
pyicloud==1.0.0
# homeassistant.components.insteon
pyinsteon==1.1.0
pyinsteon==1.1.1
# homeassistant.components.ipma
pyipma==2.0.5

View File

@ -1,5 +1,5 @@
[metadata]
version = 2022.6.6
version = 2022.6.7
url = https://www.home-assistant.io/
[options]

View File

@ -12,7 +12,7 @@ from homeassistant.const import CONF_HOST, CONF_TYPE
from tests.common import MockConfigEntry, load_fixture
CONFIG = {CONF_HOST: "localhost", CONF_TYPE: "laser"}
CONFIG = {CONF_HOST: "127.0.0.1", CONF_TYPE: "laser"}
async def test_show_form(hass):
@ -32,13 +32,15 @@ async def test_create_entry_with_hostname(hass):
return_value=json.loads(load_fixture("printer_data.json", "brother")),
):
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}, data=CONFIG
DOMAIN,
context={"source": SOURCE_USER},
data={CONF_HOST: "example.local", CONF_TYPE: "laser"},
)
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
assert result["title"] == "HL-L2340DW 0123456789"
assert result["data"][CONF_HOST] == CONFIG[CONF_HOST]
assert result["data"][CONF_TYPE] == CONFIG[CONF_TYPE]
assert result["data"][CONF_HOST] == "example.local"
assert result["data"][CONF_TYPE] == "laser"
async def test_create_entry_with_ipv4_address(hass):
@ -48,9 +50,7 @@ async def test_create_entry_with_ipv4_address(hass):
return_value=json.loads(load_fixture("printer_data.json", "brother")),
):
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_USER},
data={CONF_HOST: "127.0.0.1", CONF_TYPE: "laser"},
DOMAIN, context={"source": SOURCE_USER}, data=CONFIG
)
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
@ -145,7 +145,7 @@ async def test_zeroconf_snmp_error(hass):
DOMAIN,
context={"source": SOURCE_ZEROCONF},
data=zeroconf.ZeroconfServiceInfo(
host="mock_host",
host="127.0.0.1",
addresses=["mock_host"],
hostname="example.local.",
name="Brother Printer",
@ -166,7 +166,7 @@ async def test_zeroconf_unsupported_model(hass):
DOMAIN,
context={"source": SOURCE_ZEROCONF},
data=zeroconf.ZeroconfServiceInfo(
host="mock_host",
host="127.0.0.1",
addresses=["mock_host"],
hostname="example.local.",
name="Brother Printer",
@ -187,15 +187,18 @@ async def test_zeroconf_device_exists_abort(hass):
"brother.Brother._get_data",
return_value=json.loads(load_fixture("printer_data.json", "brother")),
):
MockConfigEntry(domain=DOMAIN, unique_id="0123456789", data=CONFIG).add_to_hass(
hass
entry = MockConfigEntry(
domain=DOMAIN,
unique_id="0123456789",
data={CONF_HOST: "example.local", CONF_TYPE: "laser"},
)
entry.add_to_hass(hass)
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_ZEROCONF},
data=zeroconf.ZeroconfServiceInfo(
host="mock_host",
host="127.0.0.1",
addresses=["mock_host"],
hostname="example.local.",
name="Brother Printer",
@ -208,6 +211,9 @@ async def test_zeroconf_device_exists_abort(hass):
assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
assert result["reason"] == "already_configured"
# Test config entry got updated with latest IP
assert entry.data["host"] == "127.0.0.1"
async def test_zeroconf_no_probe_existing_device(hass):
"""Test we do not probe the device is the host is already configured."""
@ -218,9 +224,9 @@ async def test_zeroconf_no_probe_existing_device(hass):
DOMAIN,
context={"source": SOURCE_ZEROCONF},
data=zeroconf.ZeroconfServiceInfo(
host="mock_host",
host="127.0.0.1",
addresses=["mock_host"],
hostname="localhost",
hostname="example.local.",
name="Brother Printer",
port=None,
properties={},
@ -245,7 +251,7 @@ async def test_zeroconf_confirm_create_entry(hass):
DOMAIN,
context={"source": SOURCE_ZEROCONF},
data=zeroconf.ZeroconfServiceInfo(
host="mock_host",
host="127.0.0.1",
addresses=["mock_host"],
hostname="example.local.",
name="Brother Printer",
@ -266,5 +272,5 @@ async def test_zeroconf_confirm_create_entry(hass):
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
assert result["title"] == "HL-L2340DW 0123456789"
assert result["data"][CONF_HOST] == "example.local"
assert result["data"][CONF_HOST] == "127.0.0.1"
assert result["data"][CONF_TYPE] == "laser"

View File

@ -16,6 +16,8 @@ from homeassistant.const import (
ATTR_FRIENDLY_NAME,
ATTR_UNIT_OF_MEASUREMENT,
CONF_DEVICES,
ELECTRIC_CURRENT_AMPERE,
ELECTRIC_POTENTIAL_VOLT,
ENERGY_KILO_WATT_HOUR,
POWER_WATT,
SERVICE_TURN_OFF,
@ -48,29 +50,54 @@ async def test_setup(hass: HomeAssistant, fritz: Mock):
assert state.attributes[ATTR_FRIENDLY_NAME] == CONF_FAKE_NAME
assert ATTR_STATE_CLASS not in state.attributes
state = hass.states.get(f"{SENSOR_DOMAIN}.{CONF_FAKE_NAME}_temperature")
assert state
assert state.state == "1.23"
assert state.attributes[ATTR_FRIENDLY_NAME] == f"{CONF_FAKE_NAME} Temperature"
assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == TEMP_CELSIUS
assert state.attributes[ATTR_STATE_CLASS] == SensorStateClass.MEASUREMENT
state = hass.states.get(f"{ENTITY_ID}_humidity")
assert state is None
state = hass.states.get(f"{SENSOR_DOMAIN}.{CONF_FAKE_NAME}_power_consumption")
assert state
assert state.state == "5.678"
assert state.attributes[ATTR_FRIENDLY_NAME] == f"{CONF_FAKE_NAME} Power Consumption"
assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == POWER_WATT
assert state.attributes[ATTR_STATE_CLASS] == SensorStateClass.MEASUREMENT
sensors = (
[
f"{SENSOR_DOMAIN}.{CONF_FAKE_NAME}_temperature",
"1.23",
f"{CONF_FAKE_NAME} Temperature",
TEMP_CELSIUS,
SensorStateClass.MEASUREMENT,
],
[
f"{SENSOR_DOMAIN}.{CONF_FAKE_NAME}_power_consumption",
"5.678",
f"{CONF_FAKE_NAME} Power Consumption",
POWER_WATT,
SensorStateClass.MEASUREMENT,
],
[
f"{SENSOR_DOMAIN}.{CONF_FAKE_NAME}_total_energy",
"1.234",
f"{CONF_FAKE_NAME} Total Energy",
ENERGY_KILO_WATT_HOUR,
SensorStateClass.TOTAL_INCREASING,
],
[
f"{SENSOR_DOMAIN}.{CONF_FAKE_NAME}_voltage",
"230",
f"{CONF_FAKE_NAME} Voltage",
ELECTRIC_POTENTIAL_VOLT,
SensorStateClass.MEASUREMENT,
],
[
f"{SENSOR_DOMAIN}.{CONF_FAKE_NAME}_electric_current",
"0.0246869565217391",
f"{CONF_FAKE_NAME} Electric Current",
ELECTRIC_CURRENT_AMPERE,
SensorStateClass.MEASUREMENT,
],
)
state = hass.states.get(f"{SENSOR_DOMAIN}.{CONF_FAKE_NAME}_total_energy")
assert state
assert state.state == "1.234"
assert state.attributes[ATTR_FRIENDLY_NAME] == f"{CONF_FAKE_NAME} Total Energy"
assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == ENERGY_KILO_WATT_HOUR
assert state.attributes[ATTR_STATE_CLASS] == SensorStateClass.TOTAL_INCREASING
for sensor in sensors:
state = hass.states.get(sensor[0])
assert state
assert state.state == sensor[1]
assert state.attributes[ATTR_FRIENDLY_NAME] == sensor[2]
assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == sensor[3]
assert state.attributes[ATTR_STATE_CLASS] == sensor[4]
async def test_turn_on(hass: HomeAssistant, fritz: Mock):

View File

@ -401,7 +401,7 @@ async def test_load_properties(hass, hass_ws_client, kpl_properties_data):
)
device = devices["33.33.33"]
device.async_read_config = AsyncMock(return_value=(1, 1))
device.async_read_config = AsyncMock(return_value=1)
with patch.object(insteon.api.properties, "devices", devices):
await ws_client.send_json(
{ID: 2, TYPE: "insteon/properties/load", DEVICE_ADDRESS: "33.33.33"}
@ -418,7 +418,7 @@ async def test_load_properties_failure(hass, hass_ws_client, kpl_properties_data
)
device = devices["33.33.33"]
device.async_read_config = AsyncMock(return_value=(0, 0))
device.async_read_config = AsyncMock(return_value=0)
with patch.object(insteon.api.properties, "devices", devices):
await ws_client.send_json(
{ID: 2, TYPE: "insteon/properties/load", DEVICE_ADDRESS: "33.33.33"}

View File

@ -163,16 +163,17 @@ async def test_one_weather_site_running(hass, requests_mock):
assert weather.attributes.get("humidity") == 50
# Also has Forecasts added - again, just pick out 1 entry to check
assert len(weather.attributes.get("forecast")) == 8
# ensures that daily filters out multiple results per day
assert len(weather.attributes.get("forecast")) == 4
assert (
weather.attributes.get("forecast")[7]["datetime"] == "2020-04-29T12:00:00+00:00"
weather.attributes.get("forecast")[3]["datetime"] == "2020-04-29T12:00:00+00:00"
)
assert weather.attributes.get("forecast")[7]["condition"] == "rainy"
assert weather.attributes.get("forecast")[7]["precipitation_probability"] == 59
assert weather.attributes.get("forecast")[7]["temperature"] == 13
assert weather.attributes.get("forecast")[7]["wind_speed"] == 13
assert weather.attributes.get("forecast")[7]["wind_bearing"] == "SE"
assert weather.attributes.get("forecast")[3]["condition"] == "rainy"
assert weather.attributes.get("forecast")[3]["precipitation_probability"] == 59
assert weather.attributes.get("forecast")[3]["temperature"] == 13
assert weather.attributes.get("forecast")[3]["wind_speed"] == 13
assert weather.attributes.get("forecast")[3]["wind_bearing"] == "SE"
@freeze_time(datetime.datetime(2020, 4, 25, 12, tzinfo=datetime.timezone.utc))
@ -258,16 +259,17 @@ async def test_two_weather_sites_running(hass, requests_mock):
assert weather.attributes.get("humidity") == 50
# Also has Forecasts added - again, just pick out 1 entry to check
assert len(weather.attributes.get("forecast")) == 8
# ensures that daily filters out multiple results per day
assert len(weather.attributes.get("forecast")) == 4
assert (
weather.attributes.get("forecast")[7]["datetime"] == "2020-04-29T12:00:00+00:00"
weather.attributes.get("forecast")[3]["datetime"] == "2020-04-29T12:00:00+00:00"
)
assert weather.attributes.get("forecast")[7]["condition"] == "rainy"
assert weather.attributes.get("forecast")[7]["precipitation_probability"] == 59
assert weather.attributes.get("forecast")[7]["temperature"] == 13
assert weather.attributes.get("forecast")[7]["wind_speed"] == 13
assert weather.attributes.get("forecast")[7]["wind_bearing"] == "SE"
assert weather.attributes.get("forecast")[3]["condition"] == "rainy"
assert weather.attributes.get("forecast")[3]["precipitation_probability"] == 59
assert weather.attributes.get("forecast")[3]["temperature"] == 13
assert weather.attributes.get("forecast")[3]["wind_speed"] == 13
assert weather.attributes.get("forecast")[3]["wind_bearing"] == "SE"
# King's Lynn 3-hourly weather platform expected results
weather = hass.states.get("weather.met_office_king_s_lynn_3_hourly")
@ -305,13 +307,14 @@ async def test_two_weather_sites_running(hass, requests_mock):
assert weather.attributes.get("humidity") == 75
# All should have Forecast added - again, just picking out 1 entry to check
assert len(weather.attributes.get("forecast")) == 8
# ensures daily filters out multiple results per day
assert len(weather.attributes.get("forecast")) == 4
assert (
weather.attributes.get("forecast")[5]["datetime"] == "2020-04-28T12:00:00+00:00"
weather.attributes.get("forecast")[2]["datetime"] == "2020-04-28T12:00:00+00:00"
)
assert weather.attributes.get("forecast")[5]["condition"] == "cloudy"
assert weather.attributes.get("forecast")[5]["precipitation_probability"] == 14
assert weather.attributes.get("forecast")[5]["temperature"] == 11
assert weather.attributes.get("forecast")[5]["wind_speed"] == 7
assert weather.attributes.get("forecast")[5]["wind_bearing"] == "ESE"
assert weather.attributes.get("forecast")[2]["condition"] == "cloudy"
assert weather.attributes.get("forecast")[2]["precipitation_probability"] == 14
assert weather.attributes.get("forecast")[2]["temperature"] == 11
assert weather.attributes.get("forecast")[2]["wind_speed"] == 7
assert weather.attributes.get("forecast")[2]["wind_bearing"] == "ESE"

View File

@ -13,6 +13,7 @@ from homeassistant.components.humidifier import (
SERVICE_SET_HUMIDITY,
SERVICE_SET_MODE,
)
from homeassistant.components.mqtt import CONFIG_SCHEMA
from homeassistant.components.mqtt.humidifier import (
CONF_MODE_COMMAND_TOPIC,
CONF_MODE_STATE_TOPIC,
@ -1277,3 +1278,15 @@ async def test_setup_manual_entity_from_yaml(hass, caplog, tmp_path):
hass, caplog, tmp_path, platform, config
)
assert hass.states.get(f"{platform}.test") is not None
async def test_config_schema_validation(hass):
"""Test invalid platform options in the config schema do pass the config validation."""
platform = humidifier.DOMAIN
config = copy.deepcopy(DEFAULT_CONFIG[platform])
config["name"] = "test"
del config["platform"]
CONFIG_SCHEMA({DOMAIN: {platform: config}})
CONFIG_SCHEMA({DOMAIN: {platform: [config]}})
with pytest.raises(MultipleInvalid):
CONFIG_SCHEMA({"mqtt": {"humidifier": [{"bla": "bla"}]}})

View File

@ -14,7 +14,7 @@ import yaml
from homeassistant import config as hass_config
from homeassistant.components import mqtt
from homeassistant.components.mqtt import debug_info
from homeassistant.components.mqtt import CONFIG_SCHEMA, debug_info
from homeassistant.components.mqtt.mixins import MQTT_ENTITY_DEVICE_INFO_SCHEMA
from homeassistant.components.mqtt.models import ReceiveMessage
from homeassistant.const import (
@ -1365,31 +1365,33 @@ async def test_setup_override_configuration(hass, caplog, tmp_path):
async def test_setup_manual_mqtt_with_platform_key(hass, caplog, tmp_path):
"""Test set up a manual MQTT item with a platform key."""
config = {"platform": "mqtt", "name": "test", "command_topic": "test-topic"}
await help_test_setup_manual_entity_from_yaml(
hass,
caplog,
tmp_path,
"light",
config,
)
with pytest.raises(AssertionError):
await help_test_setup_manual_entity_from_yaml(
hass,
caplog,
tmp_path,
"light",
config,
)
assert (
"Invalid config for [light]: [platform] is an invalid option for [light]. "
"Check: light->platform. (See ?, line ?)" in caplog.text
"Invalid config for [mqtt]: [platform] is an invalid option for [mqtt]"
in caplog.text
)
async def test_setup_manual_mqtt_with_invalid_config(hass, caplog, tmp_path):
"""Test set up a manual MQTT item with an invalid config."""
config = {"name": "test"}
await help_test_setup_manual_entity_from_yaml(
hass,
caplog,
tmp_path,
"light",
config,
)
with pytest.raises(AssertionError):
await help_test_setup_manual_entity_from_yaml(
hass,
caplog,
tmp_path,
"light",
config,
)
assert (
"Invalid config for [light]: required key not provided @ data['command_topic']."
"Invalid config for [mqtt]: required key not provided @ data['mqtt']['light'][0]['command_topic']."
" Got None. (See ?, line ?)" in caplog.text
)
@ -1407,11 +1409,15 @@ async def test_setup_manual_mqtt_empty_platform(hass, caplog, tmp_path):
assert "voluptuous.error.MultipleInvalid" not in caplog.text
@patch("homeassistant.components.mqtt.PLATFORMS", [])
async def test_setup_mqtt_client_protocol(hass):
"""Test MQTT client protocol setup."""
entry = MockConfigEntry(
domain=mqtt.DOMAIN,
data={mqtt.CONF_BROKER: "test-broker", mqtt.config.CONF_PROTOCOL: "3.1"},
data={
mqtt.CONF_BROKER: "test-broker",
mqtt.config_integration.CONF_PROTOCOL: "3.1",
},
)
with patch("paho.mqtt.client.Client") as mock_client:
mock_client.on_connect(return_value=0)
@ -2617,3 +2623,10 @@ async def test_one_deprecation_warning_per_platform(
):
count += 1
assert count == 1
async def test_config_schema_validation(hass):
"""Test invalid platform options in the config schema do not pass the config validation."""
config = {"mqtt": {"sensor": [{"some_illegal_topic": "mystate/topic/path"}]}}
with pytest.raises(vol.MultipleInvalid):
CONFIG_SCHEMA(config)

View File

@ -269,6 +269,271 @@ MOCK_ASYNC_FETCH_ALL = {
}
MOCK_ASYNC_FETCH_ALL_OFFLINE_DEVICE = {
"456789": OncueDevice(
name="My Generator",
state="Off",
product_name="RDC 2.4",
hardware_version="319",
serial_number="SERIAL",
sensors={
"Product": OncueSensor(
name="Product",
display_name="Controller Type",
value="RDC 2.4",
display_value="RDC 2.4",
unit=None,
),
"FirmwareVersion": OncueSensor(
name="FirmwareVersion",
display_name="Current Firmware",
value="2.0.6",
display_value="2.0.6",
unit=None,
),
"LatestFirmware": OncueSensor(
name="LatestFirmware",
display_name="Latest Firmware",
value="2.0.6",
display_value="2.0.6",
unit=None,
),
"EngineSpeed": OncueSensor(
name="EngineSpeed",
display_name="Engine Speed",
value="0",
display_value="0 R/min",
unit="R/min",
),
"EngineTargetSpeed": OncueSensor(
name="EngineTargetSpeed",
display_name="Engine Target Speed",
value="0",
display_value="0 R/min",
unit="R/min",
),
"EngineOilPressure": OncueSensor(
name="EngineOilPressure",
display_name="Engine Oil Pressure",
value=0,
display_value="0 Psi",
unit="Psi",
),
"EngineCoolantTemperature": OncueSensor(
name="EngineCoolantTemperature",
display_name="Engine Coolant Temperature",
value=32,
display_value="32 F",
unit="F",
),
"BatteryVoltage": OncueSensor(
name="BatteryVoltage",
display_name="Battery Voltage",
value="13.4",
display_value="13.4 V",
unit="V",
),
"LubeOilTemperature": OncueSensor(
name="LubeOilTemperature",
display_name="Lube Oil Temperature",
value=32,
display_value="32 F",
unit="F",
),
"GensetControllerTemperature": OncueSensor(
name="GensetControllerTemperature",
display_name="Generator Controller Temperature",
value=84.2,
display_value="84.2 F",
unit="F",
),
"EngineCompartmentTemperature": OncueSensor(
name="EngineCompartmentTemperature",
display_name="Engine Compartment Temperature",
value=62.6,
display_value="62.6 F",
unit="F",
),
"GeneratorTrueTotalPower": OncueSensor(
name="GeneratorTrueTotalPower",
display_name="Generator True Total Power",
value="0.0",
display_value="0.0 W",
unit="W",
),
"GeneratorTruePercentOfRatedPower": OncueSensor(
name="GeneratorTruePercentOfRatedPower",
display_name="Generator True Percent Of Rated Power",
value="0",
display_value="0 %",
unit="%",
),
"GeneratorVoltageAB": OncueSensor(
name="GeneratorVoltageAB",
display_name="Generator Voltage AB",
value="0.0",
display_value="0.0 V",
unit="V",
),
"GeneratorVoltageAverageLineToLine": OncueSensor(
name="GeneratorVoltageAverageLineToLine",
display_name="Generator Voltage Average Line To Line",
value="0.0",
display_value="0.0 V",
unit="V",
),
"GeneratorCurrentAverage": OncueSensor(
name="GeneratorCurrentAverage",
display_name="Generator Current Average",
value="0.0",
display_value="0.0 A",
unit="A",
),
"GeneratorFrequency": OncueSensor(
name="GeneratorFrequency",
display_name="Generator Frequency",
value="0.0",
display_value="0.0 Hz",
unit="Hz",
),
"GensetSerialNumber": OncueSensor(
name="GensetSerialNumber",
display_name="Generator Serial Number",
value="33FDGMFR0026",
display_value="33FDGMFR0026",
unit=None,
),
"GensetState": OncueSensor(
name="GensetState",
display_name="Generator State",
value="Off",
display_value="Off",
unit=None,
),
"GensetControllerSerialNumber": OncueSensor(
name="GensetControllerSerialNumber",
display_name="Generator Controller Serial Number",
value="-1",
display_value="-1",
unit=None,
),
"GensetModelNumberSelect": OncueSensor(
name="GensetModelNumberSelect",
display_name="Genset Model Number Select",
value="38 RCLB",
display_value="38 RCLB",
unit=None,
),
"GensetControllerClockTime": OncueSensor(
name="GensetControllerClockTime",
display_name="Generator Controller Clock Time",
value="2022-01-13 18:08:13",
display_value="2022-01-13 18:08:13",
unit=None,
),
"GensetControllerTotalOperationTime": OncueSensor(
name="GensetControllerTotalOperationTime",
display_name="Generator Controller Total Operation Time",
value="16770.8",
display_value="16770.8 h",
unit="h",
),
"EngineTotalRunTime": OncueSensor(
name="EngineTotalRunTime",
display_name="Engine Total Run Time",
value="28.1",
display_value="28.1 h",
unit="h",
),
"EngineTotalRunTimeLoaded": OncueSensor(
name="EngineTotalRunTimeLoaded",
display_name="Engine Total Run Time Loaded",
value="5.5",
display_value="5.5 h",
unit="h",
),
"EngineTotalNumberOfStarts": OncueSensor(
name="EngineTotalNumberOfStarts",
display_name="Engine Total Number Of Starts",
value="101",
display_value="101",
unit=None,
),
"GensetTotalEnergy": OncueSensor(
name="GensetTotalEnergy",
display_name="Genset Total Energy",
value="1.2022309E7",
display_value="1.2022309E7 kWh",
unit="kWh",
),
"AtsContactorPosition": OncueSensor(
name="AtsContactorPosition",
display_name="Ats Contactor Position",
value="Source1",
display_value="Source1",
unit=None,
),
"AtsSourcesAvailable": OncueSensor(
name="AtsSourcesAvailable",
display_name="Ats Sources Available",
value="Source1",
display_value="Source1",
unit=None,
),
"Source1VoltageAverageLineToLine": OncueSensor(
name="Source1VoltageAverageLineToLine",
display_name="Source1 Voltage Average Line To Line",
value="253.5",
display_value="253.5 V",
unit="V",
),
"Source2VoltageAverageLineToLine": OncueSensor(
name="Source2VoltageAverageLineToLine",
display_name="Source2 Voltage Average Line To Line",
value="0.0",
display_value="0.0 V",
unit="V",
),
"IPAddress": OncueSensor(
name="IPAddress",
display_name="IP Address",
value="1.2.3.4:1026",
display_value="1.2.3.4:1026",
unit=None,
),
"MacAddress": OncueSensor(
name="MacAddress",
display_name="Mac Address",
value="--",
display_value="--",
unit=None,
),
"ConnectedServerIPAddress": OncueSensor(
name="ConnectedServerIPAddress",
display_name="Connected Server IP Address",
value="40.117.195.28",
display_value="40.117.195.28",
unit=None,
),
"NetworkConnectionEstablished": OncueSensor(
name="NetworkConnectionEstablished",
display_name="Network Connection Established",
value="true",
display_value="True",
unit=None,
),
"SerialNumber": OncueSensor(
name="SerialNumber",
display_name="Serial Number",
value="1073879692",
display_value="1073879692",
unit=None,
),
},
)
}
def _patch_login_and_data():
@contextmanager
def _patcher():
@ -279,3 +544,15 @@ def _patch_login_and_data():
yield
return _patcher()
def _patch_login_and_data_offline_device():
@contextmanager
def _patcher():
with patch("homeassistant.components.oncue.Oncue.async_login",), patch(
"homeassistant.components.oncue.Oncue.async_fetch_all",
return_value=MOCK_ASYNC_FETCH_ALL_OFFLINE_DEVICE,
):
yield
return _patcher()

View File

@ -1,19 +1,29 @@
"""Tests for the oncue sensor."""
from __future__ import annotations
import pytest
from homeassistant.components import oncue
from homeassistant.components.oncue.const import DOMAIN
from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
from homeassistant.core import HomeAssistant
from homeassistant.helpers import device_registry as dr, entity_registry as er
from homeassistant.setup import async_setup_component
from . import _patch_login_and_data
from . import _patch_login_and_data, _patch_login_and_data_offline_device
from tests.common import MockConfigEntry
async def test_sensors(hass: HomeAssistant) -> None:
@pytest.mark.parametrize(
"patcher, connections",
[
[_patch_login_and_data, {("mac", "c9:24:22:6f:14:00")}],
[_patch_login_and_data_offline_device, set()],
],
)
async def test_sensors(hass: HomeAssistant, patcher, connections) -> None:
"""Test that the sensors are setup with the expected values."""
config_entry = MockConfigEntry(
domain=DOMAIN,
@ -21,11 +31,17 @@ async def test_sensors(hass: HomeAssistant) -> None:
unique_id="any",
)
config_entry.add_to_hass(hass)
with _patch_login_and_data():
with patcher():
await async_setup_component(hass, oncue.DOMAIN, {oncue.DOMAIN: {}})
await hass.async_block_till_done()
assert config_entry.state == ConfigEntryState.LOADED
entity_registry = er.async_get(hass)
ent = entity_registry.async_get("sensor.my_generator_latest_firmware")
device_registry = dr.async_get(hass)
dev = device_registry.async_get(ent.device_id)
assert dev.connections == connections
assert len(hass.states.async_all("sensor")) == 25
assert hass.states.get("sensor.my_generator_latest_firmware").state == "2.0.6"

View File

@ -28,6 +28,7 @@ USER_OBJECT = {
"id": 123,
"display_name": "channel123",
"offline_image_url": "logo.png",
"profile_image_url": "logo.png",
"view_count": 42,
}
STREAM_OBJECT_ONLINE = {