Coverage for tests/tests/test_apis_hvac.py: 0%

1#!/usr/bin/env python3

2"""RAMSES RF - Test the Command.put_*, Command.set_* APIs."""

3

4 from collections.abc import Callable

5 from datetime import datetime as dt

6 from typing import Any

7

8 from ramses_tx.command import CODE_API_MAP, Command

9 from ramses_tx.message import Message

10 from ramses_tx.packet import Packet

11

12

13 def _test_api(api: Callable, packets: dict[str]) -> None: # NOTE: incl. addr_set check

14 """Test a verb|code pair that has a Command constructor, src and dst."""

15

16 for pkt_line, kwargs in packets.items():

17 pkt = _create_pkt_from_frame(pkt_line)

18

19 msg = Message(pkt)

20

21 _test_api_from_kwargs(api, pkt, **kwargs)

22 _test_api_from_msg(api, msg)

23

24

25 def _test_api_one(

26 api: Callable, packets: dict[str]

27) -> None: # NOTE: incl. addr_set check

28 """Test a verb|code pair that has a Command constructor and src, but no dst."""

29

30 for pkt_line, kwargs in packets.items():

31 pkt = _create_pkt_from_frame(pkt_line)

32

33 msg = Message(pkt)

34

35 _test_api_one_from_kwargs(api, pkt, **kwargs)

36 _test_api_one_from_msg(api, msg)

37

38

39 def _create_pkt_from_frame(pkt_line: str) -> Packet:

40 """Create a pkt from a pkt_line and assert their frames match."""

41

42 pkt = Packet.from_port(dt.now(), pkt_line)

43 assert str(pkt) == pkt_line[4:]

44 return pkt

45

46

47 def _test_api_from_msg(api: Callable, msg: Message) -> Command:

48 """Create a cmd from a msg with a src_id, and assert they're equal

49 (*also* asserts payload)."""

50

51 cmd: Command = api(

52 msg.dst.id,

53 src_id=msg.src.id,

54 **{k: v for k, v in msg.payload.items() if k[:1] != "_"},

55 )

56

57 assert cmd == msg._pkt # must have exact same addr set

58

59 return cmd

60

61

62 def _test_api_one_from_msg(api: Callable, msg: Message) -> Command:

63 """Create a cmd from a msg and assert they're equal (*also* asserts payload)."""

64

65 cmd: Command = api(

66 msg.dst.id,

67 **{k: v for k, v in msg.payload.items()}, # if k[:1] != "_"},

68 # requirement turned off as it skips required item like _unknown_fan_info_flags

69 )

70

71 assert cmd == msg._pkt # must have exact same addr set

72

73 return cmd

74

75

76 def _test_api_from_kwargs(api: Callable, pkt: Packet, **kwargs: Any) -> None:

77 """

78 Test comparing a created packet to an expected result.

79

80 :param api: Command lookup by Verb|Code

81 :param pkt: expected result to match

82 :param kwargs: arguments for the Command

83 """

84 cmd = api(HRU, src_id=REM, **kwargs)

85

86 assert str(cmd) == str(pkt)

87

88

89 def _test_api_one_from_kwargs(api: Callable, pkt: Packet, **kwargs: Any) -> None:

90 cmd = api(HRU, **kwargs)

91

92 assert str(cmd) == str(pkt)

93

94

95 def test_set() -> None:

96 for test_pkts in (SET_22F1_KWARGS, SET_22F7_KWARGS):

97 pkt = list(test_pkts)[0]

98 api = CODE_API_MAP[f"{pkt[4:6]}|{pkt[41:45]}"]

99 _test_api(api, test_pkts)

100

101

102 HRU = "32:155617" # also used as a FAN

103 REM = "37:171871"

104 NUL = "--:------"

105

