Coverage for src/ramses_tx/typed_dicts.py: 100%

309 statements  

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

1# We use typed dicts rather than data classes because we migrated from dicts 

2 

3from enum import EnumCheck, StrEnum, verify 

4from typing import Literal, NotRequired, TypeAlias, TypedDict 

5 

6from ramses_tx.const import FaultDeviceClass, FaultState, FaultType 

7from ramses_tx.schemas import DeviceIdT 

8 

9_HexToTempT: TypeAlias = float | None 

10 

11 

12__all__ = ["PayDictT"] 

13 

14 

15# fmt: off 

16LogIdxT = Literal[ 

17 '00', '01', '02', '03', '04', '05', '06', '07', '08', '09', '0A', '0B', '0C', '0D', '0E', '0F', 

18 '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '1A', '1B', '1C', '1D', '1E', '1F', 

19 '20', '21', '22', '23', '24', '25', '26', '27', '28', '29', '2A', '2B', '2C', '2D', '2E', '2F', 

20 '30', '31', '32', '33', '34', '35', '36', '37', '38', '39', '3A', '3B', '3C', '3D', '3E', '3F', 

21] 

22# fmt: on 

23 

24 

25class _FlowRate(TypedDict): 

26 dhw_flow_rate: _HexToTempT 

27 

28 

29class _Pressure(TypedDict): 

30 pressure: _HexToTempT 

31 

32 

33class _Setpoint(TypedDict): 

34 setpoint: _HexToTempT 

35 

36 

37class _Temperature(TypedDict): 

38 temperature: _HexToTempT 

39 

40 

41class FaultLogEntryNull(TypedDict): # NOTE: not identical to _0418 

42 _log_idx: LogIdxT # "00" to ?"3F" 

43 

44 

45class FaultLogEntry(TypedDict): # NOTE: not identical to _0418 

46 _log_idx: LogIdxT # "00" to ?"3F" 

47 

48 timestamp: str 

49 fault_state: FaultState 

50 fault_type: FaultType 

51 domain_idx: str 

52 device_class: FaultDeviceClass 

53 device_id: DeviceIdT | None 

54 

55 _unknown_3: str 

56 _unknown_7: str 

57 _unknown_15: str 

58 

59 

60# These are from 31DA... 

61class AirQuality(TypedDict): 

62 air_quality: float | None 

63 air_quality_basis: NotRequired[str] 

64 

65 

66class Co2Level(TypedDict): 

67 co2_level: float | None 

68 

69 

70class RelativeHumidity(TypedDict): 

71 relative_humidity: _HexToTempT 

72 temperature: NotRequired[float | None] 

73 dewpoint_temp: NotRequired[float | None] 

74 

75 

76class IndoorHumidity(TypedDict): 

77 indoor_humidity: _HexToTempT 

78 temperature: NotRequired[float | None] 

79 dewpoint_temp: NotRequired[float | None] 

80 

81 

82class OutdoorHumidity(TypedDict): 

83 outdoor_humidity: _HexToTempT 

84 temperature: NotRequired[float | None] 

85 dewpoint_temp: NotRequired[float | None] 

86 

87 

88class ExhaustTemp(TypedDict): 

89 exhaust_temp: _HexToTempT 

90 

91 

92class SupplyTemp(TypedDict): 

93 supply_temp: _HexToTempT 

94 

95 

96class IndoorTemp(TypedDict): 

97 indoor_temp: _HexToTempT 

98 

99 

100class OutdoorTemp(TypedDict): 

101 outdoor_temp: _HexToTempT 

102 

103 

104class Capabilities(TypedDict): 

105 speed_capabilities: list[str] | None 

106 

107 

108class BypassPosition(TypedDict): 

109 bypass_position: float | None 

110 

111 

112class FanInfo(TypedDict): 

113 fan_info: str 

114 _unknown_fan_info_flags: list[int] 

115 

116 

117class ExhaustFanSpeed(TypedDict): 

118 exhaust_fan: float | None 

119 

120 

121class SupplyFanSpeed(TypedDict): 

122 supply_fan: float | None 

123 

124 

125class RemainingMins(TypedDict): 

