Skip to content

Commit

Permalink
Merge branch 'master' of https://github.com/zxdavb/ramses_rf into eb-…
Browse files Browse the repository at this point in the history
…2e10
  • Loading branch information
silverailscolo committed Oct 23, 2024
2 parents 5f158bf + c82c7c3 commit 2e8671a
Show file tree
Hide file tree
Showing 14 changed files with 380 additions and 258 deletions.
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ exclude: ^(.secrets|docs|misc|tests/deprecated)/

repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.6.9
rev: v0.7.0
hooks:
- id: ruff # linter
- id: ruff-format # formatter
Expand Down
4 changes: 2 additions & 2 deletions requirements_dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
# - pip list | grep -E 'pre-commit|ruff|mypy|types-|voluptuous|pytest|hatch'

# used for development (linting)
pre-commit >= 3.8.0
ruff >= 0.6.4 # also: check-lint.yml, pre-commit-config.yaml
pre-commit >= 4.0.1
ruff >= 0.7.0 # also: pre-commit-config.yaml

# used for development (typing)
mypy >= 1.11.2
Expand Down
51 changes: 25 additions & 26 deletions src/ramses_tx/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -643,30 +643,6 @@ def _parse_hvac_temp(param_name: str, value: HexStr4) -> Mapping[str, float | No


# 31DA[30:34]
def parse_bypass_position(value: HexStr2) -> PayDictT.BYPASS_POSITION:
"""Return the bypass position (%), usually fully open or closed (0%, no bypass).
The sensor value is None if there is no sensor present (is not an error).
The dict does not include the key if there is a sensor fault.
"""

# TODO: remove this...
if not isinstance(value, str) or len(value) != 2:
raise ValueError(f"Invalid value: {value}, is not a 2-char hex string")

if value == "EF": # Not implemented
return {SZ_BYPASS_POSITION: None}

if int(value[:2], 16) & 0xF0 == 0xF0:
return _faulted_device(SZ_BYPASS_POSITION, value) # type: ignore[return-value]

bypass_pos = int(value, 16) / 200 # was: hex_to_percent(value)
assert bypass_pos <= 1.0, value

return {SZ_BYPASS_POSITION: bypass_pos}


# 31DA[34:36]
def parse_capabilities(value: HexStr4) -> PayDictT.CAPABILITIES:
"""Return the speed capabilities (a bitmask).
Expand Down Expand Up @@ -709,6 +685,30 @@ def parse_capabilities(value: HexStr4) -> PayDictT.CAPABILITIES:
}


# 31DA[34:36]
def parse_bypass_position(value: HexStr2) -> PayDictT.BYPASS_POSITION:
"""Return the bypass position (%), usually fully open or closed (0%, no bypass).
The sensor value is None if there is no sensor present (is not an error).
The dict does not include the key if there is a sensor fault.
"""

# TODO: remove this...
if not isinstance(value, str) or len(value) != 2:
raise ValueError(f"Invalid value: {value}, is not a 2-char hex string")

if value == "EF": # Not implemented
return {SZ_BYPASS_POSITION: None}

if int(value[:2], 16) & 0xF0 == 0xF0:
return _faulted_device(SZ_BYPASS_POSITION, value) # type: ignore[return-value]

bypass_pos = int(value, 16) / 200 # was: hex_to_percent(value)
assert bypass_pos <= 1.0, value

return {SZ_BYPASS_POSITION: bypass_pos}


# 31DA[36:38] # TODO: WIP (3 more bits), also 22F3?
def parse_fan_info(value: HexStr2) -> PayDictT.FAN_INFO:
"""Return the fan info (current speed, and...).
Expand All @@ -724,7 +724,6 @@ def parse_fan_info(value: HexStr2) -> PayDictT.FAN_INFO:
# if value == "EF": # TODO: Not implemented???
# return {SZ_FAN_INFO: None}

assert int(value, 16) & 0x1F <= 0x19, f"invalid fan_info: {int(value, 16) & 0x1F}"
assert int(value, 16) & 0xE0 in (
0x00,
0x20,
Expand Down Expand Up @@ -840,7 +839,7 @@ def parse_supply_flow(value: HexStr4) -> PayDictT.SUPPLY_FLOW:

# 31DA[54:58]
def parse_exhaust_flow(value: HexStr4) -> PayDictT.EXHAUST_FLOW:
"""Return the exhuast flow rate in m^3/hr (Orcon) ?or L/sec (?Itho)"""
"""Return the exhaust flow rate in m^3/hr (Orcon) ?or L/sec (?Itho)"""
return _parse_fan_flow(SZ_EXHAUST_FLOW, value) # type: ignore[return-value]


Expand Down
113 changes: 50 additions & 63 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 All @@ -64,7 +65,6 @@
SZ_OFFER,
SZ_OPENWINDOW_FUNCTION,
SZ_PAYLOAD,
SZ_PERCENTAGE,
SZ_PHASE,
SZ_PRESSURE,
SZ_RELAY_DEMAND,
Expand Down Expand Up @@ -256,22 +256,13 @@ def parser_0001(payload: str, msg: Message) -> Mapping[str, bool | str | None]:

# outdoor_sensor (outdoor_weather / outdoor_temperature)
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

# is it CODE_IDX_COMPLEX:
# - 02...... for outside temp?
# - 03...... for other stuff?

if msg.src.type == DEV_TYPE_MAP.HCW: # payload[2:] == DEV_TYPE_MAP.HCW, DEX
assert payload == "03020105"
return {"_unknown": payload}
if payload[6:] == "02": # or: msg.src.type == DEV_TYPE_MAP.OUT:
return {
SZ_TEMPERATURE: hex_to_temp(payload[2:6]),
"_unknown": payload[6:],
}

# if payload[6:] == "02": # msg.src.type == DEV_TYPE_MAP.OUT:
return {
SZ_TEMPERATURE: hex_to_temp(payload[2:6]),
"_unknown": payload[6:],
}
return {"_payload": payload}


# zone_name
Expand Down Expand Up @@ -812,16 +803,6 @@ def parser_0418(payload: str, msg: Message) -> PayDictT._0418 | PayDictT._0418_N

# unknown_042f, from STA, VMS
def parser_042f(payload: str, msg: Message) -> dict[str, Any]:
# .I --- 34:064023 --:------ 34:064023 042F 008 00-0000-0023-0023-F5
# .I --- 34:064023 --:------ 34:064023 042F 008 00-0000-0024-0024-F5
# .I --- 34:064023 --:------ 34:064023 042F 008 00-0000-0025-0025-F5
# .I --- 34:064023 --:------ 34:064023 042F 008 00-0000-0026-0026-F5
# .I --- 34:092243 --:------ 34:092243 042F 008 00-0001-0021-0022-01
# .I --- 34:011469 --:------ 34:011469 042F 008 00-0001-0003-0004-BC

# .I --- 32:168090 --:------ 32:168090 042F 009 00-0000100F00105050
# .I --- 32:166025 --:------ 32:166025 042F 009 00-050E0B0C00111470

return {
"counter_1": f"0x{payload[2:6]}",
"counter_3": f"0x{payload[6:10]}",
Expand Down Expand Up @@ -984,14 +965,17 @@ def parser_10d0(payload: str, msg: Message) -> dict[str, Any]:
result: dict[str, bool | float | None]

if msg.verb == W_:
result = {"reset_counter": payload[2:4] == "FF"}
else:
result = {"days_remaining": int(payload[2:4], 16)}
return {"reset_counter": payload[2:4] != "00"}

if msg.len >= 3:
result.update({"days_lifetime": int(payload[4:6], 16)})
if msg.len >= 4:
result.update({"percent_remaining": hex_to_percent(payload[6:8])})
result = {}

if payload[2:4] not in ("FF", "FE"):
result["days_remaining"] = int(payload[2:4], 16)

if payload[4:6] not in ("FF", "FE"):
result["days_lifetime"] = int(payload[4:6], 16)

result["percent_remaining"] = hex_to_percent(payload[6:8])

return result

Expand Down Expand Up @@ -1652,20 +1636,32 @@ def parser_22f3(payload: str, msg: Message) -> dict[str, Any]:

# WIP: unknown, 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
# RP --- 32:155617 18:005904 --:------ 22F4 013 00-40-B0-00000000000000-200000
# HACK: for dev/test: 37:153226 is ClimaRad Ventura fan/remote
payload = payload[8:14] if msg.src.id == "37:153226" else payload[:6]

# RP --- 32:137185 18:003599 --:------ 22F4 013 00-60-E4-00000000000000-200000
# RP --- 32:137185 18:003599 --:------ 22F4 013 00-60-E5-00000000000000-200000
# RP --- 32:137185 18:003599 --:------ 22F4 013 00-60-E6-00000000000000-200000
MODE_LOOKUP = {
0x00: "off?",
0x20: "paused",
0x40: "auto",
0x60: "manual",
}
mode = int(payload[2:4], 16) & 0x60
assert mode in MODE_LOOKUP, mode

assert payload[:2] == "00"
assert payload[6:] == "00000000000000200000"
RATE_LOOKUP = {
0x00: "speed 0", # "off"?,
0x01: "speed 1", # "low", or trickle?
0x02: "speed 2", # "medium-low", or low?
0x03: "speed 3", # "medium",
0x04: "speed 4", # "medium-high", or high?
0x05: "boost", # "boost", aka purge?
}
rate = int(payload[4:6], 16) & 0x03
assert mode != 0x60 or rate in RATE_LOOKUP, rate

return {
"value_02": payload[2:4],
"value_04": payload[4:6],
SZ_FAN_MODE: MODE_LOOKUP[mode],
SZ_FAN_RATE: RATE_LOOKUP.get(rate),
}


Expand Down Expand Up @@ -2382,28 +2378,21 @@ def parser_3221(payload: str, msg: Message) -> dict[str, Any]:

# WIP: unknown, HVAC
def parser_3222(payload: str, msg: Message) -> dict[str, Any]:
# 06:30:14.322 RP --- 32:155617 18:005904 --:------ 3222 004 00-00-01-00 # start 0, length 1
# 00:09:26.263 RP --- 32:155617 18:005904 --:------ 3222 005 00-00-02-0009 # start 0, length 2
# 02:42:27.090 RP --- 32:155617 18:005904 --:------ 3222 007 00-06-04- 000F100E # start 6, length 4
# 22:06:45.771 RP --- 32:155617 18:005904 --:------ 3222 011 00-02-08- 0009000F000F100E # start 2, length 8
# 13:30:26.792 RP --- 32:155617 18:005904 --:------ 3222 012 00-01-09- 090009000F000F100E # start 1, length 9
# 06:29:40.767 RP --- 32:155617 18:005904 --:------ 3222 013 00-00-0A-00090009000F000F100E # start 0, length 10

# 14:40:16.038 RP --- 32:137185 18:003599 --:------ 3222 013 00-00-0A-00000000000000000000 # start 0, length 10
# 14:44:13.214 RP --- 32:137185 18:003599 --:------ 3222 003 00-60-00 # {'percentage': 0.48}
# 16:37:39.346 RP --- 32:137185 18:003599 --:------ 3222 007 00-06-04- 00000000 # start 6, length 4
# 23:08:02.670 RP --- 32:137185 18:003599 --:------ 3222 003 00-EF-00 # {'percentage': None}

assert payload[:2] == "00"

# e.g. RP|3222|00FE00 (payload = 3 bytes)
if msg.len == 3:
assert payload[4:] == "00" # length 0?
return {SZ_PERCENTAGE: hex_to_percent(payload[2:4])}
assert payload[4:] == "00" # aka length 0

return {
"_value": f"0x{payload[2:4]}",
}

# e.g. RP|3222|000604000F100E (payload > 3 bytes)
return {
"start": payload[2:4],
"length": payload[4:6],
"data": f"{'..' * int(payload[2:4])}{payload[6:]}",
"offset": f"0x{payload[2:4]}", # bytes
"length": f"0x{payload[4:6]}", # bytes
"_data": f"{'..' * int(payload[2:4])}{payload[6:]}",
}


Expand Down Expand Up @@ -2465,9 +2454,7 @@ def complex_idx(payload: str, msg: Message) -> dict: # has complex idx


# actuator_state
def parser_3ef0(
payload: str, msg: Message
) -> PayDictT._3EF0_3 | PayDictT._3EF0_6 | PayDictT._3EF0_9 | PayDictT._JASPER:
def parser_3ef0(payload: str, msg: Message) -> PayDictT._3EF0 | PayDictT._JASPER:
result: dict[str, Any]

if msg.src.type == DEV_TYPE_MAP.JIM: # Honeywell Jasper
Expand Down
12 changes: 9 additions & 3 deletions src/ramses_tx/ramses.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@
W_: r"^(0[0-9A-F]|FC|FF)000005(01|05)$",
}, # TODO: there appears to be a dodgy? RQ/RP for UFC
Code._0002: { # WIP: outdoor_sensor - CODE_IDX_COMPLEX?
# is it CODE_IDX_COMPLEX:
# - 02...... for outside temp?
# - 03...... for other stuff?
SZ_NAME: "outdoor_sensor",
I_: r"^0[0-4][0-9A-F]{4}(00|01|02|05)$", # Domoticz sends ^02!!
RQ: r"^00$", # NOTE: sent by an RFG100
Expand Down Expand Up @@ -212,7 +215,7 @@
},
Code._10D0: { # filter_change - polling interval should be 1/day
SZ_NAME: "filter_change",
I_: r"^00[0-9A-F]{6}(0000)?$",
I_: r"^00[0-9A-F]{6}(0000|FFFF)?$",
RQ: r"^00(00)?$",
W_: r"^00FF$",
},
Expand Down Expand Up @@ -273,6 +276,7 @@
Code._1298: { # co2_level
SZ_NAME: "co2_level",
I_: r"^00[0-9A-F]{4}$",
RQ: r"^00$",
},
Code._12A0: { # indoor_humidity
# .I --- 32:168090 --:------ 32:168090 12A0 006 0030093504A8
Expand Down Expand Up @@ -418,10 +422,10 @@
SZ_NAME: "fan_boost",
I_: r"^(00|63)[0-9A-F]{4}([0-9A-F]{8})?$",
}, # minutes only?
Code._22F4: { # unknown_22f4, HVAC, NB: no I
Code._22F4: { # unknown_22f4, HVAC
SZ_NAME: "unknown_22f4",
I_: r"^00[0-9A-F]{24}$",
RQ: r"^00$",
RP: r"^00[0-9A-F]{24}$",
},
Code._22F7: { # fan_bypass_mode (% open), HVAC
SZ_NAME: "fan_bypass_mode",
Expand Down Expand Up @@ -1062,6 +1066,7 @@
Code._1FC9: {W_: {}},
Code._22F1: {},
Code._22F3: {},
Code._22F4: {I_: {}},
Code._22F7: {I_: {}, RP: {}},
Code._2411: {I_: {}, RP: {}},
Code._3120: {I_: {}},
Expand Down Expand Up @@ -1094,6 +1099,7 @@
Code._0001: {RQ: {}}, # from a VMI (only?)
Code._042F: {I_: {}}, # from a VMI (only?)
Code._1060: {I_: {}},
Code._10D0: {W_: {}}, # reset filter count from REM
Code._10E0: {I_: {}, RQ: {}}, # RQ from a VMI (only?)
Code._1470: {RQ: {}}, # from a VMI (only?)
Code._1FC9: {I_: {}},
Expand Down
Loading

0 comments on commit 2e8671a

Please sign in to comment.