Add support for Samsung room air conditioners (ARTIK051_PRAC-class), the first device whose core controls map onto a single Home Assistant `climate` entity rather than a scatter of switches/selects/numbers. - New `climate` platform + `ClimateDesc`: one composite entity that reads power, HVAC mode, current/target temperature, fan (wind) strength, swing (wind direction) and the convenient-mode preset across several OCF resources and writes back to each. On/off folds into HVACMode.OFF / TURN_ON/OFF; convenient mode folds into preset_mode. The entity binds one primary resource (/mode/vs/0) and reads its siblings from the coordinator snapshot, reusing the cross-resource read pattern from number/select. - New `airconditioner` capability module + by_type registry, routed via the `_PRAC_` modelNum token. Reuses common ALARMS/ENERGY_METER, fridge.FIRMWARE_UPDATE and dishwasher.DIAGNOSIS; air purify and auto clean as config switches; air dust filter status/usage as diagnostics (usage normalized to a percentage of rated capacity). - Climate-consumed and all-zero/ambiguous resources (temperature/wind, /sensors, /humidity) are declared as AC-scoped coverage so every href in the dump binds or is covered -- no coverage-gap repair. - Fan/swing modes map onto HA standard constants (auto-localized); the custom fan `turbo`, presets `quiet/smart/speed`, and the filter-status enum get translations in strings.json + translations/en.json. - Scrubbed fixture, golden, and tests: registry routing, zero unbound hrefs, the climate write contract, and filter-% normalization.
37 lines
1.4 KiB
Python
37 lines
1.4 KiB
Python
from custom_components.localthings.registry.entities import (
|
|
SensorDesc, BinarySensorDesc, SelectDesc, SwitchDesc, ButtonDesc,
|
|
NumberDesc, TimeDesc, ClimateDesc, PLATFORM_OF,
|
|
)
|
|
|
|
|
|
def test_value_fn_defaults_to_identity():
|
|
d = SensorDesc(key='power', field='x.com.samsung.da.instantaneousPower')
|
|
assert d.value_fn(42) == 42
|
|
|
|
|
|
def test_descriptions_are_frozen():
|
|
d = SensorDesc(key='power', field='f')
|
|
try:
|
|
d.key = 'other'
|
|
except Exception as e:
|
|
assert 'frozen' in str(type(e)).lower() or 'cannot' in str(e).lower()
|
|
else:
|
|
raise AssertionError("expected frozen dataclass")
|
|
|
|
|
|
def test_platform_mapping_covers_all_subclasses():
|
|
assert PLATFORM_OF[SensorDesc] == 'sensor'
|
|
assert PLATFORM_OF[BinarySensorDesc] == 'binary_sensor'
|
|
assert PLATFORM_OF[SelectDesc] == 'select'
|
|
assert PLATFORM_OF[SwitchDesc] == 'switch'
|
|
assert PLATFORM_OF[ButtonDesc] == 'button'
|
|
assert PLATFORM_OF[NumberDesc] == 'number'
|
|
assert PLATFORM_OF[TimeDesc] == 'time'
|
|
assert PLATFORM_OF[ClimateDesc] == 'climate'
|
|
|
|
|
|
def test_select_carries_options_and_write_fn():
|
|
d = SelectDesc(key='sound', field='mode', options=('voice', 'tone', 'mute'),
|
|
write_fn=lambda p, rep: (['settings', 'sound', 'mode', 'vs', '0'], {'mode': p}))
|
|
assert d.write_fn('tone', {}) == (['settings', 'sound', 'mode', 'vs', '0'], {'mode': 'tone'})
|