Skip to content

Commit c9e9c36

Browse files
Merge pull request #68 from andrew-codechimp/config-attribute
Config attribute
2 parents 726f4af + 5a3e1ec commit c9e9c36

12 files changed

Lines changed: 773 additions & 591 deletions

File tree

.vscode/tasks.json

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,68 @@
66
"type": "shell",
77
"command": "scripts/develop",
88
"problemMatcher": []
9+
},
10+
{
11+
"label": "Lint: All",
12+
"type": "shell",
13+
"command": "scripts/lint",
14+
"group": {
15+
"kind": "test",
16+
"isDefault": false
17+
},
18+
"problemMatcher": []
19+
},
20+
{
21+
"label": "Lint: Ruff Check",
22+
"type": "shell",
23+
"command": "uv run ruff check .",
24+
"group": "test",
25+
"problemMatcher": []
26+
},
27+
{
28+
"label": "Lint: Ruff Check & Fix",
29+
"type": "shell",
30+
"command": "uv run ruff check . --fix",
31+
"group": "test",
32+
"problemMatcher": []
33+
},
34+
{
35+
"label": "Lint: Ruff Format",
36+
"type": "shell",
37+
"command": "uv run ruff format .",
38+
"group": "test",
39+
"problemMatcher": []
40+
},
41+
{
42+
"label": "Lint: MyPy",
43+
"type": "shell",
44+
"command": "uv run mypy",
45+
"group": "test",
46+
"problemMatcher": []
47+
},
48+
{
49+
"label": "Test: All",
50+
"type": "shell",
51+
"command": "uv run pytest",
52+
"group": {
53+
"kind": "test",
54+
"isDefault": true
55+
},
56+
"problemMatcher": []
57+
},
58+
{
59+
"label": "Test: Verbose",
60+
"type": "shell",
61+
"command": "uv run pytest -v",
62+
"group": "test",
63+
"problemMatcher": []
64+
},
65+
{
66+
"label": "Test: Coverage",
67+
"type": "shell",
68+
"command": "uv run pytest --cov=custom_components.label_state --cov-report=term-missing",
69+
"group": "test",
70+
"problemMatcher": []
971
}
1072
],
1173
"configurations": [

custom_components/label_state/__init__.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,17 @@
88

99
from awesomeversion.awesomeversion import AwesomeVersion
1010

11-
from homeassistant.core import HomeAssistant
11+
from homeassistant.config_entries import ConfigEntry
1212
from homeassistant.const import __version__ as HA_VERSION # noqa: N812
13+
from homeassistant.core import HomeAssistant
1314
from homeassistant.helpers import config_validation as cv
14-
from homeassistant.config_entries import ConfigEntry
1515
from homeassistant.helpers.typing import ConfigType
1616

1717
from .const import (
1818
DOMAIN,
1919
LOGGER,
20-
PLATFORMS,
2120
MIN_HA_VERSION,
21+
PLATFORMS,
2222
)
2323

2424
CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)

custom_components/label_state/binary_sensor.py

Lines changed: 66 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2,34 +2,40 @@
22

33
from __future__ import annotations
44

5-
from homeassistant.core import Event, HomeAssistant, EventStateChangedData, callback
5+
from homeassistant.components.binary_sensor import BinarySensorEntity
6+
from homeassistant.config_entries import ConfigEntry
67
from homeassistant.const import (
78
CONF_NAME,
8-
STATE_UNKNOWN,
99
CONF_UNIQUE_ID,
1010
STATE_UNAVAILABLE,
11+
STATE_UNKNOWN,
12+
)
13+
from homeassistant.core import Event, EventStateChangedData, HomeAssistant, callback
14+
from homeassistant.helpers import (
15+
device_registry as dr,
16+
entity_registry as er,
17+
label_registry as lr,
1118
)
12-
from homeassistant.helpers import device_registry as dr, entity_registry as er
13-
from homeassistant.helpers.event import async_track_state_change_event
14-
from homeassistant.config_entries import ConfigEntry
15-
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
1619
from homeassistant.helpers.entity_platform import (
17-
AddEntitiesCallback,
1820
AddConfigEntryEntitiesCallback,
21+
AddEntitiesCallback,
1922
)
2023
from homeassistant.helpers.entity_registry import EVENT_ENTITY_REGISTRY_UPDATED
21-
from homeassistant.components.binary_sensor import BinarySensorEntity
24+
from homeassistant.helpers.event import async_track_state_change_event
25+
from homeassistant.helpers.label_registry import EVENT_LABEL_REGISTRY_UPDATED
26+
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
2227

