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
@@ -0,0 +1,116 @@
1
+ import { type IncomingHttpHeaders } from "node:http";
2
+ import type { DaikinDeviceInfo } from "../models/device.js";
3
+ import type { DaikinSensors, DaikinState, SpecialMode } from "../models/state.js";
4
+ /** One HTTP request a simulator received. */
5
+ export interface DaikinSimulatorRequest {
6
+ method: string;
7
+ /** From the leading `/`, without the query string. */
8
+ path: string;
9
+ /** Percent-decoded. */
10
+ query: Record<string, string>;
11
+ headers: IncomingHttpHeaders;
12
+ body: string;
13
+ }
14
+ /** What `startClassicSimulator` and `startDsiotSimulator` both resolve with. */
15
+ export interface DaikinSimulator {
16
+ /** `<address>:<port>`, as `DaikinDevice.host` takes it: `127.0.0.1`, unless
17
+ * the simulator was started at another `address`, and a port that was free
18
+ * when it started. */
19
+ readonly host: string;
20
+ /** Every request received, oldest first, the ones the simulator refused
21
+ * included. A request is added once its whole body has arrived, before it
22
+ * waits for its turn to be answered. */
23
+ readonly requests: DaikinSimulatorRequest[];
24
+ /** Changes the unit as its remote control would. A field left out, or
25
+ * `null`, stays as the unit holds it, and a new `mode` takes up what the
26
+ * unit remembers for that mode before the other fields are applied. It
27
+ * throws an `Error` for a field the unit has nothing to hold, such as a
28
+ * `verticalPosition` on a unit without fixed louver positions, and holds
29
+ * any other value as given, whether or not `getCapabilities` lists it. */
30
+ setState(state: Partial<DaikinState>): void;
31
+ /** Changes what the unit's sensors read. A reading left out stays as it is,
32
+ * and `null` is a reading the unit does not report. */
33
+ setSensors(sensors: Partial<DaikinSensors>): void;
34
+ /** Closes every connection and stops listening. A request still waiting is
35
+ * never answered. */
36
+ stop(): Promise<void>;
37
+ }
38
+ /** In °C. */
39
+ export interface DaikinSimulatorTemperatures {
40
+ min: number;
41
+ max: number;
42
+ }
43
+ /** What a simulated unit has, where both protocols can say it. Anything else
44
+ * about a unit is set in the adapter's own terms: `modelInfo` on a classic
45
+ * simulator, and `leaves` on a DSIOT one. */
46
+ export interface DaikinSimulatorUnit {
47
+ /** A unit that only cools offers dehumidify, cool and fan. Defaults to
48
+ * false: a heat pump, which offers auto and heat as well. */
49
+ coolingOnly?: boolean;
50
+ /** Whether the fan speed can be set. Defaults to true. */
51
+ fan?: boolean;
52
+ /** The axes the louvers swing on: `vertical` moves the air up and down
53
+ * alone, and `both` left and right as well. Defaults to `both`. */
54
+ swing?: "none" | "vertical" | "both";
55
+ /** The special modes the unit takes. Defaults to all three. */
56
+ specialModes?: readonly SpecialMode[];
57
+ /** The setpoints cool takes. A classic adapter reports whole degrees.
58
+ * Defaults to 18 to 32 on a DSIOT simulator, and to none reported on a
59
+ * classic one. */
60
+ coolTemperaturesInC?: DaikinSimulatorTemperatures;
61
+ /** The setpoints heat takes. A classic adapter reports whole degrees.
62
+ * Defaults to 10 to 30 on a DSIOT simulator, and to none reported on a
63
+ * classic one. */
64
+ heatTemperaturesInC?: DaikinSimulatorTemperatures;
65
+ }
66
+ /** The options `startClassicSimulator` and `startDsiotSimulator` both take.
67
+ * A simulator applies `deviceInfo`, `state` and `sensors` after the fields
68
+ * given in the adapter's own terms, so they replace a field given both
69
+ * ways. */
70
+ export interface StartSimulatorOptions {
71
+ /** The adapter's name, MAC address and firmware version, as
72
+ * `getDeviceInfo` reads them back. A field left out keeps its default.
73
+ * Defaults to a name of `Simulator`. */
74
+ deviceInfo?: DaikinDeviceInfo;
75
+ /** The state the unit starts in: see `DaikinSimulator.setState`. Defaults
76
+ * to off, in cool at 24 °C on the auto fan speed, with nothing swinging
77
+ * and no special mode on. */
78
+ state?: Partial<DaikinState>;
79
+ /** What the unit's sensors read at the start: see
80
+ * `DaikinSimulator.setSensors`. Defaults to 26 °C and 55 % indoors, and
81
+ * 30 °C outdoors. */
82
+ sensors?: Partial<DaikinSensors>;
83
+ /** How long, in milliseconds, the adapter takes to answer a request. It
84
+ * answers one request at a time, in the order they arrive, so a request
85
+ * waits this long for each one ahead of it as well. Defaults to 0. */
86
+ replyDelayInMs?: number;
87
+ /** The address of this machine the adapter is at: it listens for HTTP
88
+ * there, and answers a discovery probe from there. Defaults to `127.0.0.1`,
89
+ * which nothing outside the machine reaches. */
90
+ address?: string;
91
+ /** Whether the adapter answers the discovery probe, so `discoverDevices`
92
+ * finds it at `host`. Defaults to false. Any number of simulators answer
93
+ * at once, in one process or several, and `discoverDevices` finds each at
94
+ * its own port. Starting one rejects with the socket's error only while a
95
+ * program that is not a simulator holds UDP port 30050. */
96
+ answersDiscovery?: boolean;
97
+ }
98
+ /** What `startSimulator` resolves with: the part of a `DaikinSimulator` that
99
+ * is the same for both protocols. */
100
+ export type SimulatorServer = Pick<DaikinSimulator, "host" | "requests" | "stop">;
101
+ export interface SimulatorReply {
102
+ statusCode: number;
103
+ body: string;
104
+ }
105
+ /** The reply to a request whose answer from an adapter is not known: HTTP 501,
106
+ * which no adapter is known to send, so a caller cannot take it for one. */
107
+ export declare function unanswered(request: DaikinSimulatorRequest): SimulatorReply;
108
+ /** Joins fields as a classic reply and a discovery reply carry them:
109
+ * `key=value` pairs separated by commas, `ret` first. */
110
+ export declare function formatKeyValue(ret: string, fields?: Record<string, string>): string;
111
+ /** Listens on a free port of `options.address` and answers each request with
112
+ * what `answer` returns for it, one request at a time. With
113
+ * `options.answersDiscovery`, it answers the discovery probe with
114
+ * `discoveryFields()` and the HTTP port, as `api_port`. */
115
+ export declare function startSimulator(options: StartSimulatorOptions, answer: (request: DaikinSimulatorRequest) => SimulatorReply, discoveryFields: () => Record<string, string>): Promise<SimulatorServer>;
116
+ //# sourceMappingURL=simulator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"simulator.d.ts","sourceRoot":"","sources":["../../src/simulators/simulator.ts"],"names":[],"mappings":"AAEA,OAAO,EAAgB,KAAK,mBAAmB,EAAE,MAAM,WAAW,CAAC;AAEnE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,KAAK,EACV,aAAa,EACb,WAAW,EACX,WAAW,EACZ,MAAM,oBAAoB,CAAC;AAU5B,6CAA6C;AAC7C,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,sDAAsD;IACtD,IAAI,EAAE,MAAM,CAAC;IACb,uBAAuB;IACvB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9B,OAAO,EAAE,mBAAmB,CAAC;IAC7B,IAAI,EAAE,MAAM,CAAC;CACd;AAED,gFAAgF;AAChF,MAAM,WAAW,eAAe;IAC9B;;0BAEsB;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB;;4CAEwC;IACxC,QAAQ,CAAC,QAAQ,EAAE,sBAAsB,EAAE,CAAC;IAC5C;;;;;8EAK0E;IAC1E,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC,WAAW,CAAC,GAAG,IAAI,CAAC;IAC5C;2DACuD;IACvD,UAAU,CAAC,OAAO,EAAE,OAAO,CAAC,aAAa,CAAC,GAAG,IAAI,CAAC;IAClD;yBACqB;IACrB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACvB;AAED,aAAa;AACb,MAAM,WAAW,2BAA2B;IAC1C,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;CACb;AAED;;6CAE6C;AAC7C,MAAM,WAAW,mBAAmB;IAClC;iEAC6D;IAC7D,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,0DAA0D;IAC1D,GAAG,CAAC,EAAE,OAAO,CAAC;IACd;uEACmE;IACnE,KAAK,CAAC,EAAE,MAAM,GAAG,UAAU,GAAG,MAAM,CAAC;IACrC,+DAA+D;IAC/D,YAAY,CAAC,EAAE,SAAS,WAAW,EAAE,CAAC;IACtC;;sBAEkB;IAClB,mBAAmB,CAAC,EAAE,2BAA2B,CAAC;IAClD;;sBAEkB;IAClB,mBAAmB,CAAC,EAAE,2BAA2B,CAAC;CACnD;AAED;;;WAGW;AACX,MAAM,WAAW,qBAAqB;IACpC;;4CAEwC;IACxC,UAAU,CAAC,EAAE,gBAAgB,CAAC;IAC9B;;iCAE6B;IAC7B,KAAK,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,CAAC;IAC7B;;yBAEqB;IACrB,OAAO,CAAC,EAAE,OAAO,CAAC,aAAa,CAAC,CAAC;IACjC;;0EAEsE;IACtE,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;oDAEgD;IAChD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;+DAI2D;IAC3D,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED;qCACqC;AACrC,MAAM,MAAM,eAAe,GAAG,IAAI,CAChC,eAAe,EACf,MAAM,GAAG,UAAU,GAAG,MAAM,CAC7B,CAAC;AAEF,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;CACd;AAED;4EAC4E;AAC5E,wBAAgB,UAAU,CAAC,OAAO,EAAE,sBAAsB,GAAG,cAAc,CAK1E;AAED;yDACyD;AACzD,wBAAgB,cAAc,CAC5B,GAAG,EAAE,MAAM,EACX,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,GAClC,MAAM,CAIR;AAED;;;2DAG2D;AAC3D,wBAAsB,cAAc,CAClC,OAAO,EAAE,qBAAqB,EAC9B,MAAM,EAAE,CAAC,OAAO,EAAE,sBAAsB,KAAK,cAAc,EAC3D,eAAe,EAAE,MAAM,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC5C,OAAO,CAAC,eAAe,CAAC,CA6E1B"}
@@ -0,0 +1,141 @@
1
+ import { createSocket } from "node:dgram";
2
+ import { once } from "node:events";
3
+ import { createServer } from "node:http";
4
+ // What the classic and the DSIOT simulator share: the HTTP server, and the
5
+ // reply to a discovery probe, which both adapters send in the same form.
6
+ const DISCOVERY_PROBE = "DAIKIN_UDP/common/basic_info";
7
+ const DISCOVERY_PORT = 30050;
8
+ const DEFAULT_ADDRESS = "127.0.0.1";
9
+ /** The reply to a request whose answer from an adapter is not known: HTTP 501,
10
+ * which no adapter is known to send, so a caller cannot take it for one. */
11
+ export function unanswered(request) {
12
+ return {
13
+ statusCode: 501,
14
+ body: `The simulator does not answer ${request.method} ${request.path}`,
15
+ };
16
+ }
17
+ /** Joins fields as a classic reply and a discovery reply carry them:
18
+ * `key=value` pairs separated by commas, `ret` first. */
19
+ export function formatKeyValue(ret, fields = {}) {
20
+ return Object.entries({ ret, ...fields })
21
+ .map(([name, value]) => `${name}=${value}`)
22
+ .join(",");
23
+ }
24
+ /** Listens on a free port of `options.address` and answers each request with
25
+ * what `answer` returns for it, one request at a time. With
26
+ * `options.answersDiscovery`, it answers the discovery probe with
27
+ * `discoveryFields()` and the HTTP port, as `api_port`. */
28
+ export async function startSimulator(options, answer, discoveryFields) {
29
+ const requests = [];
30
+ let stopped = false;
31
+ let turn = Promise.resolve();
32
+ const server = createServer((incoming, response) => {
33
+ let body = "";
34
+ incoming.setEncoding("utf8");
35
+ incoming.on("data", (chunk) => {
36
+ body += chunk;
37
+ });
38
+ incoming.on("end", () => {
39
+ const url = new URL(incoming.url ?? "/", "http://simulator");
40
+ const request = {
41
+ method: incoming.method ?? "",
42
+ path: url.pathname,
43
+ query: Object.fromEntries(url.searchParams),
44
+ headers: incoming.headers,
45
+ body,
46
+ };
47
+ requests.push(request);
48
+ turn = turn.then(async () => {
49
+ if (options.replyDelayInMs) {
50
+ await sleep(options.replyDelayInMs);
51
+ }
52
+ if (stopped) {
53
+ return;
54
+ }
55
+ const reply = answer(request);
56
+ response.statusCode = reply.statusCode;
57
+ response.end(reply.body);
58
+ });
59
+ });
60
+ });
61
+ const address = options.address ?? DEFAULT_ADDRESS;
62
+ server.listen(0, address);
63
+ // `once` rejects when the server emits `error` first, as it does for an
64
+ // address the machine does not have.
65
+ await once(server, "listening");
66
+ const { port } = server.address();
67
+ const stopServer = async () => {
68
+ // A client keeps its connection open between requests, and `close` alone
69
+ // waits for it.
70
+ server.closeAllConnections();
71
+ server.close();
72
+ await once(server, "close");
73
+ };
74
+ let discoverySockets = [];
75
+ if (options.answersDiscovery) {
76
+ try {
77
+ discoverySockets = await startDiscoverySockets(address, () => formatKeyValue("OK", {
78
+ ...discoveryFields(),
79
+ api_port: String(port),
80
+ }));
81
+ }
82
+ catch (error) {
83
+ await stopServer();
84
+ throw error;
85
+ }
86
+ }
87
+ return {
88
+ host: `${address}:${port}`,
89
+ requests,
90
+ async stop() {
91
+ // A server closed a second time never emits `close`.
92
+ if (stopped) {
93
+ return;
94
+ }
95
+ stopped = true;
96
+ await Promise.all(discoverySockets.map(closeSocket));
97
+ await stopServer();
98
+ },
99
+ };
100
+ }
101
+ // The probe is a broadcast, which reaches a socket bound to every address and
102
+ // none bound to one. So each simulator hears it on a socket bound to every
103
+ // address, sharing the port with the other simulators, and answers from a
104
+ // second socket bound to its own address: the reply then comes from that
105
+ // address, and a probe sent to that address alone reaches that simulator
106
+ // alone. An adapter answers the address and port the probe came from.
107
+ async function startDiscoverySockets(address, reply) {
108
+ const own = createSocket({ type: "udp4", reuseAddr: true });
109
+ const every = createSocket({ type: "udp4", reuseAddr: true });
110
+ const sockets = [own, every];
111
+ for (const socket of sockets) {
112
+ socket.on("message", (message, remote) => {
113
+ if (message.toString("utf8") === DISCOVERY_PROBE) {
114
+ // Without a callback, a reply that fails to send is emitted as an
115
+ // `error` nothing listens for.
116
+ own.send(reply(), remote.port, remote.address, () => { });
117
+ }
118
+ });
119
+ }
120
+ try {
121
+ own.bind(DISCOVERY_PORT, address);
122
+ // `once` rejects when the socket emits `error` first, as it does for a
123
+ // port another program holds.
124
+ await once(own, "listening");
125
+ every.bind(DISCOVERY_PORT);
126
+ await once(every, "listening");
127
+ }
128
+ catch (error) {
129
+ await Promise.all(sockets.map(closeSocket));
130
+ throw error;
131
+ }
132
+ return sockets;
133
+ }
134
+ async function closeSocket(socket) {
135
+ socket.close();
136
+ await once(socket, "close");
137
+ }
138
+ function sleep(delayInMs) {
139
+ return new Promise((resolve) => setTimeout(resolve, delayInMs));
140
+ }
141
+ //# sourceMappingURL=simulator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"simulator.js","sourceRoot":"","sources":["../../src/simulators/simulator.ts"],"names":[],"mappings":"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"}
@@ -28,7 +28,8 @@ export interface DaikinTransportOptions {
28
28
  host: string;
29
29
  /** HTTPS (a Marvell adapter in secure mode). */
30
30
  usesHttps?: boolean;
31
- /** The terminal uuid, sent as `X-Daikin-uuid` on every call. */
31
+ /** The terminal uuid, sent as `X-Daikin-uuid` on every call: 32 lowercase
32
+ * hexadecimal digits, a UUID without its dashes. */
32
33
  terminalUuid: string;
33
34
  authentication?: DaikinAuthentication;
34
35
  /** How long, in milliseconds, to wait for each of the connection, the
@@ -58,7 +59,10 @@ export declare class DaikinTransport {
58
59
  private readonly options;
59
60
  private readonly origin;
60
61
  private readonly client;
61
- /** Call `close` when done with the adapter. */
62
+ private readonly terminalUuidHeaders;
63
+ /** Call `close` when done with the adapter. It throws an `Error` when
64
+ * `options.terminalUuid` is not in the form
65
+ * `DaikinTransportOptions.terminalUuid` describes. */
62
66
  constructor(options: DaikinTransportOptions);
63
67
  /** Sends one request and returns the reply's body as text. It never retries:
64
68
  * wrap the call in `withRetry` for that.
@@ -1 +1 @@
1
- {"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAIhE,MAAM,WAAW,oBAAoB;IACnC;6CACyC;IACzC,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,kBAAkB;IACjC;;oEAEgE;IAChE,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;CACvB;AAID;;wBAEwB;AACxB,wBAAsB,SAAS,CAAC,OAAO,EACrC,SAAS,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,EACjC,EAAE,UAAuC,EAAE,GAAE,kBAAuB,GACnE,OAAO,CAAC,OAAO,CAAC,CAYlB;AAoBD,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,KAAK,GAAG,MAAM,GAAG,KAAK,CAAC;IAC/B,oDAAoD;IACpD,IAAI,EAAE,MAAM,CAAC;IACb,4DAA4D;IAC5D,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,6DAA6D;IAC7D,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,sBAAsB;IACrC,mDAAmD;IACnD,IAAI,EAAE,MAAM,CAAC;IACb,gDAAgD;IAChD,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,gEAAgE;IAChE,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE,oBAAoB,CAAC;IACtC;;2DAEuD;IACvD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,2BAA2B;IAC3B,GAAG,CAAC,EAAE,gBAAgB,CAAC;CACxB;AAED;;wDAEwD;AACxD,MAAM,WAAW,gBAAgB;IAC/B;mFAC+E;IAC/E,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CACtB;AAED;;;;;;;gBAOgB;AAChB,qBAAa,eAAe;IAKd,OAAO,CAAC,QAAQ,CAAC,OAAO;IAJpC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAEhC,+CAA+C;IAC/C,YAA6B,OAAO,EAAE,sBAAsB,EAU3D;IAED;;;;;;;2BAOuB;IACjB,IAAI,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,CAoDlD;IAED,2CAA2C;IACrC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAE3B;CACF"}
1
+ {"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAIhE,MAAM,WAAW,oBAAoB;IACnC;6CACyC;IACzC,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,kBAAkB;IACjC;;oEAEgE;IAChE,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;CACvB;AAID;;wBAEwB;AACxB,wBAAsB,SAAS,CAAC,OAAO,EACrC,SAAS,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,EACjC,EAAE,UAAuC,EAAE,GAAE,kBAAuB,GACnE,OAAO,CAAC,OAAO,CAAC,CAYlB;AAoBD,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,KAAK,GAAG,MAAM,GAAG,KAAK,CAAC;IAC/B,oDAAoD;IACpD,IAAI,EAAE,MAAM,CAAC;IACb,4DAA4D;IAC5D,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,6DAA6D;IAC7D,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,sBAAsB;IACrC,mDAAmD;IACnD,IAAI,EAAE,MAAM,CAAC;IACb,gDAAgD;IAChD,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;wDACoD;IACpD,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE,oBAAoB,CAAC;IACtC;;2DAEuD;IACvD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,2BAA2B;IAC3B,GAAG,CAAC,EAAE,gBAAgB,CAAC;CACxB;AAED;;wDAEwD;AACxD,MAAM,WAAW,gBAAgB;IAC/B;mFAC+E;IAC/E,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CACtB;AAED;;;;;;;gBAOgB;AAChB,qBAAa,eAAe;IAQd,OAAO,CAAC,QAAQ,CAAC,OAAO;IAPpC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAyB;IAE7D;;0DAEsD;IACtD,YAA6B,OAAO,EAAE,sBAAsB,EAa3D;IAED;;;;;;;2BAOuB;IACjB,IAAI,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,CAoDlD;IAED,2CAA2C;IACrC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAE3B;CACF"}
package/dist/transport.js CHANGED
@@ -45,9 +45,14 @@ export class DaikinTransport {
45
45
  options;
46
46
  origin;
47
47
  client;
48
- /** Call `close` when done with the adapter. */
48
+ terminalUuidHeaders;
49
+ /** Call `close` when done with the adapter. It throws an `Error` when
50
+ * `options.terminalUuid` is not in the form
51
+ * `DaikinTransportOptions.terminalUuid` describes. */
49
52
  constructor(options) {
50
53
  this.options = options;
54
+ // Before the Client, so a uuid that throws leaves no connection open.
55
+ this.terminalUuidHeaders = encodeTerminalUuid(options.terminalUuid);
51
56
  const scheme = options.usesHttps ? "https" : "http";
52
57
  this.origin = `${scheme}://${options.host.replace(/\/+$/, "")}`;
53
58
  const timeoutInMs = options.timeoutInMs ?? DEFAULT_TIMEOUT_IN_MS;
@@ -73,7 +78,7 @@ export class DaikinTransport {
73
78
  .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
74
79
  .join("&");
75
80
  const headers = {
76
- ...encodeTerminalUuid(this.options.terminalUuid),
81
+ ...this.terminalUuidHeaders,
77
82
  ...((await this.options.authentication?.headers()) ?? {}),
78
83
  ...(request.jsonBody !== undefined ? JSON_CONTENT_HEADERS : {}),
79
84
  };
@@ -1 +1 @@
1
- {"version":3,"file":"transport.js","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"transport.js","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "daikin-local-control",
3
- "version": "0.3.1",
3
+ "version": "0.4.0",
4
4
  "description": "Unofficial Node.js and TypeScript client for controlling Daikin air conditioners over the local network (LAN), with no cloud. Supports classic (BRP069, BRP072) and DSIOT (BRP084) Wi-Fi adapters.",
5
5
  "keywords": [
6
6
  "daikin",
@@ -48,7 +48,10 @@
48
48
  "DESIGN.md",
49
49
  "UNSUPPORTED.md"
50
50
  ],
51
- "exports": "./dist/index.js",
51
+ "exports": {
52
+ ".": "./dist/index.js",
53
+ "./simulators": "./dist/simulators/index.js"
54
+ },
52
55
  "devDependencies": {
53
56
  "@biomejs/biome": "2.5.15",
54
57
  "@tsconfig/node22": "^22.0.6",
@@ -133,6 +133,14 @@ export class ClassicDaikinClient implements DaikinClient {
133
133
  return { state: await this.getState(), sensors: await this.getSensors() };
134
134
  }
135
135
 
136
+ /** `set_special_mode`, an endpoint of its own beside `set_control_info`. */
137
+ async setSpecialMode(mode: SpecialMode, enabled: boolean): Promise<void> {
138
+ await this.command(
139
+ CLASSIC_ROUTES.setSpecialMode,
140
+ encodeClassicSpecialMode(mode, enabled),
141
+ );
142
+ }
143
+
136
144
  // --- extended controls ---------------------------------------------------
137
145
  // Each write goes to its own endpoint, not `set_control_info`.
138
146
 
@@ -171,14 +179,6 @@ export class ClassicDaikinClient implements DaikinClient {
171
179
  );
172
180
  }
173
181
 
174
- /** Powerful, econo or streamer (`set_special_mode`). */
175
- async setSpecialMode(mode: SpecialMode, enabled: boolean): Promise<void> {
176
- await this.command(
177
- CLASSIC_ROUTES.setSpecialMode,
178
- encodeClassicSpecialMode(mode, enabled),
179
- );
180
- }
181
-
182
182
  /** Whether the unit can be operated from outside the home network
183
183
  * (`basic_info`): `null` when the adapter does not report it. */
184
184
  async getOutOfHomeAccess(): Promise<boolean | null> {
@@ -5,6 +5,7 @@ import type {
5
5
  DaikinSensors,
6
6
  DaikinState,
7
7
  DaikinStateAndSensors,
8
+ SpecialMode,
8
9
  } from "../models/state.js";
9
10
 
10
11
  /** The controls every adapter offers, whatever its wire protocol or
@@ -28,6 +29,11 @@ export interface DaikinClient {
28
29
  * `CreateDaikinClientOptions.maxStateAgeForSetStateInMs`. */
29
30
  setState(control: DaikinControl): Promise<void>;
30
31
 
32
+ /** Turn powerful, econo or streamer on or off, with a request of its own.
33
+ * `DaikinCapabilities.specialModes` lists the ones the unit takes in each
34
+ * mode, and the client sends any other as given. */
35
+ setSpecialMode(mode: SpecialMode, enabled: boolean): Promise<void>;
36
+
31
37
  /** Read-only temperature / humidity readings. */
32
38
  getSensors(): Promise<DaikinSensors>;
33
39
 
@@ -3,15 +3,16 @@ import { parseDsiotDeviceInfo } from "../codecs/dsiot/device.js";
3
3
  import {
4
4
  buildDsiotMultireqGet,
5
5
  buildDsiotMultireqUpdate,
6
- DSIOT_TARGETS,
7
6
  type DsiotUpdate,
8
7
  parseDsiotMultireqReply,
9
8
  } from "../codecs/dsiot/multireq.js";
10
9
  import {
11
10
  applyDsiotControl,
11
+ buildDsiotSpecialModeUpdate,
12
12
  parseDsiotSensors,
13
13
  parseDsiotState,
14
14
  } from "../codecs/dsiot/state.js";
15
+ import { DSIOT_TARGETS } from "../codecs/dsiot/tables.js";
15
16
  import type { DaikinCapabilities } from "../models/capabilities.js";
16
17
  import type { DaikinDeviceInfo } from "../models/device.js";
17
18
  import type {
@@ -19,6 +20,7 @@ import type {
19
20
  DaikinSensors,
20
21
  DaikinState,
21
22
  DaikinStateAndSensors,
23
+ SpecialMode,
22
24
  } from "../models/state.js";
23
25
  import {
24
26
  type DaikinRequestOptions,
@@ -103,6 +105,11 @@ export class DsiotDaikinClient implements DaikinClient {
103
105
  };
104
106
  }
105
107
 
108
+ /** One leaf of `dgc_status`, on the outdoor unit for powerful. */
109
+ async setSpecialMode(mode: SpecialMode, enabled: boolean): Promise<void> {
110
+ await this.multireq("PUT", buildDsiotSpecialModeUpdate(mode, enabled));
111
+ }
112
+
106
113
  // --- extended controls ---------------------------------------------------
107
114
 
108
115
  /** Reboot the adapter (`/config/system/reboot`). */
@@ -16,16 +16,12 @@ import {
16
16
  type TargetHumidity,
17
17
  type VerticalPosition,
18
18
  } from "../../models/state.js";
19
+ import {
20
+ CLASSIC_COOLING_ONLY_TYPE,
21
+ CLASSIC_SPECIAL_MODE_BITS,
22
+ } from "./tables.js";
19
23
 
20
24
  const CAPABILITY_VERSIONS = ["0", "1", "2", "3"];
21
- // `en_spmode` sets one bit for each special mode the unit takes. Bit 8 is a
22
- // mode outside `SpecialMode`. The powerful and econo bits count only at
23
- // `cpv=3` with a `cpv_minor` other than `00`: see UNSUPPORTED.md.
24
- const SPECIAL_MODE_BITS: Record<SpecialMode, number> = {
25
- powerful: 1,
26
- econo: 2,
27
- streamer: 4,
28
- };
29
25
  const COOLING_ONLY_MODES: readonly OperatingMode[] = [
30
26
  "cool",
31
27
  "dehumidify",
@@ -120,7 +116,8 @@ export function parseClassicCapabilities(
120
116
  }
121
117
 
122
118
  // Lists none for a unit that reports no `en_spmode`, and econo in every mode
123
- // but fan: see UNSUPPORTED.md.
119
+ // but fan. The powerful and econo bits count only at `cpv=3` with a
120
+ // `cpv_minor` other than `00`: see UNSUPPORTED.md.
124
121
  function specialModes(
125
122
  fields: Record<string, string>,
126
123
  cpv: string,
@@ -131,7 +128,7 @@ function specialModes(
131
128
  cpv === "3" && (fields.cpv_minor ?? "00") !== "00";
132
129
  const taken = SPECIAL_MODES.filter(
133
130
  (specialMode) =>
134
- (bits & SPECIAL_MODE_BITS[specialMode]) !== 0 &&
131
+ (bits & CLASSIC_SPECIAL_MODE_BITS[specialMode]) !== 0 &&
135
132
  (specialMode === "streamer" || followsPowerfulAndEcono),
136
133
  );
137
134
  const inMode = (mode: OperatingMode) =>
@@ -155,7 +152,7 @@ function modes(fields: Record<string, string>, cpv: string): OperatingMode[] {
155
152
  if (!CAPABILITY_VERSIONS.includes(cpv)) {
156
153
  return [];
157
154
  }
158
- return fields.model_type === "C"
155
+ return fields.model_type === CLASSIC_COOLING_ONLY_TYPE
159
156
  ? OPERATING_MODES.filter((mode) => COOLING_ONLY_MODES.includes(mode))
160
157
  : [...OPERATING_MODES];
161
158
  }
@@ -7,36 +7,20 @@ import type {
7
7
  } from "../../models/extendedControl.js";
8
8
  import type { SpecialMode } from "../../models/state.js";
9
9
  import { decodeClassicText } from "./keyValue.js";
10
+ import {
11
+ CLASSIC_REMOTE_METHODS,
12
+ CLASSIC_SPECIAL_MODE_KIND_CODES,
13
+ CLASSIC_TERMINAL_SCOPE_CODES,
14
+ } from "./tables.js";
10
15
 
11
16
  // Encodes each control outside `DaikinState` as the query its classic `set_*`
12
17
  // endpoint takes, and reads each one back from the reply that reports it.
13
18
 
14
- const TERMINAL_SCOPE_CODES: Record<TerminalScope, string> = {
15
- self: "0",
16
- others: "1",
17
- all: "2",
18
- };
19
-
20
19
  const DEMAND_MODE_CODES: Record<DemandMode, string> = {
21
20
  manual: "0",
22
21
  auto: "2",
23
22
  };
24
23
 
25
- // `spmode_kind` on the wire, which numbers the modes differently from the
26
- // `adv` that `parseClassicState` reads.
27
- const SPECIAL_MODE_KIND_CODES: Record<
28
- Exclude<SpecialMode, "streamer">,
29
- string
30
- > = {
31
- powerful: "1",
32
- econo: "2",
33
- };
34
-
35
- // `method`: the adapter is reached from outside the home network while it
36
- // polls, and from inside only otherwise.
37
- const REMOTE_METHOD_POLLING = "polling";
38
- const REMOTE_METHOD_HOME_ONLY = "home only";
39
-
40
24
  const REMOTE_METHOD_INTERVALS = ["notice_ip_int", "notice_sync_int"];
41
25
 
42
26
  export function encodeClassicLed(on: boolean): Record<string, string> {
@@ -81,7 +65,10 @@ export function encodeClassicSpecialMode(
81
65
  ): Record<string, string> {
82
66
  return mode === "streamer"
83
67
  ? { en_streamer: flag(enabled) }
84
- : { set_spmode: flag(enabled), spmode_kind: SPECIAL_MODE_KIND_CODES[mode] };
68
+ : {
69
+ set_spmode: flag(enabled),
70
+ spmode_kind: CLASSIC_SPECIAL_MODE_KIND_CODES[mode],
71
+ };
85
72
  }
86
73
 
87
74
  /** Reads out-of-home access from a `basic_info` reply: `null` when it carries
@@ -91,7 +78,7 @@ export function parseClassicOutOfHomeAccess(
91
78
  ): boolean | null {
92
79
  return fields.method === undefined
93
80
  ? null
94
- : fields.method === REMOTE_METHOD_POLLING;
81
+ : fields.method === CLASSIC_REMOTE_METHODS.polling;
95
82
  }
96
83
 
97
84
  /** Turns `current`, the `get_remote_method` reply, and `enabled` into the
@@ -102,7 +89,9 @@ export function applyClassicOutOfHomeAccess(
102
89
  enabled: boolean,
103
90
  ): Record<string, string> {
104
91
  const query: Record<string, string> = {
105
- method: enabled ? REMOTE_METHOD_POLLING : REMOTE_METHOD_HOME_ONLY,
92
+ method: enabled
93
+ ? CLASSIC_REMOTE_METHODS.polling
94
+ : CLASSIC_REMOTE_METHODS.homeOnly,
106
95
  };
107
96
  for (const key of REMOTE_METHOD_INTERVALS) {
108
97
  const value = current[key];
@@ -171,7 +160,7 @@ export function encodeClassicTerminalKey(key: string): Record<string, string> {
171
160
  export function encodeClassicTerminalScope(
172
161
  scope: TerminalScope,
173
162
  ): Record<string, string> {
174
- return { type: TERMINAL_SCOPE_CODES[scope] };
163
+ return { type: CLASSIC_TERMINAL_SCOPE_CODES[scope] };
175
164
  }
176
165
 
177
166
  function flag(on: boolean): string {