commit
f0f12fd14a
|
@ -11,5 +11,5 @@
|
|||
"documentation": "https://www.home-assistant.io/integrations/dormakaba_dkey",
|
||||
"integration_type": "device",
|
||||
"iot_class": "local_polling",
|
||||
"requirements": ["py-dormakaba-dkey==1.0.3"]
|
||||
"requirements": ["py-dormakaba-dkey==1.0.4"]
|
||||
}
|
||||
|
|
|
@ -20,5 +20,5 @@
|
|||
"documentation": "https://www.home-assistant.io/integrations/frontend",
|
||||
"integration_type": "system",
|
||||
"quality_scale": "internal",
|
||||
"requirements": ["home-assistant-frontend==20230301.0"]
|
||||
"requirements": ["home-assistant-frontend==20230302.0"]
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ from __future__ import annotations
|
|||
from abc import ABC, abstractmethod
|
||||
from collections.abc import AsyncGenerator
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
from typing import Any, Final
|
||||
|
||||
import voluptuous as vol
|
||||
|
@ -549,9 +550,12 @@ class KNXCommonFlow(ABC, FlowHandler):
|
|||
),
|
||||
None,
|
||||
)
|
||||
_tunnel_identifier = selected_tunnel_ia or self.new_entry_data.get(
|
||||
CONF_HOST
|
||||
)
|
||||
_tunnel_suffix = f" @ {_tunnel_identifier}" if _tunnel_identifier else ""
|
||||
self.new_title = (
|
||||
f"{'Secure ' if _if_user_id else ''}"
|
||||
f"Tunneling @ {selected_tunnel_ia or self.new_entry_data[CONF_HOST]}"
|
||||
f"{'Secure ' if _if_user_id else ''}Tunneling{_tunnel_suffix}"
|
||||
)
|
||||
return self.finish_flow()
|
||||
|
||||
|
@ -708,7 +712,8 @@ class KNXCommonFlow(ABC, FlowHandler):
|
|||
else:
|
||||
dest_path = Path(self.hass.config.path(STORAGE_DIR, DOMAIN))
|
||||
dest_path.mkdir(exist_ok=True)
|
||||
file_path.rename(dest_path / DEFAULT_KNX_KEYRING_FILENAME)
|
||||
dest_file = dest_path / DEFAULT_KNX_KEYRING_FILENAME
|
||||
shutil.move(file_path, dest_file)
|
||||
return keyring, errors
|
||||
|
||||
keyring, errors = await self.hass.async_add_executor_job(_process_upload)
|
||||
|
|
|
@ -12,5 +12,5 @@
|
|||
"documentation": "https://www.home-assistant.io/integrations/nuheat",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["nuheat"],
|
||||
"requirements": ["nuheat==1.0.0"]
|
||||
"requirements": ["nuheat==1.0.1"]
|
||||
}
|
||||
|
|
|
@ -271,15 +271,20 @@ class SensorEntity(Entity):
|
|||
@property
|
||||
def _numeric_state_expected(self) -> bool:
|
||||
"""Return true if the sensor must be numeric."""
|
||||
# Note: the order of the checks needs to be kept aligned
|
||||
# with the checks in `state` property.
|
||||
device_class = try_parse_enum(SensorDeviceClass, self.device_class)
|
||||
if device_class in NON_NUMERIC_DEVICE_CLASSES:
|
||||
return False
|
||||
if (
|
||||
self.state_class is not None
|
||||
or self.native_unit_of_measurement is not None
|
||||
or self.suggested_display_precision is not None
|
||||
):
|
||||
return True
|
||||
# Sensors with custom device classes are not considered numeric
|
||||
device_class = try_parse_enum(SensorDeviceClass, self.device_class)
|
||||
return device_class not in {None, *NON_NUMERIC_DEVICE_CLASSES}
|
||||
# Sensors with custom device classes will have the device class
|
||||
# converted to None and are not considered numeric
|
||||
return device_class is not None
|
||||
|
||||
@property
|
||||
def options(self) -> list[str] | None:
|
||||
|
|
|
@ -53,17 +53,18 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||
|
||||
try:
|
||||
await tibber_connection.update_info()
|
||||
if not tibber_connection.name:
|
||||
raise ConfigEntryNotReady("Could not fetch Tibber data.")
|
||||
|
||||
except asyncio.TimeoutError as err:
|
||||
raise ConfigEntryNotReady from err
|
||||
except aiohttp.ClientError as err:
|
||||
_LOGGER.error("Error connecting to Tibber: %s ", err)
|
||||
return False
|
||||
except (
|
||||
asyncio.TimeoutError,
|
||||
aiohttp.ClientError,
|
||||
tibber.RetryableHttpException,
|
||||
) as err:
|
||||
raise ConfigEntryNotReady("Unable to connect") from err
|
||||
except tibber.InvalidLogin as exp:
|
||||
_LOGGER.error("Failed to login. %s", exp)
|
||||
return False
|
||||
except tibber.FatalHttpException:
|
||||
return False
|
||||
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
|
||||
|
|
|
@ -44,10 +44,14 @@ class TibberConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||
await tibber_connection.update_info()
|
||||
except asyncio.TimeoutError:
|
||||
errors[CONF_ACCESS_TOKEN] = "timeout"
|
||||
except aiohttp.ClientError:
|
||||
errors[CONF_ACCESS_TOKEN] = "cannot_connect"
|
||||
except tibber.InvalidLogin:
|
||||
errors[CONF_ACCESS_TOKEN] = "invalid_access_token"
|
||||
except (
|
||||
aiohttp.ClientError,
|
||||
tibber.RetryableHttpException,
|
||||
tibber.FatalHttpException,
|
||||
):
|
||||
errors[CONF_ACCESS_TOKEN] = "cannot_connect"
|
||||
|
||||
if errors:
|
||||
return self.async_show_form(
|
||||
|
|
|
@ -8,5 +8,5 @@
|
|||
"iot_class": "cloud_polling",
|
||||
"loggers": ["tibber"],
|
||||
"quality_scale": "silver",
|
||||
"requirements": ["pyTibber==0.26.13"]
|
||||
"requirements": ["pyTibber==0.27.0"]
|
||||
}
|
||||
|
|
|
@ -44,6 +44,7 @@ from homeassistant.helpers.entity_registry import async_get as async_get_entity_
|
|||
from homeassistant.helpers.update_coordinator import (
|
||||
CoordinatorEntity,
|
||||
DataUpdateCoordinator,
|
||||
UpdateFailed,
|
||||
)
|
||||
from homeassistant.util import Throttle, dt as dt_util
|
||||
|
||||
|
@ -559,6 +560,8 @@ class TibberRtDataCoordinator(DataUpdateCoordinator):
|
|||
class TibberDataCoordinator(DataUpdateCoordinator[None]):
|
||||
"""Handle Tibber data and insert statistics."""
|
||||
|
||||
config_entry: ConfigEntry
|
||||
|
||||
def __init__(self, hass: HomeAssistant, tibber_connection: tibber.Tibber) -> None:
|
||||
"""Initialize the data handler."""
|
||||
super().__init__(
|
||||
|
@ -571,9 +574,17 @@ class TibberDataCoordinator(DataUpdateCoordinator[None]):
|
|||
|
||||
async def _async_update_data(self) -> None:
|
||||
"""Update data via API."""
|
||||
await self._tibber_connection.fetch_consumption_data_active_homes()
|
||||
await self._tibber_connection.fetch_production_data_active_homes()
|
||||
await self._insert_statistics()
|
||||
try:
|
||||
await self._tibber_connection.fetch_consumption_data_active_homes()
|
||||
await self._tibber_connection.fetch_production_data_active_homes()
|
||||
await self._insert_statistics()
|
||||
except tibber.RetryableHttpException as err:
|
||||
raise UpdateFailed(f"Error communicating with API ({err.status})") from err
|
||||
except tibber.FatalHttpException:
|
||||
# Fatal error. Reload config entry to show correct error.
|
||||
self.hass.async_create_task(
|
||||
self.hass.config_entries.async_reload(self.config_entry.entry_id)
|
||||
)
|
||||
|
||||
async def _insert_statistics(self) -> None:
|
||||
"""Insert Tibber statistics."""
|
||||
|
|
|
@ -8,7 +8,7 @@ from .backports.enum import StrEnum
|
|||
APPLICATION_NAME: Final = "HomeAssistant"
|
||||
MAJOR_VERSION: Final = 2023
|
||||
MINOR_VERSION: Final = 3
|
||||
PATCH_VERSION: Final = "0"
|
||||
PATCH_VERSION: Final = "1"
|
||||
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
||||
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
|
||||
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 10, 0)
|
||||
|
|
|
@ -23,14 +23,14 @@ fnvhash==0.1.0
|
|||
hass-nabucasa==0.61.0
|
||||
hassil==1.0.6
|
||||
home-assistant-bluetooth==1.9.3
|
||||
home-assistant-frontend==20230301.0
|
||||
home-assistant-frontend==20230302.0
|
||||
home-assistant-intents==2023.2.28
|
||||
httpx==0.23.3
|
||||
ifaddr==0.1.7
|
||||
janus==1.0.0
|
||||
jinja2==3.1.2
|
||||
lru-dict==1.1.8
|
||||
orjson==3.8.6
|
||||
orjson==3.8.7
|
||||
paho-mqtt==1.6.1
|
||||
pillow==9.4.0
|
||||
pip>=21.0,<23.1
|
||||
|
@ -40,7 +40,7 @@ pyserial==3.5
|
|||
python-slugify==4.0.1
|
||||
pyudev==0.23.2
|
||||
pyyaml==6.0
|
||||
requests==2.28.1
|
||||
requests==2.28.2
|
||||
scapy==2.5.0
|
||||
sqlalchemy==2.0.4
|
||||
typing-extensions>=4.5.0,<5.0
|
||||
|
|
|
@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||
|
||||
[project]
|
||||
name = "homeassistant"
|
||||
version = "2023.3.0"
|
||||
version = "2023.3.1"
|
||||
license = {text = "Apache-2.0"}
|
||||
description = "Open-source home automation platform running on Python 3."
|
||||
readme = "README.rst"
|
||||
|
@ -44,11 +44,11 @@ dependencies = [
|
|||
"cryptography==39.0.1",
|
||||
# pyOpenSSL 23.0.0 is required to work with cryptography 39+
|
||||
"pyOpenSSL==23.0.0",
|
||||
"orjson==3.8.6",
|
||||
"orjson==3.8.7",
|
||||
"pip>=21.0,<23.1",
|
||||
"python-slugify==4.0.1",
|
||||
"pyyaml==6.0",
|
||||
"requests==2.28.1",
|
||||
"requests==2.28.2",
|
||||
"typing-extensions>=4.5.0,<5.0",
|
||||
"voluptuous==0.13.1",
|
||||
"voluptuous-serialize==2.6.0",
|
||||
|
|
|
@ -18,11 +18,11 @@ lru-dict==1.1.8
|
|||
PyJWT==2.5.0
|
||||
cryptography==39.0.1
|
||||
pyOpenSSL==23.0.0
|
||||
orjson==3.8.6
|
||||
orjson==3.8.7
|
||||
pip>=21.0,<23.1
|
||||
python-slugify==4.0.1
|
||||
pyyaml==6.0
|
||||
requests==2.28.1
|
||||
requests==2.28.2
|
||||
typing-extensions>=4.5.0,<5.0
|
||||
voluptuous==0.13.1
|
||||
voluptuous-serialize==2.6.0
|
||||
|
|
|
@ -907,7 +907,7 @@ hole==0.8.0
|
|||
holidays==0.18.0
|
||||
|
||||
# homeassistant.components.frontend
|
||||
home-assistant-frontend==20230301.0
|
||||
home-assistant-frontend==20230302.0
|
||||
|
||||
# homeassistant.components.conversation
|
||||
home-assistant-intents==2023.2.28
|
||||
|
@ -1225,7 +1225,7 @@ nsapi==3.0.5
|
|||
nsw-fuel-api-client==1.1.0
|
||||
|
||||
# homeassistant.components.nuheat
|
||||
nuheat==1.0.0
|
||||
nuheat==1.0.1
|
||||
|
||||
# homeassistant.components.numato
|
||||
numato-gpio==0.10.0
|
||||
|
@ -1430,7 +1430,7 @@ py-canary==0.5.3
|
|||
py-cpuinfo==8.0.0
|
||||
|
||||
# homeassistant.components.dormakaba_dkey
|
||||
py-dormakaba-dkey==1.0.3
|
||||
py-dormakaba-dkey==1.0.4
|
||||
|
||||
# homeassistant.components.melissa
|
||||
py-melissa-climate==2.1.4
|
||||
|
@ -1473,7 +1473,7 @@ pyRFXtrx==0.30.1
|
|||
pySwitchmate==0.5.1
|
||||
|
||||
# homeassistant.components.tibber
|
||||
pyTibber==0.26.13
|
||||
pyTibber==0.27.0
|
||||
|
||||
# homeassistant.components.dlink
|
||||
pyW215==0.7.0
|
||||
|
|
|
@ -690,7 +690,7 @@ hole==0.8.0
|
|||
holidays==0.18.0
|
||||
|
||||
# homeassistant.components.frontend
|
||||
home-assistant-frontend==20230301.0
|
||||
home-assistant-frontend==20230302.0
|
||||
|
||||
# homeassistant.components.conversation
|
||||
home-assistant-intents==2023.2.28
|
||||
|
@ -903,7 +903,7 @@ notify-events==1.0.4
|
|||
nsw-fuel-api-client==1.1.0
|
||||
|
||||
# homeassistant.components.nuheat
|
||||
nuheat==1.0.0
|
||||
nuheat==1.0.1
|
||||
|
||||
# homeassistant.components.numato
|
||||
numato-gpio==0.10.0
|
||||
|
@ -1045,7 +1045,7 @@ py-canary==0.5.3
|
|||
py-cpuinfo==8.0.0
|
||||
|
||||
# homeassistant.components.dormakaba_dkey
|
||||
py-dormakaba-dkey==1.0.3
|
||||
py-dormakaba-dkey==1.0.4
|
||||
|
||||
# homeassistant.components.melissa
|
||||
py-melissa-climate==2.1.4
|
||||
|
@ -1076,7 +1076,7 @@ pyMetno==0.9.0
|
|||
pyRFXtrx==0.30.1
|
||||
|
||||
# homeassistant.components.tibber
|
||||
pyTibber==0.26.13
|
||||
pyTibber==0.27.0
|
||||
|
||||
# homeassistant.components.dlink
|
||||
pyW215==0.7.0
|
||||
|
|
|
@ -77,16 +77,17 @@ def patch_file_upload(return_value=FIXTURE_KEYRING, side_effect=None):
|
|||
side_effect=side_effect,
|
||||
), patch(
|
||||
"pathlib.Path.mkdir"
|
||||
) as mkdir_mock:
|
||||
file_path_mock = Mock()
|
||||
file_upload_mock.return_value.__enter__.return_value = file_path_mock
|
||||
) as mkdir_mock, patch(
|
||||
"shutil.move"
|
||||
) as shutil_move_mock:
|
||||
file_upload_mock.return_value.__enter__.return_value = Mock()
|
||||
yield return_value
|
||||
if side_effect:
|
||||
mkdir_mock.assert_not_called()
|
||||
file_path_mock.rename.assert_not_called()
|
||||
shutil_move_mock.assert_not_called()
|
||||
else:
|
||||
mkdir_mock.assert_called_once()
|
||||
file_path_mock.rename.assert_called_once()
|
||||
shutil_move_mock.assert_called_once()
|
||||
|
||||
|
||||
def _gateway_descriptor(
|
||||
|
|
|
@ -205,6 +205,47 @@ async def test_datetime_conversion(
|
|||
assert state.state == test_timestamp.isoformat()
|
||||
|
||||
|
||||
async def test_a_sensor_with_a_non_numeric_device_class(
|
||||
hass: HomeAssistant,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
enable_custom_integrations: None,
|
||||
) -> None:
|
||||
"""Test that a sensor with a non numeric device class will be non numeric.
|
||||
|
||||
A non numeric sensor with a valid device class should never be
|
||||
handled as numeric because it has a device class.
|
||||
"""
|
||||
test_timestamp = datetime(2017, 12, 19, 18, 29, 42, tzinfo=timezone.utc)
|
||||
test_local_timestamp = test_timestamp.astimezone(
|
||||
dt_util.get_time_zone("Europe/Amsterdam")
|
||||
)
|
||||
|
||||
platform = getattr(hass.components, "test.sensor")
|
||||
platform.init(empty=True)
|
||||
platform.ENTITIES["0"] = platform.MockSensor(
|
||||
name="Test",
|
||||
native_value=test_local_timestamp,
|
||||
native_unit_of_measurement="",
|
||||
device_class=SensorDeviceClass.TIMESTAMP,
|
||||
)
|
||||
|
||||
platform.ENTITIES["1"] = platform.MockSensor(
|
||||
name="Test",
|
||||
native_value=test_local_timestamp,
|
||||
state_class="",
|
||||
device_class=SensorDeviceClass.TIMESTAMP,
|
||||
)
|
||||
|
||||
assert await async_setup_component(hass, "sensor", {"sensor": {"platform": "test"}})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(platform.ENTITIES["0"].entity_id)
|
||||
assert state.state == test_timestamp.isoformat()
|
||||
|
||||
state = hass.states.get(platform.ENTITIES["1"].entity_id)
|
||||
assert state.state == test_timestamp.isoformat()
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("device_class", "state_value", "provides"),
|
||||
[
|
||||
|
|
Loading…
Reference in New Issue