126 remaining_mins: int | None 

127 

128 

129class PostHeater(TypedDict): 

130 post_heater: float | None 

131 

132 

133class PreHeater(TypedDict): 

134 pre_heater: float | None 

135 

136 

137class SupplyFlow(TypedDict): 

138 supply_flow: float | None 

139 

140 

141class ExhaustFlow(TypedDict): 

142 exhaust_flow: float | None 

143 

144 

145class _VentilationState( 

146 ExhaustFanSpeed, 

147 FanInfo, 

148 AirQuality, 

149 Co2Level, 

150 ExhaustTemp, 

151 SupplyTemp, 

152 IndoorTemp, 

153 OutdoorTemp, 

154 Capabilities, 

155 BypassPosition, 

156 SupplyFanSpeed, 

157 RemainingMins, 

158 PostHeater, 

159 PreHeater, 

160 SupplyFlow, 

161 ExhaustFlow, 

162): 

163 indoor_humidity: _HexToTempT 

164 outdoor_humidity: _HexToTempT 

165 extra: NotRequired[str | None] 

166 

167 

168# These are payload-specific... 

169class _empty(TypedDict): 

170 pass 

171 

172 

173class _0004(TypedDict): 

174 name: NotRequired[str | None] 

175 

176 

177class _0006(TypedDict): 

178 change_counter: NotRequired[int | None] 

179 

180 

181class _0008(TypedDict): 

182 relay_demand: float | None 

183 

184 

185class _000a(TypedDict): 

186 zone_idx: NotRequired[str] 

187 min_temp: float | None 

188 max_temp: float | None 

189 local_override: bool 

190 openwindow_function: bool 

191 multiroom_mode: bool 

192 _unknown_bitmap: str 

193 

194 

195class _0100(TypedDict): 

196 language: str 

197 _unknown_0: str 

198 

199 

200class _0404(TypedDict): 

201 frag_number: int 

202 total_frags: int | None 

203 frag_length: NotRequired[int | None] 

204 fragment: NotRequired[str] 

205 

206 

207class _0418_NULL(TypedDict): # only I_/RP with null payload 

208 log_idx: NotRequired[LogIdxT] # only when I_|0418|00 with null payload 

209 log_entry: None 

210 

211 

212class _0418(TypedDict): 

213 log_idx: LogIdxT 

214 log_entry: tuple[str, ...] # TODO: = namedtuple("Fault", "timestamp fault_state... 

215 

216 

217class _1060(TypedDict): 

218 battery_low: bool 

219 battery_level: float | None 

220 

221 

222class _1030(TypedDict): 

223 max_flow_setpoint: float 

224 min_flow_setpoint: float 

225 valve_run_time: int 

226 pump_run_time: int 

227 boolean_cc: bool 

228 

229 

230class _1090(TypedDict): 

231 temperature_0: float | None 

232 temperature_1: float | None 

233 

234 

235class _10a0(TypedDict): 

236 setpoint: _HexToTempT | None 

237 overrun: NotRequired[int] 

238 differential: NotRequired[_HexToTempT] 

239 

240 

241class _10d0(TypedDict): 

242 days_remaining: int | None 

243 days_lifetime: NotRequired[int | None] 

244 percent_remaining: NotRequired[float | None] 

245 

246 

247class _10e1(TypedDict): 

248 device_id: DeviceIdT 

249 

250 

251class _1100(TypedDict): 

252 domain_id: NotRequired[str] 

253 cycle_rate: int 

254 min_on_time: float 

255 min_off_time: float 

256 _unknown_0: str 

257 proportional_band_width: NotRequired[float | None] 

258 _unknown_1: NotRequired[str | None] 

259 

260 

261class _1100_IDX(TypedDict): 

262 domain_id: str 

263 

264 

265class _12a0(TypedDict): 

266 hvac_idx: str 

267 indoor_humidity: NotRequired[_HexToTempT | None] 

268 outdoor_humidity: NotRequired[_HexToTempT | None] 

269 relative_humidity: NotRequired[_HexToTempT | None] 

270 temperature: NotRequired[float | None] 

