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
env:
CACHE_VERSION: 10
CACHE_VERSION: 11
UV_CACHE_VERSION: 1
MYPY_CACHE_VERSION: 9
HA_SHORT_VERSION: "2024.10"

View File

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

View File

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

View File

@ -22,7 +22,8 @@
}
},
"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": {
"default": "Successfully connected to AlarmDecoder."
@ -37,7 +38,7 @@
"title": "Configure AlarmDecoder",
"description": "What would you like to edit?",
"data": {
"edit_select": "Edit"
"edit_selection": "Edit"
}
},
"arm_settings": {

View File

@ -10,7 +10,7 @@
},
"site": {
"data": {
"site_nmi": "Site NMI",
"site_id": "Site NMI",
"site_name": "Site Name"
},
"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%]"
},
"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": {
@ -37,7 +37,7 @@
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]"
},
"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": {

View File

@ -4,6 +4,7 @@ from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from .const import CONF_THRESHOLD, DEFAULT_THRESHOLD
from .coordinator import AuroraDataUpdateCoordinator
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)
entry.async_on_unload(entry.add_update_listener(update_listener))
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:
"""Unload a config entry."""
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.latitude = int(self.config_entry.data[CONF_LATITUDE])
self.longitude = int(self.config_entry.data[CONF_LONGITUDE])
self.latitude = round(self.config_entry.data[CONF_LATITUDE])
self.longitude = round(self.config_entry.data[CONF_LONGITUDE])
self.threshold = int(
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%]"
},
"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": {
"step": {
"init": {
"data": {
"threshold": "Threshold (%)"
"forecast_threshold": "Threshold (%)"
}
}
}

View File

@ -38,7 +38,7 @@
},
"options": {
"step": {
"options": {
"init": {
"title": "Options for the Azure Event Hub.",
"data": {
"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.",
"data": {
"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"
}

View File

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

View File

@ -1,7 +1,7 @@
{
"config": {
"step": {
"reconfigure": {
"reconfigure_confirm": {
"data": {
"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],
password=entry.data[CONF_PASSWORD],
ssl_verify_cert=entry.data[CONF_VERIFY_SSL],
timeout=10,
timeout=30,
)
try:
await hass.async_add_executor_job(client.principal)

View File

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

View File

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

View File

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

View File

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

View File

@ -5,18 +5,21 @@
"data": {
"host": "[%key:common::config_flow::data::host%]",
"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": {
"host": "The hostname or IP address of your Duotecno device."
}
}
},
"abort": {
"single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]"
},
"error": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
"unknown": "[%key:common::config_flow::error::unknown%]",
"single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]"
"unknown": "[%key:common::config_flow::error::unknown%]"
}
},
"entity": {

View File

@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/econet",
"iot_class": "cloud_push",
"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%]"
},
"error": {
"no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]",
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]"
}
},

View File

@ -20,5 +20,5 @@
"documentation": "https://www.home-assistant.io/integrations/frontend",
"integration_type": "system",
"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_EUROPE = "is_europe"
REGION_EU = "EU"
REGION_EU = "eu"
REGION_DEFAULT = "default"

View File

@ -5,5 +5,5 @@
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/fujitsu_fglair",
"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",
"iot_class": "cloud_polling",
"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)
return None, None
encoding = texttospeech.AudioEncoding(options[CONF_ENCODING])
gender: texttospeech.SsmlVoiceGender | None = texttospeech.SsmlVoiceGender(
encoding: texttospeech.AudioEncoding = texttospeech.AudioEncoding[
options[CONF_ENCODING]
] # type: ignore[misc]
gender: texttospeech.SsmlVoiceGender | None = texttospeech.SsmlVoiceGender[
options[CONF_GENDER]
)
] # type: ignore[misc]
voice = options[CONF_VOICE]
if voice:
gender = None

View File

@ -21,7 +21,8 @@
"wrong_account": "Wrong account: Please authenticate with the right account.",
"oauth_timeout": "[%key:common::config_flow::abort::oauth2_timeout%]",
"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": {
"default": "[%key:common::config_flow::create_entry::authenticated%]"

View File

@ -21,7 +21,8 @@
"wrong_account": "Wrong account: Please authenticate with the right account.",
"oauth_timeout": "[%key:common::config_flow::abort::oauth2_timeout%]",
"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": {
"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:
"""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)
startdate = to_date(task["startDate"])
if TYPE_CHECKING:

View File

@ -24,11 +24,11 @@
},
"cmd": {
"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": {
"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": {
"name": "Raw",
@ -36,7 +36,7 @@
},
"src": {
"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,
"documentation": "https://www.home-assistant.io/integrations/holiday",
"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."
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]:

View File

@ -19,5 +19,5 @@
"iot_class": "local_polling",
"loggers": ["aiopvapi"],
"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(
identifiers={(DOMAIN, mower_id)},
manufacturer="Husqvarna",
model=self.mower_attributes.system.model,
model=self.mower_attributes.system.model.removeprefix(
"HUSQVARNA "
).removeprefix("Husqvarna "),
name=self.mower_attributes.system.name,
serial_number=self.mower_attributes.system.serial_number,
suggested_area="Garden",

View File

@ -7,5 +7,5 @@
"documentation": "https://www.home-assistant.io/integrations/husqvarna_automower",
"iot_class": "cloud_push",
"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"
SCAN_INTERVAL = timedelta(seconds=30)
SCAN_INTERVAL = timedelta(seconds=60)
SIGNAL_UPDATE_HYDRAWISE = "hydrawise_update"

View File

@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/imgw_pib",
"iot_class": "cloud_polling",
"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"
},
"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": {

View File

@ -24,6 +24,7 @@
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
"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%]"
},
"error": {

View File

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

View File

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

View File

@ -19,7 +19,8 @@
}
},
"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": {
"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%]",
"user_rejected_authorize": "[%key:common::config_flow::abort::oauth2_user_rejected_authorize%]",
"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%]",
"wrong_account": "You can only reauthenticate this entry with the same microBees account."
},

View File

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

View File

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

View File

@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/nyt_games",
"integration_type": "service",
"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)
for description in SPELLING_BEE_SENSORS
)
entities.extend(
NYTGamesConnectionsSensor(coordinator, description)
for description in CONNECTIONS_SENSORS
)
if coordinator.data.connections is not None:
entities.extend(
NYTGamesConnectionsSensor(coordinator, description)
for description in CONNECTIONS_SENSORS
)
async_add_entities(entities)
@ -236,4 +237,5 @@ class NYTGamesConnectionsSensor(ConnectionsEntity, SensorEntity):
@property
def native_value(self) -> StateType | date:
"""Return the state of the sensor."""
assert self.coordinator.data.connections is not None
return self.entity_description.value_fn(self.coordinator.data.connections)

View File

@ -130,20 +130,32 @@ class OpowerCoordinator(DataUpdateCoordinator[dict[str, Forecast]]):
continue
start = cost_reads[0].start_time
_LOGGER.debug("Getting statistics at: %s", start)
stats = await get_instance(self.hass).async_add_executor_job(
statistics_during_period,
self.hass,
start,
start + timedelta(seconds=1),
{cost_statistic_id, consumption_statistic_id},
"hour",
None,
{"sum"},
)
# In the common case there should be a previous statistic at start time
# so we only need to fetch one statistic. If there isn't any, fetch all.
for end in (start + timedelta(seconds=1), None):
stats = await get_instance(self.hass).async_add_executor_job(
statistics_during_period,
self.hass,
start,
end,
{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"])
consumption_sum = cast(float, stats[consumption_statistic_id][0]["sum"])
last_stats_time = stats[consumption_statistic_id][0]["start"]
assert last_stats_time == start.timestamp()
cost_statistics = []
consumption_statistics = []

View File

@ -7,5 +7,5 @@
"documentation": "https://www.home-assistant.io/integrations/opower",
"iot_class": "cloud_polling",
"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%]"
},
"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": {

View File

@ -32,7 +32,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
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
try:
@ -49,7 +49,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_update_data() -> OVODailyUsage:
"""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
async with asyncio.timeout(10):

View File

@ -46,7 +46,7 @@ class OVOEnergyFlowHandler(ConfigFlow, domain=DOMAIN):
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
try:

View File

@ -10,6 +10,7 @@ from aiopyarr import exceptions
from aiopyarr.models.host_configuration import PyArrHostConfiguration
from aiopyarr.radarr_client import RadarrClient
import voluptuous as vol
from yarl import URL
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
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
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:
if result := await validate_input(self.hass, user_input):
user_input[CONF_API_KEY] = result[1]

View File

@ -10,13 +10,9 @@ import uuid
from ring_doorbell import Auth, Ring, RingDevices
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.helpers import (
device_registry as dr,
entity_registry as er,
instance_id,
)
from homeassistant.helpers import device_registry as dr, entity_registry as er
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from .const import CONF_LISTEN_CREDENTIALS, DOMAIN, PLATFORMS
@ -38,18 +34,12 @@ class RingData:
type RingConfigEntry = ConfigEntry[RingData]
async def get_auth_agent_id(hass: HomeAssistant) -> tuple[str, str]:
"""Return user-agent and hardware id for Auth instantiation.
def get_auth_user_agent() -> str:
"""Return user-agent for Auth instantiation.
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"
# 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
return f"{APPLICATION_NAME}/{DOMAIN}-integration"
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},
)
user_agent, hardware_id = await get_auth_agent_id(hass)
user_agent = get_auth_user_agent()
client_session = async_get_clientsession(hass)
auth = Auth(
user_agent,
entry.data[CONF_TOKEN],
token_updater,
hardware_id=hardware_id,
hardware_id=entry.data[CONF_DEVICE_ID],
http_client_session=client_session,
)
ring = Ring(auth)
@ -138,3 +128,25 @@ async def _migrate_old_unique_ids(hass: HomeAssistant, entry_id: str) -> None:
return None
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
import logging
from typing import Any
import uuid
from ring_doorbell import Auth, AuthenticationError, Requires2FAError
import voluptuous as vol
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.exceptions import HomeAssistantError
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from . import get_auth_agent_id
from .const import CONF_2FA, DOMAIN
from . import get_auth_user_agent
from .const import CONF_2FA, CONF_CONFIG_ENTRY_MINOR_VERSION, DOMAIN
_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_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."""
user_agent, hardware_id = await get_auth_agent_id(hass)
user_agent = get_auth_user_agent()
auth = Auth(
user_agent,
http_client_session=async_get_clientsession(hass),
@ -52,8 +63,10 @@ class RingConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle a config flow for Ring."""
VERSION = 1
MINOR_VERSION = CONF_CONFIG_ENTRY_MINOR_VERSION
user_pass: dict[str, Any] = {}
hardware_id: str | None = None
reauth_entry: ConfigEntry | None = None
async def async_step_user(
@ -64,8 +77,10 @@ class RingConfigFlow(ConfigFlow, domain=DOMAIN):
if user_input is not None:
await self.async_set_unique_id(user_input[CONF_USERNAME])
self._abort_if_unique_id_configured()
if not self.hardware_id:
self.hardware_id = str(uuid.uuid4())
try:
token = await validate_input(self.hass, user_input)
token = await validate_input(self.hass, self.hardware_id, user_input)
except Require2FA:
self.user_pass = user_input
@ -78,7 +93,11 @@ class RingConfigFlow(ConfigFlow, domain=DOMAIN):
else:
return self.async_create_entry(
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(
@ -120,8 +139,13 @@ class RingConfigFlow(ConfigFlow, domain=DOMAIN):
if user_input:
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:
token = await validate_input(self.hass, user_input)
token = await validate_input(self.hass, self.hardware_id, user_input)
except Require2FA:
self.user_pass = user_input
return await self.async_step_2fa()
@ -134,6 +158,7 @@ class RingConfigFlow(ConfigFlow, domain=DOMAIN):
data = {
CONF_USERNAME: user_input[CONF_USERNAME],
CONF_TOKEN: token,
CONF_DEVICE_ID: self.hardware_id,
}
self.hass.config_entries.async_update_entry(
self.reauth_entry, data=data
@ -146,7 +171,8 @@ class RingConfigFlow(ConfigFlow, domain=DOMAIN):
data_schema=STEP_REAUTH_DATA_SCHEMA,
errors=errors,
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 datetime import timedelta
from typing import Final
from homeassistant.const import Platform
@ -31,3 +32,5 @@ SCAN_INTERVAL = timedelta(minutes=1)
CONF_2FA = "2fa"
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%]"
},
"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": {

View File

@ -14,7 +14,8 @@
"unknown": "[%key:common::config_flow::error::unknown%]"
},
"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": {

View File

@ -90,13 +90,21 @@ class SchlageDataUpdateCoordinator(DataUpdateCoordinator[SchlageData]):
devices = dr.async_entries_for_config_entry(
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())
if removed_locks := previous_locks - current_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_id=device_id,
device_id=previous_locks_by_lock_id[lock_id].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 contextlib import suppress
import datetime
from functools import partial
import itertools
import logging
import math
@ -39,6 +38,7 @@ from homeassistant.helpers.entity import entity_sources
from homeassistant.helpers.typing import UNDEFINED, UndefinedType
from homeassistant.loader import async_suggest_report_issue
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.hass_dict import HassKey
@ -686,7 +686,6 @@ def list_statistic_ids(
@callback
def _update_issues(
report_issue: Callable[[str, str, dict[str, Any]], None],
clear_issue: Callable[[str, str], None],
sensor_states: list[State],
metadatas: dict[str, tuple[int, StatisticMetaData]],
) -> None:
@ -707,8 +706,6 @@ def _update_issues(
entity_id,
{"statistic_id": entity_id},
)
else:
clear_issue("state_class_removed", entity_id)
metadata_unit = metadata[1]["unit_of_measurement"]
converter = statistics.STATISTIC_UNIT_TO_UNIT_CONVERTER.get(metadata_unit)
@ -725,8 +722,6 @@ def _update_issues(
"supported_unit": metadata_unit,
},
)
else:
clear_issue("units_changed", entity_id)
elif numeric and state_unit not in converter.VALID_UNITS:
# The state unit can't be converted to the unit in metadata
valid_units = (unit or "<None>" for unit in converter.VALID_UNITS)
@ -741,8 +736,6 @@ def _update_issues(
"supported_unit": valid_units_str,
},
)
else:
clear_issue("units_changed", entity_id)
def update_statistics_issues(
@ -756,36 +749,50 @@ def update_statistics_issues(
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(
issue_type: str, statistic_id: str, data: dict[str, Any]
) -> None:
"""Create an issue registry issue."""
hass.loop.call_soon_threadsafe(
partial(
ir.async_create_issue,
hass,
DOMAIN,
f"{issue_type}_{statistic_id}",
data=data | {"issue_type": issue_type},
is_fixable=False,
severity=ir.IssueSeverity.WARNING,
translation_key=issue_type,
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}"
issue_id = f"{issue_type}_{statistic_id}"
issues.discard(issue_id)
ir.create_issue(
hass,
DOMAIN,
issue_id,
data=data | {"issue_type": issue_type},
is_fixable=False,
severity=ir.IssueSeverity.WARNING,
translation_key=issue_type,
translation_placeholders=data,
)
_update_issues(
create_issue_registry_issue,
delete_issue_registry_issue,
sensor_states,
metadatas,
)
for issue_id in issues:
hass.loop.call_soon_threadsafe(ir.async_delete_issue, hass, DOMAIN, issue_id)
def validate_statistics(
@ -811,7 +818,6 @@ def validate_statistics(
_update_issues(
create_statistic_validation_issue,
lambda issue_type, statistic_id: None,
sensor_states,
metadatas,
)

View File

@ -135,3 +135,21 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
data[PYSMA_REMOVE_LISTENER]()
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."""
VERSION = 1
MINOR_VERSION = 2
def __init__(self) -> None:
"""Initialize."""
@ -76,7 +77,7 @@ class SmaConfigFlow(ConfigFlow, domain=DOMAIN):
errors["base"] = "unknown"
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)
return self.async_create_entry(
title=self._data[CONF_HOST], data=self._data

View File

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

View File

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

View File

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

View File

@ -32,7 +32,8 @@
"reconfigure_confirm": {
"title": "Configure SolarLog",
"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": {
"service": "mdi:database"
},
"sync": {
"service": "mdi:sync"
},
"unsync": {
"service": "mdi:sync-off"
}
}
}

View File

@ -30,19 +30,3 @@ call_query:
advanced: true
selector:
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%]"
}
}
},
"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": {

View File

@ -21,7 +21,8 @@
"unknown": "[%key:common::config_flow::error::unknown%]"
},
"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": {

View File

@ -37,17 +37,29 @@ class SensorData:
def as_dict(self) -> dict[str, Any]:
"""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 {
"disk_usage": {k: str(v) for k, v in self.disk_usage.items()},
"disk_usage": disk_usage,
"swap": str(self.swap),
"memory": str(self.memory),
"io_counters": {k: str(v) for k, v in self.io_counters.items()},
"addresses": {k: str(v) for k, v in self.addresses.items()},
"io_counters": io_counters,
"addresses": addresses,
"load": str(self.load),
"cpu_percent": str(self.cpu_percent),
"boot_time": str(self.boot_time),
"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
async def do_nothing() -> None:
"""Do nothing."""
async def do_nothing() -> dict[str, dict[str, bool]]:
"""Do nothing with a positive result."""
return {"response": {"result": True}}
@dataclass(frozen=True, kw_only=True)

View File

@ -378,7 +378,17 @@ ENERGY_LIVE_DESCRIPTIONS: tuple[SensorEntityDescription, ...] = (
device_class=SensorDeviceClass.POWER,
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, ...] = (

View File

@ -392,6 +392,16 @@
"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": {
"name": "Load power"
},

View File

@ -1,7 +1,8 @@
{
"config": {
"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": {
"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",
"integration_type": "hub",
"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,
HVACMode,
)
from homeassistant.const import PRECISION_WHOLE
from homeassistant.const import PRECISION_TENTHS
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import ServiceValidationError
from homeassistant.helpers.entity_platform import AddEntitiesCallback
@ -64,7 +64,7 @@ class TPLinkClimateEntity(CoordinatedTPLinkEntity, ClimateEntity):
| ClimateEntityFeature.TURN_ON
)
_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.
_enable_turn_on_off_backwards_compatibility = False

View File

@ -301,5 +301,5 @@
"iot_class": "local_polling",
"loggers": ["kasa"],
"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 .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
class IntegerTypeData:
@ -256,7 +267,13 @@ class TuyaEntity(Entity):
order = ["function", "status_range"]
for key in order:
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

View File

@ -42,7 +42,8 @@
},
"abort": {
"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": {

View File

@ -34,11 +34,11 @@ class VenstarEntity(CoordinatorEntity[VenstarDataUpdateCoordinator]):
@property
def device_info(self) -> DeviceInfo:
"""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(
identifiers={(DOMAIN, self._config.entry_id)},
name=self._client.name,
manufacturer="Venstar",
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."""
from collections.abc import Callable
from contextlib import suppress
from dataclasses import dataclass
import enum
from typing import Any
@ -48,8 +49,12 @@ class HeatingProgram(enum.StrEnum):
) -> str | None:
"""Return the mapped ViCare heating program for the Home Assistant preset."""
for program in supported_heating_programs:
if VICARE_TO_HA_PRESET_HEATING.get(HeatingProgram(program)) == ha_preset:
return program
with suppress(ValueError):
if (
VICARE_TO_HA_PRESET_HEATING.get(HeatingProgram(program))
== ha_preset
):
return program
return None

View File

@ -27,7 +27,8 @@
}
},
"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": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",

View File

@ -802,7 +802,8 @@ async def async_setup_entry(
if not entities:
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)

View File

@ -20,7 +20,9 @@
"oauth_error": "[%key:common::config_flow::abort::oauth2_error%]",
"oauth_timeout": "[%key:common::config_flow::abort::oauth2_timeout%]",
"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": {
"default": "Successfully authenticated with Withings."

View File

@ -75,7 +75,7 @@ class WebControlProConfigFlow(ConfigFlow, domain=DOMAIN):
if self.source == dhcp.DOMAIN:
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:
data_values = {CONF_HOST: SUGGESTED_HOST}

View File

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

View File

@ -7,5 +7,5 @@
"iot_class": "local_polling",
"loggers": ["holidays"],
"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."
},
"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": {

View File

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

View File

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

View File

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

View File

@ -9,6 +9,7 @@ import collections.abc
from collections.abc import Callable, Generator, Iterable
from contextlib import AbstractContextManager
from contextvars import ContextVar
from copy import deepcopy
from datetime import date, datetime, time, timedelta
from functools import cache, cached_property, lru_cache, partial, wraps
import json
@ -2166,7 +2167,8 @@ def merge_response(value: ServiceResponse) -> list[Any]:
is_single_list = False
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):
raise TypeError("Response is not a dictionary")
for value_key, type_response in entity_response.items():

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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