homebridge-navilink 0.1.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/CHANGELOG.md +5 -0
- package/LICENSE +202 -0
- package/README.md +161 -0
- package/SECURITY.md +73 -0
- package/config.schema.json +138 -0
- package/dist/api/channel.d.ts +94 -0
- package/dist/api/channel.js +334 -0
- package/dist/api/http.d.ts +68 -0
- package/dist/api/http.js +205 -0
- package/dist/api/identity.d.ts +51 -0
- package/dist/api/identity.js +80 -0
- package/dist/api/index.d.ts +17 -0
- package/dist/api/index.js +33 -0
- package/dist/api/mqtt-codec.d.ts +181 -0
- package/dist/api/mqtt-codec.js +377 -0
- package/dist/api/mqtt.d.ts +178 -0
- package/dist/api/mqtt.js +450 -0
- package/dist/api/protocol.d.ts +212 -0
- package/dist/api/protocol.js +286 -0
- package/dist/api/rest.d.ts +139 -0
- package/dist/api/rest.js +317 -0
- package/dist/api/sigv4.d.ts +70 -0
- package/dist/api/sigv4.js +123 -0
- package/dist/api/topics.d.ts +75 -0
- package/dist/api/topics.js +90 -0
- package/dist/devices/base-accessory.d.ts +127 -0
- package/dist/devices/base-accessory.js +232 -0
- package/dist/devices/dhw-accessory.d.ts +62 -0
- package/dist/devices/dhw-accessory.js +100 -0
- package/dist/devices/fault-accessory.d.ts +31 -0
- package/dist/devices/fault-accessory.js +73 -0
- package/dist/devices/heating-accessory.d.ts +63 -0
- package/dist/devices/heating-accessory.js +128 -0
- package/dist/devices/host.d.ts +77 -0
- package/dist/devices/host.js +23 -0
- package/dist/devices/index.d.ts +17 -0
- package/dist/devices/index.js +33 -0
- package/dist/devices/power-accessory.d.ts +32 -0
- package/dist/devices/power-accessory.js +86 -0
- package/dist/devices/probe-accessory.d.ts +45 -0
- package/dist/devices/probe-accessory.js +108 -0
- package/dist/devices/recirculation-accessory.d.ts +47 -0
- package/dist/devices/recirculation-accessory.js +122 -0
- package/dist/devices/thermostat-accessory.d.ts +129 -0
- package/dist/devices/thermostat-accessory.js +372 -0
- package/dist/discovery.d.ts +99 -0
- package/dist/discovery.js +423 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +15 -0
- package/dist/platform.d.ts +120 -0
- package/dist/platform.js +425 -0
- package/dist/session.d.ts +243 -0
- package/dist/session.js +717 -0
- package/dist/settings.d.ts +218 -0
- package/dist/settings.js +240 -0
- package/dist/types/index.d.ts +265 -0
- package/dist/types/index.js +58 -0
- package/dist/ui-api.d.ts +27 -0
- package/dist/ui-api.js +39 -0
- package/dist/utils/context.d.ts +32 -0
- package/dist/utils/context.js +74 -0
- package/dist/utils/errors.d.ts +69 -0
- package/dist/utils/errors.js +118 -0
- package/dist/utils/index.d.ts +15 -0
- package/dist/utils/index.js +31 -0
- package/dist/utils/redact.d.ts +85 -0
- package/dist/utils/redact.js +210 -0
- package/dist/utils/serial.d.ts +18 -0
- package/dist/utils/serial.js +24 -0
- package/dist/utils/temperature.d.ts +114 -0
- package/dist/utils/temperature.js +150 -0
- package/dist/utils/timing.d.ts +68 -0
- package/dist/utils/timing.js +91 -0
- package/dist/utils/validators.d.ts +96 -0
- package/dist/utils/validators.js +353 -0
- package/docs/FEATURES.md +80 -0
- package/docs/PROTOCOL.md +200 -0
- package/docs/README-DETAILED.md +312 -0
- package/homebridge-ui/public/index.html +123 -0
- package/homebridge-ui/public/index.js +503 -0
- package/homebridge-ui/server.js +174 -0
- package/package.json +87 -0
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Copyright (c) 2026 tbaur
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0
|
|
6
|
+
* See LICENSE file for full license text
|
|
7
|
+
*
|
|
8
|
+
* @fileoverview NaviLink request frames: the envelope, and the commands inside
|
|
9
|
+
* it.
|
|
10
|
+
*
|
|
11
|
+
* Every message to a gateway has the same outer shape. A `request` object
|
|
12
|
+
* carries the command code and its arguments; the envelope around it says who
|
|
13
|
+
* is asking, where the answer should go, and which protocol revision is being
|
|
14
|
+
* spoken.
|
|
15
|
+
*
|
|
16
|
+
* ```json
|
|
17
|
+
* {
|
|
18
|
+
* "clientID": "…",
|
|
19
|
+
* "protocolVersion": 1,
|
|
20
|
+
* "request": { "command": 16777217, "macAddress": "…", "…": "…" },
|
|
21
|
+
* "requestTopic": "cmd/1/navilink-…/status/start",
|
|
22
|
+
* "responseTopic": "cmd/1/…/res/channelinfo",
|
|
23
|
+
* "sessionID": "1700000000000"
|
|
24
|
+
* }
|
|
25
|
+
* ```
|
|
26
|
+
*
|
|
27
|
+
* The command codes are not arbitrary. They are a class in the high byte and
|
|
28
|
+
* an index in the low: `0x01000001` is the first read, `0x01000004` the
|
|
29
|
+
* fourth. They are written in hex here with the decimal alongside. The
|
|
30
|
+
* decimal is what appears in a capture, and the hex is what makes the
|
|
31
|
+
* pattern visible.
|
|
32
|
+
*
|
|
33
|
+
* Nothing in this file talks to a socket. It builds objects, so the frames
|
|
34
|
+
* this plugin sends can be asserted on byte for byte in a test.
|
|
35
|
+
*/
|
|
36
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
+
exports.ON_DEMAND_WARMUP = exports.OnOff = exports.ControlMode = exports.Command = exports.PROTOCOL_VERSION = void 0;
|
|
38
|
+
exports.channelInfoRequest = channelInfoRequest;
|
|
39
|
+
exports.channelStatusRequest = channelStatusRequest;
|
|
40
|
+
exports.controlRequest = controlRequest;
|
|
41
|
+
exports.powerControl = powerControl;
|
|
42
|
+
exports.heatingEnableControl = heatingEnableControl;
|
|
43
|
+
exports.dhwSetpointControl = dhwSetpointControl;
|
|
44
|
+
exports.heatingSetpointControl = heatingSetpointControl;
|
|
45
|
+
exports.onDemandControl = onDemandControl;
|
|
46
|
+
exports.appDisconnectWill = appDisconnectWill;
|
|
47
|
+
exports.encodeSetpoint = encodeSetpoint;
|
|
48
|
+
const temperature_1 = require("../utils/temperature");
|
|
49
|
+
/**
|
|
50
|
+
* Protocol revision sent in every envelope.
|
|
51
|
+
*
|
|
52
|
+
* `1` is what the gateway accepts for every frame this plugin sends.
|
|
53
|
+
*/
|
|
54
|
+
exports.PROTOCOL_VERSION = 1;
|
|
55
|
+
/**
|
|
56
|
+
* Command codes.
|
|
57
|
+
*
|
|
58
|
+
* The high byte is the class: `0x01` reads, `0x02` writes. The low byte is
|
|
59
|
+
* the operation index. `0x02000000 | index` is the write for that
|
|
60
|
+
* operation.
|
|
61
|
+
*
|
|
62
|
+
* These are sent to a gas appliance, so the codes and mode strings are the
|
|
63
|
+
* ones the gateway dispatches on, not a generated pair.
|
|
64
|
+
*/
|
|
65
|
+
exports.Command = {
|
|
66
|
+
/** Ask what the gateway has attached. Answered on `res/channelinfo`. */
|
|
67
|
+
CHANNEL_INFO: 0x0100_0001,
|
|
68
|
+
/** Ask a channel what it is doing. Answered on `res/channelstatus`. */
|
|
69
|
+
CHANNEL_STATUS: 0x0100_0004,
|
|
70
|
+
/** Switch the appliance on or off. */
|
|
71
|
+
POWER: 0x0200_0001,
|
|
72
|
+
/** Enable or disable the space-heating loop. */
|
|
73
|
+
HEAT: 0x0200_0002,
|
|
74
|
+
/** Set the domestic hot water setpoint. */
|
|
75
|
+
DHW_TEMPERATURE: 0x0200_0003,
|
|
76
|
+
/** Set the space-heating water setpoint. */
|
|
77
|
+
HEAT_TEMPERATURE: 0x0200_0004,
|
|
78
|
+
/** Start, stop or warm up the recirculation pump. */
|
|
79
|
+
ON_DEMAND: 0x0200_0005,
|
|
80
|
+
/** Set the recirculation setpoint. */
|
|
81
|
+
RECIRCULATION_TEMPERATURE: 0x0200_0007,
|
|
82
|
+
};
|
|
83
|
+
/**
|
|
84
|
+
* The `control.mode` string that accompanies each write command.
|
|
85
|
+
*
|
|
86
|
+
* The command code alone is not enough: the gateway dispatches on the mode
|
|
87
|
+
* string, and the capitalisation is not consistent between them:
|
|
88
|
+
* `DHWTemperature` but `heatTemperature`, `power` but not `Power`. The
|
|
89
|
+
* table spells each one out because the gateway matches the string as
|
|
90
|
+
* written.
|
|
91
|
+
*/
|
|
92
|
+
exports.ControlMode = {
|
|
93
|
+
POWER: 'power',
|
|
94
|
+
HEAT: 'heat',
|
|
95
|
+
DHW_TEMPERATURE: 'DHWTemperature',
|
|
96
|
+
HEAT_TEMPERATURE: 'heatTemperature',
|
|
97
|
+
ON_DEMAND: 'onDemand',
|
|
98
|
+
RECIRCULATION_TEMPERATURE: 'recirculation',
|
|
99
|
+
};
|
|
100
|
+
/**
|
|
101
|
+
* The appliance's boolean encoding.
|
|
102
|
+
*
|
|
103
|
+
* Not zero and one. Zero means "unknown" throughout this protocol, so a
|
|
104
|
+
* command carrying it is asking for nothing. Disabled features report `2`
|
|
105
|
+
* on the read side, and writes use the same pair.
|
|
106
|
+
*/
|
|
107
|
+
exports.OnOff = {
|
|
108
|
+
ON: 1,
|
|
109
|
+
OFF: 2,
|
|
110
|
+
};
|
|
111
|
+
/** On-demand recirculation takes a third value the other flags do not. */
|
|
112
|
+
exports.ON_DEMAND_WARMUP = 3;
|
|
113
|
+
/** The envelope, with the caller's request inside it. */
|
|
114
|
+
function envelope(input) {
|
|
115
|
+
const { context, request, requestTopic, responseTopic } = input;
|
|
116
|
+
return JSON.stringify({
|
|
117
|
+
clientID: context.clientId,
|
|
118
|
+
protocolVersion: exports.PROTOCOL_VERSION,
|
|
119
|
+
request,
|
|
120
|
+
requestTopic,
|
|
121
|
+
responseTopic,
|
|
122
|
+
// A correlation value the gateway echoes. Milliseconds since the epoch, as
|
|
123
|
+
// a string, which is what the app sends. Not used to match responses: the
|
|
124
|
+
// answers arrive on the gateway's own topic, where they are not
|
|
125
|
+
// necessarily answers to anything we asked.
|
|
126
|
+
sessionID: String(Date.now()),
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
/** Fields every request carries, whatever the command. */
|
|
130
|
+
function addressing(context, command) {
|
|
131
|
+
return {
|
|
132
|
+
additionalValue: context.additionalValue,
|
|
133
|
+
command,
|
|
134
|
+
deviceType: context.deviceType,
|
|
135
|
+
macAddress: context.macAddress,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Ask the gateway to describe itself.
|
|
140
|
+
*
|
|
141
|
+
* The first thing sent on a new session. Until it is answered nothing else is
|
|
142
|
+
* meaningful: the reply carries `temperatureType`, without which every
|
|
143
|
+
* temperature in every later frame is unreadable.
|
|
144
|
+
*/
|
|
145
|
+
function channelInfoRequest(input) {
|
|
146
|
+
const { context, topics, responseTopic } = input;
|
|
147
|
+
return {
|
|
148
|
+
topic: topics.start,
|
|
149
|
+
payload: envelope({
|
|
150
|
+
context,
|
|
151
|
+
request: addressing(context, exports.Command.CHANNEL_INFO),
|
|
152
|
+
requestTopic: topics.start,
|
|
153
|
+
responseTopic,
|
|
154
|
+
}),
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Ask one channel for its current state.
|
|
159
|
+
*
|
|
160
|
+
* `unitNumberStart` and `unitNumberEnd` select which appliances in a cascade
|
|
161
|
+
* to report. For a single appliance both ends are 1. The range is inclusive
|
|
162
|
+
* and one-based, which is why a caller passes a count rather than an index.
|
|
163
|
+
*/
|
|
164
|
+
function channelStatusRequest(input) {
|
|
165
|
+
const { context, topics, responseTopic, channelNumber, unitCount } = input;
|
|
166
|
+
return {
|
|
167
|
+
topic: topics.statusRequest,
|
|
168
|
+
payload: envelope({
|
|
169
|
+
context,
|
|
170
|
+
request: {
|
|
171
|
+
...addressing(context, exports.Command.CHANNEL_STATUS),
|
|
172
|
+
status: {
|
|
173
|
+
channelNumber,
|
|
174
|
+
unitNumberStart: 1,
|
|
175
|
+
unitNumberEnd: Math.max(1, unitCount),
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
requestTopic: topics.statusRequest,
|
|
179
|
+
responseTopic,
|
|
180
|
+
}),
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Build a control frame.
|
|
185
|
+
*
|
|
186
|
+
* `param` is always an array, even for a single scalar and even when empty.
|
|
187
|
+
* That is the vendor's shape, not a convenience: the gateway parses the field
|
|
188
|
+
* as a list and a bare number is rejected.
|
|
189
|
+
*/
|
|
190
|
+
function controlRequest(input) {
|
|
191
|
+
const { context, topics, responseTopic, channelNumber, command, mode, param } = input;
|
|
192
|
+
return {
|
|
193
|
+
topic: topics.control,
|
|
194
|
+
payload: envelope({
|
|
195
|
+
context,
|
|
196
|
+
request: {
|
|
197
|
+
...addressing(context, command),
|
|
198
|
+
control: { channelNumber, mode, param: [...param] },
|
|
199
|
+
},
|
|
200
|
+
requestTopic: topics.control,
|
|
201
|
+
responseTopic,
|
|
202
|
+
}),
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
/** Switch the appliance on or off. */
|
|
206
|
+
function powerControl(input) {
|
|
207
|
+
return controlRequest({
|
|
208
|
+
...input,
|
|
209
|
+
command: exports.Command.POWER,
|
|
210
|
+
mode: exports.ControlMode.POWER,
|
|
211
|
+
param: [input.on ? exports.OnOff.ON : exports.OnOff.OFF],
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
/** Enable or disable the space-heating loop. */
|
|
215
|
+
function heatingEnableControl(input) {
|
|
216
|
+
return controlRequest({
|
|
217
|
+
...input,
|
|
218
|
+
command: exports.Command.HEAT,
|
|
219
|
+
mode: exports.ControlMode.HEAT,
|
|
220
|
+
param: [input.on ? exports.OnOff.ON : exports.OnOff.OFF],
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
/** Set the domestic hot water setpoint, in the appliance's native scale. */
|
|
224
|
+
function dhwSetpointControl(input) {
|
|
225
|
+
return controlRequest({
|
|
226
|
+
...input,
|
|
227
|
+
command: exports.Command.DHW_TEMPERATURE,
|
|
228
|
+
mode: exports.ControlMode.DHW_TEMPERATURE,
|
|
229
|
+
param: [encodeSetpoint(input.native, input.scale)],
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
/** Set the space-heating water setpoint, in the appliance's native scale. */
|
|
233
|
+
function heatingSetpointControl(input) {
|
|
234
|
+
return controlRequest({
|
|
235
|
+
...input,
|
|
236
|
+
command: exports.Command.HEAT_TEMPERATURE,
|
|
237
|
+
mode: exports.ControlMode.HEAT_TEMPERATURE,
|
|
238
|
+
param: [encodeSetpoint(input.native, input.scale)],
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
/** Start or stop the recirculation pump. */
|
|
242
|
+
function onDemandControl(input) {
|
|
243
|
+
return controlRequest({
|
|
244
|
+
...input,
|
|
245
|
+
command: exports.Command.ON_DEMAND,
|
|
246
|
+
mode: exports.ControlMode.ON_DEMAND,
|
|
247
|
+
param: [input.on ? exports.OnOff.ON : exports.OnOff.OFF],
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* The message the broker publishes if this client disappears.
|
|
252
|
+
*
|
|
253
|
+
* `status: 0` means an app has gone away. The gateway uses this to decide
|
|
254
|
+
* whether anyone is watching, which on some firmware governs how eagerly it
|
|
255
|
+
* pushes updates. `os: 'A'` is what the app sends; the gateway has not been
|
|
256
|
+
* observed to treat it as meaningful.
|
|
257
|
+
*
|
|
258
|
+
* Registered as the MQTT will rather than sent, so it fires precisely when
|
|
259
|
+
* this client stops answering. That is the one case it is for, and the one
|
|
260
|
+
* case where the plugin cannot send anything itself.
|
|
261
|
+
*/
|
|
262
|
+
function appDisconnectWill(context, topic) {
|
|
263
|
+
return JSON.stringify({
|
|
264
|
+
clientID: context.clientId,
|
|
265
|
+
event: {
|
|
266
|
+
additionalValue: context.additionalValue,
|
|
267
|
+
connection: { os: 'A', status: 0 },
|
|
268
|
+
deviceType: context.deviceType,
|
|
269
|
+
macAddress: context.macAddress,
|
|
270
|
+
},
|
|
271
|
+
protocolVersion: exports.PROTOCOL_VERSION,
|
|
272
|
+
requestTopic: topic,
|
|
273
|
+
sessionID: '',
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Encode a setpoint for a control command.
|
|
278
|
+
*
|
|
279
|
+
* Kept here rather than inlined at the call site because the wire encoding of
|
|
280
|
+
* a temperature has to be identical in a command and in the status frame that
|
|
281
|
+
* reports it back. If the two ever disagree, a setpoint written as 120 reads
|
|
282
|
+
* back as 60 and the thermostat appears to halve itself on every write.
|
|
283
|
+
*/
|
|
284
|
+
function encodeSetpoint(native, scale) {
|
|
285
|
+
return (0, temperature_1.encodeWireTemperature)(native, scale);
|
|
286
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026 tbaur
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0
|
|
5
|
+
* See LICENSE file for full license text
|
|
6
|
+
*
|
|
7
|
+
* @fileoverview The NaviLink REST surface: sign in, and list what the account
|
|
8
|
+
* owns.
|
|
9
|
+
*
|
|
10
|
+
* Three calls, and they do very little. Everything that changes arrives over
|
|
11
|
+
* MQTT: a temperature, a setpoint, a fault. REST exists to get the credentials
|
|
12
|
+
* that make MQTT possible and to learn which appliances the account has.
|
|
13
|
+
*
|
|
14
|
+
* This is an unofficial API. There is no specification, no versioning promise
|
|
15
|
+
* and no deprecation notice; it is the interface the NaviLink mobile app uses.
|
|
16
|
+
* Everything here therefore treats an unexpected response as a protocol error
|
|
17
|
+
* with a readable message. It does not reach into a shape it assumes is there.
|
|
18
|
+
* When Navien changes something, the plugin should say what it did not
|
|
19
|
+
* recognise.
|
|
20
|
+
*
|
|
21
|
+
* **Every value on this path is a credential or personal data.** The request
|
|
22
|
+
* body carries the password; the response carries a JWT pair, temporary AWS
|
|
23
|
+
* IAM credentials, the account holder's name, and, from `device/info`, the
|
|
24
|
+
* street address and coordinates of the installation. Nothing from a response
|
|
25
|
+
* is logged, and the fields the plugin does not need are never read out of it.
|
|
26
|
+
*/
|
|
27
|
+
import type { PluginLogger } from '../types';
|
|
28
|
+
import { type JsonPost } from './http';
|
|
29
|
+
import type { IotCredentials } from './sigv4';
|
|
30
|
+
/** A signed-in session. */
|
|
31
|
+
export interface NaviLinkSessionTokens {
|
|
32
|
+
/** Account sequence number, which appears in every response topic. */
|
|
33
|
+
userSeq: string;
|
|
34
|
+
/** Bearer-less token for later REST calls. */
|
|
35
|
+
accessToken: string;
|
|
36
|
+
/** Present when the cloud offered one. */
|
|
37
|
+
refreshToken: string | undefined;
|
|
38
|
+
/** Temporary IAM credentials for the IoT endpoint. */
|
|
39
|
+
credentials: IotCredentials;
|
|
40
|
+
/** Epoch milliseconds after which the session must be re-established. */
|
|
41
|
+
expiresAt: number;
|
|
42
|
+
}
|
|
43
|
+
/** One appliance gateway on the account, as `device/list` reports it. */
|
|
44
|
+
export interface ListedDevice {
|
|
45
|
+
/** Gateway MAC, as the cloud spells it: lower-case hex, no separators. */
|
|
46
|
+
macAddress: string;
|
|
47
|
+
/** Opaque discriminator the topics need. Frequently an empty string. */
|
|
48
|
+
additionalValue: string;
|
|
49
|
+
/** Gateway kind. `1` for every NaviLink gateway seen so far. */
|
|
50
|
+
deviceType: number;
|
|
51
|
+
/** Home grouping id, which appears in every response topic. */
|
|
52
|
+
homeSeq: string;
|
|
53
|
+
/** The name the user gave it in the NaviLink app. */
|
|
54
|
+
deviceName: string;
|
|
55
|
+
/** `2` means the cloud believes the gateway is online. */
|
|
56
|
+
connected: number;
|
|
57
|
+
}
|
|
58
|
+
/** Injectable collaborators, so tests need neither sockets nor real clocks. */
|
|
59
|
+
export interface NaviLinkRestOptions {
|
|
60
|
+
log: PluginLogger;
|
|
61
|
+
post?: JsonPost;
|
|
62
|
+
now?: () => number;
|
|
63
|
+
/** Cancels in-flight calls, so Homebridge shutdown need not wait out a deadline. */
|
|
64
|
+
signal?: AbortSignal;
|
|
65
|
+
}
|
|
66
|
+
/** Talks to the NaviLink REST service. */
|
|
67
|
+
export declare class NaviLinkRest {
|
|
68
|
+
private readonly log;
|
|
69
|
+
private readonly post;
|
|
70
|
+
private readonly now;
|
|
71
|
+
private readonly signal;
|
|
72
|
+
constructor(options: NaviLinkRestOptions);
|
|
73
|
+
/**
|
|
74
|
+
* Exchange an email and password for a session.
|
|
75
|
+
*
|
|
76
|
+
* Distinguishes a rejected account from an unreachable cloud, because the
|
|
77
|
+
* two need opposite responses: a wrong password must stop the plugin
|
|
78
|
+
* retrying, and a cloud outage must not.
|
|
79
|
+
*/
|
|
80
|
+
signIn(email: string, password: string): Promise<NaviLinkSessionTokens>;
|
|
81
|
+
/**
|
|
82
|
+
* List the gateways on the account.
|
|
83
|
+
*
|
|
84
|
+
* `count` is a page size the API requires rather than a limit worth
|
|
85
|
+
* configuring. Pages are walked until a short one arrives, so a 21st
|
|
86
|
+
* gateway is not dropped. A cloud that never sends a short page is capped.
|
|
87
|
+
*/
|
|
88
|
+
listDevices(input: {
|
|
89
|
+
email: string;
|
|
90
|
+
accessToken: string;
|
|
91
|
+
}): Promise<ListedDevice[]>;
|
|
92
|
+
/** One page of the device list, already parsed. */
|
|
93
|
+
private listDevicePage;
|
|
94
|
+
/**
|
|
95
|
+
* Read a single device's detail.
|
|
96
|
+
*
|
|
97
|
+
* Used only to learn the firmware revision, which is worth having in
|
|
98
|
+
* Accessory Information and in a bug report. **The response also carries the
|
|
99
|
+
* installation's street address and coordinates**; those fields are never
|
|
100
|
+
* read, so they cannot reach a log, an accessory context or a capture.
|
|
101
|
+
*/
|
|
102
|
+
readFirmware(input: {
|
|
103
|
+
email: string;
|
|
104
|
+
accessToken: string;
|
|
105
|
+
macAddress: string;
|
|
106
|
+
additionalValue: string;
|
|
107
|
+
}): Promise<string | undefined>;
|
|
108
|
+
private call;
|
|
109
|
+
private readBody;
|
|
110
|
+
/**
|
|
111
|
+
* Turn a failed sign-in into the right kind of error.
|
|
112
|
+
*
|
|
113
|
+
* The distinction is the point. `AuthenticationError` with
|
|
114
|
+
* `credentialsRejected` stops the plugin trying again, because repeating a
|
|
115
|
+
* wrong password is how an account gets locked out. Anything else is
|
|
116
|
+
* transient and must be retried, because a cloud that is briefly unwell is
|
|
117
|
+
* not a reason to require the user to restart Homebridge.
|
|
118
|
+
*/
|
|
119
|
+
private signInFailure;
|
|
120
|
+
/**
|
|
121
|
+
* Decide how long this session is good for.
|
|
122
|
+
*
|
|
123
|
+
* The response reports `authorizationExpiresIn` and
|
|
124
|
+
* `authenticationExpiresIn`. Neither is documented, the unit is not stated,
|
|
125
|
+
* and which one governs the AWS credentials rather than the JWT is not
|
|
126
|
+
* obvious from the names. So: read both as seconds, take the shorter, and
|
|
127
|
+
* only believe it if it lands in a plausible band. A value outside that band
|
|
128
|
+
* means the reading is wrong, and a wrong reading in the optimistic
|
|
129
|
+
* direction is a session that dies mid-winter without reconnecting.
|
|
130
|
+
*/
|
|
131
|
+
private sessionLifetimeMs;
|
|
132
|
+
/**
|
|
133
|
+
* Read one device-list entry, skipping anything unusable.
|
|
134
|
+
*
|
|
135
|
+
* Skipped rather than thrown on: an account with one unrecognised gateway
|
|
136
|
+
* and one good one should expose the good one.
|
|
137
|
+
*/
|
|
138
|
+
private readListedDevice;
|
|
139
|
+
}
|