106 SET_22F1_KWARGS = {

107 f"000 I --- {REM} {HRU} {NUL} 22F1 002 0000": {"fan_mode": None},

108 #

109 f"001 I --- {REM} {HRU} {NUL} 22F1 002 0000": {"fan_mode": 0},

110 f"001 I --- {REM} {HRU} {NUL} 22F1 002 0001": {"fan_mode": 1},

111 f"001 I --- {REM} {HRU} {NUL} 22F1 002 0002": {"fan_mode": 2},

112 f"001 I --- {REM} {HRU} {NUL} 22F1 002 0003": {"fan_mode": 3},

113 f"001 I --- {REM} {HRU} {NUL} 22F1 002 0004": {"fan_mode": 4},

114 f"001 I --- {REM} {HRU} {NUL} 22F1 002 0005": {"fan_mode": 5},

115 f"001 I --- {REM} {HRU} {NUL} 22F1 002 0006": {"fan_mode": 6},

116 f"001 I --- {REM} {HRU} {NUL} 22F1 002 0007": {"fan_mode": 7},

117 #

118 f"002 I --- {REM} {HRU} {NUL} 22F1 002 0000": {"fan_mode": "00"},

119 f"002 I --- {REM} {HRU} {NUL} 22F1 002 0001": {"fan_mode": "01"},

120 f"002 I --- {REM} {HRU} {NUL} 22F1 002 0002": {"fan_mode": "02"},

121 f"002 I --- {REM} {HRU} {NUL} 22F1 002 0003": {"fan_mode": "03"},

122 f"002 I --- {REM} {HRU} {NUL} 22F1 002 0004": {"fan_mode": "04"},

123 f"002 I --- {REM} {HRU} {NUL} 22F1 002 0005": {"fan_mode": "05"},

124 f"002 I --- {REM} {HRU} {NUL} 22F1 002 0006": {"fan_mode": "06"},

125 f"002 I --- {REM} {HRU} {NUL} 22F1 002 0007": {"fan_mode": "07"},

126 #

127 f"003 I --- {REM} {HRU} {NUL} 22F1 002 0000": {"fan_mode": "away"},

128 f"003 I --- {REM} {HRU} {NUL} 22F1 002 0001": {"fan_mode": "low"},

129 f"003 I --- {REM} {HRU} {NUL} 22F1 002 0002": {"fan_mode": "medium"},

130 f"003 I --- {REM} {HRU} {NUL} 22F1 002 0003": {"fan_mode": "high"},

131 f"003 I --- {REM} {HRU} {NUL} 22F1 002 0004": {"fan_mode": "auto"},

132 f"003 I --- {REM} {HRU} {NUL} 22F1 002 0005": {"fan_mode": "auto_alt"},

133 f"003 I --- {REM} {HRU} {NUL} 22F1 002 0006": {"fan_mode": "boost"},

134 f"003 I --- {REM} {HRU} {NUL} 22F1 002 0007": {"fan_mode": "off"},

135}

136

137

138 SET_22F7_KWARGS = {

139 f"000 W --- {REM} {HRU} {NUL} 22F7 002 00FF": {}, # shouldn't be OK

140 #

141 f"001 W --- {REM} {HRU} {NUL} 22F7 002 00FF": {

142 "bypass_position": None

143 }, # is auto?

144 f"001 W --- {REM} {HRU} {NUL} 22F7 002 0000": {"bypass_position": 0.0},

145 # 001 W --- {REM} {HRU} {NUL} 22F7 002 0064": {"bypass_position": 0.5},

146 f"001 W --- {REM} {HRU} {NUL} 22F7 002 00C8": {"bypass_position": 1.0},

147 f"002 W --- {REM} {HRU} {NUL} 22F7 002 00FF": {

148 "bypass_mode": "auto"

149 }, # is auto, or None?

150 f"002 W --- {REM} {HRU} {NUL} 22F7 002 0000": {"bypass_mode": "off"},

151 f"002 W --- {REM} {HRU} {NUL} 22F7 002 00C8": {"bypass_mode": "on"},

152}

153

154

155# new tests

156 def test_get() -> None:

157 for test_pkts in (GET_12A0_KWARGS, GET_1298_KWARGS, GET_31DA_KWARGS):

158 pkt = list(test_pkts)[0]

159 api = CODE_API_MAP[f"{pkt[4:6]}|{pkt[41:45]}"]

160 _test_api_one(api, test_pkts)

161

162

