@@ -6,6 +6,11 @@ import { AccessoryConfig, AccessoryPlugin, NodeCallback, API, Logging, Service,
6
6
*/
7
7
const STATUS_COALLESCE_WINDOW_MILLIS = 5_000 ;
8
8
9
+ /**
10
+ * How long to wait to connect to Roomba.
11
+ */
12
+ const CONNECT_TIMEOUT = 15_000 ;
13
+
9
14
/**
10
15
* Whether to output debug logging at info level. Useful during debugging to be able to
11
16
* see debug logs from this plugin.
@@ -66,6 +71,16 @@ export default class RoombaAccessory implements AccessoryPlugin {
66
71
*/
67
72
private pendingStatusRequests : NodeCallback < Status > [ ]
68
73
74
+ /**
75
+ * The currently connected Roomba instance _only_ used in the connect() method.
76
+ */
77
+ private _currentlyConnectedRoomba ?: Roomba ;
78
+
79
+ /**
80
+ * How many requests are currently using the connected Roomba instance.
81
+ */
82
+ private _currentlyConnectedRoombaRequests = 0 ;
83
+
69
84
public constructor ( log : Logging , config : AccessoryConfig , api : API ) {
70
85
this . api = api ;
71
86
this . log = ! DEBUG
@@ -178,24 +193,79 @@ export default class RoombaAccessory implements AccessoryPlugin {
178
193
return services ;
179
194
}
180
195
181
- private getRoomba ( ) {
182
- return new dorita980 . Local ( this . blid , this . robotpwd , this . ipaddress ) ;
183
- }
196
+ private async connect ( callback : ( error : Error | null , roomba ?: Roomba ) => Promise < void > ) {
197
+ const getRoomba = ( ) => {
198
+ if ( this . _currentlyConnectedRoomba ) {
199
+ this . _currentlyConnectedRoombaRequests ++ ;
200
+ return this . _currentlyConnectedRoomba ;
201
+ }
202
+
203
+ const roomba = new dorita980 . Local ( this . blid , this . robotpwd , this . ipaddress ) ;
204
+ this . _currentlyConnectedRoomba = roomba ;
205
+ this . _currentlyConnectedRoombaRequests = 1 ;
206
+ return roomba ;
207
+ } ;
208
+ const stopUsingRoomba = async ( roomba : Roomba ) => {
209
+ if ( roomba !== this . _currentlyConnectedRoomba ) {
210
+ this . log . warn ( "Releasing an unexpected Roomba instance" ) ;
211
+ await roomba . end ( ) ;
212
+ return ;
213
+ }
214
+
215
+ this . _currentlyConnectedRoombaRequests -- ;
216
+ if ( this . _currentlyConnectedRoombaRequests === 0 ) {
217
+ this . log . debug ( "Releasing Roomba instance" ) ;
218
+ await roomba . end ( ) ;
219
+ this . _currentlyConnectedRoomba = undefined ;
220
+ } else {
221
+ this . log . debug ( "Leaving Roomba instance with %i ongoing requests" , this . _currentlyConnectedRoombaRequests ) ;
222
+ }
223
+ } ;
224
+
225
+ const roomba = getRoomba ( ) ;
226
+ if ( roomba . connected ) {
227
+ this . log . debug ( "Reusing connected Roomba" ) ;
228
+
229
+ await callback ( null , roomba ) ;
230
+ await stopUsingRoomba ( roomba ) ;
231
+ return ;
232
+ }
233
+
234
+ let timedOut = false ;
235
+
236
+ const timeout = setTimeout ( async ( ) => {
237
+ timedOut = true ;
238
+
239
+ this . log . warn ( "Timed out trying to connect to Roomba" ) ;
240
+
241
+ await callback ( new Error ( "Connect timed out" ) ) ;
242
+ await stopUsingRoomba ( roomba ) ;
243
+ } , CONNECT_TIMEOUT ) ;
244
+
245
+ roomba . on ( "connect" , async ( ) => {
246
+ if ( timedOut ) {
247
+ this . log . debug ( "Connection established to Roomba after timeout" ) ;
248
+ return ;
249
+ }
250
+
251
+ clearTimeout ( timeout ) ;
184
252
185
- private onConnected ( roomba : Roomba , callback : ( ) => void ) {
186
- roomba . on ( "connect" , ( ) => {
187
253
this . log . debug ( "Connected to Roomba" ) ;
188
- callback ( ) ;
254
+ await callback ( null , roomba ) ;
255
+ await stopUsingRoomba ( roomba ) ;
189
256
} ) ;
190
257
}
191
258
192
259
private setRunningState ( powerOn : CharacteristicValue , callback : CharacteristicSetCallback ) {
193
- const roomba = this . getRoomba ( ) ;
194
-
195
260
if ( powerOn ) {
196
261
this . log ( "Starting Roomba" ) ;
197
262
198
- this . onConnected ( roomba , async ( ) => {
263
+ this . connect ( async ( error , roomba ) => {
264
+ if ( error || ! roomba ) {
265
+ callback ( error || new Error ( "Unknown error" ) ) ;
266
+ return ;
267
+ }
268
+
199
269
try {
200
270
/* To start Roomba we signal both a clean and a resume, as if Roomba is paused in a clean cycle,
201
271
we need to instruct it to resume instead.
@@ -216,14 +286,17 @@ export default class RoombaAccessory implements AccessoryPlugin {
216
286
this . log ( "Roomba failed: %s" , ( error as Error ) . message ) ;
217
287
218
288
callback ( error as Error ) ;
219
- } finally {
220
- this . endRoombaIfNeeded ( roomba ) ;
221
289
}
222
290
} ) ;
223
291
} else {
224
292
this . log ( "Roomba pause and dock" ) ;
225
293
226
- this . onConnected ( roomba , async ( ) => {
294
+ this . connect ( async ( error , roomba ) => {
295
+ if ( error || ! roomba ) {
296
+ callback ( error || new Error ( "Unknown error" ) ) ;
297
+ return ;
298
+ }
299
+
227
300
try {
228
301
this . log ( "Roomba is pausing" ) ;
229
302
@@ -239,22 +312,16 @@ export default class RoombaAccessory implements AccessoryPlugin {
239
312
240
313
this . log ( "Roomba paused, returning to Dock" ) ;
241
314
242
- this . dockWhenStopped ( roomba , 3000 ) ;
315
+ await this . dockWhenStopped ( roomba , 3000 ) ;
243
316
} catch ( error ) {
244
317
this . log ( "Roomba failed: %s" , ( error as Error ) . message ) ;
245
318
246
- this . endRoombaIfNeeded ( roomba ) ;
247
-
248
319
callback ( error as Error ) ;
249
320
}
250
321
} ) ;
251
322
}
252
323
}
253
324
254
- private endRoombaIfNeeded ( roomba : Roomba ) {
255
- roomba . end ( ) ;
256
- }
257
-
258
325
private async dockWhenStopped ( roomba : Roomba , pollingInterval : number ) {
259
326
try {
260
327
const state = await roomba . getRobotState ( [ "cleanMissionStatus" ] ) ;
@@ -264,7 +331,6 @@ export default class RoombaAccessory implements AccessoryPlugin {
264
331
this . log ( "Roomba has stopped, issuing dock request" ) ;
265
332
266
333
await roomba . dock ( ) ;
267
- this . endRoombaIfNeeded ( roomba ) ;
268
334
269
335
this . log ( "Roomba docking" ) ;
270
336
@@ -279,19 +345,16 @@ export default class RoombaAccessory implements AccessoryPlugin {
279
345
this . log ( "Roomba is still running. Will check again in %is" , pollingInterval / 1000 ) ;
280
346
281
347
await setTimeout ( ( ) => this . log . debug ( "Trying to dock again..." ) , pollingInterval ) ;
282
- this . dockWhenStopped ( roomba , pollingInterval ) ;
348
+ await this . dockWhenStopped ( roomba , pollingInterval ) ;
283
349
284
350
break ;
285
351
default :
286
- this . endRoombaIfNeeded ( roomba ) ;
287
-
288
352
this . log ( "Roomba is not running" ) ;
289
353
290
354
break ;
291
355
}
292
356
} catch ( error ) {
293
357
this . log . warn ( "Roomba failed to dock: %s" , ( error as Error ) . message ) ;
294
- this . endRoombaIfNeeded ( roomba ) ;
295
358
}
296
359
}
297
360
@@ -342,9 +405,15 @@ export default class RoombaAccessory implements AccessoryPlugin {
342
405
}
343
406
this . currentGetStatusTimestamp = now ;
344
407
345
- const roomba = this . getRoomba ( ) ;
408
+ this . log . debug ( "getStatus connecting to Roomba..." ) ;
409
+
410
+ this . connect ( async ( error , roomba ) => {
411
+ if ( error || ! roomba ) {
412
+ callback ( error || new Error ( "Unknown error" ) ) ;
413
+ this . setCachedStatus ( { error : error as Error } ) ;
414
+ return ;
415
+ }
346
416
347
- this . onConnected ( roomba , async ( ) => {
348
417
this . log . debug ( "getStatus connected to Roomba in %ims" , Date . now ( ) - now ) ;
349
418
350
419
try {
@@ -368,8 +437,6 @@ export default class RoombaAccessory implements AccessoryPlugin {
368
437
} finally {
369
438
this . currentGetStatusTimestamp = undefined ;
370
439
this . pendingStatusRequests = [ ] ;
371
-
372
- this . endRoombaIfNeeded ( roomba ) ;
373
440
}
374
441
} ) ;
375
442
}
0 commit comments