daikin-local-control 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/DESIGN.md +98 -0
- package/LICENSE +674 -0
- package/README.md +317 -0
- package/UNSUPPORTED.md +43 -0
- package/dist/authentication.d.ts +24 -0
- package/dist/authentication.d.ts.map +1 -0
- package/dist/authentication.js +30 -0
- package/dist/authentication.js.map +1 -0
- package/dist/clients/classic.d.ts +53 -0
- package/dist/clients/classic.d.ts.map +1 -0
- package/dist/clients/classic.js +142 -0
- package/dist/clients/classic.js.map +1 -0
- package/dist/clients/client.d.ts +21 -0
- package/dist/clients/client.d.ts.map +1 -0
- package/dist/clients/client.js +2 -0
- package/dist/clients/client.js.map +1 -0
- package/dist/clients/dsiot.d.ts +28 -0
- package/dist/clients/dsiot.d.ts.map +1 -0
- package/dist/clients/dsiot.js +66 -0
- package/dist/clients/dsiot.js.map +1 -0
- package/dist/codecs/classic/capabilities.d.ts +19 -0
- package/dist/codecs/classic/capabilities.d.ts.map +1 -0
- package/dist/codecs/classic/capabilities.js +208 -0
- package/dist/codecs/classic/capabilities.js.map +1 -0
- package/dist/codecs/classic/extendedControl.d.ts +20 -0
- package/dist/codecs/classic/extendedControl.d.ts.map +1 -0
- package/dist/codecs/classic/extendedControl.js +88 -0
- package/dist/codecs/classic/extendedControl.js.map +1 -0
- package/dist/codecs/classic/keyValue.d.ts +10 -0
- package/dist/codecs/classic/keyValue.d.ts.map +1 -0
- package/dist/codecs/classic/keyValue.js +36 -0
- package/dist/codecs/classic/keyValue.js.map +1 -0
- package/dist/codecs/classic/state.d.ts +18 -0
- package/dist/codecs/classic/state.d.ts.map +1 -0
- package/dist/codecs/classic/state.js +235 -0
- package/dist/codecs/classic/state.js.map +1 -0
- package/dist/codecs/credentials.d.ts +8 -0
- package/dist/codecs/credentials.d.ts.map +1 -0
- package/dist/codecs/credentials.js +14 -0
- package/dist/codecs/credentials.js.map +1 -0
- package/dist/codecs/device.d.ts +6 -0
- package/dist/codecs/device.d.ts.map +1 -0
- package/dist/codecs/device.js +35 -0
- package/dist/codecs/device.js.map +1 -0
- package/dist/codecs/dsiot/capabilities.d.ts +5 -0
- package/dist/codecs/dsiot/capabilities.d.ts.map +1 -0
- package/dist/codecs/dsiot/capabilities.js +139 -0
- package/dist/codecs/dsiot/capabilities.js.map +1 -0
- package/dist/codecs/dsiot/multireq.d.ts +37 -0
- package/dist/codecs/dsiot/multireq.d.ts.map +1 -0
- package/dist/codecs/dsiot/multireq.js +122 -0
- package/dist/codecs/dsiot/multireq.js.map +1 -0
- package/dist/codecs/dsiot/state.d.ts +92 -0
- package/dist/codecs/dsiot/state.d.ts.map +1 -0
- package/dist/codecs/dsiot/state.js +461 -0
- package/dist/codecs/dsiot/state.js.map +1 -0
- package/dist/discovery.d.ts +17 -0
- package/dist/discovery.d.ts.map +1 -0
- package/dist/discovery.js +67 -0
- package/dist/discovery.js.map +1 -0
- package/dist/error.d.ts +15 -0
- package/dist/error.d.ts.map +1 -0
- package/dist/error.js +20 -0
- package/dist/error.js.map +1 -0
- package/dist/factory.d.ts +26 -0
- package/dist/factory.d.ts.map +1 -0
- package/dist/factory.js +31 -0
- package/dist/factory.js.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/models/capabilities.d.ts +42 -0
- package/dist/models/capabilities.d.ts.map +1 -0
- package/dist/models/capabilities.js +2 -0
- package/dist/models/capabilities.js.map +1 -0
- package/dist/models/device.d.ts +24 -0
- package/dist/models/device.d.ts.map +1 -0
- package/dist/models/device.js +2 -0
- package/dist/models/device.js.map +1 -0
- package/dist/models/extendedControl.d.ts +31 -0
- package/dist/models/extendedControl.d.ts.map +1 -0
- package/dist/models/extendedControl.js +7 -0
- package/dist/models/extendedControl.js.map +1 -0
- package/dist/models/state.d.ts +56 -0
- package/dist/models/state.d.ts.map +1 -0
- package/dist/models/state.js +49 -0
- package/dist/models/state.js.map +1 -0
- package/dist/transport.d.ts +75 -0
- package/dist/transport.d.ts.map +1 -0
- package/dist/transport.js +118 -0
- package/dist/transport.js.map +1 -0
- package/package.json +62 -0
- package/src/authentication.ts +53 -0
- package/src/clients/classic.ts +251 -0
- package/src/clients/client.ts +29 -0
- package/src/clients/dsiot.ts +116 -0
- package/src/codecs/classic/capabilities.ts +272 -0
- package/src/codecs/classic/extendedControl.ts +132 -0
- package/src/codecs/classic/keyValue.ts +39 -0
- package/src/codecs/classic/state.ts +302 -0
- package/src/codecs/credentials.ts +19 -0
- package/src/codecs/device.ts +41 -0
- package/src/codecs/dsiot/capabilities.ts +209 -0
- package/src/codecs/dsiot/multireq.ts +162 -0
- package/src/codecs/dsiot/state.ts +644 -0
- package/src/discovery.ts +85 -0
- package/src/error.ts +20 -0
- package/src/factory.ts +66 -0
- package/src/index.ts +21 -0
- package/src/models/capabilities.ts +52 -0
- package/src/models/device.ts +25 -0
- package/src/models/extendedControl.ts +41 -0
- package/src/models/state.ts +110 -0
- package/src/transport.ts +191 -0
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { DaikinError } from "../../error.js";
|
|
2
|
+
|
|
3
|
+
// The DSIOT (BRP084, `type=GPF`) protocol speaks one endpoint, `/dsiot/multireq`,
|
|
4
|
+
// carrying operations on property trees. A request addresses a resource (`to`);
|
|
5
|
+
// a tree node has pn (name) / pt (type) / pch (children) / pv (value) / md
|
|
6
|
+
// (metadata: mi min, mx max, st step).
|
|
7
|
+
|
|
8
|
+
export const DSIOT_OPERATIONS = {
|
|
9
|
+
get: 2,
|
|
10
|
+
update: 3,
|
|
11
|
+
delete: 4,
|
|
12
|
+
} as const;
|
|
13
|
+
|
|
14
|
+
/** The indoor and the outdoor unit are separate edges on the adapter. */
|
|
15
|
+
export const DSIOT_EDGES = {
|
|
16
|
+
indoor: "adr_0100",
|
|
17
|
+
outdoor: "adr_0200",
|
|
18
|
+
} as const;
|
|
19
|
+
|
|
20
|
+
const EDGE_ROOT = "/dsiot/edge";
|
|
21
|
+
|
|
22
|
+
/** What a read fetches, as the `to` of a request: whole trees, never single
|
|
23
|
+
* leaves. */
|
|
24
|
+
export const DSIOT_TARGETS = {
|
|
25
|
+
adapterInfo: `${EDGE_ROOT}.adp_i?filter=pv`,
|
|
26
|
+
indoorStatus: `${EDGE_ROOT}/${DSIOT_EDGES.indoor}.dgc_status?filter=pv,md`,
|
|
27
|
+
outdoorStatus: `${EDGE_ROOT}/${DSIOT_EDGES.outdoor}.dgc_status?filter=pv,md`,
|
|
28
|
+
} as const;
|
|
29
|
+
|
|
30
|
+
interface PropertyNode {
|
|
31
|
+
pn: string;
|
|
32
|
+
pt?: number;
|
|
33
|
+
pv?: unknown;
|
|
34
|
+
pch?: PropertyNode[];
|
|
35
|
+
md?: { mi?: unknown; mx?: unknown; st?: unknown };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
interface MultireqResponse {
|
|
39
|
+
rsc?: number | string;
|
|
40
|
+
pc?: PropertyNode;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** One leaf to write: `path` is dotted from its resource, such as
|
|
44
|
+
* `dgc_status.e_1002.e_A002.p_01`, and `value` is the leaf's hex string. */
|
|
45
|
+
export interface DsiotUpdate {
|
|
46
|
+
path: string;
|
|
47
|
+
value: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function buildDsiotMultireqGet(targets: readonly string[]): object {
|
|
51
|
+
return {
|
|
52
|
+
requests: targets.map((to) => ({ op: DSIOT_OPERATIONS.get, to })),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Builds the body of a multireq write: one `update` request per resource on
|
|
57
|
+
* `edge`, carrying every leaf in a single tree. A `path` that names a resource
|
|
58
|
+
* alone throws an `Error`. */
|
|
59
|
+
export function buildDsiotMultireqUpdate(
|
|
60
|
+
updates: DsiotUpdate[],
|
|
61
|
+
edge: string = DSIOT_EDGES.indoor,
|
|
62
|
+
): object {
|
|
63
|
+
const roots: PropertyNode[] = [];
|
|
64
|
+
for (const { path, value } of updates) {
|
|
65
|
+
const [rootName, ...rest] = path.split(".");
|
|
66
|
+
if (rootName === undefined || rest.length === 0) {
|
|
67
|
+
throw new Error(`Cannot write the DSIOT path "${path}"`);
|
|
68
|
+
}
|
|
69
|
+
let node = childNamed(roots, rootName);
|
|
70
|
+
for (const name of rest) {
|
|
71
|
+
node.pch ??= [];
|
|
72
|
+
node = childNamed(node.pch, name);
|
|
73
|
+
}
|
|
74
|
+
node.pv = value;
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
requests: roots.map((pc) => ({
|
|
78
|
+
op: DSIOT_OPERATIONS.update,
|
|
79
|
+
to: `${EDGE_ROOT}/${edge}.${pc.pn}`,
|
|
80
|
+
pc,
|
|
81
|
+
})),
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function childNamed(siblings: PropertyNode[], name: string): PropertyNode {
|
|
86
|
+
let child = siblings.find((node) => node.pn === name);
|
|
87
|
+
if (child === undefined) {
|
|
88
|
+
child = { pn: name };
|
|
89
|
+
siblings.push(child);
|
|
90
|
+
}
|
|
91
|
+
return child;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Flattens a `/dsiot/multireq` reply into dotted paths -> `pv`, plus
|
|
95
|
+
* `<path>.md.mi` / `.md.mx` / `.md.st` where metadata came back.
|
|
96
|
+
*
|
|
97
|
+
* Each of its `responses` carries its own `rsc`; any not starting with 200 is
|
|
98
|
+
* the adapter refusing, and throws a `DaikinError`: status 401 for `rsc` 401
|
|
99
|
+
* or 4001, 503 for 503, 502 for anything else. A body that is not JSON, or has
|
|
100
|
+
* no `responses` array, throws one with status 502. */
|
|
101
|
+
export function parseDsiotMultireqReply(body: string): Record<string, string> {
|
|
102
|
+
let parsed: unknown;
|
|
103
|
+
try {
|
|
104
|
+
parsed = JSON.parse(body);
|
|
105
|
+
} catch {
|
|
106
|
+
throw new DaikinError("DSIOT reply was not JSON", 502);
|
|
107
|
+
}
|
|
108
|
+
const responses = (parsed as { responses?: MultireqResponse[] } | null)
|
|
109
|
+
?.responses;
|
|
110
|
+
if (!Array.isArray(responses)) {
|
|
111
|
+
throw new DaikinError("DSIOT reply had no `responses` array", 502);
|
|
112
|
+
}
|
|
113
|
+
const values: Record<string, string> = {};
|
|
114
|
+
for (const response of responses) {
|
|
115
|
+
assertRscOk(response.rsc);
|
|
116
|
+
if (response.pc) {
|
|
117
|
+
flatten(response.pc, undefined, values);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return values;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function assertRscOk(rsc: number | string | undefined): void {
|
|
124
|
+
if (rsc === undefined || String(rsc).startsWith("200")) {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
const code = String(rsc);
|
|
128
|
+
if (code === "401" || code === "4001") {
|
|
129
|
+
throw new DaikinError(`Daikin rejected the credentials (rsc=${code})`, 401);
|
|
130
|
+
}
|
|
131
|
+
throw new DaikinError(
|
|
132
|
+
`Daikin command failed (rsc=${code})`,
|
|
133
|
+
code === "503" ? 503 : 502,
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function flatten(
|
|
138
|
+
node: PropertyNode,
|
|
139
|
+
parent: string | undefined,
|
|
140
|
+
values: Record<string, string>,
|
|
141
|
+
): void {
|
|
142
|
+
const path = parent === undefined ? node.pn : `${parent}.${node.pn}`;
|
|
143
|
+
if (node.pch) {
|
|
144
|
+
for (const child of node.pch) {
|
|
145
|
+
flatten(child, path, values);
|
|
146
|
+
}
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
if (node.pv !== undefined) {
|
|
150
|
+
values[path] = stringify(node.pv);
|
|
151
|
+
}
|
|
152
|
+
for (const key of ["mi", "mx", "st"] as const) {
|
|
153
|
+
const metadata = node.md?.[key];
|
|
154
|
+
if (metadata !== undefined && metadata !== null) {
|
|
155
|
+
values[`${path}.md.${key}`] = stringify(metadata);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function stringify(value: unknown): string {
|
|
161
|
+
return typeof value === "string" ? value : JSON.stringify(value);
|
|
162
|
+
}
|