mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 13:17:32 +00:00
2022.12.8 (#84369)
This commit is contained in:
commit
2205006d31
@ -369,6 +369,7 @@ class BluetoothManager:
|
||||
all_history = self._all_history
|
||||
connectable = service_info.connectable
|
||||
connectable_history = self._connectable_history
|
||||
old_connectable_service_info = connectable and connectable_history.get(address)
|
||||
|
||||
source = service_info.source
|
||||
debug = _LOGGER.isEnabledFor(logging.DEBUG)
|
||||
@ -399,7 +400,6 @@ class BluetoothManager:
|
||||
# but not in the connectable history or the connectable source is the same
|
||||
# as the new source, we need to add it to the connectable history
|
||||
if connectable:
|
||||
old_connectable_service_info = connectable_history.get(address)
|
||||
if old_connectable_service_info and (
|
||||
# If its the same as the preferred source, we are done
|
||||
# as we know we prefer the old advertisement
|
||||
@ -442,17 +442,24 @@ class BluetoothManager:
|
||||
tracker.async_collect(service_info)
|
||||
|
||||
# If the advertisement data is the same as the last time we saw it, we
|
||||
# don't need to do anything else.
|
||||
if old_service_info and not (
|
||||
service_info.manufacturer_data != old_service_info.manufacturer_data
|
||||
or service_info.service_data != old_service_info.service_data
|
||||
or service_info.service_uuids != old_service_info.service_uuids
|
||||
or service_info.name != old_service_info.name
|
||||
# don't need to do anything else unless its connectable and we are missing
|
||||
# connectable history for the device so we can make it available again
|
||||
# after unavailable callbacks.
|
||||
if (
|
||||
# Ensure its not a connectable device missing from connectable history
|
||||
not (connectable and not old_connectable_service_info)
|
||||
# Than check if advertisement data is the same
|
||||
and old_service_info
|
||||
and not (
|
||||
service_info.manufacturer_data != old_service_info.manufacturer_data
|
||||
or service_info.service_data != old_service_info.service_data
|
||||
or service_info.service_uuids != old_service_info.service_uuids
|
||||
or service_info.name != old_service_info.name
|
||||
)
|
||||
):
|
||||
return
|
||||
|
||||
is_connectable_by_any_source = address in self._connectable_history
|
||||
if not connectable and is_connectable_by_any_source:
|
||||
if not connectable and old_connectable_service_info:
|
||||
# Since we have a connectable path and our BleakClient will
|
||||
# route any connection attempts to the connectable path, we
|
||||
# mark the service_info as connectable so that the callbacks
|
||||
@ -481,7 +488,7 @@ class BluetoothManager:
|
||||
matched_domains,
|
||||
)
|
||||
|
||||
if is_connectable_by_any_source:
|
||||
if connectable or old_connectable_service_info:
|
||||
# Bleak callbacks must get a connectable device
|
||||
for callback_filters in self._bleak_callbacks:
|
||||
_dispatch_bleak_callback(*callback_filters, device, advertisement_data)
|
||||
|
@ -10,7 +10,7 @@
|
||||
"bleak-retry-connector==2.10.2",
|
||||
"bluetooth-adapters==0.12.0",
|
||||
"bluetooth-auto-recovery==1.0.3",
|
||||
"bluetooth-data-tools==0.3.0",
|
||||
"bluetooth-data-tools==0.3.1",
|
||||
"dbus-fast==1.75.0"
|
||||
],
|
||||
"codeowners": ["@bdraco"],
|
||||
|
@ -226,14 +226,16 @@ class CloudRegisterView(HomeAssistantView):
|
||||
|
||||
client_metadata = None
|
||||
|
||||
if location_info := await async_detect_location_info(
|
||||
async_get_clientsession(hass)
|
||||
):
|
||||
client_metadata = {
|
||||
"NC_COUNTRY_CODE": location_info.country_code,
|
||||
"NC_REGION_CODE": location_info.region_code,
|
||||
"NC_ZIP_CODE": location_info.zip_code,
|
||||
}
|
||||
if (
|
||||
location_info := await async_detect_location_info(
|
||||
async_get_clientsession(hass)
|
||||
)
|
||||
) and location_info.country_code is not None:
|
||||
client_metadata = {"NC_COUNTRY_CODE": location_info.country_code}
|
||||
if location_info.region_code is not None:
|
||||
client_metadata["NC_REGION_CODE"] = location_info.region_code
|
||||
if location_info.zip_code is not None:
|
||||
client_metadata["NC_ZIP_CODE"] = location_info.zip_code
|
||||
|
||||
async with async_timeout.timeout(REQUEST_TIMEOUT):
|
||||
await cloud.auth.async_register(
|
||||
|
@ -3,7 +3,7 @@
|
||||
"name": "LED BLE",
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/led_ble/",
|
||||
"requirements": ["bluetooth-data-tools==0.3.0", "led-ble==1.0.0"],
|
||||
"requirements": ["bluetooth-data-tools==0.3.1", "led-ble==1.0.0"],
|
||||
"dependencies": ["bluetooth"],
|
||||
"codeowners": ["@bdraco"],
|
||||
"bluetooth": [
|
||||
|
@ -3,7 +3,7 @@
|
||||
"name": "Local Calendar",
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/local_calendar",
|
||||
"requirements": ["ical==4.2.3"],
|
||||
"requirements": ["ical==4.2.4"],
|
||||
"codeowners": ["@allenporter"],
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["ical"]
|
||||
|
@ -17,6 +17,8 @@ from homeassistant.const import TEMP_CELSIUS
|
||||
|
||||
from ..entity import OverkizEntity
|
||||
|
||||
PRESET_COMFORT1 = "comfort-1"
|
||||
PRESET_COMFORT2 = "comfort-2"
|
||||
PRESET_FROST_PROTECTION = "frost_protection"
|
||||
|
||||
OVERKIZ_TO_HVAC_MODES: dict[str, HVACMode] = {
|
||||
@ -31,6 +33,8 @@ OVERKIZ_TO_PRESET_MODES: dict[str, str] = {
|
||||
OverkizCommandParam.FROSTPROTECTION: PRESET_FROST_PROTECTION,
|
||||
OverkizCommandParam.ECO: PRESET_ECO,
|
||||
OverkizCommandParam.COMFORT: PRESET_COMFORT,
|
||||
OverkizCommandParam.COMFORT_1: PRESET_COMFORT1,
|
||||
OverkizCommandParam.COMFORT_2: PRESET_COMFORT2,
|
||||
}
|
||||
|
||||
PRESET_MODES_TO_OVERKIZ = {v: k for k, v in OVERKIZ_TO_PRESET_MODES.items()}
|
||||
|
@ -394,7 +394,7 @@ class PrometheusMetrics:
|
||||
metric.labels(**self._labels(state)).set(value)
|
||||
|
||||
def _handle_climate_temp(self, state, attr, metric_name, metric_description):
|
||||
if temp := state.attributes.get(attr):
|
||||
if (temp := state.attributes.get(attr)) is not None:
|
||||
if self._climate_units == TEMP_FAHRENHEIT:
|
||||
temp = TemperatureConverter.convert(temp, TEMP_FAHRENHEIT, TEMP_CELSIUS)
|
||||
metric = self._metric(
|
||||
|
@ -3,7 +3,7 @@
|
||||
"domain": "tibber",
|
||||
"name": "Tibber",
|
||||
"documentation": "https://www.home-assistant.io/integrations/tibber",
|
||||
"requirements": ["pyTibber==0.26.5"],
|
||||
"requirements": ["pyTibber==0.26.6"],
|
||||
"codeowners": ["@danielhiversen"],
|
||||
"quality_scale": "silver",
|
||||
"config_flow": true,
|
||||
|
@ -8,7 +8,7 @@ from .backports.enum import StrEnum
|
||||
APPLICATION_NAME: Final = "HomeAssistant"
|
||||
MAJOR_VERSION: Final = 2022
|
||||
MINOR_VERSION: Final = 12
|
||||
PATCH_VERSION: Final = "7"
|
||||
PATCH_VERSION: Final = "8"
|
||||
__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)
|
||||
|
@ -14,7 +14,7 @@ bleak-retry-connector==2.10.2
|
||||
bleak==0.19.2
|
||||
bluetooth-adapters==0.12.0
|
||||
bluetooth-auto-recovery==1.0.3
|
||||
bluetooth-data-tools==0.3.0
|
||||
bluetooth-data-tools==0.3.1
|
||||
certifi>=2021.5.30
|
||||
ciso8601==2.2.0
|
||||
cryptography==38.0.3
|
||||
|
@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "homeassistant"
|
||||
version = "2022.12.7"
|
||||
version = "2022.12.8"
|
||||
license = {text = "Apache-2.0"}
|
||||
description = "Open-source home automation platform running on Python 3."
|
||||
readme = "README.rst"
|
||||
|
@ -454,7 +454,7 @@ bluetooth-auto-recovery==1.0.3
|
||||
|
||||
# homeassistant.components.bluetooth
|
||||
# homeassistant.components.led_ble
|
||||
bluetooth-data-tools==0.3.0
|
||||
bluetooth-data-tools==0.3.1
|
||||
|
||||
# homeassistant.components.bond
|
||||
bond-async==0.1.22
|
||||
@ -926,7 +926,7 @@ ibm-watson==5.2.2
|
||||
ibmiotf==0.3.4
|
||||
|
||||
# homeassistant.components.local_calendar
|
||||
ical==4.2.3
|
||||
ical==4.2.4
|
||||
|
||||
# homeassistant.components.ping
|
||||
icmplib==3.0
|
||||
@ -1432,7 +1432,7 @@ pyRFXtrx==0.30.0
|
||||
pySwitchmate==0.5.1
|
||||
|
||||
# homeassistant.components.tibber
|
||||
pyTibber==0.26.5
|
||||
pyTibber==0.26.6
|
||||
|
||||
# homeassistant.components.dlink
|
||||
pyW215==0.7.0
|
||||
|
@ -368,7 +368,7 @@ bluetooth-auto-recovery==1.0.3
|
||||
|
||||
# homeassistant.components.bluetooth
|
||||
# homeassistant.components.led_ble
|
||||
bluetooth-data-tools==0.3.0
|
||||
bluetooth-data-tools==0.3.1
|
||||
|
||||
# homeassistant.components.bond
|
||||
bond-async==0.1.22
|
||||
@ -691,7 +691,7 @@ iaqualink==0.5.0
|
||||
ibeacon_ble==1.0.1
|
||||
|
||||
# homeassistant.components.local_calendar
|
||||
ical==4.2.3
|
||||
ical==4.2.4
|
||||
|
||||
# homeassistant.components.ping
|
||||
icmplib==3.0
|
||||
@ -1032,7 +1032,7 @@ pyMetno==0.9.0
|
||||
pyRFXtrx==0.30.0
|
||||
|
||||
# homeassistant.components.tibber
|
||||
pyTibber==0.26.5
|
||||
pyTibber==0.26.6
|
||||
|
||||
# homeassistant.components.nextbus
|
||||
py_nextbusnext==0.1.5
|
||||
|
@ -1,27 +1,46 @@
|
||||
"""Tests for the Bluetooth integration manager."""
|
||||
|
||||
from datetime import timedelta
|
||||
import time
|
||||
from unittest.mock import patch
|
||||
|
||||
from bleak.backends.scanner import BLEDevice
|
||||
from bleak.backends.scanner import AdvertisementData, BLEDevice
|
||||
from bluetooth_adapters import AdvertisementHistory
|
||||
import pytest
|
||||
|
||||
from homeassistant.components import bluetooth
|
||||
from homeassistant.components.bluetooth import BaseHaScanner
|
||||
from homeassistant.components.bluetooth import (
|
||||
BaseHaRemoteScanner,
|
||||
BaseHaScanner,
|
||||
BluetoothChange,
|
||||
BluetoothScanningMode,
|
||||
BluetoothServiceInfo,
|
||||
BluetoothServiceInfoBleak,
|
||||
HaBluetoothConnector,
|
||||
async_ble_device_from_address,
|
||||
async_get_advertisement_callback,
|
||||
async_scanner_count,
|
||||
async_track_unavailable,
|
||||
)
|
||||
from homeassistant.components.bluetooth.const import UNAVAILABLE_TRACK_SECONDS
|
||||
from homeassistant.components.bluetooth.manager import (
|
||||
FALLBACK_MAXIMUM_STALE_ADVERTISEMENT_SECONDS,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.setup import async_setup_component
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
from . import (
|
||||
MockBleakClient,
|
||||
_get_manager,
|
||||
generate_advertisement_data,
|
||||
inject_advertisement_with_source,
|
||||
inject_advertisement_with_time_and_source,
|
||||
inject_advertisement_with_time_and_source_connectable,
|
||||
)
|
||||
|
||||
from tests.common import async_fire_time_changed
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def register_hci0_scanner(hass: HomeAssistant) -> None:
|
||||
@ -514,3 +533,172 @@ async def test_switching_adapters_when_one_stop_scanning(
|
||||
)
|
||||
|
||||
cancel_hci2()
|
||||
|
||||
|
||||
async def test_goes_unavailable_connectable_only_and_recovers(
|
||||
hass, mock_bluetooth_adapters
|
||||
):
|
||||
"""Test all connectable scanners go unavailable, and than recover when there is a non-connectable scanner."""
|
||||
assert await async_setup_component(hass, bluetooth.DOMAIN, {})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert async_scanner_count(hass, connectable=True) == 0
|
||||
assert async_scanner_count(hass, connectable=False) == 0
|
||||
switchbot_device_connectable = BLEDevice(
|
||||
"44:44:33:11:23:45",
|
||||
"wohand",
|
||||
{},
|
||||
rssi=-100,
|
||||
)
|
||||
switchbot_device_non_connectable = BLEDevice(
|
||||
"44:44:33:11:23:45",
|
||||
"wohand",
|
||||
{},
|
||||
rssi=-100,
|
||||
)
|
||||
switchbot_device_adv = generate_advertisement_data(
|
||||
local_name="wohand",
|
||||
service_uuids=["050a021a-0000-1000-8000-00805f9b34fb"],
|
||||
service_data={"050a021a-0000-1000-8000-00805f9b34fb": b"\n\xff"},
|
||||
manufacturer_data={1: b"\x01"},
|
||||
rssi=-100,
|
||||
)
|
||||
callbacks = []
|
||||
|
||||
def _fake_subscriber(
|
||||
service_info: BluetoothServiceInfo,
|
||||
change: BluetoothChange,
|
||||
) -> None:
|
||||
"""Fake subscriber for the BleakScanner."""
|
||||
callbacks.append((service_info, change))
|
||||
|
||||
cancel = bluetooth.async_register_callback(
|
||||
hass,
|
||||
_fake_subscriber,
|
||||
{"address": "44:44:33:11:23:45", "connectable": True},
|
||||
BluetoothScanningMode.ACTIVE,
|
||||
)
|
||||
|
||||
class FakeScanner(BaseHaRemoteScanner):
|
||||
def inject_advertisement(
|
||||
self, device: BLEDevice, advertisement_data: AdvertisementData
|
||||
) -> None:
|
||||
"""Inject an advertisement."""
|
||||
self._async_on_advertisement(
|
||||
device.address,
|
||||
advertisement_data.rssi,
|
||||
device.name,
|
||||
advertisement_data.service_uuids,
|
||||
advertisement_data.service_data,
|
||||
advertisement_data.manufacturer_data,
|
||||
advertisement_data.tx_power,
|
||||
{"scanner_specific_data": "test"},
|
||||
)
|
||||
|
||||
new_info_callback = async_get_advertisement_callback(hass)
|
||||
connector = (
|
||||
HaBluetoothConnector(MockBleakClient, "mock_bleak_client", lambda: False),
|
||||
)
|
||||
connectable_scanner = FakeScanner(
|
||||
hass,
|
||||
"connectable",
|
||||
"connectable",
|
||||
new_info_callback,
|
||||
connector,
|
||||
True,
|
||||
)
|
||||
unsetup_connectable_scanner = connectable_scanner.async_setup()
|
||||
cancel_connectable_scanner = _get_manager().async_register_scanner(
|
||||
connectable_scanner, True
|
||||
)
|
||||
connectable_scanner.inject_advertisement(
|
||||
switchbot_device_connectable, switchbot_device_adv
|
||||
)
|
||||
assert async_ble_device_from_address(hass, "44:44:33:11:23:45") is not None
|
||||
assert async_scanner_count(hass, connectable=True) == 1
|
||||
assert len(callbacks) == 1
|
||||
|
||||
assert (
|
||||
"44:44:33:11:23:45"
|
||||
in connectable_scanner.discovered_devices_and_advertisement_data
|
||||
)
|
||||
|
||||
not_connectable_scanner = FakeScanner(
|
||||
hass,
|
||||
"not_connectable",
|
||||
"not_connectable",
|
||||
new_info_callback,
|
||||
connector,
|
||||
False,
|
||||
)
|
||||
unsetup_not_connectable_scanner = not_connectable_scanner.async_setup()
|
||||
cancel_not_connectable_scanner = _get_manager().async_register_scanner(
|
||||
not_connectable_scanner, False
|
||||
)
|
||||
not_connectable_scanner.inject_advertisement(
|
||||
switchbot_device_non_connectable, switchbot_device_adv
|
||||
)
|
||||
assert async_scanner_count(hass, connectable=True) == 1
|
||||
assert async_scanner_count(hass, connectable=False) == 2
|
||||
|
||||
assert (
|
||||
"44:44:33:11:23:45"
|
||||
in not_connectable_scanner.discovered_devices_and_advertisement_data
|
||||
)
|
||||
|
||||
unavailable_callbacks: list[BluetoothServiceInfoBleak] = []
|
||||
|
||||
@callback
|
||||
def _unavailable_callback(service_info: BluetoothServiceInfoBleak) -> None:
|
||||
"""Wrong device unavailable callback."""
|
||||
nonlocal unavailable_callbacks
|
||||
unavailable_callbacks.append(service_info.address)
|
||||
|
||||
cancel_unavailable = async_track_unavailable(
|
||||
hass,
|
||||
_unavailable_callback,
|
||||
switchbot_device_connectable.address,
|
||||
connectable=True,
|
||||
)
|
||||
|
||||
assert async_scanner_count(hass, connectable=True) == 1
|
||||
cancel_connectable_scanner()
|
||||
unsetup_connectable_scanner()
|
||||
assert async_scanner_count(hass, connectable=True) == 0
|
||||
assert async_scanner_count(hass, connectable=False) == 1
|
||||
|
||||
async_fire_time_changed(
|
||||
hass, dt_util.utcnow() + timedelta(seconds=UNAVAILABLE_TRACK_SECONDS)
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert "44:44:33:11:23:45" in unavailable_callbacks
|
||||
cancel_unavailable()
|
||||
|
||||
connectable_scanner_2 = FakeScanner(
|
||||
hass,
|
||||
"connectable",
|
||||
"connectable",
|
||||
new_info_callback,
|
||||
connector,
|
||||
True,
|
||||
)
|
||||
unsetup_connectable_scanner_2 = connectable_scanner_2.async_setup()
|
||||
cancel_connectable_scanner_2 = _get_manager().async_register_scanner(
|
||||
connectable_scanner, True
|
||||
)
|
||||
connectable_scanner_2.inject_advertisement(
|
||||
switchbot_device_connectable, switchbot_device_adv
|
||||
)
|
||||
assert (
|
||||
"44:44:33:11:23:45"
|
||||
in connectable_scanner_2.discovered_devices_and_advertisement_data
|
||||
)
|
||||
|
||||
# We should get another callback to make the device available again
|
||||
assert len(callbacks) == 2
|
||||
|
||||
cancel()
|
||||
cancel_connectable_scanner_2()
|
||||
unsetup_connectable_scanner_2()
|
||||
cancel_not_connectable_scanner()
|
||||
unsetup_not_connectable_scanner()
|
||||
|
@ -293,6 +293,12 @@ async def test_climate(client, climate_entities):
|
||||
'friendly_name="Ecobee"} 24.0' in body
|
||||
)
|
||||
|
||||
assert (
|
||||
'climate_target_temperature_celsius{domain="climate",'
|
||||
'entity="climate.fritzdect",'
|
||||
'friendly_name="Fritz!DECT"} 0.0' in body
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("namespace", [""])
|
||||
async def test_humidifier(client, humidifier_entities):
|
||||
@ -1001,6 +1007,23 @@ async def climate_fixture(hass, registry):
|
||||
data["climate_2"] = climate_2
|
||||
data["climate_2_attributes"] = climate_2_attributes
|
||||
|
||||
climate_3 = registry.async_get_or_create(
|
||||
domain=climate.DOMAIN,
|
||||
platform="test",
|
||||
unique_id="climate_3",
|
||||
unit_of_measurement=TEMP_CELSIUS,
|
||||
suggested_object_id="fritzdect",
|
||||
original_name="Fritz!DECT",
|
||||
)
|
||||
climate_3_attributes = {
|
||||
ATTR_TEMPERATURE: 0,
|
||||
ATTR_CURRENT_TEMPERATURE: 22,
|
||||
ATTR_HVAC_ACTION: climate.HVACAction.OFF,
|
||||
}
|
||||
set_state_with_entry(hass, climate_3, climate.HVACAction.OFF, climate_3_attributes)
|
||||
data["climate_3"] = climate_3
|
||||
data["climate_3_attributes"] = climate_3_attributes
|
||||
|
||||
await hass.async_block_till_done()
|
||||
return data
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user