271 dewpoint_temp: NotRequired[float | None] 

272 

273 

274class _12b0(TypedDict): 

275 window_open: bool | None 

276 

277 

278class _12c0(TypedDict): 

279 temperature: float | None 

280 units: Literal["Fahrenheit", "Celsius"] 

281 _unknown_6: NotRequired[str] 

282 

283 

284class _1f09(TypedDict): 

285 remaining_seconds: float 

286 _next_sync: str 

287 

288 

289class _1f41(TypedDict): 

290 mode: str 

291 active: NotRequired[bool | None] 

292 until: NotRequired[str | None] 

293 

294 

295class _1fd4(TypedDict): 

296 ticker: int 

297 

298 

299@verify(EnumCheck.UNIQUE) 

300class _BindPhase(StrEnum): 

301 OFFER = "offer" 

302 ACCEPT = "accept" 

303 CONFIRM = "confirm" 

304 

305 

306class _1fc9(TypedDict): 

307 phase: str | None 

308 bindings: list[list[str]] 

309 

310 

311class _22b0(TypedDict): 

312 enabled: bool 

313 

314 

315class _22f4(TypedDict): # WIP 

316 fan_mode: str | None 

317 fan_rate: str | None 

318 

319 

320class _2309(TypedDict): 

321 zone_idx: NotRequired[str] 

322 setpoint: float | None 

323 

324 

325@verify(EnumCheck.UNIQUE) 

326class _ZoneMode(StrEnum): 

327 FOLLOW = "follow_schedule" 

328 ADVANCED = "advanced_override" # until the next scheduled setpoint 

329 PERMANENT = "permanent_override" # indefinitely, until auto_reset 

330 COUNTDOWN = "countdown_override" # for x mins (duration, max 1,215?) 

331 TEMPORARY = "temporary_override" # until a given date/time (until) 

332 

333 

334class _2349(TypedDict): 

335 mode: _ZoneMode 

336 setpoint: float | None 

337 duration: NotRequired[int | None] 

338 until: NotRequired[str | None] 

339 

340 

341class _2d49(TypedDict): 

342 state: bool | None 

343 

344 

345class _2e04(TypedDict): 

346 system_mode: str 

347 until: NotRequired[str | None] 

348 

349 

350class _3110(TypedDict): 

351 mode: str 

352 demand: NotRequired[float | None] 

353 

354 

355class _313f(TypedDict): 

356 datetime: str | None 

357 is_dst: bool | None 

358 _unknown_0: str 

359 

360 

361class _3220(TypedDict): 

362 msg_id: int # OtDataId 

363 msg_type: str # OtMsgType 

364 msg_name: str 

365 description: str 

366 

367 

368class _3222(TypedDict): 

369 start: int | None 

370 length: int 

371 data: NotRequired[str] 

372 

373 

374class _3b00(TypedDict): 

375 domain_id: NotRequired[Literal["FC"]] 

376 actuator_sync: bool | None 

377 

378 

379class _3ef0_3(TypedDict): # payload of 3 bytes 

380 modulation_level: float | None 

381 _flags_2: str 

382 

383 

384class _3ef0_6(_3ef0_3): # payload of 6 bytes 

385 _flags_3: list[int] 

386 ch_active: bool 

387 dhw_active: bool 

388 cool_active: bool 

389 flame_on: bool 

390 _unknown_4: str 

391 _unknown_5: str 

392 

393 

394class _3ef0_9(_3ef0_6): # payload of 9 bytes 

395 _flags_6: list[int] 

396 ch_enabled: bool 

397 ch_setpoint: int 

398 max_rel_modulation: float 

399 

400 

401class _3ef1(TypedDict): 

402 modulation_level: float | None 

403 actuator_countdown: int | None 

404 cycle_countdown: int | None 

405 _unknown_0: str 

406 

407 

408class _JASPER(TypedDict): 

409 ordinal: str 

410 blob: str 

411 

412 

413class PayDictT: 

414 """Payload dict types.""" 

415 

416 EMPTY: TypeAlias = _empty 

417 

418 # command codes 

419 _0004: TypeAlias = _0004 

420 _0006: TypeAlias = _0006 