2328
from .const import (
24-
LOGGER,
25-
CONF_LABEL,
2629
ATTR_ENTITIES,
27-
CONF_STATE_TO,
28-
CONF_STATE_NOT,
29-
CONF_STATE_TYPE,
3030
ATTR_ENTITY_NAMES,
31+
ATTR_LABEL_NAME,
32+
CONF_LABEL,
3133
CONF_STATE_LOWER_LIMIT,
34+
CONF_STATE_NOT,
35+
CONF_STATE_TO,
36+
CONF_STATE_TYPE,
3237
CONF_STATE_UPPER_LIMIT,
38+
LOGGER,
3339
StateTypes,
3440
)
3541

@@ -116,6 +122,7 @@ class LabelStateBinarySensor(BinarySensorEntity):
116122
_attr_should_poll = False
117123

118124
_state_dict: dict[str, str] = {}
125+
_label_name: str = ""
119126

120127
def __init__(
121128
self,
@@ -131,7 +138,7 @@ def __init__(
131138
) -> None:
132139
"""Initialize the label state sensor."""
133140
self._attr_unique_id = unique_id
134-
self._label = label
141+
self._label_id = label
135142
self._state_type = state_type
136143
self._state_to = state_to
137144
self._state_not = state_not
@@ -142,20 +149,27 @@ def __init__(
142149
self._unit_of_measurement_mismatch = False
143150

144151
self._attr_is_on = False
145-
self._attr_extra_state_attributes = {}
146-
self._attr_extra_state_attributes.update(
147-
{
148-
ATTR_ENTITIES: [],
149-
}
152+
self._attr_extra_state_attributes = {
153+
ATTR_ENTITIES: [],
154+
ATTR_ENTITY_NAMES: [],
155+
ATTR_LABEL_NAME: self._label_name,
156+
}
157+
self._unrecorded_attributes = frozenset(
158+
{ATTR_ENTITIES, ATTR_ENTITY_NAMES, ATTR_LABEL_NAME}
150159
)
151160

152161
async def async_added_to_hass(self) -> None:
153162
"""Handle added to Hass."""
154163

155164
await super().async_added_to_hass()
156165

166+
label_reg = lr.async_get(self.hass)
167+
label_entry = label_reg.async_get_label(self._label_id)
168+
if label_entry is not None:
169+
self._label_name = label_entry.name
170+
157171
ent_reg = er.async_get(self.hass)
158-
entries = er.async_entries_for_label(ent_reg, self._label)
172+
entries = er.async_entries_for_label(ent_reg, self._label_id)
159173

160174
for entity_entry in entries:
161175
if entity_entry.entity_id == self.entity_id:
@@ -165,10 +179,10 @@ async def async_added_to_hass(self) -> None:
165179
)
166180
continue
167181
for label in entity_entry.labels:
168-
if label == self._label:
182+
if label == self._label_id:
169183
LOGGER.debug(
170184
"Found label %s in entity %s",
171-
self._label,
185+
self._label_id,
172186
entity_entry.entity_id,
173187
)
174188

@@ -183,7 +197,7 @@ async def async_added_to_hass(self) -> None:
183197
},
184198
)
185199

186-
if entity_entry and self._label in entity_entry.labels:
200+
if entity_entry and self._label_id in entity_entry.labels:
187201
self._async_state_listener(state_event, update_state=False)
188202

189203
self.async_on_remove(
@@ -194,6 +208,13 @@ async def async_added_to_hass(self) -> None:
194208
)
195209
)
196210

