homebridge-orro 1.0.19 → 1.0.21
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/dist/orroConnection.d.ts +111 -0
- package/dist/orroConnection.d.ts.map +1 -0
- package/dist/orroConnection.js +381 -0
- package/dist/orroConnection.js.map +1 -0
- package/dist/platform.d.ts.map +1 -1
- package/dist/platform.js +7 -3
- package/dist/platform.js.map +1 -1
- package/dist/platformAccessory.d.ts +20 -35
- package/dist/platformAccessory.d.ts.map +1 -1
- package/dist/platformAccessory.js +119 -171
- package/dist/platformAccessory.js.map +1 -1
- package/package.json +8 -4
- package/orro-lan/index.js +0 -41
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { Logger } from 'homebridge';
|
|
2
|
+
export declare type OrroCommand = {
|
|
3
|
+
command: 'on' | 'off';
|
|
4
|
+
} | {
|
|
5
|
+
command: 'dimmer_set';
|
|
6
|
+
value: number;
|
|
7
|
+
};
|
|
8
|
+
export interface LightingChange {
|
|
9
|
+
onOff: boolean;
|
|
10
|
+
dimmerLevel: number;
|
|
11
|
+
/** False when the switch changed on its own, for example adapting to its surroundings. */
|
|
12
|
+
wasUserInitiated?: boolean;
|
|
13
|
+
}
|
|
14
|
+
export interface OrroTiming {
|
|
15
|
+
handshakeTimeoutMs: number;
|
|
16
|
+
heartbeatIntervalMs: number;
|
|
17
|
+
/** With a switch known to answer pings, silence this long means the socket is dead. */
|
|
18
|
+
heartbeatStaleMs: number;
|
|
19
|
+
/** A heartbeat count with no pong after which we stop expecting pongs and say so. */
|
|
20
|
+
pongGraceBeats: number;
|
|
21
|
+
reconnectMinMs: number;
|
|
22
|
+
reconnectMaxMs: number;
|
|
23
|
+
/** Retry delay ceiling while a command is waiting on the connection. */
|
|
24
|
+
commandReconnectMs: number;
|
|
25
|
+
/** A connection that stayed up this long resets the retry backoff. */
|
|
26
|
+
stableAfterMs: number;
|
|
27
|
+
confirmationTimeoutMs: number;
|
|
28
|
+
/** How long to keep waiting for the exact level after a non-matching event arrives. */
|
|
29
|
+
confirmationSettleMs: number;
|
|
30
|
+
/** Total time a command may take. Homebridge gives up on a handler at 10 seconds. */
|
|
31
|
+
commandDeadlineMs: number;
|
|
32
|
+
}
|
|
33
|
+
export declare const DEFAULT_TIMING: OrroTiming;
|
|
34
|
+
export interface OrroConnectionOptions {
|
|
35
|
+
name: string;
|
|
36
|
+
ipAddress: string;
|
|
37
|
+
port?: number;
|
|
38
|
+
homeId: string;
|
|
39
|
+
switchId: string;
|
|
40
|
+
accessToken: string;
|
|
41
|
+
log: Pick<Logger, 'debug' | 'info' | 'warn' | 'error'>;
|
|
42
|
+
onLightingChange: (change: LightingChange) => void;
|
|
43
|
+
timing?: Partial<OrroTiming>;
|
|
44
|
+
}
|
|
45
|
+
export declare class OrroCommandError extends Error {
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* One long-lived WebSocket to one switch, per the Orro LAN WebSocket API.
|
|
49
|
+
*
|
|
50
|
+
* The API has no command acknowledgement and does not define a keepalive, so delivery
|
|
51
|
+
* is verified with what it does provide: every state change produces an
|
|
52
|
+
* OrroLightingChange event. A command that should change the light and produces no
|
|
53
|
+
* event is treated as lost, and is resent once on a fresh socket.
|
|
54
|
+
*/
|
|
55
|
+
export declare class OrroConnection {
|
|
56
|
+
private readonly options;
|
|
57
|
+
private readonly timing;
|
|
58
|
+
private readonly clientId;
|
|
59
|
+
private socket?;
|
|
60
|
+
private stopped;
|
|
61
|
+
private reconnectTimer?;
|
|
62
|
+
private heartbeatTimer?;
|
|
63
|
+
private reconnectAttempts;
|
|
64
|
+
private reconnectImmediately;
|
|
65
|
+
private openedAt;
|
|
66
|
+
private lastActivityAt;
|
|
67
|
+
private beatsWithoutPong;
|
|
68
|
+
private readonly openWaiters;
|
|
69
|
+
private pending?;
|
|
70
|
+
private queue;
|
|
71
|
+
private pongSeen;
|
|
72
|
+
private confirmationSeen;
|
|
73
|
+
constructor(options: OrroConnectionOptions);
|
|
74
|
+
start(): void;
|
|
75
|
+
stop(): void;
|
|
76
|
+
/**
|
|
77
|
+
* Sends a command, one at a time per switch so that a resend cannot reorder commands.
|
|
78
|
+
* Resolves once delivered. Rejects with OrroCommandError if it could not be delivered.
|
|
79
|
+
*
|
|
80
|
+
* @param expectChange whether the command should alter the light. Commands that
|
|
81
|
+
* would not change anything produce no event, so they cannot be confirmed.
|
|
82
|
+
*/
|
|
83
|
+
execute(command: OrroCommand, expectChange: boolean): Promise<void>;
|
|
84
|
+
private deliver;
|
|
85
|
+
private write;
|
|
86
|
+
private expectConfirmation;
|
|
87
|
+
/**
|
|
88
|
+
* A late event from an earlier command must not confirm this one, so a non-matching
|
|
89
|
+
* event only starts a short wait for the exact value. If that value never comes
|
|
90
|
+
* (mid-fade levels, or a switch that rounds), the event still proves the socket works.
|
|
91
|
+
* The switch's own adaptive changes say nothing about a command, so they are ignored here.
|
|
92
|
+
*/
|
|
93
|
+
private observeForConfirmation;
|
|
94
|
+
private settlePending;
|
|
95
|
+
private waitUntilOpen;
|
|
96
|
+
private settleOpenWaiters;
|
|
97
|
+
private isOpen;
|
|
98
|
+
private connect;
|
|
99
|
+
private handleOpen;
|
|
100
|
+
private heartbeat;
|
|
101
|
+
private handlePong;
|
|
102
|
+
private handleMessage;
|
|
103
|
+
private handleErrorFrame;
|
|
104
|
+
private handleClose;
|
|
105
|
+
private scheduleReconnect;
|
|
106
|
+
private reconnectNow;
|
|
107
|
+
private clearReconnectTimer;
|
|
108
|
+
private clearHeartbeat;
|
|
109
|
+
private log;
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=orroConnection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"orroConnection.d.ts","sourceRoot":"","sources":["../src/orroConnection.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.OrroConnection = exports.OrroCommandError = exports.DEFAULT_TIMING = void 0;
|
|
7
|
+
const crypto_1 = require("crypto");
|
|
8
|
+
const ws_1 = __importDefault(require("ws"));
|
|
9
|
+
exports.DEFAULT_TIMING = {
|
|
10
|
+
handshakeTimeoutMs: 5000,
|
|
11
|
+
heartbeatIntervalMs: 15000,
|
|
12
|
+
heartbeatStaleMs: 40000,
|
|
13
|
+
pongGraceBeats: 3,
|
|
14
|
+
reconnectMinMs: 1000,
|
|
15
|
+
reconnectMaxMs: 30000,
|
|
16
|
+
commandReconnectMs: 500,
|
|
17
|
+
stableAfterMs: 60000,
|
|
18
|
+
confirmationTimeoutMs: 3000,
|
|
19
|
+
confirmationSettleMs: 300,
|
|
20
|
+
commandDeadlineMs: 8000,
|
|
21
|
+
};
|
|
22
|
+
class OrroCommandError extends Error {
|
|
23
|
+
}
|
|
24
|
+
exports.OrroCommandError = OrroCommandError;
|
|
25
|
+
function matcherFor(command) {
|
|
26
|
+
switch (command.command) {
|
|
27
|
+
case 'on':
|
|
28
|
+
return change => change.onOff;
|
|
29
|
+
case 'off':
|
|
30
|
+
return change => !change.onOff;
|
|
31
|
+
case 'dimmer_set':
|
|
32
|
+
return change => change.dimmerLevel === command.value;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
const MAX_SEND_ATTEMPTS = 2;
|
|
36
|
+
/**
|
|
37
|
+
* One long-lived WebSocket to one switch, per the Orro LAN WebSocket API.
|
|
38
|
+
*
|
|
39
|
+
* The API has no command acknowledgement and does not define a keepalive, so delivery
|
|
40
|
+
* is verified with what it does provide: every state change produces an
|
|
41
|
+
* OrroLightingChange event. A command that should change the light and produces no
|
|
42
|
+
* event is treated as lost, and is resent once on a fresh socket.
|
|
43
|
+
*/
|
|
44
|
+
class OrroConnection {
|
|
45
|
+
constructor(options) {
|
|
46
|
+
this.options = options;
|
|
47
|
+
// the docs ask for a unique id per client; Error frames echo it back
|
|
48
|
+
this.clientId = `homebridge-orro-${crypto_1.randomBytes(4).toString('hex')}`;
|
|
49
|
+
this.stopped = false;
|
|
50
|
+
this.reconnectAttempts = 0;
|
|
51
|
+
this.reconnectImmediately = false;
|
|
52
|
+
this.openedAt = 0;
|
|
53
|
+
this.lastActivityAt = 0;
|
|
54
|
+
this.beatsWithoutPong = 0;
|
|
55
|
+
this.openWaiters = new Set();
|
|
56
|
+
this.queue = Promise.resolve();
|
|
57
|
+
// Neither of these is documented behaviour, so both are learned from the switch
|
|
58
|
+
// rather than assumed. Until seen, their absence is never treated as a failure.
|
|
59
|
+
this.pongSeen = false;
|
|
60
|
+
this.confirmationSeen = false;
|
|
61
|
+
this.timing = { ...exports.DEFAULT_TIMING, ...options.timing };
|
|
62
|
+
}
|
|
63
|
+
start() {
|
|
64
|
+
this.stopped = false;
|
|
65
|
+
this.connect();
|
|
66
|
+
}
|
|
67
|
+
stop() {
|
|
68
|
+
this.stopped = true;
|
|
69
|
+
this.clearReconnectTimer();
|
|
70
|
+
this.clearHeartbeat();
|
|
71
|
+
this.settlePending({ kind: 'closed' });
|
|
72
|
+
this.settleOpenWaiters(false);
|
|
73
|
+
if (this.socket) {
|
|
74
|
+
this.socket.terminate();
|
|
75
|
+
this.socket = undefined;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Sends a command, one at a time per switch so that a resend cannot reorder commands.
|
|
80
|
+
* Resolves once delivered. Rejects with OrroCommandError if it could not be delivered.
|
|
81
|
+
*
|
|
82
|
+
* @param expectChange whether the command should alter the light. Commands that
|
|
83
|
+
* would not change anything produce no event, so they cannot be confirmed.
|
|
84
|
+
*/
|
|
85
|
+
execute(command, expectChange) {
|
|
86
|
+
const result = this.queue.then(() => this.deliver(command, expectChange));
|
|
87
|
+
this.queue = result.catch(() => undefined);
|
|
88
|
+
return result;
|
|
89
|
+
}
|
|
90
|
+
async deliver(command, expectChange) {
|
|
91
|
+
const { name } = this.options;
|
|
92
|
+
const deadline = Date.now() + this.timing.commandDeadlineMs;
|
|
93
|
+
let failure = 'no connection';
|
|
94
|
+
let wentUnconfirmed = false;
|
|
95
|
+
for (let attempt = 1; attempt <= MAX_SEND_ATTEMPTS; attempt++) {
|
|
96
|
+
if (!await this.waitUntilOpen(deadline - Date.now())) {
|
|
97
|
+
failure = 'no connection';
|
|
98
|
+
wentUnconfirmed = false;
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
const timestamp = Date.now();
|
|
102
|
+
const confirmation = expectChange
|
|
103
|
+
? this.expectConfirmation(timestamp, Math.min(this.timing.confirmationTimeoutMs, deadline - timestamp), matcherFor(command))
|
|
104
|
+
: undefined;
|
|
105
|
+
try {
|
|
106
|
+
await this.write(command, timestamp);
|
|
107
|
+
}
|
|
108
|
+
catch (e) {
|
|
109
|
+
this.settlePending({ kind: 'closed' });
|
|
110
|
+
failure = `send failed: ${e.message}`;
|
|
111
|
+
wentUnconfirmed = false;
|
|
112
|
+
this.log('warn', `${name}: ${failure} (attempt ${attempt})`);
|
|
113
|
+
this.reconnectNow();
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
if (!confirmation) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
const outcome = await confirmation;
|
|
120
|
+
if (outcome.kind === 'confirmed') {
|
|
121
|
+
this.confirmationSeen = true;
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
if (outcome.kind === 'rejected') {
|
|
125
|
+
throw new OrroCommandError(`${name} rejected "${command.command}": ${outcome.error}`);
|
|
126
|
+
}
|
|
127
|
+
if (outcome.kind === 'unconfirmed') {
|
|
128
|
+
failure = 'no confirmation from switch';
|
|
129
|
+
wentUnconfirmed = true;
|
|
130
|
+
this.log('warn', `${name}: no confirmation for "${command.command}" (attempt ${attempt}), reconnecting`);
|
|
131
|
+
this.reconnectNow();
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
failure = 'connection closed before confirmation';
|
|
135
|
+
wentUnconfirmed = false;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
// Guards against a firmware that doesn't echo events to the sending client,
|
|
139
|
+
// where treating silence as failure would break every command.
|
|
140
|
+
if (wentUnconfirmed && !this.confirmationSeen) {
|
|
141
|
+
this.log('warn', `${name}: "${command.command}" was sent but this switch has not confirmed any command yet`);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
throw new OrroCommandError(`${name}: "${command.command}" not delivered (${failure})`);
|
|
145
|
+
}
|
|
146
|
+
write(command, timestamp) {
|
|
147
|
+
const payload = JSON.stringify({
|
|
148
|
+
targetId: this.options.switchId,
|
|
149
|
+
clientId: this.clientId,
|
|
150
|
+
type: 'RemoteCommand',
|
|
151
|
+
timestamp,
|
|
152
|
+
data: command,
|
|
153
|
+
});
|
|
154
|
+
return new Promise((resolve, reject) => {
|
|
155
|
+
if (!this.socket) {
|
|
156
|
+
reject(new Error('socket gone'));
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
// ws drops sends on a closing socket silently unless a callback is given
|
|
160
|
+
this.socket.send(payload, (error) => error ? reject(error) : resolve());
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
expectConfirmation(timestamp, timeoutMs, matches) {
|
|
164
|
+
return new Promise(resolve => {
|
|
165
|
+
const timer = setTimeout(() => this.settlePending({ kind: 'unconfirmed' }), timeoutMs);
|
|
166
|
+
this.pending = { timestamp, timer, settle: resolve, matches, settling: false };
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* A late event from an earlier command must not confirm this one, so a non-matching
|
|
171
|
+
* event only starts a short wait for the exact value. If that value never comes
|
|
172
|
+
* (mid-fade levels, or a switch that rounds), the event still proves the socket works.
|
|
173
|
+
* The switch's own adaptive changes say nothing about a command, so they are ignored here.
|
|
174
|
+
*/
|
|
175
|
+
observeForConfirmation(change) {
|
|
176
|
+
const pending = this.pending;
|
|
177
|
+
if (!pending) {
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
if (pending.matches(change)) {
|
|
181
|
+
this.settlePending({ kind: 'confirmed' });
|
|
182
|
+
}
|
|
183
|
+
else if (!pending.settling && change.wasUserInitiated !== false) {
|
|
184
|
+
pending.settling = true;
|
|
185
|
+
clearTimeout(pending.timer);
|
|
186
|
+
pending.timer = setTimeout(() => this.settlePending({ kind: 'confirmed' }), this.timing.confirmationSettleMs);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
settlePending(outcome) {
|
|
190
|
+
if (this.pending) {
|
|
191
|
+
const { timer, settle } = this.pending;
|
|
192
|
+
clearTimeout(timer);
|
|
193
|
+
this.pending = undefined;
|
|
194
|
+
settle(outcome);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
waitUntilOpen(timeoutMs) {
|
|
198
|
+
if (this.isOpen()) {
|
|
199
|
+
return Promise.resolve(true);
|
|
200
|
+
}
|
|
201
|
+
if (this.stopped || timeoutMs <= 0) {
|
|
202
|
+
return Promise.resolve(false);
|
|
203
|
+
}
|
|
204
|
+
// someone is waiting, so don't sit out the rest of a long backoff
|
|
205
|
+
if (this.reconnectTimer) {
|
|
206
|
+
this.clearReconnectTimer();
|
|
207
|
+
this.connect();
|
|
208
|
+
}
|
|
209
|
+
return new Promise(resolve => {
|
|
210
|
+
const waiter = {
|
|
211
|
+
timer: setTimeout(() => {
|
|
212
|
+
this.openWaiters.delete(waiter);
|
|
213
|
+
resolve(false);
|
|
214
|
+
}, timeoutMs),
|
|
215
|
+
settle: resolve,
|
|
216
|
+
};
|
|
217
|
+
this.openWaiters.add(waiter);
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
settleOpenWaiters(open) {
|
|
221
|
+
for (const waiter of this.openWaiters) {
|
|
222
|
+
clearTimeout(waiter.timer);
|
|
223
|
+
waiter.settle(open);
|
|
224
|
+
}
|
|
225
|
+
this.openWaiters.clear();
|
|
226
|
+
}
|
|
227
|
+
isOpen() {
|
|
228
|
+
return this.socket !== undefined && this.socket.readyState === ws_1.default.OPEN;
|
|
229
|
+
}
|
|
230
|
+
connect() {
|
|
231
|
+
if (this.stopped || this.socket) {
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
const { ipAddress, port = 8080, homeId, accessToken } = this.options;
|
|
235
|
+
const socket = new ws_1.default(`ws://${ipAddress}:${port}/edison/${homeId}`, {
|
|
236
|
+
headers: { 'offline-access-token': accessToken },
|
|
237
|
+
handshakeTimeout: this.timing.handshakeTimeoutMs,
|
|
238
|
+
});
|
|
239
|
+
this.socket = socket;
|
|
240
|
+
socket.on('open', () => this.handleOpen());
|
|
241
|
+
socket.on('message', (data) => this.handleMessage(data));
|
|
242
|
+
socket.on('pong', () => this.handlePong());
|
|
243
|
+
socket.on('close', (code, reason) => this.handleClose(socket, code, reason));
|
|
244
|
+
socket.on('error', (error) => {
|
|
245
|
+
// a retry loop against an offline switch shouldn't flood the log
|
|
246
|
+
this.log(this.reconnectAttempts === 0 ? 'warn' : 'debug', `${this.options.name}: ${error.message}`);
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
handleOpen() {
|
|
250
|
+
this.openedAt = Date.now();
|
|
251
|
+
this.lastActivityAt = this.openedAt;
|
|
252
|
+
this.beatsWithoutPong = 0;
|
|
253
|
+
this.log('info', `Connected to ${this.options.name}`);
|
|
254
|
+
this.settleOpenWaiters(true);
|
|
255
|
+
this.clearHeartbeat();
|
|
256
|
+
this.heartbeatTimer = setInterval(() => this.heartbeat(), this.timing.heartbeatIntervalMs);
|
|
257
|
+
}
|
|
258
|
+
heartbeat() {
|
|
259
|
+
if (!this.socket || !this.isOpen()) {
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
const silentFor = Date.now() - this.lastActivityAt;
|
|
263
|
+
if (this.pongSeen && silentFor >= this.timing.heartbeatStaleMs) {
|
|
264
|
+
this.log('warn', `${this.options.name}: no response for ${Math.round(silentFor / 1000)}s, reconnecting`);
|
|
265
|
+
this.reconnectNow();
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
if (!this.pongSeen && ++this.beatsWithoutPong === this.timing.pongGraceBeats) {
|
|
269
|
+
this.log('info', `${this.options.name} does not answer pings, relying on command confirmation`);
|
|
270
|
+
}
|
|
271
|
+
// Writing also surfaces a switch that rebooted: its TCP reset closes this socket.
|
|
272
|
+
this.socket.ping(undefined, undefined, () => undefined);
|
|
273
|
+
}
|
|
274
|
+
handlePong() {
|
|
275
|
+
this.lastActivityAt = Date.now();
|
|
276
|
+
if (!this.pongSeen) {
|
|
277
|
+
this.pongSeen = true;
|
|
278
|
+
this.log('debug', `${this.options.name} answers pings`);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
handleMessage(raw) {
|
|
282
|
+
var _a;
|
|
283
|
+
this.lastActivityAt = Date.now();
|
|
284
|
+
let message;
|
|
285
|
+
try {
|
|
286
|
+
message = JSON.parse(raw.toString());
|
|
287
|
+
}
|
|
288
|
+
catch (e) {
|
|
289
|
+
this.log('debug', `${this.options.name}: unparseable message: ${e.message}`);
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
const { type, targetId, clientId, data } = message || {};
|
|
293
|
+
if (type === 'Error') {
|
|
294
|
+
this.handleErrorFrame(clientId, data);
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
// Identified by shape rather than `type`, so a firmware that names the event
|
|
298
|
+
// differently from the 2021 docs can't make every command look unconfirmed.
|
|
299
|
+
if (!data || typeof data.onOff !== 'boolean' || typeof data.dimmerLevel !== 'number') {
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
// Events are home-wide, and targetId may name a group or room. The docs
|
|
303
|
+
// guarantee switchTargetId is the switch that actually changed.
|
|
304
|
+
if (((_a = data.switchTargetId) !== null && _a !== void 0 ? _a : targetId) !== this.options.switchId) {
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
const change = { onOff: data.onOff, dimmerLevel: data.dimmerLevel, wasUserInitiated: data.wasUserInitiated };
|
|
308
|
+
this.observeForConfirmation(change);
|
|
309
|
+
this.options.onLightingChange(change);
|
|
310
|
+
}
|
|
311
|
+
handleErrorFrame(clientId, data) {
|
|
312
|
+
var _a;
|
|
313
|
+
if (clientId && clientId !== this.clientId) {
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
const error = (_a = data === null || data === void 0 ? void 0 : data.error) !== null && _a !== void 0 ? _a : 'unknown error';
|
|
317
|
+
this.log('error', `${this.options.name} reported an error: ${error}`);
|
|
318
|
+
if (this.pending && ((data === null || data === void 0 ? void 0 : data.messageTimestamp) === undefined || data.messageTimestamp === this.pending.timestamp)) {
|
|
319
|
+
this.settlePending({ kind: 'rejected', error });
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
handleClose(socket, code, reason) {
|
|
323
|
+
if (socket !== this.socket) {
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
this.socket = undefined;
|
|
327
|
+
this.clearHeartbeat();
|
|
328
|
+
this.settlePending({ kind: 'closed' });
|
|
329
|
+
if (this.stopped) {
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
if (this.openedAt && Date.now() - this.openedAt >= this.timing.stableAfterMs) {
|
|
333
|
+
this.reconnectAttempts = 0;
|
|
334
|
+
}
|
|
335
|
+
const detail = reason && reason.length ? `, ${reason.toString()}` : '';
|
|
336
|
+
this.log(this.reconnectAttempts === 0 ? 'warn' : 'debug', `Disconnected from ${this.options.name} (code ${code}${detail})`);
|
|
337
|
+
this.openedAt = 0;
|
|
338
|
+
this.scheduleReconnect();
|
|
339
|
+
}
|
|
340
|
+
scheduleReconnect() {
|
|
341
|
+
const { reconnectMinMs, reconnectMaxMs, commandReconnectMs } = this.timing;
|
|
342
|
+
const backoff = Math.min(reconnectMaxMs, reconnectMinMs * 2 ** this.reconnectAttempts);
|
|
343
|
+
// jitter keeps every switch from retrying in lockstep after a network blip
|
|
344
|
+
let delay = backoff / 2 + Math.random() * (backoff / 2);
|
|
345
|
+
if (this.openWaiters.size > 0) {
|
|
346
|
+
delay = Math.min(delay, commandReconnectMs);
|
|
347
|
+
}
|
|
348
|
+
if (this.reconnectImmediately) {
|
|
349
|
+
delay = 0;
|
|
350
|
+
this.reconnectImmediately = false;
|
|
351
|
+
}
|
|
352
|
+
this.reconnectAttempts++;
|
|
353
|
+
this.reconnectTimer = setTimeout(() => {
|
|
354
|
+
this.reconnectTimer = undefined;
|
|
355
|
+
this.connect();
|
|
356
|
+
}, delay);
|
|
357
|
+
}
|
|
358
|
+
reconnectNow() {
|
|
359
|
+
if (this.socket) {
|
|
360
|
+
this.reconnectImmediately = true;
|
|
361
|
+
this.socket.terminate();
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
clearReconnectTimer() {
|
|
365
|
+
if (this.reconnectTimer) {
|
|
366
|
+
clearTimeout(this.reconnectTimer);
|
|
367
|
+
this.reconnectTimer = undefined;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
clearHeartbeat() {
|
|
371
|
+
if (this.heartbeatTimer) {
|
|
372
|
+
clearInterval(this.heartbeatTimer);
|
|
373
|
+
this.heartbeatTimer = undefined;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
log(level, message) {
|
|
377
|
+
this.options.log[level](message);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
exports.OrroConnection = OrroConnection;
|
|
381
|
+
//# sourceMappingURL=orroConnection.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"orroConnection.js","sourceRoot":"","sources":["../src/orroConnection.ts"],"names":[],"mappings":";;;;;;AAAA,mCAAqC;AAErC,4CAA2B;AAiCd,QAAA,cAAc,GAAe;IACtC,kBAAkB,EAAE,IAAI;IACxB,mBAAmB,EAAE,KAAK;IAC1B,gBAAgB,EAAE,KAAK;IACvB,cAAc,EAAE,CAAC;IACjB,cAAc,EAAE,IAAI;IACpB,cAAc,EAAE,KAAK;IACrB,kBAAkB,EAAE,GAAG;IACvB,aAAa,EAAE,KAAK;IACpB,qBAAqB,EAAE,IAAI;IAC3B,oBAAoB,EAAE,GAAG;IACzB,iBAAiB,EAAE,IAAI;CAC1B,CAAC;AAcF,MAAa,gBAAiB,SAAQ,KAAK;CAAI;AAA/C,4CAA+C;AAgB/C,SAAS,UAAU,CAAC,OAAoB;IACpC,QAAO,OAAO,CAAC,OAAO,EAAE;QACpB,KAAK,IAAI;YACL,OAAO,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC;QAClC,KAAK,KAAK;YACN,OAAO,MAAM,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;QACnC,KAAK,YAAY;YACb,OAAO,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,WAAW,KAAK,OAAO,CAAC,KAAK,CAAC;KAC7D;AACL,CAAC;AAOD,MAAM,iBAAiB,GAAG,CAAC,CAAC;AAE5B;;;;;;;GAOG;AACH,MAAa,cAAc;IAuBvB,YAA6B,OAA8B;QAA9B,YAAO,GAAP,OAAO,CAAuB;QArB3D,qEAAqE;QACpD,aAAQ,GAAG,mBAAmB,oBAAW,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QAGxE,YAAO,GAAG,KAAK,CAAC;QAGhB,sBAAiB,GAAG,CAAC,CAAC;QACtB,yBAAoB,GAAG,KAAK,CAAC;QAC7B,aAAQ,GAAG,CAAC,CAAC;QACb,mBAAc,GAAG,CAAC,CAAC;QACnB,qBAAgB,GAAG,CAAC,CAAC;QACZ,gBAAW,GAAG,IAAI,GAAG,EAAc,CAAC;QAE7C,UAAK,GAAqB,OAAO,CAAC,OAAO,EAAE,CAAC;QAEpD,gFAAgF;QAChF,gFAAgF;QACxE,aAAQ,GAAG,KAAK,CAAC;QACjB,qBAAgB,GAAG,KAAK,CAAC;QAG7B,IAAI,CAAC,MAAM,GAAG,EAAE,GAAG,sBAAc,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAC3D,CAAC;IAED,KAAK;QACD,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC;QACrB,IAAI,CAAC,OAAO,EAAE,CAAC;IACnB,CAAC;IAED,IAAI;QACA,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;QACpB,IAAI,CAAC,mBAAmB,EAAE,CAAC;QAC3B,IAAI,CAAC,cAAc,EAAE,CAAC;QACtB,IAAI,CAAC,aAAa,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC;QACvC,IAAI,CAAC,iBAAiB,CAAC,KAAK,CAAC,CAAC;QAC9B,IAAG,IAAI,CAAC,MAAM,EAAE;YACZ,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC;YACxB,IAAI,CAAC,MAAM,GAAG,SAAS,CAAC;SAC3B;IACL,CAAC;IAED;;;;;;OAMG;IACH,OAAO,CAAC,OAAoB,EAAE,YAAqB;QAC/C,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC;QAC1E,IAAI,CAAC,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;QAC3C,OAAO,MAAM,CAAC;IAClB,CAAC;IAEO,KAAK,CAAC,OAAO,CAAC,OAAoB,EAAE,YAAqB;QAC7D,MAAM,EAAE,IAAI,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC;QAC9B,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,MAAM,CAAC,iBAAiB,CAAC;QAC5D,IAAI,OAAO,GAAG,eAAe,CAAC;QAC9B,IAAI,eAAe,GAAG,KAAK,CAAC;QAE5B,KAAI,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,IAAI,iBAAiB,EAAE,OAAO,EAAE,EAAE;YAC1D,IAAG,CAAC,MAAM,IAAI,CAAC,aAAa,CAAC,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,EAAE;gBACjD,OAAO,GAAG,eAAe,CAAC;gBAC1B,eAAe,GAAG,KAAK,CAAC;gBACxB,MAAM;aACT;YAED,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YAC7B,MAAM,YAAY,GAAG,YAAY;gBAC7B,CAAC,CAAC,IAAI,CAAC,kBAAkB,CAAC,SAAS,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,qBAAqB,EAAE,QAAQ,GAAG,SAAS,CAAC,EAAE,UAAU,CAAC,OAAO,CAAC,CAAC;gBAC5H,CAAC,CAAC,SAAS,CAAC;YAEhB,IAAI;gBACA,MAAM,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;aACxC;YAAC,OAAM,CAAC,EAAE;gBACP,IAAI,CAAC,aAAa,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC;gBACvC,OAAO,GAAG,gBAAgB,CAAC,CAAC,OAAO,EAAE,CAAC;gBACtC,eAAe,GAAG,KAAK,CAAC;gBACxB,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,IAAI,KAAK,OAAO,aAAa,OAAO,GAAG,CAAC,CAAC;gBAC7D,IAAI,CAAC,YAAY,EAAE,CAAC;gBACpB,SAAS;aACZ;YAED,IAAG,CAAC,YAAY,EAAE;gBACd,OAAO;aACV;YAED,MAAM,OAAO,GAAG,MAAM,YAAY,CAAC;YACnC,IAAG,OAAO,CAAC,IAAI,KAAK,WAAW,EAAE;gBAC7B,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAC;gBAC7B,OAAO;aACV;YACD,IAAG,OAAO,CAAC,IAAI,KAAK,UAAU,EAAE;gBAC5B,MAAM,IAAI,gBAAgB,CAAC,GAAG,IAAI,cAAc,OAAO,CAAC,OAAO,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC;aACzF;YAED,IAAG,OAAO,CAAC,IAAI,KAAK,aAAa,EAAE;gBAC/B,OAAO,GAAG,6BAA6B,CAAC;gBACxC,eAAe,GAAG,IAAI,CAAC;gBACvB,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,IAAI,0BAA0B,OAAO,CAAC,OAAO,cAAc,OAAO,iBAAiB,CAAC,CAAC;gBACzG,IAAI,CAAC,YAAY,EAAE,CAAC;aACvB;iBAAM;gBACH,OAAO,GAAG,uCAAuC,CAAC;gBAClD,eAAe,GAAG,KAAK,CAAC;aAC3B;SACJ;QAED,4EAA4E;QAC5E,+DAA+D;QAC/D,IAAG,eAAe,IAAI,CAAC,IAAI,CAAC,gBAAgB,EAAE;YAC1C,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,IAAI,MAAM,OAAO,CAAC,OAAO,8DAA8D,CAAC,CAAC;YAC7G,OAAO;SACV;QAED,MAAM,IAAI,gBAAgB,CAAC,GAAG,IAAI,MAAM,OAAO,CAAC,OAAO,oBAAoB,OAAO,GAAG,CAAC,CAAC;IAC3F,CAAC;IAEO,KAAK,CAAC,OAAoB,EAAE,SAAiB;QACjD,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC;YAC3B,QAAQ,EAAE,IAAI,CAAC,OAAO,CAAC,QAAQ;YAC/B,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,IAAI,EAAE,eAAe;YACrB,SAAS;YACT,IAAI,EAAE,OAAO;SAChB,CAAC,CAAC;QAEH,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACnC,IAAG,CAAC,IAAI,CAAC,MAAM,EAAE;gBACb,MAAM,CAAC,IAAI,KAAK,CAAC,aAAa,CAAC,CAAC,CAAC;gBACjC,OAAO;aACV;YACD,yEAAyE;YACzE,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,KAAa,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;QACpF,CAAC,CAAC,CAAC;IACP,CAAC;IAEO,kBAAkB,CAAC,SAAiB,EAAE,SAAiB,EAAE,OAAuC;QACpG,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC,EAAE;YACzB,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,aAAa,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,CAAC,EAAE,SAAS,CAAC,CAAC;YACvF,IAAI,CAAC,OAAO,GAAG,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;QACnF,CAAC,CAAC,CAAC;IACP,CAAC;IAED;;;;;OAKG;IACK,sBAAsB,CAAC,MAAsB;QACjD,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC;QAC7B,IAAG,CAAC,OAAO,EAAE;YACT,OAAO;SACV;QAED,IAAG,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE;YACxB,IAAI,CAAC,aAAa,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC,CAAC;SAC7C;aAAM,IAAG,CAAC,OAAO,CAAC,QAAQ,IAAI,MAAM,CAAC,gBAAgB,KAAK,KAAK,EAAE;YAC9D,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;YACxB,YAAY,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;YAC5B,OAAO,CAAC,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,aAAa,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAC;SACjH;IACL,CAAC;IAEO,aAAa,CAAC,OAAgB;QAClC,IAAG,IAAI,CAAC,OAAO,EAAE;YACb,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC;YACvC,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,IAAI,CAAC,OAAO,GAAG,SAAS,CAAC;YACzB,MAAM,CAAC,OAAO,CAAC,CAAC;SACnB;IACL,CAAC;IAEO,aAAa,CAAC,SAAiB;QACnC,IAAG,IAAI,CAAC,MAAM,EAAE,EAAE;YACd,OAAO,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;SAChC;QACD,IAAG,IAAI,CAAC,OAAO,IAAI,SAAS,IAAI,CAAC,EAAE;YAC/B,OAAO,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;SACjC;QAED,kEAAkE;QAClE,IAAG,IAAI,CAAC,cAAc,EAAE;YACpB,IAAI,CAAC,mBAAmB,EAAE,CAAC;YAC3B,IAAI,CAAC,OAAO,EAAE,CAAC;SAClB;QAED,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC,EAAE;YACzB,MAAM,MAAM,GAAe;gBACvB,KAAK,EAAE,UAAU,CAAC,GAAG,EAAE;oBACnB,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;oBAChC,OAAO,CAAC,KAAK,CAAC,CAAC;gBACnB,CAAC,EAAE,SAAS,CAAC;gBACb,MAAM,EAAE,OAAO;aAClB,CAAC;YACF,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACjC,CAAC,CAAC,CAAC;IACP,CAAC;IAEO,iBAAiB,CAAC,IAAa;QACnC,KAAI,MAAM,MAAM,IAAI,IAAI,CAAC,WAAW,EAAE;YAClC,YAAY,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YAC3B,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;SACvB;QACD,IAAI,CAAC,WAAW,CAAC,KAAK,EAAE,CAAC;IAC7B,CAAC;IAEO,MAAM;QACV,OAAO,IAAI,CAAC,MAAM,KAAK,SAAS,IAAI,IAAI,CAAC,MAAM,CAAC,UAAU,KAAK,YAAS,CAAC,IAAI,CAAC;IAClF,CAAC;IAEO,OAAO;QACX,IAAG,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,MAAM,EAAE;YAC5B,OAAO;SACV;QAED,MAAM,EAAE,SAAS,EAAE,IAAI,GAAG,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC;QACrE,MAAM,MAAM,GAAG,IAAI,YAAS,CAAC,QAAQ,SAAS,IAAI,IAAI,WAAW,MAAM,EAAE,EAAE;YACvE,OAAO,EAAE,EAAE,sBAAsB,EAAE,WAAW,EAAE;YAChD,gBAAgB,EAAE,IAAI,CAAC,MAAM,CAAC,kBAAkB;SACnD,CAAC,CAAC;QACH,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QAErB,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC;QAC3C,MAAM,CAAC,EAAE,CAAC,SAAS,EAAE,CAAC,IAAoB,EAAE,EAAE,CAAC,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC;QACzE,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC;QAC3C,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,IAAY,EAAE,MAAc,EAAE,EAAE,CAAC,IAAI,CAAC,WAAW,CAAC,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;QAC7F,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,KAAY,EAAE,EAAE;YAChC,iEAAiE;YACjE,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,iBAAiB,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,KAAK,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;QACxG,CAAC,CAAC,CAAC;IACP,CAAC;IAEO,UAAU;QACd,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAC3B,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC,QAAQ,CAAC;QACpC,IAAI,CAAC,gBAAgB,GAAG,CAAC,CAAC;QAC1B,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,gBAAgB,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;QACtD,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,CAAC;QAE7B,IAAI,CAAC,cAAc,EAAE,CAAC;QACtB,IAAI,CAAC,cAAc,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,SAAS,EAAE,EAAE,IAAI,CAAC,MAAM,CAAC,mBAAmB,CAAC,CAAC;IAC/F,CAAC;IAEO,SAAS;QACb,IAAG,CAAC,IAAI,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE;YAC/B,OAAO;SACV;QAED,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,cAAc,CAAC;QACnD,IAAG,IAAI,CAAC,QAAQ,IAAI,SAAS,IAAI,IAAI,CAAC,MAAM,CAAC,gBAAgB,EAAE;YAC3D,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,qBAAqB,IAAI,CAAC,KAAK,CAAC,SAAS,GAAG,IAAI,CAAC,iBAAiB,CAAC,CAAC;YACzG,IAAI,CAAC,YAAY,EAAE,CAAC;YACpB,OAAO;SACV;QAED,IAAG,CAAC,IAAI,CAAC,QAAQ,IAAI,EAAE,IAAI,CAAC,gBAAgB,KAAK,IAAI,CAAC,MAAM,CAAC,cAAc,EAAE;YACzE,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,yDAAyD,CAAC,CAAC;SACnG;QAED,kFAAkF;QAClF,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,EAAE,SAAS,EAAE,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;IAC5D,CAAC;IAEO,UAAU;QACd,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACjC,IAAG,CAAC,IAAI,CAAC,QAAQ,EAAE;YACf,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;YACrB,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,gBAAgB,CAAC,CAAC;SAC3D;IACL,CAAC;IAEO,aAAa,CAAC,GAAmB;;QACrC,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAEjC,IAAI,OAAO,CAAC;QACZ,IAAI;YACA,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC,CAAC;SACxC;QAAC,OAAM,CAAC,EAAE;YACP,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,0BAA0B,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;YAC7E,OAAO;SACV;QAED,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,GAAG,OAAO,IAAI,EAAE,CAAC;QAEzD,IAAG,IAAI,KAAK,OAAO,EAAE;YACjB,IAAI,CAAC,gBAAgB,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;YACtC,OAAO;SACV;QAED,6EAA6E;QAC7E,4EAA4E;QAC5E,IAAG,CAAC,IAAI,IAAI,OAAO,IAAI,CAAC,KAAK,KAAK,SAAS,IAAI,OAAO,IAAI,CAAC,WAAW,KAAK,QAAQ,EAAE;YACjF,OAAO;SACV;QAED,wEAAwE;QACxE,gEAAgE;QAChE,IAAG,CAAC,MAAA,IAAI,CAAC,cAAc,mCAAI,QAAQ,CAAC,KAAK,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE;YAC5D,OAAO;SACV;QAED,MAAM,MAAM,GAAG,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,WAAW,EAAE,IAAI,CAAC,WAAW,EAAE,gBAAgB,EAAE,IAAI,CAAC,gBAAgB,EAAE,CAAC;QAC7G,IAAI,CAAC,sBAAsB,CAAC,MAAM,CAAC,CAAC;QACpC,IAAI,CAAC,OAAO,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC;IAC1C,CAAC;IAEO,gBAAgB,CAAC,QAA4B,EAAE,IAA+D;;QAClH,IAAG,QAAQ,IAAI,QAAQ,KAAK,IAAI,CAAC,QAAQ,EAAE;YACvC,OAAO;SACV;QAED,MAAM,KAAK,GAAG,MAAA,IAAI,aAAJ,IAAI,uBAAJ,IAAI,CAAE,KAAK,mCAAI,eAAe,CAAC;QAC7C,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,uBAAuB,KAAK,EAAE,CAAC,CAAC;QAEtE,IAAG,IAAI,CAAC,OAAO,IAAI,CAAC,CAAA,IAAI,aAAJ,IAAI,uBAAJ,IAAI,CAAE,gBAAgB,MAAK,SAAS,IAAI,IAAI,CAAC,gBAAgB,KAAK,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE;YAC3G,IAAI,CAAC,aAAa,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC,CAAC;SACnD;IACL,CAAC;IAEO,WAAW,CAAC,MAAiB,EAAE,IAAY,EAAE,MAAc;QAC/D,IAAG,MAAM,KAAK,IAAI,CAAC,MAAM,EAAE;YACvB,OAAO;SACV;QAED,IAAI,CAAC,MAAM,GAAG,SAAS,CAAC;QACxB,IAAI,CAAC,cAAc,EAAE,CAAC;QACtB,IAAI,CAAC,aAAa,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC;QAEvC,IAAG,IAAI,CAAC,OAAO,EAAE;YACb,OAAO;SACV;QAED,IAAG,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,MAAM,CAAC,aAAa,EAAE;YACzE,IAAI,CAAC,iBAAiB,GAAG,CAAC,CAAC;SAC9B;QAED,MAAM,MAAM,GAAG,MAAM,IAAI,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,MAAM,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACvE,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,iBAAiB,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,EAAE,qBAAqB,IAAI,CAAC,OAAO,CAAC,IAAI,UAAU,IAAI,GAAG,MAAM,GAAG,CAAC,CAAC;QAE5H,IAAI,CAAC,QAAQ,GAAG,CAAC,CAAC;QAClB,IAAI,CAAC,iBAAiB,EAAE,CAAC;IAC7B,CAAC;IAEO,iBAAiB;QACrB,MAAM,EAAE,cAAc,EAAE,cAAc,EAAE,kBAAkB,EAAE,GAAG,IAAI,CAAC,MAAM,CAAC;QAC3E,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,cAAc,EAAE,cAAc,GAAG,CAAC,IAAI,IAAI,CAAC,iBAAiB,CAAC,CAAC;QACvF,2EAA2E;QAC3E,IAAI,KAAK,GAAG,OAAO,GAAG,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,OAAO,GAAG,CAAC,CAAC,CAAC;QAExD,IAAG,IAAI,CAAC,WAAW,CAAC,IAAI,GAAG,CAAC,EAAE;YAC1B,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,kBAAkB,CAAC,CAAC;SAC/C;QACD,IAAG,IAAI,CAAC,oBAAoB,EAAE;YAC1B,KAAK,GAAG,CAAC,CAAC;YACV,IAAI,CAAC,oBAAoB,GAAG,KAAK,CAAC;SACrC;QAED,IAAI,CAAC,iBAAiB,EAAE,CAAC;QACzB,IAAI,CAAC,cAAc,GAAG,UAAU,CAAC,GAAG,EAAE;YAClC,IAAI,CAAC,cAAc,GAAG,SAAS,CAAC;YAChC,IAAI,CAAC,OAAO,EAAE,CAAC;QACnB,CAAC,EAAE,KAAK,CAAC,CAAC;IACd,CAAC;IAEO,YAAY;QAChB,IAAG,IAAI,CAAC,MAAM,EAAE;YACZ,IAAI,CAAC,oBAAoB,GAAG,IAAI,CAAC;YACjC,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC;SAC3B;IACL,CAAC;IAEO,mBAAmB;QACvB,IAAG,IAAI,CAAC,cAAc,EAAE;YACpB,YAAY,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;YAClC,IAAI,CAAC,cAAc,GAAG,SAAS,CAAC;SACnC;IACL,CAAC;IAEO,cAAc;QAClB,IAAG,IAAI,CAAC,cAAc,EAAE;YACpB,aAAa,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;YACnC,IAAI,CAAC,cAAc,GAAG,SAAS,CAAC;SACnC;IACL,CAAC;IAEO,GAAG,CAAC,KAA0C,EAAE,OAAe;QACnE,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,CAAC;IACrC,CAAC;CACJ;AAxYD,wCAwYC"}
|
package/dist/platform.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"platform.d.ts","sourceRoot":"","sources":["../src/platform.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,qBAAqB,EAAE,MAAM,EAAE,iBAAiB,EAAE,cAAc,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAK5H;;;;EAIE;AACF,qBAAa,YAAa,YAAW,qBAAqB;aAQlC,GAAG,EAAE,MAAM;aACX,MAAM,EAAE,cAAc;aACtB,GAAG,EAAE,GAAG;IAT5B,SAAgB,OAAO,EAAE,OAAO,OAAO,CAAwB;IAC/D,SAAgB,cAAc,EAAE,OAAO,cAAc,CAA+B;IAGpF,SAAgB,WAAW,EAAE,iBAAiB,EAAE,CAAM;gBAGlC,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,cAAc,EACtB,GAAG,EAAE,GAAG;IAe5B;;;MAGE;IACF,kBAAkB,CAAC,SAAS,EAAE,iBAAiB;IAO/C;;;;MAIE;IACF,eAAe;
|
|
1
|
+
{"version":3,"file":"platform.d.ts","sourceRoot":"","sources":["../src/platform.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,qBAAqB,EAAE,MAAM,EAAE,iBAAiB,EAAE,cAAc,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAK5H;;;;EAIE;AACF,qBAAa,YAAa,YAAW,qBAAqB;aAQlC,GAAG,EAAE,MAAM;aACX,MAAM,EAAE,cAAc;aACtB,GAAG,EAAE,GAAG;IAT5B,SAAgB,OAAO,EAAE,OAAO,OAAO,CAAwB;IAC/D,SAAgB,cAAc,EAAE,OAAO,cAAc,CAA+B;IAGpF,SAAgB,WAAW,EAAE,iBAAiB,EAAE,CAAM;gBAGlC,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,cAAc,EACtB,GAAG,EAAE,GAAG;IAe5B;;;MAGE;IACF,kBAAkB,CAAC,SAAS,EAAE,iBAAiB;IAO/C;;;;MAIE;IACF,eAAe;CAsDlB"}
|
package/dist/platform.js
CHANGED
|
@@ -43,6 +43,10 @@ class OrroPlatform {
|
|
|
43
43
|
* must not be registered again to prevent "duplicate UUID" errors.
|
|
44
44
|
*/
|
|
45
45
|
discoverDevices() {
|
|
46
|
+
if (!Array.isArray(this.config.switches) || !this.config.homeID || !this.config.accessToken) {
|
|
47
|
+
this.log.error('Config needs homeID, accessToken and a switches list. Not loading any switches.');
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
46
50
|
// loop over the discovered devices and register each one if it has not already been registered
|
|
47
51
|
for (const device of this.config.switches) {
|
|
48
52
|
// generate a unique id for the accessory this should be generated from
|
|
@@ -55,9 +59,9 @@ class OrroPlatform {
|
|
|
55
59
|
if (existingAccessory) {
|
|
56
60
|
// the accessory already exists
|
|
57
61
|
this.log.info('Restoring existing accessory from cache:', existingAccessory.displayName);
|
|
58
|
-
//
|
|
59
|
-
|
|
60
|
-
|
|
62
|
+
// always use the current config, otherwise an edited IP address is ignored
|
|
63
|
+
existingAccessory.context.device = device;
|
|
64
|
+
this.api.updatePlatformAccessories([existingAccessory]);
|
|
61
65
|
// create the accessory handler for the restored accessory
|
|
62
66
|
// this is imported from `platformAccessory.ts`
|
|
63
67
|
new platformAccessory_1.Switch(this, existingAccessory);
|
package/dist/platform.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"platform.js","sourceRoot":"","sources":["../src/platform.ts"],"names":[],"mappings":";;;AAEA,yCAAwD;AACxD,2DAA6C;AAE7C;;;;EAIE;AACF,MAAa,YAAY;IAOrB,YACoB,GAAW,EACX,MAAsB,EACtB,GAAQ;QAFR,QAAG,GAAH,GAAG,CAAQ;QACX,WAAM,GAAN,MAAM,CAAgB;QACtB,QAAG,GAAH,GAAG,CAAK;QATZ,YAAO,GAAmB,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC;QAC/C,mBAAc,GAA0B,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,cAAc,CAAC;QAEpF,oDAAoD;QACpC,gBAAW,GAAwB,EAAE,CAAC;QAOlD,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,iCAAiC,EAAE,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAExE,8FAA8F;QAC9F,4FAA4F;QAC5F,2FAA2F;QAC3F,yCAAyC;QACzC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,oBAAoB,EAAE,GAAG,EAAE;YACnC,GAAG,CAAC,KAAK,CAAC,sCAAsC,CAAC,CAAC;YAClD,oEAAoE;YACpE,IAAI,CAAC,eAAe,EAAE,CAAC;QAC3B,CAAC,CAAC,CAAC;IACP,CAAC;IAED;;;MAGE;IACF,kBAAkB,CAAC,SAA4B;QAC3C,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,+BAA+B,EAAE,SAAS,CAAC,WAAW,CAAC,CAAC;QAEtE,wGAAwG;QACxG,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IACrC,CAAC;IAED;;;;MAIE;IACF,eAAe;QAEX,+FAA+F;QAC/F,KAAI,MAAM,MAAM,IAAI,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE;YAEtC,uEAAuE;YACvE,0EAA0E;YAC1E,wBAAwB;YACxB,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;YAE7D,uFAAuF;YACvF,wEAAwE;YACxE,MAAM,iBAAiB,GAAG,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC,SAAS,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;YACtF,IAAG,iBAAiB,EAAE;gBAClB,+BAA+B;gBAC/B,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,0CAA0C,EAAE,iBAAiB,CAAC,WAAW,CAAC,CAAC;gBAEzF,
|
|
1
|
+
{"version":3,"file":"platform.js","sourceRoot":"","sources":["../src/platform.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,45 +1,30 @@
|
|
|
1
1
|
import { CharacteristicValue, PlatformAccessory } from 'homebridge';
|
|
2
2
|
import { OrroPlatform } from './platform';
|
|
3
|
-
/**
|
|
4
|
-
* Platform Accessory
|
|
5
|
-
* An instance of this class is created for each accessory your platform registers
|
|
6
|
-
* Each accessory may expose multiple services of different service types.
|
|
7
|
-
*/
|
|
8
3
|
export declare class Switch {
|
|
9
4
|
private readonly platform;
|
|
10
5
|
private readonly accessory;
|
|
11
|
-
private service;
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
private
|
|
17
|
-
private
|
|
6
|
+
private readonly service;
|
|
7
|
+
private readonly connection;
|
|
8
|
+
private readonly state;
|
|
9
|
+
private pendingBrightness;
|
|
10
|
+
private brightnessTimer?;
|
|
11
|
+
private brightnessWaiters;
|
|
12
|
+
private brightnessSending;
|
|
13
|
+
private holdTimer?;
|
|
14
|
+
private heldChange?;
|
|
18
15
|
constructor(platform: OrroPlatform, accessory: PlatformAccessory);
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Handle "SET" requests from HomeKit
|
|
22
|
-
* These are sent when the user changes the state of an accessory, for example, turning on a Light bulb.
|
|
23
|
-
*/
|
|
16
|
+
private handleLightingChange;
|
|
24
17
|
setOn(value: CharacteristicValue): Promise<void>;
|
|
25
|
-
/**
|
|
26
|
-
* Handle the "GET" requests from HomeKit
|
|
27
|
-
* These are sent when HomeKit wants to know the current state of the accessory, for example, checking if a Light bulb is on.
|
|
28
|
-
*
|
|
29
|
-
* GET requests should return as fast as possbile. A long delay here will result in
|
|
30
|
-
* HomeKit being unresponsive and a bad user experience in general.
|
|
31
|
-
*
|
|
32
|
-
* If your device takes time to respond you should update the status of your device
|
|
33
|
-
* asynchronously instead using the `updateCharacteristic` method instead.
|
|
34
|
-
|
|
35
|
-
* @example
|
|
36
|
-
* this.service.updateCharacteristic(this.platform.Characteristic.On, true)
|
|
37
|
-
*/
|
|
38
|
-
getOn(): Promise<CharacteristicValue>;
|
|
39
|
-
/**
|
|
40
|
-
* Handle "SET" requests from HomeKit
|
|
41
|
-
* These are sent when the user changes the state of an accessory, for example, changing the Brightness
|
|
42
|
-
*/
|
|
43
18
|
setBrightness(value: CharacteristicValue): Promise<void>;
|
|
19
|
+
/**
|
|
20
|
+
* Only one brightness command is ever outstanding. Values that arrive meanwhile
|
|
21
|
+
* replace each other, so the switch goes straight to the newest one.
|
|
22
|
+
*/
|
|
23
|
+
private flushBrightness;
|
|
24
|
+
/** Holds back switch reports, then applies the most recent one so the real level wins. */
|
|
25
|
+
private holdSwitchReports;
|
|
26
|
+
/** Throws a HomeKit error on failure, so a lost command is never reported as a success. */
|
|
27
|
+
private send;
|
|
28
|
+
private saveState;
|
|
44
29
|
}
|
|
45
30
|
//# sourceMappingURL=platformAccessory.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"platformAccessory.d.ts","sourceRoot":"","sources":["../src/platformAccessory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,iBAAiB,EAAW,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"platformAccessory.d.ts","sourceRoot":"","sources":["../src/platformAccessory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,iBAAiB,EAAW,MAAM,YAAY,CAAC;AAE7E,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAc1C,qBAAa,MAAM;IAaX,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAb9B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAU;IAClC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAiB;IAC5C,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAsC;IAE5D,OAAO,CAAC,iBAAiB,CAAK;IAC9B,OAAO,CAAC,eAAe,CAAC,CAAiB;IACzC,OAAO,CAAC,iBAAiB,CAA0B;IACnD,OAAO,CAAC,iBAAiB,CAAS;IAClC,OAAO,CAAC,SAAS,CAAC,CAAiB;IACnC,OAAO,CAAC,UAAU,CAAC,CAAiB;gBAGf,QAAQ,EAAE,YAAY,EACtB,SAAS,EAAE,iBAAiB;IA2CjD,OAAO,CAAC,oBAAoB;IA2BtB,KAAK,CAAC,KAAK,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;IAOtD,aAAa,CAAC,KAAK,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;IAaxD;;;OAGG;YACW,eAAe;IA0B7B,0FAA0F;IAC1F,OAAO,CAAC,iBAAiB;IAezB,2FAA2F;YAC7E,IAAI;IAWlB,OAAO,CAAC,SAAS;CAGpB"}
|
|
@@ -1,199 +1,147 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
3
|
exports.Switch = void 0;
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
4
|
+
const orroConnection_1 = require("./orroConnection");
|
|
5
|
+
// HomeKit sends a stream of values while a slider is dragged; only the last one matters
|
|
6
|
+
const BRIGHTNESS_DEBOUNCE_MS = 120;
|
|
7
|
+
// How long after a brightness request the switch's own reports are held back, since
|
|
8
|
+
// events from earlier values and fades keep arriving after the newest value is sent
|
|
9
|
+
const BRIGHTNESS_HOLD_MS = 2000;
|
|
13
10
|
class Switch {
|
|
14
11
|
constructor(platform, accessory) {
|
|
15
12
|
this.platform = platform;
|
|
16
13
|
this.accessory = accessory;
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
14
|
+
this.pendingBrightness = 0;
|
|
15
|
+
this.brightnessWaiters = [];
|
|
16
|
+
this.brightnessSending = false;
|
|
17
|
+
const { device, lastState } = accessory.context;
|
|
18
|
+
// The API has no way to read a switch's light state, so the last known
|
|
19
|
+
// state is kept across restarts instead of reporting everything as off.
|
|
21
20
|
this.state = {
|
|
22
|
-
On:
|
|
23
|
-
Brightness: 0
|
|
21
|
+
On: (lastState === null || lastState === void 0 ? void 0 : lastState.On) === true,
|
|
22
|
+
Brightness: typeof (lastState === null || lastState === void 0 ? void 0 : lastState.Brightness) === 'number' ? lastState.Brightness : 0,
|
|
24
23
|
};
|
|
25
|
-
this.connect = async () => {
|
|
26
|
-
const url = `ws://${this.accessory.context.device.ipAddress}:8080/edison/${this.platform.config.homeID}`;
|
|
27
|
-
this.platform.log.info('Connecting to:', url);
|
|
28
|
-
const connection = new ws_1.default(url, { headers: { 'offline-access-token': this.platform.config.accessToken } });
|
|
29
|
-
connection.on('open', () => {
|
|
30
|
-
this.platform.log.info(`Connected to ${this.accessory.context.device.name}`);
|
|
31
|
-
// listen for system events
|
|
32
|
-
this.connection.on('message', response => {
|
|
33
|
-
try {
|
|
34
|
-
// prevent moving forward if event is not for this switch
|
|
35
|
-
const { data, targetId } = JSON.parse(response.toString());
|
|
36
|
-
if (targetId !== this.accessory.context.device.switchID) {
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
39
|
-
// update on/off state if changed externally
|
|
40
|
-
const { dimmerLevel, onOff } = data || {};
|
|
41
|
-
if (typeof (onOff) === 'boolean' && onOff !== this.state.On) {
|
|
42
|
-
this.platform.log.info(`Updated "on" to ${onOff ? 'true' : 'false'} from system event`);
|
|
43
|
-
this.state.On = onOff;
|
|
44
|
-
this.service.updateCharacteristic(this.platform.Characteristic.On, onOff);
|
|
45
|
-
}
|
|
46
|
-
// turn light off if dimmer level is zero
|
|
47
|
-
if (dimmerLevel === 0 && this.state.On !== false) {
|
|
48
|
-
this.platform.log.info(`Updated "on" to false to match zero dimmer level from system event`);
|
|
49
|
-
this.state.On = false;
|
|
50
|
-
this.service.updateCharacteristic(this.platform.Characteristic.On, false);
|
|
51
|
-
}
|
|
52
|
-
// prevent moving forward if a manuanl hange triggered this event
|
|
53
|
-
if (this.accessory.context.device.action) {
|
|
54
|
-
const { type, value } = this.accessory.context.device.action;
|
|
55
|
-
if (type === 'brightness' && value === dimmerLevel) {
|
|
56
|
-
this.accessory.context.device.action = null;
|
|
57
|
-
this.platform.log.info(`Brightness system event blocked by manual value change`);
|
|
58
|
-
return;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
// update dimmer level if changed externally
|
|
62
|
-
if (dimmerLevel >= 0 && dimmerLevel !== this.state.Brightness) {
|
|
63
|
-
this.platform.log.info(`Updated "brightness" to ${dimmerLevel} from system event`);
|
|
64
|
-
this.state.Brightness = dimmerLevel;
|
|
65
|
-
this.service.updateCharacteristic(this.platform.Characteristic.Brightness, dimmerLevel);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
catch (e) {
|
|
69
|
-
this.platform.log.error('Websocket api response error:', e.message);
|
|
70
|
-
}
|
|
71
|
-
});
|
|
72
|
-
});
|
|
73
|
-
connection.on('close', () => {
|
|
74
|
-
this.platform.log.error(`Disconnected from ${this.accessory.context.device.name}, attempting reconnection in 30 seconds...`);
|
|
75
|
-
setTimeout(this.connect, 30000);
|
|
76
|
-
});
|
|
77
|
-
connection.on('error', error => {
|
|
78
|
-
this.platform.log.error(`${this.accessory.context.device.name} error: ${error.message}`);
|
|
79
|
-
});
|
|
80
|
-
this.connection = connection;
|
|
81
|
-
};
|
|
82
|
-
// set accessory information
|
|
83
24
|
this.accessory.getService(this.platform.Service.AccessoryInformation)
|
|
84
25
|
.setCharacteristic(this.platform.Characteristic.Manufacturer, 'Orro')
|
|
85
26
|
.setCharacteristic(this.platform.Characteristic.Model, 'Switch')
|
|
86
|
-
.setCharacteristic(this.platform.Characteristic.SerialNumber,
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
this.service
|
|
90
|
-
// set the service name, this is what is displayed as the default name on the Home app
|
|
91
|
-
// in this example we are using the name we stored in the `accessory.context` in the `discoverDevices` method.
|
|
92
|
-
this.service.setCharacteristic(this.platform.Characteristic.Name, accessory.context.device.name);
|
|
93
|
-
// each service must implement at-minimum the "required characteristics" for the given service type
|
|
94
|
-
// see https://developers.homebridge.io/#/service/Lightbulb
|
|
95
|
-
// register handlers for the On/Off Characteristic
|
|
27
|
+
.setCharacteristic(this.platform.Characteristic.SerialNumber, device.serialNumber);
|
|
28
|
+
this.service = this.accessory.getService(this.platform.Service.Lightbulb)
|
|
29
|
+
|| this.accessory.addService(this.platform.Service.Lightbulb);
|
|
30
|
+
this.service.setCharacteristic(this.platform.Characteristic.Name, device.name);
|
|
96
31
|
this.service.getCharacteristic(this.platform.Characteristic.On)
|
|
97
|
-
.onSet(this.setOn.bind(this))
|
|
98
|
-
.onGet(this.
|
|
99
|
-
// register handlers for the Brightness Characteristic
|
|
32
|
+
.onSet(this.setOn.bind(this))
|
|
33
|
+
.onGet(() => this.state.On);
|
|
100
34
|
this.service.getCharacteristic(this.platform.Characteristic.Brightness)
|
|
101
|
-
.onSet(this.setBrightness.bind(this))
|
|
102
|
-
|
|
35
|
+
.onSet(this.setBrightness.bind(this))
|
|
36
|
+
.onGet(() => this.state.Brightness);
|
|
37
|
+
this.connection = new orroConnection_1.OrroConnection({
|
|
38
|
+
name: device.name,
|
|
39
|
+
ipAddress: device.ipAddress,
|
|
40
|
+
homeId: this.platform.config.homeID,
|
|
41
|
+
switchId: device.switchID,
|
|
42
|
+
accessToken: this.platform.config.accessToken,
|
|
43
|
+
log: this.platform.log,
|
|
44
|
+
onLightingChange: change => this.handleLightingChange(change),
|
|
45
|
+
});
|
|
46
|
+
this.connection.start();
|
|
47
|
+
this.platform.api.on('shutdown', () => this.connection.stop());
|
|
103
48
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
async setOn(value) {
|
|
109
|
-
try {
|
|
110
|
-
this.platform.log.info('Set On');
|
|
111
|
-
const payload = JSON.stringify({
|
|
112
|
-
targetId: this.accessory.context.device.switchID,
|
|
113
|
-
clientId: 'homebridge-orro',
|
|
114
|
-
type: 'RemoteCommand',
|
|
115
|
-
timestamp: Date.now(),
|
|
116
|
-
data: {
|
|
117
|
-
command: value ? 'on' : 'off'
|
|
118
|
-
}
|
|
119
|
-
});
|
|
120
|
-
this.connection.send(payload);
|
|
121
|
-
this.state.On = value;
|
|
122
|
-
this.platform.log.info('Set Characteristic On ->', {
|
|
123
|
-
targetId: this.accessory.context.device.switchID,
|
|
124
|
-
clientId: 'homebridge-orro',
|
|
125
|
-
type: 'RemoteCommand',
|
|
126
|
-
timestamp: Date.now(),
|
|
127
|
-
data: {
|
|
128
|
-
command: value ? 'on' : 'off'
|
|
129
|
-
}
|
|
130
|
-
});
|
|
49
|
+
handleLightingChange(change) {
|
|
50
|
+
if (this.holdTimer) {
|
|
51
|
+
this.heldChange = change;
|
|
52
|
+
return;
|
|
131
53
|
}
|
|
132
|
-
|
|
133
|
-
|
|
54
|
+
const { onOff, dimmerLevel, wasUserInitiated } = change;
|
|
55
|
+
const source = wasUserInitiated === false ? 'adaptive' : 'from switch';
|
|
56
|
+
// HomeKit has no "on at zero" state, so level 0 is shown as off
|
|
57
|
+
const on = onOff && dimmerLevel !== 0;
|
|
58
|
+
if (on !== this.state.On) {
|
|
59
|
+
this.platform.log.info(`${this.accessory.displayName}: on -> ${on} (${source})`);
|
|
60
|
+
this.state.On = on;
|
|
61
|
+
this.service.updateCharacteristic(this.platform.Characteristic.On, on);
|
|
62
|
+
}
|
|
63
|
+
// level 0 is skipped so turning back on restores the previous brightness
|
|
64
|
+
if (dimmerLevel > 0 && dimmerLevel !== this.state.Brightness) {
|
|
65
|
+
this.platform.log.info(`${this.accessory.displayName}: brightness -> ${dimmerLevel} (${source})`);
|
|
66
|
+
this.state.Brightness = dimmerLevel;
|
|
67
|
+
this.service.updateCharacteristic(this.platform.Characteristic.Brightness, dimmerLevel);
|
|
134
68
|
}
|
|
69
|
+
this.saveState();
|
|
135
70
|
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
return isOn;
|
|
71
|
+
async setOn(value) {
|
|
72
|
+
const on = value;
|
|
73
|
+
await this.send({ command: on ? 'on' : 'off' }, on !== this.state.On);
|
|
74
|
+
this.state.On = on;
|
|
75
|
+
this.saveState();
|
|
76
|
+
}
|
|
77
|
+
setBrightness(value) {
|
|
78
|
+
this.pendingBrightness = value;
|
|
79
|
+
this.holdSwitchReports();
|
|
80
|
+
return new Promise((resolve, reject) => {
|
|
81
|
+
this.brightnessWaiters.push({ resolve, reject });
|
|
82
|
+
if (this.brightnessTimer) {
|
|
83
|
+
clearTimeout(this.brightnessTimer);
|
|
84
|
+
}
|
|
85
|
+
this.brightnessTimer = setTimeout(() => this.flushBrightness(), BRIGHTNESS_DEBOUNCE_MS);
|
|
86
|
+
});
|
|
153
87
|
}
|
|
154
88
|
/**
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
async
|
|
89
|
+
* Only one brightness command is ever outstanding. Values that arrive meanwhile
|
|
90
|
+
* replace each other, so the switch goes straight to the newest one.
|
|
91
|
+
*/
|
|
92
|
+
async flushBrightness() {
|
|
93
|
+
this.brightnessTimer = undefined;
|
|
94
|
+
if (this.brightnessSending) {
|
|
95
|
+
return; // the loop below picks up the newest value when its send finishes
|
|
96
|
+
}
|
|
97
|
+
this.brightnessSending = true;
|
|
98
|
+
// a running debounce timer means the user is still dragging, and it will flush again
|
|
99
|
+
while (this.brightnessWaiters.length > 0 && !this.brightnessTimer) {
|
|
100
|
+
const waiters = this.brightnessWaiters;
|
|
101
|
+
this.brightnessWaiters = [];
|
|
102
|
+
const value = this.pendingBrightness;
|
|
103
|
+
try {
|
|
104
|
+
await this.send({ command: 'dimmer_set', value }, value !== this.state.Brightness);
|
|
105
|
+
this.state.Brightness = value;
|
|
106
|
+
this.saveState();
|
|
107
|
+
waiters.forEach(waiter => waiter.resolve());
|
|
108
|
+
}
|
|
109
|
+
catch (e) {
|
|
110
|
+
waiters.forEach(waiter => waiter.reject(e));
|
|
111
|
+
}
|
|
112
|
+
this.holdSwitchReports();
|
|
113
|
+
}
|
|
114
|
+
this.brightnessSending = false;
|
|
115
|
+
}
|
|
116
|
+
/** Holds back switch reports, then applies the most recent one so the real level wins. */
|
|
117
|
+
holdSwitchReports() {
|
|
118
|
+
if (this.holdTimer) {
|
|
119
|
+
clearTimeout(this.holdTimer);
|
|
120
|
+
}
|
|
121
|
+
this.holdTimer = setTimeout(() => {
|
|
122
|
+
this.holdTimer = undefined;
|
|
123
|
+
const change = this.heldChange;
|
|
124
|
+
this.heldChange = undefined;
|
|
125
|
+
if (change) {
|
|
126
|
+
this.handleLightingChange(change);
|
|
127
|
+
}
|
|
128
|
+
}, BRIGHTNESS_HOLD_MS);
|
|
129
|
+
}
|
|
130
|
+
/** Throws a HomeKit error on failure, so a lost command is never reported as a success. */
|
|
131
|
+
async send(command, expectChange) {
|
|
159
132
|
try {
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
targetId: this.accessory.context.device.switchID,
|
|
163
|
-
clientId: 'homebridge-orro',
|
|
164
|
-
type: 'RemoteCommand',
|
|
165
|
-
timestamp: Date.now(),
|
|
166
|
-
data: {
|
|
167
|
-
command: 'dimmer_set',
|
|
168
|
-
value: value
|
|
169
|
-
}
|
|
170
|
-
});
|
|
171
|
-
// submit api request
|
|
172
|
-
this.connection.send(payload);
|
|
173
|
-
// update local state for brightness
|
|
174
|
-
this.state.Brightness = value;
|
|
175
|
-
this.service.updateCharacteristic(this.platform.Characteristic.Brightness, this.state.Brightness);
|
|
176
|
-
// set object for manual change
|
|
177
|
-
this.accessory.context.device.action = {
|
|
178
|
-
type: 'brightness',
|
|
179
|
-
value: this.state.Brightness
|
|
180
|
-
};
|
|
181
|
-
// log payload to console
|
|
182
|
-
this.platform.log.info('Set Characteristic Brightness -> ', {
|
|
183
|
-
targetId: this.accessory.context.device.switchID,
|
|
184
|
-
clientId: 'homebridge-orro',
|
|
185
|
-
type: 'RemoteCommand',
|
|
186
|
-
timestamp: Date.now(),
|
|
187
|
-
data: {
|
|
188
|
-
command: 'dimmer_set',
|
|
189
|
-
value: value
|
|
190
|
-
}
|
|
191
|
-
});
|
|
133
|
+
await this.connection.execute(command, expectChange);
|
|
134
|
+
this.platform.log.debug(`${this.accessory.displayName}: delivered`, command);
|
|
192
135
|
}
|
|
193
136
|
catch (e) {
|
|
194
|
-
this.platform.log.error(
|
|
137
|
+
this.platform.log.error(e.message);
|
|
138
|
+
const { HapStatusError, HAPStatus } = this.platform.api.hap;
|
|
139
|
+
throw new HapStatusError(-70402 /* SERVICE_COMMUNICATION_FAILURE */);
|
|
195
140
|
}
|
|
196
141
|
}
|
|
142
|
+
saveState() {
|
|
143
|
+
this.accessory.context.lastState = { ...this.state };
|
|
144
|
+
}
|
|
197
145
|
}
|
|
198
146
|
exports.Switch = Switch;
|
|
199
147
|
//# sourceMappingURL=platformAccessory.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"platformAccessory.js","sourceRoot":"","sources":["../src/platformAccessory.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"platformAccessory.js","sourceRoot":"","sources":["../src/platformAccessory.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"displayName": "Homebridge Orro",
|
|
3
3
|
"name": "homebridge-orro",
|
|
4
|
-
"version": "1.0.
|
|
4
|
+
"version": "1.0.21",
|
|
5
5
|
"description": "Homebridge support for Orro lan switches.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"repository": {
|
|
@@ -16,7 +16,13 @@
|
|
|
16
16
|
"homebridge": ">=1.3.0"
|
|
17
17
|
},
|
|
18
18
|
"main": "dist/index.js",
|
|
19
|
+
"files": [
|
|
20
|
+
"dist",
|
|
21
|
+
"config.schema.json",
|
|
22
|
+
"LICENSE"
|
|
23
|
+
],
|
|
19
24
|
"scripts": {
|
|
25
|
+
"test": "npm run build && node --test \"test/*.test.js\"",
|
|
20
26
|
"lint": "eslint src/**.ts --max-warnings=0",
|
|
21
27
|
"watch": "npm run build && npm link && nodemon",
|
|
22
28
|
"build": "rimraf ./dist && tsc",
|
|
@@ -37,8 +43,6 @@
|
|
|
37
43
|
"typescript": "^4.2.2"
|
|
38
44
|
},
|
|
39
45
|
"dependencies": {
|
|
40
|
-
"
|
|
41
|
-
"websocket": "^1.0.34",
|
|
42
|
-
"ws": "^8.4.0"
|
|
46
|
+
"ws": "^8.22.2"
|
|
43
47
|
}
|
|
44
48
|
}
|
package/orro-lan/index.js
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
const net = require('net');
|
|
2
|
-
const WebSocketClient = require('websocket').client;
|
|
3
|
-
|
|
4
|
-
let switch_ip = '192.168.7.125';
|
|
5
|
-
let switch_id = 'de1bc286-51f7-480e-b702-7957b9952618';
|
|
6
|
-
let home_id = '3debeb6b-023d-4dba-8e70-92663b6d2610';
|
|
7
|
-
|
|
8
|
-
const setOn = (connection, val) => {
|
|
9
|
-
let payload = JSON.stringify({
|
|
10
|
-
targetId: switch_id,
|
|
11
|
-
clientId: 'homebridge-orro',
|
|
12
|
-
type: 'RemoteCommand',
|
|
13
|
-
timestamp: Date.now(),
|
|
14
|
-
data: {
|
|
15
|
-
command: val ? 'on' : 'off'
|
|
16
|
-
}
|
|
17
|
-
});
|
|
18
|
-
connection.sendUTF(payload);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
let client = new WebSocketClient();
|
|
22
|
-
client.connect(`ws://${switch_ip}:8080/edison/${home_id}`, null, null, [{ 'offline-access-token': 'YxqRX78eQU_gyzdftPmOG_7n261njz2NFF3hgckXEyQjZGi7qS1RBGdeGhjIb0gYKRKA_wMqeu8RdG9Aybaz1Q' }]);
|
|
23
|
-
client.on('connect', connection => {
|
|
24
|
-
|
|
25
|
-
// post on/off change
|
|
26
|
-
setOn(connection, true);
|
|
27
|
-
setTimeout(setOn.bind(this, connection, false), 5000);
|
|
28
|
-
|
|
29
|
-
// listen for system events
|
|
30
|
-
connection.on('message', data => {
|
|
31
|
-
try {
|
|
32
|
-
let json = JSON.parse(data.utf8Data);
|
|
33
|
-
console.log(json);
|
|
34
|
-
} catch(e) {
|
|
35
|
-
console.log(e.message);
|
|
36
|
-
}
|
|
37
|
-
})
|
|
38
|
-
})
|
|
39
|
-
client.on('error', error => {
|
|
40
|
-
console.log(error.message);
|
|
41
|
-
})
|