This commit is contained in:
Franck Nijhof 2024-10-11 18:25:02 +02:00 committed by GitHub
commit 6952d2420f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
121 changed files with 925 additions and 455 deletions

View File

@ -37,7 +37,7 @@ on:
type: boolean type: boolean
env: env:
CACHE_VERSION: 10 CACHE_VERSION: 11
UV_CACHE_VERSION: 1 UV_CACHE_VERSION: 1
MYPY_CACHE_VERSION: 9 MYPY_CACHE_VERSION: 9
HA_SHORT_VERSION: "2024.10" HA_SHORT_VERSION: "2024.10"

View File

@ -6,6 +6,6 @@
"documentation": "https://www.home-assistant.io/integrations/airgradient", "documentation": "https://www.home-assistant.io/integrations/airgradient",
"integration_type": "device", "integration_type": "device",
"iot_class": "local_polling", "iot_class": "local_polling",
"requirements": ["airgradient==0.9.0"], "requirements": ["airgradient==0.9.1"],
"zeroconf": ["_airgradient._tcp.local."] "zeroconf": ["_airgradient._tcp.local."]
} }

View File

@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/airzone_cloud", "documentation": "https://www.home-assistant.io/integrations/airzone_cloud",
"iot_class": "cloud_push", "iot_class": "cloud_push",
"loggers": ["aioairzone_cloud"], "loggers": ["aioairzone_cloud"],
"requirements": ["aioairzone-cloud==0.6.5"] "requirements": ["aioairzone-cloud==0.6.6"]
} }

View File

@ -22,7 +22,8 @@
} }
}, },
"error": { "error": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]" "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"unknown": "[%key:common::config_flow::error::unknown%]"
}, },
"create_entry": { "create_entry": {
"default": "Successfully connected to AlarmDecoder." "default": "Successfully connected to AlarmDecoder."
@ -37,7 +38,7 @@
"title": "Configure AlarmDecoder", "title": "Configure AlarmDecoder",
"description": "What would you like to edit?", "description": "What would you like to edit?",
"data": { "data": {
"edit_select": "Edit" "edit_selection": "Edit"
} }
}, },
"arm_settings": { "arm_settings": {

View File

@ -10,7 +10,7 @@
}, },
"site": { "site": {
"data": { "data": {
"site_nmi": "Site NMI", "site_id": "Site NMI",
"site_name": "Site Name" "site_name": "Site Name"
}, },
"description": "Select the NMI of the site you would like to add" "description": "Select the NMI of the site you would like to add"

View File

@ -17,7 +17,7 @@
"unknown": "[%key:common::config_flow::error::unknown%]" "unknown": "[%key:common::config_flow::error::unknown%]"
}, },
"error": { "error": {
"no_integration_selected": "You must select at least one integration to track" "no_integrations_selected": "You must select at least one integration to track"
} }
}, },
"options": { "options": {
@ -37,7 +37,7 @@
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]" "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]"
}, },
"error": { "error": {
"no_integration_selected": "[%key:component::analytics_insights::config::error::no_integration_selected%]" "no_integrations_selected": "[%key:component::analytics_insights::config::error::no_integrations_selected%]"
} }
}, },
"entity": { "entity": {

View File

@ -4,6 +4,7 @@ from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform from homeassistant.const import Platform
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from .const import CONF_THRESHOLD, DEFAULT_THRESHOLD
from .coordinator import AuroraDataUpdateCoordinator from .coordinator import AuroraDataUpdateCoordinator
PLATFORMS = [Platform.BINARY_SENSOR, Platform.SENSOR] PLATFORMS = [Platform.BINARY_SENSOR, Platform.SENSOR]
@ -21,9 +22,19 @@ async def async_setup_entry(hass: HomeAssistant, entry: AuroraConfigEntry) -> bo
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
entry.async_on_unload(entry.add_update_listener(update_listener))
return True return True
async def update_listener(hass: HomeAssistant, entry: AuroraConfigEntry) -> None:
"""Handle options update."""
entry.runtime_data.threshold = int(
entry.options.get(CONF_THRESHOLD, DEFAULT_THRESHOLD)
)
# refresh the state of the visibility alert binary sensor
await entry.runtime_data.async_request_refresh()
async def async_unload_entry(hass: HomeAssistant, entry: AuroraConfigEntry) -> bool: async def async_unload_entry(hass: HomeAssistant, entry: AuroraConfigEntry) -> bool:
"""Unload a config entry.""" """Unload a config entry."""
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS) return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)

View File

@ -38,8 +38,8 @@ class AuroraDataUpdateCoordinator(DataUpdateCoordinator[int]):
) )
self.api = AuroraForecast(async_get_clientsession(hass)) self.api = AuroraForecast(async_get_clientsession(hass))
self.latitude = int(self.config_entry.data[CONF_LATITUDE]) self.latitude = round(self.config_entry.data[CONF_LATITUDE])
self.longitude = int(self.config_entry.data[CONF_LONGITUDE]) self.longitude = round(self.config_entry.data[CONF_LONGITUDE])
self.threshold = int( self.threshold = int(
self.config_entry.options.get(CONF_THRESHOLD, DEFAULT_THRESHOLD) self.config_entry.options.get(CONF_THRESHOLD, DEFAULT_THRESHOLD)
) )

View File

@ -14,14 +14,15 @@
"already_configured": "[%key:common::config_flow::abort::already_configured_service%]" "already_configured": "[%key:common::config_flow::abort::already_configured_service%]"
}, },
"error": { "error": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]" "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"unknown": "[%key:common::config_flow::error::unknown%]"
} }
}, },
"options": { "options": {
"step": { "step": {
"init": { "init": {
"data": { "data": {
"threshold": "Threshold (%)" "forecast_threshold": "Threshold (%)"
} }
} }
} }

View File

@ -38,7 +38,7 @@
}, },
"options": { "options": {
"step": { "step": {
"options": { "init": {
"title": "Options for the Azure Event Hub.", "title": "Options for the Azure Event Hub.",
"data": { "data": {
"send_interval": "Interval between sending batches to the hub." "send_interval": "Interval between sending batches to the hub."

View File

@ -15,7 +15,9 @@
"description": "Set up your BleBox to integrate with Home Assistant.", "description": "Set up your BleBox to integrate with Home Assistant.",
"data": { "data": {
"host": "[%key:common::config_flow::data::ip%]", "host": "[%key:common::config_flow::data::ip%]",
"port": "[%key:common::config_flow::data::port%]" "password": "[%key:common::config_flow::data::password%]",
"port": "[%key:common::config_flow::data::port%]",
"username": "[%key:common::config_flow::data::username%]"
}, },
"title": "Set up your BleBox device" "title": "Set up your BleBox device"
} }

View File

@ -6,7 +6,7 @@
"config_flow": true, "config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/bluesound", "documentation": "https://www.home-assistant.io/integrations/bluesound",
"iot_class": "local_polling", "iot_class": "local_polling",
"requirements": ["pyblu==1.0.2"], "requirements": ["pyblu==1.0.3"],
"zeroconf": [ "zeroconf": [
{ {
"type": "_musc._tcp.local." "type": "_musc._tcp.local."

View File

@ -1,7 +1,7 @@
{ {
"config": { "config": {
"step": { "step": {
"reconfigure": { "reconfigure_confirm": {
"data": { "data": {
"filename": "[%key:component::bryant_evolution::config::step::user::data::filename%]" "filename": "[%key:component::bryant_evolution::config::step::user::data::filename%]"
} }

View File

@ -34,7 +34,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
username=entry.data[CONF_USERNAME], username=entry.data[CONF_USERNAME],
password=entry.data[CONF_PASSWORD], password=entry.data[CONF_PASSWORD],
ssl_verify_cert=entry.data[CONF_VERIFY_SSL], ssl_verify_cert=entry.data[CONF_VERIFY_SSL],
timeout=10, timeout=30,
) )
try: try:
await hass.async_add_executor_job(client.principal) await hass.async_add_executor_job(client.principal)

View File

@ -7,6 +7,6 @@
"integration_type": "device", "integration_type": "device",
"iot_class": "local_push", "iot_class": "local_push",
"loggers": ["aiostreammagic"], "loggers": ["aiostreammagic"],
"requirements": ["aiostreammagic==2.3.1"], "requirements": ["aiostreammagic==2.5.0"],
"zeroconf": ["_stream-magic._tcp.local.", "_smoip._tcp.local."] "zeroconf": ["_stream-magic._tcp.local.", "_smoip._tcp.local."]
} }

View File

@ -14,6 +14,6 @@
"documentation": "https://www.home-assistant.io/integrations/cast", "documentation": "https://www.home-assistant.io/integrations/cast",
"iot_class": "local_polling", "iot_class": "local_polling",
"loggers": ["casttube", "pychromecast"], "loggers": ["casttube", "pychromecast"],
"requirements": ["PyChromecast==14.0.1"], "requirements": ["PyChromecast==14.0.3"],
"zeroconf": ["_googlecast._tcp.local."] "zeroconf": ["_googlecast._tcp.local."]
} }

View File

@ -7,7 +7,7 @@
"documentation": "https://www.home-assistant.io/integrations/doorbird", "documentation": "https://www.home-assistant.io/integrations/doorbird",
"iot_class": "local_push", "iot_class": "local_push",
"loggers": ["doorbirdpy"], "loggers": ["doorbirdpy"],
"requirements": ["DoorBirdPy==3.0.2"], "requirements": ["DoorBirdPy==3.0.4"],
"zeroconf": [ "zeroconf": [
{ {
"type": "_axis-video._tcp.local.", "type": "_axis-video._tcp.local.",

View File

@ -7,5 +7,5 @@
"iot_class": "local_push", "iot_class": "local_push",
"loggers": ["pyduotecno", "pyduotecno-node", "pyduotecno-unit"], "loggers": ["pyduotecno", "pyduotecno-node", "pyduotecno-unit"],
"quality_scale": "silver", "quality_scale": "silver",
"requirements": ["pyDuotecno==2024.9.0"] "requirements": ["pyDuotecno==2024.10.0"]
} }

View File

@ -5,18 +5,21 @@
"data": { "data": {
"host": "[%key:common::config_flow::data::host%]", "host": "[%key:common::config_flow::data::host%]",
"username": "[%key:common::config_flow::data::username%]", "username": "[%key:common::config_flow::data::username%]",
"password": "[%key:common::config_flow::data::password%]" "password": "[%key:common::config_flow::data::password%]",
"port": "[%key:common::config_flow::data::port%]"
}, },
"data_description": { "data_description": {
"host": "The hostname or IP address of your Duotecno device." "host": "The hostname or IP address of your Duotecno device."
} }
} }
}, },
"abort": {
"single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]"
},
"error": { "error": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]", "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
"unknown": "[%key:common::config_flow::error::unknown%]", "unknown": "[%key:common::config_flow::error::unknown%]"
"single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]"
} }
}, },
"entity": { "entity": {

View File

@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/econet", "documentation": "https://www.home-assistant.io/integrations/econet",
"iot_class": "cloud_push", "iot_class": "cloud_push",
"loggers": ["paho_mqtt", "pyeconet"], "loggers": ["paho_mqtt", "pyeconet"],
"requirements": ["pyeconet==0.1.22"] "requirements": ["pyeconet==0.1.23"]
} }

View File

@ -47,6 +47,7 @@
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]" "reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]"
}, },
"error": { "error": {
"no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]",
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]" "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]"
} }
}, },

