Skip to content

Commit 70abcc7

Browse files
Add equal updates option
1 parent c1314ef commit 70abcc7

16 files changed

Lines changed: 256 additions & 92 deletions

File tree

.devcontainer-lock.json

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"features": {
3+
"ghcr.io/devcontainers-extra/features/apt-packages:1": {
4+
"version": "1.0.6",
5+
"resolved": "ghcr.io/devcontainers-extra/features/apt-packages@sha256:55c54412112da81b9381e470cdbbe55278564950d1ff536ce925b1e8e096babd",
6+
"integrity": "sha256:55c54412112da81b9381e470cdbbe55278564950d1ff536ce925b1e8e096babd"
7+
},
8+
"ghcr.io/devcontainers/features/git:1": {
9+
"version": "1.3.5",
10+
"resolved": "ghcr.io/devcontainers/features/git@sha256:27905dc196c01f77d6ba8709cb82eeaf330b3b108772e2f02d1cd0d826de1251",
11+
"integrity": "sha256:27905dc196c01f77d6ba8709cb82eeaf330b3b108772e2f02d1cd0d826de1251"
12+
},
13+
"ghcr.io/devcontainers/features/node:2": {
14+
"version": "2.0.0",
15+
"resolved": "ghcr.io/devcontainers/features/node@sha256:fedd4c11f7adfb64283b578dddc7da906728daa25fa293351c9d913231acf12f",
16+
"integrity": "sha256:fedd4c11f7adfb64283b578dddc7da906728daa25fa293351c9d913231acf12f"
17+
}
18+
}
19+
}

