mirror of
https://github.com/home-assistant/core.git
synced 2026-04-19 16:09:06 +02:00
Fix Victron BLE false reauth triggered by unknown enum bitmask combinations (#167809)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -19,7 +19,7 @@ from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_ACCESS_TOKEN, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from .const import REAUTH_AFTER_FAILURES
|
||||
from .const import REAUTH_AFTER_FAILURES, VICTRON_IDENTIFIER
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -38,18 +38,24 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
nonlocal consecutive_failures
|
||||
update = data.update(service_info)
|
||||
|
||||
# If the device type was recognized (devices dict populated) but
|
||||
# only signal strength came back, decryption likely failed.
|
||||
# Unsupported devices have an empty devices dict and won't trigger this.
|
||||
if update.devices and len(update.entity_values) <= 1:
|
||||
consecutive_failures += 1
|
||||
if consecutive_failures >= REAUTH_AFTER_FAILURES:
|
||||
_LOGGER.debug(
|
||||
"Triggering reauth for %s after %d consecutive failures",
|
||||
address,
|
||||
consecutive_failures,
|
||||
)
|
||||
entry.async_start_reauth(hass)
|
||||
# Only consider a reauth when the device type is recognised (devices
|
||||
# populated) but the advertisement key fails the quick-check built into
|
||||
# validate_advertisement_key. Using the key check instead of counting
|
||||
# entity values avoids false positives: some devices legitimately return
|
||||
# few (or zero) sensor values when in certain error or alarm states.
|
||||
raw_data = service_info.manufacturer_data.get(VICTRON_IDENTIFIER)
|
||||
if update.devices and raw_data is not None:
|
||||
if not data.validate_advertisement_key(raw_data):
|
||||
consecutive_failures += 1
|
||||
if consecutive_failures >= REAUTH_AFTER_FAILURES:
|
||||
_LOGGER.debug(
|
||||
"Triggering reauth for %s after %d consecutive failures",
|
||||
address,
|
||||
consecutive_failures,
|
||||
)
|
||||
entry.async_start_reauth(hass)
|
||||
consecutive_failures = 0
|
||||
else:
|
||||
consecutive_failures = 0
|
||||
else:
|
||||
consecutive_failures = 0
|
||||
|
||||
@@ -187,6 +187,20 @@ VICTRON_VEBUS_BAD_KEY_SERVICE_INFO = BluetoothServiceInfo(
|
||||
source="local",
|
||||
)
|
||||
|
||||
# Same DC/DC converter but with OffReason=0x81 (NO_INPUT_POWER|ENGINE_SHUTDOWN),
|
||||
# a real bitmask combination that the current OffReason enum doesn't handle.
|
||||
# The key check byte is valid so validate_advertisement_key passes, but
|
||||
# parsing raises ValueError → sparse update (signal strength only).
|
||||
VICTRON_DC_DC_CONVERTER_UNKNOWN_OFF_REASON_SERVICE_INFO = BluetoothServiceInfo(
|
||||
name="DC/DC Converter",
|
||||
address="01:02:03:04:05:08",
|
||||
rssi=-60,
|
||||
manufacturer_data={0x02E1: bytes.fromhex("1000c0a304121d64ca8d442b90bbde6a8cba")},
|
||||
service_data={},
|
||||
service_uuids=[],
|
||||
source="local",
|
||||
)
|
||||
|
||||
VICTRON_VEBUS_SENSORS = {
|
||||
"inverter_charger_device_state": "float",
|
||||
"inverter_charger_battery_voltage": "14.45",
|
||||
|
||||
@@ -24,6 +24,7 @@ from .fixtures import (
|
||||
VICTRON_BATTERY_SENSE_TOKEN,
|
||||
VICTRON_DC_DC_CONVERTER_SERVICE_INFO,
|
||||
VICTRON_DC_DC_CONVERTER_TOKEN,
|
||||
VICTRON_DC_DC_CONVERTER_UNKNOWN_OFF_REASON_SERVICE_INFO,
|
||||
VICTRON_DC_ENERGY_METER_SERVICE_INFO,
|
||||
VICTRON_DC_ENERGY_METER_TOKEN,
|
||||
VICTRON_SMART_BATTERY_PROTECT_SERVICE_INFO,
|
||||
@@ -208,6 +209,52 @@ async def test_reauth_triggered_only_once(
|
||||
assert len(flows) == 1
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("enable_bluetooth")
|
||||
async def test_reauth_not_triggered_on_unknown_enum_value(
|
||||
hass: HomeAssistant,
|
||||
) -> None:
|
||||
"""Test reauth is NOT triggered when a valid key yields a sparse update.
|
||||
|
||||
Some devices report bitmask combinations for OffReason or AlarmReason that
|
||||
are not in the enum (e.g. NO_INPUT_POWER|ENGINE_SHUTDOWN = 0x81 on a DC-DC
|
||||
converter that stopped due to both conditions simultaneously). The parser
|
||||
raises ValueError, producing a sparse update (signal strength only).
|
||||
This must not be mistaken for a wrong encryption key.
|
||||
|
||||
Regression test for https://github.com/home-assistant/core/issues/167105
|
||||
"""
|
||||
entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
data={
|
||||
"address": VICTRON_DC_DC_CONVERTER_UNKNOWN_OFF_REASON_SERVICE_INFO.address,
|
||||
CONF_ACCESS_TOKEN: VICTRON_DC_DC_CONVERTER_TOKEN,
|
||||
},
|
||||
unique_id=VICTRON_DC_DC_CONVERTER_UNKNOWN_OFF_REASON_SERVICE_INFO.address,
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
assert await hass.config_entries.async_setup(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
service_info = VICTRON_DC_DC_CONVERTER_UNKNOWN_OFF_REASON_SERVICE_INFO
|
||||
for idx in range(REAUTH_AFTER_FAILURES + 1):
|
||||
inject_bluetooth_service_info(
|
||||
hass,
|
||||
BluetoothServiceInfo(
|
||||
name=service_info.name,
|
||||
address=service_info.address,
|
||||
rssi=service_info.rssi - idx,
|
||||
manufacturer_data=service_info.manufacturer_data,
|
||||
service_data=service_info.service_data,
|
||||
service_uuids=service_info.service_uuids,
|
||||
source=service_info.source,
|
||||
),
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
flows = hass.config_entries.flow.async_progress_by_handler(DOMAIN)
|
||||
assert len(flows) == 0
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("enable_bluetooth")
|
||||
@pytest.mark.parametrize(
|
||||
("payload_hex", "expected_state"),
|
||||
|
||||
Reference in New Issue
Block a user