View File

@ -20,5 +20,5 @@
"documentation": "https://www.home-assistant.io/integrations/frontend", "documentation": "https://www.home-assistant.io/integrations/frontend",
"integration_type": "system", "integration_type": "system",
"quality_scale": "internal", "quality_scale": "internal",
"requirements": ["home-assistant-frontend==20241002.2"] "requirements": ["home-assistant-frontend==20241002.3"]
} }

View File

@ -9,5 +9,5 @@ DOMAIN = "fujitsu_fglair"
CONF_REGION = "region" CONF_REGION = "region"
CONF_EUROPE = "is_europe" CONF_EUROPE = "is_europe"
REGION_EU = "EU" REGION_EU = "eu"
REGION_DEFAULT = "default" REGION_DEFAULT = "default"

View File

@ -5,5 +5,5 @@
"config_flow": true, "config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/fujitsu_fglair", "documentation": "https://www.home-assistant.io/integrations/fujitsu_fglair",
"iot_class": "cloud_polling", "iot_class": "cloud_polling",
"requirements": ["ayla-iot-unofficial==1.4.1"] "requirements": ["ayla-iot-unofficial==1.4.2"]
} }

View File

@ -7,5 +7,5 @@
"integration_type": "hub", "integration_type": "hub",
"iot_class": "cloud_polling", "iot_class": "cloud_polling",
"quality_scale": "platinum", "quality_scale": "platinum",
"requirements": ["fyta_cli==0.6.6"] "requirements": ["fyta_cli==0.6.7"]
} }

View File

@ -172,10 +172,12 @@ class BaseGoogleCloudProvider:
_LOGGER.error("Error: %s when validating options: %s", err, options) _LOGGER.error("Error: %s when validating options: %s", err, options)
return None, None return None, None
encoding = texttospeech.AudioEncoding(options[CONF_ENCODING]) encoding: texttospeech.AudioEncoding = texttospeech.AudioEncoding[
gender: texttospeech.SsmlVoiceGender | None = texttospeech.SsmlVoiceGender( options[CONF_ENCODING]
] # type: ignore[misc]
gender: texttospeech.SsmlVoiceGender | None = texttospeech.SsmlVoiceGender[
options[CONF_GENDER] options[CONF_GENDER]
) ] # type: ignore[misc]
voice = options[CONF_VOICE] voice = options[CONF_VOICE]
if voice: if voice:
gender = None gender = None

View File

@ -21,7 +21,8 @@
"wrong_account": "Wrong account: Please authenticate with the right account.", "wrong_account": "Wrong account: Please authenticate with the right account.",
"oauth_timeout": "[%key:common::config_flow::abort::oauth2_timeout%]", "oauth_timeout": "[%key:common::config_flow::abort::oauth2_timeout%]",
"oauth_unauthorized": "[%key:common::config_flow::abort::oauth2_unauthorized%]", "oauth_unauthorized": "[%key:common::config_flow::abort::oauth2_unauthorized%]",
"oauth_failed": "[%key:common::config_flow::abort::oauth2_failed%]" "oauth_failed": "[%key:common::config_flow::abort::oauth2_failed%]",
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
}, },
"create_entry": { "create_entry": {
"default": "[%key:common::config_flow::create_entry::authenticated%]" "default": "[%key:common::config_flow::create_entry::authenticated%]"

View File

@ -21,7 +21,8 @@
"wrong_account": "Wrong account: Please authenticate with the right account.", "wrong_account": "Wrong account: Please authenticate with the right account.",
"oauth_timeout": "[%key:common::config_flow::abort::oauth2_timeout%]", "oauth_timeout": "[%key:common::config_flow::abort::oauth2_timeout%]",
"oauth_unauthorized": "[%key:common::config_flow::abort::oauth2_unauthorized%]", "oauth_unauthorized": "[%key:common::config_flow::abort::oauth2_unauthorized%]",
"oauth_failed": "[%key:common::config_flow::abort::oauth2_failed%]" "oauth_failed": "[%key:common::config_flow::abort::oauth2_failed%]",
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
}, },
"create_entry": { "create_entry": {
"default": "[%key:common::config_flow::create_entry::authenticated%]" "default": "[%key:common::config_flow::create_entry::authenticated%]"

View File

@ -14,6 +14,9 @@ from homeassistant.util import dt as dt_util
def next_due_date(task: dict[str, Any], last_cron: str) -> datetime.date | None: def next_due_date(task: dict[str, Any], last_cron: str) -> datetime.date | None:
"""Calculate due date for dailies and yesterdailies.""" """Calculate due date for dailies and yesterdailies."""
if task["everyX"] == 0 or not task.get("nextDue"): # grey dailies never become due
return None
today = to_date(last_cron) today = to_date(last_cron)
startdate = to_date(task["startDate"]) startdate = to_date(task["startDate"])
if TYPE_CHECKING: if TYPE_CHECKING:

View File

@ -24,11 +24,11 @@
}, },
"cmd": { "cmd": {
"name": "Command", "name": "Command",
"description": "Command itself. Could be decimal number or string with hexadeximal notation: \"0x10\"." "description": "Command itself. Could be decimal number or string with hexadecimal notation: \"0x10\"."
}, },
"dst": { "dst": {
"name": "Destination", "name": "Destination",
"description": "Destination for command. Could be decimal number or string with hexadeximal notation: \"0x10\"." "description": "Destination for command. Could be decimal number or string with hexadecimal notation: \"0x10\"."
}, },
"raw": { "raw": {
"name": "Raw", "name": "Raw",
@ -36,7 +36,7 @@
}, },
"src": { "src": {
"name": "Source", "name": "Source",
"description": "Source of command. Could be decimal number or string with hexadeximal notation: \"0x10\"." "description": "Source of command. Could be decimal number or string with hexadecimal notation: \"0x10\"."
} }
} }
}, },

View File

@ -5,5 +5,5 @@
"config_flow": true, "config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/holiday", "documentation": "https://www.home-assistant.io/integrations/holiday",
"iot_class": "local_polling", "iot_class": "local_polling",
"requirements": ["holidays==0.57", "babel==2.15.0"] "requirements": ["holidays==0.58", "babel==2.15.0"]
} }

View File

@ -23,7 +23,7 @@ _LOGGER = logging.getLogger(__name__)
HAP_SUFFIX = "._hap._tcp.local." HAP_SUFFIX = "._hap._tcp.local."
POWERVIEW_G2_SUFFIX = "._powerview._tcp.local." POWERVIEW_G2_SUFFIX = "._powerview._tcp.local."
POWERVIEW_G3_SUFFIX = "._powerview-g3._tcp.local." POWERVIEW_G3_SUFFIX = "._PowerView-G3._tcp.local."
async def validate_input(hass: HomeAssistant, hub_address: str) -> dict[str, str]: async def validate_input(hass: HomeAssistant, hub_address: str) -> dict[str, str]:

View File

@ -19,5 +19,5 @@
"iot_class": "local_polling", "iot_class": "local_polling",
"loggers": ["aiopvapi"], "loggers": ["aiopvapi"],
"requirements": ["aiopvapi==3.1.1"], "requirements": ["aiopvapi==3.1.1"],
"zeroconf": ["_powerview._tcp.local.", "_powerview-g3._tcp.local."] "zeroconf": ["_powerview._tcp.local.", "_PowerView-G3._tcp.local."]
} }

View File

@ -125,7 +125,9 @@ class AutomowerBaseEntity(CoordinatorEntity[AutomowerDataUpdateCoordinator]):
self._attr_device_info = DeviceInfo( self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, mower_id)}, identifiers={(DOMAIN, mower_id)},
manufacturer="Husqvarna", manufacturer="Husqvarna",
model=self.mower_attributes.system.model, model=self.mower_attributes.system.model.removeprefix(
"HUSQVARNA "
).removeprefix("Husqvarna "),
name=self.mower_attributes.system.name, name=self.mower_attributes.system.name,
serial_number=self.mower_attributes.system.serial_number, serial_number=self.mower_attributes.system.serial_number,
suggested_area="Garden", suggested_area="Garden",

View File

@ -7,5 +7,5 @@
"documentation": "https://www.home-assistant.io/integrations/husqvarna_automower", "documentation": "https://www.home-assistant.io/integrations/husqvarna_automower",
"iot_class": "cloud_push", "iot_class": "cloud_push",
"loggers": ["aioautomower"], "loggers": ["aioautomower"],
"requirements": ["aioautomower==2024.9.3"] "requirements": ["aioautomower==2024.10.0"]
} }

View File

@ -10,7 +10,7 @@ DEFAULT_WATERING_TIME = timedelta(minutes=15)
MANUFACTURER = "Hydrawise" MANUFACTURER = "Hydrawise"
SCAN_INTERVAL = timedelta(seconds=30) SCAN_INTERVAL = timedelta(seconds=60)
SIGNAL_UPDATE_HYDRAWISE = "hydrawise_update" SIGNAL_UPDATE_HYDRAWISE = "hydrawise_update"

View File

@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/imgw_pib", "documentation": "https://www.home-assistant.io/integrations/imgw_pib",
"iot_class": "cloud_polling", "iot_class": "cloud_polling",
"quality_scale": "platinum", "quality_scale": "platinum",
"requirements": ["imgw_pib==1.0.5"] "requirements": ["imgw_pib==1.0.6"]
} }

View File

@ -29,7 +29,8 @@
"invalid_host": "The host entry was not in full URL format, e.g., http://192.168.10.100:80" "invalid_host": "The host entry was not in full URL format, e.g., http://192.168.10.100:80"
}, },
"abort": { "abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]" "already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
} }
}, },
"options": { "options": {

View File

@ -24,6 +24,7 @@
"abort": { "abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]", "already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
"already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]", "already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]",
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]",
"unknown": "[%key:common::config_flow::error::unknown%]" "unknown": "[%key:common::config_flow::error::unknown%]"
}, },
"error": { "error": {

View File

@ -12,7 +12,7 @@
"quality_scale": "platinum", "quality_scale": "platinum",
"requirements": [ "requirements": [
"xknx==3.2.0", "xknx==3.2.0",
"xknxproject==3.8.0", "xknxproject==3.8.1",
"knx-frontend==2024.9.10.221729" "knx-frontend==2024.9.10.221729"
], ],
"single_config_entry": true "single_config_entry": true

View File

@ -6,6 +6,7 @@
"documentation": "https://www.home-assistant.io/integrations/linkplay", "documentation": "https://www.home-assistant.io/integrations/linkplay",
"integration_type": "hub", "integration_type": "hub",
"iot_class": "local_polling", "iot_class": "local_polling",
"requirements": ["python-linkplay==0.0.12"], "loggers": ["linkplay"],
"requirements": ["python-linkplay==0.0.15"],
"zeroconf": ["_linkplay._tcp.local."] "zeroconf": ["_linkplay._tcp.local."]
} }

