From 1a76bf6d47ce797d9a7a69f3aa40e446c9f1d3e8 Mon Sep 17 00:00:00 2001 From: Marc Billow Date: Sun, 28 Jun 2026 19:30:12 -0500 Subject: [PATCH] Fix C1 (switch discovery), C2 (power write_fn), I2 (golden oracle) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit C1: SwitchDesc MQTT discovery now emits payload_on/off='On'/'Off', state_on/off='On'/'Off', and a Jinja2 boolean-to-string value_template so HA can correctly interpret and command switch entities. C2: POWER write_fn corrected — path is ['power','vs','0'] segment list (not a single slash-prefixed string), and payload comparison is p == 'On' (not truthiness) so 'Off' can actually be sent. I2: Golden regression oracle strengthened to exact equality for both state_keys and discovery_unique_ids; prior subset-only check allowed over-production to go undetected. Two golden tests now fail revealing stale fixtures (dishwasher: +3 keys; refrigerator: +9 keys) — fixtures intentionally NOT updated, human review required. --- samsung_appliance/registry/adapter.py | 6 +++++- .../registry/capabilities/common.py | 2 +- tests/test_adapter.py | 19 +++++++++++++++++++ tests/test_golden_regression.py | 14 +++++++++++--- 4 files changed, 36 insertions(+), 5 deletions(-) diff --git a/samsung_appliance/registry/adapter.py b/samsung_appliance/registry/adapter.py index 1b4c7f9..3892810 100644 --- a/samsung_appliance/registry/adapter.py +++ b/samsung_appliance/registry/adapter.py @@ -204,7 +204,11 @@ def _discovery_payloads(bound, topic_prefix, ha_prefix, device_name, model): cfg['options'] = list(opts) if isinstance(opts, (list, tuple)) else [] elif isinstance(d, SwitchDesc): cfg['state_topic'] = state_topic - cfg['value_template'] = f"{{{{ value_json.{key} }}}}" + cfg['value_template'] = f"{{% if value_json.{key} %}}On{{% else %}}Off{{% endif %}}" + cfg['state_on'] = 'On' + cfg['state_off'] = 'Off' + cfg['payload_on'] = 'On' + cfg['payload_off'] = 'Off' cfg['command_topic'] = f"{topic_prefix}/cmd/{key}" elif isinstance(d, ButtonDesc): cfg['command_topic'] = f"{topic_prefix}/cmd/{key}" diff --git a/samsung_appliance/registry/capabilities/common.py b/samsung_appliance/registry/capabilities/common.py index 945487b..b942c0e 100644 --- a/samsung_appliance/registry/capabilities/common.py +++ b/samsung_appliance/registry/capabilities/common.py @@ -74,7 +74,7 @@ POWER = Capability( SwitchDesc(key='power_switch', field='x.com.samsung.da.power', name='Power', value_fn=lambda v: v == 'On', - write_fn=lambda p, rep: (['/power/vs/0'], {'x.com.samsung.da.power': 'On' if p else 'Off'})), + write_fn=lambda p, rep: (['power', 'vs', '0'], {'x.com.samsung.da.power': 'On' if p == 'On' else 'Off'})), SensorDesc(key='power_state', field='x.com.samsung.da.power', name='Power state'), BinarySensorDesc(key='power_state_binary', field='x.com.samsung.da.power', diff --git a/tests/test_adapter.py b/tests/test_adapter.py index 5d6c6d9..0785322 100644 --- a/tests/test_adapter.py +++ b/tests/test_adapter.py @@ -10,6 +10,11 @@ def _bound(resources): return discover(resources, reg) +def _bound_with_power(resources): + reg = {c.href: c for c in (common.KIDS_LOCK, common.ENERGY_METER, common.POWER)} + return discover(resources, reg) + + def test_flatten_applies_value_fn(dishwasher_resources): rd = build_runtime_descriptor( _bound(dishwasher_resources), topic_prefix='t', ha_prefix='homeassistant', @@ -38,3 +43,17 @@ def test_discovery_payloads_have_unique_ids(dishwasher_resources): # payloads are JSON bytes carrying unique_id cfg = json.loads(next(p for t, p in rd.discovery_payloads if 'child_lock' in t)) assert cfg['unique_id'] == 'samsung_dishwasher_child_lock' + + +def test_switch_discovery_config_has_correct_payloads(dishwasher_resources): + rd = build_runtime_descriptor( + _bound_with_power(dishwasher_resources), topic_prefix='samsung_dishwasher', + ha_prefix='homeassistant', device_name='Dishwasher', model='M', + name='dishwasher', default_port=49154) + cfg = json.loads(next(p for t, p in rd.discovery_payloads if 'power_switch' in t)) + assert cfg['payload_on'] == 'On' + assert cfg['payload_off'] == 'Off' + assert cfg['state_on'] == 'On' + assert cfg['state_off'] == 'Off' + assert 'On' in cfg['value_template'] + assert 'Off' in cfg['value_template'] diff --git a/tests/test_golden_regression.py b/tests/test_golden_regression.py index c95e8a4..b9f1f7d 100644 --- a/tests/test_golden_regression.py +++ b/tests/test_golden_regression.py @@ -26,6 +26,14 @@ def _new_state_and_uids(name, resources): def test_registry_reproduces_golden_state_keys(name, ip, request): resources = json.load(open(f'local-tools/dumps/{ip}.json'))['resources'] golden = json.loads((GOLDEN / f'{name}.json').read_text()) - state_keys, _ = _new_state_and_uids(name, resources) - missing = set(golden['state_keys']) - set(state_keys) - assert not missing, f"registry missing state keys: {sorted(missing)}" + state_keys, uids = _new_state_and_uids(name, resources) + assert set(state_keys) == set(golden['state_keys']), ( + f"state_keys mismatch:\n" + f" extra: {sorted(set(state_keys) - set(golden['state_keys']))}\n" + f" missing: {sorted(set(golden['state_keys']) - set(state_keys))}" + ) + assert set(uids) == set(golden['discovery_unique_ids']), ( + f"unique_ids mismatch:\n" + f" extra: {sorted(set(uids) - set(golden['discovery_unique_ids']))}\n" + f" missing: {sorted(set(golden['discovery_unique_ids']) - set(uids))}" + )