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

130 statements  

« prev     ^ index     » next       coverage.py v7.11.3, created at 2026-01-05 21:46 +0100

1#!/usr/bin/env python3 

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

3 

4from collections.abc import Callable, Iterable 

5from datetime import datetime as dt 

6 

7from ramses_rf.const import SZ_DOMAIN_ID 

8from ramses_rf.helpers import shrink 

9from ramses_tx.address import HGI_DEV_ADDR 

10from ramses_tx.command import Command 

11from ramses_tx.const import SZ_TIMESTAMP 

12from ramses_tx.helpers import parse_fault_log_entry 

13from ramses_tx.message import Message 

14from ramses_tx.packet import Packet 

15 

16 

17# NOTE: not used for 0418 

18def _test_api_good( 

19 api: Callable, packets: Iterable[str] 

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

21 """Test a verb|code pair that has a Command constructor.""" 

22 

23 for pkt_line in packets: 

24 pkt = _create_pkt_from_frame(pkt_line.split("#")[0].rstrip()) 

25 msg = Message(pkt) 

26 

27 cmd = _test_api_from_msg(api, msg) 

28 assert cmd.payload == msg._pkt.payload # aka pkt.payload 

29 

30 if isinstance(packets, dict) and (payload := packets[pkt_line]): 

31 assert shrink(msg.payload, keep_falsys=True) == eval(payload) 

32 

33 

34def _test_api_fail( 

35 api: Callable, packets: Iterable[str] 

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

37 """Test a verb|code pair that has a Command constructor.""" 

38 

39 for pkt_line in packets: 

40 pkt = _create_pkt_from_frame(pkt_line.split("#")[0].rstrip()) 

41 msg = Message(pkt) 

42 

43 try: 

44 cmd = _test_api_from_msg(api, msg) 

45 except (AssertionError, TypeError, ValueError): 

46 cmd = None 

47 else: 

48 assert cmd and cmd.payload == msg._pkt.payload # aka pkt.payload 

49 

50 if isinstance(packets, dict) and (payload := packets[pkt_line]): 

51 assert shrink(msg.payload, keep_falsys=True) == eval(payload) 

52 

53 

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

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

56 

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

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

59 return pkt 

60 

61 

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

63 """Create a cmd from a msg and assert their meta-data (doesn"t assert payload.).""" 

64 

65 cmd: Command = api( 

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

67 ) 

68 

69 if msg.src.id == HGI_DEV_ADDR.id: 

70 assert cmd == msg._pkt # assert str(cmd) == str(pkt) 

71 assert cmd.dst.id == msg._pkt.dst.id 

72 assert cmd.verb == msg._pkt.verb 

73 assert cmd.code == msg._pkt.code 

74 # assert cmd.payload == pkt.payload 

75 

76 return cmd 

77 

78 

79SET_0004_FAIL = ( 

80 "... W --- 18:000730 01:145038 --:------ 0004 022 05000000000000000000000000000000000000000000", # name is None 

81 "... W --- 18:000730 01:145038 --:------ 0004 022 05005468697320497320412056657279204C6F6E6720", # trailing space 

82) 

83SET_0004_GOOD = ( 

84 "... W --- 18:000730 01:145038 --:------ 0004 022 00004D617374657220426564726F6F6D000000000000", 

85 "... W --- 18:000730 01:145038 --:------ 0004 022 05005468697320497320412056657279204C6F6E6767", 

86) 

87 

88 

89def test_set_0004() -> None: 

90 _test_api_good(Command.set_zone_name, SET_0004_GOOD) 

91 _test_api_fail(Command.set_zone_name, SET_0004_FAIL) 

92 

93 

94SET_000A_GOOD = ( 

95 "... W --- 18:000730 01:145038 --:------ 000A 006 010001F40DAC", 

96 "... W --- 18:000730 01:145038 --:------ 000A 006 031001F409C4", 

97 "... W --- 18:000730 01:145038 --:------ 000A 006 050201F40898", 

98) 

99 

100 

101def test_set_000a() -> None: 

102 _test_api_good(Command.set_zone_config, SET_000A_GOOD) 

103 

104 

105GET_0404_GOOD = { 

106 "... RQ --- 18:000730 01:076010 --:------ 0404 007 00230008000100": "{'zone_idx': 'HW', 'frag_number': 1, 'total_frags': None}", 

107 "... RQ --- 18:000730 01:076010 --:------ 0404 007 02200008000100": "{'zone_idx': '02', 'frag_number': 1, 'total_frags': None}", 

108 "... RQ --- 18:000730 01:076010 --:------ 0404 007 02200008000204": "{'zone_idx': '02', 'frag_number': 2, 'total_frags': 4}", 

109 "... RQ --- 18:000730 01:076010 --:------ 0404 007 02200008000304": "{'zone_idx': '02', 'frag_number': 3, 'total_frags': 4}", 

110 "... RQ --- 18:000730 01:076010 --:------ 0404 007 02200008000404": "{'zone_idx': '02', 'frag_number': 4, 'total_frags': 4}", 

111} 

112 

113 

114def test_get_0404() -> None: 

115 _test_api_good(Command.get_schedule_fragment, GET_0404_GOOD) 

116 

117 

118GET_0418_GOOD = { # NOTE: this constructor is used only for testing 

119 "... I --- 01:145038 --:------ 01:145038 0418 022 000000B0000000000000000000007FFFFF7000000000": "{'log_idx': '00', 'log_entry': None}", 

120 "... I --- 01:145038 --:------ 01:145038 0418 022 000000B0060804000000B897A0697FFFFF70001003B6": "{'log_idx': '00', 'log_entry': ('23-11-17T20:03:18', 'fault', 'comms_fault', 'actuator', '08', '04:000950', 'B0', '0000', 'FFFF7000')}", 

121} 

122 

123 

124# NOTE: does not use _test_api_good() as main payload is a tuple, and not a dict 

125def test_put_0418() -> None: 

126 for pkt_line in GET_0418_GOOD: 

127 pkt = _create_pkt_from_frame(pkt_line.split("#")[0].rstrip()) 

128 log_pkt = parse_fault_log_entry(pkt.payload) 

129 

130 if SZ_TIMESTAMP not in log_pkt: # ignore null log entries 

131 continue 

132 

133 cmd = Command._put_system_log_entry(pkt.src.id, **log_pkt) # type: ignore[call-arg] 

134 log_cmd = parse_fault_log_entry(cmd.payload) 

135 

136 assert log_pkt == log_cmd 

137 

138 

139SET_1030_GOOD = { # NOTE: no W|1030 seen in the wild 

140 "... W --- 18:000730 01:145038 --:------ 1030 016 01C80137C9010FCA0196CB010FCC0101": "{'zone_idx': '01', 'max_flow_setpoint': 55, 'min_flow_setpoint': 15, 'valve_run_time': 150, 'pump_run_time': 15, 'boolean_cc': 1}", 

141} 

142 

143 

144def test_set_1030() -> None: 

145 _test_api_good(Command.set_mix_valve_params, SET_1030_GOOD) 

146 

147 

148SET_10A0_GOOD = { # NOTE: no W|10A0 seen in the wild 

149 "000 W --- 01:123456 07:031785 --:------ 10A0 006 000F6E050064": "{'dhw_idx': '00', 'setpoint': 39.5, 'overrun': 5, 'differential': 1.0}", 

150 "000 W --- 01:123456 07:031785 --:------ 10A0 006 000F6E0003E8": "{'dhw_idx': '00', 'setpoint': 39.5, 'overrun': 0, 'differential': 10.0}", 

151 "000 W --- 01:123456 07:031785 --:------ 10A0 006 0015180301F4": "{'dhw_idx': '00', 'setpoint': 54.0, 'overrun': 3, 'differential': 5.0}", 

152 "000 W --- 01:123456 07:031785 --:------ 10A0 006 0013240003E8": "{'dhw_idx': '00', 'setpoint': 49.0, 'overrun': 0, 'differential': 10.0}", 

153 # 

154 "001 W --- 01:123456 07:031785 --:------ 10A0 006 010F6E050064": "{'dhw_idx': '01', 'setpoint': 39.5, 'overrun': 5, 'differential': 1.0}", 

155 "001 W --- 01:123456 07:031785 --:------ 10A0 006 010F6E0003E8": "{'dhw_idx': '01', 'setpoint': 39.5, 'overrun': 0, 'differential': 10.0}", 

156 "001 W --- 01:123456 07:031785 --:------ 10A0 006 0115180301F4": "{'dhw_idx': '01', 'setpoint': 54.0, 'overrun': 3, 'differential': 5.0}", 

157 "001 W --- 01:123456 07:031785 --:------ 10A0 006 0113240003E8": "{'dhw_idx': '01', 'setpoint': 49.0, 'overrun': 0, 'differential': 10.0}", 

158} 

159 

160 

161def test_set_10a0() -> None: 

162 _test_api_good(Command.set_dhw_params, SET_10A0_GOOD) 

163 

164 

165SET_1100_FAIL = ( 

166 "... W --- 01:145038 13:163733 --:------ 1100 008 000C1400007FFF01", # no domain_id 

167) 

168SET_1100_GOOD = { 

169 "... W --- 01:145038 13:035462 --:------ 1100 008 00240414007FFF01": "{'domain_id': '00', 'cycle_rate': 9, 'min_on_time': 1.0, 'min_off_time': 5.0, 'proportional_band_width': None}", 

170 "... W --- 01:145038 13:163733 --:------ 1100 008 000C14000000C801": "{'domain_id': '00', 'cycle_rate': 3, 'min_on_time': 5.0, 'min_off_time': 0.0, 'proportional_band_width': 2.0}", 

171 "... W --- 01:145038 13:163733 --:------ 1100 008 00180400007FFF01": "{'domain_id': '00', 'cycle_rate': 6, 'min_on_time': 1.0, 'min_off_time': 0.0, 'proportional_band_width': None}", 

172 "... W --- 01:145038 13:035462 --:------ 1100 008 FC042814007FFF01": "{'domain_id': 'FC', 'cycle_rate': 1, 'min_on_time': 10.0, 'min_off_time': 5.0, 'proportional_band_width': None}", 

173 "... W --- 01:145038 13:035462 --:------ 1100 008 FC082814007FFF01": "{'domain_id': 'FC', 'cycle_rate': 2, 'min_on_time': 10.0, 'min_off_time': 5.0, 'proportional_band_width': None}", 

174 "... W --- 01:145038 13:035462 --:------ 1100 008 FC243C14007FFF01": "{'domain_id': 'FC', 'cycle_rate': 9, 'min_on_time': 15.0, 'min_off_time': 5.0, 'proportional_band_width': None}", 

175 "... W --- 01:145038 13:035462 --:------ 1100 008 FC240414007FFF01": "{'domain_id': 'FC', 'cycle_rate': 9, 'min_on_time': 1.0, 'min_off_time': 5.0, 'proportional_band_width': None}", 

176 "... W --- 01:145038 13:035462 --:------ 1100 008 FC240428007FFF01": "{'domain_id': 'FC', 'cycle_rate': 9, 'min_on_time': 1.0, 'min_off_time': 10.0, 'proportional_band_width': None}", 

177 "... W --- 01:145038 13:035462 --:------ 1100 008 FC083C14007FFF01": "{'domain_id': 'FC', 'cycle_rate': 2, 'min_on_time': 15.0, 'min_off_time': 5.0, 'proportional_band_width': None}", 

178 "... W --- 01:145038 13:035462 --:------ 1100 008 FC083C00007FFF01": "{'domain_id': 'FC', 'cycle_rate': 2, 'min_on_time': 15.0, 'min_off_time': 0.0, 'proportional_band_width': None}", 

179} 

180 

181 

182def test_set_1100() -> None: # NOTE: bespoke: see params 

183 packets = SET_1100_GOOD 

184 

185 for pkt_line in packets: 

186 pkt = _create_pkt_from_frame(pkt_line) 

187 msg = Message(pkt) 

188 

189 msg.payload[SZ_DOMAIN_ID] = msg.payload.get(SZ_DOMAIN_ID, "00") 

190 

191 cmd = _test_api_from_msg(Command.set_tpi_params, msg) 

192 assert cmd.payload == msg._pkt.payload 

193 

194 if isinstance(packets, dict) and (payload := packets[pkt_line]): 

195 assert shrink(msg.payload, keep_falsys=True) == eval(payload) 

196 

197 

198PUT_1260_GOOD = { # TODO: RPs being converted to Is 

199 "... I --- 07:017494 --:------ 07:017494 1260 003 00111E": "{'temperature': 43.82}", 

200 "... I --- 07:017494 --:------ 07:017494 1260 003 007FFF": "{'temperature': None}", 

201 # "... I --- 07:123456 --:------ 07:123456 1260 003 010E74": "{'temperature': 37.0, 'dhw_idx': '01'}", # contrived 

202 # "... I --- 07:123456 --:------ 07:123456 1260 003 017FFF": "{'temperature': None}", # contrived 

203 # "... RP --- 01:123456 18:123456 --:------ 1260 003 00116A": "{'temperature': 44.58}", 

204 # "... RP --- 01:078710 18:002563 --:------ 1260 003 00116A": "{'temperature': 44.58, 'dhw_idx': '00'}", 

205 # "... RP --- 01:078710 18:002563 --:------ 1260 003 01116A": "{'temperature': 44.58, 'dhw_idx': '01'}", # contrived 

206 # "... RP --- 10:124973 18:132629 --:------ 1260 003 000E74": "{'temperature': 37.0}", 

207} 

208 

209 

210def test_set_1260() -> None: 

211 _test_api_good(Command.put_dhw_temp, PUT_1260_GOOD) 

212 

213 

214SET_1F41_GOOD = { 

215 # 00 W --- 18:000730 01:050858 --:------ 1F41 006 000000FFFFFF ": "{'dhw_idx': '00', 'mode': 'follow_schedule'}", 

216 # 00 W --- 18:000730 01:050858 --:------ 1F41 006 000100FFFFFF ": "{'dhw_idx': '00', 'mode': 'follow_schedule'}", 

217 "000 W --- 18:000730 01:050858 --:------ 1F41 006 00FF00FFFFFF ": "{'dhw_idx': '00', 'mode': 'follow_schedule'}", 

218 "000 W --- 18:000730 01:050858 --:------ 1F41 006 000102FFFFFF ": "{'dhw_idx': '00', 'mode': 'permanent_override', 'active': 1}", 

219 "000 W --- 18:000730 01:050858 --:------ 1F41 012 000004FFFFFF0509160607E5": "{'dhw_idx': '00', 'mode': 'temporary_override', 'active': 0, 'until': '2021-06-22T09:05:00'}", 

220 "000 W --- 18:000730 01:050858 --:------ 1F41 012 000104FFFFFF2F0E0D0B07E5": "{'dhw_idx': '00', 'mode': 'temporary_override', 'active': 1, 'until': '2021-11-13T14:47:00'}", 

221 # 

222 # 01 W --- 18:000730 01:050858 --:------ 1F41 006 010000FFFFFF ": "{'dhw_idx': '01', 'mode': 'follow_schedule'}", 

223 # 01 W --- 18:000730 01:050858 --:------ 1F41 006 010100FFFFFF ": "{'dhw_idx': '01', 'mode': 'follow_schedule'}", 

224 "001 W --- 18:000730 01:050858 --:------ 1F41 006 01FF00FFFFFF ": "{'dhw_idx': '01', 'mode': 'follow_schedule'}", 

225 "001 W --- 18:000730 01:050858 --:------ 1F41 006 010102FFFFFF ": "{'dhw_idx': '01', 'mode': 'permanent_override', 'active': 1}", 

226 "001 W --- 18:000730 01:050858 --:------ 1F41 012 010004FFFFFF0509160607E5": "{'dhw_idx': '01', 'mode': 'temporary_override', 'active': 0, 'until': '2021-06-22T09:05:00'}", 

227 "001 W --- 18:000730 01:050858 --:------ 1F41 012 010104FFFFFF2F0E0D0B07E5": "{'dhw_idx': '01', 'mode': 'temporary_override', 'active': 1, 'until': '2021-11-13T14:47:00'}", 

228} # TODO: add other modes 

229SET_1F41_FAIL = ( 

230 "000 W --- 18:000730 01:050858 --:------ 1F41 006 020000FFFFFF", # dhw_idx = 02 

231 "000 W --- 18:000730 01:050858 --:------ 1F41 006 000005FFFFFF", # zone_mode = 05 

232 "000 W --- 18:000730 01:050858 --:------ 1F41 006 000005FFFFFF", # zone_mode = 05 

233) 

234 

235 

236def test_set_1f41() -> None: 

237 _test_api_good(Command.set_dhw_mode, SET_1F41_GOOD) 

238 

239 

240SET_2309_FAIL = ( 

241 "... W --- 18:000730 01:145038 --:------ 2309 003 017FFF", # temp is None - should be good? 

242) 

243SET_2309_GOOD = ( 

244 "... W --- 18:000730 01:145038 --:------ 2309 003 00047E", 

245 "... W --- 18:000730 01:145038 --:------ 2309 003 0101F4", 

246) 

247 

248 

249def test_set_2309() -> None: 

250 _test_api_good(Command.set_zone_setpoint, SET_2309_GOOD) 

251 

252 

253SET_2349_GOOD = ( 

254 "... W --- 18:005567 01:223036 --:------ 2349 007 037FFF00FFFFFF", 

255 "... W --- 22:015492 01:076010 --:------ 2349 007 0101F400FFFFFF", 

256 "... W --- 18:000730 01:145038 --:------ 2349 007 06028A01FFFFFF", 

257 "... W --- 22:081652 01:063844 --:------ 2349 007 0106400300003C", 

258 "... W --- 18:000730 01:050858 --:------ 2349 013 06096004FFFFFF240A050107E6", 

259 "... W --- 18:000730 01:050858 --:------ 2349 013 02096004FFFFFF1B0D050107E6", 

260) 

261 

262 

263def test_set_2349() -> None: 

264 _test_api_good(Command.set_zone_mode, SET_2349_GOOD) 

265 

266 

267SET_2E04_GOOD = { 

268 "... W --- 30:258720 01:073976 --:------ 2E04 008 00FFFFFFFFFFFF00": "{'system_mode': 'auto'}", 

269 "... W --- 30:258720 01:073976 --:------ 2E04 008 01FFFFFFFFFFFF00": "{'system_mode': 'heat_off'}", 

270 "... W --- 30:258720 01:073976 --:------ 2E04 008 06FFFFFFFFFFFF00": "{'system_mode': 'auto_with_reset'}", 

271 # 

272 "... W --- 30:258720 01:073976 --:------ 2E04 008 03FFFFFFFFFFFF00": "{'system_mode': 'away', 'until': None}", 

273 "... W --- 30:258720 01:073976 --:------ 2E04 008 0300001D0A07E301": "{'system_mode': 'away', 'until': '2019-10-29T00:00:00'}", 

274 "... W --- 30:258720 01:073976 --:------ 2E04 008 07FFFFFFFFFFFF00": "{'system_mode': 'custom', 'until': None}", 

275 "... W --- 30:258720 01:073976 --:------ 2E04 008 0700001D0A07E301": "{'system_mode': 'custom', 'until': '2019-10-29T00:00:00'}", 

276 "... W --- 30:258720 01:073976 --:------ 2E04 008 02FFFFFFFFFFFF00": "{'system_mode': 'eco_boost', 'until': None}", 

277 "... W --- 30:258720 01:073976 --:------ 2E04 008 020B011A0607E401": "{'system_mode': 'eco_boost', 'until': '2020-06-26T01:11:00'}", 

278 "... W --- 30:258720 01:073976 --:------ 2E04 008 04FFFFFFFFFFFF00": "{'system_mode': 'day_off', 'until': None}", 

279 "... W --- 30:258720 01:073976 --:------ 2E04 008 0400001D0A07E301": "{'system_mode': 'day_off', 'until': '2019-10-29T00:00:00'}", 

280 "... W --- 30:258720 01:073976 --:------ 2E04 008 05FFFFFFFFFFFF00": "{'system_mode': 'day_off_eco', 'until': None}", 

281 "... W --- 30:258720 01:073976 --:------ 2E04 008 0500001D0A07E301": "{'system_mode': 'day_off_eco', 'until': '2019-10-29T00:00:00'}", 

282 "... W --- 30:258720 01:073976 --:------ 2E04 008 0521011A0607E401": "{'system_mode': 'day_off_eco', 'until': '2020-06-26T01:33:00'}", # a contrived time, usu. 00:00 

283} 

284 

285 

286def test_set_2e04() -> None: 

287 _test_api_good(Command.set_system_mode, SET_2E04_GOOD) 

288 

289 

290PUT_30C9_FAIL = ( 

291 "... I --- 13:074756 --:------ 13:074756 30C9 003 007FFF", 

292 "... I --- 01:197498 --:------ 01:197498 30C9 024 01086D02087003086604070A0508DF06083307083008085C", 

293) 

294PUT_30C9_GOOD = ( 

295 "... I --- 04:068997 --:------ 04:068997 30C9 003 007FFF", 

296 "... I --- 04:068997 --:------ 04:068997 30C9 003 000838", 

297 "... I --- 03:123456 --:------ 03:123456 30C9 003 0007C1", 

298 "... I --- 03:123456 --:------ 03:123456 30C9 003 007FFF", 

299 "... I --- 13:074756 --:------ 03:074756 30C9 003 00086D", 

300) 

301 

302 

303def test_put_30c9() -> None: 

304 _test_api_good(Command.put_sensor_temp, PUT_30C9_GOOD) 

305 

306 

307SET_313F_GOOD = ( 

308 "... W --- 30:258720 01:073976 --:------ 313F 009 006000320C040207E6", 

309 "... W --- 30:258720 01:073976 --:------ 313F 009 0060011E09010707E6", 

310 "... W --- 30:258720 01:073976 --:------ 313F 009 006002210D080C07E5", 

311 "... W --- 30:042165 01:076010 --:------ 313F 009 006003090A0D0207E6", 

312 "... W --- 30:042165 01:076010 --:------ 313F 009 0060041210040207E6", 

313) 

314 

315 

316def test_set_313f() -> None: # NOTE: bespoke: payload 

317 for pkt_line in SET_313F_GOOD: 

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

319 assert str(pkt)[:4] == pkt_line[4:8] 

320 assert str(pkt)[6:] == pkt_line[10:] 

321 

322 msg = Message(pkt) 

323 

324 cmd = _test_api_from_msg(Command.set_system_time, msg) 

325 assert cmd.payload[:4] == msg._pkt.payload[:4] 

326 assert cmd.payload[6:] == msg._pkt.payload[6:] 

327 

328 

329PUT_3EF0_FAIL = ("... I --- 13:123456 --:------ 13:123456 3EF0 003 00AAFF",) 

330PUT_3EF0_GOOD = ( 

331 "... I --- 13:123456 --:------ 13:123456 3EF0 003 0000FF", 

332 "... I --- 13:123456 --:------ 13:123456 3EF0 003 00C8FF", 

333) 

334 

335 

336def test_put_3ef0() -> None: 

337 _test_api_good(Command.put_actuator_state, PUT_3EF0_GOOD) 

338 

339 

340PUT_3EF1_GOOD = ( # TODO: needs checking 

341 "... RP --- 13:123456 01:123456 --:------ 3EF1 007 000126012600FF", 

342 "... RP --- 13:123456 18:123456 --:------ 3EF1 007 007FFF003C0010", # NOTE: should be: RP|10|3EF1 

343) 

344 

345 

346def test_put_3ef1() -> None: # NOTE: bespoke: params, ?payload 

347 for pkt_line in PUT_3EF1_GOOD: 

348 pkt = _create_pkt_from_frame(pkt_line) 

349 msg = Message(pkt) 

350 

351 kwargs = msg.payload 

352 modulation_level = kwargs.pop("modulation_level") 

353 actuator_countdown = kwargs.pop("actuator_countdown") 

354 

355 cmd = Command.put_actuator_cycle( 

356 msg.src.id, 

357 msg.dst.id, 

358 modulation_level, 

359 actuator_countdown, 

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

361 ) 

362 

363 if msg.src.id != HGI_DEV_ADDR.id: 

364 assert cmd.src.id == pkt.src.id 

365 assert cmd.dst.id == pkt.dst.id 

366 assert cmd.verb == pkt.verb 

367 assert cmd.code == pkt.code 

368 

369 assert cmd.payload[:-2] == pkt.payload[:-2]