View File

@ -19,7 +19,8 @@
} }
}, },
"abort": { "abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_account%]" "already_configured": "[%key:common::config_flow::abort::already_configured_account%]",
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
}, },
"error": { "error": {
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]", "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",

View File

@ -21,6 +21,7 @@
"no_url_available": "[%key:common::config_flow::abort::oauth2_no_url_available%]", "no_url_available": "[%key:common::config_flow::abort::oauth2_no_url_available%]",
"user_rejected_authorize": "[%key:common::config_flow::abort::oauth2_user_rejected_authorize%]", "user_rejected_authorize": "[%key:common::config_flow::abort::oauth2_user_rejected_authorize%]",
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]", "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]",
"unknown": "[%key:common::config_flow::error::unknown%]", "unknown": "[%key:common::config_flow::error::unknown%]",
"wrong_account": "You can only reauthenticate this entry with the same microBees account." "wrong_account": "You can only reauthenticate this entry with the same microBees account."
}, },

View File

@ -14,5 +14,5 @@
"integration_type": "device", "integration_type": "device",
"iot_class": "assumed_state", "iot_class": "assumed_state",
"loggers": ["motionblindsble"], "loggers": ["motionblindsble"],
"requirements": ["motionblindsble==0.1.1"] "requirements": ["motionblindsble==0.1.2"]
} }

View File

@ -23,7 +23,7 @@ class NYTGamesData:
wordle: Wordle wordle: Wordle
spelling_bee: SpellingBee | None spelling_bee: SpellingBee | None
connections: Connections connections: Connections | None
class NYTGamesCoordinator(DataUpdateCoordinator[NYTGamesData]): class NYTGamesCoordinator(DataUpdateCoordinator[NYTGamesData]):

View File

@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/nyt_games", "documentation": "https://www.home-assistant.io/integrations/nyt_games",
"integration_type": "service", "integration_type": "service",
"iot_class": "cloud_polling", "iot_class": "cloud_polling",
"requirements": ["nyt_games==0.4.2"] "requirements": ["nyt_games==0.4.3"]
} }

View File

@ -161,10 +161,11 @@ async def async_setup_entry(
NYTGamesSpellingBeeSensor(coordinator, description) NYTGamesSpellingBeeSensor(coordinator, description)
for description in SPELLING_BEE_SENSORS for description in SPELLING_BEE_SENSORS
) )
entities.extend( if coordinator.data.connections is not None:
NYTGamesConnectionsSensor(coordinator, description) entities.extend(
for description in CONNECTIONS_SENSORS NYTGamesConnectionsSensor(coordinator, description)
) for description in CONNECTIONS_SENSORS
)
async_add_entities(entities) async_add_entities(entities)
@ -236,4 +237,5 @@ class NYTGamesConnectionsSensor(ConnectionsEntity, SensorEntity):
@property @property
def native_value(self) -> StateType | date: def native_value(self) -> StateType | date:
"""Return the state of the sensor.""" """Return the state of the sensor."""
assert self.coordinator.data.connections is not None
return self.entity_description.value_fn(self.coordinator.data.connections) return self.entity_description.value_fn(self.coordinator.data.connections)

View File

@ -130,20 +130,32 @@ class OpowerCoordinator(DataUpdateCoordinator[dict[str, Forecast]]):
continue continue
start = cost_reads[0].start_time start = cost_reads[0].start_time
_LOGGER.debug("Getting statistics at: %s", start) _LOGGER.debug("Getting statistics at: %s", start)
stats = await get_instance(self.hass).async_add_executor_job( # In the common case there should be a previous statistic at start time
statistics_during_period, # so we only need to fetch one statistic. If there isn't any, fetch all.
self.hass, for end in (start + timedelta(seconds=1), None):
start, stats = await get_instance(self.hass).async_add_executor_job(
start + timedelta(seconds=1), statistics_during_period,
{cost_statistic_id, consumption_statistic_id}, self.hass,
"hour", start,
None, end,
{"sum"}, {cost_statistic_id, consumption_statistic_id},
) "hour",
None,
{"sum"},
)
if stats:
break
if end:
_LOGGER.debug(
"Not found. Trying to find the oldest statistic after %s",
start,
)
# We are in this code path only if get_last_statistics found a stat
# so statistics_during_period should also have found at least one.
assert stats
cost_sum = cast(float, stats[cost_statistic_id][0]["sum"]) cost_sum = cast(float, stats[cost_statistic_id][0]["sum"])
consumption_sum = cast(float, stats[consumption_statistic_id][0]["sum"]) consumption_sum = cast(float, stats[consumption_statistic_id][0]["sum"])
last_stats_time = stats[consumption_statistic_id][0]["start"] last_stats_time = stats[consumption_statistic_id][0]["start"]
assert last_stats_time == start.timestamp()
cost_statistics = [] cost_statistics = []
consumption_statistics = [] consumption_statistics = []

View File

@ -7,5 +7,5 @@
"documentation": "https://www.home-assistant.io/integrations/opower", "documentation": "https://www.home-assistant.io/integrations/opower",
"iot_class": "cloud_polling", "iot_class": "cloud_polling",
"loggers": ["opower"], "loggers": ["opower"],
"requirements": ["opower==0.8.0"] "requirements": ["opower==0.8.3"]
} }

View File

@ -13,7 +13,9 @@
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]" "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]"
}, },
"abort": { "abort": {
"single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]" "already_configured": "The Thread border router is already configured",
"single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]",
"unknown": "[%key:common::config_flow::error::unknown%]"
} }
}, },
"issues": { "issues": {

View File

@ -32,7 +32,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
client_session=async_get_clientsession(hass), client_session=async_get_clientsession(hass),
) )
if custom_account := entry.data.get(CONF_ACCOUNT) is not None: if (custom_account := entry.data.get(CONF_ACCOUNT)) is not None:
client.custom_account_id = custom_account client.custom_account_id = custom_account
try: try:
@ -49,7 +49,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_update_data() -> OVODailyUsage: async def async_update_data() -> OVODailyUsage:
"""Fetch data from OVO Energy.""" """Fetch data from OVO Energy."""
if custom_account := entry.data.get(CONF_ACCOUNT) is not None: if (custom_account := entry.data.get(CONF_ACCOUNT)) is not None:
client.custom_account_id = custom_account client.custom_account_id = custom_account
async with asyncio.timeout(10): async with asyncio.timeout(10):

View File

@ -46,7 +46,7 @@ class OVOEnergyFlowHandler(ConfigFlow, domain=DOMAIN):
client_session=async_get_clientsession(self.hass), client_session=async_get_clientsession(self.hass),
) )
if custom_account := user_input.get(CONF_ACCOUNT) is not None: if (custom_account := user_input.get(CONF_ACCOUNT)) is not None:
client.custom_account_id = custom_account client.custom_account_id = custom_account
try: try:

View File

@ -10,6 +10,7 @@ from aiopyarr import exceptions
from aiopyarr.models.host_configuration import PyArrHostConfiguration from aiopyarr.models.host_configuration import PyArrHostConfiguration
from aiopyarr.radarr_client import RadarrClient from aiopyarr.radarr_client import RadarrClient
import voluptuous as vol import voluptuous as vol
from yarl import URL
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
from homeassistant.const import CONF_API_KEY, CONF_URL, CONF_VERIFY_SSL from homeassistant.const import CONF_API_KEY, CONF_URL, CONF_VERIFY_SSL
@ -54,6 +55,12 @@ class RadarrConfigFlow(ConfigFlow, domain=DOMAIN):
user_input = dict(self.entry.data) if self.entry else None user_input = dict(self.entry.data) if self.entry else None
else: else:
# aiopyarr defaults to the service port if one isn't given
# this is counter to standard practice where http = 80
# and https = 443.
url = URL(user_input[CONF_URL])
user_input[CONF_URL] = f"{url.scheme}://{url.host}:{url.port}{url.path}"
try: try:
if result := await validate_input(self.hass, user_input): if result := await validate_input(self.hass, user_input):
user_input[CONF_API_KEY] = result[1] user_input[CONF_API_KEY] = result[1]

View File

