Skip to content

Commit

Permalink
Merge branch 'dev' into bugfix/task-endtime
Browse files Browse the repository at this point in the history
  • Loading branch information
boralyl committed May 9, 2023
2 parents 38ccf64 + 23e24d7 commit e6e2c34
Show file tree
Hide file tree
Showing 36 changed files with 365 additions and 109 deletions.
2 changes: 1 addition & 1 deletion homeassistant/components/airzone/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,5 @@
"documentation": "https://www.home-assistant.io/integrations/airzone",
"iot_class": "local_polling",
"loggers": ["aioairzone"],
"requirements": ["aioairzone==0.5.3"]
"requirements": ["aioairzone==0.5.5"]
}
18 changes: 8 additions & 10 deletions homeassistant/components/alexa/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,12 @@
class AbstractConfig(ABC):
"""Hold the configuration for Alexa."""

_unsub_proactive_report: asyncio.Task[CALLBACK_TYPE] | None = None
_unsub_proactive_report: CALLBACK_TYPE | None = None

def __init__(self, hass: HomeAssistant) -> None:
"""Initialize abstract config."""
self.hass = hass
self._enable_proactive_mode_lock = asyncio.Lock()
self._store = None

async def async_initialize(self):
Expand Down Expand Up @@ -67,20 +68,17 @@ def user_identifier(self):
async def async_enable_proactive_mode(self):
"""Enable proactive mode."""
_LOGGER.debug("Enable proactive mode")
if self._unsub_proactive_report is None:
self._unsub_proactive_report = self.hass.async_create_task(
async_enable_proactive_mode(self.hass, self)
async with self._enable_proactive_mode_lock:
if self._unsub_proactive_report is not None:
return
self._unsub_proactive_report = await async_enable_proactive_mode(
self.hass, self
)
try:
await self._unsub_proactive_report
except Exception:
self._unsub_proactive_report = None
raise

async def async_disable_proactive_mode(self):
"""Disable proactive mode."""
_LOGGER.debug("Disable proactive mode")
if unsub_func := await self._unsub_proactive_report:
if unsub_func := self._unsub_proactive_report:
unsub_func()
self._unsub_proactive_report = None

Expand Down
14 changes: 9 additions & 5 deletions homeassistant/components/aranet/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
UnitOfTime,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.entity import DEVICE_CLASS_NAME, DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback

from .const import DOMAIN
Expand Down Expand Up @@ -105,6 +105,13 @@ def sensor_update_to_bluetooth_data_update(
adv: Aranet4Advertisement,
) -> PassiveBluetoothDataUpdate:
"""Convert a sensor update to a Bluetooth data update."""
entity_names: dict[PassiveBluetoothEntityKey, str | None] = {}
for key, desc in SENSOR_DESCRIPTIONS.items():
# PassiveBluetoothDataUpdate does not support DEVICE_CLASS_NAME
# the assert satisfies the type checker and will catch attempts
# to use DEVICE_CLASS_NAME in the entity descriptions.
assert desc.name is not DEVICE_CLASS_NAME
entity_names[_device_key_to_bluetooth_entity_key(adv.device, key)] = desc.name
return PassiveBluetoothDataUpdate(
devices={adv.device.address: _sensor_device_info_to_hass(adv)},
entity_descriptions={
Expand All @@ -117,10 +124,7 @@ def sensor_update_to_bluetooth_data_update(
)
for key in SENSOR_DESCRIPTIONS
},
entity_names={
_device_key_to_bluetooth_entity_key(adv.device, key): desc.name
for key, desc in SENSOR_DESCRIPTIONS.items()
},
entity_names=entity_names,
)


Expand Down
6 changes: 4 additions & 2 deletions homeassistant/components/balboa/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,17 @@
from pybalboa import EVENT_UPDATE, SpaClient

from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC
from homeassistant.helpers.entity import DeviceInfo, Entity
from homeassistant.helpers.entity import DeviceClassName, DeviceInfo, Entity

from .const import DOMAIN


class BalboaBaseEntity(Entity):
"""Balboa base entity."""

def __init__(self, client: SpaClient, name: str | None = None) -> None:
def __init__(
self, client: SpaClient, name: str | DeviceClassName | None = None
) -> None:
"""Initialize the control."""
mac = client.mac_address
model = client.model
Expand Down
5 changes: 5 additions & 0 deletions homeassistant/components/bond/button.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ class BondButtonEntityDescription(
):
"""Class to describe a Bond Button entity."""

# BondEntity does not support DEVICE_CLASS_NAME
# Restrict the type to satisfy the type checker and catch attempts
# to use DEVICE_CLASS_NAME in the entity descriptions.
name: str | None = None


STOP_BUTTON = BondButtonEntityDescription(
key=Action.STOP,
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/brunt/cover.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ def __init__(
self._attr_attribution = ATTRIBUTION
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, self._attr_unique_id)}, # type: ignore[arg-type]
name=self._attr_name,
name=self._thing.name,
via_device=(DOMAIN, self._entry_id),
manufacturer="Brunt",
sw_version=self._thing.fw_version,
Expand Down
4 changes: 2 additions & 2 deletions homeassistant/components/freebox/home_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ def __init__(
self._node = node
self._sub_node = sub_node
self._id = node["id"]
self._attr_name = node["label"].strip()
self._device_name = self._attr_name
self._device_name = node["label"].strip()
self._attr_name = self._device_name
self._attr_unique_id = f"{self._router.mac}-node_{self._id}"

if sub_node is not None:
Expand Down
10 changes: 7 additions & 3 deletions homeassistant/components/hassio/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -590,13 +590,13 @@ async def async_handle_core_service(call: ServiceCall) -> None:
await async_setup_addon_panel(hass, hassio)

# Setup hardware integration for the detected board type
async def _async_setup_hardware_integration(_: datetime) -> None:
async def _async_setup_hardware_integration(_: datetime | None = None) -> None:
"""Set up hardaware integration for the detected board type."""
if (os_info := get_os_info(hass)) is None:
# os info not yet fetched from supervisor, retry later
async_track_point_in_utc_time(
hass,
_async_setup_hardware_integration,
async_setup_hardware_integration_job,
utcnow() + HASSIO_UPDATE_INTERVAL,
)
return
Expand All @@ -610,7 +610,11 @@ async def _async_setup_hardware_integration(_: datetime) -> None:
)
)

