mclimate-payload-helper 1.3.4 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONTEXT.md +165 -0
- package/dist/decoders/commandsReadingHelper.d.ts.map +1 -1
- package/dist/decoders/commandsReadingHelper.js +64 -2
- package/dist/decoders/commandsReadingHelper.js.map +1 -1
- package/dist/decoders/payloadParsers/types/vicki.d.ts +2 -0
- package/dist/decoders/payloadParsers/types/vicki.d.ts.map +1 -1
- package/dist/decoders/payloadParsers/vickiPayloadParser.d.ts.map +1 -1
- package/dist/decoders/payloadParsers/vickiPayloadParser.js +4 -0
- package/dist/decoders/payloadParsers/vickiPayloadParser.js.map +1 -1
- package/dist/encoders/FanCoilThermostatCommands.d.ts +7 -0
- package/dist/encoders/FanCoilThermostatCommands.d.ts.map +1 -1
- package/dist/encoders/FanCoilThermostatCommands.js +63 -2
- package/dist/encoders/FanCoilThermostatCommands.js.map +1 -1
- package/dist/encoders/FanCoilThermostatCommands.test.js +21 -0
- package/dist/encoders/FanCoilThermostatCommands.test.js.map +1 -1
- package/dist/encoders/HTSensorCommands.d.ts +4 -0
- package/dist/encoders/HTSensorCommands.d.ts.map +1 -1
- package/dist/encoders/HTSensorCommands.js +50 -0
- package/dist/encoders/HTSensorCommands.js.map +1 -1
- package/dist/encoders/HTSensorCommands.test.js +18 -0
- package/dist/encoders/HTSensorCommands.test.js.map +1 -1
- package/dist/encoders/TValveCommands.d.ts +1 -0
- package/dist/encoders/TValveCommands.d.ts.map +1 -1
- package/dist/encoders/TValveCommands.js +3 -0
- package/dist/encoders/TValveCommands.js.map +1 -1
- package/dist/encoders/TValveCommands.test.js +3 -0
- package/dist/encoders/TValveCommands.test.js.map +1 -1
- package/dist/encoders/VickiCommands.d.ts +6 -0
- package/dist/encoders/VickiCommands.d.ts.map +1 -1
- package/dist/encoders/VickiCommands.js +76 -0
- package/dist/encoders/VickiCommands.js.map +1 -1
- package/dist/encoders/VickiCommands.test.js +33 -0
- package/dist/encoders/VickiCommands.test.js.map +1 -1
- package/dist/encoders/WirelessThermostatCommands.d.ts +5 -0
- package/dist/encoders/WirelessThermostatCommands.d.ts.map +1 -1
- package/dist/encoders/WirelessThermostatCommands.js +57 -0
- package/dist/encoders/WirelessThermostatCommands.js.map +1 -1
- package/dist/encoders/WirelessThermostatCommands.test.js +21 -0
- package/dist/encoders/WirelessThermostatCommands.test.js.map +1 -1
- package/dist/encoders/types/schemas.d.ts +402 -24
- package/dist/encoders/types/schemas.d.ts.map +1 -1
- package/dist/encoders/types/schemas.js +67 -1
- package/dist/encoders/types/schemas.js.map +1 -1
- package/dist/test/payloadDecoders.test.js +90 -0
- package/dist/test/payloadDecoders.test.js.map +1 -1
- package/package.json +1 -1
- package/src/decoders/commandsReadingHelper.ts +59 -2
- package/src/decoders/payloadParsers/types/vicki.ts +2 -0
- package/src/decoders/payloadParsers/vickiPayloadParser.ts +4 -0
- package/src/encoders/FanCoilThermostatCommands.test.ts +38 -0
- package/src/encoders/FanCoilThermostatCommands.ts +66 -2
- package/src/encoders/HTSensorCommands.test.ts +34 -0
- package/src/encoders/HTSensorCommands.ts +50 -0
- package/src/encoders/TValveCommands.test.ts +4 -0
- package/src/encoders/TValveCommands.ts +4 -0
- package/src/encoders/VickiCommands.test.ts +59 -0
- package/src/encoders/VickiCommands.ts +76 -0
- package/src/encoders/WirelessThermostatCommands.test.ts +38 -0
- package/src/encoders/WirelessThermostatCommands.ts +60 -0
- package/src/encoders/types/schemas.ts +104 -2
- package/src/test/payloadDecoders.test.ts +98 -0
package/CONTEXT.md
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# CONTEXT.md
|
|
2
|
+
|
|
3
|
+
This file is the canonical, high-level reference for the `mclimate-payload-helper` repository. It is intended for AI assistants and new contributors so they can orient quickly without re-reading the entire codebase.
|
|
4
|
+
|
|
5
|
+
## 1. Project Overview
|
|
6
|
+
|
|
7
|
+
- **Name:** `mclimate-payload-helper` (npm package)
|
|
8
|
+
- **Version:** `1.3.4` (see `package.json`)
|
|
9
|
+
- **Language / Runtime:** TypeScript, compiled to CommonJS for Node.js consumers
|
|
10
|
+
- **Purpose:** Encode LoRaWAN downlink commands and decode uplink payloads for MClimate IoT devices (Vicki, Relay 16, T-Valve, T-Flood, CO₂ sensors/displays, Fan-Coil thermostat, PIR Mini, etc.).
|
|
11
|
+
- **Distribution:** Published to npm; consumers import `uplinkPayloadParser`, `CommandBuilder`, `DeviceType`, schemas and per-device command classes.
|
|
12
|
+
- **License:** ISC. **Author:** MClimate.
|
|
13
|
+
|
|
14
|
+
## 2. Tech Stack and Dependencies
|
|
15
|
+
|
|
16
|
+
- **Runtime dep:** `zod` (^3.23.8) — schema validation for command parameters.
|
|
17
|
+
- **Build:** `typescript` (^5.5.4) + `tsc-alias` (resolves `@/*` path aliases in emitted JS) + `typescript-transform-paths` (rewrites paths inside `.d.ts`).
|
|
18
|
+
- **Test:** `jest` (^29) + `ts-jest`.
|
|
19
|
+
- **Lint/Format:** `eslint` (with `@typescript-eslint`), `prettier`, `eslint-config-prettier`, `eslint-plugin-jest`.
|
|
20
|
+
- **Git hooks:** `husky` runs format/type-check/lint/test on pre-commit (see `.husky/pre-commit`).
|
|
21
|
+
- **TS Config:** `target: ES2016`, `module: commonjs`, `strict: true`, `rootDir: ./src`, `outDir: ./dist`, path alias `@/* -> ./src/*` (see `tsconfig.json`).
|
|
22
|
+
|
|
23
|
+
## 3. Project Structure
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
.
|
|
27
|
+
├── src/
|
|
28
|
+
│ ├── index.ts # Public package entry — re-exports parser, CommandBuilder, device commands, schemas, enums, CustomError
|
|
29
|
+
│ ├── decoders/
|
|
30
|
+
│ │ ├── index.ts
|
|
31
|
+
│ │ ├── commandsReadingHelper.ts # Large helper that decodes command-response bytes embedded in keepalives
|
|
32
|
+
│ │ └── payloadParsers/
|
|
33
|
+
│ │ ├── index.ts
|
|
34
|
+
│ │ ├── uplinkPayloadParser.ts # Switch on DeviceType → device-specific parser
|
|
35
|
+
│ │ ├── <Device>PayloadParser.ts # One file per device (Vicki, HTSensor, TFlood, TValve, CO2*, Relay16*, PirMini, MultiSensor, Melissa, etc.)
|
|
36
|
+
│ │ └── types/ # DeviceType enum + Vicki/Relay enum types
|
|
37
|
+
│ ├── encoders/
|
|
38
|
+
│ │ ├── index.ts # Re-exports BaseCommand, CommandBuilder, all device command classes & mixins
|
|
39
|
+
│ │ ├── BaseCommand.ts # Hex serialization (cmdId + params → hex string)
|
|
40
|
+
│ │ ├── CommandBuilder.ts # Registry: device_type → command class; build(command, params), combine([...])
|
|
41
|
+
│ │ ├── GeneralCommands.ts # Shared commands (keepalive, uplinkType, watchdog, region, customHex, joinRetry, deviceVersion)
|
|
42
|
+
│ │ ├── DisplayCommands.ts # Mixin
|
|
43
|
+
│ │ ├── TemperatureCommonCommands.ts # Mixin
|
|
44
|
+
│ │ ├── PIRCommands.ts # Mixin
|
|
45
|
+
│ │ ├── ChildLockCommands.ts # Mixin
|
|
46
|
+
│ │ ├── <Device>Commands.ts # One per device, extends GeneralCommands (+ mixins via applyMixins)
|
|
47
|
+
│ │ ├── <Device>Commands.test.ts # Jest tests colocated with each command class
|
|
48
|
+
│ │ └── types/
|
|
49
|
+
│ │ ├── index.ts
|
|
50
|
+
│ │ └── schemas.ts # All Zod schemas + per-device enums (large, ~82KB)
|
|
51
|
+
│ ├── helpers/ # byteArrayParser, decbin, toBool
|
|
52
|
+
│ ├── utils/ # caseConverter (toCamelCase), customErrorHandler (CustomError), decToHex, delMethods, mixin (applyMixins)
|
|
53
|
+
│ └── test/
|
|
54
|
+
│ └── payloadDecoders.test.ts # Consolidated decoder tests (all devices)
|
|
55
|
+
├── docs/ # External LoRaWAN API docs (PDFs + how-to-add-new-device.md, PIR Mini API)
|
|
56
|
+
├── add-encoder-command-existing.md # Step-by-step guide for adding a command to an existing device
|
|
57
|
+
├── add-encoder-command-new.md # Step-by-step guide for adding a brand-new device
|
|
58
|
+
├── README.md # Public-facing usage doc
|
|
59
|
+
├── CLAUDE.md # AI assistant guidance (overlap with this file)
|
|
60
|
+
├── package.json / package-lock.json
|
|
61
|
+
├── tsconfig.json
|
|
62
|
+
├── jest.config.ts
|
|
63
|
+
├── .eslintrc.cjs / .prettierrc
|
|
64
|
+
└── .husky/pre-commit
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## 4. Core Flows
|
|
68
|
+
|
|
69
|
+
### 4.1 Uplink decode flow
|
|
70
|
+
|
|
71
|
+
1. Consumer calls `uplinkPayloadParser(hexData, DeviceType)` (`src/decoders/payloadParsers/uplinkPayloadParser.ts`).
|
|
72
|
+
2. Switch dispatches to a device-specific parser function (e.g. `vickiPayloadParser`, `pirMiniPayloadParser`).
|
|
73
|
+
3. The parser reads bytes (hex pairs) from the head, identifies the frame type by the first byte (e.g. keepalive command id), and returns a typed object of decoded fields (temperatures, humidity, battery, status flags, etc.).
|
|
74
|
+
4. If the frame contains a response to a previously-sent command, the parser delegates remaining bytes to `commandsReadingHelper.ts`, which switches on the response cmdId to populate the appropriate fields.
|
|
75
|
+
5. **Default fallback:** if `DeviceType` does not match any case, `vickiPayloadParser` is used (see TODO comment in `uplinkPayloadParser.ts`).
|
|
76
|
+
|
|
77
|
+
### 4.2 Downlink encode flow
|
|
78
|
+
|
|
79
|
+
1. Consumer constructs `new CommandBuilder(device_type)` (string matching the `DeviceType` enum value, e.g. `'vicki'`).
|
|
80
|
+
2. Calls `.build(commandName, params?)`.
|
|
81
|
+
- `commandName` is converted via `toCamelCase` (snake_case or kebab → camelCase), then looked up as a static method on the registered command class.
|
|
82
|
+
- The static method validates `params` with a Zod schema from `src/encoders/types/schemas.ts`; on failure, throws a `CustomError` wrapping the `ZodError`.
|
|
83
|
+
- On success it returns a `BaseCommand(cmdName, cmdId, ...hexParams)`.
|
|
84
|
+
3. `BaseCommand.toHex()` produces the wire hex (cmdId padded to 2 chars + each param padded to 2 chars). Special case for `SetOpenWindow` skips param padding.
|
|
85
|
+
4. Multiple commands can be concatenated for a single downlink via `commandBuilder.combine([cmd1, cmd2, ...])`.
|
|
86
|
+
|
|
87
|
+
### 4.3 Mixin / inheritance pattern
|
|
88
|
+
|
|
89
|
+
- `GeneralCommands` is the base class — every device class extends it.
|
|
90
|
+
- Reusable command sets live in mixin classes (`DisplayCommands`, `TemperatureCommonCommands`, `PIRCommands`, `ChildLockCommands`).
|
|
91
|
+
- Mixins are applied via `applyMixins` in `src/utils/mixin.ts`. `delMethods` is used to remove inherited methods that don't apply to a particular device.
|
|
92
|
+
|
|
93
|
+
## 5. Data Stores
|
|
94
|
+
|
|
95
|
+
None. This package is a pure library — no database, no cache, no I/O at runtime. All inputs are hex strings / JS objects; outputs are decoded objects / hex strings.
|
|
96
|
+
|
|
97
|
+
## 6. Environment Variables
|
|
98
|
+
|
|
99
|
+
None required by the library or its tests.
|
|
100
|
+
|
|
101
|
+
## 7. How to Run and Test
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
npm install
|
|
105
|
+
npm run build # tsc + tsc-alias → ./dist
|
|
106
|
+
npm test # jest
|
|
107
|
+
npm test -- VickiCommands # filter by file/pattern
|
|
108
|
+
npm run type-check # tsc --noEmit
|
|
109
|
+
npm run lint # eslint
|
|
110
|
+
npm run format # prettier --write .
|
|
111
|
+
npm run format:check # prettier --check .
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Publishing:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
npm version patch|minor|major # bump + git tag
|
|
118
|
+
npm publish # prepublishOnly runs build
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## 8. Key Patterns and Conventions
|
|
122
|
+
|
|
123
|
+
- **Path alias `@/`** maps to `src/` everywhere (imports, tests, build output).
|
|
124
|
+
- **Public entry** is `src/index.ts` only — keep new exports there.
|
|
125
|
+
- **Device registration is three-sided.** When adding a device, all three sides must be updated:
|
|
126
|
+
1. `DeviceType` enum in `src/decoders/payloadParsers/types/allDevices.ts`.
|
|
127
|
+
2. Decoder: new `xxxPayloadParser.ts` exported via `src/decoders/payloadParsers/index.ts` and wired into the switch in `uplinkPayloadParser.ts`.
|
|
128
|
+
3. Encoder: new `XxxCommands.ts` (extends `GeneralCommands`, optional mixins) exported via `src/encoders/index.ts`, registered in `CommandBuilder.commandRegistry`, and re-exported in `src/index.ts`. Add Zod schemas + enums under `src/encoders/types/schemas.ts`.
|
|
129
|
+
- **Command class style:** all commands are `static` methods on the class. Each set/get pair returns `new BaseCommand(name, cmdId, ...params)`. `decToHex` from `src/utils/decToHex.ts` is the standard numeric-to-hex helper.
|
|
130
|
+
- **Validation:** every command with parameters parses with Zod first, wrapping failures in `CustomError` (`src/utils/customErrorHandler.ts`).
|
|
131
|
+
- **Error type:** `CustomError` is the only error type the library throws publicly.
|
|
132
|
+
- **Tests live next to encoders** (`<Class>.test.ts`); decoder tests are consolidated in `src/test/payloadDecoders.test.ts`.
|
|
133
|
+
- **Encoding helpers:** keep numeric → hex conversions in `src/utils/decToHex.ts`. Bit/byte parsing helpers live in `src/helpers/` (`byteArrayParser`, `decbin`, `toBool`).
|
|
134
|
+
- **Naming:** Device class files are PascalCase (`VickiCommands.ts`); decoder parser files are mixed (`vickiPayloadParser.ts`, `htSensorPayloadParser.ts`, `CO2SensorPayloadParser.ts`) — match the existing neighbours when adding new ones.
|
|
135
|
+
|
|
136
|
+
## 9. Known Gotchas
|
|
137
|
+
|
|
138
|
+
- **`uplinkPayloadParser` defaults to Vicki** when an unknown `DeviceType` is passed (see comment `// Q: is this OK?`). New devices that aren't wired into the switch will silently be parsed as Vicki.
|
|
139
|
+
- **`BaseCommand.toHex` has a Vicki-specific branch** for `SetOpenWindow` (skips per-param padding). Don't reuse that command name elsewhere.
|
|
140
|
+
- **`CommandBuilder.build` uses `toCamelCase`** on the command string; consumers can pass `"set_target_temperature"` or `"setTargetTemperature"` and both resolve. Method names on command classes must therefore be camelCase.
|
|
141
|
+
- **`commandsReadingHelper.ts` is shared across devices.** The same response cmdId can mean different things on different devices (e.g. `0x3d` → `pirDemoMode` for PirMini, `integralGain` for Vicki, `pirSensorStatus` elsewhere). When adding a new response, check existing case branches before reusing a byte.
|
|
142
|
+
- **Husky pre-commit is strict:** format-check, type-check, lint, and tests must all pass. CI/local commits will fail otherwise.
|
|
143
|
+
- **`prepublishOnly` runs `npm run build`.** Always bump the version in `package.json` before `npm publish` or it will be rejected by the registry.
|
|
144
|
+
- **Mixins + `delMethods`:** if a device inherits a command via a mixin that it should not expose, it must explicitly delete that method (pattern used in several command classes).
|
|
145
|
+
|
|
146
|
+
## 10. Key Files to Read First
|
|
147
|
+
|
|
148
|
+
When starting a new task, read these in order:
|
|
149
|
+
|
|
150
|
+
1. `src/index.ts` — public API surface.
|
|
151
|
+
2. `src/decoders/payloadParsers/uplinkPayloadParser.ts` and `src/decoders/payloadParsers/types/allDevices.ts` — supported devices.
|
|
152
|
+
3. `src/encoders/CommandBuilder.ts` — device → command-class registry.
|
|
153
|
+
4. `src/encoders/BaseCommand.ts` and `src/encoders/GeneralCommands.ts` — base command shape and shared commands.
|
|
154
|
+
5. `src/encoders/types/schemas.ts` — Zod schemas + device enums (large, scan by section).
|
|
155
|
+
6. `src/decoders/commandsReadingHelper.ts` — shared response decoder switch (large).
|
|
156
|
+
7. The specific `<Device>PayloadParser.ts` and `<Device>Commands.ts` for the device you are touching.
|
|
157
|
+
8. `add-encoder-command-existing.md` / `add-encoder-command-new.md` / `docs/how-to-add-new-device.md` — official extension checklists.
|
|
158
|
+
|
|
159
|
+
## 11. Changelog
|
|
160
|
+
|
|
161
|
+
- **2026-05-14** — Initial `CONTEXT.md` created. Captures architecture as of `package.json` v1.3.4: 22 supported `DeviceType`s, mixin-based encoder hierarchy, single shared `commandsReadingHelper`, Zod-validated command params, colocated encoder tests + consolidated decoder tests.
|
|
162
|
+
- **2026-05-14** — Added `FanCoilThermostatCommands.getFctOperationalMode` (cmdId `0x53`) in `src/encoders/FanCoilThermostatCommands.ts`. Added a `DeviceType.FanCoilThermostat` branch to case `'53'` in `src/decoders/commandsReadingHelper.ts` that decodes the byte as `fctOperationalMode` (raw int, 0=Vent / 1=Heat / 2=Cool) while leaving the default `targetTemperatureStep` decoding intact for other devices. Encoder + decoder tests added (`FanCoilThermostatCommands.test.ts`, `payloadDecoders.test.ts`). Reinforces the existing pattern of guarding shared response cmdIds by `deviceType` (gotcha §9).
|
|
163
|
+
- **2026-05-19** — Audited `VickiCommands` against the official Vicki cheat-sheet (`docs.mclimate.eu/.../command-cheat-sheet`). Only one documented GET was missing: `0x52` _GET Target temperature with resolution 0.1°C_. Added `VickiCommands.getTargetTemperaturePrecisely()` returning `BaseCommand('GetTargetTemperaturePrecisely', 0x52)`, a matching no-param Zod schema entry in `VickiCommandSchemas`, and an encoder test. Decoder already handles case `'52'` as `targetTemperatureFloat` (2-byte / 10) for all devices, so no `commandsReadingHelper` change needed. All other documented Vicki GETs (incl. `0x14`/`0x15` via `ChildLockCommands`/`TemperatureCommonCommands` mixins and `0x12`/`0x19`/`0x1b`/`0x1d`/`0xa4` via `GeneralCommands`) are already present.
|
|
164
|
+
- **2026-05-19** — Completed Vicki SET audit. Two documented SETs were missing: `0x33` _Set LoRaWAN AppEUI & AppKey_ (≥4.1) and `0x57` _SET target temperature in Fahrenheit_ (≥4.4). Added `setAppEuiAndAppKey({ appEui: hex16, appKey: hex32 })` and `setTargetTemperatureFahrenheit({ targetTemperature: 41–86 })` to `VickiCommands.ts`, matching Zod schemas (`appEui`/`appKey` are hex-string-typed with regex + length), type exports, and 4 new tests (encode + zod-fail per command). All other documented Vicki SETs were already present (incl. `0x4e`/`0x4f` valve openness, `0x59`/`0x5b`/`0x5d`/`0x5f`/`0x61`/`0x63`/`0x65`/`0x67`/`0x69`/`0x6b`/`0x6d` heating-schedule/time/setback/etc.). `0x58` _Additional notifications_ and `0xa6` _External crystal not working_ are uplink-only — out of scope.
|
|
165
|
+
- **2026-05-19** — HT-Sensor D2D protocol additions (per internal _HT new commands (1).odt_). **Encoder** (`HTSensorCommands.ts`): added `setD2dCommunicationState({ enabled: boolean })` (`0xa9`), `getD2dCommunicationState()` (`0xaa`), `setD2dCommunicationPeriod({ period: 1–8 min })` (`0xab`), `getD2dCommunicationPeriod()` (`0xac`). **Decoder** (`commandsReadingHelper.ts`): added `HTSensor`-guarded branches for cases `'aa'` → `d2dCommunicationState: boolean` and `'ac'` → `d2dCommunicationPeriod: number`. **Schemas** (`schemas.ts`): 4 new entries in `HTSensorCommandSchemas` + 2 type exports. 9 new tests (6 encoder + 3 decoder). 324/324 passing.\n- **2026-05-19** — Vicki keep-alive bit-by-bit audit (current docs `/vicki-lorawan-device-communication-protocol/keep-alive`). Only one bit was missing from `vickiPayloadParser`: byte 8 bit 1 → `batteryTooLow`. Pre-4.6 docs label that bit as "Reserved", but current spec exposes it. Added `batteryTooLow: boolean` to `VickiKeepAliveData` type and parser. All 5 existing Vicki keepalive snapshots updated (`batteryTooLow: false` since byte 8 = `0x30`/`0x34` in fixtures). New regression test with byte 8 = `0x32` confirms `batteryTooLow: true`. All other byte 7/8 bits were already covered.\n- **2026-05-19** — Vicki D2D / HT-sensor protocol additions (per internal _Vicki new commands (7).odt_). **Encoder:** added `setD2dNotificationDeviceAppKey({ appKey: hex32 })` (`0x6f` + 16-byte AppKey), `getD2dNotificationDeviceAppKey()` (`0x70`), `requestListenForD2dNotificationDevice()` (`0x71`). Extended `VickiCommandSchemas.setOperationalMode` enum + `VickiEnums.setOperationalMode` to include mode `'03'` (Online automatic with HT-sensor D2D temp). **Decoder:** added Vicki branches to `case '70'` (16-byte AppKey → `d2dNotificationDeviceAppKey` as uppercase hex) and `case '72'` (2-byte temp/10 → `htSensorTemperature`) in `commandsReadingHelper.ts`; other devices' existing handling on those bytes is preserved. **Keep-alive:** `vickiPayloadParser` now exposes `d2dCommunicationReliable` (byte 8 bit 2, previously reserved). `VickiKeepAliveData` type updated. Existing keepalive snapshot tests updated to include the new field (all `false` since byte 8 = `0x30`). 8 new tests added (3 decoder + 5 encoder). 314/314 passing.\n- **2026-05-19** — Thermostat-family audit (WirelessThermostat, FanCoilThermostat, T-Valve) against official cheat-sheets. **WirelessThermostat** (`WirelessThermostatCommands.ts`): added 5 commands — `0x55`/`0x56` `setSensorCompensationTemperature`/`getSensorCompensationTemperature` (sign-byte + abs\*10), `0x5d`/`0x5e` `setTemperatureMeasurementPeriod`/`getTemperatureMeasurementPeriod` (minutes 1–255), and `0xa5` `restartDevice`. **FanCoilThermostat** (`FanCoilThermostatCommands.ts`): added 6 commands — `0x2f` `getTargetTemperature`, `0x3b` `setExtTemperatureSensor1C` (int 1–99°C), `0x3c` `setExtTemperatureSensor` (0.1–99.9°C, 2-byte payload), `0x3e` `getExtTemperatureSensor`, `0x74` `getModeChangedByAutoChangeover`, `0x75` `getPowerModuleCommunicationStatus`. Also fixed two pre-existing `BaseCommand`-name bugs in FCT: the `0x77` get-method emitted name `'SetHeatingCoolingTargetTempRangesUnoccupied'` (now `'Get…'`) and `0x79` emitted `'getFanOffDelayTime'` (now `'GetFanOffDelayTime'`). **T-Valve** (`TValveCommands.ts`): added `0x0b` `deactivateDevice`. Matching Zod schemas + type exports added in `schemas.ts`; 14 new tests added (encode + zod-fail where applicable). **Out of scope** for this batch: `Thermostat` (T-Ring) and `MelissaLorawan` — no public cheat-sheets exist at `docs.mclimate.eu` for these two; their command sets are bespoke (TRing uses 6-byte fixed-format commands, Melissa only has IR recording at `0x09`) and verifying against firmware would require the internal MClimate spec, not the public docs.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"commandsReadingHelper.d.ts","sourceRoot":"","sources":["../../src/decoders/commandsReadingHelper.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAA;AAG5D,eAAO,MAAM,qBAAqB,YAAa,MAAM,iBAAiB,MAAM,cAAc,UAAU,
|
|
1
|
+
{"version":3,"file":"commandsReadingHelper.d.ts","sourceRoot":"","sources":["../../src/decoders/commandsReadingHelper.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAA;AAG5D,eAAO,MAAM,qBAAqB,YAAa,MAAM,iBAAiB,MAAM,cAAc,UAAU,mBA82FnG,CAAA"}
|
|
@@ -1801,8 +1801,15 @@ const commandsReadingHelper = (hexData, payloadLength, deviceType) => {
|
|
|
1801
1801
|
case '53':
|
|
1802
1802
|
{
|
|
1803
1803
|
try {
|
|
1804
|
-
|
|
1805
|
-
|
|
1804
|
+
let data;
|
|
1805
|
+
if (deviceType === types_1.DeviceType.FanCoilThermostat) {
|
|
1806
|
+
command_len = 1;
|
|
1807
|
+
data = { fctOperationalMode: parseInt(commands[i + 1], 16) };
|
|
1808
|
+
}
|
|
1809
|
+
else {
|
|
1810
|
+
command_len = 1;
|
|
1811
|
+
data = { targetTemperatureStep: parseInt(commands[i + 1], 16) / 10 };
|
|
1812
|
+
}
|
|
1806
1813
|
Object.assign(resultToPass, Object.assign({}, resultToPass), Object.assign({}, data));
|
|
1807
1814
|
}
|
|
1808
1815
|
catch (e) {
|
|
@@ -2299,6 +2306,15 @@ const commandsReadingHelper = (hexData, payloadLength, deviceType) => {
|
|
|
2299
2306
|
command_len = 2;
|
|
2300
2307
|
data = { overheatingRecoveryTime: (parseInt(commands[i + 1], 16) << 8) | parseInt(commands[i + 2], 16) };
|
|
2301
2308
|
}
|
|
2309
|
+
else if (deviceType === types_1.DeviceType.Vicki) {
|
|
2310
|
+
command_len = 16;
|
|
2311
|
+
data = {
|
|
2312
|
+
d2dNotificationDeviceAppKey: commands
|
|
2313
|
+
.slice(i + 1, i + 17)
|
|
2314
|
+
.join('')
|
|
2315
|
+
.toUpperCase(),
|
|
2316
|
+
};
|
|
2317
|
+
}
|
|
2302
2318
|
Object.assign(resultToPass, Object.assign({}, resultToPass), Object.assign({}, data));
|
|
2303
2319
|
}
|
|
2304
2320
|
catch (e) {
|
|
@@ -2349,6 +2365,12 @@ const commandsReadingHelper = (hexData, payloadLength, deviceType) => {
|
|
|
2349
2365
|
command_len = 1;
|
|
2350
2366
|
data = { overcurrentRecoveryTemp: parseInt(commands[i + 1], 16) };
|
|
2351
2367
|
}
|
|
2368
|
+
else if (deviceType === types_1.DeviceType.Vicki) {
|
|
2369
|
+
command_len = 2;
|
|
2370
|
+
data = {
|
|
2371
|
+
htSensorTemperature: ((parseInt(commands[i + 1], 16) << 8) | parseInt(commands[i + 2], 16)) / 10,
|
|
2372
|
+
};
|
|
2373
|
+
}
|
|
2352
2374
|
Object.assign(resultToPass, Object.assign({}, resultToPass), Object.assign({}, data));
|
|
2353
2375
|
}
|
|
2354
2376
|
catch (e) {
|
|
@@ -3001,6 +3023,46 @@ const commandsReadingHelper = (hexData, payloadLength, deviceType) => {
|
|
|
3001
3023
|
}
|
|
3002
3024
|
}
|
|
3003
3025
|
break;
|
|
3026
|
+
case 'aa':
|
|
3027
|
+
{
|
|
3028
|
+
try {
|
|
3029
|
+
if (deviceType === types_1.DeviceType.HTSensor) {
|
|
3030
|
+
command_len = 1;
|
|
3031
|
+
const data = { d2dCommunicationState: parseInt(commands[i + 1], 16) === 1 };
|
|
3032
|
+
Object.assign(resultToPass, Object.assign({}, resultToPass), Object.assign({}, data));
|
|
3033
|
+
}
|
|
3034
|
+
}
|
|
3035
|
+
catch (e) {
|
|
3036
|
+
throw new utils_1.CustomError({
|
|
3037
|
+
message: `Failed to process command 'aa'`,
|
|
3038
|
+
hexData,
|
|
3039
|
+
command,
|
|
3040
|
+
deviceType,
|
|
3041
|
+
originalError: e,
|
|
3042
|
+
});
|
|
3043
|
+
}
|
|
3044
|
+
}
|
|
3045
|
+
break;
|
|
3046
|
+
case 'ac':
|
|
3047
|
+
{
|
|
3048
|
+
try {
|
|
3049
|
+
if (deviceType === types_1.DeviceType.HTSensor) {
|
|
3050
|
+
command_len = 1;
|
|
3051
|
+
const data = { d2dCommunicationPeriod: parseInt(commands[i + 1], 16) };
|
|
3052
|
+
Object.assign(resultToPass, Object.assign({}, resultToPass), Object.assign({}, data));
|
|
3053
|
+
}
|
|
3054
|
+
}
|
|
3055
|
+
catch (e) {
|
|
3056
|
+
throw new utils_1.CustomError({
|
|
3057
|
+
message: `Failed to process command 'ac'`,
|
|
3058
|
+
hexData,
|
|
3059
|
+
command,
|
|
3060
|
+
deviceType,
|
|
3061
|
+
originalError: e,
|
|
3062
|
+
});
|
|
3063
|
+
}
|
|
3064
|
+
}
|
|
3065
|
+
break;
|
|
3004
3066
|
default:
|
|
3005
3067
|
decodeKeepalive(commands, payloadLength, deviceType);
|
|
3006
3068
|
commands.splice(i, commands.length);
|