@ -10,13 +10,9 @@ import uuid
from ring_doorbell import Auth, Ring, RingDevices from ring_doorbell import Auth, Ring, RingDevices
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import APPLICATION_NAME, CONF_TOKEN from homeassistant.const import APPLICATION_NAME, CONF_DEVICE_ID, CONF_TOKEN
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import ( from homeassistant.helpers import device_registry as dr, entity_registry as er
device_registry as dr,
entity_registry as er,
instance_id,
)
from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.aiohttp_client import async_get_clientsession
from .const import CONF_LISTEN_CREDENTIALS, DOMAIN, PLATFORMS from .const import CONF_LISTEN_CREDENTIALS, DOMAIN, PLATFORMS
@ -38,18 +34,12 @@ class RingData:
type RingConfigEntry = ConfigEntry[RingData] type RingConfigEntry = ConfigEntry[RingData]
async def get_auth_agent_id(hass: HomeAssistant) -> tuple[str, str]: def get_auth_user_agent() -> str:
"""Return user-agent and hardware id for Auth instantiation. """Return user-agent for Auth instantiation.
user_agent will be the display name in the ring.com authorised devices. user_agent will be the display name in the ring.com authorised devices.
hardware_id will uniquely describe the authorised HA device.
""" """
user_agent = f"{APPLICATION_NAME}/{DOMAIN}-integration" return f"{APPLICATION_NAME}/{DOMAIN}-integration"
# Generate a new uuid from the instance_uuid to keep the HA one private
instance_uuid = uuid.UUID(hex=await instance_id.async_get(hass))
hardware_id = str(uuid.uuid5(instance_uuid, user_agent))
return user_agent, hardware_id
async def async_setup_entry(hass: HomeAssistant, entry: RingConfigEntry) -> bool: async def async_setup_entry(hass: HomeAssistant, entry: RingConfigEntry) -> bool:
@ -69,13 +59,13 @@ async def async_setup_entry(hass: HomeAssistant, entry: RingConfigEntry) -> bool
data={**entry.data, CONF_LISTEN_CREDENTIALS: token}, data={**entry.data, CONF_LISTEN_CREDENTIALS: token},
) )
user_agent, hardware_id = await get_auth_agent_id(hass) user_agent = get_auth_user_agent()
client_session = async_get_clientsession(hass) client_session = async_get_clientsession(hass)
auth = Auth( auth = Auth(
user_agent, user_agent,
entry.data[CONF_TOKEN], entry.data[CONF_TOKEN],
token_updater, token_updater,
hardware_id=hardware_id, hardware_id=entry.data[CONF_DEVICE_ID],
http_client_session=client_session, http_client_session=client_session,
) )
ring = Ring(auth) ring = Ring(auth)
@ -138,3 +128,25 @@ async def _migrate_old_unique_ids(hass: HomeAssistant, entry_id: str) -> None:
return None return None
await er.async_migrate_entries(hass, entry_id, _async_migrator) await er.async_migrate_entries(hass, entry_id, _async_migrator)
async def async_migrate_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Migrate old config entry."""
entry_version = entry.version
entry_minor_version = entry.minor_version
new_minor_version = 2
if entry_version == 1 and entry_minor_version == 1:
_LOGGER.debug(
"Migrating from version %s.%s", entry_version, entry_minor_version
)
hardware_id = str(uuid.uuid4())
hass.config_entries.async_update_entry(
entry,
data={**entry.data, CONF_DEVICE_ID: hardware_id},
minor_version=new_minor_version,
)
_LOGGER.debug(
"Migration to version %s.%s complete", entry_version, new_minor_version
)
return True

View File

@ -3,18 +3,25 @@
from collections.abc import Mapping from collections.abc import Mapping
import logging import logging
from typing import Any from typing import Any
import uuid
from ring_doorbell import Auth, AuthenticationError, Requires2FAError from ring_doorbell import Auth, AuthenticationError, Requires2FAError
import voluptuous as vol import voluptuous as vol
from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult
from homeassistant.const import CONF_PASSWORD, CONF_TOKEN, CONF_USERNAME from homeassistant.const import (
CONF_DEVICE_ID,
CONF_NAME,
CONF_PASSWORD,
CONF_TOKEN,
CONF_USERNAME,
)
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.aiohttp_client import async_get_clientsession
from . import get_auth_agent_id from . import get_auth_user_agent
from .const import CONF_2FA, DOMAIN from .const import CONF_2FA, CONF_CONFIG_ENTRY_MINOR_VERSION, DOMAIN
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -23,11 +30,15 @@ STEP_USER_DATA_SCHEMA = vol.Schema(
) )
STEP_REAUTH_DATA_SCHEMA = vol.Schema({vol.Required(CONF_PASSWORD): str}) STEP_REAUTH_DATA_SCHEMA = vol.Schema({vol.Required(CONF_PASSWORD): str})
STEP_RECONFIGURE_DATA_SCHEMA = vol.Schema({vol.Required(CONF_PASSWORD): str})
async def validate_input(hass: HomeAssistant, data: dict[str, str]) -> dict[str, Any]:
async def validate_input(
hass: HomeAssistant, hardware_id: str, data: dict[str, str]
) -> dict[str, Any]:
"""Validate the user input allows us to connect.""" """Validate the user input allows us to connect."""
user_agent, hardware_id = await get_auth_agent_id(hass) user_agent = get_auth_user_agent()
auth = Auth( auth = Auth(
user_agent, user_agent,
http_client_session=async_get_clientsession(hass), http_client_session=async_get_clientsession(hass),
@ -52,8 +63,10 @@ class RingConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle a config flow for Ring.""" """Handle a config flow for Ring."""
VERSION = 1 VERSION = 1
MINOR_VERSION = CONF_CONFIG_ENTRY_MINOR_VERSION
user_pass: dict[str, Any] = {} user_pass: dict[str, Any] = {}
hardware_id: str | None = None
reauth_entry: ConfigEntry | None = None reauth_entry: ConfigEntry | None = None
async def async_step_user( async def async_step_user(
@ -64,8 +77,10 @@ class RingConfigFlow(ConfigFlow, domain=DOMAIN):
if user_input is not None: if user_input is not None:
await self.async_set_unique_id(user_input[CONF_USERNAME]) await self.async_set_unique_id(user_input[CONF_USERNAME])
self._abort_if_unique_id_configured() self._abort_if_unique_id_configured()
if not self.hardware_id:
self.hardware_id = str(uuid.uuid4())
try: try:
token = await validate_input(self.hass, user_input) token = await validate_input(self.hass, self.hardware_id, user_input)
except Require2FA: except Require2FA:
self.user_pass = user_input self.user_pass = user_input
@ -78,7 +93,11 @@ class RingConfigFlow(ConfigFlow, domain=DOMAIN):
else: else:
return self.async_create_entry( return self.async_create_entry(
title=user_input[CONF_USERNAME], title=user_input[CONF_USERNAME],
data={CONF_USERNAME: user_input[CONF_USERNAME], CONF_TOKEN: token}, data={
CONF_DEVICE_ID: self.hardware_id,
CONF_USERNAME: user_input[CONF_USERNAME],
CONF_TOKEN: token,
},
) )
return self.async_show_form( return self.async_show_form(
@ -120,8 +139,13 @@ class RingConfigFlow(ConfigFlow, domain=DOMAIN):
if user_input: if user_input:
user_input[CONF_USERNAME] = self.reauth_entry.data[CONF_USERNAME] user_input[CONF_USERNAME] = self.reauth_entry.data[CONF_USERNAME]
# Reauth will use the same hardware id and re-authorise an existing
# authorised device.
if not self.hardware_id:
self.hardware_id = self.reauth_entry.data[CONF_DEVICE_ID]
assert self.hardware_id
try: try:
token = await validate_input(self.hass, user_input) token = await validate_input(self.hass, self.hardware_id, user_input)
except Require2FA: except Require2FA:
self.user_pass = user_input self.user_pass = user_input
return await self.async_step_2fa() return await self.async_step_2fa()
@ -134,6 +158,7 @@ class RingConfigFlow(ConfigFlow, domain=DOMAIN):
data = { data = {
CONF_USERNAME: user_input[CONF_USERNAME], CONF_USERNAME: user_input[CONF_USERNAME],
CONF_TOKEN: token, CONF_TOKEN: token,
CONF_DEVICE_ID: self.hardware_id,
} }
self.hass.config_entries.async_update_entry( self.hass.config_entries.async_update_entry(
self.reauth_entry, data=data self.reauth_entry, data=data
@ -146,7 +171,8 @@ class RingConfigFlow(ConfigFlow, domain=DOMAIN):
data_schema=STEP_REAUTH_DATA_SCHEMA, data_schema=STEP_REAUTH_DATA_SCHEMA,
errors=errors, errors=errors,
description_placeholders={ description_placeholders={
CONF_USERNAME: self.reauth_entry.data[CONF_USERNAME] CONF_USERNAME: self.reauth_entry.data[CONF_USERNAME],
CONF_NAME: self.reauth_entry.data[CONF_USERNAME],
}, },
) )

View File

@ -3,6 +3,7 @@
from __future__ import annotations from __future__ import annotations
from datetime import timedelta from datetime import timedelta
from typing import Final
from homeassistant.const import Platform from homeassistant.const import Platform
@ -31,3 +32,5 @@ SCAN_INTERVAL = timedelta(minutes=1)
CONF_2FA = "2fa" CONF_2FA = "2fa"
CONF_LISTEN_CREDENTIALS = "listen_token" CONF_LISTEN_CREDENTIALS = "listen_token"
CONF_CONFIG_ENTRY_MINOR_VERSION: Final = 2

View File

@ -28,7 +28,8 @@
"unknown": "[%key:common::config_flow::error::unknown%]" "unknown": "[%key:common::config_flow::error::unknown%]"
}, },
"abort": { "abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]" "already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
} }
}, },
"options": { "options": {

View File

@ -14,7 +14,8 @@
"unknown": "[%key:common::config_flow::error::unknown%]" "unknown": "[%key:common::config_flow::error::unknown%]"
}, },
"abort": { "abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]" "already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
} }
}, },
"entity": { "entity": {

View File

@ -90,13 +90,21 @@ class SchlageDataUpdateCoordinator(DataUpdateCoordinator[SchlageData]):
devices = dr.async_entries_for_config_entry( devices = dr.async_entries_for_config_entry(
device_registry, self.config_entry.entry_id device_registry, self.config_entry.entry_id
) )
previous_locks = {device.id for device in devices} previous_locks = set()
previous_locks_by_lock_id = {}
for device in devices:
for domain, identifier in device.identifiers:
if domain == DOMAIN:
previous_locks.add(identifier)
previous_locks_by_lock_id[identifier] = device
continue
current_locks = set(self.data.locks.keys()) current_locks = set(self.data.locks.keys())
if removed_locks := previous_locks - current_locks: if removed_locks := previous_locks - current_locks:
LOGGER.debug("Removed locks: %s", ", ".join(removed_locks)) LOGGER.debug("Removed locks: %s", ", ".join(removed_locks))
for device_id in removed_locks: for lock_id in removed_locks:
device_registry.async_update_device( device_registry.async_update_device(
device_id=device_id, device_id=previous_locks_by_lock_id[lock_id].id,
remove_config_entry_id=self.config_entry.entry_id, remove_config_entry_id=self.config_entry.entry_id,
) )

View File

@ -6,7 +6,6 @@ from collections import defaultdict
from collections.abc import Callable, Iterable from collections.abc import Callable, Iterable
from contextlib import suppress from contextlib import suppress
import datetime import datetime
from functools import partial
import itertools import itertools
import logging import logging
import math import math
@ -39,6 +38,7 @@ from homeassistant.helpers.entity import entity_sources
from homeassistant.helpers.typing import UNDEFINED, UndefinedType from homeassistant.helpers.typing import UNDEFINED, UndefinedType
from homeassistant.loader import async_suggest_report_issue from homeassistant.loader import async_suggest_report_issue
from homeassistant.util import dt as dt_util from homeassistant.util import dt as dt_util
from homeassistant.util.async_ import run_callback_threadsafe
from homeassistant.util.enum import try_parse_enum from homeassistant.util.enum import try_parse_enum
from homeassistant.util.hass_dict import HassKey from homeassistant.util.hass_dict import HassKey
@ -686,7 +686,6 @@ def list_statistic_ids(
@callback @callback
def _update_issues( def _update_issues(
report_issue: Callable[[str, str, dict[str, Any]], None], report_issue: Callable[[str, str, dict[str, Any]], None],
clear_issue: Callable[[str, str], None],
sensor_states: list[State], sensor_states: list[State],
metadatas: dict[str, tuple[int, StatisticMetaData]], metadatas: dict[str, tuple[int, StatisticMetaData]],
) -> None: ) -> None:
@ -707,8 +706,6 @@ def _update_issues(
entity_id, entity_id,
{"statistic_id": entity_id}, {"statistic_id": entity_id},
) )
else:
clear_issue("state_class_removed", entity_id)
metadata_unit = metadata[1]["unit_of_measurement"] metadata_unit = metadata[1]["unit_of_measurement"]
converter = statistics.STATISTIC_UNIT_TO_UNIT_CONVERTER.get(metadata_unit) converter = statistics.STATISTIC_UNIT_TO_UNIT_CONVERTER.get(metadata_unit)
@ -725,8 +722,6 @@ def _update_issues(
"supported_unit": metadata_unit, "supported_unit": metadata_unit,
}, },
) )
else:
clear_issue("units_changed", entity_id)
elif numeric and state_unit not in converter.VALID_UNITS: elif numeric and state_unit not in converter.VALID_UNITS:
# The state unit can't be converted to the unit in metadata # The state unit can't be converted to the unit in metadata
valid_units = (unit or "<None>" for unit in converter.VALID_UNITS) valid_units = (unit or "<None>" for unit in converter.VALID_UNITS)
@ -741,8 +736,6 @@ def _update_issues(
"supported_unit": valid_units_str, "supported_unit": valid_units_str,
}, },
) )
else:
clear_issue("units_changed", entity_id)
def update_statistics_issues( def update_statistics_issues(
@ -756,36 +749,50 @@ def update_statistics_issues(
instance, session, statistic_source=RECORDER_DOMAIN instance, session, statistic_source=RECORDER_DOMAIN
) )
@callback
def get_sensor_statistics_issues(hass: HomeAssistant) -> set[str]:
"""Return a list of statistics issues."""
issues = set()
issue_registry = ir.async_get(hass)
for issue in issue_registry.issues.values():
if (
issue.domain != DOMAIN
or not (issue_data := issue.data)
or issue_data.get("issue_type")
not in ("state_class_removed", "units_changed")
):
continue
issues.add(issue.issue_id)
return issues
issues = run_callback_threadsafe(
hass.loop, get_sensor_statistics_issues, hass
).result()
def create_issue_registry_issue( def create_issue_registry_issue(
issue_type: str, statistic_id: str, data: dict[str, Any] issue_type: str, statistic_id: str, data: dict[str, Any]
) -> None: ) -> None:
"""Create an issue registry issue.""" """Create an issue registry issue."""
hass.loop.call_soon_threadsafe( issue_id = f"{issue_type}_{statistic_id}"
partial( issues.discard(issue_id)
ir.async_create_issue, ir.create_issue(
hass, hass,
DOMAIN, DOMAIN,
f"{issue_type}_{statistic_id}", issue_id,
data=data | {"issue_type": issue_type}, data=data | {"issue_type": issue_type},
is_fixable=False, is_fixable=False,
severity=ir.IssueSeverity.WARNING, severity=ir.IssueSeverity.WARNING,
translation_key=issue_type, translation_key=issue_type,
translation_placeholders=data, translation_placeholders=data,
)
)
def delete_issue_registry_issue(issue_type: str, statistic_id: str) -> None:
"""Delete an issue registry issue."""
hass.loop.call_soon_threadsafe(
ir.async_delete_issue, hass, DOMAIN, f"{issue_type}_{statistic_id}"
) )
_update_issues( _update_issues(
create_issue_registry_issue, create_issue_registry_issue,
delete_issue_registry_issue,
sensor_states, sensor_states,
metadatas, metadatas,
) )
for issue_id in issues:
hass.loop.call_soon_threadsafe(ir.async_delete_issue, hass, DOMAIN, issue_id)
def validate_statistics( def validate_statistics(
@ -811,7 +818,6 @@ def validate_statistics(
_update_issues( _update_issues(
create_statistic_validation_issue, create_statistic_validation_issue,
lambda issue_type, statistic_id: None,
sensor_states, sensor_states,
metadatas, metadatas,
) )

View File

@ -135,3 +135,21 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
data[PYSMA_REMOVE_LISTENER]() data[PYSMA_REMOVE_LISTENER]()
return unload_ok return unload_ok
async def async_migrate_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Migrate entry."""
_LOGGER.debug("Migrating from version %s", entry.version)
if entry.version == 1:
# 1 -> 2: Unique ID from integer to string
if entry.minor_version == 1:
minor_version = 2
hass.config_entries.async_update_entry(
entry, unique_id=str(entry.unique_id), minor_version=minor_version
)
_LOGGER.debug("Migration successful")
return True

View File

@ -40,6 +40,7 @@ class SmaConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle a config flow for SMA.""" """Handle a config flow for SMA."""
VERSION = 1 VERSION = 1
MINOR_VERSION = 2
def __init__(self) -> None: def __init__(self) -> None:
"""Initialize.""" """Initialize."""
@ -76,7 +77,7 @@ class SmaConfigFlow(ConfigFlow, domain=DOMAIN):
errors["base"] = "unknown" errors["base"] = "unknown"
if not errors: if not errors:
await self.async_set_unique_id(device_info["serial"]) await self.async_set_unique_id(str(device_info["serial"]))
self._abort_if_unique_id_configured(updates=self._data) self._abort_if_unique_id_configured(updates=self._data)
return self.async_create_entry( return self.async_create_entry(
title=self._data[CONF_HOST], data=self._data title=self._data[CONF_HOST], data=self._data

View File

@ -6,7 +6,7 @@
"documentation": "https://www.home-assistant.io/integrations/smlight", "documentation": "https://www.home-assistant.io/integrations/smlight",
"integration_type": "device", "integration_type": "device",
"iot_class": "local_push", "iot_class": "local_push",
"requirements": ["pysmlight==0.1.2"], "requirements": ["pysmlight==0.1.3"],
"zeroconf": [ "zeroconf": [
{ {
"type": "_slzb-06._tcp.local." "type": "_slzb-06._tcp.local."

View File

@ -38,7 +38,7 @@ class SolarLogCoordinatorEntity(SolarLogBaseEntity):
"""Initialize the SolarLogCoordinator sensor.""" """Initialize the SolarLogCoordinator sensor."""
super().__init__(coordinator, description) super().__init__(coordinator, description)
self._attr_unique_id = f"{coordinator.unique_id}-{description.key}" self._attr_unique_id = f"{coordinator.unique_id}_{description.key}"
self._attr_device_info = DeviceInfo( self._attr_device_info = DeviceInfo(
manufacturer="Solar-Log", manufacturer="Solar-Log",
model="Controller", model="Controller",
@ -59,8 +59,8 @@ class SolarLogInverterEntity(SolarLogBaseEntity):
) -> None: ) -> None:
"""Initialize the SolarLogInverter sensor.""" """Initialize the SolarLogInverter sensor."""
super().__init__(coordinator, description) super().__init__(coordinator, description)
name = f"{coordinator.unique_id}-{slugify(coordinator.solarlog.device_name(device_id))}" name = f"{coordinator.unique_id}_{slugify(coordinator.solarlog.device_name(device_id))}"
self._attr_unique_id = f"{name}-{description.key}" self._attr_unique_id = f"{name}_{description.key}"
self._attr_device_info = DeviceInfo( self._attr_device_info = DeviceInfo(
manufacturer="Solar-Log", manufacturer="Solar-Log",
model="Inverter", model="Inverter",

View File

@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/solarlog", "documentation": "https://www.home-assistant.io/integrations/solarlog",
"iot_class": "local_polling", "iot_class": "local_polling",
"loggers": ["solarlog_cli"], "loggers": ["solarlog_cli"],
"requirements": ["solarlog_cli==0.3.0"] "requirements": ["solarlog_cli==0.3.1"]
} }

View File

@ -32,7 +32,8 @@
"reconfigure_confirm": { "reconfigure_confirm": {
"title": "Configure SolarLog", "title": "Configure SolarLog",
"data": { "data": {
"has_password": "[%key:component::solarlog::config::step::user::data::has_password%]" "has_password": "[%key:component::solarlog::config::step::user::data::has_password%]",
"password": "[%key:common::config_flow::data::password%]"
} }
} }
}, },

View File

@ -27,12 +27,6 @@
}, },
"call_query": { "call_query": {
"service": "mdi:database" "service": "mdi:database"
},
"sync": {
"service": "mdi:sync"
},
"unsync": {
"service": "mdi:sync-off"
} }
} }
} }

View File

@ -30,19 +30,3 @@ call_query:
advanced: true advanced: true
selector: selector:
object: object:
sync:
target:
entity:
integration: squeezebox
domain: media_player
fields:
other_player:
required: true
example: "media_player.living_room"
selector:
text:
unsync:
target:
entity:
integration: squeezebox
domain: media_player

View File

@ -60,20 +60,6 @@
"description": "[%key:component::squeezebox::services::call_method::fields::parameters::description%]" "description": "[%key:component::squeezebox::services::call_method::fields::parameters::description%]"
} }
} }
},
"sync": {
"name": "Sync",
"description": "Adds another player to this player's sync group. If the other player is already in a sync group, it will leave it.\n.",
"fields": {
"other_player": {
"name": "Other player",
"description": "Name of the other Squeezebox player to link."
}
}
},
"unsync": {
"name": "Unsync",
"description": "Removes this player from its sync group."
} }
}, },
"entity": { "entity": {

View File

@ -21,7 +21,8 @@
"unknown": "[%key:common::config_flow::error::unknown%]" "unknown": "[%key:common::config_flow::error::unknown%]"
}, },
"abort": { "abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_account%]" "already_configured": "[%key:common::config_flow::abort::already_configured_account%]",
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
} }
}, },
"services": { "services": {

View File

@ -37,17 +37,29 @@ class SensorData:
def as_dict(self) -> dict[str, Any]: def as_dict(self) -> dict[str, Any]:
"""Return as dict.""" """Return as dict."""
disk_usage = None
if self.disk_usage:
disk_usage = {k: str(v) for k, v in self.disk_usage.items()}
io_counters = None
if self.io_counters:
io_counters = {k: str(v) for k, v in self.io_counters.items()}
addresses = None
if self.addresses:
addresses = {k: str(v) for k, v in self.addresses.items()}
temperatures = None
if self.temperatures:
temperatures = {k: str(v) for k, v in self.temperatures.items()}
return { return {
"disk_usage": {k: str(v) for k, v in self.disk_usage.items()}, "disk_usage": disk_usage,
"swap": str(self.swap), "swap": str(self.swap),
"memory": str(self.memory), "memory": str(self.memory),
"io_counters": {k: str(v) for k, v in self.io_counters.items()}, "io_counters": io_counters,
"addresses": {k: str(v) for k, v in self.addresses.items()}, "addresses": addresses,
"load": str(self.load), "load": str(self.load),
"cpu_percent": str(self.cpu_percent), "cpu_percent": str(self.cpu_percent),
"boot_time": str(self.boot_time), "boot_time": str(self.boot_time),
"processes": str(self.processes), "processes": str(self.processes),
"temperatures": {k: str(v) for k, v in self.temperatures.items()}, "temperatures": temperatures,
} }

View File

@ -20,8 +20,9 @@ from .models import TeslaFleetVehicleData
PARALLEL_UPDATES = 0 PARALLEL_UPDATES = 0
async def do_nothing() -> None: async def do_nothing() -> dict[str, dict[str, bool]]:
"""Do nothing.""" """Do nothing with a positive result."""
return {"response": {"result": True}}
@dataclass(frozen=True, kw_only=True) @dataclass(frozen=True, kw_only=True)

View File

@ -378,7 +378,17 @@ ENERGY_LIVE_DESCRIPTIONS: tuple[SensorEntityDescription, ...] = (
device_class=SensorDeviceClass.POWER, device_class=SensorDeviceClass.POWER,
entity_registry_enabled_default=False, entity_registry_enabled_default=False,
), ),
SensorEntityDescription(key="island_status", device_class=SensorDeviceClass.ENUM), SensorEntityDescription(
key="island_status",
device_class=SensorDeviceClass.ENUM,
options=[
"on_grid",
"off_grid",
"off_grid_intentional",
"off_grid_unintentional",
"island_status_unknown",
],
),
) )
WALL_CONNECTOR_DESCRIPTIONS: tuple[TeslemetrySensorEntityDescription, ...] = ( WALL_CONNECTOR_DESCRIPTIONS: tuple[TeslemetrySensorEntityDescription, ...] = (

View File

@ -392,6 +392,16 @@
"grid_services_power": { "grid_services_power": {
"name": "Grid services power" "name": "Grid services power"
}, },
"island_status": {
"name": "Island status",
"state": {
"island_status_unknown": "Unknown",
"on_grid": "On grid",
"off_grid": "Off grid",
"off_grid_intentional": "Off grid intentional",
"off_grid_unintentional": "Off grid unintentional"
}
},
"load_power": { "load_power": {
"name": "Load power" "name": "Load power"
}, },

View File

@ -1,7 +1,8 @@
{ {
"config": { "config": {
"abort": { "abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_service%]" "already_configured": "[%key:common::config_flow::abort::already_configured_service%]",
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
}, },
"error": { "error": {
"invalid_access_token": "[%key:common::config_flow::error::invalid_access_token%]", "invalid_access_token": "[%key:common::config_flow::error::invalid_access_token%]",

View File

@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/touchline_sl", "documentation": "https://www.home-assistant.io/integrations/touchline_sl",
"integration_type": "hub", "integration_type": "hub",
"iot_class": "cloud_polling", "iot_class": "cloud_polling",
"requirements": ["pytouchlinesl==0.1.7"] "requirements": ["pytouchlinesl==0.1.8"]
} }

View File

@ -15,7 +15,7 @@ from homeassistant.components.climate import (
HVACAction, HVACAction,
HVACMode, HVACMode,
) )
from homeassistant.const import PRECISION_WHOLE from homeassistant.const import PRECISION_TENTHS
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import ServiceValidationError from homeassistant.exceptions import ServiceValidationError
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
@ -64,7 +64,7 @@ class TPLinkClimateEntity(CoordinatedTPLinkEntity, ClimateEntity):
| ClimateEntityFeature.TURN_ON | ClimateEntityFeature.TURN_ON
) )
_attr_hvac_modes = [HVACMode.HEAT, HVACMode.OFF] _attr_hvac_modes = [HVACMode.HEAT, HVACMode.OFF]
_attr_precision = PRECISION_WHOLE _attr_precision = PRECISION_TENTHS
# This disables the warning for async_turn_{on,off}, can be removed later. # This disables the warning for async_turn_{on,off}, can be removed later.
_enable_turn_on_off_backwards_compatibility = False _enable_turn_on_off_backwards_compatibility = False

View File

@ -301,5 +301,5 @@
"iot_class": "local_polling", "iot_class": "local_polling",
"loggers": ["kasa"], "loggers": ["kasa"],
"quality_scale": "platinum", "quality_scale": "platinum",
"requirements": ["python-kasa[speedups]==0.7.4"] "requirements": ["python-kasa[speedups]==0.7.5"]
} }

