1
0
mirror of https://github.com/home-assistant/core.git synced 2026-05-21 16:00:12 +01:00

Fix line length violations in components s (#170722)

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: frenck <195327+frenck@users.noreply.github.com>
This commit is contained in:
Franck Nijhof
2026-05-14 23:01:09 +02:00
committed by GitHub
parent 0ed8d24b54
commit daffc8c2ce
102 changed files with 664 additions and 327 deletions
+2 -1
View File
@@ -37,7 +37,8 @@ class SamsungTVEntity(CoordinatorEntity[SamsungTVDataUpdateCoordinator], Entity)
config_entry = coordinator.config_entry
self._mac: str | None = config_entry.data.get(CONF_MAC)
self._host: str | None = config_entry.data.get(CONF_HOST)
# Fallback for legacy models that doesn't have a API to retrieve MAC or SerialNumber
# Fallback for legacy models that doesn't have a API
# to retrieve MAC or SerialNumber
self._attr_unique_id = config_entry.unique_id or config_entry.entry_id
self._attr_device_info = DeviceInfo(
manufacturer=config_entry.data.get(CONF_MANUFACTURER),
@@ -135,7 +135,8 @@ async def async_migrate_entry(
SUBENTRY_TYPE_SWITCHABLE_OUTPUT: CONF_SWITCHABLE_OUTPUT_NUMBER,
}
new_title = f"{subentry.title} ({subentry.data[property_map[subentry.subentry_type]]})"
prop = property_map[subentry.subentry_type]
new_title = f"{subentry.title} ({subentry.data[prop]})"
hass.config_entries.async_update_subentry(
config_entry, subentry, title=new_title
@@ -143,7 +144,8 @@ async def async_migrate_entry(
hass.config_entries.async_update_entry(config_entry, minor_version=2)
# 2.1 Migrate all entity unique IDs to replace "satel" prefix with config entry ID, allows multiple entries to be configured
# 2.1 Migrate all entity unique IDs to replace "satel" prefix
# with config entry ID, allows multiple entries to be configured
if config_entry.version == 1:
@callback
@@ -41,7 +41,8 @@ class SatelClient:
host = entry.data[CONF_HOST]
port = entry.data[CONF_PORT]
# Make sure we initialize the Satel controller with the configured entries to monitor
# Make sure we initialize the Satel controller
# with the configured entries to monitor
partitions = [
subentry.data[CONF_PARTITION_NUMBER]
for subentry in entry.subentries.values()
@@ -312,7 +312,9 @@ class PartitionSubentryFlowHandler(ConfigSubentryFlow):
if not errors:
return self.async_create_entry(
title=f"{user_input[CONF_NAME]} ({user_input[CONF_PARTITION_NUMBER]})",
title=(
f"{user_input[CONF_NAME]} ({user_input[CONF_PARTITION_NUMBER]})"
),
data=user_input,
unique_id=unique_id,
)
@@ -339,7 +341,10 @@ class PartitionSubentryFlowHandler(ConfigSubentryFlow):
return self.async_update_and_abort(
self._get_entry(),
subconfig_entry,
title=f"{user_input[CONF_NAME]} ({subconfig_entry.data[CONF_PARTITION_NUMBER]})",
title=(
f"{user_input[CONF_NAME]}"
f" ({subconfig_entry.data[CONF_PARTITION_NUMBER]})"
),
data_updates=user_input,
)
@@ -425,7 +430,10 @@ class ZoneSubentryFlowHandler(ConfigSubentryFlow):
return self.async_update_and_abort(
self._get_entry(),
subconfig_entry,
title=f"{user_input[CONF_NAME]} ({subconfig_entry.data[CONF_ZONE_NUMBER]})",
title=(
f"{user_input[CONF_NAME]}"
f" ({subconfig_entry.data[CONF_ZONE_NUMBER]})"
),
data_updates=user_input,
)
@@ -491,7 +499,10 @@ class OutputSubentryFlowHandler(ConfigSubentryFlow):
return self.async_update_and_abort(
self._get_entry(),
subconfig_entry,
title=f"{user_input[CONF_NAME]} ({subconfig_entry.data[CONF_OUTPUT_NUMBER]})",
title=(
f"{user_input[CONF_NAME]}"
f" ({subconfig_entry.data[CONF_OUTPUT_NUMBER]})"
),
data_updates=user_input,
)
@@ -516,7 +527,10 @@ class SwitchableOutputSubentryFlowHandler(ConfigSubentryFlow):
errors: dict[str, str] = {}
if user_input is not None:
unique_id = f"{SUBENTRY_TYPE_SWITCHABLE_OUTPUT}_{user_input[CONF_SWITCHABLE_OUTPUT_NUMBER]}"
unique_id = (
f"{SUBENTRY_TYPE_SWITCHABLE_OUTPUT}"
f"_{user_input[CONF_SWITCHABLE_OUTPUT_NUMBER]}"
)
for existing_subentry in self._get_entry().subentries.values():
if existing_subentry.unique_id == unique_id:
@@ -524,7 +538,10 @@ class SwitchableOutputSubentryFlowHandler(ConfigSubentryFlow):
if not errors:
return self.async_create_entry(
title=f"{user_input[CONF_NAME]} ({user_input[CONF_SWITCHABLE_OUTPUT_NUMBER]})",
title=(
f"{user_input[CONF_NAME]}"
f" ({user_input[CONF_SWITCHABLE_OUTPUT_NUMBER]})"
),
data=user_input,
unique_id=unique_id,
)
@@ -551,7 +568,10 @@ class SwitchableOutputSubentryFlowHandler(ConfigSubentryFlow):
return self.async_update_and_abort(
self._get_entry(),
subconfig_entry,
title=f"{user_input[CONF_NAME]} ({subconfig_entry.data[CONF_SWITCHABLE_OUTPUT_NUMBER]})",
title=(
f"{user_input[CONF_NAME]}"
f" ({subconfig_entry.data[CONF_SWITCHABLE_OUTPUT_NUMBER]})"
),
data_updates=user_input,
)
@@ -68,7 +68,8 @@ def valid_schedule(schedule: list[dict[str, str]]) -> list[dict[str, str]]:
# Sort the schedule by start times
schedule = sorted(schedule, key=lambda time_range: time_range[CONF_FROM])
# Check if the start time of the next event is before the end time of the previous event
# Check if the start time of the next event is before
# the end time of the previous event
previous_to = None
for time_range in schedule:
if time_range[CONF_FROM] >= time_range[CONF_TO]:
@@ -269,7 +270,8 @@ class Schedule(CollectionEntity):
self._attr_name = self._config[CONF_NAME]
self._attr_unique_id = self._config[CONF_ID]
# Exclude any custom attributes that may be present on time ranges from recording.
# Exclude any custom attributes that may be present
# on time ranges from recording.
self._unrecorded_attributes = self.all_custom_data_keys()
self._Entity__combined_unrecorded_attributes = (
self._entity_component_unrecorded_attributes | self._unrecorded_attributes
+2 -1
View File
@@ -190,7 +190,8 @@ async def async_migrate_entry(hass: HomeAssistant, entry: ScrapeConfigEntry) ->
unique_id=None,
)
_LOGGER.debug(
"Migrating sensor %s with unique id %s to sub config entry id %s, old data %s, new data %s",
"Migrating sensor %s with unique id %s to sub config"
" entry id %s, old data %s, new data %s",
title,
old_unique_id,
new_sub_entry.subentry_id,
@@ -59,7 +59,8 @@ class ScrapeCoordinator(DataUpdateCoordinator[BeautifulSoup]):
raise UpdateFailed("REST data is not available")
# Detect if content is XML and use appropriate parser
# Check Content-Type header first (most reliable), then fall back to content detection
# Check Content-Type header first (most reliable),
# then fall back to content detection
parser = "lxml"
headers = self._rest.headers
content_type = headers.get("Content-Type", "") if headers else ""
@@ -76,7 +77,8 @@ class ScrapeCoordinator(DataUpdateCoordinator[BeautifulSoup]):
after_xml_lower = after_xml.lower()
is_html = after_xml_lower.startswith(("<!doctype html", "<html"))
if is_html:
# Strip XML declaration from HTML to avoid XMLParsedAsHTMLWarning
# Strip XML declaration from HTML
# to avoid XMLParsedAsHTMLWarning
data = after_xml
else:
parser = "lxml-xml"
@@ -145,7 +145,8 @@ async def _async_migrate_entries(
continue
if device == "pump" and source_index is None:
_LOGGER.debug(
"Unable to parse 'source_index' from existing unique_id for pump entity '%s'",
"Unable to parse 'source_index' from existing"
" unique_id for pump entity '%s'",
source_key,
)
continue
@@ -160,7 +161,8 @@ async def _async_migrate_entries(
entry.domain, entry.platform, new_unique_id
):
_LOGGER.debug(
"Cannot migrate '%s' to unique_id '%s', already exists for entity '%s'. Aborting",
"Cannot migrate '%s' to unique_id '%s',"
" already exists for entity '%s'. Aborting",
entry.unique_id,
new_unique_id,
existing_entity_id,
@@ -138,7 +138,8 @@ class ScreenLogicPushEntity(ScreenLogicEntity):
@callback
def _handle_coordinator_update(self) -> None:
"""Handle updated data from the coordinator."""
# For push entities, only take updates from the coordinator if availability changes.
# For push entities, only take updates from the
# coordinator if availability changes.
if self.coordinator.last_update_success != self._last_update_success:
self._async_data_updated()
+2 -1
View File
@@ -135,7 +135,8 @@ class Searcher:
# Scripts referencing this area
self._add(ItemType.SCRIPT, script.scripts_with_area(self.hass, area_id))
# Entity in this area, will extend this with the entities of the devices in this area
# Entity in this area, will extend this with
# the entities of the devices in this area
entity_entries = er.async_entries_for_area(self._entity_registry, area_id)
# Devices in this area
+40 -22
View File
@@ -300,7 +300,8 @@ class SensorEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
await super().async_internal_added_to_hass()
if self.entity_category == EntityCategory.CONFIG:
raise HomeAssistantError(
f"Entity {self.entity_id} cannot be added as the entity category is set to config"
f"Entity {self.entity_id} cannot be added as"
" the entity category is set to config"
)
if not self.registry_entry:
@@ -417,8 +418,10 @@ class SensorEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
if suggested_unit_of_measurement is None and (
unit_converter := UNIT_CONVERTERS.get(self.device_class)
):
# If the device class is not known by the unit system but has a unit converter,
# fall back to the unit suggested by the unit converter's unit class.
# If the device class is not known by the unit
# system but has a unit converter, fall back to
# the unit suggested by the unit converter's
# unit class.
suggested_unit_of_measurement = self.hass.config.units.get_converted_unit(
unit_converter.UNIT_CLASS, self.__native_unit_of_measurement_compat
)
@@ -458,9 +461,12 @@ class SensorEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
state_class = self.state_class
if state_class != SensorStateClass.TOTAL:
raise ValueError(
f"Entity {self.entity_id} ({type(self)}) with state_class {state_class}"
" has set last_reset. Setting last_reset for entities with state_class"
" other than 'total' is not supported. Please update your configuration"
f"Entity {self.entity_id} ({type(self)})"
f" with state_class {state_class}"
" has set last_reset. Setting last_reset"
" for entities with state_class"
" other than 'total' is not supported."
" Please update your configuration"
" if state_class is manually configured."
)
@@ -567,9 +573,13 @@ class SensorEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
):
if native_unit_of_measurement is not None:
raise ValueError(
f"Sensor {type(self)} from integration '{self.platform.platform_name}' "
f"has a translation key for unit_of_measurement '{unit_of_measurement}', "
f"but also has a native_unit_of_measurement '{native_unit_of_measurement}'"
f"Sensor {type(self)} from integration"
f" '{self.platform.platform_name}' "
"has a translation key for"
f" unit_of_measurement '{unit_of_measurement}'"
", but also has a"
" native_unit_of_measurement"
f" '{native_unit_of_measurement}'"
)
return unit_of_measurement
@@ -654,8 +664,10 @@ class SensorEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
return value.isoformat(timespec="seconds")
except (AttributeError, OverflowError, TypeError) as err:
raise ValueError(
f"Invalid datetime: {self.entity_id} has {device_class.value} device class "
f"but provides state {value}:{type(value)} resulting in '{err}'"
f"Invalid datetime: {self.entity_id}"
f" has {device_class.value} device class"
f" but provides state {value}:{type(value)}"
f" resulting in '{err}'"
) from err
# Received a date value
@@ -773,9 +785,12 @@ class SensorEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
and native_unit_of_measurement not in units
):
raise ValueError(
f"Sensor {self.entity_id} ({type(self)}) is using native unit of "
f"measurement '{native_unit_of_measurement}' which is not a valid unit "
f"for the state class ('{state_class}') it is using; expected one of {units};"
f"Sensor {self.entity_id} ({type(self)}) is"
" using native unit of measurement"
f" '{native_unit_of_measurement}' which is"
" not a valid unit for the state class"
f" ('{state_class}') it is using;"
f" expected one of {units};"
)
return value
@@ -794,15 +809,18 @@ class SensorEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
def _get_adjusted_display_precision(self) -> int | None:
"""Return the display precision for the sensor.
When the integration has specified a suggested display precision, it will be used.
If a unit conversion is needed, the display precision will be adjusted based on
the ratio from the native unit to the current one.
When the integration has specified a suggested display
precision, it will be used. If a unit conversion is needed,
the display precision will be adjusted based on the ratio
from the native unit to the current one.
When the integration does not specify a suggested display precision, a default
device class precision will be used from UNITS_PRECISION, and the final precision
will be adjusted based on the ratio from the default unit to the current one. It
will also be capped so that the extra precision (from the base unit) does not
exceed DEFAULT_PRECISION_LIMIT.
When the integration does not specify a suggested
display precision, a default device class precision will
be used from UNITS_PRECISION, and the final precision
will be adjusted based on the ratio from the default
unit to the current one. It will also be capped so that
the extra precision (from the base unit) does not exceed
DEFAULT_PRECISION_LIMIT.
"""
display_precision = self.suggested_display_precision
device_class = self.device_class
+25 -11
View File
@@ -175,7 +175,8 @@ class SensorDeviceClass(StrEnum):
CO = "carbon_monoxide"
"""Carbon Monoxide gas concentration.
Unit of measurement: `ppb` (parts per billion), `ppm` (parts per million), `mg/`, `μg/`
Unit of measurement: `ppb` (parts per billion),
`ppm` (parts per million), `mg/`, `μg/`
"""
CO2 = "carbon_dioxide"
@@ -227,16 +228,20 @@ class SensorDeviceClass(StrEnum):
Use this device class for sensors measuring energy consumption, for example
electric energy consumption.
Unit of measurement: `J`, `kJ`, `MJ`, `GJ`, `mWh`, `Wh`, `kWh`, `MWh`, `GWh`, `TWh`, `cal`, `kcal`, `Mcal`, `Gcal`
Unit of measurement: `J`, `kJ`, `MJ`, `GJ`, `mWh`,
`Wh`, `kWh`, `MWh`, `GWh`, `TWh`, `cal`, `kcal`,
`Mcal`, `Gcal`
"""
ENERGY_DISTANCE = "energy_distance"
"""Energy distance.
Use this device class for sensors measuring energy by distance, for example the amount
of electric energy consumed by an electric car.
Use this device class for sensors measuring energy by
distance, for example the amount of electric energy
consumed by an electric car.
Unit of measurement: `kWh/100km`, `Wh/km`, `mi/kWh`, `km/kWh`
Unit of measurement: `kWh/100km`, `Wh/km`,
`mi/kWh`, `km/kWh`
"""
ENERGY_STORAGE = "energy_storage"
@@ -245,7 +250,9 @@ class SensorDeviceClass(StrEnum):
Use this device class for sensors measuring stored energy, for example the amount
of electric energy currently stored in a battery or the capacity of a battery.
Unit of measurement: `J`, `kJ`, `MJ`, `GJ`, `mWh`, `Wh`, `kWh`, `MWh`, `GWh`, `TWh`, `cal`, `kcal`, `Mcal`, `Gcal`
Unit of measurement: `J`, `kJ`, `MJ`, `GJ`, `mWh`,
`Wh`, `kWh`, `MWh`, `GWh`, `TWh`, `cal`, `kcal`,
`Mcal`, `Gcal`
"""
FREQUENCY = "frequency"
@@ -464,8 +471,10 @@ class SensorDeviceClass(StrEnum):
Unit of measurement: `VOLUME_*` units
- SI / metric: `mL`, `L`, ``
- USCS / imperial: `ft³`, `CCF`, `MCF`, `fl. oz.`, `gal` (warning: volumes expressed in
USCS/imperial units are currently assumed to be US volumes)
- USCS / imperial: `ft³`, `CCF`, `MCF`,
`fl. oz.`, `gal` (warning: volumes expressed in
USCS/imperial units are currently assumed to be
US volumes)
"""
VOLUME_STORAGE = "volume_storage"
@@ -476,8 +485,10 @@ class SensorDeviceClass(StrEnum):
Unit of measurement: `VOLUME_*` units
- SI / metric: `mL`, `L`, ``
- USCS / imperial: `ft³`, `CCF`, `MCF`, `fl. oz.`, `gal` (warning: volumes expressed in
USCS/imperial units are currently assumed to be US volumes)
- USCS / imperial: `ft³`, `CCF`, `MCF`,
`fl. oz.`, `gal` (warning: volumes expressed in
USCS/imperial units are currently assumed to be
US volumes)
"""
VOLUME_FLOW_RATE = "volume_flow_rate"
@@ -545,7 +556,10 @@ class SensorStateClass(StrEnum):
"""The state represents a measurement in present time."""
MEASUREMENT_ANGLE = "measurement_angle"
"""The state represents a angle measurement in present time. Currently only degrees are supported."""
"""The state represents an angle measurement in present time.
Currently only degrees are supported.
"""
TOTAL = "total"
"""The state represents a total amount.
+12 -9
View File
@@ -92,7 +92,8 @@ WARN_NEGATIVE: HassKey[set[str]] = HassKey(f"{DOMAIN}_warn_total_increasing_nega
# Keep track of entities for which a warning about unsupported unit has been logged
WARN_UNSUPPORTED_UNIT: HassKey[set[str]] = HassKey(f"{DOMAIN}_warn_unsupported_unit")
WARN_UNSTABLE_UNIT: HassKey[set[str]] = HassKey(f"{DOMAIN}_warn_unstable_unit")
# Keep track of entities for which a warning about statistics mean algorithm change has been logged
# Keep track of entities for which a warning about
# statistics mean algorithm change has been logged
WARN_STATISTICS_MEAN_CHANGED: HassKey[set[str]] = HassKey(
f"{DOMAIN}_warn_statistics_mean_change"
)
@@ -165,8 +166,8 @@ def _time_weighted_circular_mean(
) -> tuple[float, float]:
"""Calculate a time weighted circular mean.
The circular mean is calculated by weighting the states by duration in seconds between
state changes.
The circular mean is calculated by weighting the states
by duration in seconds between state changes.
Note: there's no interpolation of values between state changes.
"""
old_fstate: float | None = None
@@ -407,11 +408,12 @@ def _suggest_report_issue(hass: HomeAssistant, entity_id: str) -> str:
def warn_dip(
hass: HomeAssistant, entity_id: str, state: State, previous_fstate: float
) -> None:
"""Log a warning once if a sensor with state class TOTAL_INCREASING has a decreasing value.
"""Log a warning once if a sensor with TOTAL_INCREASING has a decreasing value.
The log will be suppressed until two dips have been seen to prevent warning due to
rounding issues with databases storing the state as a single precision float, which
was fixed in recorder DB version 20.
The log will be suppressed until two dips have been seen
to prevent warning due to rounding issues with databases
storing the state as a single precision float, which was
fixed in recorder DB version 20.
"""
if SEEN_DIP not in hass.data:
hass.data[SEEN_DIP] = set()
@@ -443,7 +445,7 @@ def warn_dip(
def warn_negative(hass: HomeAssistant, entity_id: str, state: State) -> None:
"""Log a warning once if a sensor with state class TOTAL_INCREASING has a negative value."""
"""Log a warning once if a sensor with TOTAL_INCREASING has a negative value."""
if WARN_NEGATIVE not in hass.data:
hass.data[WARN_NEGATIVE] = set()
if entity_id not in hass.data[WARN_NEGATIVE]:
@@ -662,7 +664,8 @@ def compile_statistics( # noqa: C901
hass.data[WARN_STATISTICS_MEAN_CHANGED].add(entity_id)
_LOGGER.warning(
(
"The statistics mean algorithm for %s have changed from %s to %s."
"The statistics mean algorithm for %s have"
" changed from %s to %s."
" Generation of long term statistics will be suppressed"
" unless it changes back or go to %s to delete the old"
" statistics"
+1 -1
View File
@@ -9,7 +9,7 @@ from homeassistant.helpers import config_entry_oauth2_flow
class SENZConfigEntryAuth(AbstractSENZAuth):
"""Provide nVent RAYCHEM SENZ authentication tied to an OAuth2 based config entry."""
"""Provide nVent RAYCHEM SENZ authentication tied to an OAuth2 config entry."""
def __init__(
self,
@@ -89,7 +89,8 @@ async def async_remove_entry(hass: HomeAssistant, entry: SFTPConfigEntry) -> Non
pkey.unlink()
except OSError as e:
LOGGER.warning(
"Failed to remove private key %s for %s integration for host %s@%s. %s",
"Failed to remove private key %s for %s"
" integration for host %s@%s. %s",
pkey.name,
DOMAIN,
entry.data[CONF_USERNAME],
@@ -103,7 +104,9 @@ async def async_remove_entry(hass: HomeAssistant, entry: SFTPConfigEntry) -> Non
if e.errno == errno.ENOTEMPTY: # Directory not empty
if LOGGER.isEnabledFor(logging.DEBUG):
leftover_files = []
# If we get an exception while gathering leftover files, make sure to log plain message.
# If we get an exception while gathering
# leftover files, make sure to log plain
# message.
with contextlib.suppress(OSError):
leftover_files = [f.name for f in pkey.parent.iterdir()]
@@ -117,7 +120,8 @@ async def async_remove_entry(hass: HomeAssistant, entry: SFTPConfigEntry) -> Non
)
else:
LOGGER.warning(
"Error occurred while removing directory %s for integration %s: %s at host %s@%s",
"Error occurred while removing directory %s"
" for integration %s: %s at host %s@%s",
str(pkey.parent),
DOMAIN,
str(e),
@@ -67,7 +67,8 @@ class SFTPBackupAgent(BackupAgent):
) -> AsyncIterator[bytes]:
"""Download a backup file from SFTP."""
LOGGER.debug(
"Establishing SFTP connection to remote host in order to download backup id: %s",
"Establishing SFTP connection to remote host"
" in order to download backup id: %s",
backup_id,
)
try:
@@ -30,7 +30,7 @@ if TYPE_CHECKING:
def get_client_options(cfg: SFTPConfigEntryData) -> SSHClientConnectionOptions:
"""Use this function with `hass.async_add_executor_job` to asynchronously get `SSHClientConnectionOptions`."""
"""Get `SSHClientConnectionOptions` for use with `hass.async_add_executor_job`."""
return SSHClientConnectionOptions(
known_hosts=None,
@@ -177,7 +177,8 @@ class BackupAgentClient:
if not await self.sftp.exists(metadata.file_path):
await self.sftp.unlink(metadata.metadata_file)
raise FileNotFoundError(
f"File at provided remote location: {metadata.file_path} does not exist."
"File at provided remote location:"
f" {metadata.file_path} does not exist."
)
LOGGER.debug("Removing file at path: %s", metadata.file_path)
@@ -186,7 +187,7 @@ class BackupAgentClient:
await self.sftp.unlink(metadata.metadata_file)
async def async_list_backups(self) -> list[AgentBackup]:
"""Iterate through a list of metadata files and return a list of `AgentBackup` objects."""
"""Iterate through metadata files and return a list of `AgentBackup` objects."""
backups: list[AgentBackup] = []
@@ -217,7 +218,7 @@ class BackupAgentClient:
iterator: AsyncIterator[bytes],
backup: AgentBackup,
) -> None:
"""Accept `iterator` as bytes iterator and write backup archive to SFTP Server."""
"""Accept `iterator` as bytes iterator and write backup archive."""
file_path = (
f"{self.cfg.runtime_data.backup_location}/{suggested_filename(backup)}"
@@ -244,8 +245,10 @@ class BackupAgentClient:
async def iter_file(self, backup_id: str) -> AsyncFileIterator:
"""Return Async File Iterator object.
`SFTPClientFile` object (that would be returned with `sftp.open`) is not an iterator.
So we return custom made class - `AsyncFileIterator` that would allow iteration on file object.
`SFTPClientFile` object (that would be returned with
`sftp.open`) is not an iterator. So we return custom
made class - `AsyncFileIterator` that would allow
iteration on file object.
Raises:
------
@@ -294,7 +297,9 @@ class BackupAgentClient:
)
except (OSError, PermissionDenied) as e:
raise BackupAgentError(
"Failure while attempting to establish SSH connection. Please check SSH credentials and if changed, re-install the integration"
"Failure while attempting to establish SSH"
" connection. Please check SSH credentials"
" and if changed, re-install the integration"
) from e
# Configure SFTP Client Connection
@@ -303,7 +308,8 @@ class BackupAgentClient:
await self.sftp.chdir(self.cfg.runtime_data.backup_location)
except (SFTPNoSuchFile, SFTPPermissionDenied) as e:
raise BackupAgentError(
"Failed to create SFTP client. Re-installing integration might be required"
"Failed to create SFTP client."
" Re-installing integration might be required"
) from e
return self
@@ -58,11 +58,11 @@ class SFTPStorageException(Exception):
class SFTPStorageInvalidPrivateKey(SFTPStorageException):
"""Exception raised during config flow - when user provided invalid private key file."""
"""Exception raised when user provided invalid private key file."""
class SFTPStorageMissingPasswordOrPkey(SFTPStorageException):
"""Exception raised during config flow - when user did not provide password or private key file."""
"""Exception raised when user did not provide password or private key file."""
class SFTPFlowHandler(ConfigFlow, domain=DOMAIN):
@@ -85,8 +85,10 @@ class SFTPFlowHandler(ConfigFlow, domain=DOMAIN):
Returns: the possibly updated `user_input`.
Raises:
- SFTPStorageMissingPasswordOrPkey: Neither password nor private key provided
- SFTPStorageInvalidPrivateKey: The provided private key has an invalid format
- SFTPStorageMissingPasswordOrPkey: Neither password
nor private key provided
- SFTPStorageInvalidPrivateKey: The provided private
key has an invalid format
"""
# If neither password nor private key is provided, error out;
@@ -153,9 +155,11 @@ class SFTPFlowHandler(ConfigFlow, domain=DOMAIN):
# - OSError, if host or port are not correct.
# - SFTPStorageInvalidPrivateKey, if private key is not valid format.
# - asyncssh.misc.PermissionDenied, if credentials are not correct.
# - SFTPStorageMissingPasswordOrPkey, if password and private key are not provided.
# - SFTPStorageMissingPasswordOrPkey, if password
# and private key are not provided.
# - asyncssh.sftp.SFTPNoSuchFile, if directory does not exist.
# - asyncssh.sftp.SFTPPermissionDenied, if we don't have access to said directory
# - asyncssh.sftp.SFTPPermissionDenied,
# if we don't have access to said directory
async with (
connect(
host=user_config.host,
@@ -71,7 +71,9 @@ async def _validate_input(
LOGGER.exception("Unexpected exception")
LOGGER.error(error)
raise UnknownAuth(
"An unknown error occurred. Check your region settings and open an issue on Github if the issue persists."
"An unknown error occurred. Check your region"
" settings and open an issue on GitHub"
" if the issue persists."
) from error
# Return info that you want to store in the config entry.
+4 -3
View File
@@ -137,9 +137,10 @@ class SharkVacuumEntity(CoordinatorEntity[SharkIqUpdateCoordinator], StateVacuum
def activity(self) -> VacuumActivity | None:
"""Get the current vacuum state.
NB: Currently, we do not return an error state because they can be very, very stale.
In the app, these are (usually) handled by showing the robot as stopped and sending the
user a notification.
NB: Currently, we do not return an error state
because they can be very, very stale. In the app,
these are (usually) handled by showing the robot as
stopped and sending the user a notification.
"""
if self.sharkiq.get_property_value(Properties.CHARGING_STATUS):
return VacuumActivity.DOCKED
+13 -8
View File
@@ -236,7 +236,8 @@ class ShellyConfigFlow(ConfigFlow, domain=DOMAIN):
and isinstance(model_id, int)
and (model_name := get_name_from_model_id(model_id))
):
# Remove spaces from model name (e.g., "Shelly 1 Mini Gen4" -> "Shelly1MiniGen4")
# Remove spaces from model name
# (e.g., "Shelly 1 Mini Gen4" -> "Shelly1MiniGen4")
return f"{model_name.replace(' ', '')}-{mac}"
return f"Shelly-{mac}"
@@ -407,11 +408,13 @@ class ShellyConfigFlow(ConfigFlow, domain=DOMAIN):
async def _async_connect_and_get_info(
self, host: str, port: int
) -> ConfigFlowResult | None:
"""Connect to device, validate, and create entry or return None to continue flow.
"""Connect to device, validate, and create entry or return None.
This helper consolidates the common logic between Zeroconf device selection
and manual entry flows. Returns a ConfigFlowResult if the flow should end
(create_entry or abort), or None if the flow should continue (e.g., to credentials).
This helper consolidates the common logic between
Zeroconf device selection and manual entry flows.
Returns a ConfigFlowResult if the flow should end
(create_entry or abort), or None if the flow should
continue (e.g., to credentials).
Sets self.info, self.host, and self.port on success.
"""
@@ -685,7 +688,7 @@ class ShellyConfigFlow(ConfigFlow, domain=DOMAIN):
await self._async_discovered_mac(mac, host)
async def _async_discovered_mac(self, mac: str, host: str) -> None:
"""Abort and reconnect soon if the device with the mac address is already configured."""
"""Abort and reconnect soon if the device with the mac is already configured."""
if (
current_entry := await self.async_set_unique_id(mac)
) and current_entry.data.get(CONF_HOST) == host:
@@ -914,7 +917,8 @@ class ShellyConfigFlow(ConfigFlow, domain=DOMAIN):
) -> ConfigFlowResult | None:
"""Provision WiFi credentials via BLE and wait for zeroconf discovery.
Returns the flow result to be stored in self._provision_result, or None if failed.
Returns the flow result to be stored in
self._provision_result, or None if failed.
"""
# Provision WiFi via BLE using persistent connection
try:
@@ -974,7 +978,8 @@ class ShellyConfigFlow(ConfigFlow, domain=DOMAIN):
state.port = DEFAULT_HTTP_PORT
else:
LOGGER.debug("BLE fallback also failed - provisioning unsuccessful")
# Store failure info and return None - provision_done will handle redirect
# Store failure info and return None
# provision_done will handle redirect
return None
else:
state.host, state.port = result
@@ -122,8 +122,9 @@ class ShellyCoordinatorBase[_DeviceT: BlockDevice | RpcDevice](
self.suggested_area: str | None = None
device_name = device.name if device.initialized else entry.title
interval_td = timedelta(seconds=update_interval)
# The device has come online at least once. In the case of a sleeping RPC
# device, this means that the device has connected to the WS server at least once.
# The device has come online at least once. In the case
# of a sleeping RPC device, this means that the device
# has connected to the WS server at least once.
self._came_online_once = False
super().__init__(
hass,
+2 -1
View File
@@ -163,7 +163,8 @@ def _async_setup_rpc_entry(
ShellyRpcScriptEvent(coordinator, script, SCRIPT_EVENT, event_types)
)
# If a script is removed, from the device configuration, we need to remove orphaned entities
# If a script is removed, from the device configuration,
# we need to remove orphaned entities
async_remove_orphaned_entities(
hass,
config_entry.entry_id,
+4 -1
View File
@@ -41,7 +41,10 @@ def async_describe_events(
rpc_coordinator = get_rpc_coordinator_by_device_id(hass, device_id)
if rpc_coordinator and rpc_coordinator.device.initialized:
key = f"input:{channel - 1}"
input_name = f"{rpc_coordinator.device.name} {get_rpc_channel_name(rpc_coordinator.device, key)}"
input_name = (
f"{rpc_coordinator.device.name}"
f" {get_rpc_channel_name(rpc_coordinator.device, key)}"
)
elif click_type in BLOCK_INPUTS_EVENTS_TYPES:
block_coordinator = get_block_coordinator_by_device_id(hass, device_id)
@@ -83,6 +83,8 @@ class ListTopItemsIntent(intent.IntentHandler):
else:
items_list = ", ".join(str(itm["name"]) for itm in reversed(items))
response.async_set_speech(
f"These are the top {min(len(items), 5)} items on your shopping list: {items_list}"
"These are the top"
f" {min(len(items), 5)} items on your"
f" shopping list: {items_list}"
)
return response
+3 -2
View File
@@ -132,7 +132,7 @@ class SIAConfigFlow(ConfigFlow, domain=DOMAIN):
async def async_handle_data_and_route(
self, user_input: dict[str, Any]
) -> ConfigFlowResult:
"""Handle the user_input, check if configured and route to the right next step or create entry."""
"""Handle user_input, check if configured and route to the right next step."""
self._update_data(user_input)
self._async_abort_entries_match({CONF_PORT: self._data[CONF_PORT]})
@@ -148,7 +148,8 @@ class SIAConfigFlow(ConfigFlow, domain=DOMAIN):
def _update_data(self, user_input: dict[str, Any]) -> None:
"""Parse the user_input and store in data and options attributes.
If there is a port in the input or no data, assume it is fully new and overwrite.
If there is a port in the input or no data, assume
it is fully new and overwrite.
Add the default options and overwrite the zones in options.
"""
if not self._data or user_input.get(CONF_PORT):
+1 -1
View File
@@ -116,7 +116,7 @@ class SIABaseEntity(RestoreEntity):
@callback
def async_handle_event(self, sia_event: SIAEvent) -> None:
"""Listen to dispatcher events for this port and account and update state and attributes.
"""Listen to dispatcher events for this port and account, update state.
If the event is for either the zone or the 0 zone (hub zone),
then handle it further.
+15 -9
View File
@@ -51,7 +51,7 @@ class SIAHub:
@callback
def async_setup_hub(self) -> None:
"""Add a device to the device_registry, register shutdown listener, load reactions."""
"""Add a device to the device_registry, register shutdown listener."""
self.update_accounts()
device_registry = dr.async_get(self._hass)
for acc in self._accounts:
@@ -74,10 +74,14 @@ class SIAHub:
await self.sia_client.async_stop()
async def async_create_and_fire_event(self, event: SIAEvent) -> None:
"""Create a event on HA dispatcher and then on HA's bus, with the data from the SIAEvent.
The created event is handled by default for only a small subset for each platform (there are about 320 SIA Codes defined, only 22 of those are used in the alarm_control_panel), a user can choose to build other automation or even entities on the same event for SIA codes not handled by the built-in platforms.
"""Create an event on HA dispatcher and then on HA's bus.
The created event is handled by default for only a
small subset for each platform (there are about 320
SIA Codes defined, only 22 of those are used in the
alarm_control_panel), a user can choose to build other
automation or even entities on the same event for SIA
codes not handled by the built-in platforms.
"""
_LOGGER.debug(
"Adding event to dispatch and bus for code %s for port %s and account %s",
@@ -109,7 +113,8 @@ class SIAHub:
if self.sia_client is not None:
self.sia_client.accounts = self.sia_accounts
return
# the new client class method creates a subclass based on protocol, hence the type ignore
# the new client class method creates a subclass
# based on protocol, hence the type ignore
self.sia_client = SIAClient(
host="",
port=self._port,
@@ -119,7 +124,7 @@ class SIAHub:
)
def _load_options(self) -> None:
"""Store attributes to avoid property call overhead since they are called frequently."""
"""Store attributes to avoid property call overhead."""
options = dict(self._entry.options)
for acc in self._accounts:
acc_id = acc[CONF_ACCOUNT]
@@ -135,9 +140,10 @@ class SIAHub:
) -> None:
"""Handle signals of config entry being updated.
First, update the accounts, this will reflect any changes with ignore_timestamps.
Second, unload underlying platforms, and then setup platforms, this reflects any changes in number of zones.
First, update the accounts, this will reflect any
changes with ignore_timestamps. Second, unload
underlying platforms, and then setup platforms, this
reflects any changes in number of zones.
"""
if config_entry.state != ConfigEntryState.LOADED:
return
@@ -97,7 +97,7 @@ class SignalNotificationService(BaseNotificationService):
self._signal_cli_rest_api = signal_cli_rest_api
def send_message(self, message: str = "", **kwargs: Any) -> None:
"""Send a message to one or more recipients. Additionally a file can be attached."""
"""Send a message to one or more recipients."""
_LOGGER.debug("Sending signal message")
@@ -43,6 +43,8 @@ class SimpleFinDataUpdateCoordinator(DataUpdateCoordinator[FinancialData]):
except SimpleFinPaymentRequiredError as err:
LOGGER.warning(
"There is a billing issue with your SimpleFin account, contact Simplefin to address this issue"
"There is a billing issue with your SimpleFin"
" account, contact SimpleFin to address"
" this issue"
)
raise UpdateFailed from err
+5 -2
View File
@@ -350,8 +350,11 @@ class SlackNotificationService(BaseNotificationService):
channel_name = channel_name.lstrip("#")
# Get channel list
# Multiple types is not working. Tested here: https://api.slack.com/methods/conversations.list/test
# response = await self._client.conversations_list(types="public_channel,private_channel")
# Multiple types is not working. Tested here:
# https://api.slack.com/methods/conversations.list/test
# response = await self._client.conversations_list(
# types="public_channel,private_channel"
# )
#
# Workaround for the types parameter not working
channels = []
+4 -2
View File
@@ -24,12 +24,14 @@ async def upload_file_to_slack(
Args:
client (AsyncWebClient): The Slack WebClient instance.
channel_ids (list[str | None]): List of channel IDs to upload the file to.
file_content (Union[bytes, str, None]): Content of the file (local or remote). If None, file_path is used.
file_content (Union[bytes, str, None]): Content of the
file (local or remote). If None, file_path is used.
filename (str): The file's name.
title (str | None): Title of the file in Slack.
message (str): Initial comment to accompany the file.
thread_ts (str | None): Thread timestamp for threading messages.
file_path (str | None): Path to the local file to be read if file_content is None.
file_path (str | None): Path to the local file to be
read if file_content is None.
Raises:
SlackApiError: If the Slack API call fails.
+3 -1
View File
@@ -59,7 +59,9 @@ def _get_actuator_name(bed: SleepIQBed, actuator: SleepIQActuator) -> str:
if actuator.side:
return (
"SleepNumber"
f" {bed.name} {actuator.side_full} {actuator.actuator_full} {ENTITY_TYPES[ACTUATOR]}"
f" {bed.name} {actuator.side_full}"
f" {actuator.actuator_full}"
f" {ENTITY_TYPES[ACTUATOR]}"
)
return f"SleepNumber {bed.name} {actuator.actuator_full} {ENTITY_TYPES[ACTUATOR]}"
@@ -100,7 +100,8 @@ class SlideCoordinator(DataUpdateCoordinator[dict[str, Any]]):
if not self.config_entry.options.get(CONF_INVERT_POSITION, False):
# For slide 0->open, 1->closed; for HA 0->closed, 1->open
# Value has therefore to be inverted, unless CONF_INVERT_POSITION is true
# Value has therefore to be inverted,
# unless CONF_INVERT_POSITION is true
data["pos"] = 1 - data["pos"]
if oldpos is None or oldpos == data["pos"]:
+2 -1
View File
@@ -255,7 +255,8 @@ class SmaConfigFlow(ConfigFlow, domain=DOMAIN):
entry, data_updates={CONF_MAC: self._data[CONF_MAC]}
)
# Finally, check if the hostname (which represents the SMA serial number) is unique
# Finally, check if the hostname
# (which represents the SMA serial number) is unique
serial_number = discovery_info.hostname.lower()
# Example hostname: sma12345678-01
# Remove 'sma' prefix and strip everything after the dash (including the dash)
@@ -135,7 +135,8 @@ class SmappeeFlowHandler(
errors={},
)
# Environment chosen, request additional host information for LOCAL or OAuth2 flow for CLOUD
# Environment chosen, request additional host information
# for LOCAL or OAuth2 flow for CLOUD
# Ask for host detail
if user_input["environment"] == ENV_LOCAL:
return await self.async_step_local()
@@ -160,7 +161,8 @@ class SmappeeFlowHandler(
ip_address = user_input["host"]
serial_number = None
# Attempt 1: try to use the local api (older generation) to resolve host to serialnumber
# Attempt 1: try to use the local api (older generation)
# to resolve host to serialnumber
smappee_api = api.api.SmappeeLocalApi(ip=ip_address)
logon = await self.hass.async_add_executor_job(smappee_api.logon)
if logon is not None:
@@ -171,7 +173,8 @@ class SmappeeFlowHandler(
if config_item["key"] == "mdnsHostName":
serial_number = config_item["value"]
else:
# Attempt 2: try to use the local mqtt broker (newer generation) to resolve host to serialnumber
# Attempt 2: try to use the local mqtt broker
# (newer generation) to resolve host to serialnumber
smappee_mqtt = mqtt.SmappeeLocalMqtt()
connect = await self.hass.async_add_executor_job(smappee_mqtt.start_attempt)
if not connect:
@@ -156,7 +156,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: SmartThingsConfigEntry)
def _handle_max_connections() -> None:
_LOGGER.debug(
"We hit the limit of max connections or we could not remove the old one, so retrying"
"We hit the limit of max connections or we could"
" not remove the old one, so retrying"
)
hass.config_entries.async_schedule_reload(entry.entry_id)
@@ -335,7 +336,8 @@ async def async_migrate_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Handle config entry migration."""
if entry.version < 3:
# We keep the old data around, so we can use that to clean up the webhook in the future
# We keep the old data around, so we can use that
# to clean up the webhook in the future
hass.config_entries.async_update_entry(
entry, version=3, data={OLD_DATA: dict(entry.data)}
)
@@ -377,7 +379,12 @@ async def async_migrate_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"energySaved_meter",
}:
return {
"new_unique_id": f"{device_id}_{MAIN}_{Capability.POWER_CONSUMPTION_REPORT}_{Attribute.POWER_CONSUMPTION}_{attribute}",
"new_unique_id": (
f"{device_id}_{MAIN}"
f"_{Capability.POWER_CONSUMPTION_REPORT}"
f"_{Attribute.POWER_CONSUMPTION}"
f"_{attribute}"
),
}
if attribute in {
"X Coordinate",
@@ -390,7 +397,12 @@ async def async_migrate_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"Z Coordinate": "z_coordinate",
}[attribute]
return {
"new_unique_id": f"{device_id}_{MAIN}_{Capability.THREE_AXIS}_{Attribute.THREE_AXIS}_{new_attribute}",
"new_unique_id": (
f"{device_id}_{MAIN}"
f"_{Capability.THREE_AXIS}"
f"_{Attribute.THREE_AXIS}"
f"_{new_attribute}"
),
}
if attribute in {
Attribute.MACHINE_STATE,
@@ -402,16 +414,27 @@ async def async_migrate_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
if capability is None:
return None
return {
"new_unique_id": f"{device_id}_{MAIN}_{capability}_{attribute}_{attribute}",
"new_unique_id": (
f"{device_id}_{MAIN}"
f"_{capability}"
f"_{attribute}_{attribute}"
),
}
return None
return {
"new_unique_id": f"{device_id}_{MAIN}_{capability}_{attribute}_{attribute}",
"new_unique_id": (
f"{device_id}_{MAIN}_{capability}_{attribute}_{attribute}"
),
}
if entity_entry.domain == "switch":
return {
"new_unique_id": f"{entity_entry.unique_id}_{MAIN}_{Capability.SWITCH}_{Attribute.SWITCH}_{Attribute.SWITCH}",
"new_unique_id": (
f"{entity_entry.unique_id}_{MAIN}"
f"_{Capability.SWITCH}"
f"_{Attribute.SWITCH}"
f"_{Attribute.SWITCH}"
),
}
return None
@@ -328,7 +328,10 @@ class SmartThingsBinarySensor(SmartThingsEntity, BinarySensorEntity):
self._attribute = attribute
self.capability = capability
self.entity_description = entity_description
self._attr_unique_id = f"{device.device.device_id}_{component}_{capability}_{attribute}_{attribute}"
self._attr_unique_id = (
f"{device.device.device_id}_{component}"
f"_{capability}_{attribute}_{attribute}"
)
if (
entity_description.category_device_class
and (category := get_main_component_category(device))
@@ -167,7 +167,11 @@ class SmartThingsButtonEntity(SmartThingsEntity, ButtonEntity):
super().__init__(client, device, capabilities)
self.entity_description = entity_description
self.button_capability = capability
self._attr_unique_id = f"{device.device.device_id}_{component}_{entity_description.key}_{entity_description.command}"
self._attr_unique_id = (
f"{device.device.device_id}_{component}"
f"_{entity_description.key}"
f"_{entity_description.command}"
)
if entity_description.command_identifier is not None:
self._attr_unique_id += f"_{entity_description.command_identifier}"
@@ -434,8 +434,9 @@ class SmartThingsAirConditioner(SmartThingsEntity, ClimateEntity):
tasks.append(self.async_turn_on())
mode = STATE_TO_AC_MODE[hvac_mode]
# If new hvac_mode is HVAC_MODE_FAN_ONLY and AirConditioner support "wind" or "fan" mode the AirConditioner
# new mode has to be "wind" or "fan"
# If new hvac_mode is HVAC_MODE_FAN_ONLY and
# AirConditioner supports "wind" or "fan" mode,
# the AirConditioner new mode has to be "wind" or "fan"
if hvac_mode == HVACMode.FAN_ONLY:
for fan_mode in (WIND, FAN):
if fan_mode in self.get_attribute_value(
@@ -69,7 +69,9 @@ SENSOR_ATTRIBUTES_TO_CAPABILITIES: dict[str, str] = {
Attribute.DUST_LEVEL: Capability.DUST_SENSOR,
Attribute.FINE_DUST_LEVEL: Capability.DUST_SENSOR,
Attribute.ENERGY: Capability.ENERGY_METER,
Attribute.EQUIVALENT_CARBON_DIOXIDE_MEASUREMENT: Capability.EQUIVALENT_CARBON_DIOXIDE_MEASUREMENT,
Attribute.EQUIVALENT_CARBON_DIOXIDE_MEASUREMENT: (
Capability.EQUIVALENT_CARBON_DIOXIDE_MEASUREMENT
),
Attribute.FORMALDEHYDE_LEVEL: Capability.FORMALDEHYDE_MEASUREMENT,
Attribute.GAS_METER: Capability.GAS_METER,
Attribute.GAS_METER_CALORIFIC: Capability.GAS_METER,
+18 -3
View File
@@ -60,7 +60,12 @@ class SmartThingsWasherRinseCyclesNumberEntity(SmartThingsEntity, NumberEntity):
def __init__(self, client: SmartThings, device: FullDevice) -> None:
"""Initialize the instance."""
super().__init__(client, device, {Capability.CUSTOM_WASHER_RINSE_CYCLES})
self._attr_unique_id = f"{device.device.device_id}_{MAIN}_{Capability.CUSTOM_WASHER_RINSE_CYCLES}_{Attribute.WASHER_RINSE_CYCLES}_{Attribute.WASHER_RINSE_CYCLES}"
self._attr_unique_id = (
f"{device.device.device_id}_{MAIN}"
f"_{Capability.CUSTOM_WASHER_RINSE_CYCLES}"
f"_{Attribute.WASHER_RINSE_CYCLES}"
f"_{Attribute.WASHER_RINSE_CYCLES}"
)
@property
def options(self) -> list[int]:
@@ -112,7 +117,12 @@ class SmartThingsHoodNumberEntity(SmartThingsEntity, NumberEntity):
super().__init__(
client, device, {Capability.SAMSUNG_CE_HOOD_FAN_SPEED}, component="hood"
)
self._attr_unique_id = f"{device.device.device_id}_hood_{Capability.SAMSUNG_CE_HOOD_FAN_SPEED}_{Attribute.HOOD_FAN_SPEED}_{Attribute.HOOD_FAN_SPEED}"
self._attr_unique_id = (
f"{device.device.device_id}_hood"
f"_{Capability.SAMSUNG_CE_HOOD_FAN_SPEED}"
f"_{Attribute.HOOD_FAN_SPEED}"
f"_{Attribute.HOOD_FAN_SPEED}"
)
@property
def options(self) -> list[int]:
@@ -169,7 +179,12 @@ class SmartThingsRefrigeratorTemperatureNumberEntity(SmartThingsEntity, NumberEn
{Capability.THERMOSTAT_COOLING_SETPOINT},
component=component,
)
self._attr_unique_id = f"{device.device.device_id}_{component}_{Capability.THERMOSTAT_COOLING_SETPOINT}_{Attribute.COOLING_SETPOINT}_{Attribute.COOLING_SETPOINT}"
self._attr_unique_id = (
f"{device.device.device_id}_{component}"
f"_{Capability.THERMOSTAT_COOLING_SETPOINT}"
f"_{Attribute.COOLING_SETPOINT}"
f"_{Attribute.COOLING_SETPOINT}"
)
unit = self._internal_state[Capability.THERMOSTAT_COOLING_SETPOINT][
Attribute.COOLING_SETPOINT
].unit
+15 -8
View File
@@ -165,13 +165,15 @@ CAPABILITIES_TO_SELECT: dict[Capability | str, SmartThingsSelectDescription] = {
command=Command.SET_AMOUNT,
entity_category=EntityCategory.CONFIG,
),
Capability.SAMSUNG_CE_FLEXIBLE_AUTO_DISPENSE_DETERGENT: SmartThingsSelectDescription(
key=Capability.SAMSUNG_CE_FLEXIBLE_AUTO_DISPENSE_DETERGENT,
translation_key="flexible_detergent_amount",
options_attribute=Attribute.SUPPORTED_AMOUNT,
status_attribute=Attribute.AMOUNT,
command=Command.SET_AMOUNT,
entity_category=EntityCategory.CONFIG,
Capability.SAMSUNG_CE_FLEXIBLE_AUTO_DISPENSE_DETERGENT: (
SmartThingsSelectDescription(
key=Capability.SAMSUNG_CE_FLEXIBLE_AUTO_DISPENSE_DETERGENT,
translation_key="flexible_detergent_amount",
options_attribute=Attribute.SUPPORTED_AMOUNT,
status_attribute=Attribute.AMOUNT,
command=Command.SET_AMOUNT,
entity_category=EntityCategory.CONFIG,
)
),
Capability.SAMSUNG_CE_LAMP: SmartThingsSelectDescription(
key=Capability.SAMSUNG_CE_LAMP,
@@ -361,7 +363,12 @@ class SmartThingsSelectEntity(SmartThingsEntity, SelectEntity):
capabilities.update(extra_capabilities)
super().__init__(client, device, capabilities, component=component)
self.entity_description = entity_description
self._attr_unique_id = f"{device.device.device_id}_{component}_{entity_description.key}_{entity_description.status_attribute}_{entity_description.status_attribute}"
self._attr_unique_id = (
f"{device.device.device_id}_{component}"
f"_{entity_description.key}"
f"_{entity_description.status_attribute}"
f"_{entity_description.status_attribute}"
)
@property
def options(self) -> list[str]:
@@ -1319,7 +1319,9 @@ async def async_setup_entry(
capability in device.status[MAIN]
for capability in capability_list
)
for capability_list in description.capability_ignore_list
for capability_list in (
description.capability_ignore_list
)
)
)
and (
@@ -1401,7 +1403,11 @@ class SmartThingsSensor(SmartThingsEntity, SensorEntity):
if entity_description.use_temperature_unit:
capabilities_to_subscribe.add(Capability.TEMPERATURE_MEASUREMENT)
super().__init__(client, device, capabilities_to_subscribe, component=component)
self._attr_unique_id = f"{device.device.device_id}_{component}_{capability}_{attribute}_{entity_description.key}"
self._attr_unique_id = (
f"{device.device.device_id}_{component}"
f"_{capability}_{attribute}"
f"_{entity_description.key}"
)
self._attribute = attribute
self.capability = capability
self.entity_description = entity_description
+63 -44
View File
@@ -85,12 +85,14 @@ CAPABILITY_TO_COMMAND_SWITCHES: dict[
command=Command.SET_SPI_MODE,
entity_category=EntityCategory.CONFIG,
),
Capability.SAMSUNG_CE_AIR_CONDITIONER_LIGHTING: SmartThingsCommandSwitchEntityDescription(
key=Capability.SAMSUNG_CE_AIR_CONDITIONER_LIGHTING,
translation_key="display_lighting",
status_attribute=Attribute.LIGHTING,
command=Command.SET_LIGHTING_LEVEL,
entity_category=EntityCategory.CONFIG,
Capability.SAMSUNG_CE_AIR_CONDITIONER_LIGHTING: (
SmartThingsCommandSwitchEntityDescription(
key=Capability.SAMSUNG_CE_AIR_CONDITIONER_LIGHTING,
translation_key="display_lighting",
status_attribute=Attribute.LIGHTING,
command=Command.SET_LIGHTING_LEVEL,
entity_category=EntityCategory.CONFIG,
)
),
Capability.CUSTOM_DRYER_WRINKLE_PREVENT: SmartThingsCommandSwitchEntityDescription(
key=Capability.CUSTOM_DRYER_WRINKLE_PREVENT,
@@ -99,21 +101,25 @@ CAPABILITY_TO_COMMAND_SWITCHES: dict[
command=Command.SET_DRYER_WRINKLE_PREVENT,
entity_category=EntityCategory.CONFIG,
),
Capability.SAMSUNG_CE_STEAM_CLOSET_AUTO_CYCLE_LINK: SmartThingsCommandSwitchEntityDescription(
key=Capability.SAMSUNG_CE_STEAM_CLOSET_AUTO_CYCLE_LINK,
translation_key="auto_cycle_link",
status_attribute=Attribute.STEAM_CLOSET_AUTO_CYCLE_LINK,
command=Command.SET_STEAM_CLOSET_AUTO_CYCLE_LINK,
entity_category=EntityCategory.CONFIG,
Capability.SAMSUNG_CE_STEAM_CLOSET_AUTO_CYCLE_LINK: (
SmartThingsCommandSwitchEntityDescription(
key=Capability.SAMSUNG_CE_STEAM_CLOSET_AUTO_CYCLE_LINK,
translation_key="auto_cycle_link",
status_attribute=Attribute.STEAM_CLOSET_AUTO_CYCLE_LINK,
command=Command.SET_STEAM_CLOSET_AUTO_CYCLE_LINK,
entity_category=EntityCategory.CONFIG,
)
),
Capability.SAMSUNG_CE_MICROFIBER_FILTER_SETTINGS: SmartThingsCommandSwitchEntityDescription(
key=Capability.SAMSUNG_CE_MICROFIBER_FILTER_SETTINGS,
translation_key="bypass_mode",
status_attribute=Attribute.BYPASS_MODE,
entity_category=EntityCategory.CONFIG,
on_key="enabled",
off_key="disabled",
command=Command.SET_BYPASS_MODE,
Capability.SAMSUNG_CE_MICROFIBER_FILTER_SETTINGS: (
SmartThingsCommandSwitchEntityDescription(
key=Capability.SAMSUNG_CE_MICROFIBER_FILTER_SETTINGS,
translation_key="bypass_mode",
status_attribute=Attribute.BYPASS_MODE,
entity_category=EntityCategory.CONFIG,
on_key="enabled",
off_key="disabled",
command=Command.SET_BYPASS_MODE,
)
),
}
CAPABILITY_TO_SWITCHES: dict[Capability | str, SmartThingsSwitchEntityDescription] = {
@@ -164,17 +170,21 @@ CAPABILITY_TO_SWITCHES: dict[Capability | str, SmartThingsSwitchEntityDescriptio
off_command=Command.DEACTIVATE,
entity_category=EntityCategory.CONFIG,
),
Capability.SAMSUNG_CE_STEAM_CLOSET_SANITIZE_MODE: SmartThingsSwitchEntityDescription(
key=Capability.SAMSUNG_CE_STEAM_CLOSET_SANITIZE_MODE,
translation_key="sanitize",
status_attribute=Attribute.STATUS,
entity_category=EntityCategory.CONFIG,
Capability.SAMSUNG_CE_STEAM_CLOSET_SANITIZE_MODE: (
SmartThingsSwitchEntityDescription(
key=Capability.SAMSUNG_CE_STEAM_CLOSET_SANITIZE_MODE,
translation_key="sanitize",
status_attribute=Attribute.STATUS,
entity_category=EntityCategory.CONFIG,
)
),
Capability.SAMSUNG_CE_STEAM_CLOSET_KEEP_FRESH_MODE: SmartThingsSwitchEntityDescription(
key=Capability.SAMSUNG_CE_STEAM_CLOSET_KEEP_FRESH_MODE,
translation_key="keep_fresh_mode",
status_attribute=Attribute.STATUS,
entity_category=EntityCategory.CONFIG,
Capability.SAMSUNG_CE_STEAM_CLOSET_KEEP_FRESH_MODE: (
SmartThingsSwitchEntityDescription(
key=Capability.SAMSUNG_CE_STEAM_CLOSET_KEEP_FRESH_MODE,
translation_key="keep_fresh_mode",
status_attribute=Attribute.STATUS,
entity_category=EntityCategory.CONFIG,
)
),
Capability.CUSTOM_DO_NOT_DISTURB_MODE: SmartThingsSwitchEntityDescription(
key=Capability.CUSTOM_DO_NOT_DISTURB_MODE,
@@ -193,13 +203,15 @@ CAPABILITY_TO_SWITCHES: dict[Capability | str, SmartThingsSwitchEntityDescriptio
on_command=Command.ENABLE_SOUND_DETECTION,
off_command=Command.DISABLE_SOUND_DETECTION,
),
Capability.SAMSUNG_CE_STICK_CLEANER_DUSTBIN_STATUS: SmartThingsSwitchEntityDescription(
key=Capability.SAMSUNG_CE_STICK_CLEANER_DUSTBIN_STATUS,
translation_key="empty_dustbin",
status_attribute=Attribute.OPERATING_STATE,
on_key="emptying",
on_command=Command.START_EMPTYING,
off_command=Command.STOP_EMPTYING,
Capability.SAMSUNG_CE_STICK_CLEANER_DUSTBIN_STATUS: (
SmartThingsSwitchEntityDescription(
key=Capability.SAMSUNG_CE_STICK_CLEANER_DUSTBIN_STATUS,
translation_key="empty_dustbin",
status_attribute=Attribute.OPERATING_STATE,
on_key="emptying",
on_command=Command.START_EMPTYING,
off_command=Command.STOP_EMPTYING,
)
),
}
DISHWASHER_WASHING_OPTIONS_TO_SWITCHES: dict[
@@ -261,12 +273,14 @@ DISHWASHER_WASHING_OPTIONS_TO_SWITCHES: dict[
command=Command.SET_SANITIZE,
entity_category=EntityCategory.CONFIG,
),
Attribute.SANITIZING_WASH: SmartThingsDishwasherWashingOptionSwitchEntityDescription(
key=Attribute.SANITIZING_WASH,
translation_key="sanitizing_wash",
status_attribute=Attribute.SANITIZING_WASH,
command=Command.SET_SANITIZING_WASH,
entity_category=EntityCategory.CONFIG,
Attribute.SANITIZING_WASH: (
SmartThingsDishwasherWashingOptionSwitchEntityDescription(
key=Attribute.SANITIZING_WASH,
translation_key="sanitizing_wash",
status_attribute=Attribute.SANITIZING_WASH,
command=Command.SET_SANITIZING_WASH,
entity_category=EntityCategory.CONFIG,
)
),
Attribute.SPEED_BOOSTER: SmartThingsDishwasherWashingOptionSwitchEntityDescription(
key=Attribute.SPEED_BOOSTER,
@@ -427,7 +441,12 @@ class SmartThingsSwitch(SmartThingsEntity, SwitchEntity):
)
self.entity_description = entity_description
self.switch_capability = capability
self._attr_unique_id = f"{device.device.device_id}_{component}_{capability}_{entity_description.status_attribute}_{entity_description.status_attribute}"
self._attr_unique_id = (
f"{device.device.device_id}_{component}"
f"_{capability}"
f"_{entity_description.status_attribute}"
f"_{entity_description.status_attribute}"
)
if (
translation_keys := entity_description.component_translation_key
) is not None and (
+9 -2
View File
@@ -67,7 +67,12 @@ class SmartThingsDnDTime(SmartThingsEntity, TimeEntity):
"""Initialize the time entity."""
super().__init__(client, device, {Capability.CUSTOM_DO_NOT_DISTURB_MODE})
self.entity_description = entity_description
self._attr_unique_id = f"{device.device.device_id}_{MAIN}_{Capability.CUSTOM_DO_NOT_DISTURB_MODE}_{entity_description.attribute}_{entity_description.attribute}"
self._attr_unique_id = (
f"{device.device.device_id}_{MAIN}"
f"_{Capability.CUSTOM_DO_NOT_DISTURB_MODE}"
f"_{entity_description.attribute}"
f"_{entity_description.attribute}"
)
async def async_set_value(self, value: time) -> None:
"""Set the time value."""
@@ -87,7 +92,9 @@ class SmartThingsDnDTime(SmartThingsEntity, TimeEntity):
Command.SET_DO_NOT_DISTURB_MODE,
{
**payload,
self.entity_description.attribute: f"{value.hour:02d}{value.minute:02d}",
self.entity_description.attribute: (
f"{value.hour:02d}{value.minute:02d}"
),
},
)
@@ -93,7 +93,10 @@ async def async_setup_entry(
class SmartTubOnline(SmartTubOnboardSensorBase, BinarySensorEntity):
"""A binary sensor indicating whether the spa is currently online (connected to the cloud)."""
"""A binary sensor indicating whether the spa is online.
Indicates if it is connected to the cloud.
"""
_attr_device_class = BinarySensorDeviceClass.CONNECTIVITY
# This seems to be very noisy and not generally useful, so disable by default.
+1 -1
View File
@@ -101,5 +101,5 @@ class SmartTubExternalSensorBase(SmartTubEntity):
@property
def sensor(self) -> SpaSensor:
"""Convenience property to access the smarttub.SpaSensor instance for this sensor."""
"""Access the smarttub.SpaSensor instance for this sensor."""
return self.coordinator.data[self.spa.id][ATTR_SENSORS][self.sensor_address]
+4 -1
View File
@@ -23,7 +23,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: SMHIConfigEntry) -> bool
# Setting unique id where missing
if entry.unique_id is None:
unique_id = f"{entry.data[CONF_LOCATION][CONF_LATITUDE]}-{entry.data[CONF_LOCATION][CONF_LONGITUDE]}"
unique_id = (
f"{entry.data[CONF_LOCATION][CONF_LATITUDE]}"
f"-{entry.data[CONF_LOCATION][CONF_LONGITUDE]}"
)
hass.config_entries.async_update_entry(entry, unique_id=unique_id)
coordinator = SMHIDataUpdateCoordinator(hass, entry)
+4 -1
View File
@@ -25,5 +25,8 @@ class SmEntity(CoordinatorEntity[SmBaseDataUpdateCoordinator]):
connections={(CONNECTION_NETWORK_MAC, mac)},
manufacturer=ATTR_MANUFACTURER,
model=coordinator.data.info.model,
sw_version=f"core: {coordinator.data.info.sw_version} / zigbee: {coordinator.data.info.zb_version}",
sw_version=(
f"core: {coordinator.data.info.sw_version}"
f" / zigbee: {coordinator.data.info.zb_version}"
),
)
@@ -249,7 +249,7 @@ class SnapcastClientDevice(SnapcastCoordinatorEntity, MediaPlayerEntity):
@property
def group_members(self) -> list[str] | None:
"""List of player entities which are currently grouped together for synchronous playback."""
"""List of players currently grouped for synchronous playback."""
if self._current_group is None:
return None
@@ -298,7 +298,8 @@ class SnapcastClientDevice(SnapcastCoordinatorEntity, MediaPlayerEntity):
# Validate client belongs to the same server
if not client.unique_id.startswith(unique_id_prefix):
raise ServiceValidationError(
f"Entity '{client.entity_id}' does not belong to the same Snapcast server."
f"Entity '{client.entity_id}' does not belong"
" to the same Snapcast server."
)
# Extract client ID and join it to the current group
@@ -307,7 +308,8 @@ class SnapcastClientDevice(SnapcastCoordinatorEntity, MediaPlayerEntity):
await self._current_group.add_client(identifier)
except KeyError as e:
raise ServiceValidationError(
f"Client with identifier '{identifier}' does not exist on the server."
f"Client with identifier '{identifier}'"
" does not exist on the server."
) from e
self.async_write_ha_state()
@@ -102,7 +102,7 @@ class SnmpScanner(DeviceScanner):
@classmethod
async def create(cls, config):
"""Asynchronously test the target device before fully initializing the scanner."""
"""Test the target device before fully initializing."""
host = config[CONF_HOST]
try:
@@ -125,7 +125,7 @@ class SnmpScanner(DeviceScanner):
return instance
async def async_init(self, hass: HomeAssistant) -> None:
"""Make a one-off read to check if the target device is reachable and readable."""
"""Check if the target device is reachable and readable."""
self.request_args = await async_create_request_cmd_args(
hass,
self._auth_data,
@@ -146,7 +146,7 @@ class SnmpScanner(DeviceScanner):
return None
async def async_get_extra_attributes(self, device: str) -> dict:
"""Return the extra attributes of the given device or an empty dictionary if we have none."""
"""Return extra attributes of the given device."""
for client in self.last_results:
if client.get("mac") and device == client["mac"]:
return {"mac": client["mac"]}
@@ -115,7 +115,8 @@ class SolarEdgeOverviewDataService(SolarEdgeDataService):
data = value
self.data[key] = data
# Sanity check the energy values. SolarEdge API sometimes report "lifetimedata" of zero,
# Sanity check the energy values. SolarEdge API sometimes
# reports "lifetimedata" of zero,
# while values for last Year, Month and Day energy are still OK.
# See https://github.com/home-assistant/core/issues/59285 .
if set(energy_keys).issubset(self.data.keys()):
@@ -453,8 +454,9 @@ class SolarEdgeModulesCoordinator(DataUpdateCoordinator[None]):
async def _async_update_data(self) -> None:
"""Fetch data from API endpoint and update statistics."""
equipment: dict[int, dict[str, Any]] = await self.api.async_get_equipment()
# We fetch last week's data from the API and refresh every 12h so we overwrite recent
# statistics. This is intended to allow adding any corrected/updated data from the API.
# We fetch last week's data from the API and refresh
# every 12h so we overwrite recent statistics. This is
# intended to allow adding any corrected/updated data.
energy_data_list: list[EnergyData] = await self.api.async_get_energy_data(
TimeUnit.WEEK
)
@@ -545,9 +547,10 @@ class SolarEdgeModulesCoordinator(DataUpdateCoordinator[None]):
if statistic_id in current_stats:
statistic_sum = current_stats[statistic_id][0]["sum"]
else:
# If no statistics found right before start_time, try to get the last statistic
# but use it only if it's before start_time.
# This is needed if the integration hasn't run successfully for at least a week.
# If no statistics found right before start_time,
# try to get the last statistic but use it only
# if it's before start_time. This is needed if
# the integration hasn't run for at least a week.
last_stat = await get_instance(self.hass).async_add_executor_job(
get_last_statistics, self.hass, 1, statistic_id, True, {"sum"}
)
@@ -74,7 +74,8 @@ class SolarLogBasicDataCoordinator(DataUpdateCoordinator[SolarlogData]):
) from ex
except SolarLogAuthenticationError as ex:
if await self.renew_authentication():
# login was successful, update availability of extended data, retry data update
# login was successful, update availability
# of extended data, retry data update
await self.solarlog.test_extended_data_available()
raise ConfigEntryNotReady(
translation_domain=DOMAIN,
@@ -258,7 +259,9 @@ class SolarLogLongtimeDataCoordinator(DataUpdateCoordinator[EnergyData]):
if energy_data is None:
energy_data = EnergyData(None, None)
self.config_entry.runtime_data.basic_data_coordinator.data.self_consumption_year = energy_data.self_consumption
(
self.config_entry.runtime_data.basic_data_coordinator.data.self_consumption_year
) = energy_data.self_consumption
_LOGGER.debug("Energy data successfully updated")
+2 -1
View File
@@ -50,7 +50,8 @@ class SolarLogInverterEntity(CoordinatorEntity[SolarLogDeviceDataCoordinator]):
) -> None:
"""Initialize the SolarLogInverter sensor."""
super().__init__(coordinator)
name = f"{coordinator.config_entry.entry_id}_{slugify(coordinator.solarlog.device_name(device_id))}"
device_name = coordinator.solarlog.device_name(device_id)
name = f"{coordinator.config_entry.entry_id}_{slugify(device_name)}"
self._attr_unique_id = f"{name}_{description.key}"
self._attr_device_info = DeviceInfo(
manufacturer="Solar-Log",
+5 -3
View File
@@ -261,8 +261,9 @@ SOLARLOG_BASIC_SENSOR_TYPES: tuple[SolarLogCoordinatorSensorEntityDescription, .
),
)
"""SOLARLOG_LONGTIME_SENSOR_TYPES represent data points that may require longer timeout and
therefore are retrieved with different DataUpdateCoordinator."""
"""SOLARLOG_LONGTIME_SENSOR_TYPES represent data points that
may require longer timeout and therefore are retrieved with
different DataUpdateCoordinator."""
SOLARLOG_LONGTIME_SENSOR_TYPES: tuple[SolarLogLongtimeSensorEntityDescription, ...] = (
SolarLogLongtimeSensorEntityDescription(
key="self_consumption_year",
@@ -351,7 +352,8 @@ async def async_setup_entry(
for sensor in SOLARLOG_LONGTIME_SENSOR_TYPES
)
# add battery sensors only if respective data is available (otherwise no battery attached to solarlog)
# add battery sensors only if respective data is
# available (otherwise no battery attached to solarlog)
if solarLogIntegrationData.basic_data_coordinator.data.battery_data is not None:
entities.extend(
SolarLogBatterySensor(
+2 -1
View File
@@ -30,7 +30,8 @@ async def async_setup_entry(
entities: list[SomaTilt | SomaShade] = []
for device in data.devices:
# Assume a shade device if the type is not present in the api response (Connect <2.2.6)
# Assume a shade device if the type is not present
# in the api response (Connect <2.2.6)
if "type" in device and device["type"].lower() == "tilt":
entities.append(SomaTilt(device, api))
else:
+10 -4
View File
@@ -46,7 +46,8 @@ def format_queue_item(item: Any, base_url: str | None = None) -> dict[str, Any]:
if episode := getattr(item, "episode", None):
result["episode_number"] = getattr(episode, "episodeNumber", None)
result["episode_title"] = getattr(episode, "title", None)
# Add formatted identifier like the sensor uses (if we have both season and episode)
# Add formatted identifier like the sensor uses
# (if we have both season and episode)
if result["season_number"] is not None and result["episode_number"] is not None:
result["episode_identifier"] = (
f"S{result['season_number']:02d}E{result['episode_number']:02d}"
@@ -197,7 +198,8 @@ def format_diskspace(
Args:
disks: List of disk space objects from Sonarr.
space_unit: Unit for space values (bytes, kb, kib, mb, mib, gb, gib, tb, tib, pb, pib).
space_unit: Unit for space values
(bytes, kb, kib, mb, mib, gb, gib, tb, tib, pb, pib).
Returns:
Dictionary of disk information keyed by path.
@@ -244,7 +246,9 @@ def format_upcoming_item(
"series_id": episode.seriesId,
"season_number": episode.seasonNumber,
"episode_number": episode.episodeNumber,
"episode_identifier": f"S{episode.seasonNumber:02d}E{episode.episodeNumber:02d}",
"episode_identifier": (
f"S{episode.seasonNumber:02d}E{episode.episodeNumber:02d}"
),
"title": episode.title,
"air_date": str(getattr(episode, "airDate", None)),
"air_date_utc": str(getattr(episode, "airDateUtc", None)),
@@ -341,7 +345,9 @@ def format_episode(episode: SonarrEpisode) -> dict[str, Any]:
"tvdb_id": getattr(episode, "tvdbId", None),
"season_number": episode.seasonNumber,
"episode_number": episode.episodeNumber,
"episode_identifier": f"S{episode.seasonNumber:02d}E{episode.episodeNumber:02d}",
"episode_identifier": (
f"S{episode.seasonNumber:02d}E{episode.episodeNumber:02d}"
),
"title": episode.title,
"air_date": str(getattr(episode, "airDate", None)),
"air_date_utc": str(getattr(episode, "airDateUtc", None)),
@@ -343,7 +343,8 @@ class SongpalEntity(MediaPlayerEntity):
def sound_mode_list(self) -> list[str] | None:
"""Return list of available sound modes.
When active mode is None it means that sound mode is unavailable on the sound bar.
When active mode is None it means that sound mode is
unavailable on the sound bar.
Can be due to incompatible sound bar or the sound bar is in a mode that does not
support sound mode changes.
"""
+14 -8
View File
@@ -218,13 +218,15 @@ class SonosDiscoveryManager:
_ = IPv4Address(ip_address)
except AddressValueError:
_LOGGER.debug(
"Sonos integration only supports IPv4 addresses, invalid ip_address received: %s",
"Sonos integration only supports IPv4 addresses,"
" invalid ip_address received: %s",
ip_address,
)
return
soco = SoCo(ip_address)
try:
# Cache now to avoid household ID lookup during first ZoneGroupState processing
# Cache now to avoid household ID lookup during
# first ZoneGroupState processing
await self.hass.async_add_executor_job(
getattr,
soco,
@@ -426,7 +428,8 @@ class SonosDiscoveryManager:
) -> None:
"""Add and maintain Sonos devices from a manual configuration."""
# Loop through each configured host and verify that Soco attributes are available for it.
# Loop through each configured host and verify that
# Soco attributes are available for it.
for host in self.hosts.copy():
ip_addr = await self.hass.async_add_executor_job(socket.gethostbyname, host)
soco = SoCo(ip_addr)
@@ -457,8 +460,9 @@ class SonosDiscoveryManager:
if self.hosts_in_error.pop(ip_addr, None):
_LOGGER.warning("Connection reestablished to Sonos device %s", ip_addr)
# Each speaker has the topology for other online speakers, so add them in here if they were not
# configured. The metadata is already in Soco for these.
# Each speaker has the topology for other online
# speakers, so add them in here if they were not
# configured. The metadata is already in Soco.
if new_hosts := {
x.ip_address for x in visible_zones if x.ip_address not in self.hosts
}:
@@ -469,12 +473,14 @@ class SonosDiscoveryManager:
_LOGGER.debug("Discarding %s from manual hosts", ip_addr)
self.hosts.discard(ip_addr)
# Loop through each configured host that is not in error. Send a discovery message
# if a speaker does not already exist, or ping the speaker if it is unavailable.
# Loop through each configured host that is not in
# error. Send a discovery message if a speaker does
# not already exist, or ping if it is unavailable.
for host in self.hosts.copy():
ip_addr = await self.hass.async_add_executor_job(socket.gethostbyname, host)
soco = SoCo(ip_addr)
# Skip hosts that are in error to avoid blocking call on soco.uuid in event loop
# Skip hosts that are in error to avoid blocking
# call on soco.uuid in event loop
if self.hosts_in_error.get(ip_addr):
continue
known_speaker = next(
+8 -4
View File
@@ -89,10 +89,14 @@ class SonosAlarms(SonosHouseholdCoordinator):
if "Alarm list UID" in err_msg and "does not match" in err_msg:
if not self._household_mismatch_logged:
_LOGGER.warning(
"Sonos alarms for %s cannot be updated due to a household mismatch. "
"This is a known limitation in setups with multiple households. "
"You can safely ignore this warning, or to silence it, remove the "
"affected household from your Sonos system. Error: %s",
"Sonos alarms for %s cannot be updated"
" due to a household mismatch. "
"This is a known limitation in setups"
" with multiple households. "
"You can safely ignore this warning,"
" or to silence it, remove the "
"affected household from your Sonos"
" system. Error: %s",
soco.player_name,
err_msg,
)
+1 -1
View File
@@ -107,7 +107,7 @@ class SonosEntity(Entity):
class SonosPollingEntity(SonosEntity):
"""Representation of a Sonos entity which may not support updating by subscriptions."""
"""Representation of a Sonos entity without subscription support."""
@abstractmethod
def poll_state(self) -> None:
+1 -1
View File
@@ -93,7 +93,7 @@ def soco_error[_T: _SonosEntitiesType, **_P, _R](
def _find_target_identifier(instance: Any, fallback_soco: SoCo | None) -> str | None:
"""Extract the best available target identifier from the provided instance object."""
"""Extract the best target identifier from the instance."""
if entity_id := getattr(instance, "entity_id", None):
# SonosEntity instance
return entity_id
@@ -81,7 +81,10 @@ class SonosHouseholdCoordinator:
raise NotImplementedError
def update_cache(self, soco: SoCo, update_id: int | None = None) -> bool:
"""Update the cache of the household-level feature and return if cache has changed."""
"""Update the household-level feature cache.
Return if cache has changed.
"""
raise NotImplementedError
def add_speaker(self, soco: SoCo) -> None:
@@ -46,9 +46,11 @@ type GetBrowseImageUrlType = Callable[[str, str, str | None], str]
def fix_image_url(url: str) -> str:
"""Update the image url to fully encode characters to allow image display in media_browser UI.
"""Update the image url to fully encode characters.
Images whose file path contains characters such as ',()+ are not loaded without escaping them.
This allows image display in media_browser UI. Images
whose file path contains characters such as ',()+ are
not loaded without escaping them.
"""
# Before parsing encode the plus sign; otherwise it'll be interpreted as a space.
@@ -108,7 +110,8 @@ def _get_title(id_string: str) -> str:
"""Extract a suitable title from the content id string."""
if id_string.startswith("S:"):
# Format is S://server/share/folder
# If just S: this will be in the mappings; otherwise use the last folder in path.
# If just S: this will be in the mappings;
# otherwise use the last folder in path.
title = LIBRARY_TITLES_MAPPING.get(
id_string, urllib.parse.unquote(id_string.rsplit("/", maxsplit=1)[-1])
)
@@ -621,9 +624,10 @@ def get_media(
)
matches = [result]
else:
# When requesting media by album_artist, composer, genre use the browse interface
# to navigate the hierarchy. This occurs when invoked from media browser or service
# calls
# When requesting media by album_artist, composer,
# genre use the browse interface to navigate the
# hierarchy. This occurs when invoked from media
# browser or service calls
# Example: A:ALBUMARTIST/Neil Young/Greatest Hits - get specific album
# Example: A:ALBUMARTIST/Neil Young - get all albums
# Others: composer, genre
@@ -839,15 +839,17 @@ class SonosMediaPlayerEntity(SonosEntity, MediaPlayerEntity):
async def async_unjoin_player(self) -> None:
"""Remove this player from any group.
Coalesces all calls within UNJOIN_SERVICE_TIMEOUT to allow use of SonosSpeaker.unjoin_multi()
which optimizes the order in which speakers are removed from their groups.
Removing coordinators last better preserves playqueues on the speakers.
Coalesces all calls within UNJOIN_SERVICE_TIMEOUT to
allow use of SonosSpeaker.unjoin_multi() which
optimizes the order in which speakers are removed
from their groups. Removing coordinators last better
preserves playqueues on the speakers.
"""
sonos_data = self.config_entry.runtime_data
household_id = self.speaker.household_id
async def async_process_unjoin(now: datetime.datetime) -> None:
"""Process the unjoin with all remove requests within the coalescing period."""
"""Process the unjoin with all remove requests."""
unjoin_data = sonos_data.unjoin_data.pop(household_id)
_LOGGER.debug(
"Processing unjoins for %s", [x.zone_name for x in unjoin_data.speakers]
+10 -6
View File
@@ -351,7 +351,7 @@ class SonosSpeaker:
def log_subscription_result(
self, result: Any, event: str, level: int = logging.DEBUG
) -> None:
"""Log a message if a subscription action (create/renew/stop) results in an exception."""
"""Log if a subscription action results in an exception."""
if not isinstance(result, Exception):
return
@@ -966,7 +966,8 @@ class SonosSpeaker:
new_members = set(sonos_group[1:])
removed_members = old_members - new_members
for removed_speaker in removed_members:
# Only clear if this speaker was coordinated by self and in the same group
# Only clear if this speaker was coordinated
# by self and in the same group
if (
removed_speaker.coordinator == self
and removed_speaker.sonos_group is self.sonos_group
@@ -1176,10 +1177,12 @@ class SonosSpeaker:
if not with_group:
return groups
# Unjoin non-coordinator speakers not contained in the desired snapshot group
# Unjoin non-coordinator speakers not contained in
# the desired snapshot group
#
# If a coordinator is unjoined from its group, another speaker from the group
# will inherit the coordinator's playqueue and its own playqueue will be lost
# If a coordinator is unjoined from its group,
# another speaker from the group will inherit the
# coordinator's playqueue and its own will be lost
speakers_to_unjoin = set()
for speaker in speakers:
if speaker.sonos_group == speaker.snapshot_group:
@@ -1265,7 +1268,8 @@ class SonosSpeaker:
await config_entry.runtime_data.topology_condition.wait()
except TimeoutError:
group_description = "; ".join(
f"{group[0].zone_name}: {', '.join(speaker.zone_name for speaker in group)}"
f"{group[0].zone_name}: "
f"{', '.join(speaker.zone_name for speaker in group)}"
for group in groups
)
raise HomeAssistantError(
+3 -2
View File
@@ -155,7 +155,7 @@ async def async_setup_entry(
def _get_switch_state(
speaker: SonosSpeaker,
) -> tuple[list[str], str | None, bool | None]:
"""Return all switch state needed for entity creation in a single executor call."""
"""Return all switch state for entity creation."""
return (
available_soco_attributes(speaker),
_get_tv_autoplay_state(speaker),
@@ -399,7 +399,8 @@ class SonosTVUngroupAutoplaySwitchEntity(SonosPollingEntity, SwitchEntity):
"""Enable or disable ungroup on autoplay on the device."""
try:
self.soco.deviceProperties.SetAutoplayLinkedZones(
# enable=True (ungroup) → IncludeLinkedZones=0 (don't include linked zones)
# enable=True (ungroup) → IncludeLinkedZones=0
# (don't include linked zones)
[("IncludeLinkedZones", "0" if enable else "1"), *_TV_SOURCE]
)
except SoCoUPnPException as exc:
@@ -393,7 +393,7 @@ class SoundTouchMediaPlayer(MediaPlayerEntity):
return None
def _get_instance_by_id(self, instance_id):
"""Search and return a SoundTouchDevice instance by it's ID (aka MAC address)."""
"""Search and return a SoundTouchDevice by its ID."""
for entry in self.hass.config_entries.async_loaded_entries(DOMAIN):
data = entry.runtime_data
if data.device.config.device_id == instance_id:
@@ -121,8 +121,9 @@ class SpotifyCoordinator(DataUpdateCoordinator[SpotifyCoordinatorData]):
position_updated_at=None,
playlist=None,
)
# Record the last updated time, because Spotify's timestamp property is unreliable
# and doesn't actually return the fetch time as is mentioned in the API description
# Record the last updated time, because Spotify's
# timestamp property is unreliable and doesn't
# actually return the fetch time as described in API
position_updated_at = dt_util.utcnow()
dj_playlist = False
+2 -1
View File
@@ -121,7 +121,8 @@ def validate_query(
Args:
hass: The Home Assistant instance.
query_template: The SQL query string to be validated.
uses_recorder_db: A boolean indicating if the query is against the recorder database.
uses_recorder_db: A boolean indicating if the query is
against the recorder database.
unique_id: The unique ID of the entity, used for creating issue registry keys.
Raises:
@@ -140,7 +140,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: SqueezeboxConfigEntry) -
},
)
# For other errors where status is None (e.g., server error, connection refused by server)
# For other errors where status is None
# (e.g., server error, connection refused by server)
_LOGGER.warning(
"LMS %s returned no status or an error (HTTP status: %s). Retrying setup",
host,
@@ -324,10 +324,12 @@ async def build_item_response(
child_media = _build_response_favorites(item)
elif search_type in ["apps", "radios"]:
# item["cmd"] contains the name of the command to use with the cli for the app
# item["cmd"] contains the name of the command
# to use with the cli for the app;
# add the command to the dictionaries
if item["title"] == "Search" or item.get("type") in UNPLAYABLE_TYPES:
# Skip searches in apps as they'd need UI or if the link isn't to audio
# Skip searches in apps as they'd need UI
# or if the link isn't to audio
continue
app_cmd = "app-" + item["cmd"]
@@ -287,7 +287,10 @@ class SqueezeboxConfigFlow(ConfigFlow, domain=DOMAIN):
return self.async_show_form(
step_id="edit_integration_discovered",
description_placeholders={
"desc": f"LMS Host: {self.chosen_server[CONF_HOST]}, Port: {self.chosen_server[CONF_PORT]}"
"desc": (
f"LMS Host: {self.chosen_server[CONF_HOST]},"
f" Port: {self.chosen_server[CONF_PORT]}"
)
},
data_schema=SHORT_EDIT_SCHEMA,
errors=errors,
@@ -330,7 +333,10 @@ class SqueezeboxConfigFlow(ConfigFlow, domain=DOMAIN):
if TYPE_CHECKING:
assert self.unique_id
# if we have detected this player, do nothing. if not, there must be a server out there for us to configure, so start the normal user flow (which tries to autodetect server)
# if we have detected this player, do nothing. if not,
# there must be a server out there for us to configure,
# so start the normal user flow (which tries to
# autodetect server)
if registry.async_get_entity_id(MP_DOMAIN, DOMAIN, self.unique_id) is not None:
# this player is already known, so do nothing other than mark as configured
raise AbortFlow("already_configured")
@@ -106,7 +106,8 @@ class SqueezeBoxPlayerUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
async def _async_update_data(self) -> dict[str, Any]:
"""Update the Player() object if available, or listen for rediscovery if not."""
if self.available:
# Only update players available at last update, unavailable players are rediscovered instead
# Only update players available at last update,
# unavailable players are rediscovered instead
await self.player.async_update()
if not self.player.connected:
@@ -32,8 +32,12 @@ class SqueezeboxEntity(CoordinatorEntity[SqueezeBoxPlayerUpdateCoordinator]):
@property
def available(self) -> bool:
"""Return True if entity is available."""
# super().available refers to CoordinatorEntity.available (self.coordinator.last_update_success)
# self.coordinator.available is the custom availability flag from SqueezeBoxPlayerUpdateCoordinator
# super().available refers to
# CoordinatorEntity.available
# (self.coordinator.last_update_success).
# self.coordinator.available is the custom
# availability flag from
# SqueezeBoxPlayerUpdateCoordinator
return self.coordinator.available and super().available
@@ -134,7 +134,8 @@ async def async_setup_entry(
manufacturer = player.creator
model_id = player.model_type
sw_version = ""
# Why? so we nicely merge with a server and a player linked by a MAC server is not all info lost
# So we nicely merge with a server and a player
# linked by a MAC server is not all info lost
if (
server_device
and (CONNECTION_NETWORK_MAC, format_mac(player.player_id))
@@ -762,8 +763,8 @@ class SqueezeBoxMediaPlayerEntity(SqueezeboxEntity, MediaPlayerEntity):
async def async_join_players(self, group_members: list[str]) -> None:
"""Add other Squeezebox players to this player's sync group.
If the other player is a member of a sync group, it will leave the current sync group
without asking.
If the other player is a member of a sync group,
it will leave the current sync group without asking.
"""
ent_reg = er.async_get(self.hass)
for other_player_entity_id in group_members:
@@ -798,7 +799,8 @@ class SqueezeBoxMediaPlayerEntity(SqueezeboxEntity, MediaPlayerEntity):
def get_synthetic_id_and_cache_url(self, url: str) -> str:
"""Cache a thumbnail URL and return a synthetic ID.
This enables us to proxy thumbnails for apps and favorites, as those do not have IDs.
This enables us to proxy thumbnails for apps and
favorites, as those do not have IDs.
"""
synthetic_id = f"s_{ulid_now()}"
@@ -81,10 +81,12 @@ async def async_setup_entry(
coordinator.async_add_listener(_async_listener)
# If coordinator already has alarm data from the initial refresh,
# call the listener immediately to process existing alarms and create alarm entities.
# call the listener immediately to process existing
# alarms and create alarm entities.
if coordinator.data["alarms"]:
_LOGGER.debug(
"Coordinator has alarm data, calling _async_listener immediately for player %s",
"Coordinator has alarm data, calling"
" _async_listener immediately for player %s",
coordinator.player,
)
_async_listener()
@@ -115,8 +115,10 @@ class ServerStatusUpdatePlugins(ServerStatusUpdate):
"""If install is supported give some info."""
rs = self.coordinator.data[UPDATE_PLUGINS_RELEASE_SUMMARY]
return (
(rs or "")
+ "The Plugins will be updated on the next restart triggered by selecting the Update button. Allow enough time for the service to restart. It will become briefly unavailable."
(rs or "") + "The Plugins will be updated on the next restart"
" triggered by selecting the Update button."
" Allow enough time for the service to restart."
" It will become briefly unavailable."
if self.coordinator.can_server_restart
else rs
)
+2 -1
View File
@@ -388,7 +388,8 @@ class Scanner:
ssdp_change = SSDP_SOURCE_SSDP_CHANGE_MAPPING[source]
_async_process_callbacks(self.hass, callbacks, discovery_info, ssdp_change)
# Config flows should only be created for alive/update messages from alive devices
# Config flows should only be created for alive/update
# messages from alive devices
if source == SsdpSource.ADVERTISEMENT_BYEBYE:
self._async_dismiss_discoveries(discovery_info)
return
+5 -1
View File
@@ -124,7 +124,11 @@ class Server:
async def _async_get_instance_udn(self) -> str:
"""Get Unique Device Name for this instance."""
instance_id = await async_get_instance_id(self.hass)
return f"uuid:{instance_id[0:8]}-{instance_id[8:12]}-{instance_id[12:16]}-{instance_id[16:20]}-{instance_id[20:32]}".upper()
return (
f"uuid:{instance_id[0:8]}-{instance_id[8:12]}"
f"-{instance_id[12:16]}-{instance_id[16:20]}"
f"-{instance_id[20:32]}"
).upper()
async def _async_start_upnp_servers(self, event: Event) -> None:
"""Start the UPnP/SSDP servers."""
+2 -1
View File
@@ -60,7 +60,8 @@ SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
SensorEntityDescription(
key="fuel",
translation_key="fuel",
# No device_class: fuel can be reported as percentage or volume depending on vehicle
# No device_class: fuel can be reported as percentage
# or volume depending on vehicle
state_class=SensorStateClass.TOTAL,
),
SensorEntityDescription(
@@ -140,7 +140,8 @@ class StarlinkUpdateCoordinator(DataUpdateCoordinator[StarlinkData]):
"""Set Starlink system sleep schedule end time."""
duration = end - self.data.sleep[0]
if duration < 0:
# If the duration pushed us into the next day, add one days worth to correct that.
# If the duration pushed us into the next day,
# add one days worth to correct that.
duration += 1440
async with asyncio.timeout(4):
try:
+1 -1
View File
@@ -64,7 +64,7 @@ class StarlinkSensorEntity(StarlinkEntity, SensorEntity):
class StarlinkAccumulationSensor(StarlinkSensorEntity, RestoreSensor):
"""A StarlinkAccumulationSensor for Starlink devices. Handles creating unique IDs."""
"""A StarlinkAccumulationSensor for Starlink devices."""
_attr_native_value: int | float = 0
@@ -525,7 +525,7 @@ ICON = "mdi:calculator"
def valid_state_characteristic_configuration(config: dict[str, Any]) -> dict[str, Any]:
"""Validate that the characteristic selected is valid for the source sensor type, throw if it isn't."""
"""Validate characteristic is valid for source sensor type."""
is_binary = split_entity_id(config[CONF_ENTITY_ID])[0] == BINARY_SENSOR_DOMAIN
characteristic = cast(str, config[CONF_STATE_CHARACTERISTIC])
if (is_binary and characteristic not in STATS_BINARY_SUPPORT) or (
@@ -556,7 +556,8 @@ def valid_keep_last_sample(config: dict[str, Any]) -> dict[str, Any]:
if config.get(CONF_KEEP_LAST_SAMPLE) is True and config.get(CONF_MAX_AGE) is None:
raise vol.RequiredFieldInvalid(
"The sensor configuration must provide 'max_age' if 'keep_last_sample' is True"
"The sensor configuration must provide 'max_age'"
" if 'keep_last_sample' is True"
)
return config
@@ -932,7 +933,8 @@ class StatisticsSensor(SensorEntity):
while self.ages and (now_timestamp - self.ages[0]) > max_age:
if self.samples_keep_last and len(self.ages) == 1:
# Under normal circumstance this will not be executed, as a purge will not
# Under normal circumstance this will not be
# executed, as a purge will not
# be scheduled for the last value if samples_keep_last is enabled.
# If this happens to be called outside normal scheduling logic or a
# source sensor update, this ensures the last value is preserved.
@@ -1096,7 +1098,8 @@ class StatisticsSensor(SensorEntity):
def _update_value(self) -> None:
"""Front to call the right statistical characteristics functions.
One of the _stat_*() functions is represented by self._state_characteristic_fn().
One of the _stat_*() functions is represented by
self._state_characteristic_fn().
"""
value = self._state_characteristic_fn(self.states, self.ages, self._percentile)
+17 -10
View File
@@ -185,8 +185,10 @@ def create_stream(
) -> Stream:
"""Create a stream with the specified identifier based on the source url.
The stream_source is typically an rtsp url (though any url accepted by ffmpeg is fine) and
options (see STREAM_OPTIONS_SCHEMA) are converted and passed into pyav / ffmpeg.
The stream_source is typically an rtsp url (though any
url accepted by ffmpeg is fine) and options (see
STREAM_OPTIONS_SCHEMA) are converted and passed into
pyav / ffmpeg.
The stream_label is a string used as an additional message in logging.
"""
@@ -460,7 +462,8 @@ class Stream:
def _run_worker(self) -> None:
"""Handle consuming streams and restart keepalive streams."""
# Keep import here so that we can import stream integration without installing reqs
# Keep import here so that we can import stream
# integration without installing reqs
from .worker import StreamState, stream_worker # noqa: PLC0415
stream_state = StreamState(self.hass, self.outputs, self._diagnostics)
@@ -491,7 +494,8 @@ class Stream:
stream_state.discontinuity()
if not _should_retry() or self._thread_quit.is_set():
if self._fast_restart_once:
# The stream source is updated, restart without any delay and reset the retry
# The stream source is updated, restart
# without any delay and reset the retry
# backoff for the new url.
wait_timeout = 0
self._fast_restart_once = False
@@ -501,8 +505,9 @@ class Stream:
self._set_state(False)
# To avoid excessive restarts, wait before restarting
# As the required recovery time may be different for different setups, start
# with trying a short wait_timeout and increase it on each reconnection attempt.
# As the required recovery time may be different
# for different setups, start with trying a short
# wait_timeout and increase it on each attempt.
# Reset the wait_timeout after the worker has been up for several minutes
if time.time() - start_time > STREAM_RESTART_RESET_TIME:
wait_timeout = 0
@@ -515,9 +520,10 @@ class Stream:
)
async def worker_finished() -> None:
# The worker is no checking availability of the stream and can no longer track
# availability so mark it as available, otherwise the frontend may not be able to
# interact with the stream.
# The worker is no longer checking availability
# of the stream and can no longer track it so
# mark it as available, otherwise the frontend
# may not be able to interact with the stream.
if not self.available:
self._async_update_state(True)
# We can call remove_provider() sequentially as the wrapped _stop() function
@@ -555,7 +561,8 @@ class Stream:
) -> None:
"""Make a .mp4 recording from a provided stream."""
# Keep import here so that we can import stream integration without installing reqs
# Keep import here so that we can import stream
# integration without installing reqs
from .recorder import RecorderOutput # noqa: PLC0415
# Check for file access
+8 -6
View File
@@ -198,7 +198,7 @@ class Segment:
def render_hls(
self, last_stream_id: int, render_parts: bool, add_hint: bool
) -> str:
"""Render the HLS playlist section for the Segment including a hint if requested."""
"""Render the Segment HLS playlist, optionally including parts and a hint."""
playlist_template = self._render_hls_template(last_stream_id, render_parts)
playlist = playlist_template.format(
self.hls_playlist_parts[0] if render_parts else ""
@@ -417,15 +417,17 @@ TRANSFORM_IMAGE_FUNCTION = (
class KeyFrameConverter:
"""Enables generating and getting an image from the last keyframe seen in the stream.
"""Generate and get an image from the last keyframe.
An overview of the thread and state interaction:
the worker thread sets a packet
get_image is called from the main asyncio loop
get_image schedules _generate_image in an executor thread
_generate_image will try to create an image from the packet
_generate_image will clear the packet, so there will only be one attempt per packet
If successful, self._image will be updated and returned by get_image
get_image schedules _generate_image in an executor
_generate_image will try to create an image from
the packet
_generate_image will clear the packet, so there
will only be one attempt per packet
If successful, self._image will be updated and returned
If unsuccessful, get_image will return the previous image
"""
+7 -5
View File
@@ -119,8 +119,10 @@ class HlsMasterPlaylistView(StreamView):
@staticmethod
def render(track: StreamOutput) -> str:
"""Render M3U8 file."""
# Need to calculate max bandwidth as input_container.bit_rate doesn't seem to work
# Calculate file size / duration and use a small multiplier to account for variation
# Need to calculate max bandwidth as
# input_container.bit_rate doesn't seem to work.
# Calculate file size / duration and use a small
# multiplier to account for variation
# hls spec already allows for 25% variation
if not (segment := track.get_segment(track.sequences[-2])):
return ""
@@ -184,15 +186,15 @@ class HlsPlaylistView(StreamView):
]
if track.stream_settings.ll_hls:
part_dur = track.stream_settings.part_target_duration
start_offset = EXT_X_START_LL_HLS * part_dur
playlist.extend(
[
"#EXT-X-PART-INF:PART-TARGET="
f"{track.stream_settings.part_target_duration:.3f}",
"#EXT-X-SERVER-CONTROL:CAN-BLOCK-RELOAD=YES,PART-HOLD-BACK="
f"{2 * track.stream_settings.part_target_duration:.3f}",
"#EXT-X-START:TIME-OFFSET=-"
f"{EXT_X_START_LL_HLS * track.stream_settings.part_target_duration:.3f}"
",PRECISE=YES",
f"#EXT-X-START:TIME-OFFSET=-{start_offset:.3f},PRECISE=YES",
]
)
else:
+5 -3
View File
@@ -80,10 +80,12 @@ class RecorderOutput(StreamOutput):
def write_segment(segment: Segment) -> None:
"""Write a segment to output."""
# fmt: off
nonlocal output, output_v, output_a, last_stream_id, running_duration, last_sequence
nonlocal output, output_v, output_a
nonlocal last_stream_id, running_duration, last_sequence
# fmt: on
# Because the stream_worker is in a different thread from the record service,
# the lookback segments may still have some overlap with the recorder segments
# Because the stream_worker is in a different
# thread from the record service, the lookback
# segments may still overlap with recorder ones
if segment.sequence <= last_sequence:
return
last_sequence = segment.sequence
+13 -3
View File
@@ -185,7 +185,11 @@ class StreamMuxer:
# https://github.com/home-assistant/core/pull/39970
# "cmaf" flag replaces several of the movflags used,
# but too recent to use for now
"movflags": "frag_custom+empty_moov+default_base_moof+frag_discont+negative_cts_offsets+skip_trailer+delay_moov",
"movflags": (
"frag_custom+empty_moov+default_base_moof"
"+frag_discont+negative_cts_offsets"
"+skip_trailer+delay_moov"
),
# Sometimes the first segment begins with negative timestamps,
# and this setting just
# adjusts the timestamps in the output from that segment to start
@@ -199,7 +203,12 @@ class StreamMuxer:
# Fragment durations may exceed the 15% allowed variance but it seems ok
**(
{
"movflags": "empty_moov+default_base_moof+frag_discont+negative_cts_offsets+skip_trailer+delay_moov",
"movflags": (
"empty_moov+default_base_moof"
"+frag_discont"
"+negative_cts_offsets"
"+skip_trailer+delay_moov"
),
# Create a fragment every TARGET_PART_DURATION. The data from
# each fragment is stored in a "Part" that can be combined with
# the data from all the other "Part"s, plus an init section,
@@ -662,7 +671,8 @@ def stream_worker(
except av.FFmpegError as ex:
container.close()
raise StreamWorkerError(
f"Error demuxing stream while finding first packet ({redact_av_error_string(ex)})"
"Error demuxing stream while finding first packet"
f" ({redact_av_error_string(ex)})"
) from ex
muxer = StreamMuxer(
@@ -83,7 +83,8 @@ async def _refresh_subaru_data(
for vehicle in vehicle_info.values():
vin = vehicle[VEHICLE_VIN]
# Optionally send an "update" remote command to vehicle (throttled with update_interval)
# Optionally send an "update" remote command to
# vehicle (throttled with update_interval)
if config_entry.options.get(CONF_UPDATE_ENABLED, False):
await _update_subaru(vehicle, controller)
+3 -1
View File
@@ -53,7 +53,9 @@ async def async_setup_entry(
class SubaruLock(LockEntity):
"""Representation of a Subaru door lock.
Note that the Subaru API currently does not support returning the status of the locks. Lock status is always unknown.
Note that the Subaru API currently does not support
returning the status of the locks. Lock status is
always unknown.
"""
_attr_has_entity_name = True
@@ -145,7 +145,8 @@ async def async_migrate_entry(
new_unique_id=f"{new_unique_id}_departure",
)
_LOGGER.debug(
"Faulty entity with unique_id 'None_departure' migrated to new unique_id '%s'",
"Faulty entity with unique_id 'None_departure'"
" migrated to new unique_id '%s'",
f"{new_unique_id}_departure",
)
@@ -155,7 +156,9 @@ async def async_migrate_entry(
)
if config_entry.version < 3:
# Via stations and time/offset settings now available, which are not backwards compatible if used, changes unique id
# Via stations and time/offset settings now available,
# which are not backwards compatible if used,
# changes unique id
hass.config_entries.async_update_entry(config_entry, version=3, minor_version=1)
_LOGGER.debug(
+4 -1
View File
@@ -73,7 +73,10 @@ class SwitchBeeDeviceEntity[_DeviceTypeT: SwitchBeeBaseDevice](
return self._is_online and super().available
def _check_if_became_offline(self) -> None:
"""Check if the device was online (now offline), log message and mark it as Unavailable."""
"""Check if the device was online (now offline).
Log message and mark it as unavailable.
"""
if self._is_online:
_LOGGER.warning(
+3 -2
View File
@@ -77,8 +77,9 @@ class SwitchBeeSwitchEntity[
self._check_if_became_online()
# timed power switch state is an integer representing the number of minutes left until it goes off
# regulare switches state is ON/OFF (1/0 respectively)
# timed power switch state is an integer representing
# the number of minutes left until it goes off;
# regular switches state is ON/OFF (1/0 respectively)
self._attr_is_on = coordinator_device.state != ApiStateCommand.OFF
async def async_turn_on(self, **kwargs: Any) -> None:
@@ -198,16 +198,22 @@ CLASS_BY_DEVICE = {
SupportedModels.AIR_PURIFIER_US.value: switchbot.SwitchbotAirPurifier,
SupportedModels.AIR_PURIFIER_TABLE_JP.value: switchbot.SwitchbotAirPurifier,
SupportedModels.AIR_PURIFIER_TABLE_US.value: switchbot.SwitchbotAirPurifier,
SupportedModels.EVAPORATIVE_HUMIDIFIER.value: switchbot.SwitchbotEvaporativeHumidifier,
SupportedModels.EVAPORATIVE_HUMIDIFIER.value: (
switchbot.SwitchbotEvaporativeHumidifier
),
SupportedModels.FLOOR_LAMP.value: switchbot.SwitchbotStripLight3,
SupportedModels.STRIP_LIGHT_3.value: switchbot.SwitchbotStripLight3,
SupportedModels.RGBICWW_FLOOR_LAMP.value: switchbot.SwitchbotRgbicLight,
SupportedModels.RGBICWW_STRIP_LIGHT.value: switchbot.SwitchbotRgbicLight,
SupportedModels.PERMANENT_OUTDOOR_LIGHT.value: switchbot.SwitchbotPermanentOutdoorLight,
SupportedModels.PERMANENT_OUTDOOR_LIGHT.value: (
switchbot.SwitchbotPermanentOutdoorLight
),
SupportedModels.PLUG_MINI_EU.value: switchbot.SwitchbotRelaySwitch,
SupportedModels.RELAY_SWITCH_2PM.value: switchbot.SwitchbotRelaySwitch2PM,
SupportedModels.GARAGE_DOOR_OPENER.value: switchbot.SwitchbotGarageDoorOpener,
SupportedModels.SMART_THERMOSTAT_RADIATOR.value: switchbot.SwitchbotSmartThermostatRadiator,
SupportedModels.SMART_THERMOSTAT_RADIATOR.value: (
switchbot.SwitchbotSmartThermostatRadiator
),
SupportedModels.ART_FRAME.value: switchbot.SwitchbotArtFrame,
SupportedModels.KEYPAD_VISION.value: switchbot.SwitchbotKeypadVision,
SupportedModels.KEYPAD_VISION_PRO.value: switchbot.SwitchbotKeypadVision,
+3 -2
View File
@@ -92,7 +92,7 @@ class SwitchBotArtFramePrevButton(SwitchBotArtFrameButtonBase):
class SwitchBotMeterProCO2SyncDateTimeButton(SwitchbotEntity, ButtonEntity):
"""Button to sync date and time on Meter Pro CO2 to the current HA instance datetime."""
"""Button to sync date and time on Meter Pro CO2."""
_device: switchbot.SwitchbotMeterProCO2
_attr_entity_category = EntityCategory.CONFIG
@@ -119,7 +119,8 @@ class SwitchBotMeterProCO2SyncDateTimeButton(SwitchbotEntity, ButtonEntity):
timestamp = int(now.timestamp())
_LOGGER.debug(
"Syncing time for %s: timestamp=%s, utc_offset_hours=%s, utc_offset_minutes=%s",
"Syncing time for %s: timestamp=%s,"
" utc_offset_hours=%s, utc_offset_minutes=%s",
self._address,
timestamp,
utc_offset_hours,
+3 -1
View File
@@ -182,7 +182,9 @@ ENCRYPTED_SWITCHBOT_MODEL_TO_CLASS: dict[
SwitchbotModel.PLUG_MINI_EU: switchbot.SwitchbotRelaySwitch,
SwitchbotModel.RELAY_SWITCH_2PM: switchbot.SwitchbotRelaySwitch2PM,
SwitchbotModel.GARAGE_DOOR_OPENER: switchbot.SwitchbotRelaySwitch,
SwitchbotModel.SMART_THERMOSTAT_RADIATOR: switchbot.SwitchbotSmartThermostatRadiator,
SwitchbotModel.SMART_THERMOSTAT_RADIATOR: (
switchbot.SwitchbotSmartThermostatRadiator
),
SwitchbotModel.ART_FRAME: switchbot.SwitchbotArtFrame,
SwitchbotModel.KEYPAD_VISION: switchbot.SwitchbotKeypadVision,
SwitchbotModel.KEYPAD_VISION_PRO: switchbot.SwitchbotKeypadVision,
+2 -1
View File
@@ -41,7 +41,8 @@ class SwitchbotEntity(
self._attr_device_info = DeviceInfo(
connections={(dr.CONNECTION_BLUETOOTH, self._address)},
manufacturer=MANUFACTURER,
model=coordinator.model, # Sometimes the modelName is missing from the advertisement data
# Sometimes the modelName is missing from ads
model=coordinator.model,
name=coordinator.device_name,
)
self._channel: int | None = None
@@ -46,7 +46,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: SwitcherConfigEntry) ->
# New device - create device
_LOGGER.info(
"Discovered Switcher device - id: %s, key: %s, name: %s, type: %s (%s), is_token_needed: %s",
"Discovered Switcher device - id: %s, key: %s,"
" name: %s, type: %s (%s), is_token_needed: %s",
device.device_id,
device.device_key,
device.name,

Some files were not shown because too many files have changed in this diff Show More