2024.10.3 (#128654)
This commit is contained in:
commit
a301d51fb2
38 changed files with 446 additions and 71 deletions
|
@ -85,6 +85,7 @@ HVAC_MODE_LIB_TO_HASS: Final[dict[OperationMode, HVACMode]] = {
|
||||||
OperationMode.HEATING: HVACMode.HEAT,
|
OperationMode.HEATING: HVACMode.HEAT,
|
||||||
OperationMode.FAN: HVACMode.FAN_ONLY,
|
OperationMode.FAN: HVACMode.FAN_ONLY,
|
||||||
OperationMode.DRY: HVACMode.DRY,
|
OperationMode.DRY: HVACMode.DRY,
|
||||||
|
OperationMode.AUX_HEATING: HVACMode.HEAT,
|
||||||
OperationMode.AUTO: HVACMode.HEAT_COOL,
|
OperationMode.AUTO: HVACMode.HEAT_COOL,
|
||||||
}
|
}
|
||||||
HVAC_MODE_HASS_TO_LIB: Final[dict[HVACMode, OperationMode]] = {
|
HVAC_MODE_HASS_TO_LIB: Final[dict[HVACMode, OperationMode]] = {
|
||||||
|
@ -157,9 +158,10 @@ class AirzoneClimate(AirzoneZoneEntity, ClimateEntity):
|
||||||
self._attr_temperature_unit = TEMP_UNIT_LIB_TO_HASS[
|
self._attr_temperature_unit = TEMP_UNIT_LIB_TO_HASS[
|
||||||
self.get_airzone_value(AZD_TEMP_UNIT)
|
self.get_airzone_value(AZD_TEMP_UNIT)
|
||||||
]
|
]
|
||||||
self._attr_hvac_modes = [
|
_attr_hvac_modes = [
|
||||||
HVAC_MODE_LIB_TO_HASS[mode] for mode in self.get_airzone_value(AZD_MODES)
|
HVAC_MODE_LIB_TO_HASS[mode] for mode in self.get_airzone_value(AZD_MODES)
|
||||||
]
|
]
|
||||||
|
self._attr_hvac_modes = list(dict.fromkeys(_attr_hvac_modes))
|
||||||
if (
|
if (
|
||||||
self.get_airzone_value(AZD_SPEED) is not None
|
self.get_airzone_value(AZD_SPEED) is not None
|
||||||
and self.get_airzone_value(AZD_SPEEDS) is not None
|
and self.get_airzone_value(AZD_SPEEDS) is not None
|
||||||
|
|
|
@ -11,5 +11,5 @@
|
||||||
"documentation": "https://www.home-assistant.io/integrations/airzone",
|
"documentation": "https://www.home-assistant.io/integrations/airzone",
|
||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"loggers": ["aioairzone"],
|
"loggers": ["aioairzone"],
|
||||||
"requirements": ["aioairzone==0.9.3"]
|
"requirements": ["aioairzone==0.9.5"]
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/bluesound",
|
"documentation": "https://www.home-assistant.io/integrations/bluesound",
|
||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"requirements": ["pyblu==1.0.3"],
|
"requirements": ["pyblu==1.0.4"],
|
||||||
"zeroconf": [
|
"zeroconf": [
|
||||||
{
|
{
|
||||||
"type": "_musc._tcp.local."
|
"type": "_musc._tcp.local."
|
||||||
|
|
|
@ -493,6 +493,8 @@ class BluesoundPlayer(MediaPlayerEntity):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
position = self._status.seconds
|
position = self._status.seconds
|
||||||
|
if position is None:
|
||||||
|
return None
|
||||||
|
|
||||||
if mediastate == MediaPlayerState.PLAYING:
|
if mediastate == MediaPlayerState.PLAYING:
|
||||||
position += (dt_util.utcnow() - self._last_status_update).total_seconds()
|
position += (dt_util.utcnow() - self._last_status_update).total_seconds()
|
||||||
|
|
|
@ -159,6 +159,7 @@ class DaikinClimate(DaikinEntity, ClimateEntity):
|
||||||
|
|
||||||
if values:
|
if values:
|
||||||
await self.device.set(values)
|
await self.device.set(values)
|
||||||
|
await self.coordinator.async_refresh()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def unique_id(self) -> str:
|
def unique_id(self) -> str:
|
||||||
|
@ -261,6 +262,7 @@ class DaikinClimate(DaikinEntity, ClimateEntity):
|
||||||
await self.device.set_advanced_mode(
|
await self.device.set_advanced_mode(
|
||||||
HA_PRESET_TO_DAIKIN[PRESET_ECO], ATTR_STATE_OFF
|
HA_PRESET_TO_DAIKIN[PRESET_ECO], ATTR_STATE_OFF
|
||||||
)
|
)
|
||||||
|
await self.coordinator.async_refresh()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def preset_modes(self) -> list[str]:
|
def preset_modes(self) -> list[str]:
|
||||||
|
@ -275,9 +277,11 @@ class DaikinClimate(DaikinEntity, ClimateEntity):
|
||||||
async def async_turn_on(self) -> None:
|
async def async_turn_on(self) -> None:
|
||||||
"""Turn device on."""
|
"""Turn device on."""
|
||||||
await self.device.set({})
|
await self.device.set({})
|
||||||
|
await self.coordinator.async_refresh()
|
||||||
|
|
||||||
async def async_turn_off(self) -> None:
|
async def async_turn_off(self) -> None:
|
||||||
"""Turn device off."""
|
"""Turn device off."""
|
||||||
await self.device.set(
|
await self.device.set(
|
||||||
{HA_ATTR_TO_DAIKIN[ATTR_HVAC_MODE]: HA_STATE_TO_DAIKIN[HVACMode.OFF]}
|
{HA_ATTR_TO_DAIKIN[ATTR_HVAC_MODE]: HA_STATE_TO_DAIKIN[HVACMode.OFF]}
|
||||||
)
|
)
|
||||||
|
await self.coordinator.async_refresh()
|
||||||
|
|
|
@ -63,10 +63,12 @@ class DaikinZoneSwitch(DaikinEntity, SwitchEntity):
|
||||||
async def async_turn_on(self, **kwargs: Any) -> None:
|
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||||
"""Turn the zone on."""
|
"""Turn the zone on."""
|
||||||
await self.device.set_zone(self._zone_id, "zone_onoff", "1")
|
await self.device.set_zone(self._zone_id, "zone_onoff", "1")
|
||||||
|
await self.coordinator.async_refresh()
|
||||||
|
|
||||||
async def async_turn_off(self, **kwargs: Any) -> None:
|
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||||
"""Turn the zone off."""
|
"""Turn the zone off."""
|
||||||
await self.device.set_zone(self._zone_id, "zone_onoff", "0")
|
await self.device.set_zone(self._zone_id, "zone_onoff", "0")
|
||||||
|
await self.coordinator.async_refresh()
|
||||||
|
|
||||||
|
|
||||||
class DaikinStreamerSwitch(DaikinEntity, SwitchEntity):
|
class DaikinStreamerSwitch(DaikinEntity, SwitchEntity):
|
||||||
|
@ -88,10 +90,12 @@ class DaikinStreamerSwitch(DaikinEntity, SwitchEntity):
|
||||||
async def async_turn_on(self, **kwargs: Any) -> None:
|
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||||
"""Turn the zone on."""
|
"""Turn the zone on."""
|
||||||
await self.device.set_streamer("on")
|
await self.device.set_streamer("on")
|
||||||
|
await self.coordinator.async_refresh()
|
||||||
|
|
||||||
async def async_turn_off(self, **kwargs: Any) -> None:
|
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||||
"""Turn the zone off."""
|
"""Turn the zone off."""
|
||||||
await self.device.set_streamer("off")
|
await self.device.set_streamer("off")
|
||||||
|
await self.coordinator.async_refresh()
|
||||||
|
|
||||||
|
|
||||||
class DaikinToggleSwitch(DaikinEntity, SwitchEntity):
|
class DaikinToggleSwitch(DaikinEntity, SwitchEntity):
|
||||||
|
@ -112,7 +116,9 @@ class DaikinToggleSwitch(DaikinEntity, SwitchEntity):
|
||||||
async def async_turn_on(self, **kwargs: Any) -> None:
|
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||||
"""Turn the zone on."""
|
"""Turn the zone on."""
|
||||||
await self.device.set({})
|
await self.device.set({})
|
||||||
|
await self.coordinator.async_refresh()
|
||||||
|
|
||||||
async def async_turn_off(self, **kwargs: Any) -> None:
|
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||||
"""Turn the zone off."""
|
"""Turn the zone off."""
|
||||||
await self.device.set({DAIKIN_ATTR_MODE: "off"})
|
await self.device.set({DAIKIN_ATTR_MODE: "off"})
|
||||||
|
await self.coordinator.async_refresh()
|
||||||
|
|
|
@ -7,5 +7,5 @@
|
||||||
"documentation": "https://www.home-assistant.io/integrations/calendar.google",
|
"documentation": "https://www.home-assistant.io/integrations/calendar.google",
|
||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"loggers": ["googleapiclient"],
|
"loggers": ["googleapiclient"],
|
||||||
"requirements": ["gcal-sync==6.1.5", "oauth2client==4.1.3", "ical==8.2.0"]
|
"requirements": ["gcal-sync==6.1.6", "oauth2client==4.1.3", "ical==8.2.0"]
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,6 @@
|
||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"loggers": ["deepmerge", "pyipp"],
|
"loggers": ["deepmerge", "pyipp"],
|
||||||
"quality_scale": "platinum",
|
"quality_scale": "platinum",
|
||||||
"requirements": ["pyipp==0.16.0"],
|
"requirements": ["pyipp==0.17.0"],
|
||||||
"zeroconf": ["_ipps._tcp.local.", "_ipp._tcp.local."]
|
"zeroconf": ["_ipps._tcp.local.", "_ipp._tcp.local."]
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ from __future__ import annotations
|
||||||
|
|
||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from pyipp import Marker, Printer
|
from pyipp import Marker, Printer
|
||||||
|
@ -19,7 +19,6 @@ from homeassistant.const import ATTR_LOCATION, PERCENTAGE, EntityCategory
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
from homeassistant.helpers.typing import StateType
|
from homeassistant.helpers.typing import StateType
|
||||||
from homeassistant.util.dt import utcnow
|
|
||||||
|
|
||||||
from . import IPPConfigEntry
|
from . import IPPConfigEntry
|
||||||
from .const import (
|
from .const import (
|
||||||
|
@ -80,7 +79,7 @@ PRINTER_SENSORS: tuple[IPPSensorEntityDescription, ...] = (
|
||||||
device_class=SensorDeviceClass.TIMESTAMP,
|
device_class=SensorDeviceClass.TIMESTAMP,
|
||||||
entity_category=EntityCategory.DIAGNOSTIC,
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
entity_registry_enabled_default=False,
|
entity_registry_enabled_default=False,
|
||||||
value_fn=lambda printer: (utcnow() - timedelta(seconds=printer.info.uptime)),
|
value_fn=lambda printer: printer.booted_at,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,6 @@
|
||||||
"iot_class": "calculated",
|
"iot_class": "calculated",
|
||||||
"loggers": ["yt_dlp"],
|
"loggers": ["yt_dlp"],
|
||||||
"quality_scale": "internal",
|
"quality_scale": "internal",
|
||||||
"requirements": ["yt-dlp==2024.09.27"],
|
"requirements": ["yt-dlp==2024.10.07"],
|
||||||
"single_config_entry": true
|
"single_config_entry": true
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,5 +6,5 @@
|
||||||
"documentation": "https://www.home-assistant.io/integrations/opentherm_gw",
|
"documentation": "https://www.home-assistant.io/integrations/opentherm_gw",
|
||||||
"iot_class": "local_push",
|
"iot_class": "local_push",
|
||||||
"loggers": ["pyotgw"],
|
"loggers": ["pyotgw"],
|
||||||
"requirements": ["pyotgw==2.2.1"]
|
"requirements": ["pyotgw==2.2.2"]
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import CONF_HOST, Platform
|
from homeassistant.const import CONF_HOST, Platform
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
|
||||||
from .const import DOMAIN
|
from .const import CONF_PLAY_MEDIA_APP_ID, DEFAULT_PLAY_MEDIA_APP_ID, DOMAIN
|
||||||
from .coordinator import RokuDataUpdateCoordinator
|
from .coordinator import RokuDataUpdateCoordinator
|
||||||
|
|
||||||
PLATFORMS = [
|
PLATFORMS = [
|
||||||
|
@ -24,7 +24,12 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
device_id = entry.entry_id
|
device_id = entry.entry_id
|
||||||
|
|
||||||
coordinator = RokuDataUpdateCoordinator(
|
coordinator = RokuDataUpdateCoordinator(
|
||||||
hass, host=entry.data[CONF_HOST], device_id=device_id
|
hass,
|
||||||
|
host=entry.data[CONF_HOST],
|
||||||
|
device_id=device_id,
|
||||||
|
play_media_app_id=entry.options.get(
|
||||||
|
CONF_PLAY_MEDIA_APP_ID, DEFAULT_PLAY_MEDIA_APP_ID
|
||||||
|
),
|
||||||
)
|
)
|
||||||
await coordinator.async_config_entry_first_refresh()
|
await coordinator.async_config_entry_first_refresh()
|
||||||
|
|
||||||
|
@ -32,6 +37,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
|
|
||||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||||
|
|
||||||
|
entry.async_on_unload(entry.add_update_listener(async_reload_entry))
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
@ -40,3 +47,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
|
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
|
||||||
hass.data[DOMAIN].pop(entry.entry_id)
|
hass.data[DOMAIN].pop(entry.entry_id)
|
||||||
return unload_ok
|
return unload_ok
|
||||||
|
|
||||||
|
|
||||||
|
async def async_reload_entry(hass: HomeAssistant, entry: ConfigEntry) -> None:
|
||||||
|
"""Reload the config entry when it changed."""
|
||||||
|
await hass.config_entries.async_reload(entry.entry_id)
|
||||||
|
|
|
@ -10,12 +10,17 @@ from rokuecp import Roku, RokuError
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.components import ssdp, zeroconf
|
from homeassistant.components import ssdp, zeroconf
|
||||||
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
|
from homeassistant.config_entries import (
|
||||||
|
ConfigEntry,
|
||||||
|
ConfigFlow,
|
||||||
|
ConfigFlowResult,
|
||||||
|
OptionsFlowWithConfigEntry,
|
||||||
|
)
|
||||||
from homeassistant.const import CONF_HOST, CONF_NAME
|
from homeassistant.const import CONF_HOST, CONF_NAME
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
|
|
||||||
from .const import DOMAIN
|
from .const import CONF_PLAY_MEDIA_APP_ID, DEFAULT_PLAY_MEDIA_APP_ID, DOMAIN
|
||||||
|
|
||||||
DATA_SCHEMA = vol.Schema({vol.Required(CONF_HOST): str})
|
DATA_SCHEMA = vol.Schema({vol.Required(CONF_HOST): str})
|
||||||
|
|
||||||
|
@ -155,3 +160,36 @@ class RokuConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||||
title=self.discovery_info[CONF_NAME],
|
title=self.discovery_info[CONF_NAME],
|
||||||
data=self.discovery_info,
|
data=self.discovery_info,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
@callback
|
||||||
|
def async_get_options_flow(
|
||||||
|
config_entry: ConfigEntry,
|
||||||
|
) -> OptionsFlowWithConfigEntry:
|
||||||
|
"""Create the options flow."""
|
||||||
|
return RokuOptionsFlowHandler(config_entry)
|
||||||
|
|
||||||
|
|
||||||
|
class RokuOptionsFlowHandler(OptionsFlowWithConfigEntry):
|
||||||
|
"""Handle Roku options."""
|
||||||
|
|
||||||
|
async def async_step_init(
|
||||||
|
self, user_input: dict[str, Any] | None = None
|
||||||
|
) -> ConfigFlowResult:
|
||||||
|
"""Manage Roku options."""
|
||||||
|
if user_input is not None:
|
||||||
|
return self.async_create_entry(title="", data=user_input)
|
||||||
|
|
||||||
|
return self.async_show_form(
|
||||||
|
step_id="init",
|
||||||
|
data_schema=vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Optional(
|
||||||
|
CONF_PLAY_MEDIA_APP_ID,
|
||||||
|
default=self.options.get(
|
||||||
|
CONF_PLAY_MEDIA_APP_ID, DEFAULT_PLAY_MEDIA_APP_ID
|
||||||
|
),
|
||||||
|
): str,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
|
@ -15,3 +15,9 @@ DEFAULT_PORT = 8060
|
||||||
|
|
||||||
# Services
|
# Services
|
||||||
SERVICE_SEARCH = "search"
|
SERVICE_SEARCH = "search"
|
||||||
|
|
||||||
|
# Config
|
||||||
|
CONF_PLAY_MEDIA_APP_ID = "play_media_app_id"
|
||||||
|
|
||||||
|
# Defaults
|
||||||
|
DEFAULT_PLAY_MEDIA_APP_ID = "15985"
|
||||||
|
|
|
@ -29,15 +29,12 @@ class RokuDataUpdateCoordinator(DataUpdateCoordinator[Device]):
|
||||||
roku: Roku
|
roku: Roku
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self, hass: HomeAssistant, *, host: str, device_id: str, play_media_app_id: str
|
||||||
hass: HomeAssistant,
|
|
||||||
*,
|
|
||||||
host: str,
|
|
||||||
device_id: str,
|
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize global Roku data updater."""
|
"""Initialize global Roku data updater."""
|
||||||
self.device_id = device_id
|
self.device_id = device_id
|
||||||
self.roku = Roku(host=host, session=async_get_clientsession(hass))
|
self.roku = Roku(host=host, session=async_get_clientsession(hass))
|
||||||
|
self.play_media_app_id = play_media_app_id
|
||||||
|
|
||||||
self.full_update_interval = timedelta(minutes=15)
|
self.full_update_interval = timedelta(minutes=15)
|
||||||
self.last_full_update = None
|
self.last_full_update = None
|
||||||
|
|
|
@ -445,17 +445,25 @@ class RokuMediaPlayer(RokuEntity, MediaPlayerEntity):
|
||||||
if attr in extra
|
if attr in extra
|
||||||
}
|
}
|
||||||
|
|
||||||
params = {"t": "a", **params}
|
params = {"u": media_id, "t": "a", **params}
|
||||||
|
|
||||||
await self.coordinator.roku.play_on_roku(media_id, params)
|
await self.coordinator.roku.launch(
|
||||||
|
self.coordinator.play_media_app_id,
|
||||||
|
params,
|
||||||
|
)
|
||||||
elif media_type in {MediaType.URL, MediaType.VIDEO}:
|
elif media_type in {MediaType.URL, MediaType.VIDEO}:
|
||||||
params = {
|
params = {
|
||||||
param: extra[attr]
|
param: extra[attr]
|
||||||
for (attr, param) in ATTRS_TO_PLAY_ON_ROKU_PARAMS.items()
|
for (attr, param) in ATTRS_TO_PLAY_ON_ROKU_PARAMS.items()
|
||||||
if attr in extra
|
if attr in extra
|
||||||
}
|
}
|
||||||
|
params["u"] = media_id
|
||||||
|
params["t"] = "v"
|
||||||
|
|
||||||
await self.coordinator.roku.play_on_roku(media_id, params)
|
await self.coordinator.roku.launch(
|
||||||
|
self.coordinator.play_media_app_id,
|
||||||
|
params,
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
_LOGGER.error("Media type %s is not supported", original_media_type)
|
_LOGGER.error("Media type %s is not supported", original_media_type)
|
||||||
return
|
return
|
||||||
|
|
|
@ -24,6 +24,18 @@
|
||||||
"unknown": "[%key:common::config_flow::error::unknown%]"
|
"unknown": "[%key:common::config_flow::error::unknown%]"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"options": {
|
||||||
|
"step": {
|
||||||
|
"init": {
|
||||||
|
"data": {
|
||||||
|
"play_media_app_id": "Play Media Roku Application ID"
|
||||||
|
},
|
||||||
|
"data_description": {
|
||||||
|
"play_media_app_id": "The application ID to use when launching media playback. Must support the PlayOnRoku API."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"entity": {
|
"entity": {
|
||||||
"binary_sensor": {
|
"binary_sensor": {
|
||||||
"headphones_connected": {
|
"headphones_connected": {
|
||||||
|
|
|
@ -6,5 +6,5 @@
|
||||||
"documentation": "https://www.home-assistant.io/integrations/solarlog",
|
"documentation": "https://www.home-assistant.io/integrations/solarlog",
|
||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"loggers": ["solarlog_cli"],
|
"loggers": ["solarlog_cli"],
|
||||||
"requirements": ["solarlog_cli==0.3.1"]
|
"requirements": ["solarlog_cli==0.3.2"]
|
||||||
}
|
}
|
||||||
|
|
|
@ -84,7 +84,9 @@ def _create_entry(
|
||||||
original_name=f"{DEFAULT_NAME} {tag_id}",
|
original_name=f"{DEFAULT_NAME} {tag_id}",
|
||||||
suggested_object_id=slugify(name) if name else tag_id,
|
suggested_object_id=slugify(name) if name else tag_id,
|
||||||
)
|
)
|
||||||
return entity_registry.async_update_entity(entry.entity_id, name=name)
|
if name:
|
||||||
|
return entity_registry.async_update_entity(entry.entity_id, name=name)
|
||||||
|
return entry
|
||||||
|
|
||||||
|
|
||||||
class TagStore(Store[collection.SerializedStorageCollection]):
|
class TagStore(Store[collection.SerializedStorageCollection]):
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import ipaddress
|
||||||
import logging
|
import logging
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
|
@ -38,7 +39,19 @@ class WebControlProConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||||
"""Handle the DHCP discovery step."""
|
"""Handle the DHCP discovery step."""
|
||||||
unique_id = format_mac(discovery_info.macaddress)
|
unique_id = format_mac(discovery_info.macaddress)
|
||||||
await self.async_set_unique_id(unique_id)
|
await self.async_set_unique_id(unique_id)
|
||||||
self._abort_if_unique_id_configured()
|
|
||||||
|
entry = self.hass.config_entries.async_entry_for_domain_unique_id(
|
||||||
|
DOMAIN, unique_id
|
||||||
|
)
|
||||||
|
if entry:
|
||||||
|
try: # Check if current host is a valid IP address
|
||||||
|
ipaddress.ip_address(entry.data[CONF_HOST])
|
||||||
|
except ValueError: # Do not touch name-based host
|
||||||
|
return self.async_abort(reason="already_configured")
|
||||||
|
else: # Update existing host with new IP address
|
||||||
|
self._abort_if_unique_id_configured(
|
||||||
|
updates={CONF_HOST: discovery_info.ip}
|
||||||
|
)
|
||||||
|
|
||||||
for entry in self.hass.config_entries.async_entries(DOMAIN):
|
for entry in self.hass.config_entries.async_entries(DOMAIN):
|
||||||
if not entry.unique_id and entry.data[CONF_HOST] in (
|
if not entry.unique_id and entry.data[CONF_HOST] in (
|
||||||
|
|
|
@ -1198,7 +1198,7 @@ def create_zha_config(hass: HomeAssistant, ha_zha_data: HAZHAData) -> ZHAData:
|
||||||
# deep copy the yaml config to avoid modifying the original and to safely
|
# deep copy the yaml config to avoid modifying the original and to safely
|
||||||
# pass it to the ZHA library
|
# pass it to the ZHA library
|
||||||
app_config = copy.deepcopy(ha_zha_data.yaml_config.get(CONF_ZIGPY, {}))
|
app_config = copy.deepcopy(ha_zha_data.yaml_config.get(CONF_ZIGPY, {}))
|
||||||
database = app_config.get(
|
database = ha_zha_data.yaml_config.get(
|
||||||
CONF_DATABASE,
|
CONF_DATABASE,
|
||||||
hass.config.path(DEFAULT_DATABASE_NAME),
|
hass.config.path(DEFAULT_DATABASE_NAME),
|
||||||
)
|
)
|
||||||
|
|
|
@ -24,8 +24,6 @@ from homeassistant.components.climate import (
|
||||||
ATTR_HVAC_MODE,
|
ATTR_HVAC_MODE,
|
||||||
ATTR_TARGET_TEMP_HIGH,
|
ATTR_TARGET_TEMP_HIGH,
|
||||||
ATTR_TARGET_TEMP_LOW,
|
ATTR_TARGET_TEMP_LOW,
|
||||||
DEFAULT_MAX_TEMP,
|
|
||||||
DEFAULT_MIN_TEMP,
|
|
||||||
DOMAIN as CLIMATE_DOMAIN,
|
DOMAIN as CLIMATE_DOMAIN,
|
||||||
PRESET_NONE,
|
PRESET_NONE,
|
||||||
ClimateEntity,
|
ClimateEntity,
|
||||||
|
@ -421,7 +419,7 @@ class ZWaveClimate(ZWaveBaseEntity, ClimateEntity):
|
||||||
@property
|
@property
|
||||||
def min_temp(self) -> float:
|
def min_temp(self) -> float:
|
||||||
"""Return the minimum temperature."""
|
"""Return the minimum temperature."""
|
||||||
min_temp = DEFAULT_MIN_TEMP
|
min_temp = 0.0 # Not using DEFAULT_MIN_TEMP to allow wider range
|
||||||
base_unit: str = UnitOfTemperature.CELSIUS
|
base_unit: str = UnitOfTemperature.CELSIUS
|
||||||
try:
|
try:
|
||||||
temp = self._setpoint_value_or_raise(self._current_mode_setpoint_enums[0])
|
temp = self._setpoint_value_or_raise(self._current_mode_setpoint_enums[0])
|
||||||
|
@ -437,7 +435,7 @@ class ZWaveClimate(ZWaveBaseEntity, ClimateEntity):
|
||||||
@property
|
@property
|
||||||
def max_temp(self) -> float:
|
def max_temp(self) -> float:
|
||||||
"""Return the maximum temperature."""
|
"""Return the maximum temperature."""
|
||||||
max_temp = DEFAULT_MAX_TEMP
|
max_temp = 50.0 # Not using DEFAULT_MAX_TEMP to allow wider range
|
||||||
base_unit: str = UnitOfTemperature.CELSIUS
|
base_unit: str = UnitOfTemperature.CELSIUS
|
||||||
try:
|
try:
|
||||||
temp = self._setpoint_value_or_raise(self._current_mode_setpoint_enums[0])
|
temp = self._setpoint_value_or_raise(self._current_mode_setpoint_enums[0])
|
||||||
|
|
|
@ -24,7 +24,7 @@ if TYPE_CHECKING:
|
||||||
APPLICATION_NAME: Final = "HomeAssistant"
|
APPLICATION_NAME: Final = "HomeAssistant"
|
||||||
MAJOR_VERSION: Final = 2024
|
MAJOR_VERSION: Final = 2024
|
||||||
MINOR_VERSION: Final = 10
|
MINOR_VERSION: Final = 10
|
||||||
PATCH_VERSION: Final = "2"
|
PATCH_VERSION: Final = "3"
|
||||||
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
||||||
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
|
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
|
||||||
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 12, 0)
|
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 12, 0)
|
||||||
|
|
|
@ -31,7 +31,7 @@ ha-ffmpeg==3.2.0
|
||||||
habluetooth==3.4.0
|
habluetooth==3.4.0
|
||||||
hass-nabucasa==0.81.1
|
hass-nabucasa==0.81.1
|
||||||
hassil==1.7.4
|
hassil==1.7.4
|
||||||
home-assistant-bluetooth==1.12.2
|
home-assistant-bluetooth==1.13.0
|
||||||
home-assistant-frontend==20241002.3
|
home-assistant-frontend==20241002.3
|
||||||
home-assistant-intents==2024.10.2
|
home-assistant-intents==2024.10.2
|
||||||
httpx==0.27.2
|
httpx==0.27.2
|
||||||
|
|
|
@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "homeassistant"
|
name = "homeassistant"
|
||||||
version = "2024.10.2"
|
version = "2024.10.3"
|
||||||
license = {text = "Apache-2.0"}
|
license = {text = "Apache-2.0"}
|
||||||
description = "Open-source home automation platform running on Python 3."
|
description = "Open-source home automation platform running on Python 3."
|
||||||
readme = "README.rst"
|
readme = "README.rst"
|
||||||
|
@ -46,7 +46,7 @@ dependencies = [
|
||||||
# When bumping httpx, please check the version pins of
|
# When bumping httpx, please check the version pins of
|
||||||
# httpcore, anyio, and h11 in gen_requirements_all
|
# httpcore, anyio, and h11 in gen_requirements_all
|
||||||
"httpx==0.27.2",
|
"httpx==0.27.2",
|
||||||
"home-assistant-bluetooth==1.12.2",
|
"home-assistant-bluetooth==1.13.0",
|
||||||
"ifaddr==0.2.0",
|
"ifaddr==0.2.0",
|
||||||
"Jinja2==3.1.4",
|
"Jinja2==3.1.4",
|
||||||
"lru-dict==1.3.0",
|
"lru-dict==1.3.0",
|
||||||
|
|
|
@ -20,7 +20,7 @@ ciso8601==2.3.1
|
||||||
fnv-hash-fast==1.0.2
|
fnv-hash-fast==1.0.2
|
||||||
hass-nabucasa==0.81.1
|
hass-nabucasa==0.81.1
|
||||||
httpx==0.27.2
|
httpx==0.27.2
|
||||||
home-assistant-bluetooth==1.12.2
|
home-assistant-bluetooth==1.13.0
|
||||||
ifaddr==0.2.0
|
ifaddr==0.2.0
|
||||||
Jinja2==3.1.4
|
Jinja2==3.1.4
|
||||||
lru-dict==1.3.0
|
lru-dict==1.3.0
|
||||||
|
|
|
@ -179,7 +179,7 @@ aioairq==0.3.2
|
||||||
aioairzone-cloud==0.6.6
|
aioairzone-cloud==0.6.6
|
||||||
|
|
||||||
# homeassistant.components.airzone
|
# homeassistant.components.airzone
|
||||||
aioairzone==0.9.3
|
aioairzone==0.9.5
|
||||||
|
|
||||||
# homeassistant.components.ambient_network
|
# homeassistant.components.ambient_network
|
||||||
# homeassistant.components.ambient_station
|
# homeassistant.components.ambient_station
|
||||||
|
@ -945,7 +945,7 @@ gardena-bluetooth==1.4.3
|
||||||
gassist-text==0.0.11
|
gassist-text==0.0.11
|
||||||
|
|
||||||
# homeassistant.components.google
|
# homeassistant.components.google
|
||||||
gcal-sync==6.1.5
|
gcal-sync==6.1.6
|
||||||
|
|
||||||
# homeassistant.components.geniushub
|
# homeassistant.components.geniushub
|
||||||
geniushub-client==0.7.1
|
geniushub-client==0.7.1
|
||||||
|
@ -1780,7 +1780,7 @@ pybbox==0.0.5-alpha
|
||||||
pyblackbird==0.6
|
pyblackbird==0.6
|
||||||
|
|
||||||
# homeassistant.components.bluesound
|
# homeassistant.components.bluesound
|
||||||
pyblu==1.0.3
|
pyblu==1.0.4
|
||||||
|
|
||||||
# homeassistant.components.neato
|
# homeassistant.components.neato
|
||||||
pybotvac==0.0.25
|
pybotvac==0.0.25
|
||||||
|
@ -1960,7 +1960,7 @@ pyintesishome==1.8.0
|
||||||
pyipma==3.0.7
|
pyipma==3.0.7
|
||||||
|
|
||||||
# homeassistant.components.ipp
|
# homeassistant.components.ipp
|
||||||
pyipp==0.16.0
|
pyipp==0.17.0
|
||||||
|
|
||||||
# homeassistant.components.iqvia
|
# homeassistant.components.iqvia
|
||||||
pyiqvia==2022.04.0
|
pyiqvia==2022.04.0
|
||||||
|
@ -2119,7 +2119,7 @@ pyoppleio-legacy==1.0.8
|
||||||
pyosoenergyapi==1.1.4
|
pyosoenergyapi==1.1.4
|
||||||
|
|
||||||
# homeassistant.components.opentherm_gw
|
# homeassistant.components.opentherm_gw
|
||||||
pyotgw==2.2.1
|
pyotgw==2.2.2
|
||||||
|
|
||||||
# homeassistant.auth.mfa_modules.notify
|
# homeassistant.auth.mfa_modules.notify
|
||||||
# homeassistant.auth.mfa_modules.totp
|
# homeassistant.auth.mfa_modules.totp
|
||||||
|
@ -2676,7 +2676,7 @@ soco==0.30.4
|
||||||
solaredge-local==0.2.3
|
solaredge-local==0.2.3
|
||||||
|
|
||||||
# homeassistant.components.solarlog
|
# homeassistant.components.solarlog
|
||||||
solarlog_cli==0.3.1
|
solarlog_cli==0.3.2
|
||||||
|
|
||||||
# homeassistant.components.solax
|
# homeassistant.components.solax
|
||||||
solax==3.1.1
|
solax==3.1.1
|
||||||
|
@ -3032,7 +3032,7 @@ youless-api==2.1.2
|
||||||
youtubeaio==1.1.5
|
youtubeaio==1.1.5
|
||||||
|
|
||||||
# homeassistant.components.media_extractor
|
# homeassistant.components.media_extractor
|
||||||
yt-dlp==2024.09.27
|
yt-dlp==2024.10.07
|
||||||
|
|
||||||
# homeassistant.components.zamg
|
# homeassistant.components.zamg
|
||||||
zamg==0.3.6
|
zamg==0.3.6
|
||||||
|
|
|
@ -167,7 +167,7 @@ aioairq==0.3.2
|
||||||
aioairzone-cloud==0.6.6
|
aioairzone-cloud==0.6.6
|
||||||
|
|
||||||
# homeassistant.components.airzone
|
# homeassistant.components.airzone
|
||||||
aioairzone==0.9.3
|
aioairzone==0.9.5
|
||||||
|
|
||||||
# homeassistant.components.ambient_network
|
# homeassistant.components.ambient_network
|
||||||
# homeassistant.components.ambient_station
|
# homeassistant.components.ambient_station
|
||||||
|
@ -798,7 +798,7 @@ gardena-bluetooth==1.4.3
|
||||||
gassist-text==0.0.11
|
gassist-text==0.0.11
|
||||||
|
|
||||||
# homeassistant.components.google
|
# homeassistant.components.google
|
||||||
gcal-sync==6.1.5
|
gcal-sync==6.1.6
|
||||||
|
|
||||||
# homeassistant.components.geniushub
|
# homeassistant.components.geniushub
|
||||||
geniushub-client==0.7.1
|
geniushub-client==0.7.1
|
||||||
|
@ -1448,7 +1448,7 @@ pybalboa==1.0.2
|
||||||
pyblackbird==0.6
|
pyblackbird==0.6
|
||||||
|
|
||||||
# homeassistant.components.bluesound
|
# homeassistant.components.bluesound
|
||||||
pyblu==1.0.3
|
pyblu==1.0.4
|
||||||
|
|
||||||
# homeassistant.components.neato
|
# homeassistant.components.neato
|
||||||
pybotvac==0.0.25
|
pybotvac==0.0.25
|
||||||
|
@ -1574,7 +1574,7 @@ pyinsteon==1.6.3
|
||||||
pyipma==3.0.7
|
pyipma==3.0.7
|
||||||
|
|
||||||
# homeassistant.components.ipp
|
# homeassistant.components.ipp
|
||||||
pyipp==0.16.0
|
pyipp==0.17.0
|
||||||
|
|
||||||
# homeassistant.components.iqvia
|
# homeassistant.components.iqvia
|
||||||
pyiqvia==2022.04.0
|
pyiqvia==2022.04.0
|
||||||
|
@ -1703,7 +1703,7 @@ pyopnsense==0.4.0
|
||||||
pyosoenergyapi==1.1.4
|
pyosoenergyapi==1.1.4
|
||||||
|
|
||||||
# homeassistant.components.opentherm_gw
|
# homeassistant.components.opentherm_gw
|
||||||
pyotgw==2.2.1
|
pyotgw==2.2.2
|
||||||
|
|
||||||
# homeassistant.auth.mfa_modules.notify
|
# homeassistant.auth.mfa_modules.notify
|
||||||
# homeassistant.auth.mfa_modules.totp
|
# homeassistant.auth.mfa_modules.totp
|
||||||
|
@ -2122,7 +2122,7 @@ snapcast==2.3.6
|
||||||
soco==0.30.4
|
soco==0.30.4
|
||||||
|
|
||||||
# homeassistant.components.solarlog
|
# homeassistant.components.solarlog
|
||||||
solarlog_cli==0.3.1
|
solarlog_cli==0.3.2
|
||||||
|
|
||||||
# homeassistant.components.solax
|
# homeassistant.components.solax
|
||||||
solax==3.1.1
|
solax==3.1.1
|
||||||
|
@ -2415,7 +2415,7 @@ youless-api==2.1.2
|
||||||
youtubeaio==1.1.5
|
youtubeaio==1.1.5
|
||||||
|
|
||||||
# homeassistant.components.media_extractor
|
# homeassistant.components.media_extractor
|
||||||
yt-dlp==2024.09.27
|
yt-dlp==2024.10.07
|
||||||
|
|
||||||
# homeassistant.components.zamg
|
# homeassistant.components.zamg
|
||||||
zamg==0.3.6
|
zamg==0.3.6
|
||||||
|
|
|
@ -220,6 +220,45 @@
|
||||||
}),
|
}),
|
||||||
]),
|
]),
|
||||||
}),
|
}),
|
||||||
|
dict({
|
||||||
|
'data': list([
|
||||||
|
dict({
|
||||||
|
'air_demand': 0,
|
||||||
|
'coldStage': 0,
|
||||||
|
'coldStages': 0,
|
||||||
|
'coolmaxtemp': 30,
|
||||||
|
'coolmintemp': 15,
|
||||||
|
'coolsetpoint': 20,
|
||||||
|
'errors': list([
|
||||||
|
]),
|
||||||
|
'floor_demand': 0,
|
||||||
|
'heatStage': 0,
|
||||||
|
'heatStages': 0,
|
||||||
|
'heatmaxtemp': 30,
|
||||||
|
'heatmintemp': 15,
|
||||||
|
'heatsetpoint': 20,
|
||||||
|
'humidity': 0,
|
||||||
|
'maxTemp': 30,
|
||||||
|
'minTemp': 15,
|
||||||
|
'mode': 6,
|
||||||
|
'modes': list([
|
||||||
|
1,
|
||||||
|
2,
|
||||||
|
3,
|
||||||
|
4,
|
||||||
|
5,
|
||||||
|
6,
|
||||||
|
]),
|
||||||
|
'name': 'Aux Heat',
|
||||||
|
'on': 1,
|
||||||
|
'roomTemp': 22,
|
||||||
|
'setpoint': 20,
|
||||||
|
'systemID': 4,
|
||||||
|
'units': 0,
|
||||||
|
'zoneID': 1,
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
}),
|
||||||
]),
|
]),
|
||||||
}),
|
}),
|
||||||
'version': dict({
|
'version': dict({
|
||||||
|
@ -269,8 +308,8 @@
|
||||||
'temp-set': 45,
|
'temp-set': 45,
|
||||||
'temp-unit': 0,
|
'temp-unit': 0,
|
||||||
}),
|
}),
|
||||||
'num-systems': 3,
|
'num-systems': 4,
|
||||||
'num-zones': 7,
|
'num-zones': 8,
|
||||||
'systems': dict({
|
'systems': dict({
|
||||||
'1': dict({
|
'1': dict({
|
||||||
'available': True,
|
'available': True,
|
||||||
|
@ -320,6 +359,23 @@
|
||||||
]),
|
]),
|
||||||
'problems': False,
|
'problems': False,
|
||||||
}),
|
}),
|
||||||
|
'4': dict({
|
||||||
|
'available': True,
|
||||||
|
'full-name': 'Airzone [4] System',
|
||||||
|
'id': 4,
|
||||||
|
'master-system-zone': '4:1',
|
||||||
|
'master-zone': 1,
|
||||||
|
'mode': 6,
|
||||||
|
'modes': list([
|
||||||
|
1,
|
||||||
|
2,
|
||||||
|
3,
|
||||||
|
4,
|
||||||
|
5,
|
||||||
|
6,
|
||||||
|
]),
|
||||||
|
'problems': False,
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
'version': '1.62',
|
'version': '1.62',
|
||||||
'webserver': dict({
|
'webserver': dict({
|
||||||
|
@ -683,6 +739,46 @@
|
||||||
'temp-step': 1.0,
|
'temp-step': 1.0,
|
||||||
'temp-unit': 1,
|
'temp-unit': 1,
|
||||||
}),
|
}),
|
||||||
|
'4:1': dict({
|
||||||
|
'absolute-temp-max': 30.0,
|
||||||
|
'absolute-temp-min': 15.0,
|
||||||
|
'action': 5,
|
||||||
|
'air-demand': False,
|
||||||
|
'available': True,
|
||||||
|
'cold-stage': 0,
|
||||||
|
'cool-temp-max': 30.0,
|
||||||
|
'cool-temp-min': 15.0,
|
||||||
|
'cool-temp-set': 20.0,
|
||||||
|
'demand': False,
|
||||||
|
'double-set-point': False,
|
||||||
|
'floor-demand': False,
|
||||||
|
'full-name': 'Airzone [4:1] Aux Heat',
|
||||||
|
'heat-stage': 0,
|
||||||
|
'heat-temp-max': 30.0,
|
||||||
|
'heat-temp-min': 15.0,
|
||||||
|
'heat-temp-set': 20.0,
|
||||||
|
'id': 1,
|
||||||
|
'master': True,
|
||||||
|
'mode': 6,
|
||||||
|
'modes': list([
|
||||||
|
1,
|
||||||
|
2,
|
||||||
|
3,
|
||||||
|
4,
|
||||||
|
5,
|
||||||
|
6,
|
||||||
|
]),
|
||||||
|
'name': 'Aux Heat',
|
||||||
|
'on': True,
|
||||||
|
'problems': False,
|
||||||
|
'system': 4,
|
||||||
|
'temp': 22.0,
|
||||||
|
'temp-max': 30.0,
|
||||||
|
'temp-min': 15.0,
|
||||||
|
'temp-set': 20.0,
|
||||||
|
'temp-step': 0.5,
|
||||||
|
'temp-unit': 0,
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
|
|
|
@ -225,6 +225,23 @@ async def test_airzone_create_climates(hass: HomeAssistant) -> None:
|
||||||
assert state.attributes.get(ATTR_TARGET_TEMP_HIGH) == 25.0
|
assert state.attributes.get(ATTR_TARGET_TEMP_HIGH) == 25.0
|
||||||
assert state.attributes.get(ATTR_TARGET_TEMP_LOW) == 22.8
|
assert state.attributes.get(ATTR_TARGET_TEMP_LOW) == 22.8
|
||||||
|
|
||||||
|
state = hass.states.get("climate.aux_heat")
|
||||||
|
assert state.state == HVACMode.HEAT
|
||||||
|
assert state.attributes.get(ATTR_CURRENT_HUMIDITY) is None
|
||||||
|
assert state.attributes.get(ATTR_CURRENT_TEMPERATURE) == 22
|
||||||
|
assert state.attributes.get(ATTR_HVAC_ACTION) == HVACAction.IDLE
|
||||||
|
assert state.attributes.get(ATTR_HVAC_MODES) == [
|
||||||
|
HVACMode.OFF,
|
||||||
|
HVACMode.COOL,
|
||||||
|
HVACMode.HEAT,
|
||||||
|
HVACMode.FAN_ONLY,
|
||||||
|
HVACMode.DRY,
|
||||||
|
]
|
||||||
|
assert state.attributes.get(ATTR_MAX_TEMP) == 30
|
||||||
|
assert state.attributes.get(ATTR_MIN_TEMP) == 15
|
||||||
|
assert state.attributes.get(ATTR_TARGET_TEMP_STEP) == API_TEMPERATURE_STEP
|
||||||
|
assert state.attributes.get(ATTR_TEMPERATURE) == 20.0
|
||||||
|
|
||||||
HVAC_MOCK_CHANGED = copy.deepcopy(HVAC_MOCK)
|
HVAC_MOCK_CHANGED = copy.deepcopy(HVAC_MOCK)
|
||||||
HVAC_MOCK_CHANGED[API_SYSTEMS][0][API_DATA][0][API_MAX_TEMP] = 25
|
HVAC_MOCK_CHANGED[API_SYSTEMS][0][API_DATA][0][API_MAX_TEMP] = 25
|
||||||
HVAC_MOCK_CHANGED[API_SYSTEMS][0][API_DATA][0][API_MIN_TEMP] = 10
|
HVAC_MOCK_CHANGED[API_SYSTEMS][0][API_DATA][0][API_MIN_TEMP] = 10
|
||||||
|
|
|
@ -272,6 +272,37 @@ HVAC_MOCK = {
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
API_DATA: [
|
||||||
|
{
|
||||||
|
API_SYSTEM_ID: 4,
|
||||||
|
API_ZONE_ID: 1,
|
||||||
|
API_NAME: "Aux Heat",
|
||||||
|
API_ON: 1,
|
||||||
|
API_COOL_SET_POINT: 20,
|
||||||
|
API_COOL_MAX_TEMP: 30,
|
||||||
|
API_COOL_MIN_TEMP: 15,
|
||||||
|
API_HEAT_SET_POINT: 20,
|
||||||
|
API_HEAT_MAX_TEMP: 30,
|
||||||
|
API_HEAT_MIN_TEMP: 15,
|
||||||
|
API_MAX_TEMP: 30,
|
||||||
|
API_MIN_TEMP: 15,
|
||||||
|
API_SET_POINT: 20,
|
||||||
|
API_ROOM_TEMP: 22,
|
||||||
|
API_MODES: [1, 2, 3, 4, 5, 6],
|
||||||
|
API_MODE: 6,
|
||||||
|
API_COLD_STAGES: 0,
|
||||||
|
API_COLD_STAGE: 0,
|
||||||
|
API_HEAT_STAGES: 0,
|
||||||
|
API_HEAT_STAGE: 0,
|
||||||
|
API_HUMIDITY: 0,
|
||||||
|
API_UNITS: 0,
|
||||||
|
API_ERRORS: [],
|
||||||
|
API_AIR_DEMAND: 0,
|
||||||
|
API_FLOOR_DEMAND: 0,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
# name: test_diagnostics
|
# name: test_diagnostics
|
||||||
dict({
|
dict({
|
||||||
'data': dict({
|
'data': dict({
|
||||||
|
'booted_at': '2019-11-11T09:10:02+00:00',
|
||||||
'info': dict({
|
'info': dict({
|
||||||
'command_set': 'ESCPL2,BDC,D4,D4PX,ESCPR7,END4,GENEP,URF',
|
'command_set': 'ESCPL2,BDC,D4,D4PX,ESCPR7,END4,GENEP,URF',
|
||||||
'location': None,
|
'location': None,
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
"""Tests for the diagnostics data provided by the Internet Printing Protocol (IPP) integration."""
|
"""Tests for the diagnostics data provided by the Internet Printing Protocol (IPP) integration."""
|
||||||
|
|
||||||
|
import pytest
|
||||||
from syrupy import SnapshotAssertion
|
from syrupy import SnapshotAssertion
|
||||||
|
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
@ -9,6 +10,7 @@ from tests.components.diagnostics import get_diagnostics_for_config_entry
|
||||||
from tests.typing import ClientSessionGenerator
|
from tests.typing import ClientSessionGenerator
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.freeze_time("2019-11-11 09:10:32+00:00")
|
||||||
async def test_diagnostics(
|
async def test_diagnostics(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
hass_client: ClientSessionGenerator,
|
hass_client: ClientSessionGenerator,
|
||||||
|
|
|
@ -6,7 +6,7 @@ from unittest.mock import MagicMock
|
||||||
import pytest
|
import pytest
|
||||||
from rokuecp import RokuConnectionError
|
from rokuecp import RokuConnectionError
|
||||||
|
|
||||||
from homeassistant.components.roku.const import DOMAIN
|
from homeassistant.components.roku.const import CONF_PLAY_MEDIA_APP_ID, DOMAIN
|
||||||
from homeassistant.config_entries import SOURCE_HOMEKIT, SOURCE_SSDP, SOURCE_USER
|
from homeassistant.config_entries import SOURCE_HOMEKIT, SOURCE_SSDP, SOURCE_USER
|
||||||
from homeassistant.const import CONF_HOST, CONF_NAME, CONF_SOURCE
|
from homeassistant.const import CONF_HOST, CONF_NAME, CONF_SOURCE
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
@ -254,3 +254,25 @@ async def test_ssdp_discovery(
|
||||||
assert result["data"]
|
assert result["data"]
|
||||||
assert result["data"][CONF_HOST] == HOST
|
assert result["data"][CONF_HOST] == HOST
|
||||||
assert result["data"][CONF_NAME] == UPNP_FRIENDLY_NAME
|
assert result["data"][CONF_NAME] == UPNP_FRIENDLY_NAME
|
||||||
|
|
||||||
|
|
||||||
|
async def test_options_flow(
|
||||||
|
hass: HomeAssistant, mock_config_entry: MockConfigEntry
|
||||||
|
) -> None:
|
||||||
|
"""Test options config flow."""
|
||||||
|
mock_config_entry.add_to_hass(hass)
|
||||||
|
|
||||||
|
result = await hass.config_entries.options.async_init(mock_config_entry.entry_id)
|
||||||
|
|
||||||
|
assert result.get("type") is FlowResultType.FORM
|
||||||
|
assert result.get("step_id") == "init"
|
||||||
|
|
||||||
|
result2 = await hass.config_entries.options.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
user_input={CONF_PLAY_MEDIA_APP_ID: "782875"},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result2.get("type") is FlowResultType.CREATE_ENTRY
|
||||||
|
assert result2.get("data") == {
|
||||||
|
CONF_PLAY_MEDIA_APP_ID: "782875",
|
||||||
|
}
|
||||||
|
|
|
@ -32,6 +32,7 @@ from homeassistant.components.roku.const import (
|
||||||
ATTR_FORMAT,
|
ATTR_FORMAT,
|
||||||
ATTR_KEYWORD,
|
ATTR_KEYWORD,
|
||||||
ATTR_MEDIA_TYPE,
|
ATTR_MEDIA_TYPE,
|
||||||
|
DEFAULT_PLAY_MEDIA_APP_ID,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
SERVICE_SEARCH,
|
SERVICE_SEARCH,
|
||||||
)
|
)
|
||||||
|
@ -495,7 +496,7 @@ async def test_services_play_media(
|
||||||
blocking=True,
|
blocking=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
assert mock_roku.play_on_roku.call_count == 0
|
assert mock_roku.launch.call_count == 0
|
||||||
|
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
MP_DOMAIN,
|
MP_DOMAIN,
|
||||||
|
@ -509,7 +510,7 @@ async def test_services_play_media(
|
||||||
blocking=True,
|
blocking=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
assert mock_roku.play_on_roku.call_count == 0
|
assert mock_roku.launch.call_count == 0
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
|
@ -546,9 +547,10 @@ async def test_services_play_media_audio(
|
||||||
},
|
},
|
||||||
blocking=True,
|
blocking=True,
|
||||||
)
|
)
|
||||||
mock_roku.play_on_roku.assert_called_once_with(
|
mock_roku.launch.assert_called_once_with(
|
||||||
content_id,
|
DEFAULT_PLAY_MEDIA_APP_ID,
|
||||||
{
|
{
|
||||||
|
"u": content_id,
|
||||||
"t": "a",
|
"t": "a",
|
||||||
"songName": resolved_name,
|
"songName": resolved_name,
|
||||||
"songFormat": resolved_format,
|
"songFormat": resolved_format,
|
||||||
|
@ -591,9 +593,11 @@ async def test_services_play_media_video(
|
||||||
},
|
},
|
||||||
blocking=True,
|
blocking=True,
|
||||||
)
|
)
|
||||||
mock_roku.play_on_roku.assert_called_once_with(
|
mock_roku.launch.assert_called_once_with(
|
||||||
content_id,
|
DEFAULT_PLAY_MEDIA_APP_ID,
|
||||||
{
|
{
|
||||||
|
"u": content_id,
|
||||||
|
"t": "v",
|
||||||
"videoName": resolved_name,
|
"videoName": resolved_name,
|
||||||
"videoFormat": resolved_format,
|
"videoFormat": resolved_format,
|
||||||
},
|
},
|
||||||
|
@ -617,10 +621,12 @@ async def test_services_camera_play_stream(
|
||||||
blocking=True,
|
blocking=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
assert mock_roku.play_on_roku.call_count == 1
|
assert mock_roku.launch.call_count == 1
|
||||||
mock_roku.play_on_roku.assert_called_with(
|
mock_roku.launch.assert_called_with(
|
||||||
"https://awesome.tld/api/hls/api_token/master_playlist.m3u8",
|
DEFAULT_PLAY_MEDIA_APP_ID,
|
||||||
{
|
{
|
||||||
|
"u": "https://awesome.tld/api/hls/api_token/master_playlist.m3u8",
|
||||||
|
"t": "v",
|
||||||
"videoName": "Camera Stream",
|
"videoName": "Camera Stream",
|
||||||
"videoFormat": "hls",
|
"videoFormat": "hls",
|
||||||
},
|
},
|
||||||
|
@ -653,14 +659,21 @@ async def test_services_play_media_local_source(
|
||||||
blocking=True,
|
blocking=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
assert mock_roku.play_on_roku.call_count == 1
|
assert mock_roku.launch.call_count == 1
|
||||||
assert mock_roku.play_on_roku.call_args
|
assert mock_roku.launch.call_args
|
||||||
call_args = mock_roku.play_on_roku.call_args.args
|
call_args = mock_roku.launch.call_args.args
|
||||||
assert "/local/Epic%20Sax%20Guy%2010%20Hours.mp4?authSig=" in call_args[0]
|
assert call_args[0] == DEFAULT_PLAY_MEDIA_APP_ID
|
||||||
assert call_args[1] == {
|
assert "u" in call_args[1]
|
||||||
"videoFormat": "mp4",
|
assert "/local/Epic%20Sax%20Guy%2010%20Hours.mp4?authSig=" in call_args[1]["u"]
|
||||||
"videoName": "media-source://media_source/local/Epic Sax Guy 10 Hours.mp4",
|
assert "t" in call_args[1]
|
||||||
}
|
assert call_args[1]["t"] == "v"
|
||||||
|
assert "videoFormat" in call_args[1]
|
||||||
|
assert call_args[1]["videoFormat"] == "mp4"
|
||||||
|
assert "videoName" in call_args[1]
|
||||||
|
assert (
|
||||||
|
call_args[1]["videoName"]
|
||||||
|
== "media-source://media_source/local/Epic Sax Guy 10 Hours.mp4"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("mock_device", ["roku/rokutv-7820x.json"], indirect=True)
|
@pytest.mark.parametrize("mock_device", ["roku/rokutv-7820x.json"], indirect=True)
|
||||||
|
|
|
@ -294,6 +294,10 @@ async def test_entity_created_and_removed(
|
||||||
assert item["id"] == "1234567890"
|
assert item["id"] == "1234567890"
|
||||||
assert item["name"] == "Kitchen tag"
|
assert item["name"] == "Kitchen tag"
|
||||||
|
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
er_entity = entity_registry.async_get("tag.kitchen_tag")
|
||||||
|
assert er_entity.name == "Kitchen tag"
|
||||||
|
|
||||||
entity = hass.states.get("tag.kitchen_tag")
|
entity = hass.states.get("tag.kitchen_tag")
|
||||||
assert entity
|
assert entity
|
||||||
assert entity.state == STATE_UNKNOWN
|
assert entity.state == STATE_UNKNOWN
|
||||||
|
|
|
@ -112,6 +112,96 @@ async def test_config_flow_from_dhcp_add_mac(
|
||||||
assert hass.config_entries.async_entries(DOMAIN)[0].unique_id == "00:11:22:33:44:55"
|
assert hass.config_entries.async_entries(DOMAIN)[0].unique_id == "00:11:22:33:44:55"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_config_flow_from_dhcp_ip_update(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mock_setup_entry: AsyncMock,
|
||||||
|
) -> None:
|
||||||
|
"""Test we can use DHCP discovery to update IP in a config entry."""
|
||||||
|
info = DhcpServiceInfo(
|
||||||
|
ip="1.2.3.4", hostname="webcontrol", macaddress="00:11:22:33:44:55"
|
||||||
|
)
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": SOURCE_DHCP}, data=info
|
||||||
|
)
|
||||||
|
assert result["type"] is FlowResultType.FORM
|
||||||
|
assert result["errors"] == {}
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"wmspro.webcontrol.WebControlPro.ping",
|
||||||
|
return_value=True,
|
||||||
|
):
|
||||||
|
result = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
{
|
||||||
|
CONF_HOST: "1.2.3.4",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] is FlowResultType.CREATE_ENTRY
|
||||||
|
assert result["title"] == "1.2.3.4"
|
||||||
|
assert result["data"] == {
|
||||||
|
CONF_HOST: "1.2.3.4",
|
||||||
|
}
|
||||||
|
assert len(mock_setup_entry.mock_calls) == 1
|
||||||
|
assert hass.config_entries.async_entries(DOMAIN)[0].unique_id == "00:11:22:33:44:55"
|
||||||
|
|
||||||
|
info = DhcpServiceInfo(
|
||||||
|
ip="5.6.7.8", hostname="webcontrol", macaddress="00:11:22:33:44:55"
|
||||||
|
)
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": SOURCE_DHCP}, data=info
|
||||||
|
)
|
||||||
|
assert result["type"] is FlowResultType.ABORT
|
||||||
|
assert result["reason"] == "already_configured"
|
||||||
|
assert hass.config_entries.async_entries(DOMAIN)[0].unique_id == "00:11:22:33:44:55"
|
||||||
|
assert hass.config_entries.async_entries(DOMAIN)[0].data[CONF_HOST] == "5.6.7.8"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_config_flow_from_dhcp_no_update(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mock_setup_entry: AsyncMock,
|
||||||
|
) -> None:
|
||||||
|
"""Test we do not use DHCP discovery to overwrite hostname with IP in config entry."""
|
||||||
|
info = DhcpServiceInfo(
|
||||||
|
ip="1.2.3.4", hostname="webcontrol", macaddress="00:11:22:33:44:55"
|
||||||
|
)
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": SOURCE_DHCP}, data=info
|
||||||
|
)
|
||||||
|
assert result["type"] is FlowResultType.FORM
|
||||||
|
assert result["errors"] == {}
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"wmspro.webcontrol.WebControlPro.ping",
|
||||||
|
return_value=True,
|
||||||
|
):
|
||||||
|
result = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
{
|
||||||
|
CONF_HOST: "webcontrol",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] is FlowResultType.CREATE_ENTRY
|
||||||
|
assert result["title"] == "webcontrol"
|
||||||
|
assert result["data"] == {
|
||||||
|
CONF_HOST: "webcontrol",
|
||||||
|
}
|
||||||
|
assert len(mock_setup_entry.mock_calls) == 1
|
||||||
|
assert hass.config_entries.async_entries(DOMAIN)[0].unique_id == "00:11:22:33:44:55"
|
||||||
|
|
||||||
|
info = DhcpServiceInfo(
|
||||||
|
ip="5.6.7.8", hostname="webcontrol", macaddress="00:11:22:33:44:55"
|
||||||
|
)
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": SOURCE_DHCP}, data=info
|
||||||
|
)
|
||||||
|
assert result["type"] is FlowResultType.ABORT
|
||||||
|
assert result["reason"] == "already_configured"
|
||||||
|
assert hass.config_entries.async_entries(DOMAIN)[0].unique_id == "00:11:22:33:44:55"
|
||||||
|
assert hass.config_entries.async_entries(DOMAIN)[0].data[CONF_HOST] == "webcontrol"
|
||||||
|
|
||||||
|
|
||||||
async def test_config_flow_ping_failed(
|
async def test_config_flow_ping_failed(
|
||||||
hass: HomeAssistant, mock_setup_entry: AsyncMock
|
hass: HomeAssistant, mock_setup_entry: AsyncMock
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|
|
@ -812,8 +812,8 @@ async def test_thermostat_heatit_z_trm2fx(
|
||||||
| ClimateEntityFeature.TURN_OFF
|
| ClimateEntityFeature.TURN_OFF
|
||||||
| ClimateEntityFeature.TURN_ON
|
| ClimateEntityFeature.TURN_ON
|
||||||
)
|
)
|
||||||
assert state.attributes[ATTR_MIN_TEMP] == 7
|
assert state.attributes[ATTR_MIN_TEMP] == 0
|
||||||
assert state.attributes[ATTR_MAX_TEMP] == 35
|
assert state.attributes[ATTR_MAX_TEMP] == 50
|
||||||
|
|
||||||
# Try switching to external sensor
|
# Try switching to external sensor
|
||||||
event = Event(
|
event = Event(
|
||||||
|
|
Loading…
Add table
Reference in a new issue