daikin-local-control 0.2.1 → 0.3.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.
- package/DESIGN.md +52 -0
- package/README.md +39 -2
- package/UNSUPPORTED.md +10 -1
- package/dist/clients/classic.d.ts +22 -4
- package/dist/clients/classic.d.ts.map +1 -1
- package/dist/clients/classic.js +55 -8
- package/dist/clients/classic.js.map +1 -1
- package/dist/clients/client.d.ts +11 -2
- package/dist/clients/client.d.ts.map +1 -1
- package/dist/clients/dsiot.d.ts +10 -3
- package/dist/clients/dsiot.d.ts.map +1 -1
- package/dist/clients/dsiot.js +43 -5
- package/dist/clients/dsiot.js.map +1 -1
- package/dist/clients/setStateCache.d.ts +15 -0
- package/dist/clients/setStateCache.d.ts.map +1 -0
- package/dist/clients/setStateCache.js +26 -0
- package/dist/clients/setStateCache.js.map +1 -0
- package/dist/codecs/classic/capabilities.d.ts.map +1 -1
- package/dist/codecs/classic/capabilities.js +28 -1
- package/dist/codecs/classic/capabilities.js.map +1 -1
- package/dist/codecs/classic/extendedControl.d.ts +14 -1
- package/dist/codecs/classic/extendedControl.d.ts.map +1 -1
- package/dist/codecs/classic/extendedControl.js +39 -4
- package/dist/codecs/classic/extendedControl.js.map +1 -1
- package/dist/codecs/classic/keyValue.d.ts +3 -0
- package/dist/codecs/classic/keyValue.d.ts.map +1 -1
- package/dist/codecs/classic/keyValue.js +10 -0
- package/dist/codecs/classic/keyValue.js.map +1 -1
- package/dist/codecs/classic/state.d.ts +3 -2
- package/dist/codecs/classic/state.d.ts.map +1 -1
- package/dist/codecs/classic/state.js +14 -1
- package/dist/codecs/classic/state.js.map +1 -1
- package/dist/codecs/device.d.ts +11 -1
- package/dist/codecs/device.d.ts.map +1 -1
- package/dist/codecs/device.js +26 -10
- package/dist/codecs/device.js.map +1 -1
- package/dist/codecs/dsiot/capabilities.d.ts +4 -2
- package/dist/codecs/dsiot/capabilities.d.ts.map +1 -1
- package/dist/codecs/dsiot/capabilities.js +20 -4
- package/dist/codecs/dsiot/capabilities.js.map +1 -1
- package/dist/codecs/dsiot/device.d.ts +7 -0
- package/dist/codecs/dsiot/device.d.ts.map +1 -0
- package/dist/codecs/dsiot/device.js +18 -0
- package/dist/codecs/dsiot/device.js.map +1 -0
- package/dist/codecs/dsiot/multireq.d.ts +7 -4
- package/dist/codecs/dsiot/multireq.d.ts.map +1 -1
- package/dist/codecs/dsiot/multireq.js +10 -4
- package/dist/codecs/dsiot/multireq.js.map +1 -1
- package/dist/codecs/dsiot/state.d.ts +6 -2
- package/dist/codecs/dsiot/state.d.ts.map +1 -1
- package/dist/codecs/dsiot/state.js +12 -2
- package/dist/codecs/dsiot/state.js.map +1 -1
- package/dist/factory.d.ts +7 -0
- package/dist/factory.d.ts.map +1 -1
- package/dist/factory.js +2 -2
- package/dist/factory.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/models/capabilities.d.ts +5 -1
- package/dist/models/capabilities.d.ts.map +1 -1
- package/dist/models/device.d.ts +15 -2
- package/dist/models/device.d.ts.map +1 -1
- package/dist/models/extendedControl.d.ts +2 -3
- package/dist/models/extendedControl.d.ts.map +1 -1
- package/dist/models/extendedControl.js +0 -1
- package/dist/models/extendedControl.js.map +1 -1
- package/dist/models/state.d.ts +15 -4
- package/dist/models/state.d.ts.map +1 -1
- package/dist/models/state.js +1 -0
- package/dist/models/state.js.map +1 -1
- package/package.json +1 -1
- package/src/clients/classic.ts +76 -10
- package/src/clients/client.ts +13 -1
- package/src/clients/dsiot.ts +55 -9
- package/src/clients/setStateCache.ts +28 -0
- package/src/codecs/classic/capabilities.ts +39 -0
- package/src/codecs/classic/extendedControl.ts +56 -5
- package/src/codecs/classic/keyValue.ts +10 -0
- package/src/codecs/classic/state.ts +30 -12
- package/src/codecs/device.ts +36 -10
- package/src/codecs/dsiot/capabilities.ts +27 -2
- package/src/codecs/dsiot/device.ts +24 -0
- package/src/codecs/dsiot/multireq.ts +10 -4
- package/src/codecs/dsiot/state.ts +16 -1
- package/src/factory.ts +17 -2
- package/src/index.ts +1 -0
- package/src/models/capabilities.ts +5 -0
- package/src/models/device.ts +16 -2
- package/src/models/extendedControl.ts +2 -5
- package/src/models/state.ts +18 -4
package/src/codecs/device.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { DaikinDevice } from "../models/device.js";
|
|
1
|
+
import type { DaikinDevice, DaikinDeviceInfo } from "../models/device.js";
|
|
2
|
+
import { decodeClassicText } from "./classic/keyValue.js";
|
|
2
3
|
|
|
3
4
|
const DSIOT_TYPE = "GPF";
|
|
4
5
|
const MARVELL_ADP_KIND = "3";
|
|
@@ -15,7 +16,6 @@ export function parseDevice(
|
|
|
15
16
|
return {
|
|
16
17
|
host: basicInfo.api_port ? `${host}:${basicInfo.api_port}` : host,
|
|
17
18
|
protocol,
|
|
18
|
-
name: decodeName(basicInfo.name),
|
|
19
19
|
// Only classic adapters register terminals.
|
|
20
20
|
requiresRegistration: secure && protocol === "classic",
|
|
21
21
|
// Only a Marvell adapter (adp_kind=3) in secure mode speaks HTTPS; every
|
|
@@ -25,17 +25,43 @@ export function parseDevice(
|
|
|
25
25
|
protocol === "classic" &&
|
|
26
26
|
basicInfo.adp_kind === MARVELL_ADP_KIND,
|
|
27
27
|
requiresLocalPassword: basicInfo.lpw_flag === "1",
|
|
28
|
+
...parseDeviceInfo(basicInfo),
|
|
28
29
|
};
|
|
29
30
|
}
|
|
30
31
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
/** Reads what a `basic_info` reply says to identify the adapter. The name
|
|
33
|
+
* arrives form-encoded. */
|
|
34
|
+
export function parseDeviceInfo(
|
|
35
|
+
basicInfo: Record<string, string>,
|
|
36
|
+
): DaikinDeviceInfo {
|
|
37
|
+
return {
|
|
38
|
+
name:
|
|
39
|
+
basicInfo.name === undefined
|
|
40
|
+
? undefined
|
|
41
|
+
: decodeClassicText(basicInfo.name),
|
|
42
|
+
macAddress: decodeMacAddress(basicInfo.mac),
|
|
43
|
+
firmwareVersion: decodeFirmwareVersion(basicInfo.ver),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** The adapter sends a MAC address without separators. It keeps the adapter's
|
|
48
|
+
* letter case. */
|
|
49
|
+
export function decodeMacAddress(
|
|
50
|
+
value: string | undefined,
|
|
51
|
+
): string | undefined {
|
|
52
|
+
return value ? value.replace(/..(?=..)/g, "$&:") : undefined;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** The adapter sends a version as three numbers joined by underscores, with a
|
|
56
|
+
* fourth part, `pre`, on a pre-release. This drops any other fourth part, and
|
|
57
|
+
* decodes a value that is not three numbers to `undefined`. */
|
|
58
|
+
export function decodeFirmwareVersion(
|
|
59
|
+
value: string | undefined,
|
|
60
|
+
): string | undefined {
|
|
61
|
+
const parts = /^(\d+)_(\d+)_(\d+)(?:_([^_]*))?$/.exec(value ?? "");
|
|
62
|
+
if (parts === null) {
|
|
34
63
|
return undefined;
|
|
35
64
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
} catch {
|
|
39
|
-
return value;
|
|
40
|
-
}
|
|
65
|
+
const version = parts.slice(1, 4).map(Number).join(".");
|
|
66
|
+
return parts[4] === "pre" ? `${version}.pre` : version;
|
|
41
67
|
}
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
HUMIDITY_SETTINGS,
|
|
12
12
|
OPERATING_MODES,
|
|
13
13
|
type OperatingMode,
|
|
14
|
+
SPECIAL_MODES,
|
|
14
15
|
SWING_MODES,
|
|
15
16
|
type SwingMode,
|
|
16
17
|
VERTICAL_POSITIONS,
|
|
@@ -20,6 +21,7 @@ import {
|
|
|
20
21
|
DSIOT_HORIZONTAL_POSITION_VALUES,
|
|
21
22
|
DSIOT_MODE_PATHS,
|
|
22
23
|
DSIOT_PATHS,
|
|
24
|
+
DSIOT_SPECIAL_MODE_PATHS,
|
|
23
25
|
DSIOT_VERTICAL_POSITION_VALUES,
|
|
24
26
|
type DsiotSetpoint,
|
|
25
27
|
decodeDsiotLittleEndian,
|
|
@@ -35,6 +37,9 @@ const ADAPTER_PATHS = {
|
|
|
35
37
|
scheduleTimer: "adp_i.func.en_scdltmr",
|
|
36
38
|
} as const;
|
|
37
39
|
|
|
40
|
+
// The `pt` of a special mode's leaf on a unit that takes the mode.
|
|
41
|
+
const SPECIAL_MODE_OFFERED_TYPE = "2";
|
|
42
|
+
|
|
38
43
|
const COOLING_ONLY_MODEL_TYPE = "01";
|
|
39
44
|
const COOLING_ONLY_MODES: readonly OperatingMode[] = [
|
|
40
45
|
"dehumidify",
|
|
@@ -48,8 +53,10 @@ const FIXED_POSITIONS: readonly string[] = FIXED_LOUVER_POSITIONS;
|
|
|
48
53
|
|
|
49
54
|
type LouverAxis = "vertical" | "horizontal";
|
|
50
55
|
|
|
51
|
-
/** Turns the flattened `adp_i` and indoor
|
|
52
|
-
* `DaikinCapabilities`. Setpoints count half
|
|
56
|
+
/** Turns the flattened `adp_i` tree and the indoor and outdoor `dgc_status`
|
|
57
|
+
* trees (with `pt` and `md`) into `DaikinCapabilities`. Setpoints count half
|
|
58
|
+
* degrees. Without the outdoor tree, `specialModes` lists no `powerful`. It
|
|
59
|
+
* lists econo in every mode but fan: see UNSUPPORTED.md. */
|
|
53
60
|
export function parseDsiotCapabilities(
|
|
54
61
|
values: Record<string, string>,
|
|
55
62
|
): DaikinCapabilities {
|
|
@@ -66,6 +73,17 @@ export function parseDsiotCapabilities(
|
|
|
66
73
|
? [...HUMIDITY_SETTINGS]
|
|
67
74
|
: [];
|
|
68
75
|
};
|
|
76
|
+
const takenSpecialModes = SPECIAL_MODES.filter(
|
|
77
|
+
(specialMode) =>
|
|
78
|
+
values[`${DSIOT_SPECIAL_MODE_PATHS[specialMode]}.pt`] ===
|
|
79
|
+
SPECIAL_MODE_OFFERED_TYPE,
|
|
80
|
+
);
|
|
81
|
+
const specialModes = (mode: OperatingMode) =>
|
|
82
|
+
modes.includes(mode)
|
|
83
|
+
? takenSpecialModes.filter(
|
|
84
|
+
(specialMode) => mode !== "fan" || specialMode !== "econo",
|
|
85
|
+
)
|
|
86
|
+
: [];
|
|
69
87
|
const vertical = hasLouver(values, "vertical");
|
|
70
88
|
const horizontal = hasLouver(values, "horizontal");
|
|
71
89
|
const energyMonitoring =
|
|
@@ -105,6 +123,13 @@ export function parseDsiotCapabilities(
|
|
|
105
123
|
heat: humiditySettings("heat"),
|
|
106
124
|
fan: [],
|
|
107
125
|
},
|
|
126
|
+
specialModes: {
|
|
127
|
+
auto: specialModes("auto"),
|
|
128
|
+
dehumidify: specialModes("dehumidify"),
|
|
129
|
+
cool: specialModes("cool"),
|
|
130
|
+
heat: specialModes("heat"),
|
|
131
|
+
fan: specialModes("fan"),
|
|
132
|
+
},
|
|
108
133
|
// Which leaf reports the special air outlet is not known: see
|
|
109
134
|
// UNSUPPORTED.md.
|
|
110
135
|
specialOutlet: false,
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { DaikinDeviceInfo } from "../../models/device.js";
|
|
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;
|
|
9
|
+
|
|
10
|
+
/** Reads what the flattened `adp_i` and `adp_d` trees say to identify the
|
|
11
|
+
* adapter. Both `adp_i` leaves hold the value a classic adapter sends in
|
|
12
|
+
* `basic_info`. `adp_d.name` holds the name as plain text, where a classic
|
|
13
|
+
* adapter form-encodes it. */
|
|
14
|
+
export function parseDsiotDeviceInfo(
|
|
15
|
+
values: Record<string, string>,
|
|
16
|
+
): DaikinDeviceInfo {
|
|
17
|
+
return {
|
|
18
|
+
name: values[ADAPTER_PATHS.name],
|
|
19
|
+
macAddress: decodeMacAddress(values[ADAPTER_PATHS.macAddress]),
|
|
20
|
+
firmwareVersion: decodeFirmwareVersion(
|
|
21
|
+
values[ADAPTER_PATHS.firmwareVersion],
|
|
22
|
+
),
|
|
23
|
+
};
|
|
24
|
+
}
|
|
@@ -20,11 +20,13 @@ export const DSIOT_EDGES = {
|
|
|
20
20
|
const EDGE_ROOT = "/dsiot/edge";
|
|
21
21
|
|
|
22
22
|
/** What a read fetches, as the `to` of a request: whole trees, never single
|
|
23
|
-
* leaves.
|
|
23
|
+
* leaves. The status trees come with `pt`, which `parseDsiotCapabilities`
|
|
24
|
+
* reads. */
|
|
24
25
|
export const DSIOT_TARGETS = {
|
|
25
26
|
adapterInfo: `${EDGE_ROOT}.adp_i?filter=pv`,
|
|
26
|
-
|
|
27
|
-
|
|
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`,
|
|
28
30
|
} as const;
|
|
29
31
|
|
|
30
32
|
interface PropertyNode {
|
|
@@ -92,7 +94,8 @@ function childNamed(siblings: PropertyNode[], name: string): PropertyNode {
|
|
|
92
94
|
}
|
|
93
95
|
|
|
94
96
|
/** Flattens a `/dsiot/multireq` reply into dotted paths -> `pv`, plus
|
|
95
|
-
* `<path>.md.mi` / `.md.mx` /
|
|
97
|
+
* `<path>.pt` where a leaf's type came back and `<path>.md.mi` / `.md.mx` /
|
|
98
|
+
* `.md.st` where metadata did.
|
|
96
99
|
*
|
|
97
100
|
* Each of its `responses` carries its own `rsc`; any not starting with 200 is
|
|
98
101
|
* the adapter refusing, and throws a `DaikinError`: status 401 for `rsc` 401
|
|
@@ -149,6 +152,9 @@ function flatten(
|
|
|
149
152
|
if (node.pv !== undefined) {
|
|
150
153
|
values[path] = stringify(node.pv);
|
|
151
154
|
}
|
|
155
|
+
if (node.pt !== undefined) {
|
|
156
|
+
values[`${path}.pt`] = stringify(node.pt);
|
|
157
|
+
}
|
|
152
158
|
for (const key of ["mi", "mx", "st"] as const) {
|
|
153
159
|
const metadata = node.md?.[key];
|
|
154
160
|
if (metadata !== undefined && metadata !== null) {
|
|
@@ -8,6 +8,8 @@ import {
|
|
|
8
8
|
type HorizontalPosition,
|
|
9
9
|
type HumiditySetting,
|
|
10
10
|
type OperatingMode,
|
|
11
|
+
SPECIAL_MODES,
|
|
12
|
+
type SpecialMode,
|
|
11
13
|
type SwingMode,
|
|
12
14
|
type TargetHumidity,
|
|
13
15
|
VERTICAL_POSITIONS,
|
|
@@ -30,6 +32,15 @@ export const DSIOT_PATHS = {
|
|
|
30
32
|
energyMonitoring: "dgc_status.e_1002.e_A011.p_01",
|
|
31
33
|
} as const;
|
|
32
34
|
|
|
35
|
+
/** Each leaf holds `01` while its mode is on. Powerful is on the outdoor
|
|
36
|
+
* edge. */
|
|
37
|
+
export const DSIOT_SPECIAL_MODE_PATHS: Record<SpecialMode, string> = {
|
|
38
|
+
powerful: "dgc_status.e_1003.e_3002.p_44",
|
|
39
|
+
econo: "dgc_status.e_1002.e_3003.p_24",
|
|
40
|
+
streamer: "dgc_status.e_1002.e_3003.p_20",
|
|
41
|
+
};
|
|
42
|
+
const SPECIAL_MODE_ON = "01";
|
|
43
|
+
|
|
33
44
|
export interface DsiotSetpoint {
|
|
34
45
|
path: string;
|
|
35
46
|
/** Auto and dehumidify are a signed byte; cool and heat are little-endian. */
|
|
@@ -476,7 +487,8 @@ const POWER_TRANSITIONS = {
|
|
|
476
487
|
/** Turns the flattened indoor `dgc_status` tree, read with `md`, into a
|
|
477
488
|
* `DaikinState`. Every value is per mode: the setpoint, humidity, fan speed and
|
|
478
489
|
* louvers each have a leaf for each mode, and the state reads the current
|
|
479
|
-
* mode's.
|
|
490
|
+
* mode's. Powerful is read from the outdoor tree, and reads as off without
|
|
491
|
+
* it. */
|
|
480
492
|
export function parseDsiotState(values: Record<string, string>): DaikinState {
|
|
481
493
|
const mode = decodeDsiotMode(values[DSIOT_PATHS.mode]) ?? "auto";
|
|
482
494
|
const paths = DSIOT_MODE_PATHS[mode];
|
|
@@ -491,6 +503,9 @@ export function parseDsiotState(values: Record<string, string>): DaikinState {
|
|
|
491
503
|
swingMode: decodeSwing(values[paths.vertical], values[paths.horizontal]),
|
|
492
504
|
verticalPosition: parseVerticalPosition(values, paths.vertical),
|
|
493
505
|
horizontalPosition: parseHorizontalPosition(values[paths.horizontal]),
|
|
506
|
+
specialModes: SPECIAL_MODES.filter(
|
|
507
|
+
(mode) => values[DSIOT_SPECIAL_MODE_PATHS[mode]] === SPECIAL_MODE_ON,
|
|
508
|
+
),
|
|
494
509
|
};
|
|
495
510
|
}
|
|
496
511
|
|
package/src/factory.ts
CHANGED
|
@@ -26,6 +26,13 @@ export interface CreateDaikinClientOptions {
|
|
|
26
26
|
* waits of 0, 50, 100, 200 and 400 milliseconds: see
|
|
27
27
|
* `DaikinRetryOptions.delaysInMs`. */
|
|
28
28
|
retry?: DaikinRetryOptions;
|
|
29
|
+
/** How old, in milliseconds, a reply to `getState` or `getStateAndSensors`
|
|
30
|
+
* may be for `setState` to build its write from it instead of reading the
|
|
31
|
+
* unit first. Defaults to 0: `setState` always reads first. It changes no
|
|
32
|
+
* read: `getState` asks the unit every time. The write undoes a change made
|
|
33
|
+
* on the remote control since that reply: see
|
|
34
|
+
* DESIGN.md#the-read-before-a-write. */
|
|
35
|
+
maxStateAgeForSetStateInMs?: number;
|
|
29
36
|
/** HTTPS adapters only. Without a CA the adapter's certificate isn't verified. */
|
|
30
37
|
tls?: DaikinTlsOptions;
|
|
31
38
|
}
|
|
@@ -49,8 +56,16 @@ export function createDaikinClient(
|
|
|
49
56
|
});
|
|
50
57
|
|
|
51
58
|
return device.protocol === "dsiot"
|
|
52
|
-
? new DsiotDaikinClient(
|
|
53
|
-
|
|
59
|
+
? new DsiotDaikinClient(
|
|
60
|
+
transport,
|
|
61
|
+
options.retry,
|
|
62
|
+
options.maxStateAgeForSetStateInMs,
|
|
63
|
+
)
|
|
64
|
+
: new ClassicDaikinClient(
|
|
65
|
+
transport,
|
|
66
|
+
options.retry,
|
|
67
|
+
options.maxStateAgeForSetStateInMs,
|
|
68
|
+
);
|
|
54
69
|
}
|
|
55
70
|
|
|
56
71
|
function localPasswordAuthentication(
|
package/src/index.ts
CHANGED
|
@@ -9,6 +9,7 @@ export * from "./codecs/classic/state.js";
|
|
|
9
9
|
export * from "./codecs/credentials.js";
|
|
10
10
|
export * from "./codecs/device.js";
|
|
11
11
|
export * from "./codecs/dsiot/capabilities.js";
|
|
12
|
+
export * from "./codecs/dsiot/device.js";
|
|
12
13
|
export * from "./codecs/dsiot/multireq.js";
|
|
13
14
|
export * from "./codecs/dsiot/state.js";
|
|
14
15
|
export * from "./discovery.js";
|
|
@@ -2,6 +2,7 @@ import type {
|
|
|
2
2
|
FanSpeed,
|
|
3
3
|
HorizontalPosition,
|
|
4
4
|
OperatingMode,
|
|
5
|
+
SpecialMode,
|
|
5
6
|
SwingMode,
|
|
6
7
|
TargetHumidity,
|
|
7
8
|
VerticalPosition,
|
|
@@ -41,6 +42,10 @@ export interface DaikinCapabilities {
|
|
|
41
42
|
/** Humidity settings per mode; empty where the mode has none or the unit does
|
|
42
43
|
* not offer it. A percentage is listed for no unit: see UNSUPPORTED.md. */
|
|
43
44
|
targetHumidities: Readonly<Record<OperatingMode, readonly TargetHumidity[]>>;
|
|
45
|
+
/** Special modes per mode; empty where the unit takes none or does not offer
|
|
46
|
+
* the mode. `getCapabilities` lists econo in every mode but fan: see
|
|
47
|
+
* UNSUPPORTED.md. */
|
|
48
|
+
specialModes: Readonly<Record<OperatingMode, readonly SpecialMode[]>>;
|
|
44
49
|
/** Whether the unit has the special air outlet, which heat can swing
|
|
45
50
|
* through. That swing is not modelled: see UNSUPPORTED.md. */
|
|
46
51
|
specialOutlet: boolean;
|
package/src/models/device.ts
CHANGED
|
@@ -5,15 +5,29 @@ export const DAIKIN_PROTOCOLS = ["classic", "dsiot"] as const;
|
|
|
5
5
|
* a CGI adapter such as the BRP069 or BRP072, `dsiot` for a BRP084. */
|
|
6
6
|
export type DaikinProtocol = (typeof DAIKIN_PROTOCOLS)[number];
|
|
7
7
|
|
|
8
|
+
/** What an adapter reports to identify itself. `discoverDevices` fills it into
|
|
9
|
+
* each `DaikinDevice`, and `DaikinClient.getDeviceInfo` reads it for a device
|
|
10
|
+
* written by hand. */
|
|
11
|
+
export interface DaikinDeviceInfo {
|
|
12
|
+
/** The name the adapter was given, such as `Bedroom`. Left out when the
|
|
13
|
+
* adapter reports none. */
|
|
14
|
+
name?: string;
|
|
15
|
+
/** In pairs joined by colons, in the letter case the adapter sends, such as
|
|
16
|
+
* `A4:08:EA:CC:91:D4`. Left out when the adapter reports none. */
|
|
17
|
+
macAddress?: string;
|
|
18
|
+
/** The adapter's firmware, such as `1.2.51`, or `1.2.51.pre` for a
|
|
19
|
+
* pre-release. Left out when the adapter reports none. */
|
|
20
|
+
firmwareVersion?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
8
23
|
/** An adapter on the LAN, as `discoverDevices` finds it or as a caller writes
|
|
9
24
|
* it by hand, carrying what `createDaikinClient` needs to reach it. A flag left
|
|
10
25
|
* out means `false`, so a device written by hand needs only `host` and
|
|
11
26
|
* `protocol`. */
|
|
12
|
-
export interface DaikinDevice {
|
|
27
|
+
export interface DaikinDevice extends DaikinDeviceInfo {
|
|
13
28
|
/** Address, with `:port` when the adapter announces one. */
|
|
14
29
|
host: string;
|
|
15
30
|
protocol: DaikinProtocol;
|
|
16
|
-
name?: string;
|
|
17
31
|
/** The adapter answers a terminal uuid only once it is registered with the
|
|
18
32
|
* KEY on the adapter's label (`registerTerminal`). Classic only. */
|
|
19
33
|
requiresRegistration?: boolean;
|
|
@@ -22,17 +22,14 @@ export interface DemandControl {
|
|
|
22
22
|
mode: DemandMode;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
export const SPECIAL_MODES = ["powerful", "econo", "streamer"] as const;
|
|
26
|
-
|
|
27
|
-
export type SpecialMode = (typeof SPECIAL_MODES)[number];
|
|
28
|
-
|
|
29
25
|
export interface DeviceGroup {
|
|
30
26
|
name: string;
|
|
31
27
|
/** Whether the unit is in the group. */
|
|
32
28
|
enabled: boolean;
|
|
33
29
|
}
|
|
34
30
|
|
|
35
|
-
/**
|
|
31
|
+
/** `setMetadata` writes no field that is left out, and `getMetadata` leaves
|
|
32
|
+
* out a field the adapter does not report. */
|
|
36
33
|
export interface DeviceMetadata {
|
|
37
34
|
name?: string;
|
|
38
35
|
/** The adapter's own code for the icon: see UNSUPPORTED.md. */
|
package/src/models/state.ts
CHANGED
|
@@ -78,6 +78,10 @@ export type HumiditySetting = (typeof HUMIDITY_SETTINGS)[number];
|
|
|
78
78
|
* `DaikinCapabilities.targetHumidities` lists which of them a unit takes. */
|
|
79
79
|
export type TargetHumidity = HumiditySetting | { percent: number };
|
|
80
80
|
|
|
81
|
+
export const SPECIAL_MODES = ["powerful", "econo", "streamer"] as const;
|
|
82
|
+
|
|
83
|
+
export type SpecialMode = (typeof SPECIAL_MODES)[number];
|
|
84
|
+
|
|
81
85
|
export interface DaikinState {
|
|
82
86
|
power: boolean;
|
|
83
87
|
mode: OperatingMode;
|
|
@@ -93,14 +97,19 @@ export interface DaikinState {
|
|
|
93
97
|
/** On a 3D unit. `null` where the unit reports none, or one outside
|
|
94
98
|
* `HorizontalPosition`. */
|
|
95
99
|
horizontalPosition: HorizontalPosition | null;
|
|
100
|
+
/** The special modes that are on, in the order of `SPECIAL_MODES`; empty
|
|
101
|
+
* when none is. `setState` does not take it: see `DaikinControl`. */
|
|
102
|
+
specialModes: SpecialMode[];
|
|
96
103
|
}
|
|
97
104
|
|
|
98
105
|
/** A partial `DaikinState`, as `DaikinClient.setState` takes it: set only the
|
|
99
|
-
* fields to change. The client
|
|
100
|
-
* and writes the whole state back. It drops a field the unit does not take in
|
|
106
|
+
* fields to change. The client overlays the control on the state the unit
|
|
107
|
+
* holds, and writes the whole state back. It drops a field the unit does not take in
|
|
101
108
|
* the target mode, such as a louver position outside
|
|
102
|
-
* `DaikinCapabilities.verticalPositions`.
|
|
103
|
-
|
|
109
|
+
* `DaikinCapabilities.verticalPositions`. It takes no `specialModes`: a
|
|
110
|
+
* classic adapter is set one with `ClassicDaikinClient.setSpecialMode`, and
|
|
111
|
+
* UNSUPPORTED.md says what is not known about the rest. */
|
|
112
|
+
export type DaikinControl = Partial<Omit<DaikinState, "specialModes">>;
|
|
104
113
|
|
|
105
114
|
/** A reading is `null` when the unit does not report it. */
|
|
106
115
|
export interface DaikinSensors {
|
|
@@ -108,3 +117,8 @@ export interface DaikinSensors {
|
|
|
108
117
|
outdoorTemperatureInC: number | null;
|
|
109
118
|
indoorHumidityInPercent: number | null;
|
|
110
119
|
}
|
|
120
|
+
|
|
121
|
+
export interface DaikinStateAndSensors {
|
|
122
|
+
state: DaikinState;
|
|
123
|
+
sensors: DaikinSensors;
|
|
124
|
+
}
|