Skip to content

Commit

Permalink
climarad parsers, states
Browse files Browse the repository at this point in the history
  • Loading branch information
silverailscolo committed Oct 8, 2024
1 parent eedd4a2 commit 3492aa8
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 35 deletions.
2 changes: 1 addition & 1 deletion src/ramses_tx/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,7 @@ def hex_from_str(value: str) -> str:


def hex_to_temp(value: HexStr4) -> bool | float | None: # TODO: remove bool
"""Convert a 2's complement 4-byte hex string to an float."""
"""Convert a 2's complement 4-byte hex string to a float."""
if not isinstance(value, str) or len(value) != 4:
raise ValueError(f"Invalid value: {value}, is not a 4-char hex string")
if value == "31FF": # means: N/A (== 127.99, 2s complement), signed?
Expand Down
97 changes: 73 additions & 24 deletions src/ramses_tx/parsers.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
SZ_DOMAIN_IDX,
SZ_DURATION,
SZ_FAN_MODE,
SZ_FAN_RATE,
SZ_FAULT_STATE,
SZ_FAULT_TYPE,
SZ_FRAG_LENGTH,
Expand Down Expand Up @@ -258,9 +259,9 @@ def parser_0001(payload: str, msg: Message) -> Mapping[str, bool | str | None]:
def parser_0002(payload: str, msg: Message) -> dict[str, Any]:
# seen with: 03:125829, 03:196221, 03:196196, 03:052382, 03:201498, 03:201565:
# .I 000 03:201565 --:------ 03:201565 0002 004 03020105 # no zone_idx, domain_id
# also seen on FAN ClimaRad Ventura HRU
# .I --- 37:153226 --:------ 37:153226 0002 004 00180005

# seen on FAN ClimaRad Ventura HRU:
# .I --- 37:153226 --:------ 37:153226 0002 004 00120005 0x12 = seems default, some default valve state?
# .I --- 37:153226 --:------ 37:153226 0002 004 00180005 0x18 = very scarce, minutes after&before -0x12-
# is it CODE_IDX_COMPLEX:
# - 02...... for outside temp?
# - 03...... for other stuff?
Expand All @@ -269,6 +270,13 @@ def parser_0002(payload: str, msg: Message) -> dict[str, Any]:
assert payload == "03020105"
return {"_unknown": payload}

if msg.src.type == "37": # FAN/HRU ClimaRad VenturaV1x
assert payload[4:8] == "0005"
return {
"outdoor_state": payload[2:4],
"_unknown": payload[4:],
}

# if payload[6:] == "02": # msg.src.type == DEV_TYPE_MAP.OUT:
return {
SZ_TEMPERATURE: hex_to_temp(payload[2:6]),
Expand Down Expand Up @@ -1146,18 +1154,19 @@ def parser_1298(payload: str, msg: Message) -> PayDictT._1298:
# HVAC: indoor_humidity
def parser_12a0(payload: str, msg: Message) -> PayDictT._12A0:
if len(payload) == 21:
# for ClimaRad VenturaV1x: --- 12A0 021 00 29 081D7FFF0001EF7FFF7FFF0002410505029400
# for ClimaRad VenturaV1x: --- 12A0 021 00 29 081D7FFF0001EF7FFF7FFF0002410505 0294 00
temp = int(payload[2:4], 16)/2 # Fahrenheit/Celsius flag? WIP
result = {SZ_TEMPERATURE: temp,
"units": {"01": "Fahrenheit", "02": "Celsius"}[payload[28:30]], # only 0x02 Celsius seen
"units": {"01": "Fahrenheit", "02": "Celsius"}[payload[28:30]], # only 0x02 (decimal/Celsius?) seen
"indoor_humidity": parse_indoor_humidity(payload[30:32]),
"CO2": parse_co2_level(payload[36:40]),
"_unknown_2": payload[6:30],
"_unknown_6": payload[32:]}
# TODO determine complete message: fan speed? room temperature = payload[5:6]
"_unknown_6": payload[32:36]}
# TODO determine complete message: fan speed? room temperature = payload[5:6]?
return result
else:
return parse_indoor_humidity(payload[2:])
# .I --- 29:099029 --:------ 29:099029 12A0 002 0042 ClimaRad MiniBox FAN
# .I --- 29:099029 --:------ 29:099029 12A0 002 0042 from a ClimaRad MiniBox FAN (0x42 = 66% = 0.6)


