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.
- package/CLAUDE.md +66 -0
- package/add-encoder-command-existing.md +299 -0
- package/add-encoder-command-new.md +349 -0
- package/dist/decoders/commandsReadingHelper.d.ts.map +1 -1
- package/dist/decoders/commandsReadingHelper.js +45 -7
- package/dist/decoders/commandsReadingHelper.js.map +1 -1
- package/dist/encoders/Co2DisplayCommonCommands.d.ts +0 -4
- package/dist/encoders/Co2DisplayCommonCommands.d.ts.map +1 -1
- package/dist/encoders/Co2DisplayCommonCommands.js +0 -38
- package/dist/encoders/Co2DisplayCommonCommands.js.map +1 -1
- package/dist/encoders/Co2PirLiteCommands.d.ts.map +1 -1
- package/dist/encoders/Co2PirLiteCommands.js +1 -5
- package/dist/encoders/Co2PirLiteCommands.js.map +1 -1
- package/dist/encoders/FanCoilThermostat.d.ts +61 -0
- package/dist/encoders/FanCoilThermostat.d.ts.map +1 -0
- package/dist/encoders/FanCoilThermostat.js +719 -0
- package/dist/encoders/FanCoilThermostat.js.map +1 -0
- package/dist/encoders/FanCoilThermostatCommands.d.ts +3 -3
- package/dist/encoders/FanCoilThermostatCommands.d.ts.map +1 -1
- package/dist/encoders/FanCoilThermostatCommands.js.map +1 -1
- package/dist/encoders/GeneralCommands.js +1 -1
- package/dist/encoders/GeneralCommands.js.map +1 -1
- package/dist/encoders/Relay16Commands.d.ts +5 -0
- package/dist/encoders/Relay16Commands.d.ts.map +1 -1
- package/dist/encoders/Relay16Commands.js +56 -2
- package/dist/encoders/Relay16Commands.js.map +1 -1
- package/dist/encoders/SetAqiLed.d.ts +6 -0
- package/dist/encoders/SetAqiLed.d.ts.map +1 -0
- package/dist/encoders/SetAqiLed.js +46 -0
- package/dist/encoders/SetAqiLed.js.map +1 -0
- package/dist/encoders/types/enumValidation.d.ts +6 -0
- package/dist/encoders/types/enumValidation.d.ts.map +1 -0
- package/dist/encoders/types/enumValidation.js +50 -0
- package/dist/encoders/types/enumValidation.js.map +1 -0
- package/dist/encoders/types/schemaValidatedEnums.d.ts +40 -0
- package/dist/encoders/types/schemaValidatedEnums.d.ts.map +1 -0
- package/dist/encoders/types/schemaValidatedEnums.js +181 -0
- package/dist/encoders/types/schemaValidatedEnums.js.map +1 -0
- package/dist/encoders/types/schemas.d.ts +559 -145
- package/dist/encoders/types/schemas.d.ts.map +1 -1
- package/dist/encoders/types/schemas.js +402 -224
- package/dist/encoders/types/schemas.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -1
- package/dist/index.js.map +1 -1
- package/dist/test/payloadDecoders.test.js +18 -2
- package/dist/test/payloadDecoders.test.js.map +1 -1
- package/dist/test/payloadEncoders.test.js +30 -0
- package/dist/test/payloadEncoders.test.js.map +1 -1
- package/dist/types/deviceTypes.d.ts +19 -0
- package/dist/types/deviceTypes.d.ts.map +1 -0
- package/dist/types/deviceTypes.js +23 -0
- package/dist/types/deviceTypes.js.map +1 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +18 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/types.d.ts +13 -0
- package/dist/types/types.d.ts.map +1 -0
- package/dist/types/types.js +9 -0
- package/dist/types/types.js.map +1 -0
- package/dist/validation.d.ts +3 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +8 -0
- package/dist/validation.js.map +1 -0
- package/package.json +1 -1
- package/src/decoders/commandsReadingHelper.ts +50 -11
- package/src/encoders/Co2PirLiteCommands.ts +28 -32
- package/src/encoders/FanCoilThermostatCommands.ts +22 -6
- package/src/encoders/GeneralCommands.ts +1 -1
- package/src/encoders/Relay16Commands.ts +57 -2
- package/src/encoders/types/schemas.ts +445 -224
- package/src/index.ts +9 -1
- package/src/test/payloadDecoders.test.ts +18 -2
- 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.
|