daikin-local-control 0.3.1 → 0.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/DESIGN.md +113 -2
- package/README.md +129 -11
- package/UNSUPPORTED.md +41 -3
- package/dist/clients/classic.d.ts +2 -2
- package/dist/clients/classic.d.ts.map +1 -1
- package/dist/clients/classic.js +4 -4
- package/dist/clients/classic.js.map +1 -1
- package/dist/clients/client.d.ts +5 -1
- package/dist/clients/client.d.ts.map +1 -1
- package/dist/clients/dsiot.d.ts +3 -1
- package/dist/clients/dsiot.d.ts.map +1 -1
- package/dist/clients/dsiot.js +7 -2
- package/dist/clients/dsiot.js.map +1 -1
- package/dist/codecs/classic/capabilities.d.ts.map +1 -1
- package/dist/codecs/classic/capabilities.js +5 -11
- package/dist/codecs/classic/capabilities.js.map +1 -1
- package/dist/codecs/classic/extendedControl.d.ts.map +1 -1
- package/dist/codecs/classic/extendedControl.js +10 -19
- package/dist/codecs/classic/extendedControl.js.map +1 -1
- package/dist/codecs/classic/state.d.ts.map +1 -1
- package/dist/codecs/classic/state.js +36 -95
- package/dist/codecs/classic/state.js.map +1 -1
- package/dist/codecs/classic/tables.d.ts +32 -0
- package/dist/codecs/classic/tables.d.ts.map +1 -0
- package/dist/codecs/classic/tables.js +94 -0
- package/dist/codecs/classic/tables.js.map +1 -0
- package/dist/codecs/credentials.d.ts +3 -2
- package/dist/codecs/credentials.d.ts.map +1 -1
- package/dist/codecs/credentials.js +8 -3
- package/dist/codecs/credentials.js.map +1 -1
- package/dist/codecs/device.js +2 -2
- package/dist/codecs/device.js.map +1 -1
- package/dist/codecs/dsiot/capabilities.d.ts.map +1 -1
- package/dist/codecs/dsiot/capabilities.js +8 -15
- package/dist/codecs/dsiot/capabilities.js.map +1 -1
- package/dist/codecs/dsiot/device.d.ts.map +1 -1
- package/dist/codecs/dsiot/device.js +4 -8
- package/dist/codecs/dsiot/device.js.map +1 -1
- package/dist/codecs/dsiot/multireq.d.ts +0 -19
- package/dist/codecs/dsiot/multireq.d.ts.map +1 -1
- package/dist/codecs/dsiot/multireq.js +2 -25
- package/dist/codecs/dsiot/multireq.js.map +1 -1
- package/dist/codecs/dsiot/state.d.ts +6 -62
- package/dist/codecs/dsiot/state.d.ts.map +1 -1
- package/dist/codecs/dsiot/state.js +29 -148
- package/dist/codecs/dsiot/state.js.map +1 -1
- package/dist/codecs/dsiot/tables.d.ts +116 -0
- package/dist/codecs/dsiot/tables.d.ts.map +1 -0
- package/dist/codecs/dsiot/tables.js +185 -0
- package/dist/codecs/dsiot/tables.js.map +1 -0
- package/dist/discovery.d.ts +2 -1
- package/dist/discovery.d.ts.map +1 -1
- package/dist/discovery.js +9 -5
- package/dist/discovery.js.map +1 -1
- package/dist/factory.d.ts +7 -4
- package/dist/factory.d.ts.map +1 -1
- package/dist/factory.js +4 -2
- package/dist/factory.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/models/state.d.ts +2 -3
- package/dist/models/state.d.ts.map +1 -1
- package/dist/simulators/classic.d.ts +61 -0
- package/dist/simulators/classic.d.ts.map +1 -0
- package/dist/simulators/classic.js +473 -0
- package/dist/simulators/classic.js.map +1 -0
- package/dist/simulators/dsiot.d.ts +49 -0
- package/dist/simulators/dsiot.d.ts.map +1 -0
- package/dist/simulators/dsiot.js +519 -0
- package/dist/simulators/dsiot.js.map +1 -0
- package/dist/simulators/index.d.ts +4 -0
- package/dist/simulators/index.d.ts.map +1 -0
- package/dist/simulators/index.js +3 -0
- package/dist/simulators/index.js.map +1 -0
- package/dist/simulators/simulator.d.ts +116 -0
- package/dist/simulators/simulator.d.ts.map +1 -0
- package/dist/simulators/simulator.js +141 -0
- package/dist/simulators/simulator.js.map +1 -0
- package/dist/transport.d.ts +6 -2
- package/dist/transport.d.ts.map +1 -1
- package/dist/transport.js +7 -2
- package/dist/transport.js.map +1 -1
- package/package.json +5 -2
- package/src/clients/classic.ts +8 -8
- package/src/clients/client.ts +6 -0
- package/src/clients/dsiot.ts +8 -1
- package/src/codecs/classic/capabilities.ts +8 -11
- package/src/codecs/classic/extendedControl.ts +14 -25
- package/src/codecs/classic/state.ts +49 -101
- package/src/codecs/classic/tables.ts +123 -0
- package/src/codecs/credentials.ts +11 -3
- package/src/codecs/device.ts +2 -2
- package/src/codecs/dsiot/capabilities.ts +10 -17
- package/src/codecs/dsiot/device.ts +4 -9
- package/src/codecs/dsiot/multireq.ts +2 -25
- package/src/codecs/dsiot/state.ts +57 -193
- package/src/codecs/dsiot/tables.ts +249 -0
- package/src/discovery.ts +9 -5
- package/src/factory.ts +7 -4
- package/src/index.ts +2 -0
- package/src/models/state.ts +2 -3
- package/src/simulators/classic.ts +635 -0
- package/src/simulators/dsiot.ts +736 -0
- package/src/simulators/index.ts +9 -0
- package/src/simulators/simulator.ts +281 -0
- package/src/transport.ts +10 -3
|
@@ -13,18 +13,22 @@ import {
|
|
|
13
13
|
type VerticalPosition,
|
|
14
14
|
} from "../../models/state.js";
|
|
15
15
|
import { takesClassicDirection, takesClassicHumidity } from "./capabilities.js";
|
|
16
|
+
import {
|
|
17
|
+
CLASSIC_FAN_SPEED_CODES,
|
|
18
|
+
CLASSIC_HORIZONTAL_POSITION_CODES,
|
|
19
|
+
CLASSIC_HUMIDITY_SETTING_CODES,
|
|
20
|
+
CLASSIC_LOUVER_STOP,
|
|
21
|
+
CLASSIC_LOUVER_SWING,
|
|
22
|
+
CLASSIC_NO_SETPOINT,
|
|
23
|
+
CLASSIC_OPERATING_MODE_CODES,
|
|
24
|
+
CLASSIC_SPECIAL_MODE_ADV_CODES,
|
|
25
|
+
CLASSIC_SWING_MODE_CODES,
|
|
26
|
+
CLASSIC_VERTICAL_POSITION_CODES,
|
|
27
|
+
} from "./tables.js";
|
|
16
28
|
|
|
17
29
|
// Maps between `DaikinState` and the classic `control_info` wire fields, and reads
|
|
18
30
|
// `DaikinSensors` from `get_sensor_info`.
|
|
19
31
|
|
|
20
|
-
const OPERATING_MODE_CODES: Record<OperatingMode, string> = {
|
|
21
|
-
auto: "1",
|
|
22
|
-
dehumidify: "2",
|
|
23
|
-
cool: "3",
|
|
24
|
-
heat: "4",
|
|
25
|
-
fan: "6",
|
|
26
|
-
};
|
|
27
|
-
|
|
28
32
|
const OPERATING_MODES_BY_CODE: Record<string, OperatingMode> = {
|
|
29
33
|
"0": "auto",
|
|
30
34
|
"1": "auto",
|
|
@@ -35,67 +39,23 @@ const OPERATING_MODES_BY_CODE: Record<string, OperatingMode> = {
|
|
|
35
39
|
"6": "fan",
|
|
36
40
|
};
|
|
37
41
|
|
|
38
|
-
const FAN_SPEED_CODES: Record<FanSpeed, string> = {
|
|
39
|
-
auto: "A",
|
|
40
|
-
silent: "B",
|
|
41
|
-
level1: "3",
|
|
42
|
-
level2: "4",
|
|
43
|
-
level3: "5",
|
|
44
|
-
level4: "6",
|
|
45
|
-
level5: "7",
|
|
46
|
-
};
|
|
47
|
-
|
|
48
42
|
const FAN_SPEEDS_BY_CODE: Record<string, FanSpeed> = Object.fromEntries(
|
|
49
|
-
Object.entries(
|
|
43
|
+
Object.entries(CLASSIC_FAN_SPEED_CODES).map(([speed, code]) => [
|
|
50
44
|
code,
|
|
51
45
|
speed as FanSpeed,
|
|
52
46
|
]),
|
|
53
47
|
);
|
|
54
48
|
|
|
55
|
-
const SWING_MODE_CODES: Record<SwingMode, string> = {
|
|
56
|
-
off: "0",
|
|
57
|
-
vertical: "1",
|
|
58
|
-
horizontal: "2",
|
|
59
|
-
both: "3",
|
|
60
|
-
};
|
|
61
|
-
|
|
62
49
|
const SWING_MODES_BY_CODE: Record<string, SwingMode> = Object.fromEntries(
|
|
63
|
-
Object.entries(
|
|
50
|
+
Object.entries(CLASSIC_SWING_MODE_CODES).map(([swing, code]) => [
|
|
64
51
|
code,
|
|
65
52
|
swing as SwingMode,
|
|
66
53
|
]),
|
|
67
54
|
);
|
|
68
55
|
|
|
69
|
-
// `f_dir_ud` and `f_dir_lr` carry a digit for stop and each fixed position, and
|
|
70
|
-
// a letter for the rest.
|
|
71
|
-
const LOUVER_STOP = "0";
|
|
72
|
-
const LOUVER_SWING = "S";
|
|
73
|
-
const VERTICAL_POSITION_CODES: Record<VerticalPosition, string> = {
|
|
74
|
-
stop: LOUVER_STOP,
|
|
75
|
-
position1: "1",
|
|
76
|
-
position2: "2",
|
|
77
|
-
position3: "3",
|
|
78
|
-
position4: "4",
|
|
79
|
-
position5: "5",
|
|
80
|
-
swing: LOUVER_SWING,
|
|
81
|
-
naturalWind: "N",
|
|
82
|
-
auto: "A",
|
|
83
|
-
floorWarming: "F",
|
|
84
|
-
};
|
|
85
|
-
const HORIZONTAL_POSITION_CODES: Record<HorizontalPosition, string> = {
|
|
86
|
-
stop: LOUVER_STOP,
|
|
87
|
-
position1: "1",
|
|
88
|
-
position2: "2",
|
|
89
|
-
position3: "3",
|
|
90
|
-
position4: "4",
|
|
91
|
-
position5: "5",
|
|
92
|
-
swing: LOUVER_SWING,
|
|
93
|
-
auto: "A",
|
|
94
|
-
};
|
|
95
|
-
|
|
96
56
|
const VERTICAL_POSITIONS_BY_CODE: Record<string, VerticalPosition> =
|
|
97
57
|
Object.fromEntries(
|
|
98
|
-
Object.entries(
|
|
58
|
+
Object.entries(CLASSIC_VERTICAL_POSITION_CODES).map(([position, code]) => [
|
|
99
59
|
code,
|
|
100
60
|
position as VerticalPosition,
|
|
101
61
|
]),
|
|
@@ -103,38 +63,21 @@ const VERTICAL_POSITIONS_BY_CODE: Record<string, VerticalPosition> =
|
|
|
103
63
|
|
|
104
64
|
const HORIZONTAL_POSITIONS_BY_CODE: Record<string, HorizontalPosition> =
|
|
105
65
|
Object.fromEntries(
|
|
106
|
-
Object.entries(
|
|
107
|
-
code,
|
|
108
|
-
|
|
109
|
-
]),
|
|
66
|
+
Object.entries(CLASSIC_HORIZONTAL_POSITION_CODES).map(
|
|
67
|
+
([position, code]) => [code, position as HorizontalPosition],
|
|
68
|
+
),
|
|
110
69
|
);
|
|
111
70
|
|
|
112
|
-
const HUMIDITY_SETTING_CODES: Record<HumiditySetting, string> = {
|
|
113
|
-
off: "0",
|
|
114
|
-
autoLow: "AUTO_L",
|
|
115
|
-
auto: "AUTO",
|
|
116
|
-
autoHigh: "AUTO_H",
|
|
117
|
-
continuous: "CONTINUE",
|
|
118
|
-
};
|
|
119
|
-
|
|
120
71
|
const HUMIDITY_SETTINGS_BY_CODE: Record<string, HumiditySetting> =
|
|
121
72
|
Object.fromEntries(
|
|
122
|
-
Object.entries(
|
|
73
|
+
Object.entries(CLASSIC_HUMIDITY_SETTING_CODES).map(([setting, code]) => [
|
|
123
74
|
code,
|
|
124
75
|
setting as HumiditySetting,
|
|
125
76
|
]),
|
|
126
77
|
);
|
|
127
78
|
|
|
128
|
-
//
|
|
129
|
-
const
|
|
130
|
-
powerful: "2",
|
|
131
|
-
econo: "12",
|
|
132
|
-
streamer: "13",
|
|
133
|
-
};
|
|
134
|
-
|
|
135
|
-
// Fan mode keeps no setpoint memory, and dehumidify runs the fan on auto only.
|
|
136
|
-
const NO_SETPOINT = "--";
|
|
137
|
-
const DEHUMIDIFY_FAN_SPEED = "A";
|
|
79
|
+
// Dehumidify runs the fan on auto only.
|
|
80
|
+
const DEHUMIDIFY_FAN_SPEED = CLASSIC_FAN_SPEED_CODES.auto;
|
|
138
81
|
// In `ndfd<mode>`, a louver the unit lacks.
|
|
139
82
|
const NO_LOUVER = "-";
|
|
140
83
|
|
|
@@ -188,11 +131,11 @@ export function applyClassicControl(
|
|
|
188
131
|
const currentMode =
|
|
189
132
|
heldMode === undefined
|
|
190
133
|
? (current.mode ?? "")
|
|
191
|
-
:
|
|
134
|
+
: CLASSIC_OPERATING_MODE_CODES[heldMode];
|
|
192
135
|
const mode =
|
|
193
136
|
control.mode === undefined
|
|
194
137
|
? currentMode
|
|
195
|
-
:
|
|
138
|
+
: CLASSIC_OPERATING_MODE_CODES[control.mode];
|
|
196
139
|
const modeChanged = mode !== currentMode;
|
|
197
140
|
const remembered = (field: string, memoryPrefix: string) =>
|
|
198
141
|
(modeChanged ? current[`${memoryPrefix}${mode}`] : undefined) ??
|
|
@@ -202,14 +145,14 @@ export function applyClassicControl(
|
|
|
202
145
|
const power = control.power ?? current.pow === "1";
|
|
203
146
|
const next: Record<string, string> = { pow: power ? "1" : "0", mode };
|
|
204
147
|
|
|
205
|
-
const setpointFrozen = mode ===
|
|
148
|
+
const setpointFrozen = mode === CLASSIC_OPERATING_MODE_CODES.fan;
|
|
206
149
|
// Only a mode in the model has a `dt` and a `dh` memory.
|
|
207
150
|
const hasMemory =
|
|
208
151
|
!setpointFrozen && (control.mode !== undefined || heldMode !== undefined);
|
|
209
152
|
next.stemp = setpointFrozen
|
|
210
|
-
?
|
|
153
|
+
? CLASSIC_NO_SETPOINT
|
|
211
154
|
: control.targetTemperatureInC != null &&
|
|
212
|
-
mode !==
|
|
155
|
+
mode !== CLASSIC_OPERATING_MODE_CODES.dehumidify
|
|
213
156
|
? control.targetTemperatureInC.toFixed(1)
|
|
214
157
|
: remembered("stemp", "dt");
|
|
215
158
|
if (hasMemory) {
|
|
@@ -217,7 +160,7 @@ export function applyClassicControl(
|
|
|
217
160
|
}
|
|
218
161
|
if (takesClassicHumidity(capabilityFields)) {
|
|
219
162
|
next.shum = setpointFrozen
|
|
220
|
-
?
|
|
163
|
+
? CLASSIC_NO_SETPOINT
|
|
221
164
|
: control.targetHumidity != null
|
|
222
165
|
? encodeTargetHumidity(control.targetHumidity)
|
|
223
166
|
: remembered("shum", "dh");
|
|
@@ -227,55 +170,60 @@ export function applyClassicControl(
|
|
|
227
170
|
}
|
|
228
171
|
|
|
229
172
|
next.f_rate =
|
|
230
|
-
mode ===
|
|
173
|
+
mode === CLASSIC_OPERATING_MODE_CODES.dehumidify
|
|
231
174
|
? DEHUMIDIFY_FAN_SPEED
|
|
232
175
|
: control.fanSpeed === undefined
|
|
233
176
|
? remembered("f_rate", "dfr")
|
|
234
|
-
:
|
|
177
|
+
: CLASSIC_FAN_SPEED_CODES[control.fanSpeed];
|
|
235
178
|
|
|
236
179
|
if (capabilityFields.en_fdir2 === "1") {
|
|
237
180
|
// `ndfd<mode>` holds the mode's `f_dir_ud` value, then its `f_dir_lr`
|
|
238
181
|
// value. A value outside the model is sent back as it came.
|
|
239
182
|
const louverMemory = modeChanged ? current[`ndfd${mode}`] : undefined;
|
|
240
183
|
let vertical =
|
|
241
|
-
rememberedLouver(louverMemory, 0) ||
|
|
184
|
+
rememberedLouver(louverMemory, 0) ||
|
|
185
|
+
current.f_dir_ud ||
|
|
186
|
+
CLASSIC_LOUVER_STOP;
|
|
242
187
|
let horizontal =
|
|
243
|
-
rememberedLouver(louverMemory, 1) ||
|
|
188
|
+
rememberedLouver(louverMemory, 1) ||
|
|
189
|
+
current.f_dir_lr ||
|
|
190
|
+
CLASSIC_LOUVER_STOP;
|
|
244
191
|
if (control.swingMode !== undefined) {
|
|
245
192
|
const swingsVertically =
|
|
246
193
|
control.swingMode === "vertical" || control.swingMode === "both";
|
|
247
194
|
const swingsHorizontally =
|
|
248
195
|
control.swingMode === "horizontal" || control.swingMode === "both";
|
|
249
196
|
vertical = swingsVertically
|
|
250
|
-
?
|
|
251
|
-
: vertical ===
|
|
252
|
-
?
|
|
197
|
+
? CLASSIC_LOUVER_SWING
|
|
198
|
+
: vertical === CLASSIC_LOUVER_SWING
|
|
199
|
+
? CLASSIC_LOUVER_STOP
|
|
253
200
|
: vertical;
|
|
254
201
|
horizontal = swingsHorizontally
|
|
255
|
-
?
|
|
256
|
-
: horizontal ===
|
|
257
|
-
?
|
|
202
|
+
? CLASSIC_LOUVER_SWING
|
|
203
|
+
: horizontal === CLASSIC_LOUVER_SWING
|
|
204
|
+
? CLASSIC_LOUVER_STOP
|
|
258
205
|
: horizontal;
|
|
259
206
|
}
|
|
260
207
|
if (control.verticalPosition != null) {
|
|
261
|
-
vertical =
|
|
208
|
+
vertical = CLASSIC_VERTICAL_POSITION_CODES[control.verticalPosition];
|
|
262
209
|
}
|
|
263
210
|
if (control.horizontalPosition != null) {
|
|
264
|
-
horizontal =
|
|
211
|
+
horizontal =
|
|
212
|
+
CLASSIC_HORIZONTAL_POSITION_CODES[control.horizontalPosition];
|
|
265
213
|
}
|
|
266
214
|
next.f_dir_ud = vertical;
|
|
267
215
|
next.f_dir_lr = horizontal;
|
|
268
216
|
if (takesDirection) {
|
|
269
217
|
next.f_dir = String(
|
|
270
|
-
(vertical ===
|
|
271
|
-
(horizontal ===
|
|
218
|
+
(vertical === CLASSIC_LOUVER_SWING ? 1 : 0) |
|
|
219
|
+
(horizontal === CLASSIC_LOUVER_SWING ? 2 : 0),
|
|
272
220
|
);
|
|
273
221
|
}
|
|
274
222
|
} else if (takesDirection) {
|
|
275
223
|
next.f_dir =
|
|
276
224
|
control.swingMode === undefined
|
|
277
225
|
? remembered("f_dir", "dfd")
|
|
278
|
-
:
|
|
226
|
+
: CLASSIC_SWING_MODE_CODES[control.swingMode];
|
|
279
227
|
}
|
|
280
228
|
return next;
|
|
281
229
|
}
|
|
@@ -283,7 +231,7 @@ export function applyClassicControl(
|
|
|
283
231
|
function parseSpecialModes(adv: string | undefined): SpecialMode[] {
|
|
284
232
|
const codes = (adv ?? "").split("/");
|
|
285
233
|
return SPECIAL_MODES.filter((mode) =>
|
|
286
|
-
codes.includes(
|
|
234
|
+
codes.includes(CLASSIC_SPECIAL_MODE_ADV_CODES[mode]),
|
|
287
235
|
);
|
|
288
236
|
}
|
|
289
237
|
|
|
@@ -298,7 +246,7 @@ function parseTargetHumidity(shum: string | undefined): TargetHumidity | null {
|
|
|
298
246
|
|
|
299
247
|
function encodeTargetHumidity(targetHumidity: TargetHumidity): string {
|
|
300
248
|
return typeof targetHumidity === "string"
|
|
301
|
-
?
|
|
249
|
+
? CLASSIC_HUMIDITY_SETTING_CODES[targetHumidity]
|
|
302
250
|
: String(targetHumidity.percent);
|
|
303
251
|
}
|
|
304
252
|
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import type { TerminalScope } from "../../models/extendedControl.js";
|
|
2
|
+
import type {
|
|
3
|
+
FanSpeed,
|
|
4
|
+
HorizontalPosition,
|
|
5
|
+
HumiditySetting,
|
|
6
|
+
OperatingMode,
|
|
7
|
+
SpecialMode,
|
|
8
|
+
SwingMode,
|
|
9
|
+
VerticalPosition,
|
|
10
|
+
} from "../../models/state.js";
|
|
11
|
+
|
|
12
|
+
// The codes a classic adapter holds each value of a model as. The codecs and
|
|
13
|
+
// the simulators both read them from here: see DESIGN.md#simulators.
|
|
14
|
+
|
|
15
|
+
export const CLASSIC_OPERATING_MODE_CODES: Record<OperatingMode, string> = {
|
|
16
|
+
auto: "1",
|
|
17
|
+
dehumidify: "2",
|
|
18
|
+
cool: "3",
|
|
19
|
+
heat: "4",
|
|
20
|
+
fan: "6",
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const CLASSIC_FAN_SPEED_CODES: Record<FanSpeed, string> = {
|
|
24
|
+
auto: "A",
|
|
25
|
+
silent: "B",
|
|
26
|
+
level1: "3",
|
|
27
|
+
level2: "4",
|
|
28
|
+
level3: "5",
|
|
29
|
+
level4: "6",
|
|
30
|
+
level5: "7",
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export const CLASSIC_SWING_MODE_CODES: Record<SwingMode, string> = {
|
|
34
|
+
off: "0",
|
|
35
|
+
vertical: "1",
|
|
36
|
+
horizontal: "2",
|
|
37
|
+
both: "3",
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// `f_dir_ud` and `f_dir_lr` carry a digit for stop and each fixed position, and
|
|
41
|
+
// a letter for the rest.
|
|
42
|
+
export const CLASSIC_LOUVER_STOP = "0";
|
|
43
|
+
export const CLASSIC_LOUVER_SWING = "S";
|
|
44
|
+
export const CLASSIC_VERTICAL_POSITION_CODES: Record<VerticalPosition, string> =
|
|
45
|
+
{
|
|
46
|
+
stop: CLASSIC_LOUVER_STOP,
|
|
47
|
+
position1: "1",
|
|
48
|
+
position2: "2",
|
|
49
|
+
position3: "3",
|
|
50
|
+
position4: "4",
|
|
51
|
+
position5: "5",
|
|
52
|
+
swing: CLASSIC_LOUVER_SWING,
|
|
53
|
+
naturalWind: "N",
|
|
54
|
+
auto: "A",
|
|
55
|
+
floorWarming: "F",
|
|
56
|
+
};
|
|
57
|
+
export const CLASSIC_HORIZONTAL_POSITION_CODES: Record<
|
|
58
|
+
HorizontalPosition,
|
|
59
|
+
string
|
|
60
|
+
> = {
|
|
61
|
+
stop: CLASSIC_LOUVER_STOP,
|
|
62
|
+
position1: "1",
|
|
63
|
+
position2: "2",
|
|
64
|
+
position3: "3",
|
|
65
|
+
position4: "4",
|
|
66
|
+
position5: "5",
|
|
67
|
+
swing: CLASSIC_LOUVER_SWING,
|
|
68
|
+
auto: "A",
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export const CLASSIC_HUMIDITY_SETTING_CODES: Record<HumiditySetting, string> = {
|
|
72
|
+
off: "0",
|
|
73
|
+
autoLow: "AUTO_L",
|
|
74
|
+
auto: "AUTO",
|
|
75
|
+
autoHigh: "AUTO_H",
|
|
76
|
+
continuous: "CONTINUE",
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
/** What `stemp` and `shum` hold in a mode that has neither, such as fan. */
|
|
80
|
+
export const CLASSIC_NO_SETPOINT = "--";
|
|
81
|
+
|
|
82
|
+
/** `adv` holds one code for each special mode that is on, separated by
|
|
83
|
+
* `/`. */
|
|
84
|
+
export const CLASSIC_SPECIAL_MODE_ADV_CODES: Record<SpecialMode, string> = {
|
|
85
|
+
powerful: "2",
|
|
86
|
+
econo: "12",
|
|
87
|
+
streamer: "13",
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
/** `spmode_kind` in `set_special_mode`, which numbers the modes differently
|
|
91
|
+
* from `adv`. The streamer has a parameter of its own, `en_streamer`. */
|
|
92
|
+
export const CLASSIC_SPECIAL_MODE_KIND_CODES: Record<
|
|
93
|
+
Exclude<SpecialMode, "streamer">,
|
|
94
|
+
string
|
|
95
|
+
> = {
|
|
96
|
+
powerful: "1",
|
|
97
|
+
econo: "2",
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
/** `en_spmode` sets one bit for each special mode the unit takes. Bit 8 is a
|
|
101
|
+
* mode outside `SpecialMode`. */
|
|
102
|
+
export const CLASSIC_SPECIAL_MODE_BITS: Record<SpecialMode, number> = {
|
|
103
|
+
powerful: 1,
|
|
104
|
+
econo: 2,
|
|
105
|
+
streamer: 4,
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
/** `type` in `get_model_info` on a unit that only cools. */
|
|
109
|
+
export const CLASSIC_COOLING_ONLY_TYPE = "C";
|
|
110
|
+
|
|
111
|
+
/** `type` in `unregister_terminal`. */
|
|
112
|
+
export const CLASSIC_TERMINAL_SCOPE_CODES: Record<TerminalScope, string> = {
|
|
113
|
+
self: "0",
|
|
114
|
+
others: "1",
|
|
115
|
+
all: "2",
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
/** `method`: the adapter is reached from outside the home network while it
|
|
119
|
+
* polls, and from inside only otherwise. */
|
|
120
|
+
export const CLASSIC_REMOTE_METHODS = {
|
|
121
|
+
polling: "polling",
|
|
122
|
+
homeOnly: "home only",
|
|
123
|
+
} as const;
|
|
@@ -1,7 +1,15 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
const TERMINAL_UUID_PATTERN = /^[0-9a-f]{32}$/;
|
|
2
|
+
|
|
3
|
+
/** The header that carries the terminal uuid on every call. It throws an
|
|
4
|
+
* `Error` unless the uuid is 32 lowercase hexadecimal digits, a UUID without
|
|
5
|
+
* its dashes: whether the adapter accepts any other form is not known. */
|
|
3
6
|
export function encodeTerminalUuid(uuid: string): Record<string, string> {
|
|
4
|
-
|
|
7
|
+
if (!TERMINAL_UUID_PATTERN.test(uuid)) {
|
|
8
|
+
throw new Error(
|
|
9
|
+
"The terminal uuid must be 32 lowercase hexadecimal digits, without dashes",
|
|
10
|
+
);
|
|
11
|
+
}
|
|
12
|
+
return { "X-Daikin-uuid": uuid };
|
|
5
13
|
}
|
|
6
14
|
|
|
7
15
|
/** The query parameter a classic adapter takes the local password as. */
|
package/src/codecs/device.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { DaikinDevice, DaikinDeviceInfo } from "../models/device.js";
|
|
2
2
|
import { decodeClassicText } from "./classic/keyValue.js";
|
|
3
|
+
import { DSIOT_ADAPTER_TYPE } from "./dsiot/tables.js";
|
|
3
4
|
|
|
4
|
-
const DSIOT_TYPE = "GPF";
|
|
5
5
|
const MARVELL_ADP_KIND = "3";
|
|
6
6
|
|
|
7
7
|
/** Turns a `basic_info` reply into a `DaikinDevice`: `type=GPF` marks a DSIOT
|
|
@@ -11,7 +11,7 @@ export function parseDevice(
|
|
|
11
11
|
host: string,
|
|
12
12
|
basicInfo: Record<string, string>,
|
|
13
13
|
): DaikinDevice {
|
|
14
|
-
const protocol = basicInfo.type ===
|
|
14
|
+
const protocol = basicInfo.type === DSIOT_ADAPTER_TYPE ? "dsiot" : "classic";
|
|
15
15
|
const secure = basicInfo.en_secure === "1";
|
|
16
16
|
return {
|
|
17
17
|
host: basicInfo.api_port ? `${host}:${basicInfo.api_port}` : host,
|
|
@@ -17,30 +17,23 @@ import {
|
|
|
17
17
|
VERTICAL_POSITIONS,
|
|
18
18
|
type VerticalPosition,
|
|
19
19
|
} from "../../models/state.js";
|
|
20
|
+
import { decodeDsiotLittleEndian } from "./state.js";
|
|
20
21
|
import {
|
|
22
|
+
DSIOT_ADAPTER_PATHS,
|
|
23
|
+
DSIOT_COOLING_ONLY_MODEL_TYPE,
|
|
21
24
|
DSIOT_HORIZONTAL_POSITION_VALUES,
|
|
22
25
|
DSIOT_MODE_PATHS,
|
|
23
26
|
DSIOT_PATHS,
|
|
24
27
|
DSIOT_SPECIAL_MODE_PATHS,
|
|
25
28
|
DSIOT_VERTICAL_POSITION_VALUES,
|
|
26
29
|
type DsiotSetpoint,
|
|
27
|
-
|
|
28
|
-
} from "./state.js";
|
|
30
|
+
} from "./tables.js";
|
|
29
31
|
|
|
30
32
|
const MODE_PATHS = Object.values(DSIOT_MODE_PATHS);
|
|
31
33
|
|
|
32
|
-
const ADAPTER_PATHS = {
|
|
33
|
-
region: "adp_i.reg",
|
|
34
|
-
kind: "adp_i.kind",
|
|
35
|
-
energyMonitoring: "adp_i.func.en_ipower",
|
|
36
|
-
demandControl: "adp_i.func.en_demand",
|
|
37
|
-
scheduleTimer: "adp_i.func.en_scdltmr",
|
|
38
|
-
} as const;
|
|
39
|
-
|
|
40
34
|
// The `pt` of a special mode's leaf on a unit that takes the mode.
|
|
41
35
|
const SPECIAL_MODE_OFFERED_TYPE = "2";
|
|
42
36
|
|
|
43
|
-
const COOLING_ONLY_MODEL_TYPE = "01";
|
|
44
37
|
const COOLING_ONLY_MODES: readonly OperatingMode[] = [
|
|
45
38
|
"dehumidify",
|
|
46
39
|
"cool",
|
|
@@ -61,7 +54,7 @@ export function parseDsiotCapabilities(
|
|
|
61
54
|
values: Record<string, string>,
|
|
62
55
|
): DaikinCapabilities {
|
|
63
56
|
const modes =
|
|
64
|
-
values[DSIOT_PATHS.modelType] ===
|
|
57
|
+
values[DSIOT_PATHS.modelType] === DSIOT_COOLING_ONLY_MODEL_TYPE
|
|
65
58
|
? [...COOLING_ONLY_MODES]
|
|
66
59
|
: [...OPERATING_MODES];
|
|
67
60
|
const offered = (mode: OperatingMode, range: TemperatureRange) =>
|
|
@@ -87,7 +80,7 @@ export function parseDsiotCapabilities(
|
|
|
87
80
|
const vertical = hasLouver(values, "vertical");
|
|
88
81
|
const horizontal = hasLouver(values, "horizontal");
|
|
89
82
|
const energyMonitoring =
|
|
90
|
-
values[
|
|
83
|
+
values[DSIOT_ADAPTER_PATHS.energyMonitoring] === "1" &&
|
|
91
84
|
values[DSIOT_PATHS.energyMonitoring] === "01";
|
|
92
85
|
|
|
93
86
|
return {
|
|
@@ -136,10 +129,10 @@ export function parseDsiotCapabilities(
|
|
|
136
129
|
energyMonitoring,
|
|
137
130
|
demandControl:
|
|
138
131
|
energyMonitoring &&
|
|
139
|
-
values[
|
|
140
|
-
values[
|
|
132
|
+
values[DSIOT_ADAPTER_PATHS.kind] === "4" &&
|
|
133
|
+
values[DSIOT_ADAPTER_PATHS.demandControl] === "1",
|
|
141
134
|
scheduleTimer: ["1", "2"].includes(
|
|
142
|
-
values[
|
|
135
|
+
values[DSIOT_ADAPTER_PATHS.scheduleTimer] ?? "",
|
|
143
136
|
),
|
|
144
137
|
model: values[DSIOT_PATHS.model],
|
|
145
138
|
};
|
|
@@ -164,7 +157,7 @@ function takes(mask: number, value: number | undefined): boolean {
|
|
|
164
157
|
|
|
165
158
|
// Japanese units (`jp`) follow other rules: see UNSUPPORTED.md.
|
|
166
159
|
function fanSpeeds(values: Record<string, string>): FanSpeed[] {
|
|
167
|
-
const region = values[
|
|
160
|
+
const region = values[DSIOT_ADAPTER_PATHS.region];
|
|
168
161
|
if (!region || region === "jp") {
|
|
169
162
|
return [];
|
|
170
163
|
}
|
|
@@ -1,11 +1,6 @@
|
|
|
1
1
|
import type { DaikinDeviceInfo } from "../../models/device.js";
|
|
2
2
|
import { decodeFirmwareVersion, decodeMacAddress } from "../device.js";
|
|
3
|
-
|
|
4
|
-
const ADAPTER_PATHS = {
|
|
5
|
-
name: "adp_d.name",
|
|
6
|
-
macAddress: "adp_i.mac",
|
|
7
|
-
firmwareVersion: "adp_i.ver",
|
|
8
|
-
} as const;
|
|
3
|
+
import { DSIOT_ADAPTER_PATHS } from "./tables.js";
|
|
9
4
|
|
|
10
5
|
/** Reads what the flattened `adp_i` and `adp_d` trees say to identify the
|
|
11
6
|
* adapter. Both `adp_i` leaves hold the value a classic adapter sends in
|
|
@@ -15,10 +10,10 @@ export function parseDsiotDeviceInfo(
|
|
|
15
10
|
values: Record<string, string>,
|
|
16
11
|
): DaikinDeviceInfo {
|
|
17
12
|
return {
|
|
18
|
-
name: values[
|
|
19
|
-
macAddress: decodeMacAddress(values[
|
|
13
|
+
name: values[DSIOT_ADAPTER_PATHS.name],
|
|
14
|
+
macAddress: decodeMacAddress(values[DSIOT_ADAPTER_PATHS.macAddress]),
|
|
20
15
|
firmwareVersion: decodeFirmwareVersion(
|
|
21
|
-
values[
|
|
16
|
+
values[DSIOT_ADAPTER_PATHS.firmwareVersion],
|
|
22
17
|
),
|
|
23
18
|
};
|
|
24
19
|
}
|
|
@@ -1,34 +1,11 @@
|
|
|
1
1
|
import { DaikinError } from "../../error.js";
|
|
2
|
+
import { DSIOT_EDGE_ROOT, DSIOT_EDGES, DSIOT_OPERATIONS } from "./tables.js";
|
|
2
3
|
|
|
3
4
|
// The DSIOT (BRP084, `type=GPF`) protocol speaks one endpoint, `/dsiot/multireq`,
|
|
4
5
|
// carrying operations on property trees. A request addresses a resource (`to`);
|
|
5
6
|
// a tree node has pn (name) / pt (type) / pch (children) / pv (value) / md
|
|
6
7
|
// (metadata: mi min, mx max, st step).
|
|
7
8
|
|
|
8
|
-
export const DSIOT_OPERATIONS = {
|
|
9
|
-
get: 2,
|
|
10
|
-
update: 3,
|
|
11
|
-
delete: 4,
|
|
12
|
-
} as const;
|
|
13
|
-
|
|
14
|
-
/** The indoor and the outdoor unit are separate edges on the adapter. */
|
|
15
|
-
export const DSIOT_EDGES = {
|
|
16
|
-
indoor: "adr_0100",
|
|
17
|
-
outdoor: "adr_0200",
|
|
18
|
-
} as const;
|
|
19
|
-
|
|
20
|
-
const EDGE_ROOT = "/dsiot/edge";
|
|
21
|
-
|
|
22
|
-
/** What a read fetches, as the `to` of a request: whole trees, never single
|
|
23
|
-
* leaves. The status trees come with `pt`, which `parseDsiotCapabilities`
|
|
24
|
-
* reads. */
|
|
25
|
-
export const DSIOT_TARGETS = {
|
|
26
|
-
adapterInfo: `${EDGE_ROOT}.adp_i?filter=pv`,
|
|
27
|
-
adapterSettings: `${EDGE_ROOT}.adp_d?filter=pv`,
|
|
28
|
-
indoorStatus: `${EDGE_ROOT}/${DSIOT_EDGES.indoor}.dgc_status?filter=pv,pt,md`,
|
|
29
|
-
outdoorStatus: `${EDGE_ROOT}/${DSIOT_EDGES.outdoor}.dgc_status?filter=pv,pt,md`,
|
|
30
|
-
} as const;
|
|
31
|
-
|
|
32
9
|
interface PropertyNode {
|
|
33
10
|
pn: string;
|
|
34
11
|
pt?: number;
|
|
@@ -78,7 +55,7 @@ export function buildDsiotMultireqUpdate(
|
|
|
78
55
|
return {
|
|
79
56
|
requests: roots.map((pc) => ({
|
|
80
57
|
op: DSIOT_OPERATIONS.update,
|
|
81
|
-
to: `${
|
|
58
|
+
to: `${DSIOT_EDGE_ROOT}/${edge}.${pc.pn}`,
|
|
82
59
|
pc,
|
|
83
60
|
})),
|
|
84
61
|
};
|