mclimate-payload-helper 1.0.59 → 1.1.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.
Files changed (76) hide show
  1. package/CLAUDE.md +66 -0
  2. package/add-encoder-command-existing.md +299 -0
  3. package/add-encoder-command-new.md +349 -0
  4. package/dist/decoders/commandsReadingHelper.d.ts.map +1 -1
  5. package/dist/decoders/commandsReadingHelper.js +45 -7
  6. package/dist/decoders/commandsReadingHelper.js.map +1 -1
  7. package/dist/encoders/Co2DisplayCommonCommands.d.ts +0 -4
  8. package/dist/encoders/Co2DisplayCommonCommands.d.ts.map +1 -1
  9. package/dist/encoders/Co2DisplayCommonCommands.js +0 -38
  10. package/dist/encoders/Co2DisplayCommonCommands.js.map +1 -1
  11. package/dist/encoders/Co2PirLiteCommands.d.ts.map +1 -1
  12. package/dist/encoders/Co2PirLiteCommands.js +1 -5
  13. package/dist/encoders/Co2PirLiteCommands.js.map +1 -1
  14. package/dist/encoders/FanCoilThermostat.d.ts +61 -0
  15. package/dist/encoders/FanCoilThermostat.d.ts.map +1 -0
  16. package/dist/encoders/FanCoilThermostat.js +719 -0
  17. package/dist/encoders/FanCoilThermostat.js.map +1 -0
  18. package/dist/encoders/FanCoilThermostatCommands.d.ts +3 -3
  19. package/dist/encoders/FanCoilThermostatCommands.d.ts.map +1 -1
  20. package/dist/encoders/FanCoilThermostatCommands.js.map +1 -1
  21. package/dist/encoders/GeneralCommands.js +1 -1
  22. package/dist/encoders/GeneralCommands.js.map +1 -1
  23. package/dist/encoders/Relay16Commands.d.ts +5 -0
  24. package/dist/encoders/Relay16Commands.d.ts.map +1 -1
  25. package/dist/encoders/Relay16Commands.js +56 -2
  26. package/dist/encoders/Relay16Commands.js.map +1 -1
  27. package/dist/encoders/SetAqiLed.d.ts +6 -0
  28. package/dist/encoders/SetAqiLed.d.ts.map +1 -0
  29. package/dist/encoders/SetAqiLed.js +46 -0
  30. package/dist/encoders/SetAqiLed.js.map +1 -0
  31. package/dist/encoders/types/enumValidation.d.ts +6 -0
  32. package/dist/encoders/types/enumValidation.d.ts.map +1 -0
  33. package/dist/encoders/types/enumValidation.js +50 -0
  34. package/dist/encoders/types/enumValidation.js.map +1 -0
  35. package/dist/encoders/types/schemaValidatedEnums.d.ts +40 -0
  36. package/dist/encoders/types/schemaValidatedEnums.d.ts.map +1 -0
  37. package/dist/encoders/types/schemaValidatedEnums.js +181 -0
  38. package/dist/encoders/types/schemaValidatedEnums.js.map +1 -0
  39. package/dist/encoders/types/schemas.d.ts +559 -145
  40. package/dist/encoders/types/schemas.d.ts.map +1 -1
  41. package/dist/encoders/types/schemas.js +402 -224
  42. package/dist/encoders/types/schemas.js.map +1 -1
  43. package/dist/index.d.ts +1 -1
  44. package/dist/index.d.ts.map +1 -1
  45. package/dist/index.js +7 -1
  46. package/dist/index.js.map +1 -1
  47. package/dist/test/payloadDecoders.test.js +18 -2
  48. package/dist/test/payloadDecoders.test.js.map +1 -1
  49. package/dist/test/payloadEncoders.test.js +30 -0
  50. package/dist/test/payloadEncoders.test.js.map +1 -1
  51. package/dist/types/deviceTypes.d.ts +19 -0
  52. package/dist/types/deviceTypes.d.ts.map +1 -0
  53. package/dist/types/deviceTypes.js +23 -0
  54. package/dist/types/deviceTypes.js.map +1 -0
  55. package/dist/types/index.d.ts +2 -0
  56. package/dist/types/index.d.ts.map +1 -0
  57. package/dist/types/index.js +18 -0
  58. package/dist/types/index.js.map +1 -0
  59. package/dist/types/types.d.ts +13 -0
  60. package/dist/types/types.d.ts.map +1 -0
  61. package/dist/types/types.js +9 -0
  62. package/dist/types/types.js.map +1 -0
  63. package/dist/validation.d.ts +3 -0
  64. package/dist/validation.d.ts.map +1 -0
  65. package/dist/validation.js +8 -0
  66. package/dist/validation.js.map +1 -0
  67. package/package.json +1 -1
  68. package/src/decoders/commandsReadingHelper.ts +50 -11
  69. package/src/encoders/Co2PirLiteCommands.ts +28 -32
  70. package/src/encoders/FanCoilThermostatCommands.ts +22 -6
  71. package/src/encoders/GeneralCommands.ts +1 -1
  72. package/src/encoders/Relay16Commands.ts +57 -2
  73. package/src/encoders/types/schemas.ts +445 -224
  74. package/src/index.ts +9 -1
  75. package/src/test/payloadDecoders.test.ts +18 -2
  76. package/src/test/payloadEncoders.test.ts +50 -0