View File

@ -17,6 +17,17 @@ from homeassistant.helpers.entity import Entity
from .const import DOMAIN, LOGGER, TUYA_HA_SIGNAL_UPDATE_ENTITY, DPCode, DPType from .const import DOMAIN, LOGGER, TUYA_HA_SIGNAL_UPDATE_ENTITY, DPCode, DPType
from .util import remap_value from .util import remap_value
_DPTYPE_MAPPING: dict[str, DPType] = {
"Bitmap": DPType.RAW,
"bitmap": DPType.RAW,
"bool": DPType.BOOLEAN,
"enum": DPType.ENUM,
"json": DPType.JSON,
"raw": DPType.RAW,
"string": DPType.STRING,
"value": DPType.INTEGER,
}
@dataclass @dataclass
class IntegerTypeData: class IntegerTypeData:
@ -256,7 +267,13 @@ class TuyaEntity(Entity):
order = ["function", "status_range"] order = ["function", "status_range"]
for key in order: for key in order:
if dpcode in getattr(self.device, key): if dpcode in getattr(self.device, key):
return DPType(getattr(self.device, key)[dpcode].type) current_type = getattr(self.device, key)[dpcode].type
try:
return DPType(current_type)
except ValueError:
# Sometimes, we get ill-formed DPTypes from the cloud,
# this fixes them and maps them to the correct DPType.
return _DPTYPE_MAPPING.get(current_type)
return None return None