# window_state (of a device/zone)
Expand Down Expand Up @@ -1405,10 +1414,11 @@ def parser_2210(payload: str, msg: Message) -> dict[str, Any]:
# .I --- 37:153226 --:------ 37:153226 2210 042 00FF 00FFFFFF0000000000FFFFFFFFFF 00FFFFFF0000000000FFFFFFFFFF FFFFFF000000000000 00 01 40
assert payload in (
"00FF" + "00FFFFFF0000000000FFFFFFFFFF" * 2 + ("FFFFFF000000000000000800"),
"00FF" + "00FFFFFF0000000000FFFFFFFFFF" * 2 + ("FFFFFF000000000000000140"), # ClimaRad, TODO extract special content
"00FF" + "00FFFFFF0000000000FFFFFFFFFF" * 2 + ("FFFFFF000000000000020800"),
"00FF" + "00FFFFFF0000000000FFFFFFFFFF" * 2 + ("FFFFFF000000000000000140"), # ClimaRad VenturaV1x, extract special content?
), _INFORM_DEV_MSG

return {}
return {"unknown_hvac": payload[36:]}


# now_next_setpoint - Programmer/Hometronics
Expand Down Expand Up @@ -1576,6 +1586,7 @@ def parser_22f1(payload: str, msg: Message) -> dict[str, Any]:

# Schema B: Vasco and ClimaRad fan remotes send fixed command 06, not 04, and like
# .I --- 37:117647 32:022222 --:------ 22F1 003 000506 for high
# ClimaRad VenturaV1x does not send 22F1, uses 22F4

try:
assert payload[0:2] in ("00", "63")
Expand Down Expand Up @@ -1708,7 +1719,7 @@ def parser_22f3(payload: str, msg: Message) -> dict[str, Any]:
return result


# WIP: unknown, HVAC
# WIP: unknown/fan speed mode, HVAC
def parser_22f4(payload: str, msg: Message) -> dict[str, Any]:
# RP --- 32:155617 18:005904 --:------ 22F4 013 00-60-E6-00000000000000-200000
# RP --- 32:153258 18:005904 --:------ 22F4 013 00-60-DD-00000000000000-200000
Expand All @@ -1718,18 +1729,51 @@ def parser_22f4(payload: str, msg: Message) -> dict[str, Any]:
# RP --- 32:137185 18:003599 --:------ 22F4 013 00-60-E5-00000000000000-200000
# RP --- 32:137185 18:003599 --:------ 22F4 013 00-60-E6-00000000000000-200000

# .I --- 37:153226 --:------ 37:153226 22F4 013 00-40-30-00000000000000-000000 (ClimaRad VenturaV1x - auto)
# .I --- 37:153226 --:------ 37:153226 22F4 013 00-00-00-000060C9000000-000000 (ClimaRad VenturaV1x - speed 1)
assert payload[:2] == "00"
assert (payload[6:] == "00000000000000200000" or payload[14:] == "000000000000") # TODO add some error message?

