diff --git a/custom_components/localthings/registry/capabilities/common.py b/custom_components/localthings/registry/capabilities/common.py index 8a2fd66..eadd646 100644 --- a/custom_components/localthings/registry/capabilities/common.py +++ b/custom_components/localthings/registry/capabilities/common.py @@ -160,16 +160,21 @@ _DEAD_INSTANTANEOUS_POWER = '-500' ENERGY_METER = Capability( href='/energy/consumption/vs/0', entities=( + # `not rep` keeps the empty-{} stub carve-out (see entity._is_included): + # an explicit exists_fn otherwise bypasses it, which would drop the + # entity when /device/0 returns a not-yet-fetched stub. On a populated + # rep, hide power only for the dead sentinel or an absent field. SensorDesc(key='power_watts', field='x.com.samsung.da.instantaneousPower', name='Power', device_class='power', state_class='measurement', unit='W', value_fn=clamp_power, - exists_fn=lambda rep, resources: ( + exists_fn=lambda rep, resources: not rep or ( rep.get('x.com.samsung.da.instantaneousPower') - != _DEAD_INSTANTANEOUS_POWER)), + not in (None, _DEAD_INSTANTANEOUS_POWER))), SensorDesc(key='energy_kwh', field='x.com.samsung.da.cumulativePower', name='Energy', device_class='energy', state_class='total_increasing', unit='kWh', value_fn=wh_to_kwh, - exists_fn=lambda rep, resources: 'x.com.samsung.da.cumulativePower' in rep), + exists_fn=lambda rep, resources: ( + not rep or 'x.com.samsung.da.cumulativePower' in rep)), ), ) diff --git a/tests/test_common_capabilities.py b/tests/test_common_capabilities.py index 070f0cd..f535d54 100644 --- a/tests/test_common_capabilities.py +++ b/tests/test_common_capabilities.py @@ -116,3 +116,17 @@ class TestEnergyMeter: def test_energy_kwh_shown_when_present(self): kwh = next(e for e in common.ENERGY_METER.entities if e.key == 'energy_kwh') assert kwh.exists_fn({'x.com.samsung.da.cumulativePower': '58900'}, {}) is True + + def test_both_entities_included_on_empty_stub(self): + """An empty {} rep means the resource exists but data isn't fetched yet + (see entity._is_included) -- include both so sub-polls populate them.""" + pw = next(e for e in common.ENERGY_METER.entities if e.key == 'power_watts') + kwh = next(e for e in common.ENERGY_METER.entities if e.key == 'energy_kwh') + assert pw.exists_fn({}, {}) is True + assert kwh.exists_fn({}, {}) is True + + def test_power_watts_hidden_when_field_absent_in_populated_rep(self): + """A populated rep that lacks instantaneousPower must not spawn a + phantom power sensor (the exists_fn replaces the field-presence gate).""" + pw = next(e for e in common.ENERGY_METER.entities if e.key == 'power_watts') + assert pw.exists_fn({'x.com.samsung.da.cumulativePower': '5'}, {}) is False