View File

@ -42,7 +42,8 @@
}, },
"abort": { "abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]", "already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
"discovery_started": "Discovery started" "discovery_started": "Discovery started",
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
} }
}, },
"options": { "options": {

View File

@ -34,11 +34,11 @@ class VenstarEntity(CoordinatorEntity[VenstarDataUpdateCoordinator]):
@property @property
def device_info(self) -> DeviceInfo: def device_info(self) -> DeviceInfo:
"""Return the device information for this entity.""" """Return the device information for this entity."""
fw_ver_major, fw_ver_minor = self._client.get_firmware_ver() firmware_version = self._client.get_firmware_ver()
return DeviceInfo( return DeviceInfo(
identifiers={(DOMAIN, self._config.entry_id)}, identifiers={(DOMAIN, self._config.entry_id)},
name=self._client.name, name=self._client.name,
manufacturer="Venstar", manufacturer="Venstar",
model=f"{self._client.model}-{self._client.get_type()}", model=f"{self._client.model}-{self._client.get_type()}",
sw_version=f"{fw_ver_major}.{fw_ver_minor}", sw_version=f"{firmware_version[0]}.{firmware_version[1]}",
) )

View File

@ -1,6 +1,7 @@
"""Types for the ViCare integration.""" """Types for the ViCare integration."""
from collections.abc import Callable from collections.abc import Callable
from contextlib import suppress
from dataclasses import dataclass from dataclasses import dataclass
import enum import enum
from typing import Any from typing import Any
@ -48,8 +49,12 @@ class HeatingProgram(enum.StrEnum):
) -> str | None: ) -> str | None:
"""Return the mapped ViCare heating program for the Home Assistant preset.""" """Return the mapped ViCare heating program for the Home Assistant preset."""
for program in supported_heating_programs: for program in supported_heating_programs:
if VICARE_TO_HA_PRESET_HEATING.get(HeatingProgram(program)) == ha_preset: with suppress(ValueError):
return program if (
VICARE_TO_HA_PRESET_HEATING.get(HeatingProgram(program))
== ha_preset
):
return program
return None return None

View File

@ -27,7 +27,8 @@
} }
}, },
"abort": { "abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_account%]" "already_configured": "[%key:common::config_flow::abort::already_configured_account%]",
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
}, },
"error": { "error": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",

View File

@ -802,7 +802,8 @@ async def async_setup_entry(
if not entities: if not entities:
LOGGER.warning( LOGGER.warning(
"No data found for Withings entry %s, sensors will be added when new data is available" "No data found for Withings entry %s, sensors will be added when new data is available",
entry.title,
) )
async_add_entities(entities) async_add_entities(entities)

View File

@ -20,7 +20,9 @@
"oauth_error": "[%key:common::config_flow::abort::oauth2_error%]", "oauth_error": "[%key:common::config_flow::abort::oauth2_error%]",
"oauth_timeout": "[%key:common::config_flow::abort::oauth2_timeout%]", "oauth_timeout": "[%key:common::config_flow::abort::oauth2_timeout%]",
"oauth_unauthorized": "[%key:common::config_flow::abort::oauth2_unauthorized%]", "oauth_unauthorized": "[%key:common::config_flow::abort::oauth2_unauthorized%]",
"oauth_failed": "[%key:common::config_flow::abort::oauth2_failed%]" "oauth_failed": "[%key:common::config_flow::abort::oauth2_failed%]",
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]",
"wrong_account": "Authenticated account does not match the account to be reauthenticated. Please log in with the correct account."
}, },
"create_entry": { "create_entry": {
"default": "Successfully authenticated with Withings." "default": "Successfully authenticated with Withings."

View File

@ -75,7 +75,7 @@ class WebControlProConfigFlow(ConfigFlow, domain=DOMAIN):
if self.source == dhcp.DOMAIN: if self.source == dhcp.DOMAIN:
discovery_info: DhcpServiceInfo = self.init_data discovery_info: DhcpServiceInfo = self.init_data
data_values = {CONF_HOST: discovery_info.hostname or discovery_info.ip} data_values = {CONF_HOST: discovery_info.ip}
else: else:
data_values = {CONF_HOST: SUGGESTED_HOST} data_values = {CONF_HOST: SUGGESTED_HOST}

View File

@ -15,5 +15,5 @@
"documentation": "https://www.home-assistant.io/integrations/wmspro", "documentation": "https://www.home-assistant.io/integrations/wmspro",
"integration_type": "hub", "integration_type": "hub",
"iot_class": "local_polling", "iot_class": "local_polling",
"requirements": ["pywmspro==0.2.0"] "requirements": ["pywmspro==0.2.1"]
} }

View File

@ -7,5 +7,5 @@
"iot_class": "local_polling", "iot_class": "local_polling",
"loggers": ["holidays"], "loggers": ["holidays"],
"quality_scale": "internal", "quality_scale": "internal",
"requirements": ["holidays==0.57"] "requirements": ["holidays==0.58"]
} }

View File

