Skip to content

Commit

Permalink
fix(alarm): address HA 2025.x deprecations (#171)
Browse files Browse the repository at this point in the history
- Fix testing for Home Assistant > 2024.8
- Fix alarm panel deprecations
- Fix `ConfigEntryState` deprecations
- Fix `async_forward_entry_setup` deprecation (fixes #169)
- Fix `alarm_state` deprecation for HA Core 2025.11
  • Loading branch information
andreapier authored Nov 14, 2024
1 parent 83cb580 commit 88491c7
Show file tree
Hide file tree
Showing 6 changed files with 38 additions and 62 deletions.
3 changes: 1 addition & 2 deletions custom_components/econnect_metronet/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,7 @@ async def async_setup_entry(hass: HomeAssistant, config: ConfigEntry) -> bool:
hass.services.async_register(DOMAIN, "disarm_sectors", partial(services.disarm_sectors, hass, config.entry_id))
hass.services.async_register(DOMAIN, "update_state", partial(services.update_state, hass, config.entry_id))

for component in PLATFORMS:
hass.async_create_task(hass.config_entries.async_forward_entry_setup(config, component))
await hass.config_entries.async_forward_entry_setups(config, PLATFORMS)

return True

Expand Down
24 changes: 8 additions & 16 deletions custom_components/econnect_metronet/alarm_control_panel.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,14 @@

from homeassistant.components.alarm_control_panel import (
AlarmControlPanelEntity,
AlarmControlPanelState,
CodeFormat,
)
from homeassistant.components.alarm_control_panel.const import (
AlarmControlPanelEntityFeature as AlarmFeatures,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
CONF_USERNAME,
STATE_ALARM_ARMED_AWAY,
STATE_ALARM_ARMED_HOME,
STATE_ALARM_ARMED_NIGHT,
STATE_ALARM_ARMED_VACATION,
STATE_ALARM_ARMING,
STATE_ALARM_DISARMED,
STATE_ALARM_DISARMING,
)
from homeassistant.const import CONF_USERNAME
from homeassistant.core import HomeAssistant
from homeassistant.helpers.update_coordinator import CoordinatorEntity

Expand Down Expand Up @@ -77,7 +69,7 @@ def icon(self):
return "hass:shield-home"

@property
def state(self):
def alarm_state(self):
"""Return the state of the device."""
return self._device.state

Expand All @@ -91,19 +83,19 @@ def supported_features(self):
"""Return the list of supported features."""
return AlarmFeatures.ARM_HOME | AlarmFeatures.ARM_AWAY | AlarmFeatures.ARM_NIGHT | AlarmFeatures.ARM_VACATION

@set_device_state(STATE_ALARM_DISARMED, STATE_ALARM_DISARMING)
@set_device_state(AlarmControlPanelState.DISARMED, AlarmControlPanelState.DISARMING)
@retry_refresh_token
async def async_alarm_disarm(self, code=None):
"""Send disarm command."""
await self.hass.async_add_executor_job(self._device.disarm, code)

@set_device_state(STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMING)
@set_device_state(AlarmControlPanelState.ARMED_AWAY, AlarmControlPanelState.ARMING)
@retry_refresh_token
async def async_alarm_arm_away(self, code=None):
"""Send arm away command."""
await self.hass.async_add_executor_job(self._device.arm, code, self._device._sectors_away)

@set_device_state(STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMING)
@set_device_state(AlarmControlPanelState.ARMED_HOME, AlarmControlPanelState.ARMING)
@retry_refresh_token
async def async_alarm_arm_home(self, code=None):
"""Send arm home command."""
Expand All @@ -113,7 +105,7 @@ async def async_alarm_arm_home(self, code=None):

await self.hass.async_add_executor_job(self._device.arm, code, self._device._sectors_home)

@set_device_state(STATE_ALARM_ARMED_NIGHT, STATE_ALARM_ARMING)
@set_device_state(AlarmControlPanelState.ARMED_NIGHT, AlarmControlPanelState.ARMING)
@retry_refresh_token
async def async_alarm_arm_night(self, code=None):
"""Send arm night command."""
Expand All @@ -123,7 +115,7 @@ async def async_alarm_arm_night(self, code=None):

await self.hass.async_add_executor_job(self._device.arm, code, self._device._sectors_night)

@set_device_state(STATE_ALARM_ARMED_VACATION, STATE_ALARM_ARMING)
@set_device_state(AlarmControlPanelState.ARMED_VACATION, AlarmControlPanelState.ARMING)
@retry_refresh_token
async def async_alarm_arm_vacation(self, code=None):
"""Send arm vacation command."""
Expand Down
25 changes: 8 additions & 17 deletions custom_components/econnect_metronet/devices.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,7 @@
LockError,
ParseError,
)
from homeassistant.const import (
STATE_ALARM_ARMED_AWAY,
STATE_ALARM_ARMED_HOME,
STATE_ALARM_ARMED_NIGHT,
STATE_ALARM_ARMED_VACATION,
STATE_ALARM_ARMING,
STATE_ALARM_DISARMED,
STATE_ALARM_DISARMING,
STATE_UNAVAILABLE,
)
from homeassistant.components.alarm_control_panel import AlarmControlPanelState
from requests.exceptions import HTTPError

from .const import (
Expand Down Expand Up @@ -72,7 +63,7 @@ def __init__(self, connection, config=None):
self._sectors_vacation = config.get(CONF_AREAS_ARM_VACATION) or []

# Alarm state
self.state = STATE_UNAVAILABLE
self.state = None

def _register_sector(self, entity):
"""Register a sector entity in the device's internal inventory."""
Expand Down Expand Up @@ -225,28 +216,28 @@ def get_state(self):
"""
# If the system is arming or disarming, return the current state
# to prevent the state from being updated while the system is in transition.
if self.state in [STATE_ALARM_ARMING, STATE_ALARM_DISARMING]:
if self.state in [AlarmControlPanelState.ARMING, AlarmControlPanelState.DISARMING]:
return self.state

sectors_armed = dict(self.items(q.SECTORS, status=True))
if not sectors_armed:
return STATE_ALARM_DISARMED
return AlarmControlPanelState.DISARMED

# Note: `element` is the sector ID you use to arm/disarm the sector.
sectors = [sectors["element"] for sectors in sectors_armed.values()]
# Sort lists here for robustness, ensuring accurate comparisons
# regardless of whether the input lists were pre-sorted or not.
sectors_armed_sorted = sorted(sectors)
if sectors_armed_sorted == sorted(self._sectors_home):
return STATE_ALARM_ARMED_HOME
return AlarmControlPanelState.ARMED_HOME

if sectors_armed_sorted == sorted(self._sectors_night):
return STATE_ALARM_ARMED_NIGHT
return AlarmControlPanelState.ARMED_NIGHT

if sectors_armed_sorted == sorted(self._sectors_vacation):
return STATE_ALARM_ARMED_VACATION
return AlarmControlPanelState.ARMED_VACATION

return STATE_ALARM_ARMED_AWAY
return AlarmControlPanelState.ARMED_AWAY

def get_status(self, query: int, id: int) -> Union[bool, int]:
"""Get the status of an item in the device inventory specified by query and id.
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ dev = [
"pytest-socket",
"requests-mock",
"syrupy",
"respx",
]

lint = [
Expand Down
2 changes: 2 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import pytest
import responses
from elmo.api.client import ElmoClient
from homeassistant.config_entries import ConfigEntryState

from custom_components.econnect_metronet import async_setup
from custom_components.econnect_metronet.alarm_control_panel import EconnectAlarm
Expand Down Expand Up @@ -164,6 +165,7 @@ def config_entry(hass):
"domain": "econnect_metronet",
"system_base_url": "https://example.com",
},
state=ConfigEntryState.SETUP_IN_PROGRESS,
)
config.add_to_hass(hass)
return config
45 changes: 18 additions & 27 deletions tests/test_devices.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,7 @@
import responses
from elmo import query as q
from elmo.api.exceptions import CodeError, CredentialError, LockError, ParseError
from homeassistant.const import (
STATE_ALARM_ARMED_AWAY,
STATE_ALARM_ARMED_HOME,
STATE_ALARM_ARMED_NIGHT,
STATE_ALARM_ARMED_VACATION,
STATE_ALARM_ARMING,
STATE_ALARM_DISARMED,
STATE_ALARM_DISARMING,
STATE_UNAVAILABLE,
)
from homeassistant.components.alarm_control_panel import AlarmControlPanelState
from requests.exceptions import HTTPError
from requests.models import Response

Expand Down Expand Up @@ -42,7 +33,7 @@ def test_device_constructor(client):
assert device._sectors_home == []
assert device._sectors_night == []
assert device._sectors_vacation == []
assert device.state == STATE_UNAVAILABLE
assert device.state is None


def test_device_constructor_with_config(client):
Expand All @@ -66,7 +57,7 @@ def test_device_constructor_with_config(client):
assert device._sectors_home == [3, 4]
assert device._sectors_night == [1, 2, 3]
assert device._sectors_vacation == [5, 3]
assert device.state == STATE_UNAVAILABLE
assert device.state is None


def test_device_constructor_with_config_empty(client):
Expand All @@ -90,7 +81,7 @@ def test_device_constructor_with_config_empty(client):
assert device._sectors_home == []
assert device._sectors_night == []
assert device._sectors_vacation == []
assert device.state == STATE_UNAVAILABLE
assert device.state is None


class TestItemInputs:
Expand Down Expand Up @@ -1468,7 +1459,7 @@ def test_get_state_no_sectors_armed(alarm_device):
alarm_device._sectors_night = []
alarm_device._inventory = {9: {}}
# Test
assert alarm_device.get_state() == STATE_ALARM_DISARMED
assert alarm_device.get_state() == AlarmControlPanelState.DISARMED


def test_get_state_armed_home(alarm_device):
Expand All @@ -1482,7 +1473,7 @@ def test_get_state_armed_home(alarm_device):
}
}
# Test
assert alarm_device.get_state() == STATE_ALARM_ARMED_HOME
assert alarm_device.get_state() == AlarmControlPanelState.ARMED_HOME