.vscode/settings.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
{
22
"git.autofetch": true,
33
"python.createEnvironment.trigger": "off",
4+
"python.defaultInterpreterPath": "${workspaceFolder}/.venv/bin/python",
5+
"python.analysis.extraPaths": [
6+
"${workspaceFolder}",
7+
"${workspaceFolder}/custom_components"
8+
],
9+
"python.analysis.diagnosticSeverityOverrides": {
10+
"reportUnsortedImports": "none"
11+
},
412
"editor.defaultFormatter": "charliermarsh.ruff",
513
"editor.formatOnSave": true,
614
"editor.codeActionsOnSave": {

custom_components/periodic_min_max/__init__.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,24 +9,24 @@
99
import voluptuous as vol
1010
from awesomeversion.awesomeversion import AwesomeVersion
1111

12-
from homeassistant.core import HomeAssistant, callback
12+
from homeassistant.config_entries import ConfigEntry
1313
from homeassistant.const import (
1414
CONF_ENTITY_ID,
1515
__version__ as HA_VERSION, # noqa: N812
1616
)
17-
from homeassistant.helpers import entity_registry as er, config_validation as cv
18-
from homeassistant.config_entries import ConfigEntry
19-
from homeassistant.helpers.typing import ConfigType
17+
from homeassistant.core import HomeAssistant, callback
18+
from homeassistant.helpers import config_validation as cv, entity_registry as er
2019
from homeassistant.helpers.helper_integration import (
2120
async_handle_source_entity_changes,
2221
async_remove_helper_config_entry_from_source_device,
2322
)
23+
from homeassistant.helpers.typing import ConfigType
2424

2525
from .const import (
2626
DOMAIN,
2727
LOGGER,
28-
PLATFORMS,
2928
MIN_HA_VERSION,
29+
PLATFORMS,
3030
)
3131
from .services import async_setup_services
3232

custom_components/periodic_min_max/config_flow.py

Lines changed: 26 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -2,51 +2,48 @@
22

33
from __future__ import annotations
44

5-
from typing import Any, cast
65
from collections.abc import Mapping
6+
from typing import Any, cast
77

88
import voluptuous as vol
99

10-
from homeassistant.const import CONF_TYPE, CONF_ENTITY_ID
11-
from homeassistant.helpers import selector
10+
from homeassistant.components.input_number import DOMAIN as INPUT_NUMBER_DOMAIN
1211
from homeassistant.components.number import DOMAIN as NUMBER_DOMAIN
1312
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
14-
from homeassistant.components.input_number import DOMAIN as INPUT_NUMBER_DOMAIN
13+
from homeassistant.const import CONF_ENTITY_ID, CONF_TYPE
14+
from homeassistant.helpers import selector
1515
from homeassistant.helpers.schema_config_entry_flow import (
16-
SchemaFlowFormStep,
1716
SchemaConfigFlowHandler,
17+
SchemaFlowFormStep,
1818
)
1919

20-
from .const import DOMAIN
20+
from .const import CONF_EQUAL_UPDATES, DOMAIN
2121

2222
_STATISTIC_MEASURES = ["min", "max"]
2323

2424

25-
OPTIONS_SCHEMA = vol.Schema(
26-
{
27-
vol.Required(CONF_ENTITY_ID): selector.EntitySelector(
28-
selector.EntitySelectorConfig(
29-
domain=[SENSOR_DOMAIN, NUMBER_DOMAIN, INPUT_NUMBER_DOMAIN],
30-
multiple=False,
31-
),
25+
OPTIONS_SCHEMA = vol.Schema({
26+
vol.Required(CONF_ENTITY_ID): selector.EntitySelector(
27+
selector.EntitySelectorConfig(
28+
domain=[SENSOR_DOMAIN, NUMBER_DOMAIN, INPUT_NUMBER_DOMAIN],
29+
multiple=False,
3230
),
33-
vol.Required(CONF_TYPE): selector.SelectSelector(
34-
selector.SelectSelectorConfig(
35-
options=_STATISTIC_MEASURES, translation_key=CONF_TYPE
36-
),
31+
),
32+
vol.Required(CONF_TYPE): selector.SelectSelector(
33+
selector.SelectSelectorConfig(
34+
options=_STATISTIC_MEASURES, translation_key=CONF_TYPE
3735
),
38-
}
39-
)
40-
41-
CONFIG_SCHEMA = vol.Schema(
42-
{
43-
vol.Required("name"): selector.TextSelector(
44-
selector.TextSelectorConfig(
45-
type=selector.TextSelectorType.TEXT, autocomplete="off"
46-
),
47-
)
48-
}
49-
).extend(OPTIONS_SCHEMA.schema)
36+
),
37+
vol.Required(CONF_EQUAL_UPDATES, default=False): selector.BooleanSelector(),
38+
})
39+
40+
CONFIG_SCHEMA = vol.Schema({
41+
vol.Required("name"): selector.TextSelector(
42+
selector.TextSelectorConfig(
43+
type=selector.TextSelectorType.TEXT, autocomplete="off"
44+
),
45+
)
46+
}).extend(OPTIONS_SCHEMA.schema)
5047

5148
CONFIG_FLOW = {
5249
"user": SchemaFlowFormStep(CONFIG_SCHEMA),

custom_components/periodic_min_max/const.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,6 @@
1313

1414
PLATFORMS = [Platform.SENSOR]
1515

16+
CONF_EQUAL_UPDATES = "equal_updates"
17+
1618
ATTR_LAST_MODIFIED = "last_modified"

custom_components/periodic_min_max/sensor.py

Lines changed: 41 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -2,39 +2,36 @@
22

33
from __future__ import annotations
44

5-
from typing import Any
65
from datetime import datetime
6+
from typing import Any
77

88
import voluptuous as vol
99

10-
from homeassistant.core import Event, HomeAssistant, EventStateChangedData, callback
11-
from homeassistant.util import dt as dt_util
10+
from homeassistant.components.sensor import (
11+
SensorDeviceClass,
12+
SensorEntity,
13+
SensorStateClass,
14+
)
15+
from homeassistant.config_entries import ConfigEntry
1216
from homeassistant.const import (
13-
CONF_TYPE,
14-
STATE_UNKNOWN,
17+
ATTR_UNIT_OF_MEASUREMENT,
1518
CONF_ENTITY_ID,
19+
CONF_TYPE,
1620
STATE_UNAVAILABLE,
17-
ATTR_UNIT_OF_MEASUREMENT,
21+
STATE_UNKNOWN,
1822
)
23+
from homeassistant.core import Event, EventStateChangedData, HomeAssistant, callback
1924
from homeassistant.helpers import device_registry as dr, entity_registry as er
25+
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
2026
from homeassistant.helpers.event import (
21-
async_track_state_change_event,
2227
async_track_entity_registry_updated_event,
23-
)
24-
from homeassistant.config_entries import ConfigEntry
25-
from homeassistant.helpers.typing import StateType
26-
from homeassistant.components.sensor import (
27-
SensorEntity,
28-
SensorStateClass,
29-
SensorDeviceClass,
28+
async_track_state_change_event,
3029
)
3130
from homeassistant.helpers.restore_state import RestoreEntity
32-
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
31+
from homeassistant.helpers.typing import StateType
32+
from homeassistant.util import dt as dt_util
3333

34-
from .const import (
35-
LOGGER,
36-
ATTR_LAST_MODIFIED,
37-
)
34+
from .const import ATTR_LAST_MODIFIED, CONF_EQUAL_UPDATES, LOGGER
3835

3936
ATTR_MIN_VALUE = "min_value"
4037
ATTR_MAX_VALUE = "max_value"
@@ -91,6 +88,7 @@ async def async_setup_entry(
9188
device_id = source_entity.device_id if source_entity else None
9289

9390
sensor_type = config_entry.options[CONF_TYPE]
91+
equal_updates = config_entry.options.get(CONF_EQUAL_UPDATES, False)
9492

9593
async def async_registry_updated(
9694
event: Event[er.EventEntityRegistryUpdatedData],
@@ -131,17 +129,16 @@ async def async_registry_updated(
131129
config_entry.add_update_listener(config_entry_update_listener)
132130
)
133131

134-
async_add_entities(
135-
[
136-
PeriodicMinMaxSensor(
137-
hass,
138-
source_entity_id,
139-
config_entry.title,
140-
sensor_type,
141-
config_entry.entry_id,
142-
)
143-
]
144-
)
132+
async_add_entities([
133+
PeriodicMinMaxSensor(
134+
hass,
135+
source_entity_id,
136+
config_entry.title,
137+
sensor_type,
138+
equal_updates,
139+
config_entry.entry_id,
140+
)
141+
])
145142

146143
return True
147144

@@ -155,18 +152,20 @@ class PeriodicMinMaxSensor(SensorEntity, RestoreEntity):
155152
_state_had_real_change = False
156153
_attr_last_modified: str = dt_util.utcnow().isoformat()
157154

158-
def __init__(
155+
def __init__( # noqa: PLR0913
159156
self,
160157
hass: HomeAssistant,
161158
source_entity_id: str,
162159
name: str | None,
163160
sensor_type: str,
161+
equal_updates: bool,
164162
unique_id: str | None,
165163
) -> None:
166164
"""Initialize the min/max sensor."""
167165
self._attr_unique_id = unique_id
168166
self._source_entity_id = source_entity_id
169167
self._sensor_type = sensor_type
168+
self._equal_updates = equal_updates
170169

171170
if name:
172171
self._attr_name = name
@@ -325,18 +324,23 @@ def _calc_values(self) -> None:
325324
"""Calculate the values."""
326325
self._state_had_real_change = False
327326

328-
"""Calculate min value, honoring unknown states."""
327+
if self._state in [STATE_UNKNOWN, STATE_UNAVAILABLE]:
328+
return
329+
329330
if self._sensor_attr == ATTR_MIN_VALUE:
330-
if self._state not in [STATE_UNKNOWN, STATE_UNAVAILABLE] and (
331-
self.min_value is None or self.min_value > self._state
331+
if self.min_value is None or (
332+
self.min_value >= self._state
333+
if self._equal_updates
334+
else self.min_value > self._state
332335
):
333336
self.min_value = self._state
334337
self._state_had_real_change = True
335338

336-
"""Calculate max value, honoring unknown states."""
337339
if self._sensor_attr == ATTR_MAX_VALUE:
338-
if self._state not in [STATE_UNKNOWN, STATE_UNAVAILABLE] and (
339-
self.max_value is None or self.max_value < self._state
340+
if self.max_value is None or (
341+
self.max_value <= self._state
342+
if self._equal_updates
343+
else self.max_value < self._state
340344
):
341345
self.max_value = self._state
342346
self._state_had_real_change = True

custom_components/periodic_min_max/services.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22

33
from __future__ import annotations
44

5+
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
56
from homeassistant.core import HomeAssistant, callback
67
from homeassistant.helpers import service
7-
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
88

99
from .const import DOMAIN
1010

custom_components/periodic_min_max/translations/de.json

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,14 @@
66
"description": "Create a sensor that is the minimum or maximum of another sensor, resetttable via an action.",
77
"data": {
88
"entity_id": "Input entity",
9+
"equal_updates": "Update on equal values",
910
"name": "Name",
10-
"type": "Statistikmerkmal"
11+
"type": "Statistic characteristic"
12+
},
13+
"data_description": {
14+
"entity_id": "The entity that the sensor will track. The sensor will calculate the minimum or maximum value of this entity until reset via the `reset` action.",
15+
"equal_updates": "If enabled, the sensor will update the `last_modified` attribute even if the input entity value is the same as the current min/max.\nNote that this only updates when the value has changed and returned to the same min/max.",
16+
"type": "Whether the sensor should track the minimum or maximum value of the input entity"
1117
}
1218
}
1319
}
@@ -17,7 +23,13 @@
1723
"init": {
1824
"data": {
1925
"entity_id": "Input entity",
20-
"type": "Statistikmerkmal"
26+
"equal_updates": "Update on equal values",
27+
"type": "Statistic characteristic"
28+
},
29+
"data_description": {
30+
"entity_id": "The entity that the sensor will track. The sensor will calculate the minimum or maximum value of this entity until reset via the `reset` action.",
31+
"equal_updates": "If enabled, the sensor will update the `last_modified` attribute even if the input entity value is the same as the current min/max.\nNote that this only updates when the value has changed and returned to the same min/max.",
32+
"type": "Whether the sensor should track the minimum or maximum value of the input entity"
2133
}
2234
}
2335
}
@@ -32,11 +44,11 @@
3244
},
3345
"services": {
3446
"reset": {
35-
"name": "Zurücksetzen",
47+
"name": "Reset",
3648
"description": "Reset a Periodic Min/Max sensor to the current input entity value.",
3749
"fields": {
3850
"entity_id": {
39-
"name": "Entität",
51+
"name": "Entity",
4052
"description": "Select the Periodic Min/Max sensor."
4153
}
4254
}

custom_components/periodic_min_max/translations/en.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,14 @@
66
"description": "Create a sensor that is the minimum or maximum of another sensor, resetttable via an action.",
77
"data": {
88
"entity_id": "Input entity",
9+
"equal_updates": "Update on equal values",
910
"name": "Name",
1011
"type": "Statistic characteristic"
12+
},
13+
"data_description": {
14+
"entity_id": "The entity that the sensor will track. The sensor will calculate the minimum or maximum value of this entity until reset via the `reset` action.",
15+
"equal_updates": "If enabled, the sensor will update the `last_modified` attribute even if the input entity value is the same as the current min/max.\nNote that this only updates when the value has changed and returned to the same min/max.",
16+
"type": "Whether the sensor should track the minimum or maximum value of the input entity"
1117
}
1218
}
1319
}
@@ -17,7 +23,13 @@
1723
"init": {
1824
"data": {
1925
"entity_id": "Input entity",
26+
"equal_updates": "Update on equal values",
2027
"type": "Statistic characteristic"
28+
},
29+
"data_description": {
30+
"entity_id": "The entity that the sensor will track. The sensor will calculate the minimum or maximum value of this entity until reset via the `reset` action.",
31+
"equal_updates": "If enabled, the sensor will update the `last_modified` attribute even if the input entity value is the same as the current min/max.\nNote that this only updates when the value has changed and returned to the same min/max.",
32+
"type": "Whether the sensor should track the minimum or maximum value of the input entity"
2133
}
2234
}
2335
}

0 commit comments

Comments
 (0)