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

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

2

3 from enum import EnumCheck, StrEnum, verify

4 from typing import Literal, NotRequired, TypeAlias, TypedDict

5

6 from ramses_tx.const import FaultDeviceClass, FaultState, FaultType

7 from ramses_tx.schemas import DeviceIdT

8

9 _HexToTempT: TypeAlias = float | None

10

11

12 __all__ = ["PayDictT"]

13

14

15# fmt: off

16 LogIdxT = 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

25 class _FlowRate(TypedDict):

26 dhw_flow_rate: _HexToTempT

27

28

29 class _Pressure(TypedDict):

30 pressure: _HexToTempT

31

32

33 class _Setpoint(TypedDict):

34 setpoint: _HexToTempT

35

36

37 class _Temperature(TypedDict):

38 temperature: _HexToTempT

39

40

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

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

43

44

45 class 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...

61 class AirQuality(TypedDict):

62 air_quality: float | None

63 air_quality_basis: NotRequired[str]

64

65

66 class Co2Level(TypedDict):

67 co2_level: float | None

68

69

70 class RelativeHumidity(TypedDict):

71 relative_humidity: _HexToTempT

72 temperature: NotRequired[float | None]

73 dewpoint_temp: NotRequired[float | None]

74

75

76 class IndoorHumidity(TypedDict):

77 indoor_humidity: _HexToTempT

78 temperature: NotRequired[float | None]

79 dewpoint_temp: NotRequired[float | None]

80

81

82 class OutdoorHumidity(TypedDict):

83 outdoor_humidity: _HexToTempT

84 temperature: NotRequired[float | None]

85 dewpoint_temp: NotRequired[float | None]

86

87

88 class ExhaustTemp(TypedDict):

89 exhaust_temp: _HexToTempT

90

91

92 class SupplyTemp(TypedDict):

93 supply_temp: _HexToTempT

94

95

96 class IndoorTemp(TypedDict):

97 indoor_temp: _HexToTempT

98

99

100 class OutdoorTemp(TypedDict):

101 outdoor_temp: _HexToTempT

102

103

104 class Capabilities(TypedDict):

105 speed_capabilities: list[str] | None

106

107

108 class BypassPosition(TypedDict):

109 bypass_position: float | None

110

111

112 class FanInfo(TypedDict):

113 fan_info: str

114 _unknown_fan_info_flags: list[int]

115

116

117 class ExhaustFanSpeed(TypedDict):

118 exhaust_fan: float | None

119

120

121 class SupplyFanSpeed(TypedDict):

122 supply_fan: float | None

123

124

125 class RemainingMins(TypedDict):

126 remaining_mins: int | None

127

128

129 class PostHeater(TypedDict):

130 post_heater: float | None

131

132

133 class PreHeater(TypedDict):

134 pre_heater: float | None

135

136

137 class SupplyFlow(TypedDict):

138 supply_flow: float | None

139

140

141 class ExhaustFlow(TypedDict):

142 exhaust_flow: float | None

143

144

145 class _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...

169 class _empty(TypedDict):

170 pass

171

172

173 class _0004(TypedDict):

174 name: NotRequired[str | None]

175

176

177 class _0006(TypedDict):

178 change_counter: NotRequired[int | None]

179

180

181 class _0008(TypedDict):

182 relay_demand: float | None

183

184

185 class _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

195 class _0100(TypedDict):

196 language: str

197 _unknown_0: str

198

199

200 class _0404(TypedDict):

201 frag_number: int

202 total_frags: int | None

203 frag_length: NotRequired[int | None]

204 fragment: NotRequired[str]

205

206

207 class _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

212 class _0418(TypedDict):

213 log_idx: LogIdxT

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

215

216

217 class _1060(TypedDict):

218 battery_low: bool

219 battery_level: float | None

220

221

222 class _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

230 class _1090(TypedDict):

231 temperature_0: float | None

232 temperature_1: float | None

233

234

235 class _10a0(TypedDict):

236 setpoint: _HexToTempT | None

237 overrun: NotRequired[int]

238 differential: NotRequired[_HexToTempT]

239

240

241 class _10d0(TypedDict):

242 days_remaining: int | None

243 days_lifetime: NotRequired[int | None]

244 percent_remaining: NotRequired[float | None]

245

246

247 class _10e1(TypedDict):

248 device_id: DeviceIdT

249

250

251 class _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

261 class _1100_IDX(TypedDict):

262 domain_id: str

263

264

265 class _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

274 class _12b0(TypedDict):

275 window_open: bool | None

276

277

278 class _12c0(TypedDict):

279 temperature: float | None

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

281 _unknown_6: NotRequired[str]

282

283

284 class _1f09(TypedDict):

285 remaining_seconds: float

286 _next_sync: str

287

288

289 class _1f41(TypedDict):

290 mode: str

291 active: NotRequired[bool | None]

292 until: NotRequired[str | None]

293

294

295 class _1fd4(TypedDict):

296 ticker: int

297

298

299@verify(EnumCheck.UNIQUE)

300 class _BindPhase(StrEnum):

301 OFFER = "offer"

302 ACCEPT = "accept"

303 CONFIRM = "confirm"

304

305

306 class _1fc9(TypedDict):

307 phase: str | None

308 bindings: list[list[str]]

309

310

311 class _22b0(TypedDict):

312 enabled: bool

313

314

315 class _22f4(TypedDict): # WIP

316 fan_mode: str | None

317 fan_rate: str | None

318

319

320 class _2309(TypedDict):

321 zone_idx: NotRequired[str]

322 setpoint: float | None

323

324

325@verify(EnumCheck.UNIQUE)

326 class _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

334 class _2349(TypedDict):

335 mode: _ZoneMode

336 setpoint: float | None

337 duration: NotRequired[int | None]

338 until: NotRequired[str | None]

339

340

341 class _2d49(TypedDict):

342 state: bool | None

343

344

345 class _2e04(TypedDict):

346 system_mode: str

347 until: NotRequired[str | None]

348

349

350 class _3110(TypedDict):

351 mode: str

352 demand: NotRequired[float | None]

353

354

355 class _313f(TypedDict):

356 datetime: str | None

357 is_dst: bool | None

358 _unknown_0: str

359

360

361 class _3220(TypedDict):

362 msg_id: int # OtDataId

363 msg_type: str # OtMsgType

364 msg_name: str

365 description: str

366

367

368 class _3222(TypedDict):

369 start: int | None

370 length: int

371 data: NotRequired[str]

372

373

374 class _3b00(TypedDict):

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

376 actuator_sync: bool | None

377

378

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

380 modulation_level: float | None

381 _flags_2: str

382

383

384 class _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

394 class _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

401 class _3ef1(TypedDict):

402 modulation_level: float | None

403 actuator_countdown: int | None

404 cycle_countdown: int | None

405 _unknown_0: str

406

407

408 class _JASPER(TypedDict):

409 ordinal: str

410 blob: str

411

412

413 class 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