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
|
@@ -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":"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"}
|
|
@@ -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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"}
|
package/dist/transport.d.ts
CHANGED
|
@@ -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
|
-
|
|
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.
|
package/dist/transport.d.ts.map
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
-
...
|
|
81
|
+
...this.terminalUuidHeaders,
|
|
77
82
|
...((await this.options.authentication?.headers()) ?? {}),
|
|
78
83
|
...(request.jsonBody !== undefined ? JSON_CONTENT_HEADERS : {}),
|
|
79
84
|
};
|
package/dist/transport.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transport.js","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,EAAE,qBAAqB,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAehE,MAAM,0BAA0B,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;AAE1D;;wBAEwB;AACxB,MAAM,CAAC,KAAK,UAAU,SAAS,CAC7B,SAAiC,EACjC,EAAE,UAAU,GAAG,0BAA0B,EAAE,GAAuB,EAAE;IAEpE,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;QACnC,IAAI,CAAC;YACH,OAAO,MAAM,SAAS,EAAE,CAAC;QAC3B,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,CAAC,KAAK,YAAY,qBAAqB,CAAC,EAAE,CAAC;gBAC9C,MAAM,KAAK,CAAC;YACd,CAAC;QACH,CAAC;QACD,MAAM,KAAK,CAAC,SAAS,CAAC,CAAC;IACzB,CAAC;IACD,OAAO,SAAS,EAAE,CAAC;AACrB,CAAC;AAED,SAAS,KAAK,CAAC,SAAiB;IAC9B,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC,CAAC;AAClE,CAAC;AAED,MAAM,oBAAoB,GAAG;IAC3B,cAAc,EAAE,kBAAkB;CACnC,CAAC;AAEF,MAAM,qBAAqB,GAAG,MAAM,CAAC;AAErC,4EAA4E;AAC5E,gDAAgD;AAChD,MAAM,wBAAwB,GAAG,IAAI,GAAG,CAAC;IACvC,cAAc;IACd,YAAY;IACZ,gBAAgB;CACjB,CAAC,CAAC;
|
|
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
|
+
"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":
|
|
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",
|
package/src/clients/classic.ts
CHANGED
|
@@ -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> {
|
package/src/clients/client.ts
CHANGED
|
@@ -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
|
|
package/src/clients/dsiot.ts
CHANGED
|
@@ -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
|
|
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 &
|
|
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 ===
|
|
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
|
-
: {
|
|
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 ===
|
|
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
|
|
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:
|
|
163
|
+
return { type: CLASSIC_TERMINAL_SCOPE_CODES[scope] };
|
|
175
164
|
}
|
|
176
165
|
|
|
177
166
|
function flag(on: boolean): string {
|