Skip to content
Merged
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
11 changes: 6 additions & 5 deletions homeassistant/components/energy/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -715,6 +715,9 @@ def _update_state(self) -> None:
self._attr_native_value = None
return

self._attr_native_unit_of_measurement = source_state.attributes.get(
ATTR_UNIT_OF_MEASUREMENT
)
self._attr_native_value = value * -1

elif self._is_combined:
Expand Down Expand Up @@ -763,13 +766,11 @@ async def async_added_to_hass(self) -> None:
# Check first sensor
if source_entry := entity_reg.async_get(self._source_sensors[0]):
device_id = source_entry.device_id
# For combined mode, always use Watts because we may have different source units; for inverted mode, copy source unit
# Combined mode always emits Watts because we convert
# heterogeneous source units internally. For inverted mode the
# unit is copied from the source state in _update_state.
if self._is_combined:
self._attr_native_unit_of_measurement = UnitOfPower.WATT
else:
self._attr_native_unit_of_measurement = (
source_entry.unit_of_measurement
)
# Get source name from registry
source_name = source_entry.name or source_entry.original_name
# Assign power sensor to same device as source sensor(s)
Expand Down
86 changes: 86 additions & 0 deletions tests/components/energy/test_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -1427,6 +1427,92 @@ async def test_power_sensor_manager_creation(
state = hass.states.get("sensor.battery_power_inverted")
assert state is not None
assert float(state.state) == -100.0
assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == UnitOfPower.WATT


async def test_power_sensor_inverted_propagates_unit(
recorder_mock: Recorder, hass: HomeAssistant
) -> None:
"""Test inverted power sensor copies unit from the source state."""
assert await async_setup_component(hass, "energy", {"energy": {}})
manager = await async_get_manager(hass)
manager.data = manager.default_preferences()

# Use a non-default unit to prove we copy from the source rather than
# hard-coding Watts.
hass.states.async_set(
"sensor.battery_power",
"1.5",
{ATTR_UNIT_OF_MEASUREMENT: UnitOfPower.KILO_WATT},
)
await hass.async_block_till_done()

await manager.async_update(
{
"energy_sources": [
{
"type": "battery",
"stat_energy_from": "sensor.battery_energy_from",
"stat_energy_to": "sensor.battery_energy_to",
"power_config": {
"stat_rate_inverted": "sensor.battery_power",
},
}
],
}
)
await hass.async_block_till_done()

state = hass.states.get("sensor.battery_power_inverted")
assert state is not None
assert float(state.state) == -1.5
assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == UnitOfPower.KILO_WATT

# Source switches to Watts — the inverted sensor should follow.
hass.states.async_set(
"sensor.battery_power",
"200.0",
{ATTR_UNIT_OF_MEASUREMENT: UnitOfPower.WATT},
)
await hass.async_block_till_done()

state = hass.states.get("sensor.battery_power_inverted")
assert state is not None
assert float(state.state) == -200.0
assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == UnitOfPower.WATT


async def test_power_sensor_inverted_source_without_unit(
recorder_mock: Recorder, hass: HomeAssistant
) -> None:
"""Test inverted sensor reports no unit when source has none."""
assert await async_setup_component(hass, "energy", {"energy": {}})
manager = await async_get_manager(hass)
manager.data = manager.default_preferences()

hass.states.async_set("sensor.battery_power", "100.0")
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Side note: It's weird that we allow sources without unit of measurement as that source doesn't mean anything when we're tracking a specific quantity like battery usage.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like we only validate that on stat_rate but not on inverted and combined sensors. They are still filtered on frontend though.

await hass.async_block_till_done()

await manager.async_update(
{
"energy_sources": [
{
"type": "battery",
"stat_energy_from": "sensor.battery_energy_from",
"stat_energy_to": "sensor.battery_energy_to",
"power_config": {
"stat_rate_inverted": "sensor.battery_power",
},
}
],
}
)
await hass.async_block_till_done()

state = hass.states.get("sensor.battery_power_inverted")
assert state is not None
assert float(state.state) == -100.0
assert ATTR_UNIT_OF_MEASUREMENT not in state.attributes


async def test_power_sensor_manager_cleanup(
Expand Down