1818 ATTR_CODE_FORMAT ,
1919 STATE_UNAVAILABLE ,
2020)
21- from homeassistant .helpers import entity_platform
22- from homeassistant .exceptions import HomeAssistantError
21+ from homeassistant .helpers import entity_platform
22+ from homeassistant .helpers import entity_registry as er
23+ from homeassistant .exceptions import HomeAssistantError
2324from homeassistant .helpers .event import (
2425 async_call_later ,
2526 async_track_point_in_time ,
4546_LOGGER = logging .getLogger (__name__ )
4647
4748# Store per-config-entry unsubscribe callbacks for platform-level dispatcher listeners
48- PLATFORM_UNSUBS = "platform_unsubs"
49-
50-
51- async def async_setup (hass , config ):
49+ PLATFORM_UNSUBS = "platform_unsubs"
50+
51+
52+ def _build_unique_id (hass : HomeAssistant , area_id : str | None = None ) -> str :
53+ """Build a stable unique ID for Alarmo entities."""
54+ coordinator_id = hass .data [const .DOMAIN ]["coordinator" ].id
55+ suffix = area_id if area_id else "master"
56+ return f"{ coordinator_id } _{ suffix } "
57+
58+
59+ def _get_available_entity_id (
60+ hass : HomeAssistant , suggested_entity_id : str , exclude_entity_id : str | None = None
61+ ) -> str :
62+ """Return an entity_id that does not collide with existing entities."""
63+ used_ids = set (hass .states .async_entity_ids (PLATFORM ))
64+ entity_registry = er .async_get (hass )
65+ used_ids .update (
66+ entity_id
67+ for entity_id in entity_registry .entities
68+ if entity_id .startswith (f"{ PLATFORM } ." )
69+ )
70+
71+ areas = hass .data .get (const .DOMAIN , {}).get ("areas" , {})
72+ for alarm_entity in areas .values ():
73+ existing_id = getattr (alarm_entity , "entity_id" , None )
74+ if existing_id :
75+ used_ids .add (existing_id )
76+
77+ master = hass .data .get (const .DOMAIN , {}).get ("master" )
78+ if master and getattr (master , "entity_id" , None ):
79+ used_ids .add (master .entity_id )
80+
81+ if exclude_entity_id :
82+ used_ids .discard (exclude_entity_id )
83+
84+ if suggested_entity_id not in used_ids :
85+ return suggested_entity_id
86+
87+ index = 2
88+ while True :
89+ candidate = f"{ suggested_entity_id } _{ index } "
90+ if candidate not in used_ids :
91+ return candidate
92+ index += 1
93+
94+
95+ async def async_setup (hass , config ):
5296 """Track states and offer events for alarm_control_panel."""
5397 return True
5498
@@ -61,54 +105,72 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
61105async def async_setup_entry (hass , config_entry , async_add_devices ):
62106 """Set up the Alarmo entities."""
63107
64- @callback
65- def async_add_alarm_entity (config : dict ):
66- """Add each entity as Alarm Control Panel."""
67- entity_id = f"{ PLATFORM } .{ slugify (config ['name' ])} "
68-
69- # Guard against duplicate registration (reloads/upgrade timing)
70- if config ["area_id" ] in hass .data [const .DOMAIN ]["areas" ]:
71- existing = hass .data [const .DOMAIN ]["areas" ][config ["area_id" ]]
72- if existing and getattr (existing , "entity_id" , None ) == entity_id :
108+ @callback
109+ def async_add_alarm_entity (config : dict ):
110+ """Add each entity as Alarm Control Panel."""
111+ unique_id = _build_unique_id (hass , config ["area_id" ])
112+ entity_registry = er .async_get (hass )
113+ existing_entity_id = entity_registry .async_get_entity_id (
114+ PLATFORM , const .DOMAIN , unique_id
115+ )
116+ suggested_entity_id = f"{ PLATFORM } .{ slugify (config ['name' ])} "
117+ entity_id = existing_entity_id or _get_available_entity_id (
118+ hass , suggested_entity_id
119+ )
120+
121+ # Guard against duplicate registration (reloads/upgrade timing)
122+ if config ["area_id" ] in hass .data [const .DOMAIN ]["areas" ]:
123+ existing = hass .data [const .DOMAIN ]["areas" ][config ["area_id" ]]
124+ if existing and getattr (existing , "entity_id" , None ) == entity_id :
73125 _LOGGER .debug (
74126 "Area %s already registered as %s; skipping duplicate add" ,
75127 config ["area_id" ],
76128 entity_id ,
77129 )
78130 return
79131
80- alarm_entity = AlarmoAreaEntity (
81- hass = hass ,
82- entity_id = entity_id ,
83- name = config ["name" ],
84- area_id = config ["area_id" ],
85- )
132+ alarm_entity = AlarmoAreaEntity (
133+ hass = hass ,
134+ entity_id = entity_id ,
135+ unique_id = unique_id ,
136+ name = config ["name" ],
137+ area_id = config ["area_id" ],
138+ )
86139 hass .data [const .DOMAIN ]["areas" ][config ["area_id" ]] = alarm_entity
87140 async_add_devices ([alarm_entity ])
88141
89142 unsub_area = async_dispatcher_connect (
90143 hass , "alarmo_register_entity" , async_add_alarm_entity
91144 )
92145
93- @callback
94- def async_add_alarm_master (config : dict ):
95- """Add each entity as Alarm Control Panel."""
96- entity_id = f"{ PLATFORM } .{ slugify (config ['name' ])} "
97-
98- # Guard against duplicate master registration
99- if hass .data [const .DOMAIN ]["master" ] is not None :
100- existing = hass .data [const .DOMAIN ]["master" ]
101- if existing and getattr (existing , "entity_id" , None ) == entity_id :
146+ @callback
147+ def async_add_alarm_master (config : dict ):
148+ """Add each entity as Alarm Control Panel."""
149+ unique_id = _build_unique_id (hass )
150+ entity_registry = er .async_get (hass )
151+ existing_entity_id = entity_registry .async_get_entity_id (
152+ PLATFORM , const .DOMAIN , unique_id
153+ )
154+ suggested_entity_id = f"{ PLATFORM } .{ slugify (config ['name' ])} "
155+ entity_id = existing_entity_id or _get_available_entity_id (
156+ hass , suggested_entity_id
157+ )
158+
159+ # Guard against duplicate master registration
160+ if hass .data [const .DOMAIN ]["master" ] is not None :
161+ existing = hass .data [const .DOMAIN ]["master" ]
162+ if existing and getattr (existing , "entity_id" , None ) == entity_id :
102163 _LOGGER .debug (
103164 "Master already registered as %s; skipping duplicate add" , entity_id
104165 )
105166 return
106167
107- alarm_entity = AlarmoMasterEntity (
108- hass = hass ,
109- entity_id = entity_id ,
110- name = config ["name" ],
111- )
168+ alarm_entity = AlarmoMasterEntity (
169+ hass = hass ,
170+ entity_id = entity_id ,
171+ unique_id = unique_id ,
172+ name = config ["name" ],
173+ )
112174 hass .data [const .DOMAIN ]["master" ] = alarm_entity
113175 async_add_devices ([alarm_entity ])
114176
@@ -155,13 +217,16 @@ async def async_unload_entry(hass, config_entry):
155217 return True
156218
157219
158- class AlarmoBaseEntity (AlarmControlPanelEntity , RestoreEntity ):
220+ class AlarmoBaseEntity (AlarmControlPanelEntity , RestoreEntity ):
159221 """Defines a base alarm_control_panel entity."""
160222
161- def __init__ (self , hass : HomeAssistant , name : str , entity_id : str ) -> None :
162- """Initialize the alarm_control_panel entity."""
163- self .entity_id = entity_id
164- self ._name = name
223+ def __init__ (
224+ self , hass : HomeAssistant , name : str , entity_id : str , unique_id : str
225+ ) -> None :
226+ """Initialize the alarm_control_panel entity."""
227+ self .entity_id = entity_id
228+ self ._attr_unique_id = unique_id
229+ self ._name = name
165230 self ._state = None
166231 self .hass = hass
167232 self ._config = {}
@@ -191,9 +256,9 @@ def device_info(self) -> dict:
191256 }
192257
193258 @property
194- def unique_id (self ):
195- """Return a unique ID to use for this entity."""
196- return f" { self .entity_id } "
259+ def unique_id (self ):
260+ """Return a unique ID to use for this entity."""
261+ return self ._attr_unique_id
197262
198263 @property
199264 def name (self ):
@@ -698,14 +763,19 @@ async def async_will_remove_from_hass(self):
698763 )
699764
700765
701- class AlarmoAreaEntity (AlarmoBaseEntity ):
766+ class AlarmoAreaEntity (AlarmoBaseEntity ):
702767 """Defines a base alarm_control_panel entity."""
703768
704- def __init__ (
705- self , hass : HomeAssistant , name : str , entity_id : str , area_id : str
706- ) -> None :
707- """Initialize the alarm_control_panel entity."""
708- super ().__init__ (hass , name , entity_id )
769+ def __init__ (
770+ self ,
771+ hass : HomeAssistant ,
772+ name : str ,
773+ entity_id : str ,
774+ unique_id : str ,
775+ area_id : str ,
776+ ) -> None :
777+ """Initialize the alarm_control_panel entity."""
778+ super ().__init__ (hass , name , entity_id , unique_id )
709779
710780 self .area_id = area_id
711781 self ._timer = None
@@ -1169,12 +1239,14 @@ def update_ready_to_arm_modes(self, value):
11691239 )
11701240
11711241
1172- class AlarmoMasterEntity (AlarmoBaseEntity ):
1242+ class AlarmoMasterEntity (AlarmoBaseEntity ):
11731243 """Defines a base alarm_control_panel entity."""
11741244
1175- def __init__ (self , hass : HomeAssistant , name : str , entity_id : str ) -> None :
1176- """Initialize the alarm_control_panel entity."""
1177- super ().__init__ (hass , name , entity_id )
1245+ def __init__ (
1246+ self , hass : HomeAssistant , name : str , entity_id : str , unique_id : str
1247+ ) -> None :
1248+ """Initialize the alarm_control_panel entity."""
1249+ super ().__init__ (hass , name , entity_id , unique_id )
11781250 self .area_id = None
11791251 self ._target_state = None
11801252
0 commit comments