daikin-local-control 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/DESIGN.md +98 -0
  2. package/LICENSE +674 -0
  3. package/README.md +317 -0
  4. package/UNSUPPORTED.md +43 -0
  5. package/dist/authentication.d.ts +24 -0
  6. package/dist/authentication.d.ts.map +1 -0
  7. package/dist/authentication.js +30 -0
  8. package/dist/authentication.js.map +1 -0
  9. package/dist/clients/classic.d.ts +53 -0
  10. package/dist/clients/classic.d.ts.map +1 -0
  11. package/dist/clients/classic.js +142 -0
  12. package/dist/clients/classic.js.map +1 -0
  13. package/dist/clients/client.d.ts +21 -0
  14. package/dist/clients/client.d.ts.map +1 -0
  15. package/dist/clients/client.js +2 -0
  16. package/dist/clients/client.js.map +1 -0
  17. package/dist/clients/dsiot.d.ts +28 -0
  18. package/dist/clients/dsiot.d.ts.map +1 -0
  19. package/dist/clients/dsiot.js +66 -0
  20. package/dist/clients/dsiot.js.map +1 -0
  21. package/dist/codecs/classic/capabilities.d.ts +19 -0
  22. package/dist/codecs/classic/capabilities.d.ts.map +1 -0
  23. package/dist/codecs/classic/capabilities.js +208 -0
  24. package/dist/codecs/classic/capabilities.js.map +1 -0
  25. package/dist/codecs/classic/extendedControl.d.ts +20 -0
  26. package/dist/codecs/classic/extendedControl.d.ts.map +1 -0
  27. package/dist/codecs/classic/extendedControl.js +88 -0
  28. package/dist/codecs/classic/extendedControl.js.map +1 -0
  29. package/dist/codecs/classic/keyValue.d.ts +10 -0
  30. package/dist/codecs/classic/keyValue.d.ts.map +1 -0
  31. package/dist/codecs/classic/keyValue.js +36 -0
  32. package/dist/codecs/classic/keyValue.js.map +1 -0
  33. package/dist/codecs/classic/state.d.ts +18 -0
  34. package/dist/codecs/classic/state.d.ts.map +1 -0
  35. package/dist/codecs/classic/state.js +235 -0
  36. package/dist/codecs/classic/state.js.map +1 -0
  37. package/dist/codecs/credentials.d.ts +8 -0
  38. package/dist/codecs/credentials.d.ts.map +1 -0
  39. package/dist/codecs/credentials.js +14 -0
  40. package/dist/codecs/credentials.js.map +1 -0
  41. package/dist/codecs/device.d.ts +6 -0
  42. package/dist/codecs/device.d.ts.map +1 -0
  43. package/dist/codecs/device.js +35 -0
  44. package/dist/codecs/device.js.map +1 -0
  45. package/dist/codecs/dsiot/capabilities.d.ts +5 -0
  46. package/dist/codecs/dsiot/capabilities.d.ts.map +1 -0
  47. package/dist/codecs/dsiot/capabilities.js +139 -0
  48. package/dist/codecs/dsiot/capabilities.js.map +1 -0
  49. package/dist/codecs/dsiot/multireq.d.ts +37 -0
  50. package/dist/codecs/dsiot/multireq.d.ts.map +1 -0
  51. package/dist/codecs/dsiot/multireq.js +122 -0
  52. package/dist/codecs/dsiot/multireq.js.map +1 -0
  53. package/dist/codecs/dsiot/state.d.ts +92 -0
  54. package/dist/codecs/dsiot/state.d.ts.map +1 -0
  55. package/dist/codecs/dsiot/state.js +461 -0
  56. package/dist/codecs/dsiot/state.js.map +1 -0
  57. package/dist/discovery.d.ts +17 -0
  58. package/dist/discovery.d.ts.map +1 -0
  59. package/dist/discovery.js +67 -0
  60. package/dist/discovery.js.map +1 -0
  61. package/dist/error.d.ts +15 -0
  62. package/dist/error.d.ts.map +1 -0
  63. package/dist/error.js +20 -0
  64. package/dist/error.js.map +1 -0
  65. package/dist/factory.d.ts +26 -0
  66. package/dist/factory.d.ts.map +1 -0
  67. package/dist/factory.js +31 -0
  68. package/dist/factory.js.map +1 -0
  69. package/dist/index.d.ts +22 -0
  70. package/dist/index.d.ts.map +1 -0
  71. package/dist/index.js +21 -0
  72. package/dist/index.js.map +1 -0
  73. package/dist/models/capabilities.d.ts +42 -0
  74. package/dist/models/capabilities.d.ts.map +1 -0
  75. package/dist/models/capabilities.js +2 -0
  76. package/dist/models/capabilities.js.map +1 -0
  77. package/dist/models/device.d.ts +24 -0
  78. package/dist/models/device.d.ts.map +1 -0
  79. package/dist/models/device.js +2 -0
  80. package/dist/models/device.js.map +1 -0
  81. package/dist/models/extendedControl.d.ts +31 -0
  82. package/dist/models/extendedControl.d.ts.map +1 -0
  83. package/dist/models/extendedControl.js +7 -0
  84. package/dist/models/extendedControl.js.map +1 -0
  85. package/dist/models/state.d.ts +56 -0
  86. package/dist/models/state.d.ts.map +1 -0
  87. package/dist/models/state.js +49 -0
  88. package/dist/models/state.js.map +1 -0
  89. package/dist/transport.d.ts +75 -0
  90. package/dist/transport.d.ts.map +1 -0
  91. package/dist/transport.js +118 -0
  92. package/dist/transport.js.map +1 -0
  93. package/package.json +62 -0
  94. package/src/authentication.ts +53 -0
  95. package/src/clients/classic.ts +251 -0
  96. package/src/clients/client.ts +29 -0
  97. package/src/clients/dsiot.ts +116 -0
  98. package/src/codecs/classic/capabilities.ts +272 -0
  99. package/src/codecs/classic/extendedControl.ts +132 -0
  100. package/src/codecs/classic/keyValue.ts +39 -0
  101. package/src/codecs/classic/state.ts +302 -0
  102. package/src/codecs/credentials.ts +19 -0
  103. package/src/codecs/device.ts +41 -0
  104. package/src/codecs/dsiot/capabilities.ts +209 -0
  105. package/src/codecs/dsiot/multireq.ts +162 -0
  106. package/src/codecs/dsiot/state.ts +644 -0
  107. package/src/discovery.ts +85 -0
  108. package/src/error.ts +20 -0
  109. package/src/factory.ts +66 -0
  110. package/src/index.ts +21 -0
  111. package/src/models/capabilities.ts +52 -0
  112. package/src/models/device.ts +25 -0
  113. package/src/models/extendedControl.ts +41 -0
  114. package/src/models/state.ts +110 -0
  115. package/src/transport.ts +191 -0
@@ -0,0 +1,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
+ }