package/CLAUDE.md ADDED
@@ -0,0 +1,66 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Common Development Commands
6
+
7
+ ### Building the Package
8
+ ```bash
9
+ npm run build # Compiles TypeScript and resolves path aliases
10
+ ```
11
+
12
+ ### Running Tests
13
+ ```bash
14
+ npm test # Runs all tests
15
+ npm test -- [pattern] # Run specific test files
16
+ ```
17
+
18
+ ### Publishing
19
+ ```bash
20
+ npm publish # Publishes to npm (runs build automatically via prepublishOnly)
21
+ ```
22
+
23
+ ## Architecture Overview
24
+
25
+ This is a TypeScript library for encoding and decoding payloads for MClimate IoT devices. The codebase is organized into three main areas:
26
+
27
+ ### 1. Decoders (`src/decoders/`)
28
+ - **Purpose**: Parse incoming device payloads (hex strings) into structured data
29
+ - **Entry Point**: `uplinkPayloadParser` in `src/decoders/payloadParsers/uplinkPayloadParser.ts`
30
+ - **Device Parsers**: Individual parser classes in `src/decoders/payloadParsers/` (e.g., `VickiPayloadParser`, `HTSensorPayloadParser`)
31
+ - **Command Reading**: `commandsReadingHelper.ts` handles parsing of command responses
32
+
33
+ ### 2. Encoders (`src/encoders/`)
34
+ - **Purpose**: Build commands to send to devices
35
+ - **Entry Point**: `CommandBuilder` class that routes to device-specific command classes
36
+ - **Command Classes**: Each device has its own command class (e.g., `VickiCommands`, `HTSensorCommands`)
37
+ - **Inheritance**: All device commands extend `GeneralCommands` and may use mixins for shared functionality
38
+ - **Schemas**: Command validation schemas in `src/encoders/types/schemas.ts`
39
+
40
+ ### 3. Type System
41
+ - **Device Types**: Enumerated in `src/decoders/payloadParsers/types/allDevices.ts`
42
+ - **Path Aliases**: Uses `@/` prefix for absolute imports from `src/`
43
+ - **Zod Schemas**: Used for command parameter validation
44
+
45
+ ## Key Implementation Patterns
46
+
47
+ ### Adding New Devices
48
+ 1. Add device type to `DeviceType` enum
49
+ 2. Create payload parser class and export it
50
+ 3. Add parser case to `uplinkPayloadParser` switch
51
+ 4. Create command class extending `GeneralCommands`
52
+ 5. Add command class to `CommandBuilder` registry in `src/encoders/CommandBuilder.ts`:
53
+ - Import the new command class
54
+ - Add entry to `this.commandRegistry` in constructor
55
+ 6. Define Zod schema for command validation
56
+
57
+ ### Command Structure
58
+ - Commands use hexadecimal encoding
59
+ - Each command extends `BaseCommand`
60
+ - Commands can be combined using `CommandBuilder.combine()`
61
+ - Mixins used for shared command sets (e.g., `TemperatureCommonCommands`)
62
+
63
+ ### Testing
64
+ - Tests located in `src/test/`
65
+ - Separate test files for decoders and encoders
66
+ - Uses Jest with TypeScript support
@@ -0,0 +1,299 @@
1
+ # Add Encoder Command Workflow - Existing Device Classes
2
+
3
+ This command guides you through adding new device commands in the encoder system. Follow each step and provide input when prompted.
4
+
5
+ ## Prerequisites
6
+
7
+ Ensure you have access to:
8
+
9
+ - `src/encoders/` directory
10
+ - `src/encoders/types/schemas.ts` file
11
+ - Knowledge of existing device command classes for reference
12
+
13
+ For internal imports always use the @ syntax e.g.:
14
+
15
+ ```typescript
16
+ import { applyMixins, delMethods } from '@/utils'
17
+ ```
18
+
19
+ ## Step 1: Locate the Device Command Class
20
+
21
+ ### 🔍 ASK: What is the name of the device command class?
22
+
23
+ Once the user provides the class name, locate the file, it will be in:
24
+
25
+ **File**: `src/encoders/[YourDeviceCommands].ts`
26
+
27
+ ## Step 2: Add Commands
28
+
29
+ ### 🔍 ASK: What commands do you want to add?
30
+
31
+ For each command, provide:
32
+
33
+ 1. **Command name** (e.g., `setTemperature`, `getTemperature`)
34
+ 2. **Static function parameters** with their types (for "set" commands)
35
+ 3. **Additional parameters for BaseCommand** (the hex value and any additional parameters like the decToHex utility function)
36
+
37
+ **Format**:
38
+
39
+ ```
40
+ Command: setTemperature
41
+ Static Function Parameters: 1) value: number, 2) state: boolean
42
+ Additional Parameters: Ask user for the additional parameter values
43
+
44
+ Command: getTemperature
45
+ Static Function Parameters: none
46
+ Additional Parameters: Ask user for the additional parameter values
47
+ ```
48
+
49
+ ### Command Templates
50
+
51
+ **For commands WITH parameters:**
52
+
53
+ ```typescript
54
+ static [commandName](params: [YourDeviceCommandTypes].[CommandNameParams]) {
55
+ try {
56
+ DeviceCommandSchemas.[YourDeviceCommandSchemas].[commandName].parse(params)
57
+ // Command implementation here
58
+ return new BaseCommand('[CommandName]', [ADDITIONAL_PARAMETERS])
59
+ } catch (e) {
60
+ if (e instanceof ZodError) {
61
+ throw new CustomError({
62
+ message: 'Zod validation error during [CommandName] execution',
63
+ command: '[CommandName]',
64
+ originalError: e,
65
+ })
66
+ } else {
67
+ throw new CustomError({
68
+ message: 'Error during [CommandName] execution',
69
+ command: '[CommandName]',
70
+ originalError: e as Error,
71
+ })
72
+ }
73
+ }
74
+ }
75
+ ```
76
+
77
+ **For commands WITHOUT parameters:**
78
+
79
+ ```typescript
80
+ static [commandName]() {
81
+ return new BaseCommand('[CommandName]', [ADDITIONAL_PARAMETERS])
82
+ }
83
+ ```
84
+
85
+ **IMPORTANT**:
86
+
87
+ - All functions are `static`
88
+ - First parameter of `new BaseCommand` is always the command name in CamelCase
89
+ - For the second parameter and any additional parameters, ASK THE USER for input
90
+ - Always include proper try/catch blocks for commands with parameters
91
+ - Use the newly (to be) created schemas for validation
92
+ - Add any new commands below the existing ones
93
+
94
+ ## Step 3: Apply Mixins (Optional)
95
+
96
+ ### 🔍 ASK: Does your device extend any additional functionality from other command classes?
97
+
98
+ Common extensions:
99
+
100
+ - `Temperature` - for temperature-related commands
101
+ - `Display` - for display-related commands
102
+ - `Sensor` - for sensor-specific commands
103
+
104
+ If yes, specify which additional classes to extend.
105
+
106
+ ### Mixin Template
107
+
108
+ Add at the bottom of your command class file:
109
+
110
+ ```typescript
111
+ import { applyMixins } from '../utils/applyMixins'
112
+
113
+ // Apply mixins
114
+ applyMixins([YourDeviceCommands], [Temperature, Display])
115
+ ```
116
+
117
+ ## Step 4: Exclude Methods (Optional)
118
+
119
+ ### 🔍 ASK: Are there any additional inherited methods you want to exclude?
120
+
121
+ If you're inheriting from other classes but don't want certain methods, list them.
122
+
123
+ **Example**: Excluding `setBacklight` and `setBrightness` from Display
124
+
125
+ ### Exclusion Template
126
+
127
+ ```typescript
128
+ import { delMethods } from '../utils/delMethods'
129
+
130
+ // Remove unwanted methods
131
+ delMethods([YourDeviceCommands], ['setBacklight', 'setBrightness'])
132
+ ```
133
+
134
+ ## Step 5: Add to Schema and Types in schemas.ts
135
+
136
+ ### 🔍 ASK: What parameters does each new command need for the scehma validation?
137
+
138
+ For each custom command from Step 2, provide the parameter definitions and their types for schema validation.
139
+
140
+ **IMPORTANT**: Schemas must be added to `src/encoders/types/schemas.ts` in the appropriate sections.
141
+
142
+ ### Schema Creation Steps
143
+
144
+ 1. **Add Command Schemas**: Create a new schema group in `schemas.ts` following the established pattern
145
+ 2. **Add Type Namespace**: Create corresponding TypeScript types
146
+ 3. **Export Schema Group**: Add to the `DeviceCommandSchemas` export at the bottom
147
+
148
+ ### Schema Template
149
+
150
+ **Add this to `src/encoders/types/schemas.ts`:**
151
+
152
+ ```typescript
153
+ /* --------------------------------------- [YOUR DEVICE] COMMANDS --------------------------------------- */
154
+
155
+ const [YourDeviceCommandSchemas] = {
156
+ ...GeneralCommandSchemas, // Always include this
157
+ // Add your custom command schemas here
158
+ [customCommand]: z.object({
159
+ [paramName]: z.[paramType](),
160
+ // Add all parameters with validation
161
+ }),
162
+ [anotherCommand]: z.object({}), // For commands with no params
163
+ }
164
+
165
+ export namespace [YourDeviceCommandTypes] {
166
+ // Only create types for commands that have parameters (typically "set" commands)
167
+ export type [CustomCommandParams] = z.infer<typeof [YourDeviceCommandSchemas].[customCommand]>
168
+ // Add more parameter types as needed
169
+ }
170
+ ```
171
+
172
+ ### Important Schema Guidelines
173
+
174
+ 1. **Location**: Add schemas directly to `@src/encoders/types/schemas.ts`
175
+ 2. **Pattern**: Follow the exact pattern used by other device schemas in the file
176
+ 3. **Types**: Only create type exports for commands with parameters
177
+ 4. **Export**: Add your schema to the `DeviceCommandSchemas` export at the bottom of the file
178
+ 5. Ensure there are no formatting issues - e.g. closing brackets, end of comments etc.
179
+
180
+ ### Schema Example
181
+
182
+ **Real example from existing CO2 PIR Lite commands in schemas.ts:**
183
+
184
+ ```typescript
185
+ /* --------------------------------------- CO2 PIR LITE COMMANDS --------------------------------------- */
186
+ const Co2PirLiteCommandSchemas = {
187
+ ...GeneralCommandSchemas,
188
+ ...PIRCommandSchemas,
189
+ setUplinkSendingOnButtonPress: z.object({
190
+ value: z.number(),
191
+ }),
192
+ getUplinkSendingOnButtonPress: z.object({}),
193
+ restartDevice: z.object({}),
194
+ setCo2BoundaryLevels: z.object({
195
+ good_medium: z.number(),
196
+ medium_bad: z.number(),
197
+ }),
198
+ getCo2BoundaryLevels: z.object({}),
199
+ // ... more commands
200
+ }
201
+
202
+ export namespace Co2PirLiteCommandTypes {
203
+ export type SetUplinkSendingOnButtonPressParams = z.infer<
204
+ typeof Co2PirLiteCommandSchemas.setUplinkSendingOnButtonPress
205
+ >
206
+ export type RestartDeviceParams = z.infer<typeof Co2PirLiteCommandSchemas.restartDevice>
207
+ export type SetCo2BoundaryLevelsParams = z.infer<typeof Co2PirLiteCommandSchemas.setCo2BoundaryLevels>
208
+ }
209
+ ```
210
+
211
+ **Then at the bottom of schemas.ts, add to DeviceCommandSchemas export:**
212
+
213
+ ```typescript
214
+ export const DeviceCommandSchemas = {
215
+ // ... existing schemas
216
+ Co2PirLiteCommandSchemas,
217
+ [YourNewDeviceCommandSchemas], // Add your new schema here
218
+ }
219
+ ```
220
+
221
+ ## Step 6: Validation Checklist
222
+
223
+ Before completing:
224
+
225
+ - [ ] Device command class created as a standalone class (no inheritance from GeneralCommands)
226
+ - [ ] All methods are `static`
227
+ - [ ] Commands with parameters follow the try/catch pattern with ZodError checking
228
+ - [ ] Commands without parameters use the simple static return pattern
229
+ - [ ] All commands return `new BaseCommand` with correct CamelCase command name as first parameter
230
+ - [ ] Schemas in `src/encoders/types/schemas.ts` updated following established patterns
231
+ - [ ] Type namespace created for commands with parameters
232
+ - [ ] Each command function uses the newly created schemas for validation
233
+
234
+ ## Example: Complete Implementation Following New Conventions
235
+
236
+ **Example Command Class:**
237
+
238
+ ```typescript
239
+ // src/encoders/MyDeviceCommands.ts
240
+ import { BaseCommand } from '@/encoders'
241
+ import { ZodError } from 'zod'
242
+ import { CustomError } from '@/utils'
243
+ import { MyDeviceCommandTypes, DeviceCommandSchemas } from '@/encoders/types'
244
+
245
+ export class MyDeviceCommands {
246
+ static setTemperature(params: MyDeviceCommandTypes.SetTemperatureParams) {
247
+ try {
248
+ DeviceCommandSchemas.MyDeviceCommandSchemas.setTemperature.parse(params)
249
+ const { value } = params
250
+ return new BaseCommand('SetTemperature', 0x15, value)
251
+ } catch (e) {
252
+ if (e instanceof ZodError) {
253
+ throw new CustomError({
254
+ message: 'Zod validation error during SetTemperature execution',
255
+ command: 'SetTemperature',
256
+ originalError: e,
257
+ })
258
+ } else {
259
+ throw new CustomError({
260
+ message: 'Error during SetTemperature execution',
261
+ command: 'SetTemperature',
262
+ originalError: e as Error,
263
+ })
264
+ }
265
+ }
266
+ }
267
+
268
+ static getTemperature() {
269
+ return new BaseCommand('GetTemperature', 0x16)
270
+ }
271
+ }
272
+ ```
273
+
274
+ **Example Schema Addition to schemas.ts:**
275
+
276
+ ```typescript
277
+ /* --------------------------------------- MY DEVICE COMMANDS --------------------------------------- */
278
+ const MyDeviceCommandSchemas = {
279
+ ...GeneralCommandSchemas,
280
+ setTemperature: z.object({
281
+ value: z.number().min(-40).max(85),
282
+ }),
283
+ getTemperature: z.object({}),
284
+ }
285
+
286
+ export namespace MyDeviceCommandTypes {
287
+ export type SetTemperatureParams = z.infer<typeof MyDeviceCommandSchemas.setTemperature>
288
+ }
289
+
290
+ // Then add to the export at bottom:
291
+ export const DeviceCommandSchemas = {
292
+ // ... existing schemas
293
+ MyDeviceCommandSchemas,
294
+ }
295
+ ```
296
+
297
+ ## Ready to Start?
298
+
299
+ When you're ready, provide the answers to the ASK prompts and the implementation will be generated step by step.
@@ -0,0 +1,349 @@
1
+ # Add Encoder Command Workflow - New Device Classes
2
+
3
+ This command guides you through adding new device commands in the encoder system. Follow each step and provide input when prompted.
4
+
5
+ ## Prerequisites
6
+
7
+ Ensure you have access to:
8
+
9
+ - `src/encoders/` directory
10
+ - `src/encoders/types/schemas.ts` file
11
+ - Knowledge of existing device command classes for reference
12
+
13
+ For internal imports always use the @ syntax e.g.:
14
+
15
+ ```typescript
16
+ import { applyMixins, delMethods } from '@/utils'
17
+ ```
18
+
19
+ ## Step 1: Create Device Command Class
20
+
21
+ ### 🔍 ASK: What is the name of your device command class?
22
+
23
+ **Naming Convention**: Follow the pattern of existing classes (e.g., `CO2DisplayCommands`, `TemperatureSensorCommands`)
24
+
25
+ ```
26
+ Example: MyDeviceCommands
27
+ ```
28
+
29
+ Once you provide the class name, the following file will be created:
30
+
31
+ **File**: `src/encoders/[YourDeviceCommands].ts`
32
+
33
+ ```typescript
34
+ import { BaseCommand, GeneralCommands } from '@/encoders'
35
+ import { ZodError } from 'zod'
36
+ import { CustomError } from '@/utils'
37
+ import { DeviceCommandSchemas } from '@/encoders/types'
38
+
39
+ export class [YourDeviceCommands] extends GeneralCommands {
40
+ // All methods will be static and follow the established patterns
41
+ // Commands will be added in Step 2
42
+ }
43
+ ```
44
+
45
+ ## Step 2: Add Commands
46
+
47
+ ### 🔍 ASK: What commands do you want to add?
48
+
49
+ For each command, provide:
50
+
51
+ 1. **Command name** (e.g., `setTemperature`, `getTemperature`)
52
+ 2. **Static function parameters** with their types (for "set" commands)
53
+ 3. **Additional parameters for BaseCommand** (the hex value and any additional parameters like the decToHex utility function)
54
+
55
+ **Format**:
56
+
57
+ ```
58
+ Command: setTemperature
59
+ Static Function Parameters: 1) value: number, 2) state: boolean
60
+ Additional Parameters: Ask user for the additional parameter values
61
+
62
+ Command: getTemperature
63
+ Static Function Parameters: none
64
+ Additional Parameters: Ask user for the additional parameter values
65
+ ```
66
+
67
+ ### Command Templates
68
+
69
+ **For commands WITH parameters:**
70
+
71
+ ```typescript
72
+ static [commandName](params: [YourDeviceCommandTypes].[CommandNameParams]) {
73
+ try {
74
+ DeviceCommandSchemas.[YourDeviceCommandSchemas].[commandName].parse(params)
75
+ // Command implementation here
76
+ return new BaseCommand('[CommandName]', [ADDITIONAL_PARAMETERS])
77
+ } catch (e) {
78
+ if (e instanceof ZodError) {
79
+ throw new CustomError({
80
+ message: 'Zod validation error during [CommandName] execution',
81
+ command: '[CommandName]',
82
+ originalError: e,
83
+ })
84
+ } else {
85
+ throw new CustomError({
86
+ message: 'Error during [CommandName] execution',
87
+ command: '[CommandName]',
88
+ originalError: e as Error,
89
+ })
90
+ }
91
+ }
92
+ }
93
+ ```
94
+
95
+ **For commands WITHOUT parameters:**
96
+
97
+ ```typescript
98
+ static [commandName]() {
99
+ return new BaseCommand('[CommandName]', [ADDITIONAL_PARAMETERS])
100
+ }
101
+ ```
102
+
103
+ **IMPORTANT**:
104
+
105
+ - All functions are `static`
106
+ - First parameter of `new BaseCommand` is always the command name in CamelCase
107
+ - For the second parameter and any additional parameters, ASK THE USER for input
108
+ - Always include proper try/catch blocks for commands with parameters
109
+ - Use the newly (to be) created schemas for validation
110
+
111
+ ## Step 3: Apply Mixins (Optional)
112
+
113
+ ### 🔍 ASK: Does your device extend functionality from other command classes?
114
+
115
+ Common extensions:
116
+
117
+ - `Temperature` - for temperature-related commands
118
+ - `Display` - for display-related commands
119
+ - `Sensor` - for sensor-specific commands
120
+
121
+ If yes, specify which classes to extend.
122
+
123
+ ### Mixin Template
124
+
125
+ Add at the bottom of your command class file:
126
+
127
+ ```typescript
128
+ import { applyMixins } from '../utils/applyMixins'
129
+
130
+ // Apply mixins
131
+ applyMixins([YourDeviceCommands], [Temperature, Display])
132
+ ```
133
+
134
+ ## Step 4: Exclude Methods (Optional)
135
+
136
+ ### 🔍 ASK: Are there any inherited methods you want to exclude?
137
+
138
+ If you're inheriting from other classes but don't want certain methods, list them.
139
+
140
+ **Example**: Excluding `setBacklight` and `setBrightness` from Display
141
+
142
+ ### Exclusion Template
143
+
144
+ ```typescript
145
+ import { delMethods } from '../utils/delMethods'
146
+
147
+ // Remove unwanted methods
148
+ delMethods([YourDeviceCommands], ['setBacklight', 'setBrightness'])
149
+ ```
150
+
151
+ ## Step 5: Update Index File
152
+
153
+ Add your new class to `src/encoders/index.ts`:
154
+
155
+ ```typescript
156
+ export { [YourDeviceCommands] } from './[YourDeviceCommands]';
157
+ ```
158
+
159
+ ## Step 6: Add to CommandBuilder Registry
160
+
161
+ Add your new command class to the CommandBuilder registry in `src/encoders/CommandBuilder.ts`:
162
+
163
+ 1. **Import** your new command class at the top of the file
164
+ 2. **Add entry** to `this.commandRegistry` in the constructor
165
+
166
+ ```typescript
167
+ // At the top with other imports
168
+ import { [YourDeviceCommands] } from './[YourDeviceCommands]';
169
+
170
+ // In constructor's commandRegistry
171
+ this.commandRegistry = {
172
+ // ... existing entries
173
+ [device_type_key]: [YourDeviceCommands],
174
+ }
175
+ ```
176
+
177
+ **Note**: The `device_type_key` should match the corresponding `DeviceType` enum value (in snake_case format). Confirm with user whether the chose name is correct.
178
+
179
+ ## Step 7: Create Schema and Types in schemas.ts
180
+
181
+ ### 🔍 ASK: What parameters does each command need for the scehma validation?
182
+
183
+ For each custom command from Step 2, provide the parameter definitions and their types for schema validation.
184
+
185
+ **IMPORTANT**: Schemas must be added to `src/encoders/types/schemas.ts` in the appropriate sections.
186
+
187
+ ### Schema Creation Steps
188
+
189
+ 1. **Add Command Schemas**: Create a new schema group in `schemas.ts` following the established pattern
190
+ 2. **Add Type Namespace**: Create corresponding TypeScript types
191
+ 3. **Export Schema Group**: Add to the `DeviceCommandSchemas` export at the bottom
192
+
193
+ ### Schema Template
194
+
195
+ **Add this to `src/encoders/types/schemas.ts`:**
196
+
197
+ ```typescript
198
+ /* --------------------------------------- [YOUR DEVICE] COMMANDS --------------------------------------- */
199
+
200
+ const [YourDeviceCommandSchemas] = {
201
+ ...GeneralCommandSchemas, // Always include this
202
+ // Add your custom command schemas here
203
+ [customCommand]: z.object({
204
+ [paramName]: z.[paramType](),
205
+ // Add all parameters with validation
206
+ }),
207
+ [anotherCommand]: z.object({}), // For commands with no params
208
+ }
209
+
210
+ export namespace [YourDeviceCommandTypes] {
211
+ // Only create types for commands that have parameters (typically "set" commands)
212
+ export type [CustomCommandParams] = z.infer<typeof [YourDeviceCommandSchemas].[customCommand]>
213
+ // Add more parameter types as needed
214
+ }
215
+ ```
216
+
217
+ ### Important Schema Guidelines
218
+
219
+ 1. **Location**: Add schemas directly to `@src/encoders/types/schemas.ts`
220
+ 2. **Pattern**: Follow the exact pattern used by other device schemas in the file
221
+ 3. **Inheritance**: Always include `...GeneralCommandSchemas`
222
+ 4. **Types**: Only create type exports for commands with parameters
223
+ 5. **Export**: Add your schema to the `DeviceCommandSchemas` export at the bottom of the file
224
+ 6. Ensure there are no formatting issues - e.g. closing brackets, end of comments etc.
225
+
226
+ ### Schema Example
227
+
228
+ **Real example from existing CO2 PIR Lite commands in schemas.ts:**
229
+
230
+ ```typescript
231
+ /* --------------------------------------- CO2 PIR LITE COMMANDS --------------------------------------- */
232
+ const Co2PirLiteCommandSchemas = {
233
+ ...GeneralCommandSchemas,
234
+ ...PIRCommandSchemas,
235
+ setUplinkSendingOnButtonPress: z.object({
236
+ value: z.number(),
237
+ }),
238
+ getUplinkSendingOnButtonPress: z.object({}),
239
+ restartDevice: z.object({}),
240
+ setCo2BoundaryLevels: z.object({
241
+ good_medium: z.number(),
242
+ medium_bad: z.number(),
243
+ }),
244
+ getCo2BoundaryLevels: z.object({}),
245
+ // ... more commands
246
+ }
247
+
248
+ export namespace Co2PirLiteCommandTypes {
249
+ export type SetUplinkSendingOnButtonPressParams = z.infer<
250
+ typeof Co2PirLiteCommandSchemas.setUplinkSendingOnButtonPress
251
+ >
252
+ export type RestartDeviceParams = z.infer<typeof Co2PirLiteCommandSchemas.restartDevice>
253
+ export type SetCo2BoundaryLevelsParams = z.infer<typeof Co2PirLiteCommandSchemas.setCo2BoundaryLevels>
254
+ }
255
+ ```
256
+
257
+ **Then at the bottom of schemas.ts, add to DeviceCommandSchemas export:**
258
+
259
+ ```typescript
260
+ export const DeviceCommandSchemas = {
261
+ // ... existing schemas
262
+ Co2PirLiteCommandSchemas,
263
+ [YourNewDeviceCommandSchemas], // Add your new schema here
264
+ }
265
+ ```
266
+
267
+ ## Step 8: Validation Checklist
268
+
269
+ Before completing:
270
+
271
+ - [ ] Device command class created as a standalone class (no inheritance from GeneralCommands)
272
+ - [ ] All methods are `static`
273
+ - [ ] Commands with parameters follow the try/catch pattern with ZodError checking
274
+ - [ ] Commands without parameters use the simple static return pattern
275
+ - [ ] All commands return `new BaseCommand` with correct CamelCase command name as first parameter
276
+ - [ ] Class exported in `index.ts`
277
+ - [ ] Command class added to CommandBuilder registry with correct import
278
+ - [ ] Schemas added to `src/encoders/types/schemas.ts` following established patterns
279
+ - [ ] Schema group includes `...GeneralCommandSchemas`
280
+ - [ ] Type namespace created for commands with parameters
281
+ - [ ] Schema added to `DeviceCommandSchemas` export at bottom of schemas.ts
282
+ - [ ] Each command function uses the newly created schemas for validation
283
+
284
+ ## Example: Complete Implementation Following New Conventions
285
+
286
+ **Example Command Class:**
287
+
288
+ ```typescript
289
+ // src/encoders/MyDeviceCommands.ts
290
+ import { BaseCommand } from '@/encoders'
291
+ import { ZodError } from 'zod'
292
+ import { CustomError } from '@/utils'
293
+ import { MyDeviceCommandTypes, DeviceCommandSchemas } from '@/encoders/types'
294
+
295
+ export class MyDeviceCommands {
296
+ static setTemperature(params: MyDeviceCommandTypes.SetTemperatureParams) {
297
+ try {
298
+ DeviceCommandSchemas.MyDeviceCommandSchemas.setTemperature.parse(params)
299
+ const { value } = params
300
+ return new BaseCommand('SetTemperature', 0x15, value)
301
+ } catch (e) {
302
+ if (e instanceof ZodError) {
303
+ throw new CustomError({
304
+ message: 'Zod validation error during SetTemperature execution',
305
+ command: 'SetTemperature',
306
+ originalError: e,
307
+ })
308
+ } else {
309
+ throw new CustomError({
310
+ message: 'Error during SetTemperature execution',
311
+ command: 'SetTemperature',
312
+ originalError: e as Error,
313
+ })
314
+ }
315
+ }
316
+ }
317
+
318
+ static getTemperature() {
319
+ return new BaseCommand('GetTemperature', 0x16)
320
+ }
321
+ }
322
+ ```
323
+
324
+ **Example Schema Addition to schemas.ts:**
325
+
326
+ ```typescript
327
+ /* --------------------------------------- MY DEVICE COMMANDS --------------------------------------- */
328
+ const MyDeviceCommandSchemas = {
329
+ ...GeneralCommandSchemas,
330
+ setTemperature: z.object({
331
+ value: z.number().min(-40).max(85),
332
+ }),
333
+ getTemperature: z.object({}),
334
+ }
335
+
336
+ export namespace MyDeviceCommandTypes {
337
+ export type SetTemperatureParams = z.infer<typeof MyDeviceCommandSchemas.setTemperature>
338
+ }
339
+
340
+ // Then add to the export at bottom:
341
+ export const DeviceCommandSchemas = {
342
+ // ... existing schemas
343
+ MyDeviceCommandSchemas,
344
+ }
345
+ ```
346
+
347
+ ## Ready to Start?
348
+
349
+ When you're ready, provide the answers to the ASK prompts and the implementation will be generated step by step.