Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
37eebc3
Restore the recommended LLM API fallback for LiteLLM (#179768)
balloob Aug 22, 2026
49259e3
Localize exception messages for CentriConnect (#179752)
gresrun Aug 22, 2026
839bb52
Add diagnostics platform to Hot Spring (#179778)
Moustachauve Aug 22, 2026
8e05f4b
Name the ekey bionyx webhook deletion poll interval (#179788)
balloob Aug 22, 2026
e1a7b87
Remove overbuilt traccar_server subscribe tests (#179785)
balloob Aug 22, 2026
e149a59
Fix flaky concord232 polling tests (#179784)
balloob Aug 22, 2026
d6e083b
Skip hassfest MDI icon generation on an outdated frontend (#179781)
balloob Aug 22, 2026
1584479
Mock entry setup in cielo_home config flow tests (#179782)
balloob Aug 22, 2026
9183a11
Fix owner of the all-lights grouped light in the Hue v2 test fixture …
balloob Aug 22, 2026
b9c9e7a
Patch MAP_SLEEP in the Roborock selected map test (#179790)
balloob Aug 22, 2026
b2e7fea
Name the JVC Projector delay between a power command and the refresh …
balloob Aug 22, 2026
d39e5d0
Report slow tests by duration instead of by rank (#179792)
balloob Aug 22, 2026
7aca54b
Remove a dead sleep from the Insteon properties test (#179793)
balloob Aug 22, 2026
2f30412
Name the Motion Blinds per-blind update delay (#179794)
balloob Aug 22, 2026
4ce0b20
Fix LG webOS TV media playback channel matching (#179796)
thecode Aug 22, 2026
8febbae
Bump guntamatic to v1.11.1 (#179773)
JensTimmerman Aug 22, 2026
c325735
Handle AuthFailedError in Nice G.O. WebSocket connection (#179753)
IceBotYT Aug 22, 2026
4130e16
Add config flow to remember_the_milk (#178808)
MartinHjelmare Aug 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 4 additions & 11 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -861,9 +861,6 @@ jobs:
- name: Register Python problem matcher
run: |
echo "::add-matcher::.github/workflows/matchers/python.json"
- name: Register pytest slow test problem matcher
run: |
echo "::add-matcher::.github/workflows/matchers/pytest-slow.json"
- name: Download pytest_buckets
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
Expand Down Expand Up @@ -895,7 +892,8 @@ jobs:
python3 -b -X dev -m pytest \
-qq \
--timeout=9 \
--durations=10 \
--durations=0 \
--durations-min=1 \
--numprocesses auto \
--snapshot-details \
--dist=loadfile \
Expand Down Expand Up @@ -997,9 +995,6 @@ jobs:
- name: Register Python problem matcher
run: |
echo "::add-matcher::.github/workflows/matchers/python.json"
- name: Register pytest slow test problem matcher
run: |
echo "::add-matcher::.github/workflows/matchers/pytest-slow.json"
- name: Install SQL Python libraries
run: |
. venv/bin/activate
Expand Down Expand Up @@ -1038,7 +1033,8 @@ jobs:
--snapshot-details \
${cov_params[@]} \
-o console_output_style=count \
--durations=10 \
--durations=0 \
--durations-min=10 \
-p no:sugar \
--exclude-warning-annotations \
--dburl=mysql://root:password@127.0.0.1/homeassistant-test \
Expand Down Expand Up @@ -1150,9 +1146,6 @@ jobs:
- name: Register Python problem matcher
run: |
echo "::add-matcher::.github/workflows/matchers/python.json"
- name: Register pytest slow test problem matcher
run: |
echo "::add-matcher::.github/workflows/matchers/pytest-slow.json"
- name: Install SQL Python libraries
run: |
. venv/bin/activate
Expand Down
20 changes: 17 additions & 3 deletions homeassistant/components/centriconnect/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,9 @@ async def _async_setup(self) -> None:
try:
tank_data = await self.api_client.async_get_tank_data()
except CentriConnectError as err:
raise UpdateFailed("Could not fetch device info") from err
raise UpdateFailed(
translation_domain=DOMAIN, translation_key="entry_setup_failed"
) from err
self.device_info = CentriConnectDeviceInfo(
device_id=tank_data.device_id,
device_name=tank_data.device_name,
Expand All @@ -87,7 +89,19 @@ async def _async_update_data(self) -> Tank:
try:
state = await self.api_client.async_get_tank_data()
except CentriConnectConnectionError as err:
raise UpdateFailed(f"Error communicating with device: {err}") from err
raise UpdateFailed(
translation_domain=DOMAIN,
translation_key="communication_error",
translation_placeholders={
"error": repr(err),
},
) from err
except CentriConnectError as err:
raise UpdateFailed(f"Unexpected response: {err}") from err
raise UpdateFailed(
translation_domain=DOMAIN,
translation_key="unexpected_response",
translation_placeholders={
"error": repr(err),
},
) from err
return state
2 changes: 1 addition & 1 deletion homeassistant/components/centriconnect/quality_scale.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ rules:
entity-device-class: done
entity-disabled-by-default: done
entity-translations: done
exception-translations: todo
exception-translations: done
icon-translations: done
reconfiguration-flow: todo
repair-issues:
Expand Down
11 changes: 11 additions & 0 deletions homeassistant/components/centriconnect/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,5 +65,16 @@
"name": "Tank size"
}
}
},
"exceptions": {
"communication_error": {
"message": "Error communicating with device: {error}"
},
"entry_setup_failed": {
"message": "Could not fetch device info"
},
"unexpected_response": {
"message": "Unexpected response: {error}"
}
}
}
4 changes: 3 additions & 1 deletion homeassistant/components/ekeybionyx/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
# does not end with space or dot
VALID_NAME_PATTERN = re.compile(r"^(?![\d\s])[\w\d \.]*[\w\d]$")

DELETION_POLL_INTERVAL = 5


class ConfigFlowEkeyApi(ekey_bionyxpy.AbstractAuth):
"""Authentication implementation used during config flow, without refresh.
Expand Down Expand Up @@ -276,4 +278,4 @@ async def async_check_deletion_status(self) -> None:
][0]
if self._data["system"].function_webhook_quotas["used"] == 0:
break
await asyncio.sleep(5)
await asyncio.sleep(DELETION_POLL_INTERVAL)
2 changes: 1 addition & 1 deletion homeassistant/components/guntamatic/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,5 @@
"integration_type": "device",
"iot_class": "local_polling",
"quality_scale": "silver",
"requirements": ["guntamatic==1.11.0"]
"requirements": ["guntamatic==1.11.1"]
}
58 changes: 58 additions & 0 deletions homeassistant/components/hotspring/diagnostics.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
"""Diagnostics support for Hot Spring."""

from dataclasses import asdict
import re
from typing import Any

from homeassistant.components.diagnostics import REDACTED, async_redact_data
from homeassistant.const import CONF_HOST
from homeassistant.core import HomeAssistant

from .coordinator import HotSpringConfigEntry

TO_REDACT = {
CONF_HOST,
}


def _redact_mac(value: str, patterns: list[str]) -> str:
"""Redact MAC address patterns from a string."""
for pattern in patterns:
value = re.sub(re.escape(pattern), REDACTED, value, flags=re.IGNORECASE)
return value


async def async_get_config_entry_diagnostics(
hass: HomeAssistant, entry: HotSpringConfigEntry
) -> dict[str, Any]:
"""Return diagnostics for a config entry."""
coordinator = entry.runtime_data
spa = coordinator.data

info = asdict(spa.info)
if mac_address := spa.info.mac_address:
clean_mac = mac_address.replace(":", "")
patterns = [mac_address, clean_mac, clean_mac[-6:]]
info["root_topic"] = _redact_mac(info["root_topic"], patterns)
info["hostname"] = _redact_mac(info["hostname"], patterns)

return {
"entry": async_redact_data(entry.data, TO_REDACT),
"data": {
"info": info,
"heater": asdict(spa.heater),
"jets": [asdict(jet) for jet in spa.jets],
"blower": asdict(spa.blower),
"light_zones": [asdict(zone) for zone in spa.light_zones],
"logo_light": asdict(spa.logo_light),
"clean_cycle": asdict(spa.clean_cycle),
"spa_lock": asdict(spa.spa_lock),
"water_care": asdict(spa.water_care),
"freshwater_iq": asdict(spa.freshwater_iq),
"energy_savings": [asdict(schedule) for schedule in spa.energy_savings],
"versions": asdict(spa.versions),
"connection_status": asdict(spa.connection_status),
"diagnostics": asdict(spa.diagnostics),
"test_metrics": asdict(spa.test_metrics),
},
}
2 changes: 1 addition & 1 deletion homeassistant/components/hotspring/quality_scale.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ rules:

# Gold
devices: done
diagnostics: todo
diagnostics: done
discovery-update-info: done
discovery: done
docs-data-update: done
Expand Down
6 changes: 4 additions & 2 deletions homeassistant/components/jvc_projector/remote.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
from .coordinator import JVCConfigEntry
from .entity import JvcProjectorEntity

POWER_SLEEP = 1

COMMANDS: list[str] = [
cmd.Remote.MENU,
cmd.Remote.UP,
Expand Down Expand Up @@ -92,14 +94,14 @@ def is_on(self) -> bool:
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn the device on."""
await self.device.set(cmd.Power, cmd.Power.ON)
await asyncio.sleep(1)
await asyncio.sleep(POWER_SLEEP)
await self.coordinator.async_refresh()

@override
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn the device off."""
await self.device.set(cmd.Power, cmd.Power.OFF)
await asyncio.sleep(1)
await asyncio.sleep(POWER_SLEEP)
await self.coordinator.async_refresh()

@override
Expand Down
7 changes: 5 additions & 2 deletions homeassistant/components/litellm/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ async def async_step_init(
return self.async_abort(reason="entry_not_loaded")

if user_input is not None:
if not user_input.get(CONF_LLM_HASS_API):
if user_input.get(CONF_LLM_HASS_API) is None:
user_input.pop(CONF_LLM_HASS_API, None)
if self._is_new:
return self.async_create_entry(
Expand Down Expand Up @@ -241,7 +241,10 @@ async def async_step_init(
): TemplateSelector(),
vol.Optional(
CONF_LLM_HASS_API,
default=self.options.get(CONF_LLM_HASS_API, []),
default=self.options.get(
CONF_LLM_HASS_API,
RECOMMENDED_CONVERSATION_OPTIONS[CONF_LLM_HASS_API],
),
): SelectSelector(
SelectSelectorConfig(options=hass_apis, multiple=True)
),
Expand Down
1 change: 1 addition & 0 deletions homeassistant/components/motion_blinds/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,6 @@
UPDATE_INTERVAL = 600
UPDATE_INTERVAL_FAST = 60
UPDATE_DELAY_STOP = 3
UPDATE_DELAY_BLIND = 1.5
UPDATE_INTERVAL_MOVING = 5
UPDATE_INTERVAL_MOVING_WIFI = 45
3 changes: 2 additions & 1 deletion homeassistant/components/motion_blinds/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
CONF_WAIT_FOR_PUSH,
DEFAULT_WAIT_FOR_PUSH,
KEY_GATEWAY,
UPDATE_DELAY_BLIND,
UPDATE_INTERVAL,
UPDATE_INTERVAL_FAST,
)
Expand Down Expand Up @@ -89,7 +90,7 @@ async def _async_update_data(self):
)

for blind in self.gateway.device_list.values():
await asyncio.sleep(1.5)
await asyncio.sleep(UPDATE_DELAY_BLIND)
async with self.api_lock:
data[blind.mac] = await self.hass.async_add_executor_job(
self.update_blind, blind
Expand Down
39 changes: 27 additions & 12 deletions homeassistant/components/nice_go/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,17 +147,8 @@ async def _async_update_data(self) -> dict[str, NiceGODevice]:
async def _async_setup(self) -> None:
"""Set up the coordinator."""
async with asyncio.timeout(10):
expiry_time = (
self.refresh_token_creation_time
+ REFRESH_TOKEN_EXPIRY_TIME.total_seconds()
)
try:
if datetime.now().timestamp() >= expiry_time: # pylint: disable=home-assistant-enforce-naive-now
await self.update_refresh_token()
else:
await self.api.authenticate_refresh(
self.refresh_token, async_get_clientsession(self.hass)
)
await self.authenticate()
_LOGGER.debug("Authenticated with Nice G.O. API")

barriers = await self.api.get_all_barriers()
Expand All @@ -171,13 +162,31 @@ async def _async_setup(self) -> None:
barrier.id: barrier for barrier in parsed_barriers if barrier
}
self.organization_id = await barriers[0].get_attr("organization")
except AuthFailedError as e:
raise ConfigEntryAuthFailed from e
except ApiError as e:
raise UpdateFailed from e
else:
self.async_set_updated_data(devices)

async def authenticate(self) -> None:
"""Authenticate with the Nice G.O. API."""
_LOGGER.debug("Authenticating with Nice G.O. API")
expiry_time = (
self.refresh_token_creation_time + REFRESH_TOKEN_EXPIRY_TIME.total_seconds()
)
try:
if datetime.now().timestamp() >= expiry_time: # pylint: disable=home-assistant-enforce-naive-now
await self.update_refresh_token()
else:
await self.api.authenticate_refresh(
self.refresh_token, async_get_clientsession(self.hass)
)
except AuthFailedError as e:
_LOGGER.exception("Authentication failed")
raise ConfigEntryAuthFailed from e
except ApiError as e:
_LOGGER.exception("API error")
raise UpdateFailed from e

async def update_refresh_token(self) -> None:
"""Update the refresh token with Nice G.O. API."""
_LOGGER.debug("Updating the refresh token with Nice G.O. API")
Expand Down Expand Up @@ -214,6 +223,12 @@ async def client_listen(self) -> None:

try:
await self.api.connect(reconnect=True)
except AuthFailedError:
# Try reauthenticating otherwise start reauth flow
_LOGGER.debug(
"Got auth failed when connecting to websocket, trying to reauthenticate"
)
await self.authenticate()
except ApiError:
_LOGGER.exception("API error")
else:
Expand Down
Loading
Loading