fix(airconditioner): Good Sleep needs the mode token its duration belongs to
Sleep_<n> written on its own is answered 2.04 Changed and then discarded, so the Number wrote nothing at all. Nano wind shares the same Comode_ slot, which is why writing the nano preset over a running timer silently changed its duration, and why the two sleep codes the board reports had to become presets: a preset_mode outside preset_modes is not a state HA allows.
This commit is contained in:
@@ -267,15 +267,26 @@ class LocalThingsClimate(LocalThingsEntity, ClimateEntity):
|
||||
# Nano=windFree, Quiet, Comfort, 2Step, Speed=Fast Turbo, Off=none.
|
||||
_LEGACY_PRESET_CODES = ("Off", "Nano", "Quiet", "Comfort", "2Step", "Speed")
|
||||
|
||||
# Good Sleep occupies the same Comode_ slot as the presets above, so a unit
|
||||
# running it reports Comode_Sleep -- or Comode_NanoSleep, which the board
|
||||
# will produce by itself when nano wind is asked for while the timer runs.
|
||||
# Neither is in the list above, and a preset_mode outside preset_modes is
|
||||
# not a state HA allows, so they are added for boards that have the Sleep_
|
||||
# token these codes come with.
|
||||
_LEGACY_SLEEP_PRESET_CODES = ("Sleep", "NanoSleep")
|
||||
|
||||
def _legacy_convenient(self) -> dict:
|
||||
"""A /mode/convenient/vs/0-shaped rep built from the Comode_* token in
|
||||
/mode/vs/0's options, for boards that have no convenient resource."""
|
||||
options = self._rep(MODE_HREF).get("x.com.samsung.da.options") or []
|
||||
codes = list(self._LEGACY_PRESET_CODES)
|
||||
if any(isinstance(option, str) and option.startswith("Sleep_") for option in options):
|
||||
codes += self._LEGACY_SLEEP_PRESET_CODES
|
||||
for option in options:
|
||||
if isinstance(option, str) and option.startswith("Comode_"):
|
||||
return {
|
||||
_MODES_FIELD: [option.split("_", 1)[1]],
|
||||
_SUPPORTED_FIELD: list(self._LEGACY_PRESET_CODES),
|
||||
_SUPPORTED_FIELD: codes,
|
||||
}
|
||||
return {}
|
||||
|
||||
|
||||
@@ -324,6 +324,59 @@ def _option_number_write(prefix, factor=1):
|
||||
return write
|
||||
|
||||
|
||||
def _good_sleep_write(payload, rep, href=None):
|
||||
"""Good Sleep needs its mode token in the same write as its duration.
|
||||
|
||||
`Sleep_<n>` on its own is answered 2.04 Changed and then thrown away:
|
||||
measured on an ARTIK051_KRAC_18K, writing `["Sleep_4"]` left the token at
|
||||
`Sleep_0` at both +8s and +45s, while the same value written together with
|
||||
`Comode_Sleep` held. So the number is a parameter of the mode, not a
|
||||
setting of its own, and the appliance's app never sends one without the
|
||||
other either.
|
||||
|
||||
Which mode token goes with it depends on nano wind, the way the app decides
|
||||
it: nano and Good Sleep share the single `Comode_` slot, so running both is
|
||||
`Comode_NanoSleep`, and switching the timer off while nano is on leaves nano
|
||||
running rather than turning everything off.
|
||||
"""
|
||||
half_hours = round(float(payload) * 2)
|
||||
nano = _option_token(rep, "Comode") in ("Nano", "NanoSleep")
|
||||
if half_hours:
|
||||
comode = "Comode_NanoSleep" if nano else "Comode_Sleep"
|
||||
else:
|
||||
comode = "Comode_Nano" if nano else "Comode_Off"
|
||||
return (
|
||||
["mode", "vs", "0"],
|
||||
{"x.com.samsung.da.options": [comode, f"Sleep_{half_hours}"]},
|
||||
)
|
||||
|
||||
|
||||
# What the appliance itself picks when a Good Sleep mode is asked for with no
|
||||
# duration to go with it: writing a bare `Comode_Nano` over a live
|
||||
# `Comode_Sleep`/`Sleep_4` came back as `Comode_NanoSleep`/`Sleep_16`. Used only
|
||||
# when a sleep preset is selected while the timer reads 0.
|
||||
_DEFAULT_SLEEP_HALF_HOURS = 16
|
||||
|
||||
|
||||
def _preset_options(code, rep):
|
||||
"""The options array for a legacy preset write.
|
||||
|
||||
One `Comode_` token has to express both nano wind and Good Sleep, so
|
||||
selecting nano while the timer is running means `Comode_NanoSleep` -- and it
|
||||
has to carry the duration, because the board otherwise supplies its own.
|
||||
Measured: `["Comode_Nano"]` written over `Comode_Sleep`/`Sleep_4` came back
|
||||
as `Comode_NanoSleep`/`Sleep_16`, silently turning the user's two hours into
|
||||
eight. Writing the pair keeps the two hours.
|
||||
"""
|
||||
sleep = _option_token(rep, "Sleep")
|
||||
running = sleep not in (None, "0")
|
||||
if code == "Nano" and running:
|
||||
code = "NanoSleep"
|
||||
if code in ("Sleep", "NanoSleep"):
|
||||
return [f"Comode_{code}", f"Sleep_{sleep if running else _DEFAULT_SLEEP_HALF_HOURS}"]
|
||||
return option_write("Comode", code)
|
||||
|
||||
|
||||
def _odor_controller_active(rep):
|
||||
"""Odor-controller self-clean on/off, from the `SmartCoolClean_<On/Off>`
|
||||
option token (matches the SmartThings cloud's airConditionerOdorController
|
||||
@@ -404,7 +457,7 @@ def _climate_write(payload, rep, href=None):
|
||||
if kind == "swing_legacy":
|
||||
return (["airflow", "vs", "0"], {"x.com.samsung.da.direction": value})
|
||||
if kind == "preset_legacy":
|
||||
return (["mode", "vs", "0"], {"x.com.samsung.da.options": option_write("Comode", value)})
|
||||
return (["mode", "vs", "0"], {"x.com.samsung.da.options": _preset_options(value, rep)})
|
||||
if kind == "preset":
|
||||
return (["mode", "convenient", "vs", "0"], {"x.com.samsung.da.modes": value})
|
||||
return None
|
||||
@@ -555,7 +608,7 @@ CLIMATE = Capability(
|
||||
key="good_sleep",
|
||||
rep_fn=_option_token_num("Sleep", divisor=2),
|
||||
exists_fn=_has_option_token("Sleep"),
|
||||
write_fn=_option_number_write("Sleep", factor=2),
|
||||
write_fn=_good_sleep_write,
|
||||
native_min=0,
|
||||
native_max=12,
|
||||
step=0.5,
|
||||
|
||||
@@ -152,6 +152,7 @@
|
||||
"smart": "Chytrý",
|
||||
"speed": "Rychlý",
|
||||
"nano": "WindFree",
|
||||
"sleep": "Spánek",
|
||||
"nanosleep": "WindFree spánek",
|
||||
"longwind": "Dlouhý vánek",
|
||||
"motionindirect": "Nepřímý vzduch při pohybu",
|
||||
|
||||
@@ -152,6 +152,7 @@
|
||||
"smart": "Smart",
|
||||
"speed": "Speed",
|
||||
"nano": "WindFree",
|
||||
"sleep": "Sleep",
|
||||
"nanosleep": "WindFree sleep",
|
||||
"longwind": "Long wind",
|
||||
"motionindirect": "Motion indirect",
|
||||
|
||||
@@ -274,6 +274,7 @@
|
||||
"smart": "Inteligente",
|
||||
"speed": "Rápido",
|
||||
"nano": "WindFree",
|
||||
"sleep": "Sueño",
|
||||
"nanosleep": "WindFree sueño",
|
||||
"longwind": "Viento prolongado",
|
||||
"motionindirect": "Indirecto al movimiento",
|
||||
|
||||
@@ -152,6 +152,7 @@
|
||||
"smart": "Smart",
|
||||
"speed": "Veloce",
|
||||
"nano": "WindFree",
|
||||
"sleep": "Sonno",
|
||||
"nanosleep": "WindFree sonno",
|
||||
"longwind": "Vento prolungato",
|
||||
"motionindirect": "Indiretto al movimento",
|
||||
|
||||
@@ -152,6 +152,7 @@
|
||||
"smart": "스마트",
|
||||
"speed": "스피드",
|
||||
"nano": "무풍",
|
||||
"sleep": "숙면",
|
||||
"nanosleep": "무풍 수면",
|
||||
"longwind": "롱바람",
|
||||
"motionindirect": "간접풍",
|
||||
|
||||
@@ -152,6 +152,7 @@
|
||||
"smart": "Slim",
|
||||
"speed": "Snel",
|
||||
"nano": "WindFree",
|
||||
"sleep": "Slaap",
|
||||
"nanosleep": "WindFree-slaap",
|
||||
"longwind": "Lange wind",
|
||||
"motionindirect": "Beweging indirect",
|
||||
|
||||
Reference in New Issue
Block a user