421 _0008: TypeAlias = _0008 

422 _000A: TypeAlias = _000a 

423 _0100: TypeAlias = _0100 

424 _0404: TypeAlias = _0404 

425 _0418: TypeAlias = _0418 

426 _0418_NULL: TypeAlias = _0418_NULL 

427 _1030: TypeAlias = _1030 

428 _1060: TypeAlias = _1060 

429 _1081: TypeAlias = _Setpoint 

430 _1090: TypeAlias = _1090 

431 _10A0: TypeAlias = _10a0 

432 _10D0: TypeAlias = _10d0 

433 _10E1: TypeAlias = _10e1 

434 _1100: TypeAlias = _1100 

435 _1100_IDX: TypeAlias = _1100_IDX 

436 _1260: TypeAlias = _Temperature 

437 _1280: TypeAlias = OutdoorHumidity 

438 _1290: TypeAlias = OutdoorTemp 

439 _1298: TypeAlias = Co2Level 

440 _12A0: TypeAlias = _12a0 

441 _12B0: TypeAlias = _12b0 

442 _12C0: TypeAlias = _12c0 

443 _12C8: TypeAlias = AirQuality 

444 _12F0: TypeAlias = _FlowRate 

445 _1300: TypeAlias = _Pressure 

446 _1F09: TypeAlias = _1f09 

447 _1F41: TypeAlias = _1f41 

448 _1FC9: TypeAlias = _1fc9 

449 _1FD4: TypeAlias = _1fd4 

450 _22B0: TypeAlias = _22b0 

451 _22F4: TypeAlias = _22f4 

452 _2309: TypeAlias = _2309 

453 _2349: TypeAlias = _2349 

454 _22D9: TypeAlias = _Setpoint 

455 _2D49: TypeAlias = _2d49 

456 _2E04: TypeAlias = _2e04 

457 _3110: TypeAlias = _3110 

458 _313F: TypeAlias = _313f 

459 _31DA: TypeAlias = _VentilationState 

460 _3200: TypeAlias = _Temperature 

461 _3210: TypeAlias = _Temperature 

462 _3B00: TypeAlias = _3b00 

463 _3EF0: TypeAlias = _3ef0_3 | _3ef0_6 | _3ef0_9 

464 _3EF1: TypeAlias = _3ef1 

465 

466 _JASPER: TypeAlias = _JASPER 

467 

468 FAULT_LOG_ENTRY: TypeAlias = FaultLogEntry 

469 FAULT_LOG_ENTRY_NULL: TypeAlias = FaultLogEntryNull 

470 TEMPERATURE: TypeAlias = _Temperature 

471 

472 # 12A0 primitive 

473 RELATIVE_HUMIDITY: TypeAlias = RelativeHumidity 

474 

475 # 31DA primitives 

476 AIR_QUALITY: TypeAlias = AirQuality 

477 CO2_LEVEL: TypeAlias = Co2Level 

478 EXHAUST_TEMP: TypeAlias = ExhaustTemp 

479 SUPPLY_TEMP: TypeAlias = SupplyTemp 

480 INDOOR_HUMIDITY: TypeAlias = IndoorHumidity 

481 OUTDOOR_HUMIDITY: TypeAlias = OutdoorHumidity 

482 INDOOR_TEMP: TypeAlias = IndoorTemp 

483 OUTDOOR_TEMP: TypeAlias = OutdoorTemp 

484 CAPABILITIES: TypeAlias = Capabilities 

485 BYPASS_POSITION: TypeAlias = BypassPosition 

486 FAN_INFO: TypeAlias = FanInfo 

487 EXHAUST_FAN_SPEED: TypeAlias = ExhaustFanSpeed 

488 SUPPLY_FAN_SPEED: TypeAlias = SupplyFanSpeed 

489 REMAINING_MINUTES: TypeAlias = RemainingMins 

490 POST_HEATER: TypeAlias = PostHeater 

491 PRE_HEATER: TypeAlias = PreHeater 

492 SUPPLY_FLOW: TypeAlias = SupplyFlow 

493 EXHAUST_FLOW: TypeAlias = ExhaustFlow