mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 13:17:32 +00:00
Merge pull request #73821 from home-assistant/rc
This commit is contained in:
commit
f97e95134b
@ -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
|
||||
|
@ -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": [
|
||||
|
@ -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(
|
||||
{
|
||||
|
@ -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,
|
||||
),
|
||||
|
@ -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"]
|
||||
|
@ -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"
|
||||
|
@ -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": [
|
||||
|
@ -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."""
|
||||
|
@ -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,
|
||||
)
|
||||
|
@ -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."""
|
||||
|
@ -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(
|
||||
|
@ -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(
|
||||
|
@ -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(
|
||||
|
@ -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(
|
||||
|
@ -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(
|
||||
|
@ -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,
|
||||
|
193
homeassistant/components/mqtt/config_integration.py
Normal file
193
homeassistant/components/mqtt/config_integration.py
Normal 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,
|
||||
]
|
@ -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,
|
||||
]
|
||||
|
@ -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(
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
)
|
||||
)
|
||||
)
|
||||
|
@ -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
|
||||
|
@ -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(
|
||||
|
@ -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(
|
||||
|
@ -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(
|
||||
|
@ -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):
|
||||
|
@ -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(
|
||||
|
@ -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(
|
||||
|
@ -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(
|
||||
|
@ -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(
|
||||
|
@ -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(
|
||||
|
@ -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(
|
||||
|
@ -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(
|
||||
|
@ -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:
|
||||
|
@ -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,
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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,
|
||||
)
|
||||
|
@ -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"
|
||||
|
@ -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:
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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"]
|
||||
|
@ -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": [
|
||||
|
@ -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)
|
||||
|
@ -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"
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -1,5 +1,5 @@
|
||||
[metadata]
|
||||
version = 2022.6.6
|
||||
version = 2022.6.7
|
||||
url = https://www.home-assistant.io/
|
||||
|
||||
[options]
|
||||
|
@ -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"
|
||||
|
@ -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):
|
||||
|
@ -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"}
|
||||
|
@ -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"
|
||||
|
@ -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"}]}})
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
|
@ -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"
|
||||
|
||||
|
@ -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 = {
|
||||
|
Loading…
x
Reference in New Issue
Block a user