Compare commits

..

8 Commits

Author SHA1 Message Date
Franck Nijhof 9ce64f8990 Bumped version to 2023.1.0b1 2022-12-28 22:18:06 +01:00
Aaron Bach 6ef4086683 Fix incorrect values for AirVisual Pro sensors (#84725) 2022-12-28 22:16:22 +01:00
Aaron Bach b146f52317 Remove incorrect unit for AirVisual AQI sensor (#84723)
fixes undefined
2022-12-28 22:16:19 +01:00
Hans Oischinger 001bd78bcb water_heater: Add unsupported states (#84720) 2022-12-28 22:16:16 +01:00
Allen Porter ba4ec8f8c1 Gracefully handle caldav event with missing summary (#84719)
fixes undefined
2022-12-28 22:16:12 +01:00
Michael 8aa3a6cc15 Remove deprecated tankerkoenig YAML config (#84711)
remove yaml import
2022-12-28 22:16:09 +01:00
Hmmbob ed43e1d3a4 Update apprise to 1.2.1 (#84705) 2022-12-28 22:16:06 +01:00
Marcel van der Veldt b7654c0fce Bump python matter server to 1.0.8 (#84692)
Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2022-12-28 22:16:02 +01:00
21 changed files with 77 additions and 198 deletions
@@ -69,7 +69,6 @@ GEOGRAPHY_SENSOR_DESCRIPTIONS = (
key=SENSOR_KIND_AQI,
name="Air quality index",
device_class=SensorDeviceClass.AQI,
native_unit_of_measurement="AQI",
state_class=SensorStateClass.MEASUREMENT,
),
SensorEntityDescription(
@@ -159,6 +159,6 @@ class AirVisualProSensor(AirVisualProEntity, SensorEntity):
elif self.entity_description.key == SENSOR_KIND_BATTERY_LEVEL:
self._attr_native_value = self.status["battery"]
else:
self._attr_native_value = self.MEASUREMENTS_KEY_TO_VALUE[
self.entity_description.key
self._attr_native_value = self.measurements[
self.MEASUREMENTS_KEY_TO_VALUE[self.entity_description.key]
]
@@ -2,7 +2,7 @@
"domain": "apprise",
"name": "Apprise",
"documentation": "https://www.home-assistant.io/integrations/apprise",
"requirements": ["apprise==1.2.0"],
"requirements": ["apprise==1.2.1"],
"codeowners": ["@caronc"],
"iot_class": "cloud_push",
"loggers": ["apprise"]
+4 -2
View File
@@ -184,7 +184,7 @@ class WebDavCalendarData:
continue
event_list.append(
CalendarEvent(
summary=vevent.summary.value,
summary=self.get_attr_value(vevent, "summary") or "",
start=vevent.dtstart.value,
end=self.get_end_date(vevent),
location=self.get_attr_value(vevent, "location"),
@@ -264,7 +264,9 @@ class WebDavCalendarData:
return
# Populate the entity attributes with the event values
(summary, offset) = extract_offset(vevent.summary.value, OFFSET)
(summary, offset) = extract_offset(
self.get_attr_value(vevent, "summary") or "", OFFSET
)
self.event = CalendarEvent(
summary=summary,
start=vevent.dtstart.value,
+1 -2
View File
@@ -2,7 +2,6 @@
from __future__ import annotations
import asyncio
from typing import cast
import async_timeout
from matter_server.client import MatterClient
@@ -245,7 +244,7 @@ def _async_init_services(hass: HomeAssistant) -> None:
# This could be more efficient
for node in await matter_client.get_nodes():
if node.unique_id == unique_id:
return cast(int, node.node_id)
return node.node_id
return None
+8 -3
View File
@@ -47,8 +47,12 @@ class MatterAdapter:
for node in await self.matter_client.get_nodes():
self._setup_node(node)
def node_added_callback(event: EventType, node: MatterNode) -> None:
def node_added_callback(event: EventType, node: MatterNode | None) -> None:
"""Handle node added event."""
if node is None:
# We can clean this up when we've improved the typing in the library.
# https://github.com/home-assistant-libs/python-matter-server/pull/153
raise RuntimeError("Node added event without node")
self._setup_node(node)
self.config_entry.async_on_unload(
@@ -61,8 +65,9 @@ class MatterAdapter:
bridge_unique_id: str | None = None
if node.aggregator_device_type_instance is not None:
node_info = node.root_device_type_instance.get_cluster(all_clusters.Basic)
if node.aggregator_device_type_instance is not None and (
node_info := node.root_device_type_instance.get_cluster(all_clusters.Basic)
):
self._create_device_registry(
node_info, node_info.nodeLabel or "Hub device", None
)
@@ -39,9 +39,8 @@ class MatterBinarySensor(MatterEntity, BinarySensorEntity):
@callback
def _update_from_device(self) -> None:
"""Update from device."""
self._attr_is_on = self._device_type_instance.get_cluster(
clusters.BooleanState
).stateValue
cluster = self._device_type_instance.get_cluster(clusters.BooleanState)
self._attr_is_on = cluster.stateValue if cluster else None
class MatterOccupancySensor(MatterBinarySensor):
@@ -52,11 +51,9 @@ class MatterOccupancySensor(MatterBinarySensor):
@callback
def _update_from_device(self) -> None:
"""Update from device."""
occupancy = self._device_type_instance.get_cluster(
clusters.OccupancySensing
).occupancy
cluster = self._device_type_instance.get_cluster(clusters.OccupancySensing)
# The first bit = if occupied
self._attr_is_on = occupancy & 1 == 1
self._attr_is_on = cluster.occupancy & 1 == 1 if cluster else None
@dataclass
@@ -20,6 +20,7 @@ from homeassistant.components.hassio import (
from homeassistant.const import CONF_URL
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import AbortFlow, FlowResult
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import aiohttp_client
from .addon import get_addon_manager
@@ -131,7 +132,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
try:
await self.start_task
except (CannotConnect, AddonError, AbortFlow) as err:
except (FailedConnect, AddonError, AbortFlow) as err:
self.start_task = None
LOGGER.error(err)
return self.async_show_progress_done(next_step_id="start_failed")
@@ -170,7 +171,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
else:
break
else:
raise CannotConnect("Failed to start Matter Server add-on: timeout")
raise FailedConnect("Failed to start Matter Server add-on: timeout")
finally:
# Continue the flow after show progress when the task is done.
self.hass.async_create_task(
@@ -324,3 +325,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
CONF_INTEGRATION_CREATED_ADDON: self.integration_created_addon,
},
)
class FailedConnect(HomeAssistantError):
"""Failed to connect to the Matter Server."""
+9 -1
View File
@@ -59,7 +59,15 @@ class MatterEntity(Entity):
self._unsubscribes: list[Callable] = []
# for fast lookups we create a mapping to the attribute paths
self._attributes_map: dict[type, str] = {}
self._attr_unique_id = f"{matter_client.server_info.compressed_fabric_id}-{node.unique_id}-{device_type_instance.endpoint}-{device_type_instance.device_type.device_type}"
server_info = matter_client.server_info
# The server info is set when the client connects to the server.
assert server_info is not None
self._attr_unique_id = (
f"{server_info.compressed_fabric_id}-"
f"{node.unique_id}-"
f"{device_type_instance.endpoint}-"
f"{device_type_instance.device_type.device_type}"
)
@property
def device_info(self) -> DeviceInfo | None:
+10 -7
View File
@@ -57,6 +57,9 @@ class MatterLight(MatterEntity, LightEntity):
return
level_control = self._device_type_instance.get_cluster(clusters.LevelControl)
# We check above that the device supports brightness, ie level control.
assert level_control is not None
level = round(
renormalize(
kwargs[ATTR_BRIGHTNESS],
@@ -86,20 +89,20 @@ class MatterLight(MatterEntity, LightEntity):
@callback
def _update_from_device(self) -> None:
"""Update from device."""
if self._attr_supported_color_modes is None:
if self._supports_brightness():
self._attr_supported_color_modes = {ColorMode.BRIGHTNESS}
supports_brigthness = self._supports_brightness()
if self._attr_supported_color_modes is None and supports_brigthness:
self._attr_supported_color_modes = {ColorMode.BRIGHTNESS}
if attr := self.get_matter_attribute(clusters.OnOff.Attributes.OnOff):
self._attr_is_on = attr.value
if (
clusters.LevelControl.Attributes.CurrentLevel
in self.entity_description.subscribe_attributes
):
if supports_brigthness:
level_control = self._device_type_instance.get_cluster(
clusters.LevelControl
)
# We check above that the device supports brightness, ie level control.
assert level_control is not None
# Convert brightness to Home Assistant = 0..255
self._attr_brightness = round(
@@ -3,7 +3,7 @@
"name": "Matter (BETA)",
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/matter",
"requirements": ["python-matter-server==1.0.7"],
"requirements": ["python-matter-server==1.0.8"],
"dependencies": ["websocket_api"],
"codeowners": ["@home-assistant/matter"],
"iot_class": "local_push"
+2 -1
View File
@@ -56,7 +56,8 @@ class MatterSwitch(MatterEntity, SwitchEntity):
@callback
def _update_from_device(self) -> None:
"""Update from device."""
self._attr_is_on = self._device_type_instance.get_cluster(clusters.OnOff).onOff
cluster = self._device_type_instance.get_cluster(clusters.OnOff)
self._attr_is_on = cluster.onOff if cluster else None
@dataclass
@@ -7,110 +7,28 @@ from math import ceil
import pytankerkoenig
from requests.exceptions import RequestException
import voluptuous as vol
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
from homeassistant.const import (
ATTR_ID,
CONF_API_KEY,
CONF_LATITUDE,
CONF_LOCATION,
CONF_LONGITUDE,
CONF_NAME,
CONF_RADIUS,
CONF_SCAN_INTERVAL,
CONF_SHOW_ON_MAP,
Platform,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ATTR_ID, CONF_API_KEY, CONF_SHOW_ON_MAP, Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.device_registry import DeviceEntryType
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.typing import ConfigType
from homeassistant.helpers.update_coordinator import (
CoordinatorEntity,
DataUpdateCoordinator,
UpdateFailed,
)
from .const import (
CONF_FUEL_TYPES,
CONF_STATIONS,
DEFAULT_RADIUS,
DEFAULT_SCAN_INTERVAL,
DOMAIN,
FUEL_TYPES,
)
from .const import CONF_FUEL_TYPES, CONF_STATIONS, DEFAULT_SCAN_INTERVAL, DOMAIN
_LOGGER = logging.getLogger(__name__)
CONFIG_SCHEMA = vol.Schema(
vol.All(
cv.deprecated(DOMAIN),
{
DOMAIN: vol.Schema(
{
vol.Required(CONF_API_KEY): cv.string,
vol.Optional(
CONF_SCAN_INTERVAL, default=DEFAULT_SCAN_INTERVAL
): cv.time_period,
vol.Optional(CONF_FUEL_TYPES, default=FUEL_TYPES): vol.All(
cv.ensure_list, [vol.In(FUEL_TYPES)]
),
vol.Inclusive(
CONF_LATITUDE,
"coordinates",
"Latitude and longitude must exist together",
): cv.latitude,
vol.Inclusive(
CONF_LONGITUDE,
"coordinates",
"Latitude and longitude must exist together",
): cv.longitude,
vol.Optional(CONF_RADIUS, default=DEFAULT_RADIUS): vol.All(
cv.positive_int, vol.Range(min=1)
),
vol.Optional(CONF_STATIONS, default=[]): vol.All(
cv.ensure_list, [cv.string]
),
vol.Optional(CONF_SHOW_ON_MAP, default=True): cv.boolean,
}
)
},
),
extra=vol.ALLOW_EXTRA,
)
PLATFORMS = [Platform.BINARY_SENSOR, Platform.SENSOR]
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set the tankerkoenig component up."""
if DOMAIN not in config:
return True
conf = config[DOMAIN]
hass.async_create_task(
hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_IMPORT},
data={
CONF_NAME: "Home",
CONF_API_KEY: conf[CONF_API_KEY],
CONF_FUEL_TYPES: conf[CONF_FUEL_TYPES],
CONF_LOCATION: {
"latitude": conf.get(CONF_LATITUDE, hass.config.latitude),
"longitude": conf.get(CONF_LONGITUDE, hass.config.longitude),
},
CONF_RADIUS: conf[CONF_RADIUS],
CONF_STATIONS: conf[CONF_STATIONS],
CONF_SHOW_ON_MAP: conf[CONF_SHOW_ON_MAP],
},
)
)
return True
CONFIG_SCHEMA = cv.removed(DOMAIN, raise_if_present=False)
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
@@ -67,37 +67,6 @@ class FlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
"""Get the options flow for this handler."""
return OptionsFlowHandler(config_entry)
async def async_step_import(self, config: dict[str, Any]) -> FlowResult:
"""Import YAML configuration."""
await self.async_set_unique_id(
f"{config[CONF_LOCATION][CONF_LATITUDE]}_{config[CONF_LOCATION][CONF_LONGITUDE]}"
)
self._abort_if_unique_id_configured()
selected_station_ids: list[str] = []
# add all nearby stations
nearby_stations = await async_get_nearby_stations(self.hass, config)
for station in nearby_stations.get("stations", []):
selected_station_ids.append(station["id"])
# add all manual added stations
for station_id in config[CONF_STATIONS]:
selected_station_ids.append(station_id)
return self._create_entry(
data={
CONF_NAME: "Home",
CONF_API_KEY: config[CONF_API_KEY],
CONF_FUEL_TYPES: config[CONF_FUEL_TYPES],
CONF_LOCATION: config[CONF_LOCATION],
CONF_RADIUS: config[CONF_RADIUS],
CONF_STATIONS: selected_station_ids,
},
options={
CONF_SHOW_ON_MAP: config[CONF_SHOW_ON_MAP],
},
)
async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
@@ -36,7 +36,9 @@ from .const import (
_LOGGER = logging.getLogger(__name__)
VICARE_MODE_DHW = "dhw"
VICARE_MODE_HEATING = "heating"
VICARE_MODE_DHWANDHEATING = "dhwAndHeating"
VICARE_MODE_DHWANDHEATINGCOOLING = "dhwAndHeatingCooling"
VICARE_MODE_FORCEDREDUCED = "forcedReduced"
VICARE_MODE_FORCEDNORMAL = "forcedNormal"
VICARE_MODE_OFF = "standby"
@@ -50,6 +52,8 @@ OPERATION_MODE_OFF = "off"
VICARE_TO_HA_HVAC_DHW = {
VICARE_MODE_DHW: OPERATION_MODE_ON,
VICARE_MODE_DHWANDHEATING: OPERATION_MODE_ON,
VICARE_MODE_DHWANDHEATINGCOOLING: OPERATION_MODE_ON,
VICARE_MODE_HEATING: OPERATION_MODE_OFF,
VICARE_MODE_FORCEDREDUCED: OPERATION_MODE_OFF,
VICARE_MODE_FORCEDNORMAL: OPERATION_MODE_ON,
VICARE_MODE_OFF: OPERATION_MODE_OFF,
+1 -1
View File
@@ -8,7 +8,7 @@ from .backports.enum import StrEnum
APPLICATION_NAME: Final = "HomeAssistant"
MAJOR_VERSION: Final = 2023
MINOR_VERSION: Final = 1
PATCH_VERSION: Final = "0b0"
PATCH_VERSION: Final = "0b1"
__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)
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "homeassistant"
version = "2023.1.0b0"
version = "2023.1.0b1"
license = {text = "Apache-2.0"}
description = "Open-source home automation platform running on Python 3."
readme = "README.rst"
+2 -2
View File
@@ -339,7 +339,7 @@ anthemav==1.4.1
apcaccess==0.0.13
# homeassistant.components.apprise
apprise==1.2.0
apprise==1.2.1
# homeassistant.components.aprs
aprslib==0.7.0
@@ -2038,7 +2038,7 @@ python-kasa==0.5.0
# python-lirc==1.2.3
# homeassistant.components.matter
python-matter-server==1.0.7
python-matter-server==1.0.8
# homeassistant.components.xiaomi_miio
python-miio==0.5.12
+2 -2
View File
@@ -305,7 +305,7 @@ anthemav==1.4.1
apcaccess==0.0.13
# homeassistant.components.apprise
apprise==1.2.0
apprise==1.2.1
# homeassistant.components.aprs
aprslib==0.7.0
@@ -1428,7 +1428,7 @@ python-juicenet==1.1.0
python-kasa==0.5.0
# homeassistant.components.matter
python-matter-server==1.0.7
python-matter-server==1.0.8
# homeassistant.components.xiaomi_miio
python-miio==0.5.12
+13 -1
View File
@@ -214,6 +214,18 @@ DESCRIPTION:The bell tolls for thee
RRULE:FREQ=HOURLY;INTERVAL=1;COUNT=12
END:VEVENT
END:VCALENDAR
""",
"""BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Global Corp.//CalDAV Client//EN
BEGIN:VEVENT
UID:14
DTSTAMP:20151125T000000Z
DTSTART:20151127T000000Z
DTEND:20151127T003000Z
RRULE:FREQ=HOURLY;INTERVAL=1;COUNT=12
END:VEVENT
END:VCALENDAR
""",
]
@@ -917,7 +929,7 @@ async def test_get_events(hass, calendar, get_api_events):
await hass.async_block_till_done()
events = await get_api_events("calendar.private")
assert len(events) == 14
assert len(events) == 15
assert calendar.call
@@ -8,7 +8,7 @@ from homeassistant.components.tankerkoenig.const import (
CONF_STATIONS,
DOMAIN,
)
from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_REAUTH, SOURCE_USER
from homeassistant.config_entries import SOURCE_REAUTH, SOURCE_USER
from homeassistant.const import (
CONF_API_KEY,
CONF_LATITUDE,
@@ -47,18 +47,6 @@ MOCK_OPTIONS_DATA = {
],
}
MOCK_IMPORT_DATA = {
CONF_API_KEY: "269534f6-xxxx-xxxx-xxxx-yyyyzzzzxxxx",
CONF_FUEL_TYPES: ["e5"],
CONF_LOCATION: {CONF_LATITUDE: 51.0, CONF_LONGITUDE: 13.0},
CONF_RADIUS: 2.0,
CONF_STATIONS: [
"3bcd61da-yyyy-yyyy-yyyy-19d5523a7ae8",
"36b4b812-yyyy-yyyy-yyyy-c51735325858",
],
CONF_SHOW_ON_MAP: True,
}
MOCK_NEARVY_STATIONS_OK = {
"ok": True,
"stations": [
@@ -187,37 +175,6 @@ async def test_user_no_stations(hass: HomeAssistant):
assert result["errors"][CONF_RADIUS] == "no_stations"
async def test_import(hass: HomeAssistant):
"""Test starting a flow by import."""
with patch(
"homeassistant.components.tankerkoenig.async_setup_entry", return_value=True
) as mock_setup_entry, patch(
"homeassistant.components.tankerkoenig.config_flow.getNearbyStations",
return_value=MOCK_NEARVY_STATIONS_OK,
):
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_IMPORT}, data=MOCK_IMPORT_DATA
)
assert result["type"] == FlowResultType.CREATE_ENTRY
assert result["type"] == FlowResultType.CREATE_ENTRY
assert result["data"][CONF_NAME] == "Home"
assert result["data"][CONF_API_KEY] == "269534f6-xxxx-xxxx-xxxx-yyyyzzzzxxxx"
assert result["data"][CONF_FUEL_TYPES] == ["e5"]
assert result["data"][CONF_LOCATION] == {"latitude": 51.0, "longitude": 13.0}
assert result["data"][CONF_RADIUS] == 2.0
assert result["data"][CONF_STATIONS] == [
"3bcd61da-xxxx-xxxx-xxxx-19d5523a7ae8",
"36b4b812-xxxx-xxxx-xxxx-c51735325858",
"3bcd61da-yyyy-yyyy-yyyy-19d5523a7ae8",
"36b4b812-yyyy-yyyy-yyyy-c51735325858",
]
assert result["options"][CONF_SHOW_ON_MAP]
await hass.async_block_till_done()
assert mock_setup_entry.called
async def test_reauth(hass: HomeAssistant):
"""Test starting a flow by user to re-auth."""