Skip to content

Commit

Permalink
Merge branch 'dev' into history_impossible
Browse files Browse the repository at this point in the history
  • Loading branch information
bdraco authored Nov 29, 2023
2 parents cd57fb1 + ba48100 commit ede04c1
Show file tree
Hide file tree
Showing 50 changed files with 1,267 additions and 400 deletions.
13 changes: 5 additions & 8 deletions homeassistant/components/api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -390,17 +390,14 @@ def _async_save_changed_entities(
)

try:
async with timeout(SERVICE_WAIT_TIMEOUT):
# shield the service call from cancellation on connection drop
await shield(
hass.services.async_call(
domain, service, data, blocking=True, context=context
)
# shield the service call from cancellation on connection drop
await shield(
hass.services.async_call(
domain, service, data, blocking=True, context=context
)
)
except (vol.Invalid, ServiceNotFound) as ex:
raise HTTPBadRequest() from ex
except TimeoutError:
pass
finally:
cancel_listen()

Expand Down
2 changes: 0 additions & 2 deletions homeassistant/components/baf/fan.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,6 @@ async def async_turn_off(self, **kwargs: Any) -> None:

async def async_set_preset_mode(self, preset_mode: str) -> None:
"""Set the preset mode of the fan."""
if preset_mode != PRESET_MODE_AUTO:
raise ValueError(f"Invalid preset mode: {preset_mode}")
self._device.fan_mode = OffOnAuto.AUTO

async def async_set_direction(self, direction: str) -> None:
Expand Down
4 changes: 0 additions & 4 deletions homeassistant/components/bond/fan.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,10 +199,6 @@ async def async_turn_on(

async def async_set_preset_mode(self, preset_mode: str) -> None:
"""Set the preset mode of the fan."""
if preset_mode != PRESET_MODE_BREEZE or not self._device.has_action(
Action.BREEZE_ON
):
raise ValueError(f"Invalid preset mode: {preset_mode}")
await self._hub.bond.action(self._device.device_id, Action(Action.BREEZE_ON))

async def async_turn_off(self, **kwargs: Any) -> None:
Expand Down
13 changes: 3 additions & 10 deletions homeassistant/components/demo/fan.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,12 +161,9 @@ def preset_modes(self) -> list[str] | None:

def set_preset_mode(self, preset_mode: str) -> None:
"""Set new preset mode."""
if self.preset_modes and preset_mode in self.preset_modes:
self._preset_mode = preset_mode
self._percentage = None
self.schedule_update_ha_state()
else:
raise ValueError(f"Invalid preset mode: {preset_mode}")
self._preset_mode = preset_mode
self._percentage = None
self.schedule_update_ha_state()

def turn_on(
self,
Expand Down Expand Up @@ -230,10 +227,6 @@ def preset_modes(self) -> list[str] | None:

async def async_set_preset_mode(self, preset_mode: str) -> None:
"""Set new preset mode."""
if self.preset_modes is None or preset_mode not in self.preset_modes:
raise ValueError(
f"{preset_mode} is not a valid preset_mode: {self.preset_modes}"
)
self._preset_mode = preset_mode
self._percentage = None
self.async_write_ha_state()
Expand Down
4 changes: 2 additions & 2 deletions homeassistant/components/devialet/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
SCAN_INTERVAL = timedelta(seconds=5)


class DevialetCoordinator(DataUpdateCoordinator):
class DevialetCoordinator(DataUpdateCoordinator[None]):
"""Devialet update coordinator."""

def __init__(self, hass: HomeAssistant, client: DevialetApi) -> None:
Expand All @@ -27,6 +27,6 @@ def __init__(self, hass: HomeAssistant, client: DevialetApi) -> None:
)
self.client = client

async def _async_update_data(self):
async def _async_update_data(self) -> None:
"""Fetch data from API endpoint."""
await self.client.async_update()
6 changes: 4 additions & 2 deletions homeassistant/components/devialet/media_player.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,15 @@ async def async_setup_entry(
async_add_entities([DevialetMediaPlayerEntity(coordinator, entry)])


class DevialetMediaPlayerEntity(CoordinatorEntity, MediaPlayerEntity):
class DevialetMediaPlayerEntity(
CoordinatorEntity[DevialetCoordinator], MediaPlayerEntity
):
"""Devialet media player."""

_attr_has_entity_name = True
_attr_name = None

def __init__(self, coordinator, entry: ConfigEntry) -> None:
def __init__(self, coordinator: DevialetCoordinator, entry: ConfigEntry) -> None:
"""Initialize the Devialet device."""
self.coordinator = coordinator
super().__init__(coordinator)
Expand Down
1 change: 1 addition & 0 deletions homeassistant/components/dsmr/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@

DEVICE_NAME_ELECTRICITY = "Electricity Meter"
DEVICE_NAME_GAS = "Gas Meter"
DEVICE_NAME_WATER = "Water Meter"

DSMR_VERSIONS = {"2.2", "4", "5", "5B", "5L", "5S", "Q3D"}

Expand Down
199 changes: 156 additions & 43 deletions homeassistant/components/dsmr/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
UnitOfVolume,
)
from homeassistant.core import CoreState, Event, HomeAssistant, callback
from homeassistant.helpers import device_registry as dr, entity_registry as er
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.dispatcher import (
async_dispatcher_connect,
Expand All @@ -57,6 +58,7 @@
DEFAULT_TIME_BETWEEN_UPDATE,
DEVICE_NAME_ELECTRICITY,
DEVICE_NAME_GAS,
DEVICE_NAME_WATER,
DOMAIN,
DSMR_PROTOCOL,
LOGGER,
Expand All @@ -73,6 +75,7 @@ class DSMRSensorEntityDescription(SensorEntityDescription):

dsmr_versions: set[str] | None = None
is_gas: bool = False
is_water: bool = False
obis_reference: str


Expand Down Expand Up @@ -374,28 +377,138 @@ class DSMRSensorEntityDescription(SensorEntityDescription):
)


def add_gas_sensor_5B(telegram: dict[str, DSMRObject]) -> DSMRSensorEntityDescription:
"""Return correct entity for 5B Gas meter."""
ref = None
if obis_references.BELGIUM_MBUS1_METER_READING2 in telegram:
ref = obis_references.BELGIUM_MBUS1_METER_READING2
elif obis_references.BELGIUM_MBUS2_METER_READING2 in telegram:
ref = obis_references.BELGIUM_MBUS2_METER_READING2
elif obis_references.BELGIUM_MBUS3_METER_READING2 in telegram:
ref = obis_references.BELGIUM_MBUS3_METER_READING2
elif obis_references.BELGIUM_MBUS4_METER_READING2 in telegram:
ref = obis_references.BELGIUM_MBUS4_METER_READING2
elif ref is None:
ref = obis_references.BELGIUM_MBUS1_METER_READING2
return DSMRSensorEntityDescription(
key="belgium_5min_gas_meter_reading",
translation_key="gas_meter_reading",
obis_reference=ref,
dsmr_versions={"5B"},
is_gas=True,
device_class=SensorDeviceClass.GAS,
state_class=SensorStateClass.TOTAL_INCREASING,
)
def create_mbus_entity(
mbus: int, mtype: int, telegram: dict[str, DSMRObject]
) -> DSMRSensorEntityDescription | None:
"""Create a new MBUS Entity."""
if (
mtype == 3
and (
obis_reference := getattr(
obis_references, f"BELGIUM_MBUS{mbus}_METER_READING2"
)
)
in telegram
):
return DSMRSensorEntityDescription(
key=f"mbus{mbus}_gas_reading",
translation_key="gas_meter_reading",
obis_reference=obis_reference,
is_gas=True,
device_class=SensorDeviceClass.GAS,
state_class=SensorStateClass.TOTAL_INCREASING,
)
if (
mtype == 7
and (
obis_reference := getattr(
obis_references, f"BELGIUM_MBUS{mbus}_METER_READING1"
)
)
in telegram
):
return DSMRSensorEntityDescription(
key=f"mbus{mbus}_water_reading",
translation_key="water_meter_reading",
obis_reference=obis_reference,
is_water=True,
device_class=SensorDeviceClass.WATER,
state_class=SensorStateClass.TOTAL_INCREASING,
)
return None


def device_class_and_uom(
telegram: dict[str, DSMRObject],
entity_description: DSMRSensorEntityDescription,
) -> tuple[SensorDeviceClass | None, str | None]:
"""Get native unit of measurement from telegram,."""
dsmr_object = telegram[entity_description.obis_reference]
uom: str | None = getattr(dsmr_object, "unit") or None
with suppress(ValueError):
if entity_description.device_class == SensorDeviceClass.GAS and (
enery_uom := UnitOfEnergy(str(uom))
):
return (SensorDeviceClass.ENERGY, enery_uom)
if uom in UNIT_CONVERSION:
return (entity_description.device_class, UNIT_CONVERSION[uom])
return (entity_description.device_class, uom)


def rename_old_gas_to_mbus(
hass: HomeAssistant, entry: ConfigEntry, mbus_device_id: str
) -> None:
"""Rename old gas sensor to mbus variant."""
dev_reg = dr.async_get(hass)
device_entry_v1 = dev_reg.async_get_device(identifiers={(DOMAIN, entry.entry_id)})
if device_entry_v1 is not None:
device_id = device_entry_v1.id

ent_reg = er.async_get(hass)
entries = er.async_entries_for_device(ent_reg, device_id)

for entity in entries:
if entity.unique_id.endswith("belgium_5min_gas_meter_reading"):
try:
ent_reg.async_update_entity(
entity.entity_id,
new_unique_id=mbus_device_id,
device_id=mbus_device_id,
)
except ValueError:
LOGGER.warning(
"Skip migration of %s because it already exists",
entity.entity_id,
)
else:
LOGGER.info(
"Migrated entity %s from unique id %s to %s",
entity.entity_id,
entity.unique_id,
mbus_device_id,
)
# Cleanup old device
dev_entities = er.async_entries_for_device(
ent_reg, device_id, include_disabled_entities=True
)
if not dev_entities:
dev_reg.async_remove_device(device_id)


def create_mbus_entities(
hass: HomeAssistant, telegram: dict[str, DSMRObject], entry: ConfigEntry
) -> list[DSMREntity]:
"""Create MBUS Entities."""
entities = []
for idx in range(1, 5):
if (
device_type := getattr(obis_references, f"BELGIUM_MBUS{idx}_DEVICE_TYPE")
) not in telegram:
continue
if (type_ := int(telegram[device_type].value)) not in (3, 7):
continue
if (
identifier := getattr(
obis_references,
f"BELGIUM_MBUS{idx}_EQUIPMENT_IDENTIFIER",
)
) in telegram:
serial_ = telegram[identifier].value
rename_old_gas_to_mbus(hass, entry, serial_)
else:
serial_ = ""
if description := create_mbus_entity(idx, type_, telegram):
entities.append(
DSMREntity(
description,
entry,
telegram,
*device_class_and_uom(telegram, description), # type: ignore[arg-type]
serial_,
idx,
)
)
return entities


async def async_setup_entry(
Expand All @@ -415,25 +528,10 @@ def init_async_add_entities(telegram: dict[str, DSMRObject]) -> None:
add_entities_handler()
add_entities_handler = None

def device_class_and_uom(
telegram: dict[str, DSMRObject],
entity_description: DSMRSensorEntityDescription,
) -> tuple[SensorDeviceClass | None, str | None]:
"""Get native unit of measurement from telegram,."""
dsmr_object = telegram[entity_description.obis_reference]
uom: str | None = getattr(dsmr_object, "unit") or None
with suppress(ValueError):
if entity_description.device_class == SensorDeviceClass.GAS and (
enery_uom := UnitOfEnergy(str(uom))
):
return (SensorDeviceClass.ENERGY, enery_uom)
if uom in UNIT_CONVERSION:
return (entity_description.device_class, UNIT_CONVERSION[uom])
return (entity_description.device_class, uom)

all_sensors = SENSORS
if dsmr_version == "5B":
all_sensors += (add_gas_sensor_5B(telegram),)
mbus_entities = create_mbus_entities(hass, telegram, entry)
for mbus_entity in mbus_entities:
entities.append(mbus_entity)

entities.extend(
[
Expand All @@ -443,7 +541,7 @@ def device_class_and_uom(
telegram,
*device_class_and_uom(telegram, description), # type: ignore[arg-type]
)
for description in all_sensors
for description in SENSORS
if (
description.dsmr_versions is None
or dsmr_version in description.dsmr_versions
Expand Down Expand Up @@ -618,6 +716,8 @@ def __init__(
telegram: dict[str, DSMRObject],
device_class: SensorDeviceClass,
native_unit_of_measurement: str | None,
serial_id: str = "",
mbus_id: int = 0,
) -> None:
"""Initialize entity."""
self.entity_description = entity_description
Expand All @@ -629,16 +729,29 @@ def __init__(
device_serial = entry.data[CONF_SERIAL_ID]
device_name = DEVICE_NAME_ELECTRICITY
if entity_description.is_gas:
device_serial = entry.data[CONF_SERIAL_ID_GAS]
if serial_id:
device_serial = serial_id
else:
device_serial = entry.data[CONF_SERIAL_ID_GAS]
device_name = DEVICE_NAME_GAS
if entity_description.is_water:
if serial_id:
device_serial = serial_id
device_name = DEVICE_NAME_WATER
if device_serial is None:
device_serial = entry.entry_id

self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, device_serial)},
name=device_name,
)
self._attr_unique_id = f"{device_serial}_{entity_description.key}"
if mbus_id != 0:
if serial_id:
self._attr_unique_id = f"{device_serial}"
else:
self._attr_unique_id = f"{device_serial}_{mbus_id}"
else:
self._attr_unique_id = f"{device_serial}_{entity_description.key}"

@callback
def update_data(self, telegram: dict[str, DSMRObject] | None) -> None:
Expand Down
3 changes: 3 additions & 0 deletions homeassistant/components/dsmr/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,9 @@
},
"voltage_swell_l3_count": {
"name": "Voltage swells phase L3"
},
"water_meter_reading": {
"name": "Water consumption"
}
}
},
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/esphome/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"iot_class": "local_push",
"loggers": ["aioesphomeapi", "noiseprotocol"],
"requirements": [
"aioesphomeapi==19.2.0",
"aioesphomeapi==19.2.1",
"bluetooth-data-tools==1.15.0",
"esphome-dashboard-api==1.2.3"
],
Expand Down
Loading

0 comments on commit ede04c1

Please sign in to comment.