def test_get_state_armed_home_out_of_order(alarm_device):
Expand All @@ -1496,7 +1487,7 @@ def test_get_state_armed_home_out_of_order(alarm_device):
}
}
# Test
assert alarm_device.get_state() == STATE_ALARM_ARMED_HOME
assert alarm_device.get_state() == AlarmControlPanelState.ARMED_HOME


def test_get_state_armed_night(alarm_device):
Expand All @@ -1510,7 +1501,7 @@ def test_get_state_armed_night(alarm_device):
}
}
# Test (out of order keys to test sorting)
assert alarm_device.get_state() == STATE_ALARM_ARMED_NIGHT
assert alarm_device.get_state() == AlarmControlPanelState.ARMED_NIGHT


def test_get_state_armed_night_out_of_order(alarm_device):
Expand All @@ -1524,7 +1515,7 @@ def test_get_state_armed_night_out_of_order(alarm_device):
}
}
# Test
assert alarm_device.get_state() == STATE_ALARM_ARMED_NIGHT
assert alarm_device.get_state() == AlarmControlPanelState.ARMED_NIGHT


def test_get_state_armed_vacation(alarm_device):
Expand All @@ -1538,7 +1529,7 @@ def test_get_state_armed_vacation(alarm_device):
}
}
# Test (out of order keys to test sorting)
assert alarm_device.get_state() == STATE_ALARM_ARMED_VACATION
assert alarm_device.get_state() == AlarmControlPanelState.ARMED_VACATION


