8000 Calculate derivative unit instantly on creation if possible by karwosts · Pull Request #147527 · home-assistant/core · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Calculate derivative unit instantly on creation if possible #147527

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 30 additions & 7 deletions homeassistant/components/derivative/sensor.py
8000
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ def __init__(

self._attr_name = name if name is not None else f"{source_entity} derivative"
self._attr_extra_state_attributes = {ATTR_SOURCE_ID: source_entity}

self._unit_template: str | None = None
if unit_of_measurement is None:
final_unit_prefix = "" if unit_prefix is None else unit_prefix
self._unit_template = f"{final_unit_prefix}{{}}/{unit_time}"
Expand All @@ -223,6 +223,24 @@ def __init__(
lambda *args: None
)

def _derive_and_set_attributes_from_state(self, source_state: State | None) -> None:
if (
self._unit_template
and source_state
and (
source_state.state not in [STATE_UNAVAILABLE, STATE_UNKNOWN]
or source_state.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
)
):
original_unit = self._attr_native_unit_of_measurement
source_unit = source_state.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
self._attr_native_unit_of_measurement = self._unit_template.format(
"" if source_unit is None else source_unit
)
if original_unit != self._attr_native_unit_of_measurement:
self._state_list = []
self._attr_native_value = Decimal("0.00")

def _calc_derivative_from_state_list(self, current_time: datetime) -> Decimal:
def calculate_weight(start: datetime, end: datetime, now: datetime) -> float:
window_start = now - timedelta(seconds=self._time_window)
Expand Down Expand Up @@ -278,6 +296,9 @@ async def async_added_to_hass(self) -> None:
except (InvalidOperation, TypeError):
self._attr_native_value = None

source_state = self.hass.states.get(self._sensor_source_id)
self._derive_and_set_attributes_from_state(source_state)

def schedule_max_sub_interval_exceeded(source_state: State | None) -> None:
"""Schedule calculation using the source state and max_sub_interval.

Expand Down Expand Up @@ -333,10 +354,18 @@ def on_state_changed(event: Event[EventStateChangedData]) -> None:
"""Handle changed sensor state."""
self._cancel_max_sub_interval_exceeded_callback()
new_state = event.data["new_state"]

if not self._handle_invalid_source_state(new_state):
return

assert new_state

original_unit = self._attr_native_unit_of_measurement
self._derive_and_set_attributes_from_state(new_state)
if original_unit != self._attr_native_unit_of_measurement:
self.async_write_ha_state()
return

schedule_max_sub_interval_exceeded(new_state)
old_state = event.data["old_state"]
if old_state is not None:
Expand All @@ -358,12 +387,6 @@ def calc_derivative(
self.async_write_ha_state()
return

if self.native_unit_of_measurement is None:
unit = new_state.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
self._attr_native_unit_of_measurement = self._unit_template.format(
"" if unit is None else unit
)

self._prune_state_list(new_state.last_reported)

try:
Expand Down
6 changes: 6 10000 additions & 0 deletions tests/components/derivative/test_config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ async def test_options(hass: HomeAssistant, platform) -> None:
hass.states.async_set("sensor.input", 10, {"unit_of_measurement": "dog"})
hass.states.async_set("sensor.valid", 10, {"unit_of_measurement": "dog"})
hass.states.async_set("sensor.invalid", 10, {"unit_of_measurement": "cat"})
await hass.async_block_till_done()

result = await hass.config_entries.options.async_init(config_entry.entry_id)
assert result["type"] is FlowResultType.FORM
Expand All @@ -109,6 +110,11 @@ async def test_options(hass: HomeAssistant, platform) -> None:
"sensor.valid",
]

state = hass.states.get(f"{platform}.my_derivative")
assert state.attributes["unit_of_measurement"] == "kdog/min"
hass.states.async_set("sensor.valid", 10, {"unit_of_measurement": "cat"})
await hass.async_block_till_done()

result = await hass.config_entries.options.async_configure(
result["flow_id"],
user_input={
Expand Down
6 changes: 4 additions & 2 deletions tests/components/derivative/test_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,9 @@ async def test_setup_and_remove_config_entry(
input_sensor_entity_id = "sensor.input"
derivative_entity_id = "sensor.my_derivative"

hass.states.async_set(input_sensor_entity_id, "10.0", {})
hass.states.async_set(
input_sensor_entity_id, "10.0", {"unit_of_measurement": "dog"}
)
await hass.async_block_till_done()

# Setup the config entry
Expand All @@ -126,7 +128,7 @@ async def test_setup_and_remove_config_entry(
# Check the platform is setup correctly
state = hass.states.get(derivative_entity_id)
assert state.state == "0.0"
assert "unit_of_measurement" not in state.attributes
assert state.attributes["unit_of_measurement"] == "kdog/min"
assert state.attributes["source"] == "sensor.input"

hass.states.async_set(input_sensor_entity_id, 10, {"unit_of_measurement": "dog"})
Expand Down
9 changes: 5 additions & 4 deletions tests/components/derivative/test_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -795,12 +795,12 @@ async def test_unavailable_boot(
"sensor.power",
restore_state,
{
"unit_of_measurement": "W",
"unit_of_measurement": "kWh/s",
},
),
{
"native_value": restore_state,
"native_unit_of_measurement": "W",
"native_unit_of_measurement": "kWh/s",
},
),
],
Expand Down Expand Up @@ -830,7 +830,7 @@ async def test_unavailable_boot(
base = dt_util.utcnow()
with freeze_time(base) as freezer:
freezer.move_to(base + timedelta(seconds=1))
hass.states.async_set(entity_id, 10, {})
hass.states.async_set(entity_id, 10, {"unit_of_measurement": "kWh"})
await hass.async_block_till_done()

state = hass.states.get("sensor.power")
Expand All @@ -840,10 +840,11 @@ async def test_unavailable_boot(
assert state.state == restore_state

freezer.move_to(base + timedelta(seconds=2))
hass.states.async_set(entity_id, 15, {})
hass.states.async_set(entity_id, 15, {"unit_of_measurement": "kWh"})
await hass.async_block_till_done()

state = hass.states.get("sensor.power")
assert state is not None
# Now that the source sensor has two valid datapoints, we can calculate derivative
assert state.state == "5.00"
assert state.attributes.get("unit_of_measurement") == "kWh/s"
0