daikin-local-control 0.1.1

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 (115) hide show
  1. package/DESIGN.md +98 -0
  2. package/LICENSE +674 -0
  3. package/README.md +317 -0
  4. package/UNSUPPORTED.md +43 -0
  5. package/dist/authentication.d.ts +24 -0
  6. package/dist/authentication.d.ts.map +1 -0
  7. package/dist/authentication.js +30 -0
  8. package/dist/authentication.js.map +1 -0
  9. package/dist/clients/classic.d.ts +53 -0
  10. package/dist/clients/classic.d.ts.map +1 -0
  11. package/dist/clients/classic.js +142 -0
  12. package/dist/clients/classic.js.map +1 -0
  13. package/dist/clients/client.d.ts +21 -0
  14. package/dist/clients/client.d.ts.map +1 -0
  15. package/dist/clients/client.js +2 -0
  16. package/dist/clients/client.js.map +1 -0
  17. package/dist/clients/dsiot.d.ts +28 -0
  18. package/dist/clients/dsiot.d.ts.map +1 -0
  19. package/dist/clients/dsiot.js +66 -0
  20. package/dist/clients/dsiot.js.map +1 -0
  21. package/dist/codecs/classic/capabilities.d.ts +19 -0
  22. package/dist/codecs/classic/capabilities.d.ts.map +1 -0
  23. package/dist/codecs/classic/capabilities.js +208 -0
  24. package/dist/codecs/classic/capabilities.js.map +1 -0
  25. package/dist/codecs/classic/extendedControl.d.ts +20 -0
  26. package/dist/codecs/classic/extendedControl.d.ts.map +1 -0
  27. package/dist/codecs/classic/extendedControl.js +88 -0
  28. package/dist/codecs/classic/extendedControl.js.map +1 -0
  29. package/dist/codecs/classic/keyValue.d.ts +10 -0
  30. package/dist/codecs/classic/keyValue.d.ts.map +1 -0
  31. package/dist/codecs/classic/keyValue.js +36 -0
  32. package/dist/codecs/classic/keyValue.js.map +1 -0
  33. package/dist/codecs/classic/state.d.ts +18 -0
  34. package/dist/codecs/classic/state.d.ts.map +1 -0
  35. package/dist/codecs/classic/state.js +235 -0
  36. package/dist/codecs/classic/state.js.map +1 -0
  37. package/dist/codecs/credentials.d.ts +8 -0
  38. package/dist/codecs/credentials.d.ts.map +1 -0
  39. package/dist/codecs/credentials.js +14 -0
  40. package/dist/codecs/credentials.js.map +1 -0
  41. package/dist/codecs/device.d.ts +6 -0
  42. package/dist/codecs/device.d.ts.map +1 -0
  43. package/dist/codecs/device.js +35 -0
  44. package/dist/codecs/device.js.map +1 -0
  45. package/dist/codecs/dsiot/capabilities.d.ts +5 -0
  46. package/dist/codecs/dsiot/capabilities.d.ts.map +1 -0
  47. package/dist/codecs/dsiot/capabilities.js +139 -0
  48. package/dist/codecs/dsiot/capabilities.js.map +1 -0
  49. package/dist/codecs/dsiot/multireq.d.ts +37 -0
  50. package/dist/codecs/dsiot/multireq.d.ts.map +1 -0
  51. package/dist/codecs/dsiot/multireq.js +122 -0
  52. package/dist/codecs/dsiot/multireq.js.map +1 -0
  53. package/dist/codecs/dsiot/state.d.ts +92 -0
  54. package/dist/codecs/dsiot/state.d.ts.map +1 -0
  55. package/dist/codecs/dsiot/state.js +461 -0
  56. package/dist/codecs/dsiot/state.js.map +1 -0
  57. package/dist/discovery.d.ts +17 -0
  58. package/dist/discovery.d.ts.map +1 -0
  59. package/dist/discovery.js +67 -0
  60. package/dist/discovery.js.map +1 -0
  61. package/dist/error.d.ts +15 -0
  62. package/dist/error.d.ts.map +1 -0
  63. package/dist/error.js +20 -0
  64. package/dist/error.js.map +1 -0
  65. package/dist/factory.d.ts +26 -0
  66. package/dist/factory.d.ts.map +1 -0
  67. package/dist/factory.js +31 -0
  68. package/dist/factory.js.map +1 -0
  69. package/dist/index.d.ts +22 -0
  70. package/dist/index.d.ts.map +1 -0
  71. package/dist/index.js +21 -0
  72. package/dist/index.js.map +1 -0
  73. package/dist/models/capabilities.d.ts +42 -0
  74. package/dist/models/capabilities.d.ts.map +1 -0
  75. package/dist/models/capabilities.js +2 -0
  76. package/dist/models/capabilities.js.map +1 -0
  77. package/dist/models/device.d.ts +24 -0
  78. package/dist/models/device.d.ts.map +1 -0
  79. package/dist/models/device.js +2 -0
  80. package/dist/models/device.js.map +1 -0
  81. package/dist/models/extendedControl.d.ts +31 -0
  82. package/dist/models/extendedControl.d.ts.map +1 -0
  83. package/dist/models/extendedControl.js +7 -0
  84. package/dist/models/extendedControl.js.map +1 -0
  85. package/dist/models/state.d.ts +56 -0
  86. package/dist/models/state.d.ts.map +1 -0
  87. package/dist/models/state.js +49 -0
  88. package/dist/models/state.js.map +1 -0
  89. package/dist/transport.d.ts +75 -0
  90. package/dist/transport.d.ts.map +1 -0
  91. package/dist/transport.js +118 -0
  92. package/dist/transport.js.map +1 -0
  93. package/package.json +62 -0
  94. package/src/authentication.ts +53 -0
  95. package/src/clients/classic.ts +251 -0
  96. package/src/clients/client.ts +29 -0
  97. package/src/clients/dsiot.ts +116 -0
  98. package/src/codecs/classic/capabilities.ts +272 -0
  99. package/src/codecs/classic/extendedControl.ts +132 -0
  100. package/src/codecs/classic/keyValue.ts +39 -0
  101. package/src/codecs/classic/state.ts +302 -0
  102. package/src/codecs/credentials.ts +19 -0
  103. package/src/codecs/device.ts +41 -0
  104. package/src/codecs/dsiot/capabilities.ts +209 -0
  105. package/src/codecs/dsiot/multireq.ts +162 -0
  106. package/src/codecs/dsiot/state.ts +644 -0
  107. package/src/discovery.ts +85 -0
  108. package/src/error.ts +20 -0
  109. package/src/factory.ts +66 -0
  110. package/src/index.ts +21 -0
  111. package/src/models/capabilities.ts +52 -0
  112. package/src/models/device.ts +25 -0
  113. package/src/models/extendedControl.ts +41 -0
  114. package/src/models/state.ts +110 -0
  115. package/src/transport.ts +191 -0
