Compare commits

..

1 Commits

Author SHA1 Message Date
abmantis c2f11d25e1 Add button platform to Edifier Infrared 2026-06-16 19:26:27 +01:00
246 changed files with 1702 additions and 4947 deletions
-1
View File
@@ -642,7 +642,6 @@ homeassistant.components.xbox.*
homeassistant.components.xiaomi_ble.*
homeassistant.components.yale_smart_alarm.*
homeassistant.components.yalexs_ble.*
homeassistant.components.yoto.*
homeassistant.components.youtube.*
homeassistant.components.zeroconf.*
homeassistant.components.zinvolt.*
Generated
+2 -2
View File
@@ -1167,8 +1167,8 @@ CLAUDE.md @home-assistant/core
/tests/components/mutesync/ @currentoor
/homeassistant/components/my/ @home-assistant/core
/tests/components/my/ @home-assistant/core
/homeassistant/components/myneomitis/ @Epyes
/tests/components/myneomitis/ @Epyes
/homeassistant/components/myneomitis/ @l-pr
/tests/components/myneomitis/ @l-pr
/homeassistant/components/mysensors/ @MartinHjelmare @functionpointer
/tests/components/mysensors/ @MartinHjelmare @functionpointer
/homeassistant/components/mystrom/ @fabaff
+1 -1
View File
@@ -210,7 +210,7 @@ async def async_generate_image(
source = hass.data[DATA_MEDIA_SOURCE]
current_time = datetime.now() # pylint: disable=home-assistant-enforce-naive-now
current_time = datetime.now()
ext = mimetypes.guess_extension(task_result.mime_type, False) or ".png"
sanitized_task_name = RE_SANITIZE_FILENAME.sub("", slugify(task_name))
+6 -6
View File
@@ -30,7 +30,7 @@ from homeassistant.exceptions import (
from homeassistant.helpers import device_registry as dr, entity_registry as er
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from .const import DEFAULT_SSL, DEFAULT_VERIFY_SSL, DOMAIN, SECTION_ADDITIONAL_SETTINGS
from .const import DEFAULT_SSL, DEFAULT_VERIFY_SSL, DOMAIN, SECTION_ADVANCED_SETTINGS
from .coordinator import (
AirOSConfigEntry,
AirOSDataUpdateCoordinator,
@@ -55,14 +55,14 @@ async def async_setup_entry(hass: HomeAssistant, entry: AirOSConfigEntry) -> boo
# By default airOS 8 comes with self-signed SSL certificates,
# with no option in the web UI to change or upload a custom certificate.
session = async_get_clientsession(
hass, verify_ssl=entry.data[SECTION_ADDITIONAL_SETTINGS][CONF_VERIFY_SSL]
hass, verify_ssl=entry.data[SECTION_ADVANCED_SETTINGS][CONF_VERIFY_SSL]
)
conn_data = {
CONF_HOST: entry.data[CONF_HOST],
CONF_USERNAME: entry.data[CONF_USERNAME],
CONF_PASSWORD: entry.data[CONF_PASSWORD],
"use_ssl": entry.data[SECTION_ADDITIONAL_SETTINGS][CONF_SSL],
"use_ssl": entry.data[SECTION_ADVANCED_SETTINGS][CONF_SSL],
"session": session,
}
@@ -116,15 +116,15 @@ async def async_setup_entry(hass: HomeAssistant, entry: AirOSConfigEntry) -> boo
async def async_migrate_entry(hass: HomeAssistant, entry: AirOSConfigEntry) -> bool:
"""Migrate old config entry."""
# 1.1 Migrate config_entry to add additional ssl settings
# 1.1 Migrate config_entry to add advanced ssl settings
if entry.version == 1 and entry.minor_version == 1:
new_minor_version = 2
new_data = {**entry.data}
additional_data = {
advanced_data = {
CONF_SSL: DEFAULT_SSL,
CONF_VERIFY_SSL: DEFAULT_VERIFY_SSL,
}
new_data[SECTION_ADDITIONAL_SETTINGS] = additional_data
new_data[SECTION_ADVANCED_SETTINGS] = advanced_data
hass.config_entries.async_update_entry(
entry,
@@ -52,7 +52,7 @@ from .const import (
HOSTNAME,
IP_ADDRESS,
MAC_ADDRESS,
SECTION_ADDITIONAL_SETTINGS,
SECTION_ADVANCED_SETTINGS,
)
_LOGGER = logging.getLogger(__name__)
@@ -66,7 +66,7 @@ STEP_DISCOVERY_DATA_SCHEMA = vol.Schema(
{
vol.Required(CONF_USERNAME, default=DEFAULT_USERNAME): str,
vol.Required(CONF_PASSWORD): str,
vol.Required(SECTION_ADDITIONAL_SETTINGS): section(
vol.Required(SECTION_ADVANCED_SETTINGS): section(
vol.Schema(
{
vol.Required(CONF_SSL, default=DEFAULT_SSL): bool,
@@ -134,7 +134,7 @@ class AirOSConfigFlow(ConfigFlow, domain=DOMAIN):
# with no option in the web UI to change or upload a custom certificate.
session = async_get_clientsession(
self.hass,
verify_ssl=config_data[SECTION_ADDITIONAL_SETTINGS][CONF_VERIFY_SSL],
verify_ssl=config_data[SECTION_ADVANCED_SETTINGS][CONF_VERIFY_SSL],
)
try:
@@ -143,7 +143,7 @@ class AirOSConfigFlow(ConfigFlow, domain=DOMAIN):
username=config_data[CONF_USERNAME],
password=config_data[CONF_PASSWORD],
session=session,
use_ssl=config_data[SECTION_ADDITIONAL_SETTINGS][CONF_SSL],
use_ssl=config_data[SECTION_ADVANCED_SETTINGS][CONF_SSL],
)
except (
@@ -234,18 +234,18 @@ class AirOSConfigFlow(ConfigFlow, domain=DOMAIN):
autocomplete="current-password",
)
),
vol.Required(SECTION_ADDITIONAL_SETTINGS): section(
vol.Required(SECTION_ADVANCED_SETTINGS): section(
vol.Schema(
{
vol.Required(
CONF_SSL,
default=current_data[SECTION_ADDITIONAL_SETTINGS][
default=current_data[SECTION_ADVANCED_SETTINGS][
CONF_SSL
],
): bool,
vol.Required(
CONF_VERIFY_SSL,
default=current_data[SECTION_ADDITIONAL_SETTINGS][
default=current_data[SECTION_ADVANCED_SETTINGS][
CONF_VERIFY_SSL
],
): bool,
+1 -1
View File
@@ -12,7 +12,7 @@ MANUFACTURER = "Ubiquiti"
DEFAULT_VERIFY_SSL = False
DEFAULT_SSL = True
SECTION_ADDITIONAL_SETTINGS = "additional_settings"
SECTION_ADVANCED_SETTINGS = "advanced_settings"
# Discovery related
DEFAULT_USERNAME = "ubnt"
+2 -2
View File
@@ -4,7 +4,7 @@ from homeassistant.const import CONF_HOST, CONF_SSL
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC, DeviceInfo
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import DOMAIN, MANUFACTURER, SECTION_ADDITIONAL_SETTINGS
from .const import DOMAIN, MANUFACTURER, SECTION_ADVANCED_SETTINGS
from .coordinator import AirOSDataUpdateCoordinator
@@ -20,7 +20,7 @@ class AirOSEntity(CoordinatorEntity[AirOSDataUpdateCoordinator]):
airos_data = self.coordinator.data
url_schema = (
"https"
if coordinator.config_entry.data[SECTION_ADDITIONAL_SETTINGS][CONF_SSL]
if coordinator.config_entry.data[SECTION_ADVANCED_SETTINGS][CONF_SSL]
else "http"
)
+12 -12
View File
@@ -33,16 +33,16 @@
},
"description": "Enter the username and password for {device_name}",
"sections": {
"additional_settings": {
"advanced_settings": {
"data": {
"ssl": "[%key:component::airos::config::step::manual::sections::additional_settings::data::ssl%]",
"ssl": "[%key:component::airos::config::step::manual::sections::advanced_settings::data::ssl%]",
"verify_ssl": "[%key:common::config_flow::data::verify_ssl%]"
},
"data_description": {
"ssl": "[%key:component::airos::config::step::manual::sections::additional_settings::data_description::ssl%]",
"verify_ssl": "[%key:component::airos::config::step::manual::sections::additional_settings::data_description::verify_ssl%]"
"ssl": "[%key:component::airos::config::step::manual::sections::advanced_settings::data_description::ssl%]",
"verify_ssl": "[%key:component::airos::config::step::manual::sections::advanced_settings::data_description::verify_ssl%]"
},
"name": "[%key:component::airos::config::step::manual::sections::additional_settings::name%]"
"name": "[%key:component::airos::config::step::manual::sections::advanced_settings::name%]"
}
}
},
@@ -58,7 +58,7 @@
"username": "Administrator username for the airOS device, normally 'ubnt'"
},
"sections": {
"additional_settings": {
"advanced_settings": {
"data": {
"ssl": "Use HTTPS",
"verify_ssl": "[%key:common::config_flow::data::verify_ssl%]"
@@ -67,7 +67,7 @@
"ssl": "Whether the connection should be encrypted (required for most devices)",
"verify_ssl": "Whether the certificate should be verified when using HTTPS. This should be off for self-signed certificates"
},
"name": "Additional settings"
"name": "Advanced settings"
}
}
},
@@ -87,16 +87,16 @@
"password": "[%key:component::airos::config::step::manual::data_description::password%]"
},
"sections": {
"additional_settings": {
"advanced_settings": {
"data": {
"ssl": "[%key:component::airos::config::step::manual::sections::additional_settings::data::ssl%]",
"ssl": "[%key:component::airos::config::step::manual::sections::advanced_settings::data::ssl%]",
"verify_ssl": "[%key:common::config_flow::data::verify_ssl%]"
},
"data_description": {
"ssl": "[%key:component::airos::config::step::manual::sections::additional_settings::data_description::ssl%]",
"verify_ssl": "[%key:component::airos::config::step::manual::sections::additional_settings::data_description::verify_ssl%]"
"ssl": "[%key:component::airos::config::step::manual::sections::advanced_settings::data_description::ssl%]",
"verify_ssl": "[%key:component::airos::config::step::manual::sections::advanced_settings::data_description::verify_ssl%]"
},
"name": "[%key:component::airos::config::step::manual::sections::additional_settings::name%]"
"name": "[%key:component::airos::config::step::manual::sections::advanced_settings::name%]"
}
}
},
@@ -8,5 +8,5 @@
"iot_class": "cloud_polling",
"loggers": ["aioamazondevices"],
"quality_scale": "platinum",
"requirements": ["aioamazondevices==14.1.2"]
"requirements": ["aioamazondevices==14.0.4"]
}
@@ -34,13 +34,11 @@ def generate_site_selector_name(site: Site) -> str:
def filter_sites(sites: list[Site]) -> list[Site]:
"""Filter out closed sites and deduplicate the list of sites."""
"""Deduplicates the list of sites."""
filtered: list[Site] = []
filtered_nmi: set[str] = set()
for site in sorted(sites, key=lambda site: site.status):
if site.status == SiteStatus.CLOSED:
continue
if site.status == SiteStatus.ACTIVE or site.nmi not in filtered_nmi:
filtered.append(site)
filtered_nmi.add(site.nmi)
@@ -75,7 +75,7 @@ class AquaCellConfigFlow(ConfigFlow, domain=DOMAIN):
**user_input,
CONF_BRAND: user_input[CONF_BRAND],
CONF_REFRESH_TOKEN: refresh_token,
CONF_REFRESH_TOKEN_CREATION_TIME: datetime.now().timestamp(), # pylint: disable=home-assistant-enforce-naive-now
CONF_REFRESH_TOKEN_CREATION_TIME: datetime.now().timestamp(),
},
)
@@ -119,7 +119,7 @@ class AquaCellConfigFlow(ConfigFlow, domain=DOMAIN):
data_updates={
CONF_PASSWORD: user_input[CONF_PASSWORD],
CONF_REFRESH_TOKEN: refresh_token,
CONF_REFRESH_TOKEN_CREATION_TIME: datetime.now().timestamp(), # pylint: disable=home-assistant-enforce-naive-now
CONF_REFRESH_TOKEN_CREATION_TIME: datetime.now().timestamp(),
},
)
@@ -71,7 +71,7 @@ class AquacellCoordinator(DataUpdateCoordinator[dict[str, Softener]]):
+ REFRESH_TOKEN_EXPIRY_TIME.total_seconds()
)
try:
if datetime.now().timestamp() >= expiry_time: # pylint: disable=home-assistant-enforce-naive-now
if datetime.now().timestamp() >= expiry_time:
await self._reauthenticate()
else:
await self.aquacell_api.authenticate_refresh(self.refresh_token)
@@ -92,7 +92,7 @@ class AquacellCoordinator(DataUpdateCoordinator[dict[str, Softener]]):
data = {
**self.config_entry.data,
CONF_REFRESH_TOKEN: self.refresh_token,
CONF_REFRESH_TOKEN_CREATION_TIME: datetime.now().timestamp(), # pylint: disable=home-assistant-enforce-naive-now
CONF_REFRESH_TOKEN_CREATION_TIME: datetime.now().timestamp(),
}
self.hass.config_entries.async_update_entry(self.config_entry, data=data)
@@ -7,6 +7,6 @@
"integration_type": "hub",
"iot_class": "cloud_polling",
"loggers": ["pyaqvify"],
"quality_scale": "platinum",
"quality_scale": "silver",
"requirements": ["pyaqvify==0.0.11"]
}
@@ -53,43 +53,29 @@ rules:
test-coverage: done
# Gold
devices: done
diagnostics: done
discovery-update-info:
status: exempt
comment: |
Discovery not possible, as device is connected via 4G only. No LAN connection.
discovery:
status: exempt
comment: |
Discovery not possible, as device is connected via 4G only. No LAN connection.
docs-data-update: done
docs-examples: done
docs-known-limitations:
status: done
comment: |
No known limitations
docs-supported-devices: done
docs-supported-functions: done
docs-troubleshooting: done
docs-use-cases: done
dynamic-devices: done
entity-category:
status: done
comment: |
None of current sensors should be set as diagnostic
entity-device-class: done
entity-disabled-by-default: done
entity-translations: done
exception-translations: done
devices: todo
diagnostics: todo
discovery-update-info: todo
discovery: todo
docs-data-update: todo
docs-examples: todo
docs-known-limitations: todo
docs-supported-devices: todo
docs-supported-functions: todo
docs-troubleshooting: todo
docs-use-cases: todo
dynamic-devices: todo
entity-category: todo
entity-device-class: todo
entity-disabled-by-default: todo
entity-translations: todo
exception-translations: todo
icon-translations: done
reconfiguration-flow: done
repair-issues:
status: exempt
comment: |
No repair issues are created.
stale-devices: done
reconfiguration-flow: todo
repair-issues: todo
stale-devices: todo
# Platinum
async-dependency: done
inject-websession: done
strict-typing: done
async-dependency: todo
inject-websession: todo
strict-typing: todo
+1 -1
View File
@@ -61,7 +61,7 @@ def _activity_time_based(latest: Activity) -> Activity | None:
"""Get the latest state of the sensor."""
start = latest.activity_start_time
end = latest.activity_end_time + TIME_TO_DECLARE_DETECTION
if start <= datetime.now() <= end: # pylint: disable=home-assistant-enforce-naive-now
if start <= datetime.now() <= end:
return latest
return None
@@ -1,6 +1,6 @@
{
"common": {
"jid_options_description": "Additional grouping options, where devices' unique Beolink IDs (Called JIDs) are used directly. JIDs can be found in the state attributes of the media player entity.",
"jid_options_description": "Advanced grouping options, where devices' unique Beolink IDs (Called JIDs) are used directly. JIDs can be found in the state attributes of the media player entity.",
"jid_options_name": "JID options",
"key_press": "Press",
"key_release": "Release",
@@ -239,11 +239,11 @@ class BraviaTVCoordinator(DataUpdateCoordinator[None]):
self.source = None
if start_datetime := playing_info.get("startDateTime"):
start_datetime = datetime.fromisoformat(start_datetime)
current_datetime = datetime.now().replace(tzinfo=start_datetime.tzinfo) # pylint: disable=home-assistant-enforce-naive-now
current_datetime = datetime.now().replace(tzinfo=start_datetime.tzinfo)
self.media_position = int(
(current_datetime - start_datetime).total_seconds()
)
self.media_position_updated_at = datetime.now() # pylint: disable=home-assistant-enforce-naive-now
self.media_position_updated_at = datetime.now()
else:
self.media_position = None
self.media_position_updated_at = None
@@ -31,7 +31,7 @@ class BroadlinkHeartbeat:
async def async_setup(self) -> None:
"""Set up the heartbeat."""
if self._unsubscribe is None:
await self.async_heartbeat(dt.datetime.now()) # pylint: disable=home-assistant-enforce-naive-now
await self.async_heartbeat(dt.datetime.now())
self._unsubscribe = event.async_track_time_interval(
self._hass, self.async_heartbeat, self.HEARTBEAT_INTERVAL
)
+1 -1
View File
@@ -158,7 +158,7 @@ class BrData:
_LOGGER.debug("Buienradar parsed data: %s", result)
if result.get(SUCCESS) is not True:
if int(datetime.now().strftime("%H")) > 0: # pylint: disable=home-assistant-enforce-naive-now
if int(datetime.now().strftime("%H")) > 0:
_LOGGER.warning(
"Unable to parse data from Buienradar. (Msg: %s)",
result.get(MESSAGE),
+16 -11
View File
@@ -3,7 +3,7 @@
import logging
from typing import Any
from compit_inext_api import Parameter
from compit_inext_api import Param, Parameter
from compit_inext_api.consts import (
CompitFanMode,
CompitHVACMode,
@@ -150,7 +150,7 @@ class CompitClimate(CoordinatorEntity[CompitDataUpdateCoordinator], ClimateEntit
value = self.get_parameter_value(CompitParameter.CURRENT_TEMPERATURE)
if value is None:
return None
return float(value)
return float(value.value)
@property
def target_temperature(self) -> float | None:
@@ -158,7 +158,7 @@ class CompitClimate(CoordinatorEntity[CompitDataUpdateCoordinator], ClimateEntit
value = self.get_parameter_value(CompitParameter.SET_TARGET_TEMPERATURE)
if value is None:
return None
return float(value)
return float(value.value)
@cached_property
def preset_modes(self) -> list[str] | None:
@@ -195,24 +195,27 @@ class CompitClimate(CoordinatorEntity[CompitDataUpdateCoordinator], ClimateEntit
"""Return the current preset mode."""
preset_mode = self.get_parameter_value(CompitParameter.PRESET_MODE)
if preset_mode is not None:
return COMPIT_PRESET_MAP.get(CompitPresetMode(preset_mode))
if preset_mode:
compit_preset_mode = CompitPresetMode(preset_mode.value)
return COMPIT_PRESET_MAP.get(compit_preset_mode)
return None
@property
def fan_mode(self) -> str | None:
"""Return the current fan mode."""
fan_mode = self.get_parameter_value(CompitParameter.FAN_MODE)
if fan_mode is not None:
return COMPIT_FANSPEED_MAP.get(CompitFanMode(fan_mode))
if fan_mode:
compit_fan_mode = CompitFanMode(fan_mode.value)
return COMPIT_FANSPEED_MAP.get(compit_fan_mode)
return None
@property
def hvac_mode(self) -> HVACMode | None:
"""Return the current HVAC mode."""
hvac_mode = self.get_parameter_value(CompitParameter.HVAC_MODE)
if hvac_mode is not None:
return COMPIT_MODE_MAP.get(CompitHVACMode(hvac_mode))
if hvac_mode:
compit_hvac_mode = CompitHVACMode(hvac_mode.value)
return COMPIT_MODE_MAP.get(compit_hvac_mode)
return None
async def async_set_temperature(self, **kwargs: Any) -> None:
@@ -255,6 +258,8 @@ class CompitClimate(CoordinatorEntity[CompitDataUpdateCoordinator], ClimateEntit
)
self.async_write_ha_state()
def get_parameter_value(self, parameter: CompitParameter) -> str | float | None:
def get_parameter_value(self, parameter: CompitParameter) -> Param | None:
"""Get the parameter value from the device state."""
return self.coordinator.connector.get_current_value(self.device_id, parameter)
return self.coordinator.connector.get_device_parameter(
self.device_id, parameter
)
@@ -8,5 +8,5 @@
"iot_class": "cloud_polling",
"loggers": ["compit"],
"quality_scale": "bronze",
"requirements": ["compit-inext-api==0.9.1"]
"requirements": ["compit-inext-api==0.8.0"]
}
@@ -852,7 +852,7 @@ class DefaultAgent(ConversationEntity):
)
# Build filtered slot list
text_lower = remove_punctuation(text).strip().lower()
text_lower = text.strip().lower()
return TextSlotList(
name="name",
values=[
@@ -889,8 +889,7 @@ class DefaultAgent(ConversationEntity):
for name in intent.async_get_entity_aliases(
self.hass, entity_entry, state=state
):
# Strip punctuation so aliases match the cleaned input text.
yield (remove_punctuation(name).strip(), name, context)
yield (name, name, context)
def _recognize_strict(
self,
@@ -1163,7 +1162,7 @@ class DefaultAgent(ConversationEntity):
areas = ar.async_get(self.hass)
area_names = []
for area in areas.async_list_areas():
area_names.append((remove_punctuation(area.name).strip(), area.name))
area_names.append((area.name, area.name))
if not area.aliases:
continue
@@ -1172,13 +1171,13 @@ class DefaultAgent(ConversationEntity):
if not alias:
continue
area_names.append((remove_punctuation(alias).strip(), alias))
area_names.append((alias, alias))
# Expose all floors.
floors = fr.async_get(self.hass)
floor_names = []
for floor in floors.async_list_floors():
floor_names.append((remove_punctuation(floor.name).strip(), floor.name))
floor_names.append((floor.name, floor.name))
if not floor.aliases:
continue
@@ -1187,7 +1186,7 @@ class DefaultAgent(ConversationEntity):
if not alias:
continue
floor_names.append((remove_punctuation(alias).strip(), floor.name))
floor_names.append((alias, floor.name))
# Build trie
self._exposed_names_trie = Trie()
@@ -4,7 +4,7 @@ from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
PLATFORMS = [Platform.MEDIA_PLAYER]
PLATFORMS = [Platform.BUTTON, Platform.MEDIA_PLAYER]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
@@ -0,0 +1,180 @@
"""Button platform for Edifier infrared integration."""
from dataclasses import dataclass
from infrared_protocols.codes.edifier.models import EdifierCommandSet, EdifierModel
from infrared_protocols.codes.edifier.r1280db import EdifierR1280DBCode
from infrared_protocols.codes.edifier.r1700bt import EdifierR1700BTCode
from infrared_protocols.codes.edifier.rc20g import EdifierRC20GCode
from infrared_protocols.codes.edifier.s360db import EdifierS360DBCode
from homeassistant.components.button import ButtonEntity, ButtonEntityDescription
from homeassistant.components.infrared import InfraredEmitterConsumerEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_MODEL
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .const import CONF_COMMAND_SET, CONF_INFRARED_ENTITY_ID, EdifierCode
from .entity import EdifierIrEntity
PARALLEL_UPDATES = 1
@dataclass(frozen=True, kw_only=True)
class EdifierIrButtonEntityDescription(ButtonEntityDescription):
"""Describes Edifier IR button entity."""
command_code: EdifierCode
COMMAND_SET_BUTTONS: dict[
EdifierCommandSet,
tuple[EdifierIrButtonEntityDescription, ...],
] = {
EdifierCommandSet.R1700BT: (
EdifierIrButtonEntityDescription(
key="bluetooth",
translation_key="bluetooth",
command_code=EdifierR1700BTCode.BLUETOOTH,
),
EdifierIrButtonEntityDescription(
key="line_1",
translation_key="line_1",
command_code=EdifierR1700BTCode.LINE_1,
),
EdifierIrButtonEntityDescription(
key="line_2",
translation_key="line_2",
command_code=EdifierR1700BTCode.LINE_2,
),
EdifierIrButtonEntityDescription(
key="fx_on",
translation_key="fx_on",
command_code=EdifierR1700BTCode.FX_ON,
),
EdifierIrButtonEntityDescription(
key="fx_off",
translation_key="fx_off",
command_code=EdifierR1700BTCode.FX_OFF,
),
),
EdifierCommandSet.R1280DB: (
EdifierIrButtonEntityDescription(
key="bluetooth",
translation_key="bluetooth",
command_code=EdifierR1280DBCode.BLUETOOTH,
),
EdifierIrButtonEntityDescription(
key="line_1",
translation_key="line_1",
command_code=EdifierR1280DBCode.LINE_1,
),
EdifierIrButtonEntityDescription(
key="line_2",
translation_key="line_2",
command_code=EdifierR1280DBCode.LINE_2,
),
EdifierIrButtonEntityDescription(
key="optical",
translation_key="optical",
command_code=EdifierR1280DBCode.OPTICAL,
),
EdifierIrButtonEntityDescription(
key="coax",
translation_key="coax",
command_code=EdifierR1280DBCode.COAX,
),
),
EdifierCommandSet.S360DB: (
EdifierIrButtonEntityDescription(
key="bluetooth",
translation_key="bluetooth",
command_code=EdifierS360DBCode.BLUETOOTH,
),
EdifierIrButtonEntityDescription(
key="optical",
translation_key="optical",
command_code=EdifierS360DBCode.OPTICAL,
),
EdifierIrButtonEntityDescription(
key="coax",
translation_key="coax",
command_code=EdifierS360DBCode.COAX,
),
EdifierIrButtonEntityDescription(
key="pc",
translation_key="pc",
command_code=EdifierS360DBCode.PC,
),
EdifierIrButtonEntityDescription(
key="aux",
translation_key="aux",
command_code=EdifierS360DBCode.AUX,
),
),
EdifierCommandSet.RC20G: (
EdifierIrButtonEntityDescription(
key="bluetooth",
translation_key="bluetooth",
command_code=EdifierRC20GCode.BLUETOOTH,
),
EdifierIrButtonEntityDescription(
key="pc",
translation_key="pc",
command_code=EdifierRC20GCode.PC,
),
EdifierIrButtonEntityDescription(
key="aux",
translation_key="aux",
command_code=EdifierRC20GCode.AUX,
),
EdifierIrButtonEntityDescription(
key="optical",
translation_key="optical",
command_code=EdifierRC20GCode.OPTICAL,
),
EdifierIrButtonEntityDescription(
key="coax",
translation_key="coax",
command_code=EdifierRC20GCode.COAX,
),
),
}
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up Edifier IR buttons from a config entry."""
infrared_entity_id = entry.data[CONF_INFRARED_ENTITY_ID]
command_set = EdifierCommandSet(entry.data[CONF_COMMAND_SET])
model = EdifierModel(entry.data[CONF_MODEL])
async_add_entities(
EdifierIrButton(entry, model, infrared_entity_id, description)
for description in COMMAND_SET_BUTTONS.get(command_set, ())
)
class EdifierIrButton(EdifierIrEntity, InfraredEmitterConsumerEntity, ButtonEntity):
"""Edifier IR button entity."""
entity_description: EdifierIrButtonEntityDescription
def __init__(
self,
entry: ConfigEntry,
model: EdifierModel,
infrared_entity_id: str,
description: EdifierIrButtonEntityDescription,
) -> None:
"""Initialize Edifier IR button."""
super().__init__(entry, model, unique_id_suffix=description.key)
self._infrared_emitter_entity_id = infrared_entity_id
self.entity_description = description
async def async_press(self) -> None:
"""Press the button."""
await self._send_command(self.entity_description.command_code.to_command())
@@ -18,5 +18,36 @@
"title": "Set up Edifier IR speaker"
}
}
},
"entity": {
"button": {
"aux": {
"name": "AUX"
},
"bluetooth": {
"name": "Bluetooth"
},
"coax": {
"name": "Coaxial"
},
"fx_off": {
"name": "FX off"
},
"fx_on": {
"name": "FX on"
},
"line_1": {
"name": "Line 1"
},
"line_2": {
"name": "Line 2"
},
"optical": {
"name": "Optical"
},
"pc": {
"name": "PC"
}
}
}
}
@@ -25,7 +25,7 @@ from homeassistant.components.climate import (
from homeassistant.const import ATTR_TEMPERATURE, UnitOfTemperature
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC, DeviceInfo
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import ElectraSmartConfigEntry
@@ -145,7 +145,6 @@ class ElectraClimateEntity(ClimateEntity):
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, self._electra_ac_device.mac)},
connections={(CONNECTION_NETWORK_MAC, self._electra_ac_device.mac)},
name=device.name,
model=self._electra_ac_device.model,
manufacturer=self._electra_ac_device.manufactor,
@@ -7,5 +7,5 @@
"integration_type": "service",
"iot_class": "cloud_polling",
"loggers": ["elevenlabs"],
"requirements": ["elevenlabs==2.51.0", "sentence-stream==1.3.0"]
"requirements": ["elevenlabs==2.3.0", "sentence-stream==1.2.0"]
}
@@ -91,7 +91,7 @@ class FireflyDataUpdateCoordinator(DataUpdateCoordinator[FireflyCoordinatorData]
async def _async_update_data(self) -> FireflyCoordinatorData:
"""Fetch data from Firefly III API."""
now = datetime.now() # pylint: disable=home-assistant-enforce-naive-now
now = datetime.now()
start_date = now.replace(day=1, hour=0, minute=0, second=0, microsecond=0)
end_date = now
+1 -1
View File
@@ -200,7 +200,7 @@ class FreeboxRouter:
"IPv6": connection_datas.get("ipv6"),
"connection_type": connection_datas["media"],
"uptime": datetime.fromtimestamp(
round(datetime.now().timestamp()) - syst_datas["uptime_val"] # pylint: disable=home-assistant-enforce-naive-now
round(datetime.now().timestamp()) - syst_datas["uptime_val"]
),
"firmware_version": self._sw_v,
"serial": syst_datas["serial"],
+1 -1
View File
@@ -54,7 +54,7 @@ class FytaCoordinator(DataUpdateCoordinator[dict[int, Plant]]):
if (
self.fyta.expiration is None
or self.fyta.expiration.timestamp() < datetime.now().timestamp() # pylint: disable=home-assistant-enforce-naive-now
or self.fyta.expiration.timestamp() < datetime.now().timestamp()
):
await self.renew_authentication()
+1 -1
View File
@@ -117,5 +117,5 @@ class FytaPlantImageEntity(FytaPlantEntity, ImageEntity):
if url != self._attr_image_url:
self._cached_image = None
self._attr_image_last_updated = datetime.now() # pylint: disable=home-assistant-enforce-naive-now
self._attr_image_last_updated = datetime.now()
return url
+2 -2
View File
@@ -154,12 +154,12 @@ class GenericCamera(Camera):
self._last_image is not None
and url == self._last_url
and self._last_update + timedelta(0, self._attr_frame_interval)
> datetime.now() # pylint: disable=home-assistant-enforce-naive-now
> datetime.now()
):
return self._last_image
try:
update_time = datetime.now() # pylint: disable=home-assistant-enforce-naive-now
update_time = datetime.now()
async_client = get_async_client(self.hass, verify_ssl=self.verify_ssl)
response = await async_client.get(
url,
@@ -584,7 +584,7 @@ async def ws_start_preview(
if user_input.get(CONF_STILL_IMAGE_URL):
ha_still_url = (
"/api/generic/preview_flow_image"
f"/{msg['flow_id']}?t={datetime.now().isoformat()}" # pylint: disable=home-assistant-enforce-naive-now
f"/{msg['flow_id']}?t={datetime.now().isoformat()}"
)
_LOGGER.debug("Got preview still URL: %s", ha_still_url)
+1 -1
View File
@@ -29,7 +29,7 @@ SYNCHRONIZE_CLOCK = GoodweButtonEntityDescription(
key="synchronize_clock",
translation_key="synchronize_clock",
entity_category=EntityCategory.CONFIG,
action=lambda inv: inv.write_setting("time", datetime.now()), # pylint: disable=home-assistant-enforce-naive-now
action=lambda inv: inv.write_setting("time", datetime.now()),
)
+1 -1
View File
@@ -104,7 +104,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: GoogleConfigEntry) -> bo
# Force a token refresh to fix a bug where tokens were persisted with
# expires_in (relative time delta) and expires_at (absolute time) swapped.
# A google session token typically only lasts a few days between refresh.
now = datetime.now() # pylint: disable=home-assistant-enforce-naive-now
now = datetime.now()
if session.token["expires_at"] >= (now + timedelta(days=365)).timestamp():
session.token["expires_in"] = 0
session.token["expires_at"] = now.timestamp()
@@ -45,7 +45,7 @@ async def async_get_config_entry_diagnostics(
payload: dict[str, Any] = {
"now": dt_util.now().isoformat(),
"timezone": str(dt_util.get_default_time_zone()),
"system_timezone": str(datetime.datetime.now().astimezone().tzinfo), # pylint: disable=home-assistant-enforce-naive-now
"system_timezone": str(datetime.datetime.now().astimezone().tzinfo),
}
store = config_entry.runtime_data.store
@@ -434,56 +434,49 @@ async def google_generative_ai_config_option_schema(
description={"suggested_value": options.get(CONF_TEMPERATURE)},
default=RECOMMENDED_TEMPERATURE,
): NumberSelector(NumberSelectorConfig(min=0, max=2, step=0.05)),
vol.Optional(
CONF_TOP_P,
description={"suggested_value": options.get(CONF_TOP_P)},
default=RECOMMENDED_TOP_P,
): NumberSelector(NumberSelectorConfig(min=0, max=1, step=0.05)),
vol.Optional(
CONF_TOP_K,
description={"suggested_value": options.get(CONF_TOP_K)},
default=RECOMMENDED_TOP_K,
): int,
vol.Optional(
CONF_MAX_TOKENS,
description={"suggested_value": options.get(CONF_MAX_TOKENS)},
default=RECOMMENDED_MAX_TOKENS,
): int,
vol.Optional(
CONF_HARASSMENT_BLOCK_THRESHOLD,
description={
"suggested_value": options.get(CONF_HARASSMENT_BLOCK_THRESHOLD)
},
default=RECOMMENDED_HARM_BLOCK_THRESHOLD,
): harm_block_thresholds_selector,
vol.Optional(
CONF_HATE_BLOCK_THRESHOLD,
description={"suggested_value": options.get(CONF_HATE_BLOCK_THRESHOLD)},
default=RECOMMENDED_HARM_BLOCK_THRESHOLD,
): harm_block_thresholds_selector,
vol.Optional(
CONF_SEXUAL_BLOCK_THRESHOLD,
description={
"suggested_value": options.get(CONF_SEXUAL_BLOCK_THRESHOLD)
},
default=RECOMMENDED_HARM_BLOCK_THRESHOLD,
): harm_block_thresholds_selector,
vol.Optional(
CONF_DANGEROUS_BLOCK_THRESHOLD,
description={
"suggested_value": options.get(CONF_DANGEROUS_BLOCK_THRESHOLD)
},
default=RECOMMENDED_HARM_BLOCK_THRESHOLD,
): harm_block_thresholds_selector,
}
)
if subentry_type != "tts":
schema.update(
{
vol.Optional(
CONF_TOP_P,
description={"suggested_value": options.get(CONF_TOP_P)},
default=RECOMMENDED_TOP_P,
): NumberSelector(NumberSelectorConfig(min=0, max=1, step=0.05)),
vol.Optional(
CONF_TOP_K,
description={"suggested_value": options.get(CONF_TOP_K)},
default=RECOMMENDED_TOP_K,
): int,
vol.Optional(
CONF_MAX_TOKENS,
description={"suggested_value": options.get(CONF_MAX_TOKENS)},
default=RECOMMENDED_MAX_TOKENS,
): int,
vol.Optional(
CONF_HARASSMENT_BLOCK_THRESHOLD,
description={
"suggested_value": options.get(CONF_HARASSMENT_BLOCK_THRESHOLD)
},
default=RECOMMENDED_HARM_BLOCK_THRESHOLD,
): harm_block_thresholds_selector,
vol.Optional(
CONF_HATE_BLOCK_THRESHOLD,
description={
"suggested_value": options.get(CONF_HATE_BLOCK_THRESHOLD)
},
default=RECOMMENDED_HARM_BLOCK_THRESHOLD,
): harm_block_thresholds_selector,
vol.Optional(
CONF_SEXUAL_BLOCK_THRESHOLD,
description={
"suggested_value": options.get(CONF_SEXUAL_BLOCK_THRESHOLD)
},
default=RECOMMENDED_HARM_BLOCK_THRESHOLD,
): harm_block_thresholds_selector,
vol.Optional(
CONF_DANGEROUS_BLOCK_THRESHOLD,
description={
"suggested_value": options.get(CONF_DANGEROUS_BLOCK_THRESHOLD)
},
default=RECOMMENDED_HARM_BLOCK_THRESHOLD,
): harm_block_thresholds_selector,
}
)
if subentry_type == "conversation":
schema.update(
{
@@ -21,7 +21,7 @@ CONF_RECOMMENDED = "recommended"
CONF_CHAT_MODEL = "chat_model"
RECOMMENDED_CHAT_MODEL = "models/gemini-3.1-flash-lite"
RECOMMENDED_STT_MODEL = RECOMMENDED_CHAT_MODEL
RECOMMENDED_TTS_MODEL = "models/gemini-3.1-flash-tts-preview"
RECOMMENDED_TTS_MODEL = "models/gemini-2.5-flash-preview-tts"
RECOMMENDED_IMAGE_MODEL = "models/gemini-2.5-flash-image"
CONF_TEMPERATURE = "temperature"
RECOMMENDED_TEMPERATURE = 1.0
@@ -18,13 +18,7 @@ from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .const import (
CONF_CHAT_MODEL,
CONF_TEMPERATURE,
LOGGER,
RECOMMENDED_TEMPERATURE,
RECOMMENDED_TTS_MODEL,
)
from .const import CONF_CHAT_MODEL, LOGGER, RECOMMENDED_TTS_MODEL
from .entity import GoogleGenerativeAILLMBaseEntity
from .helpers import convert_to_wav
@@ -197,10 +191,7 @@ class GoogleGenerativeAITextToSpeechEntity(
self, message: str, language: str, options: dict[str, Any]
) -> TtsAudioType:
"""Load tts audio file from the engine."""
config = types.GenerateContentConfig()
config.temperature = self.subentry.data.get(
CONF_TEMPERATURE, RECOMMENDED_TEMPERATURE
)
config = self.create_generate_content_config()
config.response_modalities = ["AUDIO"]
config.speech_config = types.SpeechConfig(
voice_config=types.VoiceConfig(
@@ -69,7 +69,7 @@ def _append_to_sheet(call: ServiceCall, entry: GoogleSheetsConfigEntry) -> None:
worksheet = sheet.worksheet(call.data.get(WORKSHEET, sheet.sheet1.title))
columns: list[str] = next(iter(worksheet.get_values("A1:ZZ1")), [])
add_created_column = call.data[ADD_CREATED_COLUMN]
now = str(datetime.now()) # pylint: disable=home-assistant-enforce-naive-now
now = str(datetime.now())
rows = []
for d in call.data[DATA]:
row_data = ({"created": now} | d) if add_created_column else d
@@ -128,7 +128,7 @@ class GoveeLight(CoordinatorEntity[GoveeLocalApiCoordinator], LightEntity):
"""
if not super().available:
return False
return datetime.now() - self._device.lastseen < DEVICE_TIMEOUT # pylint: disable=home-assistant-enforce-naive-now
return datetime.now() - self._device.lastseen < DEVICE_TIMEOUT
@property
def is_on(self) -> bool:
@@ -387,6 +387,6 @@ def build_hass_attribution(sections: list[dict[str, Any]]) -> str | None:
def next_datetime(simple_time: time) -> datetime:
"""Take a time like 08:00:00 and combine it with the current date."""
combined = datetime.combine(dt_util.start_of_local_day(), simple_time)
if combined < datetime.now(): # pylint: disable=home-assistant-enforce-naive-now
if combined < datetime.now():
combined = combined + timedelta(days=1)
return combined
@@ -75,7 +75,7 @@ def format_last_reset_elapsed_seconds(value: str | None) -> datetime | None:
if value is None:
return None
try:
last_reset = datetime.now() - timedelta(seconds=int(value)) # pylint: disable=home-assistant-enforce-naive-now
last_reset = datetime.now() - timedelta(seconds=int(value))
last_reset.replace(microsecond=0)
except ValueError:
return None
@@ -87,6 +87,11 @@ class IcloudTrackerEntity(TrackerEntity):
assert self._device.location is not None
return self._device.location[DEVICE_LOCATION_LONGITUDE]
@property
def battery_level(self) -> int | None:
"""Return the battery level of the device."""
return self._device.battery_level
@property
def icon(self) -> str:
"""Return the icon."""
+1 -1
View File
@@ -32,7 +32,7 @@
"port": "[%key:common::config_flow::data::port%]",
"search": "IMAP search",
"server": "Server",
"ssl_cipher_list": "SSL cipher list",
"ssl_cipher_list": "SSL cipher list (Advanced)",
"username": "[%key:common::config_flow::data::username%]",
"verify_ssl": "[%key:common::config_flow::data::verify_ssl%]"
},
@@ -2,7 +2,7 @@
from abc import ABC, abstractmethod
from dataclasses import dataclass
from datetime import datetime, timedelta
from datetime import UTC, datetime, timedelta
from decimal import Decimal, InvalidOperation
from enum import Enum
import logging
@@ -49,7 +49,6 @@ from homeassistant.helpers.event import (
async_track_state_report_event,
)
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from homeassistant.util import dt as dt_util
from .const import (
CONF_MAX_SUB_INTERVAL,
@@ -340,7 +339,8 @@ class IntegrationSensor(RestoreSensor):
else max_sub_interval
)
self._max_sub_interval_exceeded_callback: CALLBACK_TYPE = lambda *args: None
self._last_integration_time: datetime = dt_util.utcnow()
# pylint: disable-next=home-assistant-enforce-utcnow
self._last_integration_time: datetime = datetime.now(tz=UTC)
self._last_integration_trigger = _IntegrationTrigger.StateEvent
self._attr_suggested_display_precision = round_digits or 2
@@ -499,7 +499,8 @@ class IntegrationSensor(RestoreSensor):
old_timestamp, new_timestamp, old_state, new_state
)
self._last_integration_trigger = _IntegrationTrigger.StateEvent
self._last_integration_time = dt_util.utcnow()
# pylint: disable-next=home-assistant-enforce-utcnow
self._last_integration_time = datetime.now(tz=UTC)
finally:
# When max_sub_interval exceeds without state change the source is assumed
# constant with the last known state (new_state).
@@ -607,7 +608,8 @@ class IntegrationSensor(RestoreSensor):
self._update_integral(area)
self.async_write_ha_state()
self._last_integration_time = dt_util.utcnow()
# pylint: disable-next=home-assistant-enforce-utcnow
self._last_integration_time = datetime.now(tz=UTC)
self._last_integration_trigger = _IntegrationTrigger.TimeElapsed
self._schedule_max_sub_interval_exceeded_if_state_is_numeric(
+1 -1
View File
@@ -342,7 +342,7 @@ class iOSIdentifyDeviceView(HomeAssistantView):
hass = request.app[KEY_HASS]
data[ATTR_LAST_SEEN_AT] = datetime.datetime.now().isoformat() # pylint: disable=home-assistant-enforce-naive-now
data[ATTR_LAST_SEEN_AT] = datetime.datetime.now().isoformat()
device_id = data[ATTR_DEVICE_ID]
@@ -71,8 +71,8 @@ class IsraelRailDataUpdateCoordinator(DataUpdateCoordinator[list[DataConnection]
self._train_schedule.query,
self._start,
self._destination,
datetime.now().strftime("%Y-%m-%d"), # pylint: disable=home-assistant-enforce-naive-now
datetime.now().strftime("%H:%M"), # pylint: disable=home-assistant-enforce-naive-now
datetime.now().strftime("%Y-%m-%d"),
datetime.now().strftime("%H:%M"),
)
except Exception as e:
raise UpdateFailed(
@@ -1,15 +1,11 @@
"""LG IR Remote integration for Home Assistant."""
import logging
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
PLATFORMS = [Platform.BUTTON, Platform.EVENT, Platform.MEDIA_PLAYER]
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up LG IR from a config entry."""
@@ -20,14 +16,3 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a LG IR config entry."""
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
async def async_migrate_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Migrate old config entry."""
if entry.version == 1:
# v1 used the infrared entity_id in the entry's unique_id, which is
# not stable and was removed in v2.
_LOGGER.debug("Migrating config entry from version 1 to 2")
hass.config_entries.async_update_entry(entry, unique_id=None, version=2)
return True
@@ -1,6 +1,6 @@
"""Config flow for LG IR integration."""
from typing import TYPE_CHECKING, Any
from typing import Any
import voluptuous as vol
@@ -35,7 +35,7 @@ DEVICE_TYPE_NAMES: dict[LGDeviceType, str] = {
class LgIrConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle config flow for LG IR."""
VERSION = 2
VERSION = 1
async def async_step_user(
self, user_input: dict[str, Any] | None = None
@@ -49,39 +49,24 @@ class LgIrConfigFlow(ConfigFlow, domain=DOMAIN):
errors: dict[str, str] = {}
if user_input is not None:
emitter_id = user_input.get(CONF_INFRARED_ENTITY_ID)
receiver_id = user_input.get(CONF_INFRARED_RECEIVER_ENTITY_ID)
if emitter_id or receiver_id:
if entity_id := user_input.get(CONF_INFRARED_ENTITY_ID) or user_input.get(
CONF_INFRARED_RECEIVER_ENTITY_ID
):
device_type = user_input[CONF_DEVICE_TYPE]
if emitter_id:
self._async_abort_entries_match(
{
CONF_DEVICE_TYPE: device_type,
CONF_INFRARED_ENTITY_ID: emitter_id,
}
)
if receiver_id:
self._async_abort_entries_match(
{
CONF_DEVICE_TYPE: device_type,
CONF_INFRARED_RECEIVER_ENTITY_ID: receiver_id,
}
)
await self.async_set_unique_id(f"lg_ir_{device_type}_{entity_id}")
self._abort_if_unique_id_configured()
# Get entity name for the title
title_entity_id = emitter_id or receiver_id
if TYPE_CHECKING:
assert title_entity_id is not None
ent_reg = er.async_get(self.hass)
entry = ent_reg.async_get(title_entity_id)
title_entity_name = (
entry.name or entry.original_name or title_entity_id
entry = ent_reg.async_get(entity_id)
entity_name = (
entry.name or entry.original_name or entity_id
if entry
else title_entity_id
else entity_id
)
device_type_name = DEVICE_TYPE_NAMES[LGDeviceType(device_type)]
title = f"LG {device_type_name} via {title_entity_name}"
title = f"LG {device_type_name} via {entity_name}"
return self.async_create_entry(title=title, data=user_input)
@@ -214,7 +214,7 @@ class LgTVDevice(MediaPlayerEntity):
def media_image_url(self):
"""URL for obtaining a screen capture."""
return (
f"{self._client.url}data?target=screen_image&_={datetime.now().timestamp()}" # pylint: disable=home-assistant-enforce-naive-now
f"{self._client.url}data?target=screen_image&_={datetime.now().timestamp()}"
)
def turn_off(self) -> None:
@@ -18,7 +18,7 @@ async def async_get_config_entry_diagnostics(
payload: dict[str, Any] = {
"now": dt_util.now().isoformat(),
"timezone": str(dt_util.get_default_time_zone()),
"system_timezone": str(datetime.datetime.now().astimezone().tzinfo), # pylint: disable=home-assistant-enforce-naive-now
"system_timezone": str(datetime.datetime.now().astimezone().tzinfo),
}
store = config_entry.runtime_data
ics = await store.async_load()
@@ -8,5 +8,5 @@
"integration_type": "hub",
"iot_class": "local_polling",
"quality_scale": "bronze",
"requirements": ["mitsubishi-comfort==0.3.2"]
"requirements": ["mitsubishi-comfort==0.3.1"]
}
@@ -68,7 +68,7 @@ class MonarchMoneyDataUpdateCoordinator(DataUpdateCoordinator[MonarchData]):
async def _async_update_data(self) -> MonarchData:
"""Fetch data for all accounts."""
now = datetime.now() # pylint: disable=home-assistant-enforce-naive-now
now = datetime.now()
account_data, cashflow_summary = await asyncio.gather(
self.client.get_accounts_as_dict_with_id_key(),
+23 -21
View File
@@ -1124,7 +1124,7 @@ def validate_light_platform_config(user_data: dict[str, Any]) -> dict[str, str]:
if user_data.get(CONF_MIN_KELVIN, DEFAULT_MIN_KELVIN) >= user_data.get(
CONF_MAX_KELVIN, DEFAULT_MAX_KELVIN
):
errors["other_settings"] = "max_below_min_kelvin"
errors["advanced_settings"] = "max_below_min_kelvin"
return errors
@@ -1217,7 +1217,7 @@ def validate_text_platform_config(
and CONF_MAX in config
and config[CONF_MIN] > config[CONF_MAX]
):
errors["text_other_settings"] = "max_below_min"
errors["text_advanced_settings"] = "max_below_min"
return errors
@@ -1506,7 +1506,7 @@ PLATFORM_ENTITY_FIELDS: dict[Platform, dict[str, PlatformField]] = {
selector=SUGGESTED_DISPLAY_PRECISION_SELECTOR,
required=False,
validator=cv.positive_int,
section="other_settings",
section="advanced_settings",
),
CONF_OPTIONS: PlatformField(
selector=OPTIONS_SELECTOR,
@@ -1678,13 +1678,13 @@ PLATFORM_MQTT_FIELDS: dict[Platform, dict[str, PlatformField]] = {
selector=TIMEOUT_SELECTOR,
required=False,
validator=cv.positive_int,
section="other_settings",
section="advanced_settings",
),
CONF_OFF_DELAY: PlatformField(
selector=TIMEOUT_SELECTOR,
required=False,
validator=cv.positive_int,
section="other_settings",
section="advanced_settings",
),
},
Platform.BUTTON: {
@@ -3125,7 +3125,7 @@ PLATFORM_MQTT_FIELDS: dict[Platform, dict[str, PlatformField]] = {
default=False,
validator=cv.boolean,
conditions=({CONF_SCHEMA: "json"},),
section="other_settings",
section="advanced_settings",
),
CONF_FLASH_TIME_SHORT: PlatformField(
selector=FLASH_TIME_SELECTOR,
@@ -3133,7 +3133,7 @@ PLATFORM_MQTT_FIELDS: dict[Platform, dict[str, PlatformField]] = {
validator=cv.positive_int,
default=2,
conditions=({CONF_SCHEMA: "json"},),
section="other_settings",
section="advanced_settings",
),
CONF_FLASH_TIME_LONG: PlatformField(
selector=FLASH_TIME_SELECTOR,
@@ -3141,7 +3141,7 @@ PLATFORM_MQTT_FIELDS: dict[Platform, dict[str, PlatformField]] = {
validator=cv.positive_int,
default=10,
conditions=({CONF_SCHEMA: "json"},),
section="other_settings",
section="advanced_settings",
),
CONF_TRANSITION: PlatformField(
selector=BOOLEAN_SELECTOR,
@@ -3149,21 +3149,21 @@ PLATFORM_MQTT_FIELDS: dict[Platform, dict[str, PlatformField]] = {
default=False,
validator=cv.boolean,
conditions=({CONF_SCHEMA: "json"},),
section="other_settings",
section="advanced_settings",
),
CONF_MAX_KELVIN: PlatformField(
selector=KELVIN_SELECTOR,
required=False,
validator=cv.positive_int,
default=DEFAULT_MAX_KELVIN,
section="other_settings",
section="advanced_settings",
),
CONF_MIN_KELVIN: PlatformField(
selector=KELVIN_SELECTOR,
required=False,
validator=cv.positive_int,
default=DEFAULT_MIN_KELVIN,
section="other_settings",
section="advanced_settings",
),
},
Platform.LOCK: {
@@ -3372,7 +3372,7 @@ PLATFORM_MQTT_FIELDS: dict[Platform, dict[str, PlatformField]] = {
selector=TIMEOUT_SELECTOR,
required=False,
validator=cv.positive_int,
section="other_settings",
section="advanced_settings",
),
},
Platform.SIREN: {
@@ -3437,7 +3437,7 @@ PLATFORM_MQTT_FIELDS: dict[Platform, dict[str, PlatformField]] = {
required=False,
validator=validate(cv.template),
error="invalid_template",
section="siren_other_settings",
section="siren_advanced_settings",
),
},
Platform.SWITCH: {
@@ -3516,26 +3516,26 @@ PLATFORM_MQTT_FIELDS: dict[Platform, dict[str, PlatformField]] = {
selector=TEXT_SIZE_SELECTOR,
required=True,
default=0,
section="text_other_settings",
section="text_advanced_settings",
),
CONF_MAX: PlatformField(
selector=TEXT_SIZE_SELECTOR,
required=True,
default=255,
section="text_other_settings",
section="text_advanced_settings",
),
CONF_MODE: PlatformField(
selector=TEXT_MODE_SELECTOR,
required=True,
default=TextSelectorType.TEXT.value,
section="text_other_settings",
section="text_advanced_settings",
),
CONF_PATTERN: PlatformField(
selector=TEXT_SELECTOR,
required=False,
validator=validate(cv.is_regex),
error="invalid_regular_expression",
section="text_other_settings",
section="text_advanced_settings",
),
},
Platform.TIME: {
@@ -3798,10 +3798,10 @@ PLATFORM_MQTT_FIELDS: dict[Platform, dict[str, PlatformField]] = {
MQTT_DEVICE_PLATFORM_FIELDS = {
CONF_NAME: PlatformField(selector=TEXT_SELECTOR, required=True),
CONF_SW_VERSION: PlatformField(
selector=TEXT_SELECTOR, required=False, section="other_settings"
selector=TEXT_SELECTOR, required=False, section="advanced_settings"
),
CONF_HW_VERSION: PlatformField(
selector=TEXT_SELECTOR, required=False, section="other_settings"
selector=TEXT_SELECTOR, required=False, section="advanced_settings"
),
CONF_MODEL: PlatformField(selector=TEXT_SELECTOR, required=False),
CONF_MODEL_ID: PlatformField(selector=TEXT_SELECTOR, required=False),
@@ -4178,6 +4178,7 @@ class FlowHandler(ConfigFlow, domain=DOMAIN):
CONF_PROTOCOL: DEFAULT_PROTOCOL,
CONF_USERNAME: addon_discovery_config.get(CONF_USERNAME),
CONF_PASSWORD: addon_discovery_config.get(CONF_PASSWORD),
CONF_DISCOVERY: DEFAULT_DISCOVERY,
}
except AddonError:
# We do not have discovery information yet
@@ -4418,6 +4419,7 @@ class FlowHandler(ConfigFlow, domain=DOMAIN):
CONF_PROTOCOL: DEFAULT_PROTOCOL,
CONF_USERNAME: data.get(CONF_USERNAME),
CONF_PASSWORD: data.get(CONF_PASSWORD),
CONF_DISCOVERY: DEFAULT_DISCOVERY,
},
)
@@ -4684,8 +4686,8 @@ class MQTTSubentryFlowHandler(ConfigSubentryFlow):
if user_input is not None:
new_device_data: dict[str, Any] = user_input.copy()
_, errors = validate_user_input(user_input, MQTT_DEVICE_PLATFORM_FIELDS)
if "other_settings" in new_device_data:
new_device_data |= new_device_data.pop("other_settings")
if "advanced_settings" in new_device_data:
new_device_data |= new_device_data.pop("advanced_settings")
if not errors:
self._subentry_data[CONF_DEVICE] = cast(MqttDeviceData, new_device_data)
if self.source == SOURCE_RECONFIGURE:
+40 -40
View File
@@ -184,6 +184,17 @@
},
"description": "Enter the MQTT device details:",
"sections": {
"advanced_settings": {
"data": {
"hw_version": "Hardware version",
"sw_version": "Software version"
},
"data_description": {
"hw_version": "The hardware version of the device. E.g. 'v1.0 rev a'.",
"sw_version": "The software version of the device. E.g. '2025.1.0'."
},
"name": "Advanced device settings"
},
"mqtt_settings": {
"data": {
"message_expiry_interval": "Message Expiry Interval",
@@ -194,17 +205,6 @@
"qos": "The Quality of Service value the device's entities should use."
},
"name": "MQTT settings"
},
"other_settings": {
"data": {
"hw_version": "Hardware version",
"sw_version": "Software version"
},
"data_description": {
"hw_version": "The hardware version of the device. E.g. 'v1.0 rev a'.",
"sw_version": "The software version of the device. E.g. '2025.1.0'."
},
"name": "Other device settings"
}
},
"title": "Configure MQTT device details"
@@ -286,14 +286,14 @@
},
"description": "Please configure specific details for {platform} entity \"{entity}\":",
"sections": {
"other_settings": {
"advanced_settings": {
"data": {
"suggested_display_precision": "Suggested display precision"
},
"data_description": {
"suggested_display_precision": "The number of decimals which should be used in the {platform} entity state after rounding. [Learn more.]({url}#suggested_display_precision)"
},
"name": "Other settings"
"name": "Advanced options"
}
},
"title": "Configure MQTT device \"{mqtt_device}\""
@@ -438,6 +438,29 @@
},
"description": "Please configure MQTT specific details for {platform} entity \"{entity}\":",
"sections": {
"advanced_settings": {
"data": {
"expire_after": "Expire after",
"flash": "Flash support",
"flash_time_long": "Flash time long",
"flash_time_short": "Flash time short",
"max_kelvin": "Max Kelvin",
"min_kelvin": "Min Kelvin",
"off_delay": "OFF delay",
"transition": "Transition support"
},
"data_description": {
"expire_after": "If set, it defines the number of seconds after the sensors state expires, if its not updated. After expiry, the sensors state becomes unavailable. If not set, the sensor's state never expires. [Learn more.]({url}#expire_after)",
"flash": "Enable the flash feature for this light",
"flash_time_long": "The duration, in seconds, of a \"long\" flash.",
"flash_time_short": "The duration, in seconds, of a \"short\" flash.",
"max_kelvin": "The maximum color temperature in Kelvin.",
"min_kelvin": "The minimum color temperature in Kelvin.",
"off_delay": "For sensors that only send \"on\" state updates (like PIRs), this variable sets a delay in seconds after which the sensors state will be updated back to \"off\".",
"transition": "Enable the transition feature for this light"
},
"name": "Advanced settings"
},
"alarm_control_panel_payload_settings": {
"data": {
"payload_arm_away": "Payload \"arm away\"",
@@ -893,37 +916,14 @@
},
"name": "Lock payload settings"
},
"other_settings": {
"data": {
"expire_after": "Expire after",
"flash": "Flash support",
"flash_time_long": "Flash time long",
"flash_time_short": "Flash time short",
"max_kelvin": "Max Kelvin",
"min_kelvin": "Min Kelvin",
"off_delay": "OFF delay",
"transition": "Transition support"
},
"data_description": {
"expire_after": "If set, it defines the number of seconds after the sensors state expires, if its not updated. After expiry, the sensors state becomes unavailable. If not set, the sensor's state never expires. [Learn more.]({url}#expire_after)",
"flash": "Enable the flash feature for this light",
"flash_time_long": "The duration, in seconds, of a \"long\" flash.",
"flash_time_short": "The duration, in seconds, of a \"short\" flash.",
"max_kelvin": "The maximum color temperature in Kelvin.",
"min_kelvin": "The minimum color temperature in Kelvin.",
"off_delay": "For sensors that only send \"on\" state updates (like PIRs), this variable sets a delay in seconds after which the sensors state will be updated back to \"off\".",
"transition": "Enable the transition feature for this light"
},
"name": "Other settings"
},
"siren_other_settings": {
"siren_advanced_settings": {
"data": {
"command_off_template": "Command \"off\" template"
},
"data_description": {
"command_off_template": "The [template]({command_templating_url}) for \"off\" state changes. By default the \"[Command template]({url}#command_template)\" will be used. [Learn more.]({url}#command_off_template)"
},
"name": "Other siren settings"
"name": "Advanced siren settings"
},
"target_humidity_settings": {
"data": {
@@ -985,7 +985,7 @@
},
"name": "Target temperature settings"
},
"text_other_settings": {
"text_advanced_settings": {
"data": {
"max": "Maximum length",
"min": "Minimum length",
@@ -998,7 +998,7 @@
"mode": "Mode of the text input",
"pattern": "A valid regex pattern"
},
"name": "Other text entity settings"
"name": "Advanced text entity settings"
},
"valve_payload_settings": {
"data": {
@@ -1,7 +1,7 @@
{
"domain": "myneomitis",
"name": "MyNeomitis",
"codeowners": ["@Epyes"],
"codeowners": ["@l-pr"],
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/myneomitis",
"integration_type": "hub",
@@ -70,8 +70,5 @@ class MyUplinkDataCoordinator(DataUpdateCoordinator[CoordinatorData]):
points[device_id] = point_info
return CoordinatorData(
systems=systems,
devices=devices,
points=points,
time=datetime.now(), # pylint: disable=home-assistant-enforce-naive-now
systems=systems, devices=devices, points=points, time=datetime.now()
)
@@ -67,7 +67,7 @@ class NextBusDataUpdateCoordinator(
# But only if we have a reset time to unthrottle
and self.client.rate_limit_reset is not None
# Unless we are after the reset time
and datetime.now() < self.client.rate_limit_reset # pylint: disable=home-assistant-enforce-naive-now
and datetime.now() < self.client.rate_limit_reset
):
self.logger.debug(
"Rate limit threshold reached. Skipping updates for. Routes: %s",
@@ -58,7 +58,7 @@ class NiceGOConfigFlow(ConfigFlow, domain=DOMAIN):
CONF_EMAIL: user_input[CONF_EMAIL],
CONF_PASSWORD: user_input[CONF_PASSWORD],
CONF_REFRESH_TOKEN: refresh_token,
CONF_REFRESH_TOKEN_CREATION_TIME: datetime.now().timestamp(), # pylint: disable=home-assistant-enforce-naive-now
CONF_REFRESH_TOKEN_CREATION_TIME: datetime.now().timestamp(),
},
)
@@ -99,7 +99,7 @@ class NiceGOConfigFlow(ConfigFlow, domain=DOMAIN):
data={
**user_input,
CONF_REFRESH_TOKEN: refresh_token,
CONF_REFRESH_TOKEN_CREATION_TIME: datetime.now().timestamp(), # pylint: disable=home-assistant-enforce-naive-now
CONF_REFRESH_TOKEN_CREATION_TIME: datetime.now().timestamp(),
},
unique_id=user_input[CONF_EMAIL],
)
@@ -150,7 +150,7 @@ class NiceGOUpdateCoordinator(DataUpdateCoordinator[dict[str, NiceGODevice]]):
+ REFRESH_TOKEN_EXPIRY_TIME.total_seconds()
)
try:
if datetime.now().timestamp() >= expiry_time: # pylint: disable=home-assistant-enforce-naive-now
if datetime.now().timestamp() >= expiry_time:
await self.update_refresh_token()
else:
await self.api.authenticate_refresh(
@@ -194,7 +194,7 @@ class NiceGOUpdateCoordinator(DataUpdateCoordinator[dict[str, NiceGODevice]]):
data = {
**self.config_entry.data,
CONF_REFRESH_TOKEN: refresh_token,
CONF_REFRESH_TOKEN_CREATION_TIME: datetime.now().timestamp(), # pylint: disable=home-assistant-enforce-naive-now
CONF_REFRESH_TOKEN_CREATION_TIME: datetime.now().timestamp(),
}
self.hass.config_entries.async_update_entry(self.config_entry, data=data)
@@ -154,7 +154,7 @@ class NOAATidesAndCurrentsSensor(SensorEntity):
def update(self) -> None:
"""Get the latest data from NOAA Tides and Currents API."""
begin = datetime.now() # pylint: disable=home-assistant-enforce-naive-now
begin = datetime.now()
end = begin + DEFAULT_PREDICTION_LENGTH
try:
df_predictions = self._station.get_data(
@@ -9,5 +9,5 @@
"iot_class": "cloud_polling",
"loggers": ["opower"],
"quality_scale": "platinum",
"requirements": ["opower==0.18.5"]
"requirements": ["opower==0.18.4"]
}
+1 -1
View File
@@ -385,7 +385,7 @@ class DailyHistory:
def add_measurement(self, value, timestamp=None):
"""Add a new measurement for a certain day."""
day = (timestamp or datetime.now()).date() # pylint: disable=home-assistant-enforce-naive-now
day = (timestamp or datetime.now()).date()
if not isinstance(value, (int, float)):
return
if self._days is None:
@@ -26,8 +26,6 @@ from .coordinator import ProxmoxConfigEntry, ProxmoxCoordinator, ProxmoxNodeData
from .entity import ProxmoxContainerEntity, ProxmoxNodeEntity, ProxmoxVMEntity
from .helpers import is_granted
PARALLEL_UPDATES = 1
NO_PERM_VM_LXC_POWER = "no_permission_vm_lxc_power"
@@ -8,7 +8,7 @@ from homeassistant.core import HomeAssistant
from .coordinator import RabbitAirConfigEntry, RabbitAirDataUpdateCoordinator
PLATFORMS: list[Platform] = [Platform.FAN, Platform.SENSOR]
PLATFORMS: list[Platform] = [Platform.FAN]
async def async_setup_entry(hass: HomeAssistant, entry: RabbitAirConfigEntry) -> bool:
+1 -2
View File
@@ -25,8 +25,6 @@ MODELS = {
class RabbitAirBaseEntity(CoordinatorEntity[RabbitAirDataUpdateCoordinator]):
"""Base class for Rabbit Air entity."""
_attr_has_entity_name = True
def __init__(
self,
coordinator: RabbitAirDataUpdateCoordinator,
@@ -34,6 +32,7 @@ class RabbitAirBaseEntity(CoordinatorEntity[RabbitAirDataUpdateCoordinator]):
) -> None:
"""Initialize the entity."""
super().__init__(coordinator)
self._attr_name = entry.title
self._attr_unique_id = entry.unique_id
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, entry.data[CONF_MAC])},
@@ -46,7 +46,6 @@ async def async_setup_entry(
class RabbitAirFanEntity(RabbitAirBaseEntity, FanEntity):
"""Fan control functions of the Rabbit Air air purifier."""
_attr_name = None
_attr_translation_key = "rabbitair"
_attr_supported_features = (
FanEntityFeature.PRESET_MODE
@@ -12,11 +12,6 @@
}
}
}
},
"sensor": {
"air_quality": {
"default": "mdi:air-filter"
}
}
}
}
@@ -1,60 +0,0 @@
"""Support for Rabbit Air sensors."""
from rabbitair import Quality
from homeassistant.components.sensor import (
SensorDeviceClass,
SensorEntity,
SensorEntityDescription,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.typing import StateType
from .coordinator import RabbitAirConfigEntry, RabbitAirDataUpdateCoordinator
from .entity import RabbitAirBaseEntity
def _quality_value(quality: Quality | None) -> StateType:
"""Return the air quality state."""
return None if quality is None else quality.name.lower()
AIR_QUALITY_OPTIONS = [quality.name.lower() for quality in Quality]
AIR_QUALITY_DESCRIPTION = SensorEntityDescription(
key="air_quality",
translation_key="air_quality",
device_class=SensorDeviceClass.ENUM,
options=AIR_QUALITY_OPTIONS,
)
async def async_setup_entry(
hass: HomeAssistant,
entry: RabbitAirConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up Rabbit Air sensors."""
if entry.runtime_data.data.quality is not None:
async_add_entities([RabbitAirAirQualitySensor(entry.runtime_data, entry)])
class RabbitAirAirQualitySensor(RabbitAirBaseEntity, SensorEntity):
"""Rabbit Air air quality sensor."""
entity_description = AIR_QUALITY_DESCRIPTION
def __init__(
self,
coordinator: RabbitAirDataUpdateCoordinator,
entry: RabbitAirConfigEntry,
) -> None:
"""Initialize the entity."""
super().__init__(coordinator, entry)
self._attr_unique_id = f"{entry.unique_id}_{self.entity_description.key}"
@property
def native_value(self) -> StateType:
"""Return the air quality state."""
return _quality_value(self.coordinator.data.quality)
@@ -32,18 +32,6 @@
}
}
}
},
"sensor": {
"air_quality": {
"name": "Air quality",
"state": {
"high": "[%key:common::state::high%]",
"highest": "Highest",
"low": "[%key:common::state::low%]",
"lowest": "Lowest",
"medium": "[%key:common::state::medium%]"
}
}
}
}
}
@@ -184,7 +184,7 @@ class CalendarUpdateCoordinator(RadarrDataUpdateCoordinator[None]):
self._events = [
e
for e in self._events
if e.start >= datetime.now().date() - timedelta(days=30) # pylint: disable=home-assistant-enforce-naive-now
if e.start >= datetime.now().date() - timedelta(days=30)
]
_days = (end_date - start_date).days
await asyncio.gather(
+15 -19
View File
@@ -308,17 +308,17 @@ class Events(Base):
def from_event(event: Event) -> Events:
"""Create an event database object from a native event."""
context = event.context
# The unused legacy columns (event_type, event_data, time_fired,
# context_id, context_user_id, context_parent_id) are nullable with no
# default, so they are intentionally left unset here. Assigning them
# None would still insert NULL, but each assignment goes through
# SQLAlchemy's instrumented attribute machinery, which is a measurable
# cost when run for every recorded event.
return Events(
event_type=None,
event_data=None,
origin_idx=event.origin.idx,
time_fired=None,
time_fired_ts=event.time_fired_timestamp,
context_id=None,
context_id_bin=ulid_to_bytes_or_none(context.id),
context_user_id=None,
context_user_id_bin=uuid_hex_to_bytes_or_none(context.user_id),
context_parent_id=None,
context_parent_id_bin=ulid_to_bytes_or_none(context.parent_id),
)
@@ -491,18 +491,19 @@ class States(Base):
else:
last_reported_ts = state.last_reported_timestamp
context = event.context
# The unused legacy columns (entity_id, attributes, context_id,
# context_user_id, context_parent_id, last_updated, last_changed) are
# nullable with no default, so they are intentionally left unset here.
# Assigning them None would still insert NULL, but each assignment goes
# through SQLAlchemy's instrumented attribute machinery, which is a
# measurable cost when run for every recorded state change.
return States(
state=state_value,
entity_id=None,
attributes=None,
context_id=None,
context_id_bin=ulid_to_bytes_or_none(context.id),
context_user_id=None,
context_user_id_bin=uuid_hex_to_bytes_or_none(context.user_id),
context_parent_id=None,
context_parent_id_bin=ulid_to_bytes_or_none(context.parent_id),
origin_idx=event.origin.idx,
last_updated=None,
last_changed=None,
last_updated_ts=last_updated_ts,
last_changed_ts=last_changed_ts,
last_reported_ts=last_reported_ts,
@@ -559,13 +560,8 @@ class StateAttributes(Base):
# None state means the state was removed from the state machine
if (state := event.data["new_state"]) is None:
return b"{}"
if (state_info := state.state_info) and (
unrecorded_attributes := state_info["unrecorded_attributes"]
):
# The entity has unrecorded attributes, so a combined exclude set
# has to be built. The common case (no unrecorded attributes) falls
# through to the shared constant below without allocating a set per
# recorded state change.
if state_info := state.state_info:
unrecorded_attributes = state_info["unrecorded_attributes"]
exclude_attrs = {
*ALL_DOMAIN_EXCLUDE_ATTRS,
*unrecorded_attributes,
@@ -19,7 +19,7 @@ async def async_get_config_entry_diagnostics(
payload: dict[str, Any] = {
"now": dt_util.now().isoformat(),
"timezone": str(dt_util.get_default_time_zone()),
"system_timezone": str(datetime.datetime.now().astimezone().tzinfo), # pylint: disable=home-assistant-enforce-naive-now
"system_timezone": str(datetime.datetime.now().astimezone().tzinfo),
}
payload["ics"] = "\n".join(redact_ics(coordinator.ics))
return payload
+7 -6
View File
@@ -8,7 +8,7 @@ from renson_endura_delta.field_enum import (
)
from renson_endura_delta.renson import RensonVentilation
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC, DeviceInfo
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import DOMAIN
@@ -24,11 +24,10 @@ class RensonEntity(CoordinatorEntity[RensonCoordinator]):
"""Initialize the Renson entity."""
super().__init__(coordinator)
mac = api.get_field_value(coordinator.data, MAC_ADDRESS.name)
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, mac)},
connections={(CONNECTION_NETWORK_MAC, mac)},
identifiers={
(DOMAIN, api.get_field_value(coordinator.data, MAC_ADDRESS.name))
},
manufacturer="Renson",
model=api.get_field_value(coordinator.data, DEVICE_NAME_FIELD.name),
name="Ventilation",
@@ -42,4 +41,6 @@ class RensonEntity(CoordinatorEntity[RensonCoordinator]):
self.api = api
self._attr_unique_id = f"{mac}{name}"
self._attr_unique_id = (
api.get_field_value(coordinator.data, MAC_ADDRESS.name) + f"{name}"
)
+2 -3
View File
@@ -1,7 +1,7 @@
"""Reolink integration for HomeAssistant."""
from collections.abc import Callable
from datetime import timedelta
from datetime import UTC, datetime, timedelta
import logging
from random import uniform
from time import time
@@ -26,7 +26,6 @@ from homeassistant.helpers import (
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC
from homeassistant.helpers.event import async_call_later
from homeassistant.helpers.typing import ConfigType
from homeassistant.util import dt as dt_util
from .const import (
BATTERY_PASSIVE_WAKE_UPDATE_INTERVAL,
@@ -193,7 +192,7 @@ async def async_setup_entry(
hass.config_entries.async_update_entry(config_entry, data=data)
# If camera WAN blocked, firmware check fails and takes long, do not prevent setup
now = dt_util.utcnow()
now = datetime.now(UTC) # pylint: disable=home-assistant-enforce-utcnow
check_time = timedelta(seconds=check_time_sec)
delta_midnight = now - now.replace(hour=0, minute=0, second=0, microsecond=0)
firmware_check_delay = check_time - delta_midnight
+1 -1
View File
@@ -31,7 +31,7 @@ async def async_setup_services(hass: HomeAssistant) -> None:
time_to_send = time
if time is None:
time_to_send = datetime.now() # pylint: disable=home-assistant-enforce-naive-now
time_to_send = datetime.now()
await local_data.system.set_time(time_to_send)
+2 -18
View File
@@ -1,34 +1,18 @@
"""The Raspberry Pi Power Supply Checker integration."""
from rpi_bad_power import UnderVoltage, new_under_voltage
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryError
from .const import DOMAIN
PLATFORMS = [Platform.BINARY_SENSOR]
type RpiPowerConfigEntry = ConfigEntry[UnderVoltage]
async def async_setup_entry(hass: HomeAssistant, entry: RpiPowerConfigEntry) -> bool:
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up Raspberry Pi Power Supply Checker from a config entry."""
if (client := await hass.async_add_executor_job(new_under_voltage)) is None:
raise ConfigEntryError(
translation_domain=DOMAIN,
translation_key="under_voltage_not_supported",
)
entry.runtime_data = client
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True
async def async_unload_entry(hass: HomeAssistant, entry: RpiPowerConfigEntry) -> bool:
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
@@ -5,20 +5,17 @@ Minimal Kernel needed is 4.14+
import logging
from rpi_bad_power import UnderVoltage
from rpi_bad_power import UnderVoltage, new_under_voltage
from homeassistant.components.binary_sensor import (
BinarySensorDeviceClass,
BinarySensorEntity,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import RpiPowerConfigEntry
from .const import DOMAIN
_LOGGER = logging.getLogger(__name__)
DESCRIPTION_NORMALIZED = "Voltage normalized. Everything is working as intended."
@@ -30,11 +27,11 @@ DESCRIPTION_UNDER_VOLTAGE = (
async def async_setup_entry(
hass: HomeAssistant,
config_entry: RpiPowerConfigEntry,
config_entry: ConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up rpi_power binary sensor."""
under_voltage = config_entry.runtime_data
under_voltage = await hass.async_add_executor_job(new_under_voltage)
async_add_entities([RaspberryChargerBinarySensor(under_voltage)], True)
@@ -43,20 +40,14 @@ class RaspberryChargerBinarySensor(BinarySensorEntity):
_attr_device_class = BinarySensorDeviceClass.PROBLEM
_attr_entity_category = EntityCategory.DIAGNOSTIC
_attr_translation_key = "rpi_power"
_attr_has_entity_name = True
_attr_icon = "mdi:raspberry-pi"
_attr_name = "RPi Power status"
_attr_unique_id = "rpi_power" # only one sensor possible
def __init__(self, under_voltage: UnderVoltage) -> None:
"""Initialize the binary sensor."""
self._under_voltage = under_voltage
self._attr_device_info = DeviceInfo(
manufacturer="Raspberry Pi",
identifiers={(DOMAIN, "rpi_power")},
name="Raspberry Pi",
)
def update(self) -> None:
"""Update the state."""
value = self._under_voltage.get()
@@ -1,9 +0,0 @@
{
"entity": {
"binary_sensor": {
"rpi_power": {
"default": "mdi:raspberry-pi"
}
}
}
}
@@ -9,17 +9,5 @@
}
}
},
"entity": {
"binary_sensor": {
"rpi_power": {
"name": "Power status"
}
}
},
"exceptions": {
"under_voltage_not_supported": {
"message": "Under-voltage monitoring is not supported on this device."
}
},
"title": "Raspberry Pi Power Supply Checker"
}
@@ -46,7 +46,7 @@ class SensoterraConfigFlow(ConfigFlow, domain=DOMAIN):
api = CustomerApi(user_input[CONF_EMAIL], user_input[CONF_PASSWORD])
# We need a unique tag per HA instance
uuid = self.hass.data["core.uuid"]
expiration = datetime.now() + timedelta(TOKEN_EXPIRATION_DAYS) # pylint: disable=home-assistant-enforce-naive-now
expiration = datetime.now() + timedelta(TOKEN_EXPIRATION_DAYS)
try:
token: str = await api.get_token(
@@ -1,6 +1,6 @@
"""Sensoterra devices."""
from datetime import timedelta
from datetime import UTC, datetime, timedelta
from enum import StrEnum, auto
from sensoterra.probe import Probe, Sensor
@@ -22,7 +22,6 @@ from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.typing import StateType
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from homeassistant.util import dt as dt_util
from .const import CONFIGURATION_URL, DOMAIN, SENSOR_EXPIRATION_DAYS
from .coordinator import SensoterraConfigEntry, SensoterraCoordinator
@@ -166,5 +165,5 @@ class SensoterraEntity(CoordinatorEntity[SensoterraCoordinator], SensorEntity):
return False
# Expire sensor if no update within the last few days.
expiration = dt_util.utcnow() - timedelta(days=SENSOR_EXPIRATION_DAYS)
expiration = datetime.now(UTC) - timedelta(days=SENSOR_EXPIRATION_DAYS) # pylint: disable=home-assistant-enforce-utcnow
return sensor.timestamp >= expiration
@@ -70,7 +70,7 @@ class SharkIqUpdateCoordinator(DataUpdateCoordinator[bool]):
try:
if (
self.ayla_api.token_expiring_soon
or datetime.now() # pylint: disable=home-assistant-enforce-naive-now
or datetime.now()
> self.ayla_api.auth_expiration - timedelta(seconds=600)
):
await self.ayla_api.async_refresh_auth()
@@ -247,7 +247,7 @@ def _async_register_base_station(
config_entry_id=entry.entry_id,
identifiers={(DOMAIN, str(system.system_id))},
manufacturer="SimpliSafe",
model=str(system.version),
model=system.version,
name=system.address,
)
+2 -2
View File
@@ -17,7 +17,7 @@ from homeassistant.const import (
Platform,
)
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from homeassistant.exceptions import ConfigEntryError, ConfigEntryNotReady
from homeassistant.helpers import discovery
from homeassistant.util.ssl import create_client_context
@@ -75,7 +75,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: SmtpConfigEntry) -> bool
try:
await hass.async_add_executor_job(lambda: client.connect().quit())
except SMTPAuthenticationError as e:
raise ConfigEntryAuthFailed(
raise ConfigEntryError(
translation_domain=DOMAIN,
translation_key="authentication_error",
) from e
@@ -1,6 +1,5 @@
"""Config flow for the SMTP integration."""
from collections.abc import Mapping
import logging
from smtplib import SMTP, SMTP_SSL, SMTPAuthenticationError
import socket
@@ -96,22 +95,6 @@ STEP_USER_DATA_SCHEMA = vol.Schema(
vol.Required(CONF_VERIFY_SSL, default=True): cv.boolean,
}
)
STEP_REAUTH_DATA_SCHEMA = vol.Schema(
{
vol.Optional(CONF_USERNAME): TextSelector(
TextSelectorConfig(
type=TextSelectorType.TEXT,
autocomplete="username",
),
),
vol.Optional(CONF_PASSWORD): TextSelector(
TextSelectorConfig(
type=TextSelectorType.PASSWORD,
autocomplete="current-password",
),
),
}
)
OPTIONS_SCHEMA = vol.Schema(
{
@@ -218,39 +201,6 @@ class MailConfigFlow(ConfigFlow, domain=DOMAIN):
errors=errors,
)
async def async_step_reauth(
self, entry_data: Mapping[str, Any]
) -> ConfigFlowResult:
"""Perform reauth upon an authentication error."""
return await self.async_step_reauth_confirm()
async def async_step_reauth_confirm(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Confirm reauthentication dialog."""
errors: dict[str, str] = {}
entry = self._get_reauth_entry()
if user_input is not None:
errors = await self.hass.async_add_executor_job(
validate_input, {**entry.data, **user_input}
)
if not errors:
return self.async_update_and_abort(
entry,
data_updates=user_input,
)
return self.async_show_form(
step_id="reauth_confirm",
data_schema=self.add_suggested_values_to_schema(
data_schema=STEP_REAUTH_DATA_SCHEMA,
suggested_values=user_input
or {CONF_USERNAME: entry.data.get(CONF_USERNAME)},
),
errors=errors,
)
async def async_step_import(self, import_info: dict[str, Any]) -> ConfigFlowResult:
"""Import config from yaml."""
+2 -2
View File
@@ -41,7 +41,7 @@ from homeassistant.const import (
)
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType
from homeassistant.exceptions import ConfigEntryAuthFailed, HomeAssistantError
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import config_validation as cv, entity_registry as er
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
@@ -208,7 +208,7 @@ class MailNotifyEntity(NotifyEntity):
try:
client = self._client.connect()
except SMTPAuthenticationError as e:
raise ConfigEntryAuthFailed(
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="authentication_error",
) from e
@@ -2,7 +2,6 @@
"config": {
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_service%]",
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]",
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]"
},
"error": {
@@ -12,17 +11,6 @@
"unknown": "[%key:common::config_flow::error::unknown%]"
},
"step": {
"reauth_confirm": {
"data": {
"password": "[%key:common::config_flow::data::password%]",
"username": "[%key:common::config_flow::data::username%]"
},
"data_description": {
"password": "[%key:component::smtp::config::step::user::data_description::password%]",
"username": "[%key:component::smtp::config::step::user::data_description::username%]"
},
"title": "Re-authenticate SMTP"
},
"reconfigure": {
"data": {
"encryption": "[%key:component::smtp::config::step::user::data::encryption%]",
@@ -21,6 +21,7 @@ from sonos_websocket.exception import SonosWebsocketError
from homeassistant.components import media_source, spotify
from homeassistant.components.media_player import (
ATTR_INPUT_SOURCE,
ATTR_MEDIA_ALBUM_NAME,
ATTR_MEDIA_ANNOUNCE,
ATTR_MEDIA_ARTIST,
@@ -778,6 +779,9 @@ class SonosMediaPlayerEntity(SonosEntity, MediaPlayerEntity):
if self.media.queue_size:
attributes["queue_size"] = self.media.queue_size
if self.source:
attributes[ATTR_INPUT_SOURCE] = self.source
return attributes
async def async_get_browse_image(
@@ -160,7 +160,7 @@ async def async_setup_entry(
model=model,
manufacturer=manufacturer,
model_id=model_id,
hw_version=str(player.firmware) if player.firmware is not None else None,
hw_version=player.firmware,
sw_version=sw_version,
via_device=(DOMAIN, coordinator.server_uuid),
)
+1 -1
View File
@@ -47,7 +47,7 @@ class StarlineAccount:
def _check_slnet_token(self, interval: int) -> None:
"""Check SLNet token expiration and update if needed."""
now = datetime.now().timestamp() # pylint: disable=home-assistant-enforce-naive-now
now = datetime.now().timestamp()
slnet_token_expires = self._config_entry.data[DATA_EXPIRES]
if now + interval > slnet_token_expires:
@@ -40,6 +40,11 @@ class StarlineDeviceTracker(StarlineEntity, TrackerEntity, RestoreEntity):
"""Return device specific attributes."""
return self._account.gps_attrs(self._device)
@property
def battery_level(self) -> int | None:
"""Return the battery level of the device."""
return self._device.battery_level
@property
def location_accuracy(self) -> float:
"""Return the gps accuracy of the device."""
+2 -2
View File
@@ -9,7 +9,6 @@ from homeassistant.components.time import TimeEntity, TimeEntityDescription
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.util import dt as dt_util
from .coordinator import StarlinkConfigEntry, StarlinkData, StarlinkUpdateCoordinator
from .entity import StarlinkEntity
@@ -64,7 +63,8 @@ def _utc_minutes_to_time(utc_minutes: int, timezone: tzinfo) -> time:
hour -= 24
minute = utc_minutes % 60
try:
utc = dt_util.utcnow().replace(
# pylint: disable-next=home-assistant-enforce-utcnow
utc = datetime.now(UTC).replace(
hour=hour, minute=minute, second=0, microsecond=0
)
except ValueError as exc:
@@ -108,7 +108,7 @@ class SubaruConfigFlow(ConfigFlow, domain=DOMAIN):
data: contains values provided by the user.
"""
websession = aiohttp_client.async_get_clientsession(self.hass)
now = datetime.now() # pylint: disable=home-assistant-enforce-naive-now
now = datetime.now()
if not data.get(CONF_DEVICE_ID):
data[CONF_DEVICE_ID] = int(now.timestamp())
date = now.strftime("%Y-%m-%d")
+1 -8
View File
@@ -5,7 +5,7 @@ import logging
from homeassistant.config_entries import SOURCE_IMPORT
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import config_validation as cv, entity_registry as er
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.typing import ConfigType
@@ -50,13 +50,6 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
async def async_setup_entry(hass: HomeAssistant, entry: SunConfigEntry) -> bool:
"""Set up from a config entry."""
# Remove deprecated solar_rising sensor entity (removed in 2026.1)
ent_reg = er.async_get(hass)
if entity_id := ent_reg.async_get_entity_id(
Platform.SENSOR, DOMAIN, f"{entry.entry_id}-solar_rising"
):
ent_reg.async_remove(entity_id)
sun = Sun(hass)
component = EntityComponent[Sun](_LOGGER, DOMAIN, hass)
await component.async_add_entities([sun])
@@ -57,7 +57,7 @@ class SwitchBotCloudImage(SwitchBotCloudEntity, ImageEntity):
"""Set attributes from coordinator data."""
if self.coordinator.data is None:
return
self._attr_image_last_updated = datetime.datetime.now() # pylint: disable=home-assistant-enforce-naive-now
self._attr_image_last_updated = datetime.datetime.now()
self._attr_image_url = self.coordinator.data.get("imageUrl")
+1 -1
View File
@@ -447,7 +447,7 @@ class TadoDataUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
async def set_meter_reading(self, reading: int) -> dict[str, Any]:
"""Send meter reading to Tado."""
dt: str = datetime.now().strftime("%Y-%m-%d") # pylint: disable=home-assistant-enforce-naive-now
dt: str = datetime.now().strftime("%Y-%m-%d")
if self._tado is None:
raise HomeAssistantError("Tado client is not initialized")
+10 -16
View File
@@ -1,12 +1,15 @@
"""Support for Template fans."""
from enum import StrEnum
import logging
from typing import TYPE_CHECKING, Any
import voluptuous as vol
from homeassistant.components.fan import (
ATTR_DIRECTION,
ATTR_OSCILLATING,
ATTR_PERCENTAGE,
ATTR_PRESET_MODE,
DIRECTION_FORWARD,
DIRECTION_REVERSE,
DOMAIN as FAN_DOMAIN,
@@ -97,15 +100,6 @@ FAN_CONFIG_ENTRY_SCHEMA = FAN_COMMON_SCHEMA.extend(
)
class FanScriptVariable(StrEnum):
"""Variables for scripts."""
DIRECTION = "direction"
OSCILLATING = "oscillating"
PERCENTAGE = "percentage"
PRESET_MODE = "preset_mode"
async def async_setup_platform(
hass: HomeAssistant,
config: ConfigType,
@@ -241,8 +235,8 @@ class AbstractTemplateFan(AbstractTemplateEntity, FanEntity):
await self.async_run_script(
self._action_scripts[CONF_ON_ACTION],
run_variables={
FanScriptVariable.PERCENTAGE: percentage,
FanScriptVariable.PRESET_MODE: preset_mode,
ATTR_PERCENTAGE: percentage,
ATTR_PRESET_MODE: preset_mode,
},
context=self._context,
)
@@ -273,7 +267,7 @@ class AbstractTemplateFan(AbstractTemplateEntity, FanEntity):
if script := self._action_scripts.get(CONF_SET_PERCENTAGE_ACTION):
await self.async_run_script(
script,
run_variables={FanScriptVariable.PERCENTAGE: self._attr_percentage},
run_variables={ATTR_PERCENTAGE: self._attr_percentage},
context=self._context,
)
@@ -290,7 +284,7 @@ class AbstractTemplateFan(AbstractTemplateEntity, FanEntity):
if script := self._action_scripts.get(CONF_SET_PRESET_MODE_ACTION):
await self.async_run_script(
script,
run_variables={FanScriptVariable.PRESET_MODE: self._attr_preset_mode},
run_variables={ATTR_PRESET_MODE: self._attr_preset_mode},
context=self._context,
)
@@ -308,7 +302,7 @@ class AbstractTemplateFan(AbstractTemplateEntity, FanEntity):
) is not None:
await self.async_run_script(
script,
run_variables={FanScriptVariable.OSCILLATING: self.oscillating},
run_variables={ATTR_OSCILLATING: self.oscillating},
context=self._context,
)
@@ -324,7 +318,7 @@ class AbstractTemplateFan(AbstractTemplateEntity, FanEntity):
) is not None:
await self.async_run_script(
script,
run_variables={FanScriptVariable.DIRECTION: direction},
run_variables={ATTR_DIRECTION: direction},
context=self._context,
)
if CONF_DIRECTION not in self._templates:
+2 -1
View File
@@ -5,6 +5,7 @@ from typing import TYPE_CHECKING, Any
import voluptuous as vol
from homeassistant.components.number import (
ATTR_VALUE,
DEFAULT_MAX_VALUE,
DEFAULT_MIN_VALUE,
DEFAULT_STEP,
@@ -160,7 +161,7 @@ class AbstractTemplateNumber(AbstractTemplateEntity, NumberEntity):
if set_value := self._action_scripts.get(CONF_SET_VALUE):
await self.async_run_script(
set_value,
run_variables={"value": value},
run_variables={ATTR_VALUE: value},
context=self._context,
)

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