@ -20,7 +20,9 @@
"yxc_control_url_missing": "The control URL is not given in the ssdp description." "yxc_control_url_missing": "The control URL is not given in the ssdp description."
}, },
"error": { "error": {
"no_musiccast_device": "This device seems to be no MusicCast Device." "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"no_musiccast_device": "This device seems to be no MusicCast Device.",
"unknown": "[%key:common::config_flow::error::unknown%]"
} }
}, },
"entity": { "entity": {

View File

@ -34,6 +34,8 @@ def boolean(value: Any) -> bool:
VALUE_SCHEMA = vol.Any( VALUE_SCHEMA = vol.Any(
boolean, boolean,
float,
int,
vol.Coerce(int), vol.Coerce(int),
vol.Coerce(float), vol.Coerce(float),
BITMASK_SCHEMA, BITMASK_SCHEMA,

View File

@ -24,7 +24,7 @@ if TYPE_CHECKING:
APPLICATION_NAME: Final = "HomeAssistant" APPLICATION_NAME: Final = "HomeAssistant"
MAJOR_VERSION: Final = 2024 MAJOR_VERSION: Final = 2024
MINOR_VERSION: Final = 10 MINOR_VERSION: Final = 10
PATCH_VERSION: Final = "1" PATCH_VERSION: Final = "2"
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}" __short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
__version__: Final = f"{__short_version__}.{PATCH_VERSION}" __version__: Final = f"{__short_version__}.{PATCH_VERSION}"
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 12, 0) REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 12, 0)

View File

@ -267,6 +267,11 @@ HOMEKIT = {
} }
ZEROCONF = { ZEROCONF = {
"_PowerView-G3._tcp.local.": [
{
"domain": "hunterdouglas_powerview",
},
],
"_Volumio._tcp.local.": [ "_Volumio._tcp.local.": [
{ {
"domain": "volumio", "domain": "volumio",
@ -695,11 +700,6 @@ ZEROCONF = {
"domain": "plugwise", "domain": "plugwise",
}, },
], ],
"_powerview-g3._tcp.local.": [
{
"domain": "hunterdouglas_powerview",
},
],
"_powerview._tcp.local.": [ "_powerview._tcp.local.": [
{ {
"domain": "hunterdouglas_powerview", "domain": "hunterdouglas_powerview",

View File

@ -9,6 +9,7 @@ import collections.abc
from collections.abc import Callable, Generator, Iterable from collections.abc import Callable, Generator, Iterable
from contextlib import AbstractContextManager from contextlib import AbstractContextManager
from contextvars import ContextVar from contextvars import ContextVar
from copy import deepcopy
from datetime import date, datetime, time, timedelta from datetime import date, datetime, time, timedelta
from functools import cache, cached_property, lru_cache, partial, wraps from functools import cache, cached_property, lru_cache, partial, wraps
import json import json
@ -2166,7 +2167,8 @@ def merge_response(value: ServiceResponse) -> list[Any]:
is_single_list = False is_single_list = False
response_items: list = [] response_items: list = []
for entity_id, entity_response in value.items(): # pylint: disable=too-many-nested-blocks input_service_response = deepcopy(value)
for entity_id, entity_response in input_service_response.items(): # pylint: disable=too-many-nested-blocks
if not isinstance(entity_response, dict): if not isinstance(entity_response, dict):
raise TypeError("Response is not a dictionary") raise TypeError("Response is not a dictionary")
for value_key, type_response in entity_response.items(): for value_key, type_response in entity_response.items():

View File

@ -32,7 +32,7 @@ habluetooth==3.4.0
hass-nabucasa==0.81.1 hass-nabucasa==0.81.1
hassil==1.7.4 hassil==1.7.4
home-assistant-bluetooth==1.12.2 home-assistant-bluetooth==1.12.2
home-assistant-frontend==20241002.2 home-assistant-frontend==20241002.3
home-assistant-intents==2024.10.2 home-assistant-intents==2024.10.2
httpx==0.27.2 httpx==0.27.2
ifaddr==0.2.0 ifaddr==0.2.0

View File

@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project] [project]
name = "homeassistant" name = "homeassistant"
version = "2024.10.1" version = "2024.10.2"
license = {text = "Apache-2.0"} license = {text = "Apache-2.0"}
description = "Open-source home automation platform running on Python 3." description = "Open-source home automation platform running on Python 3."
readme = "README.rst" readme = "README.rst"

View File

@ -13,7 +13,7 @@ AIOSomecomfort==0.0.25
Adax-local==0.1.5 Adax-local==0.1.5
# homeassistant.components.doorbird # homeassistant.components.doorbird
DoorBirdPy==3.0.2 DoorBirdPy==3.0.4
# homeassistant.components.homekit # homeassistant.components.homekit
HAP-python==4.9.1 HAP-python==4.9.1
@ -45,7 +45,7 @@ ProgettiHWSW==0.1.3
# PyBluez==0.22 # PyBluez==0.22
# homeassistant.components.cast # homeassistant.components.cast
PyChromecast==14.0.1 PyChromecast==14.0.3
# homeassistant.components.flick_electric # homeassistant.components.flick_electric
PyFlick==0.0.2 PyFlick==0.0.2
@ -176,7 +176,7 @@ aio-georss-gdacs==0.10
aioairq==0.3.2 aioairq==0.3.2
# homeassistant.components.airzone_cloud # homeassistant.components.airzone_cloud
aioairzone-cloud==0.6.5 aioairzone-cloud==0.6.6
# homeassistant.components.airzone # homeassistant.components.airzone
aioairzone==0.9.3 aioairzone==0.9.3
@ -198,7 +198,7 @@ aioaseko==1.0.0
aioasuswrt==1.4.0 aioasuswrt==1.4.0
# homeassistant.components.husqvarna_automower # homeassistant.components.husqvarna_automower
aioautomower==2024.9.3 aioautomower==2024.10.0
# homeassistant.components.azure_devops # homeassistant.components.azure_devops
aioazuredevops==2.2.1 aioazuredevops==2.2.1
@ -380,7 +380,7 @@ aiosolaredge==0.2.0
aiosteamist==1.0.0 aiosteamist==1.0.0
# homeassistant.components.cambridge_audio # homeassistant.components.cambridge_audio
aiostreammagic==2.3.1 aiostreammagic==2.5.0
# homeassistant.components.switcher_kis # homeassistant.components.switcher_kis
aioswitcher==4.0.3 aioswitcher==4.0.3
@ -419,7 +419,7 @@ aiowithings==3.0.3
aioymaps==1.2.5 aioymaps==1.2.5
# homeassistant.components.airgradient # homeassistant.components.airgradient
airgradient==0.9.0 airgradient==0.9.1
# homeassistant.components.airly # homeassistant.components.airly
airly==1.1.0 airly==1.1.0
@ -532,7 +532,7 @@ autarco==3.0.0
axis==62 axis==62
# homeassistant.components.fujitsu_fglair # homeassistant.components.fujitsu_fglair
ayla-iot-unofficial==1.4.1 ayla-iot-unofficial==1.4.2
# homeassistant.components.azure_event_hub # homeassistant.components.azure_event_hub
azure-eventhub==5.11.1 azure-eventhub==5.11.1
@ -933,7 +933,7 @@ freesms==0.2.0
fritzconnection[qr]==1.13.2 fritzconnection[qr]==1.13.2
# homeassistant.components.fyta # homeassistant.components.fyta
fyta_cli==0.6.6 fyta_cli==0.6.7
# homeassistant.components.google_translate # homeassistant.components.google_translate
gTTS==2.2.4 gTTS==2.2.4
@ -1114,10 +1114,10 @@ hole==0.8.0
# homeassistant.components.holiday # homeassistant.components.holiday
# homeassistant.components.workday # homeassistant.components.workday
holidays==0.57 holidays==0.58
# homeassistant.components.frontend # homeassistant.components.frontend
home-assistant-frontend==20241002.2 home-assistant-frontend==20241002.3
# homeassistant.components.conversation # homeassistant.components.conversation
home-assistant-intents==2024.10.2 home-assistant-intents==2024.10.2
@ -1176,7 +1176,7 @@ iglo==1.2.7
ihcsdk==2.8.5 ihcsdk==2.8.5
# homeassistant.components.imgw_pib # homeassistant.components.imgw_pib
imgw_pib==1.0.5 imgw_pib==1.0.6
# homeassistant.components.incomfort # homeassistant.components.incomfort
incomfort-client==0.6.3-1 incomfort-client==0.6.3-1
@ -1387,7 +1387,7 @@ mopeka-iot-ble==0.8.0
motionblinds==0.6.25 motionblinds==0.6.25
# homeassistant.components.motionblinds_ble # homeassistant.components.motionblinds_ble
motionblindsble==0.1.1 motionblindsble==0.1.2
# homeassistant.components.motioneye # homeassistant.components.motioneye
motioneye-client==0.3.14 motioneye-client==0.3.14
@ -1484,7 +1484,7 @@ numato-gpio==0.13.0
numpy==1.26.4 numpy==1.26.4
# homeassistant.components.nyt_games # homeassistant.components.nyt_games
nyt_games==0.4.2 nyt_games==0.4.3
# homeassistant.components.oasa_telematics # homeassistant.components.oasa_telematics
oasatelematics==0.3 oasatelematics==0.3
@ -1544,7 +1544,7 @@ openwrt-luci-rpc==1.1.17
openwrt-ubus-rpc==0.0.2 openwrt-ubus-rpc==0.0.2
# homeassistant.components.opower # homeassistant.components.opower
opower==0.8.0 opower==0.8.3
# homeassistant.components.oralb # homeassistant.components.oralb
oralb-ble==0.17.6 oralb-ble==0.17.6
@ -1710,7 +1710,7 @@ pyCEC==0.5.2
pyControl4==1.2.0 pyControl4==1.2.0
# homeassistant.components.duotecno # homeassistant.components.duotecno
pyDuotecno==2024.9.0 pyDuotecno==2024.10.0
# homeassistant.components.electrasmart # homeassistant.components.electrasmart
pyElectra==1.2.4 pyElectra==1.2.4
@ -1780,7 +1780,7 @@ pybbox==0.0.5-alpha
pyblackbird==0.6 pyblackbird==0.6
# homeassistant.components.bluesound # homeassistant.components.bluesound
pyblu==1.0.2 pyblu==1.0.3
# homeassistant.components.neato # homeassistant.components.neato
pybotvac==0.0.25 pybotvac==0.0.25
@ -1855,7 +1855,7 @@ pyebox==1.1.4
pyecoforest==0.4.0 pyecoforest==0.4.0
# homeassistant.components.econet # homeassistant.components.econet
pyeconet==0.1.22 pyeconet==0.1.23
# homeassistant.components.ista_ecotrend # homeassistant.components.ista_ecotrend
pyecotrend-ista==3.3.1 pyecotrend-ista==3.3.1
@ -2244,7 +2244,7 @@ pysmarty2==0.10.1
pysml==0.0.12 pysml==0.0.12
# homeassistant.components.smlight # homeassistant.components.smlight
pysmlight==0.1.2 pysmlight==0.1.3
# homeassistant.components.snmp # homeassistant.components.snmp
pysnmp==6.2.6 pysnmp==6.2.6
@ -2340,10 +2340,10 @@ python-join-api==0.0.9
python-juicenet==1.1.0 python-juicenet==1.1.0
# homeassistant.components.tplink # homeassistant.components.tplink
python-kasa[speedups]==0.7.4 python-kasa[speedups]==0.7.5
# homeassistant.components.linkplay # homeassistant.components.linkplay
python-linkplay==0.0.12 python-linkplay==0.0.15
# homeassistant.components.lirc # homeassistant.components.lirc
# python-lirc==1.2.3 # python-lirc==1.2.3
@ -2413,7 +2413,7 @@ pytomorrowio==0.3.6
pytouchline==0.7 pytouchline==0.7
# homeassistant.components.touchline_sl # homeassistant.components.touchline_sl
pytouchlinesl==0.1.7 pytouchlinesl==0.1.8
# homeassistant.components.traccar # homeassistant.components.traccar
# homeassistant.components.traccar_server # homeassistant.components.traccar_server
@ -2477,7 +2477,7 @@ pywilight==0.0.74
pywizlight==0.5.14 pywizlight==0.5.14
# homeassistant.components.wmspro # homeassistant.components.wmspro
pywmspro==0.2.0 pywmspro==0.2.1
# homeassistant.components.ws66i # homeassistant.components.ws66i
pyws66i==1.1 pyws66i==1.1
@ -2676,7 +2676,7 @@ soco==0.30.4
solaredge-local==0.2.3 solaredge-local==0.2.3
# homeassistant.components.solarlog # homeassistant.components.solarlog
solarlog_cli==0.3.0 solarlog_cli==0.3.1
# homeassistant.components.solax # homeassistant.components.solax
solax==3.1.1 solax==3.1.1
@ -2992,7 +2992,7 @@ xiaomi-ble==0.32.0
xknx==3.2.0 xknx==3.2.0
# homeassistant.components.knx # homeassistant.components.knx
xknxproject==3.8.0 xknxproject==3.8.1
# homeassistant.components.fritz # homeassistant.components.fritz
# homeassistant.components.rest # homeassistant.components.rest

View File

@ -13,7 +13,7 @@ AIOSomecomfort==0.0.25
Adax-local==0.1.5 Adax-local==0.1.5
# homeassistant.components.doorbird # homeassistant.components.doorbird
DoorBirdPy==3.0.2 DoorBirdPy==3.0.4
# homeassistant.components.homekit # homeassistant.components.homekit
HAP-python==4.9.1 HAP-python==4.9.1
@ -42,7 +42,7 @@ PlexAPI==4.15.16
ProgettiHWSW==0.1.3 ProgettiHWSW==0.1.3
# homeassistant.components.cast # homeassistant.components.cast
PyChromecast==14.0.1 PyChromecast==14.0.3
# homeassistant.components.flick_electric # homeassistant.components.flick_electric
PyFlick==0.0.2 PyFlick==0.0.2
@ -164,7 +164,7 @@ aio-georss-gdacs==0.10
aioairq==0.3.2 aioairq==0.3.2
# homeassistant.components.airzone_cloud # homeassistant.components.airzone_cloud
aioairzone-cloud==0.6.5 aioairzone-cloud==0.6.6
# homeassistant.components.airzone # homeassistant.components.airzone
aioairzone==0.9.3 aioairzone==0.9.3
@ -186,7 +186,7 @@ aioaseko==1.0.0
aioasuswrt==1.4.0 aioasuswrt==1.4.0
# homeassistant.components.husqvarna_automower # homeassistant.components.husqvarna_automower
aioautomower==2024.9.3 aioautomower==2024.10.0
# homeassistant.components.azure_devops # homeassistant.components.azure_devops
aioazuredevops==2.2.1 aioazuredevops==2.2.1
@ -362,7 +362,7 @@ aiosolaredge==0.2.0
aiosteamist==1.0.0 aiosteamist==1.0.0
# homeassistant.components.cambridge_audio # homeassistant.components.cambridge_audio
aiostreammagic==2.3.1 aiostreammagic==2.5.0
# homeassistant.components.switcher_kis # homeassistant.components.switcher_kis
aioswitcher==4.0.3 aioswitcher==4.0.3
@ -401,7 +401,7 @@ aiowithings==3.0.3
aioymaps==1.2.5 aioymaps==1.2.5
# homeassistant.components.airgradient # homeassistant.components.airgradient
airgradient==0.9.0 airgradient==0.9.1
# homeassistant.components.airly # homeassistant.components.airly
airly==1.1.0 airly==1.1.0
@ -481,7 +481,7 @@ autarco==3.0.0
axis==62 axis==62
# homeassistant.components.fujitsu_fglair # homeassistant.components.fujitsu_fglair
ayla-iot-unofficial==1.4.1 ayla-iot-unofficial==1.4.2
# homeassistant.components.azure_event_hub # homeassistant.components.azure_event_hub
azure-eventhub==5.11.1 azure-eventhub==5.11.1
@ -786,7 +786,7 @@ freebox-api==1.1.0
fritzconnection[qr]==1.13.2 fritzconnection[qr]==1.13.2
# homeassistant.components.fyta # homeassistant.components.fyta
fyta_cli==0.6.6 fyta_cli==0.6.7
# homeassistant.components.google_translate # homeassistant.components.google_translate
gTTS==2.2.4 gTTS==2.2.4
@ -940,10 +940,10 @@ hole==0.8.0
# homeassistant.components.holiday # homeassistant.components.holiday
# homeassistant.components.workday # homeassistant.components.workday
holidays==0.57 holidays==0.58
# homeassistant.components.frontend # homeassistant.components.frontend
home-assistant-frontend==20241002.2 home-assistant-frontend==20241002.3
# homeassistant.components.conversation # homeassistant.components.conversation
home-assistant-intents==2024.10.2 home-assistant-intents==2024.10.2
@ -987,7 +987,7 @@ idasen-ha==2.6.2
ifaddr==0.2.0 ifaddr==0.2.0
# homeassistant.components.imgw_pib # homeassistant.components.imgw_pib
imgw_pib==1.0.5 imgw_pib==1.0.6
# homeassistant.components.incomfort # homeassistant.components.incomfort
incomfort-client==0.6.3-1 incomfort-client==0.6.3-1
@ -1156,7 +1156,7 @@ mopeka-iot-ble==0.8.0
motionblinds==0.6.25 motionblinds==0.6.25
# homeassistant.components.motionblinds_ble # homeassistant.components.motionblinds_ble
motionblindsble==0.1.1 motionblindsble==0.1.2
# homeassistant.components.motioneye # homeassistant.components.motioneye
motioneye-client==0.3.14 motioneye-client==0.3.14
@ -1232,7 +1232,7 @@ numato-gpio==0.13.0
numpy==1.26.4 numpy==1.26.4
# homeassistant.components.nyt_games # homeassistant.components.nyt_games
nyt_games==0.4.2 nyt_games==0.4.3
# homeassistant.components.google # homeassistant.components.google
oauth2client==4.1.3 oauth2client==4.1.3
@ -1274,7 +1274,7 @@ openhomedevice==2.2.0
openwebifpy==4.2.7 openwebifpy==4.2.7
# homeassistant.components.opower # homeassistant.components.opower
opower==0.8.0 opower==0.8.3
# homeassistant.components.oralb # homeassistant.components.oralb
oralb-ble==0.17.6 oralb-ble==0.17.6
@ -1396,7 +1396,7 @@ pyCEC==0.5.2
pyControl4==1.2.0 pyControl4==1.2.0
# homeassistant.components.duotecno # homeassistant.components.duotecno
pyDuotecno==2024.9.0 pyDuotecno==2024.10.0
# homeassistant.components.electrasmart # homeassistant.components.electrasmart
pyElectra==1.2.4 pyElectra==1.2.4
@ -1448,7 +1448,7 @@ pybalboa==1.0.2
pyblackbird==0.6 pyblackbird==0.6
# homeassistant.components.bluesound # homeassistant.components.bluesound
pyblu==1.0.2 pyblu==1.0.3
# homeassistant.components.neato # homeassistant.components.neato
pybotvac==0.0.25 pybotvac==0.0.25
@ -1493,7 +1493,7 @@ pydroid-ipcam==2.0.0
pyecoforest==0.4.0 pyecoforest==0.4.0
# homeassistant.components.econet # homeassistant.components.econet
pyeconet==0.1.22 pyeconet==0.1.23
# homeassistant.components.ista_ecotrend # homeassistant.components.ista_ecotrend
pyecotrend-ista==3.3.1 pyecotrend-ista==3.3.1
@ -1798,7 +1798,7 @@ pysmartthings==0.7.8
pysml==0.0.12 pysml==0.0.12
# homeassistant.components.smlight # homeassistant.components.smlight
pysmlight==0.1.2 pysmlight==0.1.3
# homeassistant.components.snmp # homeassistant.components.snmp
pysnmp==6.2.6 pysnmp==6.2.6
@ -1861,10 +1861,10 @@ python-izone==1.2.9
python-juicenet==1.1.0 python-juicenet==1.1.0
# homeassistant.components.tplink # homeassistant.components.tplink
python-kasa[speedups]==0.7.4 python-kasa[speedups]==0.7.5
# homeassistant.components.linkplay # homeassistant.components.linkplay
python-linkplay==0.0.12 python-linkplay==0.0.15
# homeassistant.components.matter # homeassistant.components.matter
python-matter-server==6.5.2 python-matter-server==6.5.2
@ -1919,7 +1919,7 @@ pytile==2023.12.0
pytomorrowio==0.3.6 pytomorrowio==0.3.6
# homeassistant.components.touchline_sl # homeassistant.components.touchline_sl
pytouchlinesl==0.1.7 pytouchlinesl==0.1.8
# homeassistant.components.traccar # homeassistant.components.traccar
# homeassistant.components.traccar_server # homeassistant.components.traccar_server
@ -1977,7 +1977,7 @@ pywilight==0.0.74
pywizlight==0.5.14 pywizlight==0.5.14
# homeassistant.components.wmspro # homeassistant.components.wmspro
pywmspro==0.2.0 pywmspro==0.2.1
# homeassistant.components.ws66i # homeassistant.components.ws66i
pyws66i==1.1 pyws66i==1.1
@ -2122,7 +2122,7 @@ snapcast==2.3.6
soco==0.30.4 soco==0.30.4
# homeassistant.components.solarlog # homeassistant.components.solarlog
solarlog_cli==0.3.0 solarlog_cli==0.3.1
# homeassistant.components.solax # homeassistant.components.solax
solax==3.1.1 solax==3.1.1
@ -2381,7 +2381,7 @@ xiaomi-ble==0.32.0
xknx==3.2.0 xknx==3.2.0
# homeassistant.components.knx # homeassistant.components.knx
xknxproject==3.8.0 xknxproject==3.8.1
# homeassistant.components.fritz # homeassistant.components.fritz
# homeassistant.components.rest # homeassistant.components.rest

View File

@ -165,6 +165,8 @@ EXCEPTIONS = {
"tapsaff", # https://github.com/bazwilliams/python-taps-aff/pull/5 "tapsaff", # https://github.com/bazwilliams/python-taps-aff/pull/5
"vincenty", # Public domain "vincenty", # Public domain
"zeversolar", # https://github.com/kvanzuijlen/zeversolar/pull/46 "zeversolar", # https://github.com/kvanzuijlen/zeversolar/pull/46
# Using License-Expression (with hatchling)
"ftfy", # Apache-2.0
} }
TODO = { TODO = {

View File

@ -41,7 +41,7 @@ ZEROCONF_DISCOVERY_GEN3 = zeroconf.ZeroconfServiceInfo(
ip_address="1.2.3.4", ip_address="1.2.3.4",
ip_addresses=[IPv4Address("1.2.3.4")], ip_addresses=[IPv4Address("1.2.3.4")],
hostname="mock_hostname", hostname="mock_hostname",
name="Powerview Generation 3._powerview-g3._tcp.local.", name="Powerview Generation 3._PowerView-G3._tcp.local.",
port=None, port=None,
properties={}, properties={},
type="mock_type", type="mock_type",

View File

@ -20,7 +20,7 @@
'labels': set({ 'labels': set({
}), }),
'manufacturer': 'Husqvarna', 'manufacturer': 'Husqvarna',
'model': 'HUSQVARNA AUTOMOWER® 450XH', 'model': 'AUTOMOWER® 450XH',
'model_id': None, 'model_id': None,
'name': 'Test Mower 1', 'name': 'Test Mower 1',
'name_by_user': None, 'name_by_user': None,

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