Add cool-only global RAC support: WindFree, Auto mode, display light
Finishes PR #91's contribution (pedroperosin) with the requested review changes applied, on our own branch: - Preset resolution is now fully dynamic, read from each unit's own /mode/convenient/vs/0 supportedModes instead of a static per-model table -- any board's convenient modes (including WindFree's Nano/ NanoSleep) surface without code changes. Unlabelled codes across existing fixtures (longwind, motionindirect, motiondirect, drycomfort) plus the two new WindFree ones are added to en.json/nl.json. - 'Auto' now maps to HVACMode.AUTO instead of HEAT_COOL: these are single-setpoint "device decides" units, not two-setpoint heat+cool ones. 'Fan' is added alongside 'Wind' as a second FAN_ONLY spelling; a new _device_code_for_hvac() picks the code from the unit's own supportedModes since the flat map can't disambiguate two device codes mapping to one HA value. - Detection gains a hyphenated '-RAC-' modelNum fallback (alongside the existing '_RAC_') for cool-only global RAC variants whose /otninformation/vs/0 ships no swVersionInfo block. - Adds a display_light switch sourced from /mode/vs/0's opaque options blob (inverted Light_On/Light_Off token) for boards with no dedicated /light/vs/0 switch. Write-path behavioural change (touches the path iterated on across #9/#17/#27/#38/#54): power now targets the vendor /power/vs/0 instead of the OCF /power/0, and _is_on() reads vendor-first. /power/0 is absent on several known AC boards, so the previous OCF-first read/write pair could report and act on stale state -- consistent with #53's "can turn on but not off". Target temperature now picks its channel (OCF pair vs. vendor items[]) based on which the unit actually reports, reading and writing the same one. The vendor temperature write and the new display-light write both carry only the changed field(s), not the whole resource -- confirmed sufficient on the wire, the device merges the rest itself. That requires the coordinator's optimistic cache to do the same merge on the read side so a setpoint change doesn't blank out current/min/max/unit for the settle window: common.py gains merge_items_field() next to the existing merge_options_field(), and async_send_command wires it in for any write touching x.com.samsung.da.items. New fixture/golden for the cool-only global RAC variant (TP1X_DA-AC-RAC- 01001, AI_RAC_GLOBAL_COOLONLY_3.0); display_light added to the goldens for boards that gain the new options-based switch. Regenerated against current main rather than copied from the original PR, since those goldens had already shifted (current_temperature_c/humidity from issue #75).
This commit is contained in:
19 files changed
+1002
-71
No files matched your search
@@ -59,6 +59,42 @@ class TestMergeOptionsField:
|
||||
assert common.merge_options_field(cached, ['nounderscore']) == ['Course_16']
|
||||
|
||||
|
||||
class TestMergeItemsField:
|
||||
"""merge_items_field() is the items[]-array counterpart of
|
||||
merge_options_field above (issue #91 review feedback): a vendor
|
||||
x.com.samsung.da.items[] write only needs to carry the item id plus the
|
||||
field(s) being changed, so the coordinator uses this to keep its
|
||||
optimistic cache entry complete (current/minimum/maximum/unit still
|
||||
present) without waiting on a real poll."""
|
||||
|
||||
def test_merges_fields_into_matching_id(self):
|
||||
cached = [{'x.com.samsung.da.id': '0', 'x.com.samsung.da.current': '20.0',
|
||||
'x.com.samsung.da.maximum': '30', 'x.com.samsung.da.minimum': '16'}]
|
||||
merged = common.merge_items_field(
|
||||
cached, [{'x.com.samsung.da.id': '0', 'x.com.samsung.da.desired': '22'}])
|
||||
assert merged == [{'x.com.samsung.da.id': '0', 'x.com.samsung.da.current': '20.0',
|
||||
'x.com.samsung.da.maximum': '30', 'x.com.samsung.da.minimum': '16',
|
||||
'x.com.samsung.da.desired': '22'}]
|
||||
|
||||
def test_appends_when_id_absent(self):
|
||||
cached = [{'x.com.samsung.da.id': '0', 'x.com.samsung.da.current': '20.0'}]
|
||||
merged = common.merge_items_field(
|
||||
cached, [{'x.com.samsung.da.id': '1', 'x.com.samsung.da.desired': '22'}])
|
||||
assert merged == [
|
||||
{'x.com.samsung.da.id': '0', 'x.com.samsung.da.current': '20.0'},
|
||||
{'x.com.samsung.da.id': '1', 'x.com.samsung.da.desired': '22'},
|
||||
]
|
||||
|
||||
def test_handles_missing_cache(self):
|
||||
assert common.merge_items_field(
|
||||
None, [{'x.com.samsung.da.id': '0', 'x.com.samsung.da.desired': '22'}]
|
||||
) == [{'x.com.samsung.da.id': '0', 'x.com.samsung.da.desired': '22'}]
|
||||
|
||||
def test_ignores_malformed_new_items(self):
|
||||
cached = [{'x.com.samsung.da.id': '0', 'x.com.samsung.da.current': '20.0'}]
|
||||
assert common.merge_items_field(cached, ['not-a-dict']) == cached
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# OCF-native / vendor '-vs' fallback pairs (power, kids-lock, remote control).
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
Reference in new issue
Block a user