163 GET_12A0_KWARGS = {

164 f"000 I --- {HRU} {NUL} {HRU} 12A0 002 00EF": {

165 "indoor_humidity": None

166 }, # shouldn't be OK

167 #

168 f"082 I --- {HRU} {NUL} {HRU} 12A0 002 0037": {"indoor_humidity": 0.55},

169}

170

171 GET_1298_KWARGS = {

172 f"064 I --- {HRU} {NUL} {HRU} 1298 003 000322": {"co2_level": 802},

173}

174

175 GET_31DA_KWARGS = {

176 # this is a composite payload, containing many keys

177 # 31DA packet from values in ramses_tx/command.py#get_hvac_fan_31da

178 f"... I --- {HRU} {NUL} {HRU} 31DA 029 00EF007FFF343308980898088A0882F800001514140000EFEF05F50613": {

179 "hvac_id": "00",

180 "bypass_position": 0.000,

181 "air_quality": None,

182 "co2_level": None,

183 "indoor_humidity": 0.52,

184 "outdoor_humidity": 0.51,

185 "exhaust_temp": 22.0,

186 "supply_temp": 22.0,

187 "indoor_temp": 21.86,

188 "outdoor_temp": 21.78,

189 "speed_capabilities": ["off", "low_med_high", "timer", "boost", "auto"],

190 "fan_info": "away",

191 "_unknown_fan_info_flags": [0, 0, 0],

192 "exhaust_fan_speed": 0.1,

193 "supply_fan_speed": 0.1,

194 "remaining_mins": 0,

195 "post_heat": None,

196 "pre_heat": None,

197 "supply_flow": 15.25,

198 "exhaust_flow": 15.55,

199 },

200 f"... I --- {HRU} {NUL} {HRU} 31DA 029 00C84004B2EFEF7FFF7FFF7FFF7FFFF808EF831F000000EFEF7FFF7FFF": {

201 "hvac_id": "00",

202 "co2_level": 1202,

203 "air_quality": 1.0,

204 "air_quality_basis": "rel_humidity",

205 "indoor_humidity": None,

206 "outdoor_humidity": None,

207 "exhaust_temp": None,

208 "supply_temp": None,

209 "indoor_temp": None,

210 "outdoor_temp": None,

211 "speed_capabilities": [

212 "off",

213 "low_med_high",

214 "timer",

215 "boost",

216 "auto",

217 "auto_night",

218 ],

219 "bypass_position": None,

220 "fan_info": "speed 3, high",

221 "_unknown_fan_info_flags": [1, 0, 0],

222 "exhaust_fan_speed": 0.155,

223 "supply_fan_speed": 0.0,

224 "remaining_mins": 0,

225 "post_heat": None,

226 "pre_heat": None,

227 "supply_flow": None,

228 "exhaust_flow": None,

229 },

230 f"... I --- {HRU} {NUL} {HRU} 31DA 029 00EF007FFF30EF7FFF7FFF7FFF7FFFF808EF83C7000000EFEF7FFF7FFF": {

231 "hvac_id": "00",

232 "speed_capabilities": [

233 "off",

234 "low_med_high",

235 "timer",

236 "boost",

237 "auto",

238 "auto_night",

239 ],

240 "fan_info": "speed 3, high",

241 "_unknown_fan_info_flags": [1, 0, 0],

242 "air_quality": None,

243 "co2_level": None,

244 "indoor_humidity": 0.48,

245 "outdoor_humidity": None,

246 "exhaust_temp": None,

247 "supply_temp": None,

248 "indoor_temp": None,

249 "outdoor_temp": None,

250 "bypass_position": None,

251 "exhaust_fan_speed": 0.995,

252 "supply_fan_speed": 0.0,

253 "remaining_mins": 0,

254 "post_heat": None,

255 "pre_heat": None,

256 "supply_flow": None,

257 "exhaust_flow": None,

258 },

259 f"... I --- {HRU} {NUL} {HRU} 31DA 029 00EF007FFFEFEF7FFF7FFF7FFF7FFFF000EF0162000000EFEF7FFF7FFF": {

260 "hvac_id": "00",

261 "outdoor_humidity": None,

262 "outdoor_temp": None,

263 "air_quality": None,

264 "co2_level": None,

265 "indoor_humidity": None,

266 "exhaust_temp": None,

267 "supply_temp": None,

268 "indoor_temp": None,

269 "speed_capabilities": ["off", "low_med_high", "timer", "boost"],

270 "bypass_position": None,

271 "fan_info": "speed 1, low",

272 "_unknown_fan_info_flags": [0, 0, 0],

273 "exhaust_fan_speed": 0.49,

274 "supply_fan_speed": 0.0,

275 "remaining_mins": 0,

276 "post_heat": None,

277 "pre_heat": None,

278 "supply_flow": None,

279 "exhaust_flow": None,

280 },

281 f"... I --- {HRU} {NUL} {HRU} 31DA 029 21EF00020136EF7FFF7FFF7FFF7FFF0002EF18FFFF000000EF7FFF7FFF": {

282 "hvac_id": "21",

283 "speed_capabilities": ["post_heater"],

284 "fan_info": "auto",

285 "_unknown_fan_info_flags": [0, 0, 0],

286 "air_quality": None,

287 "co2_level": 513,

288 "indoor_humidity": 0.54,

289 "outdoor_humidity": None,

290 "exhaust_temp": None,

291 "supply_temp": None,

292 "indoor_temp": None,

293 "outdoor_temp": None,

294 "bypass_position": None,

295 "exhaust_fan_speed": None,

296 "supply_fan_speed": None,

297 "remaining_mins": 0,

298 "post_heat": 0.0,

299 "pre_heat": None,

300 "supply_flow": None,

301 "exhaust_flow": None,

302 },

303 # messages with 30 byte payload

304 f"... I --- {HRU} {NUL} {HRU} 31DA 030 00EF007FFF2CEF7FFF7FFF7FFF7FFFF800EF0128000000EFEF7FFF7FFF00": {

305 "hvac_id": "00",

306 "exhaust_temp": None,

307 "air_quality": None,

308 "co2_level": None,

309 "indoor_humidity": 0.44,

310 "outdoor_humidity": None,

311 "supply_temp": None,

312 "indoor_temp": None,

313 "outdoor_temp": None,

314 "speed_capabilities": ["off", "low_med_high", "timer", "boost", "auto"],

315 "bypass_position": None,

316 "fan_info": "speed 1, low",

317 "_unknown_fan_info_flags": [0, 0, 0],

318 "exhaust_fan_speed": 0.2,

319 "supply_fan_speed": 0.0,

320 "remaining_mins": 0,

321 "post_heat": None,

322 "pre_heat": None,

323 "supply_flow": None,

324 "exhaust_flow": None,

325 "_extra": "00",

326 },

327 f"... I --- {HRU} {NUL} {HRU} 31DA 030 21EF007FFF41EF080607D709480737F002AA0234400000005C7FFF7FFF00": {

328 "hvac_id": "21",

329 "exhaust_fan_speed": 0.26,

330 "supply_fan_speed": 0.32,

331 "supply_flow": None,

332 "exhaust_flow": None,

333 "air_quality": None,

334 "co2_level": None,

335 "indoor_humidity": 0.65,

336 "outdoor_humidity": None,

337 "exhaust_temp": 20.54,

338 "supply_temp": 20.08,

339 "indoor_temp": 23.76,

340 "outdoor_temp": 18.47,

341 "speed_capabilities": ["off", "low_med_high", "timer", "boost", "post_heater"],

342 "bypass_position": 0.85,

343 "fan_info": "speed 2, medium",

344 "_unknown_fan_info_flags": [0, 0, 0],

345 "remaining_mins": 0,

346 "post_heat": 0.0,

347 "pre_heat": 0.46,

348 "_extra": "00",

349 },

350}

351

352

353# TODO Add tests to get states from 31DA

354# (verifies SQLite refactoring)

355# set up HVAC system first from messages

356#

357# Example: current_temperature(self) in ramses_cc.climate.py

358# simulates requesting Climate self._device.indoor_temp from a system