await _async_setup_hardware_integration(datetime.now())
async_setup_hardware_integration_job = HassJob(
_async_setup_hardware_integration, cancel_on_shutdown=True
)

await _async_setup_hardware_integration()

hass.async_create_task(
hass.config_entries.flow.async_init(DOMAIN, context={"source": "system"})
Expand Down
4 changes: 4 additions & 0 deletions homeassistant/components/incomfort/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ class IncomfortSensorEntityDescription(SensorEntityDescription):
"""Describes Incomfort sensor entity."""

extra_key: str | None = None
# IncomfortSensor does not support DEVICE_CLASS_NAME
# Restrict the type to satisfy the type checker and catch attempts
# to use DEVICE_CLASS_NAME in the entity descriptions.
name: str | None = None


SENSOR_TYPES: tuple[IncomfortSensorEntityDescription, ...] = (
Expand Down
30 changes: 15 additions & 15 deletions homeassistant/components/integration/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,23 +174,23 @@ def _unit(self, source_unit: str) -> str:
async def async_added_to_hass(self) -> None:
"""Handle entity which will be added."""
await super().async_added_to_hass()
if state := await self.async_get_last_state():
try:
self._state = Decimal(state.state)
except (DecimalException, ValueError) as err:
_LOGGER.warning(
"%s could not restore last state %s: %s",
self.entity_id,
state.state,
err,
)
else:
self._attr_device_class = state.attributes.get(ATTR_DEVICE_CLASS)
if self._unit_of_measurement is None:
self._unit_of_measurement = state.attributes.get(
ATTR_UNIT_OF_MEASUREMENT
if (state := await self.async_get_last_state()) is not None:
if state.state == STATE_UNAVAILABLE:
self._attr_available = False

Check warning on line 179 in homeassistant/components/integration/sensor.py

View check run for this annotation

Codecov / codecov/patch

homeassistant/components/integration/sensor.py#L179

Added line #L179 was not covered by tests
elif state.state != STATE_UNKNOWN:
try:
self._state = Decimal(state.state)
except (DecimalException, ValueError) as err:
_LOGGER.warning(
"%s could not restore last state %s: %s",
self.entity_id,
state.state,
err,
)

self._attr_device_class = state.attributes.get(ATTR_DEVICE_CLASS)
self._unit_of_measurement = state.attributes.get(ATTR_UNIT_OF_MEASUREMENT)

@callback
def calc_integration(event: Event) -> None:
"""Handle the sensor state changes."""
Expand Down
6 changes: 6 additions & 0 deletions homeassistant/components/modbus/binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
STATE_ON,
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity import DEVICE_CLASS_NAME
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.restore_state import RestoreEntity
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
Expand Down Expand Up @@ -73,6 +74,11 @@ async def async_setup_slaves(
# this ensures that idx = bit position of value in result
# polling is done with the base class
name = self._attr_name if self._attr_name else "modbus_sensor"

# DataUpdateCoordinator does not support DEVICE_CLASS_NAME
# the assert satisfies the type checker and will catch attempts
# to use DEVICE_CLASS_NAME in _attr_name.
assert name is not DEVICE_CLASS_NAME
self._coordinator = DataUpdateCoordinator(
hass,
_LOGGER,
Expand Down
6 changes: 6 additions & 0 deletions homeassistant/components/modbus/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
CONF_UNIT_OF_MEASUREMENT,
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity import DEVICE_CLASS_NAME
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from homeassistant.helpers.update_coordinator import (
Expand Down Expand Up @@ -79,6 +80,11 @@ async def async_setup_slaves(
# this ensures that idx = bit position of value in result
# polling is done with the base class
name = self._attr_name if self._attr_name else "modbus_sensor"

# DataUpdateCoordinator does not support DEVICE_CLASS_NAME
# the assert satisfies the type checker and will catch attempts
# to use DEVICE_CLASS_NAME in _attr_name.
assert name is not DEVICE_CLASS_NAME
self._coordinator = DataUpdateCoordinator(
hass,
_LOGGER,
Expand Down
71 changes: 45 additions & 26 deletions homeassistant/components/mqtt/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@
DISCOVERY_COOLDOWN = 2
INITIAL_SUBSCRIBE_COOLDOWN = 1.0
SUBSCRIBE_COOLDOWN = 0.1
UNSUBSCRIBE_COOLDOWN = 0.1
TIMEOUT_ACK = 10

SubscribePayloadType = str | bytes # Only bytes if encoding is None
Expand Down Expand Up @@ -387,6 +388,10 @@ def __init__(
)
self._max_qos: dict[str, int] = {} # topic, max qos
self._pending_subscriptions: dict[str, int] = {} # topic, qos
self._unsubscribe_debouncer = EnsureJobAfterCooldown(
UNSUBSCRIBE_COOLDOWN, self._async_perform_unsubscribes
)
self._pending_unsubscribes: set[str] = set() # topic

if self.hass.state == CoreState.running:
self._ha_started.set()
Expand Down Expand Up @@ -460,15 +465,15 @@ async def async_publish(
msg_info = await self.hass.async_add_executor_job(
self._mqttc.publish, topic, payload, qos, retain
)
_LOGGER.debug(
"Transmitting%s message on %s: '%s', mid: %s, qos: %s",
" retained" if retain else "",
topic,
payload,
msg_info.mid,
qos,
)
_raise_on_error(msg_info.rc)
_LOGGER.debug(
"Transmitting%s message on %s: '%s', mid: %s, qos: %s",
" retained" if retain else "",
topic,
payload,
msg_info.mid,
qos,
)
_raise_on_error(msg_info.rc)
await self._wait_for_mid(msg_info.mid)

async def async_connect(self) -> None:
Expand Down Expand Up @@ -510,6 +515,10 @@ def no_more_acks() -> bool:
await self._subscribe_debouncer.async_cleanup()
# reset timeout to initial subscribe cooldown
self._subscribe_debouncer.set_timeout(INITIAL_SUBSCRIBE_COOLDOWN)
# stop the unsubscribe debouncer
await self._unsubscribe_debouncer.async_cleanup()
# make sure the unsubscribes are processed
await self._async_perform_unsubscribes()

# wait for ACKs to be processed
async with self._pending_operations_condition:
Expand Down Expand Up @@ -573,6 +582,9 @@ def _async_queue_subscriptions(
max_qos = max(qos, self._max_qos.setdefault(topic, qos))
self._max_qos[topic] = max_qos
self._pending_subscriptions[topic] = max_qos
# Cancel any pending unsubscribe since we are subscribing now
if topic in self._pending_unsubscribes:
self._pending_unsubscribes.remove(topic)
if queue_only:
return
self._subscribe_debouncer.async_schedule()
Expand Down Expand Up @@ -608,22 +620,13 @@ def async_remove() -> None:
self._matching_subscriptions.cache_clear()
# Only unsubscribe if currently connected
if self.connected:
self.hass.async_create_task(self._async_unsubscribe(topic))
self._async_unsubscribe(topic)

return async_remove

async def _async_unsubscribe(self, topic: str) -> None:
"""Unsubscribe from a topic.
This method is a coroutine.
"""

def _client_unsubscribe(topic: str) -> int:
result, mid = self._mqttc.unsubscribe(topic)
_LOGGER.debug("Unsubscribing from %s, mid: %s", topic, mid)
_raise_on_error(result)
return mid

@callback
def _async_unsubscribe(self, topic: str) -> None:
"""Unsubscribe from a topic."""
if self._is_active_subscription(topic):
if self._max_qos[topic] == 0:
return
Expand All @@ -636,11 +639,9 @@ def _client_unsubscribe(topic: str) -> int:
if topic in self._pending_subscriptions:
# avoid any pending subscription to be executed
del self._pending_subscriptions[topic]
async with self._paho_lock:
mid = await self.hass.async_add_executor_job(_client_unsubscribe, topic)
await self._register_mid(mid)

self.hass.async_create_task(self._wait_for_mid(mid))
self._pending_unsubscribes.add(topic)
self._unsubscribe_debouncer.async_schedule()

async def _async_perform_subscriptions(self) -> None:
"""Perform MQTT client subscriptions."""
Expand Down Expand Up @@ -677,6 +678,24 @@ async def _async_perform_subscriptions(self) -> None:
else:
_raise_on_error(result)

async def _async_perform_unsubscribes(self) -> None:
"""Perform pending MQTT client unsubscribes."""
if not self._pending_unsubscribes:
return

topics = list(self._pending_unsubscribes)
self._pending_unsubscribes = set()

async with self._paho_lock:
result, mid = await self.hass.async_add_executor_job(
self._mqttc.unsubscribe, topics
)
_raise_on_error(result)
for topic in topics:
_LOGGER.debug("Unsubscribing from %s, mid: %s", topic, mid)

await self._wait_for_mid(mid)

def _mqtt_on_connect(
self,
_mqttc: mqtt.Client,
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/panasonic_bluray/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@
"documentation": "https://www.home-assistant.io/integrations/panasonic_bluray",
"iot_class": "local_polling",
"loggers": ["panacotta"],
"requirements": ["panacotta==0.1"]
"requirements": ["panacotta==0.2"]
}

0 comments on commit e6e2c34

Please sign in to comment.