def test_get_state_armed_vacation_out_of_order(alarm_device):
Expand All @@ -1552,7 +1543,7 @@ def test_get_state_armed_vacation_out_of_order(alarm_device):
}
}
# Test
assert alarm_device.get_state() == STATE_ALARM_ARMED_VACATION
assert alarm_device.get_state() == AlarmControlPanelState.ARMED_VACATION


def test_get_state_armed_away(alarm_device):
Expand All @@ -1569,7 +1560,7 @@ def test_get_state_armed_away(alarm_device):
}
}
# Test
assert alarm_device.get_state() == STATE_ALARM_ARMED_AWAY
assert alarm_device.get_state() == AlarmControlPanelState.ARMED_AWAY


def test_get_state_armed_mixed(alarm_device):
Expand All @@ -1586,7 +1577,7 @@ def test_get_state_armed_mixed(alarm_device):
}
}
# Test
assert alarm_device.get_state() == STATE_ALARM_ARMED_AWAY
assert alarm_device.get_state() == AlarmControlPanelState.ARMED_AWAY


def test_get_state_armed_away_with_config(alarm_device):
Expand All @@ -1603,7 +1594,7 @@ def test_get_state_armed_away_with_config(alarm_device):
}
}
# Test
assert alarm_device.get_state() == STATE_ALARM_ARMED_AWAY
assert alarm_device.get_state() == AlarmControlPanelState.ARMED_AWAY


def test_get_state_while_disarming(alarm_device):
Expand All @@ -1612,9 +1603,9 @@ def test_get_state_while_disarming(alarm_device):
alarm_device._sectors_home = []
alarm_device._sectors_night = []
alarm_device._inventory = {9: {}}
alarm_device.state = STATE_ALARM_DISARMING
alarm_device.state = AlarmControlPanelState.DISARMING
# Test
assert alarm_device.get_state() == STATE_ALARM_DISARMING
assert alarm_device.get_state() == AlarmControlPanelState.DISARMING


def test_get_state_while_arming(alarm_device):
Expand All @@ -1623,9 +1614,9 @@ def test_get_state_while_arming(alarm_device):
alarm_device._sectors_home = []
alarm_device._sectors_night = []
alarm_device._inventory = {9: {}}
alarm_device.state = STATE_ALARM_ARMING
alarm_device.state = AlarmControlPanelState.ARMING
# Test
assert alarm_device.get_state() == STATE_ALARM_ARMING
assert alarm_device.get_state() == AlarmControlPanelState.ARMING


class TestTurnOff:
Expand Down

0 comments on commit 88491c7

Please sign in to comment.