211+
self.async_on_remove(
212+
self.hass.bus.async_listen(
213+
EVENT_LABEL_REGISTRY_UPDATED,
214+
self._async_label_registry_modified,
215+
)
216+
)
217+
197218
self.async_on_remove(
198219
self.hass.bus.async_listen(
199220
EVENT_ENTITY_REGISTRY_UPDATED,
@@ -204,6 +225,22 @@ async def async_added_to_hass(self) -> None:
204225
self._calc_state()
205226
self.async_write_ha_state()
206227

228+
@callback
229+
def _async_label_registry_modified(
230+
self, event: Event[lr.EventLabelRegistryUpdatedData]
231+
) -> None:
232+
"""Handle label registry update."""
233+
data = event.data
234+
if data["action"] == "update" and data["label_id"] == self._label_id:
235+
# Get the label, update the name
236+
label_reg = lr.async_get(self.hass)
237+
label_entry = label_reg.async_get_label(self._label_id)
238+
if label_entry is not None:
239+
self._label_name = label_entry.name
240+
241+
self._calc_state()
242+
self.async_write_ha_state()
243+
207244
@callback
208245
def _async_entity_registry_modified(
209246
self, event: Event[er.EventEntityRegistryUpdatedData]
@@ -215,7 +252,7 @@ def _async_entity_registry_modified(
215252
entity_registry = er.async_get(self.hass)
216253
entity_entry = entity_registry.async_get(data["entity_id"])
217254

218-
if entity_entry and self._label in entity_entry.labels:
255+
if entity_entry and self._label_id in entity_entry.labels:
219256
if entity_entry.entity_id == self.entity_id:
220257
LOGGER.debug(
221258
"We don't watch ourself %s",
@@ -232,7 +269,7 @@ def _async_entity_registry_modified(
232269
)
233270
LOGGER.debug(
234271
"Found label %s in entity %s",
235-
self._label,
272+
self._label_id,
236273
entity_entry.entity_id,
237274
)
238275

@@ -273,7 +310,7 @@ def _calc_state(self) -> None:
273310
for entity_id in self._state_dict.keys():
274311
# Check if the state still has the label
275312
entity_entry = entity_registry.async_get(entity_id)
276-
if entity_entry and self._label in entity_entry.labels:
313+
if entity_entry and self._label_id in entity_entry.labels:
277314
entity_state = self._state_dict[entity_id]
278315

279316
if (
@@ -290,7 +327,7 @@ def _calc_state(self) -> None:
290327
for entity_id in self._state_dict.keys():
291328
# Check if the state still has the label
292329
entity_entry = entity_registry.async_get(entity_id)
293-
if entity_entry and self._label in entity_entry.labels:
330+
if entity_entry and self._label_id in entity_entry.labels:
294331
entity_state = self._state_dict[entity_id]
295332

296333
if (
@@ -307,7 +344,7 @@ def _calc_state(self) -> None:
307344
for entity_id in self._state_dict.keys():
308345
# Check if the state still has the label
309346
entity_entry = entity_registry.async_get(entity_id)
310-
if entity_entry and self._label in entity_entry.labels:
347+
if entity_entry and self._label_id in entity_entry.labels:
311348
entity_state = self._state_dict[entity_id]
312349

313350
try:
@@ -384,6 +421,7 @@ def _calc_state(self) -> None:
384421
self._attr_is_on = state_is_on
385422
self._attr_extra_state_attributes[ATTR_ENTITIES] = entities_on
386423
self._attr_extra_state_attributes[ATTR_ENTITY_NAMES] = entity_names
424+
self._attr_extra_state_attributes[ATTR_LABEL_NAME] = self._label_name
387425

388426
def _get_device_or_entity_name(
389427
self,

custom_components/label_state/config_flow.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,28 +2,28 @@
22

33
from __future__ import annotations
44

5+
from collections.abc import Callable, Coroutine, Mapping
56
from typing import Any, cast
6-
from collections.abc import Mapping, Callable, Coroutine
77

88
import voluptuous as vol
99

1010
from homeassistant.helpers import selector
1111
from homeassistant.helpers.schema_config_entry_flow import (
12+
SchemaCommonFlowHandler,
13+
SchemaConfigFlowHandler,
1214
SchemaFlowError,
1315
SchemaFlowFormStep,
1416
SchemaFlowMenuStep,
15-
SchemaCommonFlowHandler,
16-
SchemaConfigFlowHandler,
1717
)
1818

1919
from .const import (
20-
DOMAIN,
2120
CONF_LABEL,
22-
CONF_STATE_TO,
21+
CONF_STATE_LOWER_LIMIT,
2322
CONF_STATE_NOT,
23+
CONF_STATE_TO,
2424
CONF_STATE_TYPE,
25-
CONF_STATE_LOWER_LIMIT,
2625
CONF_STATE_UPPER_LIMIT,
26+
DOMAIN,
2727
StateTypes,
2828
)
2929

custom_components/label_state/const.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
LOGGER: Logger = getLogger(__package__)
1111

12-
MIN_HA_VERSION = "2025.4"
12+
MIN_HA_VERSION = "2025.11"
1313

1414
manifestfile = Path(__file__).parent / "manifest.json"
1515
with open(file=manifestfile, encoding="UTF-8") as json_file:
@@ -32,6 +32,7 @@
3232

3333
ATTR_ENTITIES = "entities"
3434
ATTR_ENTITY_NAMES = "entity_names"
35+
ATTR_LABEL_NAME = "label_name"
3536

3637

3738
class StateTypes(StrEnum):

hacs.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@
22
"name": "Label State",
33
"filename": "label_state.zip",
44
"hide_default_branch": true,
5-
"homeassistant": "2025.4.0",
5+
"homeassistant": "2025.11.0",
66
"zip_release": true
77
}

pyproject.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,12 @@ Changelog = "https://github.com/andrew-codechimp/HA-Label-State/releases"
2828
[dependency-groups]
2929
dev = [
3030
"colorlog",
31-
"homeassistant==2025.4.0",
31+
"homeassistant==2025.11.0",
3232
"mypy",
3333
"pygments",
3434
"pytest-homeassistant-custom-component",
3535
"ruff",
36+
"pycares<5.0.0",
3637
]
3738

3839
[tool.uv]
@@ -109,7 +110,7 @@ ignore = [
109110
max-complexity = 25
110111

111112
[tool.ruff.lint.isort]
112-
length-sort = true
113+
length-sort = false
113114
section-order = [
114115
"future",
115116
"standard-library",

0 commit comments

Comments
 (0)