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.
Files changed (108) hide show
  1. package/DESIGN.md +113 -2
  2. package/README.md +129 -11
  3. package/UNSUPPORTED.md +41 -3
  4. package/dist/clients/classic.d.ts +2 -2
  5. package/dist/clients/classic.d.ts.map +1 -1
  6. package/dist/clients/classic.js +4 -4
  7. package/dist/clients/classic.js.map +1 -1
  8. package/dist/clients/client.d.ts +5 -1
  9. package/dist/clients/client.d.ts.map +1 -1
  10. package/dist/clients/dsiot.d.ts +3 -1
  11. package/dist/clients/dsiot.d.ts.map +1 -1
  12. package/dist/clients/dsiot.js +7 -2
  13. package/dist/clients/dsiot.js.map +1 -1
  14. package/dist/codecs/classic/capabilities.d.ts.map +1 -1
  15. package/dist/codecs/classic/capabilities.js +5 -11
  16. package/dist/codecs/classic/capabilities.js.map +1 -1
  17. package/dist/codecs/classic/extendedControl.d.ts.map +1 -1
  18. package/dist/codecs/classic/extendedControl.js +10 -19
  19. package/dist/codecs/classic/extendedControl.js.map +1 -1
  20. package/dist/codecs/classic/state.d.ts.map +1 -1
  21. package/dist/codecs/classic/state.js +36 -95
  22. package/dist/codecs/classic/state.js.map +1 -1
  23. package/dist/codecs/classic/tables.d.ts +32 -0
  24. package/dist/codecs/classic/tables.d.ts.map +1 -0
  25. package/dist/codecs/classic/tables.js +94 -0
  26. package/dist/codecs/classic/tables.js.map +1 -0
  27. package/dist/codecs/credentials.d.ts +3 -2
  28. package/dist/codecs/credentials.d.ts.map +1 -1
  29. package/dist/codecs/credentials.js +8 -3
  30. package/dist/codecs/credentials.js.map +1 -1
  31. package/dist/codecs/device.js +2 -2
  32. package/dist/codecs/device.js.map +1 -1
  33. package/dist/codecs/dsiot/capabilities.d.ts.map +1 -1
  34. package/dist/codecs/dsiot/capabilities.js +8 -15
  35. package/dist/codecs/dsiot/capabilities.js.map +1 -1
  36. package/dist/codecs/dsiot/device.d.ts.map +1 -1
  37. package/dist/codecs/dsiot/device.js +4 -8
  38. package/dist/codecs/dsiot/device.js.map +1 -1
  39. package/dist/codecs/dsiot/multireq.d.ts +0 -19
  40. package/dist/codecs/dsiot/multireq.d.ts.map +1 -1
  41. package/dist/codecs/dsiot/multireq.js +2 -25
  42. package/dist/codecs/dsiot/multireq.js.map +1 -1
  43. package/dist/codecs/dsiot/state.d.ts +6 -62
  44. package/dist/codecs/dsiot/state.d.ts.map +1 -1
  45. package/dist/codecs/dsiot/state.js +29 -148
  46. package/dist/codecs/dsiot/state.js.map +1 -1
  47. package/dist/codecs/dsiot/tables.d.ts +116 -0
  48. package/dist/codecs/dsiot/tables.d.ts.map +1 -0
  49. package/dist/codecs/dsiot/tables.js +185 -0
  50. package/dist/codecs/dsiot/tables.js.map +1 -0
  51. package/dist/discovery.d.ts +2 -1
  52. package/dist/discovery.d.ts.map +1 -1
  53. package/dist/discovery.js +9 -5
  54. package/dist/discovery.js.map +1 -1
  55. package/dist/factory.d.ts +7 -4
  56. package/dist/factory.d.ts.map +1 -1
  57. package/dist/factory.js +4 -2
  58. package/dist/factory.js.map +1 -1
  59. package/dist/index.d.ts +2 -0
  60. package/dist/index.d.ts.map +1 -1
  61. package/dist/index.js +2 -0
  62. package/dist/index.js.map +1 -1
  63. package/dist/models/state.d.ts +2 -3
  64. package/dist/models/state.d.ts.map +1 -1
  65. package/dist/simulators/classic.d.ts +61 -0
  66. package/dist/simulators/classic.d.ts.map +1 -0
  67. package/dist/simulators/classic.js +473 -0
  68. package/dist/simulators/classic.js.map +1 -0
  69. package/dist/simulators/dsiot.d.ts +49 -0
  70. package/dist/simulators/dsiot.d.ts.map +1 -0
  71. package/dist/simulators/dsiot.js +519 -0
  72. package/dist/simulators/dsiot.js.map +1 -0
  73. package/dist/simulators/index.d.ts +4 -0
  74. package/dist/simulators/index.d.ts.map +1 -0
  75. package/dist/simulators/index.js +3 -0
  76. package/dist/simulators/index.js.map +1 -0
  77. package/dist/simulators/simulator.d.ts +116 -0
  78. package/dist/simulators/simulator.d.ts.map +1 -0
  79. package/dist/simulators/simulator.js +141 -0
  80. package/dist/simulators/simulator.js.map +1 -0
  81. package/dist/transport.d.ts +6 -2
  82. package/dist/transport.d.ts.map +1 -1
  83. package/dist/transport.js +7 -2
  84. package/dist/transport.js.map +1 -1
  85. package/package.json +5 -2
  86. package/src/clients/classic.ts +8 -8
  87. package/src/clients/client.ts +6 -0
  88. package/src/clients/dsiot.ts +8 -1
  89. package/src/codecs/classic/capabilities.ts +8 -11
  90. package/src/codecs/classic/extendedControl.ts +14 -25
  91. package/src/codecs/classic/state.ts +49 -101
  92. package/src/codecs/classic/tables.ts +123 -0
  93. package/src/codecs/credentials.ts +11 -3
  94. package/src/codecs/device.ts +2 -2
  95. package/src/codecs/dsiot/capabilities.ts +10 -17
  96. package/src/codecs/dsiot/device.ts +4 -9
  97. package/src/codecs/dsiot/multireq.ts +2 -25
  98. package/src/codecs/dsiot/state.ts +57 -193
  99. package/src/codecs/dsiot/tables.ts +249 -0
  100. package/src/discovery.ts +9 -5
  101. package/src/factory.ts +7 -4
  102. package/src/index.ts +2 -0
  103. package/src/models/state.ts +2 -3
  104. package/src/simulators/classic.ts +635 -0
  105. package/src/simulators/dsiot.ts +736 -0
  106. package/src/simulators/index.ts +9 -0
  107. package/src/simulators/simulator.ts +281 -0
  108. 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(FAN_SPEED_CODES).map(([speed, code]) => [
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(SWING_MODE_CODES).map(([swing, code]) => [
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(VERTICAL_POSITION_CODES).map(([position, code]) => [
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(HORIZONTAL_POSITION_CODES).map(([position, code]) => [
107
- code,
108
- position as HorizontalPosition,
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(HUMIDITY_SETTING_CODES).map(([setting, code]) => [
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
- // `adv` holds one code for each special mode that is on, separated by `/`.
129
- const SPECIAL_MODE_ADV_CODES: Record<SpecialMode, string> = {
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
- : OPERATING_MODE_CODES[heldMode];
134
+ : CLASSIC_OPERATING_MODE_CODES[heldMode];
192
135
  const mode =
193
136
  control.mode === undefined
194
137
  ? currentMode
195
- : OPERATING_MODE_CODES[control.mode];
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 === OPERATING_MODE_CODES.fan;
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
- ? NO_SETPOINT
153
+ ? CLASSIC_NO_SETPOINT
211
154
  : control.targetTemperatureInC != null &&
212
- mode !== OPERATING_MODE_CODES.dehumidify
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
- ? NO_SETPOINT
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 === OPERATING_MODE_CODES.dehumidify
173
+ mode === CLASSIC_OPERATING_MODE_CODES.dehumidify
231
174
  ? DEHUMIDIFY_FAN_SPEED
232
175
  : control.fanSpeed === undefined
233
176
  ? remembered("f_rate", "dfr")
234
- : FAN_SPEED_CODES[control.fanSpeed];
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) || current.f_dir_ud || LOUVER_STOP;
184
+ rememberedLouver(louverMemory, 0) ||
185
+ current.f_dir_ud ||
186
+ CLASSIC_LOUVER_STOP;
242
187
  let horizontal =
243
- rememberedLouver(louverMemory, 1) || current.f_dir_lr || LOUVER_STOP;
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
- ? LOUVER_SWING
251
- : vertical === LOUVER_SWING
252
- ? LOUVER_STOP
197
+ ? CLASSIC_LOUVER_SWING
198
+ : vertical === CLASSIC_LOUVER_SWING
199
+ ? CLASSIC_LOUVER_STOP
253
200
  : vertical;
254
201
  horizontal = swingsHorizontally
255
- ? LOUVER_SWING
256
- : horizontal === LOUVER_SWING
257
- ? LOUVER_STOP
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 = VERTICAL_POSITION_CODES[control.verticalPosition];
208
+ vertical = CLASSIC_VERTICAL_POSITION_CODES[control.verticalPosition];
262
209
  }
263
210
  if (control.horizontalPosition != null) {
264
- horizontal = HORIZONTAL_POSITION_CODES[control.horizontalPosition];
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 === LOUVER_SWING ? 1 : 0) |
271
- (horizontal === LOUVER_SWING ? 2 : 0),
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
- : SWING_MODE_CODES[control.swingMode];
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(SPECIAL_MODE_ADV_CODES[mode]),
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
- ? HUMIDITY_SETTING_CODES[targetHumidity]
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
- /** The header that carries the terminal uuid on every call. The adapter takes
2
- * the uuid without its dashes. */
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
- return { "X-Daikin-uuid": uuid.replaceAll("-", "") };
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. */
@@ -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 === DSIOT_TYPE ? "dsiot" : "classic";
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
- decodeDsiotLittleEndian,
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] === COOLING_ONLY_MODEL_TYPE
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[ADAPTER_PATHS.energyMonitoring] === "1" &&
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[ADAPTER_PATHS.kind] === "4" &&
140
- values[ADAPTER_PATHS.demandControl] === "1",
132
+ values[DSIOT_ADAPTER_PATHS.kind] === "4" &&
133
+ values[DSIOT_ADAPTER_PATHS.demandControl] === "1",
141
134
  scheduleTimer: ["1", "2"].includes(
142
- values[ADAPTER_PATHS.scheduleTimer] ?? "",
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[ADAPTER_PATHS.region];
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[ADAPTER_PATHS.name],
19
- macAddress: decodeMacAddress(values[ADAPTER_PATHS.macAddress]),
13
+ name: values[DSIOT_ADAPTER_PATHS.name],
14
+ macAddress: decodeMacAddress(values[DSIOT_ADAPTER_PATHS.macAddress]),
20
15
  firmwareVersion: decodeFirmwareVersion(
21
- values[ADAPTER_PATHS.firmwareVersion],
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: `${EDGE_ROOT}/${edge}.${pc.pn}`,
58
+ to: `${DSIOT_EDGE_ROOT}/${edge}.${pc.pn}`,
82
59
  pc,
83
60
  })),
84
61
  };