Add common.UNIVERSAL/POWER bundles to stop hand-duplicating capabilities
FIRMWARE_UPDATE and ALARMS were already copy-pasted into all 6 device-type registries by hand; POWER/KIDS_LOCK/REMOTE_CONTROL into 5 of 6. Consolidate into two bundles in common.py, unpacked via *common.UNIVERSAL / *common.POWER the same way ignored.IGNORED already is: - UNIVERSAL: ALARMS, ENERGY_METER, FIRMWARE_UPDATE (moved from fridge.py), SELF_CHECK (moved from fridge.py), AI_ENERGY_LEVEL, and the kids-lock/ remote-control pairs. Safe everywhere -- discover() only binds a href actually present in a device's dump, so a capability with no known conflicting family is a no-op where the href is absent and a real, wanted entity where it's present. This also broadens AI_ENERGY_LEVEL, ENERGY_METER, and SELF_CHECK to device types they weren't confirmed on before, on the same reasoning. - POWER: just POWER_GENERIC/POWER_VS_FALLBACK, applied to the 5 non-AC registries. Airconditioner keeps its own opt-out: its climate entity already owns /power/0 and /power/vs/0 via a bare, no-entity claim (airconditioner.COVERAGE), and a real power capability on the same href would make _build() raise (a href with multiple caps requires every cap to have rt_filter or match_fn; the bare COVERAGE cap has neither). Full test suite (327 tests, all 6 device-type golden fixtures) passes unchanged -- none of the newly-broadened capabilities bind on any existing fixture, confirming the no-op reasoning held in practice, not just theory.
This commit is contained in:
@@ -3,23 +3,22 @@
|
||||
The first device whose core controls surface as a single composite HA `climate`
|
||||
entity (see capabilities/airconditioner.py and climate.py). Power/mode/temp/wind
|
||||
are consumed by that entity rather than exposed as separate switches/selects, so
|
||||
this registry deliberately does NOT include the common POWER caps -- on/off is
|
||||
the climate entity's HVACMode.OFF / TURN_ON/OFF.
|
||||
this registry includes *common.UNIVERSAL but deliberately NOT common.POWER --
|
||||
on/off is the climate entity's HVACMode.OFF / TURN_ON/OFF, and /power/0 +
|
||||
/power/vs/0 are already pre-claimed (no-entity) by airconditioner.COVERAGE via
|
||||
CLIMATE_CONSUMED_HREFS. A real POWER_GENERIC/POWER_VS_FALLBACK cap on either
|
||||
href alongside that bare claim would make _build() raise (see its docstring).
|
||||
|
||||
Reuses common.ALARMS + common.ENERGY_METER, fridge.FIRMWARE_UPDATE (as every
|
||||
registry does), and dishwasher.DIAGNOSIS for /diagnosis/vs/0.
|
||||
Reuses dishwasher.DIAGNOSIS for /diagnosis/vs/0.
|
||||
"""
|
||||
from ..capabilities import airconditioner, common, dishwasher, fridge, ignored
|
||||
from ..capabilities import airconditioner, common, dishwasher, ignored
|
||||
from ._base import DeviceRegistry, _build
|
||||
|
||||
REGISTRY = DeviceRegistry(
|
||||
name='airconditioner',
|
||||
capabilities=_build([
|
||||
*ignored.IGNORED,
|
||||
common.ALARMS,
|
||||
common.ENERGY_METER,
|
||||
fridge.FIRMWARE_UPDATE,
|
||||
fridge.SELF_CHECK,
|
||||
*common.UNIVERSAL,
|
||||
dishwasher.DIAGNOSIS,
|
||||
airconditioner.CLIMATE,
|
||||
airconditioner.AIR_PURIFY,
|
||||
|
||||
@@ -1,19 +1,13 @@
|
||||
"""Dishwasher device registry."""
|
||||
from ..capabilities import common, dishwasher, fridge, ignored, laundry, operational
|
||||
from ..capabilities import common, dishwasher, ignored, laundry, operational
|
||||
from ._base import DeviceRegistry, _build
|
||||
|
||||
REGISTRY = DeviceRegistry(
|
||||
name='dishwasher',
|
||||
capabilities=_build([
|
||||
*ignored.IGNORED,
|
||||
common.POWER_GENERIC,
|
||||
common.POWER_VS_FALLBACK,
|
||||
common.KIDS_LOCK_GENERIC,
|
||||
common.KIDS_LOCK_VS_FALLBACK,
|
||||
common.REMOTE_CONTROL_GENERIC,
|
||||
common.REMOTE_CONTROL_VS_FALLBACK,
|
||||
common.ALARMS,
|
||||
common.ENERGY_METER,
|
||||
*common.UNIVERSAL,
|
||||
*common.POWER,
|
||||
common.WATER_METER,
|
||||
common.WATER_FILTER,
|
||||
operational.OPERATIONAL_STATE,
|
||||
@@ -25,6 +19,5 @@ REGISTRY = DeviceRegistry(
|
||||
laundry.DOOR_LED,
|
||||
laundry.SOUND_MODE,
|
||||
laundry.SOUND_VOLUME,
|
||||
fridge.FIRMWARE_UPDATE,
|
||||
]),
|
||||
)
|
||||
|
||||
@@ -9,21 +9,15 @@ objects the washer registry uses -- washer and dryer expose the same
|
||||
DA_WM_-family surface, so they stay consistent instead of each carrying a
|
||||
bespoke variant.
|
||||
"""
|
||||
from ..capabilities import common, dryer, fridge, ignored, laundry, operational
|
||||
from ..capabilities import common, dryer, ignored, laundry, operational
|
||||
from ._base import DeviceRegistry, _build
|
||||
|
||||
REGISTRY = DeviceRegistry(
|
||||
name='dryer',
|
||||
capabilities=_build([
|
||||
*ignored.IGNORED,
|
||||
common.POWER_GENERIC,
|
||||
common.POWER_VS_FALLBACK,
|
||||
common.KIDS_LOCK_GENERIC,
|
||||
common.KIDS_LOCK_VS_FALLBACK,
|
||||
common.REMOTE_CONTROL_GENERIC,
|
||||
common.REMOTE_CONTROL_VS_FALLBACK,
|
||||
common.ALARMS,
|
||||
common.ENERGY_METER,
|
||||
*common.UNIVERSAL,
|
||||
*common.POWER,
|
||||
operational.OPERATIONAL_STATE,
|
||||
laundry.DOOR_LED,
|
||||
laundry.SOUND_MODE,
|
||||
@@ -32,6 +26,5 @@ REGISTRY = DeviceRegistry(
|
||||
dryer.DRYER_SETTINGS,
|
||||
dryer.DRYER_COURSE,
|
||||
dryer.DRYER_DIAGNOSIS,
|
||||
fridge.FIRMWARE_UPDATE,
|
||||
]),
|
||||
)
|
||||
|
||||
@@ -1,24 +1,18 @@
|
||||
"""Oven device registry."""
|
||||
from ..capabilities import common, fridge, ignored, oven
|
||||
from ..capabilities import common, ignored, oven
|
||||
from ._base import DeviceRegistry, _build
|
||||
|
||||
REGISTRY = DeviceRegistry(
|
||||
name='oven',
|
||||
capabilities=_build([
|
||||
*ignored.IGNORED,
|
||||
common.POWER_GENERIC,
|
||||
common.POWER_VS_FALLBACK,
|
||||
common.KIDS_LOCK_GENERIC,
|
||||
common.KIDS_LOCK_VS_FALLBACK,
|
||||
common.REMOTE_CONTROL_GENERIC,
|
||||
common.REMOTE_CONTROL_VS_FALLBACK,
|
||||
common.ALARMS,
|
||||
*common.UNIVERSAL,
|
||||
*common.POWER,
|
||||
oven.OVEN_CAVITY,
|
||||
oven.OVEN_SETPOINT,
|
||||
oven.OVEN_MODE,
|
||||
oven.OVEN_OPERATIONAL_STATE,
|
||||
oven.OVEN_DOOR,
|
||||
oven.OVEN_CONNECTED,
|
||||
fridge.FIRMWARE_UPDATE,
|
||||
]),
|
||||
)
|
||||
|
||||
@@ -6,21 +6,14 @@ REGISTRY = DeviceRegistry(
|
||||
name='refrigerator',
|
||||
capabilities=_build([
|
||||
*ignored.IGNORED,
|
||||
common.POWER_GENERIC,
|
||||
common.POWER_VS_FALLBACK,
|
||||
*common.UNIVERSAL,
|
||||
*common.POWER,
|
||||
fridge.STATUS_LOCK,
|
||||
fridge.DOOR_ALERT,
|
||||
common.KIDS_LOCK_GENERIC,
|
||||
common.KIDS_LOCK_VS_FALLBACK,
|
||||
common.REMOTE_CONTROL_GENERIC,
|
||||
common.REMOTE_CONTROL_VS_FALLBACK,
|
||||
common.ALARMS,
|
||||
common.ENERGY_METER,
|
||||
common.WATER_FILTER,
|
||||
dishwasher.DIAGNOSIS,
|
||||
fridge.ICEMAKER_NIGHTTIME,
|
||||
fridge.FLEX_ZONE,
|
||||
common.AI_ENERGY_LEVEL,
|
||||
fridge.REFRIGERATION,
|
||||
fridge.AUTOFILL,
|
||||
fridge.WELCOME_LIGHTING,
|
||||
@@ -29,11 +22,9 @@ REGISTRY = DeviceRegistry(
|
||||
fridge.SABBATH,
|
||||
fridge.BEVERAGE_ZONE,
|
||||
fridge.PANTRY_ZONE,
|
||||
fridge.FIRMWARE_UPDATE,
|
||||
fridge.DEFROST_DELAY,
|
||||
fridge.DEFROST_DELAY_NATIVE_DUPLICATE,
|
||||
fridge.DEFROST_BLOCK_STATUS,
|
||||
fridge.SELF_CHECK,
|
||||
fridge.DOORS_FALLBACK,
|
||||
fridge.TEMPERATURES_FALLBACK,
|
||||
fridge.ICEMAKER_STATUS_FALLBACK,
|
||||
|
||||
@@ -1,27 +1,19 @@
|
||||
"""Washer device registry."""
|
||||
from ..capabilities import common, dishwasher, fridge, ignored, laundry, operational, washer
|
||||
from ..capabilities import common, dishwasher, ignored, laundry, operational, washer
|
||||
from ._base import DeviceRegistry, _build
|
||||
|
||||
REGISTRY = DeviceRegistry(
|
||||
name='washer',
|
||||
capabilities=_build([
|
||||
*ignored.IGNORED,
|
||||
common.POWER_GENERIC,
|
||||
common.POWER_VS_FALLBACK,
|
||||
common.KIDS_LOCK_GENERIC,
|
||||
common.KIDS_LOCK_VS_FALLBACK,
|
||||
common.REMOTE_CONTROL_GENERIC,
|
||||
common.REMOTE_CONTROL_VS_FALLBACK,
|
||||
*common.UNIVERSAL,
|
||||
*common.POWER,
|
||||
washer.WASHER_SETTINGS,
|
||||
washer.WASHER_COURSE,
|
||||
laundry.BUZZER_SOUND,
|
||||
laundry.JOB_BEGINNING_STATUS,
|
||||
common.ALARMS,
|
||||
common.ENERGY_METER,
|
||||
common.WATER_METER,
|
||||
common.AI_ENERGY_LEVEL,
|
||||
operational.OPERATIONAL_STATE,
|
||||
dishwasher.DIAGNOSIS,
|
||||
fridge.FIRMWARE_UPDATE,
|
||||
]),
|
||||
)
|
||||
|
||||
@@ -219,8 +219,9 @@ _AC_IGNORED = [
|
||||
# user-actionable state or no documented write contract, so per the
|
||||
# 'don't guess' rule they are ignored rather than modeled.
|
||||
# /option/muteonce/vs/0 and /selfcheck/vs/0 are deliberately NOT here --
|
||||
# see MUTE_ONCE and fridge.SELF_CHECK above/in the by_type registry,
|
||||
# both of which have a confirmed, cleanly modelable contract.
|
||||
# see MUTE_ONCE above and common.SELF_CHECK (via common.UNIVERSAL) in
|
||||
# the by_type registry, both of which have a confirmed, cleanly
|
||||
# modelable contract.
|
||||
'/airlevelcheck/vs/0', # periodic air-quality sensing scheduler plumbing
|
||||
'/aisleep/vs/0', # AI-sleep feedback state (no actionable control)
|
||||
'/availablecontrolsets/vs/0', # opaque hex-encoded control-set bitmap
|
||||
|
||||
@@ -11,7 +11,9 @@ against live device dumps:
|
||||
/filter/waterfilter/vs/0 -> x.com.samsung.da.filterUsage / filterStatus
|
||||
"""
|
||||
from ..capability import Capability
|
||||
from ..entities import BinarySensorDesc, SelectDesc, SensorDesc, SwitchDesc
|
||||
from ..entities import (
|
||||
BinarySensorDesc, ButtonDesc, SelectDesc, SensorDesc, SwitchDesc,
|
||||
)
|
||||
|
||||
|
||||
def _num(v):
|
||||
@@ -291,3 +293,85 @@ AI_ENERGY_LEVEL = Capability(
|
||||
write_fn=_ai_energy_level_write),
|
||||
),
|
||||
)
|
||||
|
||||
FIRMWARE_UPDATE = Capability(
|
||||
href='/otninformation/vs/0',
|
||||
poll_tier='cold',
|
||||
entities=(
|
||||
BinarySensorDesc(
|
||||
key='firmware_update',
|
||||
field='x.com.samsung.da.newVersionAvailable',
|
||||
name='Firmware update available',
|
||||
device_class='update',
|
||||
entity_category='diagnostic',
|
||||
value_fn=lambda v: str(v).lower() == 'true' if v is not None else None,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
SELF_CHECK = Capability(
|
||||
href='/selfcheck/vs/0',
|
||||
poll_tier='cold',
|
||||
entities=(
|
||||
SensorDesc(key='selfcheck_status', field='x.com.samsung.da.status',
|
||||
name='Self-check status', icon='mdi:stethoscope',
|
||||
entity_category='diagnostic'),
|
||||
SensorDesc(key='selfcheck_result', field='x.com.samsung.da.result',
|
||||
name='Self-check result', icon='mdi:clipboard-check-outline',
|
||||
entity_category='diagnostic'),
|
||||
# List of error codes from the last self-check; joined for display.
|
||||
# Not every fridge reports the field, hence the exists_fn.
|
||||
SensorDesc(key='selfcheck_error', field='x.com.samsung.da.error',
|
||||
name='Self-check error', icon='mdi:alert-circle-outline',
|
||||
entity_category='diagnostic',
|
||||
exists_fn=lambda rep, resources: (
|
||||
not rep or 'x.com.samsung.da.error' in rep),
|
||||
value_fn=lambda v: (', '.join(v) if v else None) if isinstance(v, list) else v),
|
||||
ButtonDesc(key='selfcheck_start', field='', name='Start self-check',
|
||||
payload='Start', icon='mdi:play-circle-outline',
|
||||
entity_category='diagnostic',
|
||||
write_fn=lambda p, rep, href=None: (
|
||||
['selfcheck', 'vs', '0'], {'x.com.samsung.da.status': p})),
|
||||
),
|
||||
)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Cross-family bundles, unpacked into every by_type registry's _build([...])
|
||||
# call the same way ignored.IGNORED is (*common.UNIVERSAL / *common.POWER).
|
||||
# discover() only binds a capability whose href is actually present in a
|
||||
# given device's resource dump, so listing one here for a family that
|
||||
# doesn't expose the href is a no-op, not a phantom entity -- see the
|
||||
# adding-device-support skill's coverage-discipline section.
|
||||
#
|
||||
# UNIVERSAL holds every capability with no known family that both (a) has
|
||||
# the href and (b) needs to model it some other way -- broadening one of
|
||||
# these to a new family is a safe, harmless guess (issue #40's AI energy
|
||||
# level: 2 of 6 families confirmed, blanket-added everywhere else).
|
||||
#
|
||||
# POWER is kept separate: airconditioner deliberately does NOT include it.
|
||||
# AC's climate entity (registry/capabilities/airconditioner.py) already
|
||||
# owns /power/0 and /power/vs/0 -- they're pre-claimed there via bare,
|
||||
# no-entity Capability objects (COVERAGE) so a second, real POWER_GENERIC/
|
||||
# POWER_VS_FALLBACK cap on the same href would make _build() raise
|
||||
# (a href with >1 cap must have every cap discriminated by rt_filter/
|
||||
# match_fn, and the bare COVERAGE cap has neither). Kids-lock and remote-
|
||||
# control don't have this conflict -- no AC dump has ever reported those
|
||||
# hrefs -- so they stay in UNIVERSAL.
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
UNIVERSAL = (
|
||||
ALARMS,
|
||||
ENERGY_METER,
|
||||
FIRMWARE_UPDATE,
|
||||
SELF_CHECK,
|
||||
AI_ENERGY_LEVEL,
|
||||
KIDS_LOCK_GENERIC,
|
||||
KIDS_LOCK_VS_FALLBACK,
|
||||
REMOTE_CONTROL_GENERIC,
|
||||
REMOTE_CONTROL_VS_FALLBACK,
|
||||
)
|
||||
|
||||
POWER = (
|
||||
POWER_GENERIC,
|
||||
POWER_VS_FALLBACK,
|
||||
)
|
||||
|
||||
@@ -19,7 +19,7 @@ import datetime
|
||||
|
||||
from ..capability import Capability
|
||||
from ..entities import (
|
||||
BinarySensorDesc, ButtonDesc, NumberDesc, SelectDesc, SensorDesc,
|
||||
BinarySensorDesc, NumberDesc, SelectDesc, SensorDesc,
|
||||
SwitchDesc, TimeDesc,
|
||||
)
|
||||
from .common import normalize_temp_unit
|
||||
@@ -228,36 +228,6 @@ DEFROST_BLOCK_STATUS = Capability(
|
||||
),
|
||||
)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Self-check diagnostic
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
SELF_CHECK = Capability(
|
||||
href='/selfcheck/vs/0',
|
||||
poll_tier='cold',
|
||||
entities=(
|
||||
SensorDesc(key='selfcheck_status', field='x.com.samsung.da.status',
|
||||
name='Self-check status', icon='mdi:stethoscope',
|
||||
entity_category='diagnostic'),
|
||||
SensorDesc(key='selfcheck_result', field='x.com.samsung.da.result',
|
||||
name='Self-check result', icon='mdi:clipboard-check-outline',
|
||||
entity_category='diagnostic'),
|
||||
# List of error codes from the last self-check; joined for display.
|
||||
# Not every fridge reports the field, hence the exists_fn.
|
||||
SensorDesc(key='selfcheck_error', field='x.com.samsung.da.error',
|
||||
name='Self-check error', icon='mdi:alert-circle-outline',
|
||||
entity_category='diagnostic',
|
||||
exists_fn=lambda rep, resources: (
|
||||
not rep or 'x.com.samsung.da.error' in rep),
|
||||
value_fn=lambda v: (', '.join(v) if v else None) if isinstance(v, list) else v),
|
||||
ButtonDesc(key='selfcheck_start', field='', name='Start self-check',
|
||||
payload='Start', icon='mdi:play-circle-outline',
|
||||
entity_category='diagnostic',
|
||||
write_fn=lambda p, rep, href=None: (
|
||||
['selfcheck', 'vs', '0'], {'x.com.samsung.da.status': p})),
|
||||
),
|
||||
)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Refrigeration modes (rapid cooling)
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -588,25 +558,6 @@ FLEX_ZONE = Capability(
|
||||
),
|
||||
)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Firmware update
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
FIRMWARE_UPDATE = Capability(
|
||||
href='/otninformation/vs/0',
|
||||
poll_tier='cold',
|
||||
entities=(
|
||||
BinarySensorDesc(
|
||||
key='firmware_update',
|
||||
field='x.com.samsung.da.newVersionAvailable',
|
||||
name='Firmware update available',
|
||||
device_class='update',
|
||||
entity_category='diagnostic',
|
||||
value_fn=lambda v: str(v).lower() == 'true' if v is not None else None,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Generic door pattern capability (href=None — use as pattern_cap only)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@@ -227,3 +227,72 @@ class TestAiEnergyLevelSelect:
|
||||
path, body = desc.write_fn('2', {})
|
||||
assert path == ['energy', 'ailevel', 'vs', '0']
|
||||
assert body == {'aiLevel': '2'}
|
||||
|
||||
|
||||
class TestSelfCheckError:
|
||||
"""Self-check diagnostic error list -- surfaced on hardware that reports
|
||||
x.com.samsung.da.error, joined into a single display string."""
|
||||
|
||||
def _desc(self):
|
||||
return next(e for e in common.SELF_CHECK.entities if e.key == 'selfcheck_error')
|
||||
|
||||
def test_exists_when_field_present(self):
|
||||
desc = self._desc()
|
||||
assert desc.exists_fn({'x.com.samsung.da.error': ['DA_ERROR_NONE']}, {}) is True
|
||||
|
||||
def test_does_not_exist_when_field_absent(self):
|
||||
desc = self._desc()
|
||||
assert desc.exists_fn({'x.com.samsung.da.status': 'Ready'}, {}) is False
|
||||
|
||||
def test_exists_for_empty_stub_rep(self):
|
||||
"""An empty {} rep is /device/0's not-yet-fetched-stub carve-out --
|
||||
must be included-for-now, same as ENERGY_METER's fields."""
|
||||
desc = self._desc()
|
||||
assert desc.exists_fn({}, {}) is True
|
||||
|
||||
def test_value_joins_list(self):
|
||||
desc = self._desc()
|
||||
assert desc.value_fn(['E1', 'E2']) == 'E1, E2'
|
||||
|
||||
def test_value_passes_through_scalar(self):
|
||||
desc = self._desc()
|
||||
assert desc.value_fn('DA_ERROR_NONE') == 'DA_ERROR_NONE'
|
||||
|
||||
def test_value_none_for_empty_list(self):
|
||||
"""An empty error list means no value to show -- None (unknown),
|
||||
not an empty string."""
|
||||
desc = self._desc()
|
||||
assert desc.value_fn([]) is None
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Cross-family bundles (UNIVERSAL / POWER) -- unpacked into every by_type
|
||||
# registry's _build([...]) call in place of the hand-duplicated capability
|
||||
# lists that used to live there.
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class TestUniversalAndPowerBundles:
|
||||
def test_universal_contains_the_no_conflict_capabilities(self):
|
||||
assert set(common.UNIVERSAL) == {
|
||||
common.ALARMS, common.ENERGY_METER, common.FIRMWARE_UPDATE,
|
||||
common.SELF_CHECK, common.AI_ENERGY_LEVEL,
|
||||
common.KIDS_LOCK_GENERIC, common.KIDS_LOCK_VS_FALLBACK,
|
||||
common.REMOTE_CONTROL_GENERIC, common.REMOTE_CONTROL_VS_FALLBACK,
|
||||
}
|
||||
|
||||
def test_power_kept_separate_for_airconditioners_sake(self):
|
||||
"""airconditioner.py deliberately doesn't unpack this bundle -- its
|
||||
climate entity already owns /power/0 and /power/vs/0 via a bare,
|
||||
no-entity claim (airconditioner.COVERAGE); a real POWER_GENERIC/
|
||||
POWER_VS_FALLBACK cap on the same href would make _build() raise."""
|
||||
assert set(common.POWER) == {common.POWER_GENERIC, common.POWER_VS_FALLBACK}
|
||||
|
||||
def test_no_overlap_between_the_two_bundles(self):
|
||||
assert not (set(common.UNIVERSAL) & set(common.POWER))
|
||||
|
||||
def test_airconditioner_registry_does_not_include_power(self):
|
||||
from custom_components.localthings.registry.by_type import airconditioner
|
||||
bound_caps = {c for caps in airconditioner.REGISTRY.capabilities.values() for c in caps}
|
||||
assert common.POWER_GENERIC not in bound_caps
|
||||
assert common.POWER_VS_FALLBACK not in bound_caps
|
||||
|
||||
@@ -231,42 +231,6 @@ class TestArtik051AndTp2xFixturesHaveCompleteCoverage:
|
||||
assert state['flex_zone_mode'] == 'CV_FDR_BEVERAGE'
|
||||
|
||||
|
||||
class TestSelfCheckError:
|
||||
"""Self-check diagnostic error list -- surfaced on hardware that reports
|
||||
x.com.samsung.da.error, joined into a single display string."""
|
||||
|
||||
def _desc(self):
|
||||
return next(e for e in fridge.SELF_CHECK.entities if e.key == 'selfcheck_error')
|
||||
|
||||
def test_exists_when_field_present(self):
|
||||
desc = self._desc()
|
||||
assert desc.exists_fn({'x.com.samsung.da.error': ['DA_ERROR_NONE']}, {}) is True
|
||||
|
||||
def test_does_not_exist_when_field_absent(self):
|
||||
desc = self._desc()
|
||||
assert desc.exists_fn({'x.com.samsung.da.status': 'Ready'}, {}) is False
|
||||
|
||||
def test_exists_for_empty_stub_rep(self):
|
||||
"""An empty {} rep is /device/0's not-yet-fetched-stub carve-out --
|
||||
must be included-for-now, same as ENERGY_METER's fields."""
|
||||
desc = self._desc()
|
||||
assert desc.exists_fn({}, {}) is True
|
||||
|
||||
def test_value_joins_list(self):
|
||||
desc = self._desc()
|
||||
assert desc.value_fn(['E1', 'E2']) == 'E1, E2'
|
||||
|
||||
def test_value_passes_through_scalar(self):
|
||||
desc = self._desc()
|
||||
assert desc.value_fn('DA_ERROR_NONE') == 'DA_ERROR_NONE'
|
||||
|
||||
def test_value_none_for_empty_list(self):
|
||||
"""An empty error list means no value to show -- None (unknown),
|
||||
not an empty string."""
|
||||
desc = self._desc()
|
||||
assert desc.value_fn([]) is None
|
||||
|
||||
|
||||
class TestRefrigeratorAiEnergyLevelFixtureCoverage:
|
||||
"""AI energy-saving level (common.AI_ENERGY_LEVEL, see
|
||||
test_common_capabilities.py) is exercised end-to-end here against the
|
||||
|
||||
Reference in New Issue
Block a user