return {
"value_02": payload[2:4],
"value_04": payload[4:6],
"value_05": f"0x{payload[10:12]} d{int(payload[10:12], 16)}", # 0x60 = d96 speed?
"value_06": f"0x{payload[12:14]} d{int(payload[12:14], 16)}", # 0xC9 = d201 time?
}
if msg.verb == I_:
# heard from ClimaRad Ventura, see _22F1_MODE_CLIMARAD
# .I --- 37:153226 --:------ 37:153226 22F4 013 00 4030 0000 0000 000000-000000 (auto)
# .I --- 37:153226 --:------ 37:153226 22F4 013 00 0000 0000 60C9 000000-000000 (speed 1)
# .I --- 37:153226 --:------ 37:153226 22F4 013 00 0000 0000 60CA 000000-000000 (speed 2)
# .I --- 37:153226 --:------ 37:153226 22F4 013 00 0000 0000 60CB 000000-000000 (speed 3)
# .I --- 37:153226 --:------ 37:153226 22F4 013 00 2000 0000 2000 000000-000000 (pauze ||)
_22f4_scheme = "N/A"
rate = "N/A"
mode = "N/A"
if payload[2:4] == "40":
assert payload[4:6] == "30", f"unknown auto mode: {payload[2:6]}"
_22f4_scheme = "climarad"
mode = "auto"
elif payload[2:4] == "20":
assert payload[4:6] == "00", f"unknown paused mode: {payload[2:6]}"
_22f4_scheme = "climarad"
mode = "paused"
rate = "0"
_22f4_mode_set = ("", "60")
if payload[10:12] in _22f4_mode_set:
from .ramses import _22F1_MODE_CLIMARAD as _22F4_FAN_MODE
_22f4_scheme = "climarad"
mode = "manual"
try:
assert payload[12:14] in _22F4_FAN_MODE, f"unknown fan_mode: {payload[12:14]}"
except AssertionError as err:
_LOGGER.warning(f"unknown ClimaRad speed rate {msg!r} < {_INFORM_DEV_MSG} ({err})")

rate = _22F4_FAN_MODE.get(payload[12:14], f"unknown_{payload[12:14]}")

result = {
SZ_FAN_MODE: mode,
SZ_FAN_RATE: rate,
"_scheme": _22f4_scheme
}
else:
assert (payload[6:] == "00000000000000200000" or payload[14:] == "000000000000") # TODO add an error message
result = {
"value_02": payload[2:4],
"value_04": payload[4:6],
}
return result

# bypass_mode, HVAC
def parser_22f7(payload: str, msg: Message) -> dict[str, Any]:
Expand Down Expand Up @@ -2020,10 +2064,12 @@ def parser_2e10(payload: str, msg: Message) -> dict[str, Any]:
"duration": (payload[5:6]),
"_unknown_4": payload[7:],
}
elif payload == "000000": # ClimaRad Ventura set to auto
elif msg.src.type == "37": # ClimaRad VenturaV1x FAN state set to Auto
# .I --- 37:153226 --:------ 37:153226 2E10 003 000000
# .I --- 37:153226 --:------ 37:153226 2E10 003 000100 seen while on Manual 2**
assert payload in ("000000", "000100"), _INFORM_DEV_MSG
return {
"mode": "auto", # EBR check for other values
"duration": (payload[5:6]),
"auto_state": (payload[2:4]),
}
else:
assert payload in ("0001", "000100"), _INFORM_DEV_MSG
Expand Down Expand Up @@ -2371,7 +2417,10 @@ def _parser(seqx: str) -> dict:

# supplied boiler water (flow) temp
def parser_3200(payload: str, msg: Message) -> PayDictT._3200:
return {SZ_TEMPERATURE: hex_to_temp(payload[2:])}
if payload[2:] == "7FFF": # Not implemented
return {SZ_TEMPERATURE: None}
else:
return {SZ_TEMPERATURE: hex_to_temp(payload[2:])}