@@ -0,0 +1,53 @@
1
+ import {
2
+ encodeClassicLocalPassword,
3
+ encodeDsiotLocalPassword,
4
+ } from "./codecs/credentials.js";
5
+
6
+ // Authentication is orthogonal to the wire protocol, so it is injected rather
7
+ // than subclassed. A strategy contributes both headers and query, because the
8
+ // classic protocol carries its local password as a query parameter while
9
+ // DSIOT carries it as a Basic header.
10
+ //
11
+ // An adapter without a local password (basic_info `lpw_flag=0`) takes no
12
+ // strategy: the uuid header alone is accepted. `en_secure` is not
13
+ // authentication here: it means the terminal uuid must be registered
14
+ // (`registerTerminal`), plus HTTPS on a Marvell adapter, which is the
15
+ // transport's scheme.
16
+
17
+ /** `DaikinTransport.send` calls both methods on every request and adds what
18
+ * they return. */
19
+ export interface DaikinAuthentication {
20
+ headers(): Record<string, string> | Promise<Record<string, string>>;
21
+ /** Replaces a request parameter of the same name. */
22
+ query(): Record<string, string> | Promise<Record<string, string>>;
23
+ }
24
+
25
+ /** Classic adapter that requires the local password: sent as
26
+ * `?lpw=<value>`. */
27
+ export class ClassicLocalPasswordAuthentication
28
+ implements DaikinAuthentication
29
+ {
30
+ constructor(private readonly localPassword: string) {}
31
+
32
+ headers(): Record<string, string> {
33
+ return {};
34
+ }
35
+
36
+ query(): Record<string, string> {
37
+ return encodeClassicLocalPassword(this.localPassword);
38
+ }
39
+ }
40
+
41
+ /** DSIOT adapter that requires the local password: sent as
42
+ * `Authorization: Basic <lpw>`. */
43
+ export class DsiotLocalPasswordAuthentication implements DaikinAuthentication {
44
+ constructor(private readonly localPassword: string) {}
45
+
46
+ headers(): Record<string, string> {
47
+ return encodeDsiotLocalPassword(this.localPassword);
48
+ }
49
+
50
+ query(): Record<string, string> {
51
+ return {};
52
+ }
53
+ }
@@ -0,0 +1,251 @@
1
+ import {
2
+ mergeClassicCapabilityFields,
3
+ parseClassicCapabilities,
4
+ } from "../codecs/classic/capabilities.js";
5
+ import {
6
+ applyClassicOutOfHomeAccess,
7
+ encodeClassicDemandControl,
8
+ encodeClassicEnergyPrice,
9
+ encodeClassicEnergyTarget,
10
+ encodeClassicGroup,
11
+ encodeClassicHolidayMode,
12
+ encodeClassicIcon,
13
+ encodeClassicLed,
14
+ encodeClassicName,
15
+ encodeClassicSpecialMode,
16
+ encodeClassicTerminalKey,
17
+ encodeClassicTerminalScope,
18
+ } from "../codecs/classic/extendedControl.js";
19
+ import {
20
+ assertClassicOk,
21
+ parseClassicKeyValue,
22
+ } from "../codecs/classic/keyValue.js";
23
+ import {
24
+ applyClassicControl,
25
+ parseClassicSensors,
26
+ parseClassicState,
27
+ } from "../codecs/classic/state.js";
28
+ import type { DaikinCapabilities } from "../models/capabilities.js";
29
+ import type {
30
+ DemandControl,
31
+ DeviceMetadata,
32
+ SpecialMode,
33
+ TerminalScope,
34
+ } from "../models/extendedControl.js";
35
+ import type {
36
+ DaikinControl,
37
+ DaikinSensors,
38
+ DaikinState,
39
+ } from "../models/state.js";
40
+ import {
41
+ type DaikinRequestOptions,
42
+ type DaikinRetryOptions,
43
+ type DaikinTransport,
44
+ withRetry,
45
+ } from "../transport.js";
46
+ import type { DaikinClient } from "./client.js";
47
+
48
+ // Classic CGI endpoints (BRP069-style). Reads and writes are both HTTP GET with
49
+ // query parameters; the reply is `key=value,key=value`.
50
+ const CLASSIC_ROUTES = {
51
+ basicInfo: "/common/basic_info",
52
+ getModelInfo: "/aircon/get_model_info",
53
+ getControlInfo: "/aircon/get_control_info",
54
+ setControlInfo: "/aircon/set_control_info",
55
+ getSensorInfo: "/aircon/get_sensor_info",
56
+ setSpecialMode: "/aircon/set_special_mode",
57
+ setDemandControl: "/aircon/set_demand_control",
58
+ setPrice: "/aircon/set_price",
59
+ setTarget: "/aircon/set_target",
60
+ setLed: "/common/set_led",
61
+ setHoliday: "/common/set_holiday",
62
+ setName: "/common/set_name",
63
+ setIcon: "/common/set_icon",
64
+ setGroup: "/common/set_group",
65
+ getRemoteMethod: "/common/get_remote_method",
66
+ setRemoteMethod: "/common/set_remote_method",
67
+ reboot: "/common/reboot",
68
+ registerTerminal: "/common/register_terminal",
69
+ unregisterTerminal: "/common/unregister_terminal",
70
+ } as const;
71
+
72
+ /** Classic CGI adapter (BRP069-style). Reads and writes are HTTP GET with query
73
+ * parameters; replies are `key=value,key=value`. */
74
+ export class ClassicDaikinClient implements DaikinClient {
75
+ // A unit's capabilities don't change, and `setState` needs them, so they are
76
+ // read once.
77
+ private capabilityFields: Promise<Record<string, string>> | undefined;
78
+
79
+ /** `close` closes `transport`, so give each client a transport of its own. */
80
+ constructor(
81
+ private readonly transport: DaikinTransport,
82
+ private readonly retryOptions?: DaikinRetryOptions,
83
+ ) {}
84
+
85
+ close(): Promise<void> {
86
+ return this.transport.close();
87
+ }
88
+
89
+ async getCapabilities(): Promise<DaikinCapabilities> {
90
+ return parseClassicCapabilities(await this.readCapabilityFields());
91
+ }
92
+
93
+ async getState(): Promise<DaikinState> {
94
+ return parseClassicState(await this.command(CLASSIC_ROUTES.getControlInfo));
95
+ }
96
+
97
+ async setState(control: DaikinControl): Promise<void> {
98
+ const capabilityFields = await this.readCapabilityFields();
99
+ const current = await this.command(CLASSIC_ROUTES.getControlInfo);
100
+ await this.command(
101
+ CLASSIC_ROUTES.setControlInfo,
102
+ applyClassicControl(current, control, capabilityFields),
103
+ );
104
+ }
105
+
106
+ async getSensors(): Promise<DaikinSensors> {
107
+ return parseClassicSensors(
108
+ await this.command(CLASSIC_ROUTES.getSensorInfo),
109
+ );
110
+ }
111
+
112
+ // --- extended controls ---------------------------------------------------
113
+ // Each maps to its own endpoint (not control_info).
114
+
115
+ /** Adapter status LED on/off (`set_led`). */
116
+ async setLed(on: boolean): Promise<void> {
117
+ await this.command(CLASSIC_ROUTES.setLed, encodeClassicLed(on));
118
+ }
119
+
120
+ /** Holiday / away mode (`set_holiday`). */
121
+ async setHolidayMode(enabled: boolean): Promise<void> {
122
+ await this.command(
123
+ CLASSIC_ROUTES.setHoliday,
124
+ encodeClassicHolidayMode(enabled),
125
+ );
126
+ }
127
+
128
+ /** Demand control — cap the unit's power draw (`set_demand_control`). */
129
+ async setDemandControl(demand: DemandControl): Promise<void> {
130
+ await this.command(
131
+ CLASSIC_ROUTES.setDemandControl,
132
+ encodeClassicDemandControl(demand),
133
+ );
134
+ }
135
+
136
+ /** Powerful, econo or streamer (`set_special_mode`). */
137
+ async setSpecialMode(mode: SpecialMode, enabled: boolean): Promise<void> {
138
+ await this.command(
139
+ CLASSIC_ROUTES.setSpecialMode,
140
+ encodeClassicSpecialMode(mode, enabled),
141
+ );
142
+ }
143
+
144
+ /** Whether the unit can be operated from outside the home network
145
+ * (`set_remote_method`). */
146
+ async setOutOfHomeAccess(enabled: boolean): Promise<void> {
147
+ const current = await this.command(CLASSIC_ROUTES.getRemoteMethod);
148
+ await this.command(
149
+ CLASSIC_ROUTES.setRemoteMethod,
150
+ applyClassicOutOfHomeAccess(current, enabled),
151
+ );
152
+ }
153
+
154
+ /** Electricity price per kWh for the consumption estimates (`set_price`),
155
+ * to two decimal places. */
156
+ async setEnergyPrice(price: number): Promise<void> {
157
+ await this.command(
158
+ CLASSIC_ROUTES.setPrice,
159
+ encodeClassicEnergyPrice(price),
160
+ );
161
+ }
162
+
163
+ /** Energy target for the "target" feature (`set_target`). */
164
+ async setEnergyTarget(target: number): Promise<void> {
165
+ await this.command(
166
+ CLASSIC_ROUTES.setTarget,
167
+ encodeClassicEnergyTarget(target),
168
+ );
169
+ }
170
+
171
+ /** Name / icon / group metadata (`set_name` etc.). */
172
+ async setMetadata(metadata: DeviceMetadata): Promise<void> {
173
+ if (metadata.name !== undefined) {
174
+ await this.command(
175
+ CLASSIC_ROUTES.setName,
176
+ encodeClassicName(metadata.name),
177
+ );
178
+ }
179
+ if (metadata.icon !== undefined) {
180
+ await this.command(
181
+ CLASSIC_ROUTES.setIcon,
182
+ encodeClassicIcon(metadata.icon),
183
+ );
184
+ }
185
+ if (metadata.group !== undefined) {
186
+ await this.command(
187
+ CLASSIC_ROUTES.setGroup,
188
+ encodeClassicGroup(metadata.group),
189
+ );
190
+ }
191
+ }
192
+
193
+ /** Register this client's uuid with an adapter whose
194
+ * `requiresRegistration` is true, using the KEY printed on the adapter's
195
+ * label (`register_terminal`). Once per uuid; the adapter remembers it. */
196
+ async registerTerminal(key: string): Promise<void> {
197
+ await this.command(
198
+ CLASSIC_ROUTES.registerTerminal,
199
+ encodeClassicTerminalKey(key),
200
+ );
201
+ }
202
+
203
+ /** Unregister terminals (`unregister_terminal`): this uuid, every other
204
+ * uuid, or all of them. */
205
+ async unregisterTerminal(scope: TerminalScope = "self"): Promise<void> {
206
+ await this.command(
207
+ CLASSIC_ROUTES.unregisterTerminal,
208
+ encodeClassicTerminalScope(scope),
209
+ );
210
+ }
211
+
212
+ /** Reboot the adapter (`reboot`). */
213
+ async reboot(): Promise<void> {
214
+ await this.command(CLASSIC_ROUTES.reboot, undefined, { retry: false });
215
+ }
216
+
217
+ /** Calls any classic CGI endpoint with query parameters and returns the reply's
218
+ * `key=value` fields. It reaches the controls without a typed method:
219
+ * timers, schedules, programs, network, credentials and firmware. It throws
220
+ * a `DaikinError` when the reply's `ret` is not `OK`. */
221
+ async command(
222
+ path: string,
223
+ parameters?: Record<string, string>,
224
+ { retry = true }: DaikinRequestOptions = {},
225
+ ): Promise<Record<string, string>> {
226
+ const send = () =>
227
+ this.transport.send({
228
+ method: "GET",
229
+ path,
230
+ ...(parameters ? { query: parameters } : {}),
231
+ });
232
+ const fields = parseClassicKeyValue(
233
+ await (retry ? withRetry(send, this.retryOptions) : send()),
234
+ );
235
+ assertClassicOk(fields);
236
+ return fields;
237
+ }
238
+
239
+ private readCapabilityFields(): Promise<Record<string, string>> {
240
+ this.capabilityFields ??= (async () =>
241
+ mergeClassicCapabilityFields(
242
+ await this.command(CLASSIC_ROUTES.basicInfo),
243
+ await this.command(CLASSIC_ROUTES.getModelInfo),
244
+ ))();
245
+ // A failed read is not cached, so the next call tries again.
246
+ this.capabilityFields.catch(() => {
247
+ this.capabilityFields = undefined;
248
+ });
249
+ return this.capabilityFields;
250
+ }
251
+ }
@@ -0,0 +1,29 @@
1
+ import type { DaikinCapabilities } from "../models/capabilities.js";
2
+ import type {
3
+ DaikinControl,
4
+ DaikinSensors,
5
+ DaikinState,
6
+ } from "../models/state.js";
7
+
8
+ /** The controls every adapter offers, whatever its wire protocol or
9
+ * authentication. `createDaikinClient` picks the implementation, so a caller
10
+ * needs the concrete class only for what one protocol adds, such as
11
+ * `ClassicDaikinClient.registerTerminal`. Every method rejects with a
12
+ * `DaikinError` when its request fails. */
13
+ export interface DaikinClient {
14
+ /** The unit's supported features. */
15
+ getCapabilities(): Promise<DaikinCapabilities>;
16
+
17
+ /** Current controllable state. */
18
+ getState(): Promise<DaikinState>;
19
+
20
+ /** Change part of the state. The client reads the current state, overlays the
21
+ * control, and writes the whole thing back. */
22
+ setState(control: DaikinControl): Promise<void>;
23
+
24
+ /** Read-only temperature / humidity readings. */
25
+ getSensors(): Promise<DaikinSensors>;
26
+
27
+ /** Close the connection to the adapter. */
28
+ close(): Promise<void>;
29
+ }
@@ -0,0 +1,116 @@
1
+ import { parseDsiotCapabilities } from "../codecs/dsiot/capabilities.js";
2
+ import {
3
+ buildDsiotMultireqGet,
4
+ buildDsiotMultireqUpdate,
5
+ DSIOT_TARGETS,
6
+ type DsiotUpdate,
7
+ parseDsiotMultireqReply,
8
+ } from "../codecs/dsiot/multireq.js";
9
+ import {
10
+ applyDsiotControl,
11
+ parseDsiotSensors,
12
+ parseDsiotState,
13
+ } from "../codecs/dsiot/state.js";
14
+ import type { DaikinCapabilities } from "../models/capabilities.js";
15
+ import type {
16
+ DaikinControl,
17
+ DaikinSensors,
18
+ DaikinState,
19
+ } from "../models/state.js";
20
+ import {
21
+ type DaikinRequestOptions,
22
+ type DaikinRetryOptions,
23
+ type DaikinTransport,
24
+ withRetry,
25
+ } from "../transport.js";
26
+ import type { DaikinClient } from "./client.js";
27
+
28
+ // DSIOT endpoints (BRP084-style). Climate state is a POST (read) or PUT (write)
29
+ // of a JSON property tree to `multireq`.
30
+ const DSIOT_ROUTES = {
31
+ multireq: "/dsiot/multireq",
32
+ reboot: "/config/system/reboot",
33
+ } as const;
34
+
35
+ /** DSIOT adapter (BRP084-style). One endpoint, `/dsiot/multireq`: POST to read
36
+ * whole `dgc_status` trees, PUT to write one merged tree. */
37
+ export class DsiotDaikinClient implements DaikinClient {
38
+ /** `close` closes `transport`, so give each client a transport of its own. */
39
+ constructor(
40
+ private readonly transport: DaikinTransport,
41
+ private readonly retryOptions?: DaikinRetryOptions,
42
+ ) {}
43
+
44
+ close(): Promise<void> {
45
+ return this.transport.close();
46
+ }
47
+
48
+ async getCapabilities(): Promise<DaikinCapabilities> {
49
+ return parseDsiotCapabilities(
50
+ await this.multireqGet([
51
+ DSIOT_TARGETS.adapterInfo,
52
+ DSIOT_TARGETS.indoorStatus,
53
+ ]),
54
+ );
55
+ }
56
+
57
+ async getState(): Promise<DaikinState> {
58
+ return parseDsiotState(
59
+ await this.multireqGet([DSIOT_TARGETS.indoorStatus]),
60
+ );
61
+ }
62
+
63
+ async setState(control: DaikinControl): Promise<void> {
64
+ const values = await this.multireqGet([DSIOT_TARGETS.indoorStatus]);
65
+ await this.multireqUpdate(applyDsiotControl(values, control));
66
+ }
67
+
68
+ async getSensors(): Promise<DaikinSensors> {
69
+ return parseDsiotSensors(
70
+ await this.multireqGet([
71
+ DSIOT_TARGETS.indoorStatus,
72
+ DSIOT_TARGETS.outdoorStatus,
73
+ ]),
74
+ );
75
+ }
76
+
77
+ // --- extended controls ---------------------------------------------------
78
+
79
+ /** Reboot the adapter (`/config/system/reboot`). */
80
+ async reboot(): Promise<void> {
81
+ await this.transport.send({
82
+ method: "POST",
83
+ path: DSIOT_ROUTES.reboot,
84
+ });
85
+ }
86
+
87
+ /** Raw multireq read of whole resources (`DSIOT_TARGETS` values or any `to`),
88
+ * flattened to dotted path -> value. */
89
+ async multireqGet(
90
+ targets: string[],
91
+ options?: DaikinRequestOptions,
92
+ ): Promise<Record<string, string>> {
93
+ return this.multireq("POST", buildDsiotMultireqGet(targets), options);
94
+ }
95
+
96
+ /** Raw multireq write: update any leaves by dotted path, merged into one tree
97
+ * on the indoor edge. Returns the reply, flattened as `multireqGet` does. */
98
+ async multireqUpdate(
99
+ updates: DsiotUpdate[],
100
+ options?: DaikinRequestOptions,
101
+ ): Promise<Record<string, string>> {
102
+ return this.multireq("PUT", buildDsiotMultireqUpdate(updates), options);
103
+ }
104
+
105
+ private async multireq(
106
+ method: "POST" | "PUT",
107
+ jsonBody: unknown,
108
+ { retry = true }: DaikinRequestOptions = {},
109
+ ): Promise<Record<string, string>> {
110
+ const send = () =>
111
+ this.transport.send({ method, path: DSIOT_ROUTES.multireq, jsonBody });
112
+ return parseDsiotMultireqReply(
113
+ await (retry ? withRetry(send, this.retryOptions) : send()),
114
+ );
115
+ }
116
+ }