# return (boiler) water temp
Expand Down
30 changes: 20 additions & 10 deletions src/ramses_tx/ramses.py
Original file line number Diff line number Diff line change
Expand Up @@ -277,10 +277,10 @@
Code._12A0: { # indoor_humidity
# .I --- 32:168090 --:------ 32:168090 12A0 006 0030093504A8
# .I --- 32:132125 --:------ 32:132125 12A0 007 003107B67FFF00 # only dev_id with 007
# .I --- 37:153226 --:------ 37:153226 12A0 021 003108127FFF00 01EF7FFF7FFF0002 42061503AC 00 # ClimaRad Ventura
# .I --- 37:153226 --:------ 37:153226 12A0 021 003508717FFF00 01EF7FFF7FFF0002 380683031A 00 # same ClimaRad, later
# .I --- 37:153226 --:------ 37:153226 12A0 021 002F08677FFF00 01EF7FFF7FFF0002 39068F0344 00 # same ClimaRad, later
# .I --- 37:153226 --:------ 37:153226 12A0 021 003408917FFF00 01EF7FFF7FFF0002 3706B70327 00 # etc.
# .I --- 37:153226 --:------ 37:153226 12A0 021 003108127FFF00 01EF7FFF7FFF0002 420615 03AC 00 # ClimaRad Ventura
# .I --- 37:153226 --:------ 37:153226 12A0 021 003508717FFF00 01EF7FFF7FFF0002 380683 031A 00 # same ClimaRad, later
# .I --- 37:153226 --:------ 37:153226 12A0 021 002F08677FFF00 01EF7FFF7FFF0002 39068F 0344 00 # same ClimaRad, later
# .I --- 37:153226 --:------ 37:153226 12A0 021 003408917FFF00 01EF7FFF7FFF0002 3706B7 0327 00 # etc.
# RP --- 20:008749 18:142609 --:------ 12A0 002 00EF
SZ_NAME: "indoor_humidity",
I_: r"^00[0-9A-F]{2}([0-9A-F]{8}(00)?)?((01EF7FFF7FFF0002)[0-9A-F]{10}(00))?$",
Expand Down Expand Up @@ -422,13 +422,13 @@
},
Code._22F3: { # fan_boost, HVAC
SZ_NAME: "fan_boost",
I_: r"^(00|63)(021E)?[0-9A-F]{4}([0-9A-F]{8})?$", # VASCO D60 HRU: 22F3 007 00 021E 0406 0000 (a timer)
I_: r"^(00|63)(021E)?[0-9A-F]{4}([0-9A-F]{8})?$", # VASCO D60 HRU: .I + 22F3 007 00 021E 0406 0000 (a timer)
}, # minutes only?
Code._22F4: { # unknown_22f4, HVAC
# .I + 22F4 013 00 40 30 0000 00 00 000000-000000 (ClimaRad Ventura - mode auto)
# .I + 22F4 013 00 00 00 0000 60 C9 000000-000000 (idem speed 1)
SZ_NAME: "unknown_22f4",
I_: r"^00[0-9A-F]{4}(00){2}[0-9A-F]{4}(00){6}$",
# .I + 22F4 013 00 40 30 0000 00 00 000000000000 (ClimaRad Ventura - mode Auto)
# .I + 22F4 013 00 00 00 0000 60 C9 000000000000 (ClimaRad Ventura - speed 1)
SZ_NAME: "unknown_22f4", # TODO rename?
I_: r"^00[0-9A-F]{4}(0000)[0-9A-F]{4}(00){6}$",
RQ: r"^00$",
RP: r"^00[0-9A-F]{24}$",
},
Expand Down Expand Up @@ -1247,18 +1247,28 @@
"07": "off",
}

_22F1_MODE_VASCO: dict[str, str] = { # same for ClimaRad Minibox fan/remote
_22F1_MODE_VASCO: dict[str, str] = { # for VASCO D60 AND ClimaRad Minibox fanS/remoteS
"02": "low", # low: 000206
"03": "medium", # medium: 000306
"04": "high", # high: 000406, aka boost with 22F3
"05": "auto",
}

_22F1_MODE_CLIMARAD: dict[str, str] = {
# for ClimaRad Ventura fan/remote, actually for 22F4 but stored for easier comparison
"C9": "1", # low
"CA": "2", # medium-low
"CB": "3", # medium
"CC": "4", # medium-high
"CD": "5", # high aka boost
}

_22F1_SCHEMES: dict[str, dict[str, str]] = {
"itho": _22F1_MODE_ITHO,
"nuaire": _22F1_MODE_NUAIRE,
"orcon": _22F1_MODE_ORCON,
"vasco": _22F1_MODE_VASCO,
"climarad": _22F1_MODE_CLIMARAD, # used in 22F4
}

# unclear if true for only Orcon/*all* models
Expand Down
1 change: 1 addition & 0 deletions tests/tests_rf/test_binding_fsm.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
NUAIRE = "nuaire"
ORCON_ = "orcon"
VASCO_ = "vasco"
CLIMARAD = "climarad"

TEST_SUITE_300 = [
# { # THM to CTL: FIXME: affirm is I|1FC9|07, not I|1FC9|00
Expand Down

0 comments on commit 3492aa8

Please sign in to comment.