@dronedeploy/rocos-js-sdk 4.4.5 → 4.4.7
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/cjs/api/StreamRegister.js +4 -4
- package/cjs/api/streams/caller/CallerStreamAbstract.js +2 -1
- package/cjs/api/streams/command/CommandStreamAbstract.js +2 -1
- package/cjs/api/streams/control/ControlStreamAbstract.js +2 -1
- package/cjs/api/streams/fileAccessor/FileAccessorStreamAbstract.js +2 -1
- package/cjs/api/streams/search/SearchStreamAbstract.js +2 -1
- package/cjs/api/streams/telemetry/TelemetryStream.js +1 -0
- package/cjs/api/streams/telemetry/TelemetryStreamAbstract.js +130 -25
- package/cjs/api/streams/telemetry/TelemetryStreamConnect.js +1 -0
- package/cjs/api/streams/telemetry/TelemetryStreamNode.js +1 -0
- package/cjs/api/streams/webRTCSignalling/WebRTCSignallingStreamAbstract.js +2 -1
- package/cjs/models/maps/Map.d.ts +1 -1
- package/cjs/models/robot-status/RobotStatus.d.ts +23 -0
- package/cjs/models/robot-status/RobotStatusEnums.d.ts +18 -6
- package/cjs/models/robot-status/RobotStatusEnums.js +18 -6
- package/cjs/services/RobotStatusService.d.ts +20 -1
- package/cjs/services/RobotStatusService.js +5 -0
- package/cjs/services/SearchService.js +2 -7
- package/cjs/services/TelemetryService.d.ts +4 -1
- package/cjs/services/TelemetryService.js +17 -12
- package/cjs/services/robot-status/connectivity.d.ts +16 -12
- package/cjs/services/robot-status/connectivity.js +26 -15
- package/cjs/services/robot-status/reportedStatus.d.ts +14 -1
- package/cjs/services/robot-status/reportedStatus.js +36 -1
- package/esm/api/StreamRegister.js +4 -4
- package/esm/api/streams/caller/CallerStreamAbstract.js +2 -1
- package/esm/api/streams/command/CommandStreamAbstract.js +2 -1
- package/esm/api/streams/control/ControlStreamAbstract.js +2 -1
- package/esm/api/streams/fileAccessor/FileAccessorStreamAbstract.js +2 -1
- package/esm/api/streams/search/SearchStreamAbstract.js +2 -1
- package/esm/api/streams/telemetry/TelemetryStream.js +1 -0
- package/esm/api/streams/telemetry/TelemetryStreamAbstract.js +131 -26
- package/esm/api/streams/telemetry/TelemetryStreamConnect.js +1 -0
- package/esm/api/streams/telemetry/TelemetryStreamNode.js +1 -0
- package/esm/api/streams/webRTCSignalling/WebRTCSignallingStreamAbstract.js +2 -1
- package/esm/models/maps/Map.d.ts +1 -1
- package/esm/models/robot-status/RobotStatus.d.ts +23 -0
- package/esm/models/robot-status/RobotStatusEnums.d.ts +18 -6
- package/esm/models/robot-status/RobotStatusEnums.js +18 -6
- package/esm/services/RobotStatusService.d.ts +20 -1
- package/esm/services/RobotStatusService.js +6 -1
- package/esm/services/SearchService.js +2 -7
- package/esm/services/TelemetryService.d.ts +4 -1
- package/esm/services/TelemetryService.js +17 -12
- package/esm/services/robot-status/connectivity.d.ts +16 -12
- package/esm/services/robot-status/connectivity.js +26 -15
- package/esm/services/robot-status/reportedStatus.d.ts +14 -1
- package/esm/services/robot-status/reportedStatus.js +35 -1
- package/package.json +7 -2
|
@@ -82,13 +82,13 @@ class StreamRegister {
|
|
|
82
82
|
return;
|
|
83
83
|
// Wait for init to settle before tearing down, so init can never open resources on an already
|
|
84
84
|
// stopped stream. A re-acquire in the meantime revives the entry and cancels the teardown.
|
|
85
|
-
|
|
86
|
-
.catch(() => undefined)
|
|
87
|
-
.then(() => {
|
|
85
|
+
const evictIfUnheld = () => {
|
|
88
86
|
if (this.entries.get(identifier) === entry && entry.refCount <= 0) {
|
|
89
87
|
this.evict(identifier, entry.stream);
|
|
90
88
|
}
|
|
91
|
-
}
|
|
89
|
+
};
|
|
90
|
+
void entry.ready
|
|
91
|
+
.then(() => evictIfUnheld(), () => evictIfUnheld())
|
|
92
92
|
.catch((e) => this.logger.error(`Failed to evict stream. identifier: ${identifier}`, e));
|
|
93
93
|
}
|
|
94
94
|
evict(identifier, stream) {
|
|
@@ -29,7 +29,8 @@ class CallerStreamAbstract {
|
|
|
29
29
|
this.token = token;
|
|
30
30
|
}
|
|
31
31
|
stopStream() {
|
|
32
|
-
|
|
32
|
+
this.statusStream$.next(models_1.SubscriberStatusEnum.STOPPED);
|
|
33
|
+
this.statusStream$.complete();
|
|
33
34
|
}
|
|
34
35
|
invokeRequest(params) {
|
|
35
36
|
this.logger.info('invokeRequest', params);
|
|
@@ -31,7 +31,8 @@ class CommandStreamAbstract {
|
|
|
31
31
|
this.token = token;
|
|
32
32
|
}
|
|
33
33
|
stopStream() {
|
|
34
|
-
|
|
34
|
+
this.statusStream$.next(models_1.SubscriberStatusEnum.STOPPED);
|
|
35
|
+
this.statusStream$.complete();
|
|
35
36
|
}
|
|
36
37
|
invokeRequest(params) {
|
|
37
38
|
if (params.timeoutMs === undefined || params.timeoutMs === null) {
|
|
@@ -28,7 +28,8 @@ class ControlStreamAbstract {
|
|
|
28
28
|
this.token = token;
|
|
29
29
|
}
|
|
30
30
|
stopStream() {
|
|
31
|
-
|
|
31
|
+
this.statusStream$.next(models_1.SubscriberStatusEnum.STOPPED);
|
|
32
|
+
this.statusStream$.complete();
|
|
32
33
|
}
|
|
33
34
|
takeActionWithAck(action, params, ackTimeoutInSeconds = 15) {
|
|
34
35
|
return new rxjs_1.Observable((emitter) => {
|
|
@@ -27,7 +27,8 @@ class FileAccessorStreamAbstract {
|
|
|
27
27
|
this.token = token;
|
|
28
28
|
}
|
|
29
29
|
stopStream() {
|
|
30
|
-
|
|
30
|
+
this.statusStream$.next(models_1.SubscriberStatusEnum.STOPPED);
|
|
31
|
+
this.statusStream$.complete();
|
|
31
32
|
}
|
|
32
33
|
async serverReqOp(params) {
|
|
33
34
|
return this.serverReqOpInternal(params);
|
|
@@ -28,7 +28,8 @@ class SearchStreamAbstract {
|
|
|
28
28
|
this.token = token;
|
|
29
29
|
}
|
|
30
30
|
stopStream() {
|
|
31
|
-
|
|
31
|
+
this.statusStream$.next(models_1.SubscriberStatusEnum.STOPPED);
|
|
32
|
+
this.statusStream$.complete();
|
|
32
33
|
}
|
|
33
34
|
searchStream(params) {
|
|
34
35
|
const request = this.convertToQueryMessage(params.query);
|
|
@@ -24,6 +24,7 @@ class TelemetryStream extends TelemetryStreamAbstract_1.TelemetryStreamAbstract
|
|
|
24
24
|
this.client = new teletubby_pb_client_1.TelemetryGatewayClient(transport);
|
|
25
25
|
}
|
|
26
26
|
stopInternal() {
|
|
27
|
+
this.subscriberId = undefined;
|
|
27
28
|
if (this.abortController) {
|
|
28
29
|
this.logger.info('Starting stop stream process');
|
|
29
30
|
this.abortController.abort();
|
|
@@ -13,6 +13,33 @@ const arrayRemove_1 = require("../../../helpers/arrayRemove");
|
|
|
13
13
|
const arrayUnique_1 = require("../../../helpers/arrayUnique");
|
|
14
14
|
const operators_1 = require("rxjs/operators");
|
|
15
15
|
const getSubscriptionsDifference_1 = require("../../../helpers/getSubscriptionsDifference");
|
|
16
|
+
const telemetryActionQueueStates = new WeakMap();
|
|
17
|
+
const resolveTelemetryActions = (actions) => {
|
|
18
|
+
actions.forEach((action) => action.resolve());
|
|
19
|
+
};
|
|
20
|
+
const resolveUnsentTelemetryActions = (queueState) => {
|
|
21
|
+
resolveTelemetryActions(queueState.pendingActions.splice(0));
|
|
22
|
+
};
|
|
23
|
+
const takeNextTelemetryBatch = (queueState) => {
|
|
24
|
+
const firstAction = queueState.pendingActions.shift();
|
|
25
|
+
if (!firstAction)
|
|
26
|
+
return [];
|
|
27
|
+
const activeBatch = [firstAction];
|
|
28
|
+
if (firstAction.callsignsLookup.isQuery() || firstAction.terminate)
|
|
29
|
+
return activeBatch;
|
|
30
|
+
while (queueState.pendingActions.length > 0) {
|
|
31
|
+
const [nextAction] = queueState.pendingActions;
|
|
32
|
+
if (nextAction.callsignsLookup.isQuery() ||
|
|
33
|
+
nextAction.terminate ||
|
|
34
|
+
nextAction.actionType !== firstAction.actionType ||
|
|
35
|
+
nextAction.subscriberId !== firstAction.subscriberId ||
|
|
36
|
+
nextAction.registration !== firstAction.registration) {
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
activeBatch.push(queueState.pendingActions.shift());
|
|
40
|
+
}
|
|
41
|
+
return activeBatch;
|
|
42
|
+
};
|
|
16
43
|
class TelemetryStreamAbstract {
|
|
17
44
|
constructor(config) {
|
|
18
45
|
this.telemetryActionQueue = Promise.resolve();
|
|
@@ -38,6 +65,14 @@ class TelemetryStreamAbstract {
|
|
|
38
65
|
this.messageStream$ = new rxjs_1.Subject();
|
|
39
66
|
this.statusStream$ = new rxjs_1.BehaviorSubject(models_1.SubscriberStatusEnum.STOPPED);
|
|
40
67
|
this.heartbeat = new StreamHeartbeat_1.StreamHeartbeat(this.identifier);
|
|
68
|
+
telemetryActionQueueStates.set(this, {
|
|
69
|
+
pendingActions: [],
|
|
70
|
+
activeBatch: [],
|
|
71
|
+
registration: 0,
|
|
72
|
+
drainScheduled: false,
|
|
73
|
+
requestInFlight: false,
|
|
74
|
+
stopped: false,
|
|
75
|
+
});
|
|
41
76
|
RocosStore_1.RocosStore.getChangeSubject()
|
|
42
77
|
.pipe((0, operators_1.filter)((message) => {
|
|
43
78
|
return message.type === 'token' && message.url === this.url;
|
|
@@ -55,6 +90,12 @@ class TelemetryStreamAbstract {
|
|
|
55
90
|
this.registerReceiver();
|
|
56
91
|
}
|
|
57
92
|
stopStream() {
|
|
93
|
+
const queueState = telemetryActionQueueStates.get(this);
|
|
94
|
+
if (queueState) {
|
|
95
|
+
queueState.stopped = true;
|
|
96
|
+
resolveUnsentTelemetryActions(queueState);
|
|
97
|
+
}
|
|
98
|
+
this.subscriberId = undefined;
|
|
58
99
|
this.heartbeat?.stop();
|
|
59
100
|
this.stopInternal();
|
|
60
101
|
if (this.messagesTimer) {
|
|
@@ -62,6 +103,10 @@ class TelemetryStreamAbstract {
|
|
|
62
103
|
}
|
|
63
104
|
this.subscriberStatus = models_1.SubscriberStatusEnum.STOPPED;
|
|
64
105
|
this.statusStream$.next(this.subscriberStatus);
|
|
106
|
+
// The stream is being torn down (the registry only stops a stream when it is evicted), so
|
|
107
|
+
// complete the status stream: this releases every service-level forwarder subscribed to it
|
|
108
|
+
// rather than leaving them attached to a dead instance.
|
|
109
|
+
this.statusStream$.complete();
|
|
65
110
|
}
|
|
66
111
|
addSubscription(params) {
|
|
67
112
|
this.logger.info('Adding subscriptions from stream', params.uniqueId);
|
|
@@ -83,7 +128,9 @@ class TelemetryStreamAbstract {
|
|
|
83
128
|
const toAdd = (0, arrayRemove_1.arrayRemove)(this.sources, sourcesBefore);
|
|
84
129
|
if (toAdd.length) {
|
|
85
130
|
this.logger.debug('New query subscriptions added, we need to refresh sources', { toAdd });
|
|
86
|
-
void this.enqueueTelemetryAction('subscribe', this.callsignsLookup, toAdd)
|
|
131
|
+
void this.enqueueTelemetryAction('subscribe', this.callsignsLookup, toAdd).catch((error) => {
|
|
132
|
+
this.logger.error(`Failed to subscribe: ${error}`);
|
|
133
|
+
});
|
|
87
134
|
}
|
|
88
135
|
}
|
|
89
136
|
else {
|
|
@@ -94,7 +141,9 @@ class TelemetryStreamAbstract {
|
|
|
94
141
|
const { toAdd } = (0, getSubscriptionsDifference_1.getSubscriptionsDifference)(before, after);
|
|
95
142
|
if (toAdd.callsigns.length && toAdd.sources.length) {
|
|
96
143
|
this.logger.debug('New subscriptions added, we need to refresh sources', { toAdd });
|
|
97
|
-
void this.enqueueTelemetryAction('subscribe', new models_1.CallsignsLookup(toAdd.callsigns), toAdd.sources)
|
|
144
|
+
void this.enqueueTelemetryAction('subscribe', new models_1.CallsignsLookup(toAdd.callsigns), toAdd.sources).catch((error) => {
|
|
145
|
+
this.logger.error(`Failed to subscribe: ${error}`);
|
|
146
|
+
});
|
|
98
147
|
}
|
|
99
148
|
}
|
|
100
149
|
}
|
|
@@ -200,14 +249,22 @@ class TelemetryStreamAbstract {
|
|
|
200
249
|
// Subscribe message
|
|
201
250
|
case grpc_1.GRPC_SOURCE_SUBSCRIBED: {
|
|
202
251
|
const { subscriberId } = rocosMessage.payload;
|
|
252
|
+
const queueState = telemetryActionQueueStates.get(this);
|
|
253
|
+
if (queueState?.stopped)
|
|
254
|
+
return;
|
|
203
255
|
if (this.subscriberId && subscriberId) {
|
|
204
256
|
this.logger.warn(`Replacing old subscriberId (${this.subscriberId}) with a new one (${subscriberId})`);
|
|
205
257
|
}
|
|
206
|
-
|
|
258
|
+
if (queueState) {
|
|
259
|
+
queueState.registration++;
|
|
260
|
+
resolveUnsentTelemetryActions(queueState);
|
|
261
|
+
}
|
|
207
262
|
this.subscriberId = subscriberId;
|
|
208
263
|
rocosMessage.subscriberId = this.subscriberId;
|
|
209
264
|
this.logger.info('onData', `subscriberId has been updated - ${subscriberId}`, { subscriberId });
|
|
210
|
-
void this.enqueueTelemetryAction('subscribe')
|
|
265
|
+
void this.enqueueTelemetryAction('subscribe').catch((error) => {
|
|
266
|
+
this.logger.error(`Failed to subscribe: ${error}`);
|
|
267
|
+
});
|
|
211
268
|
this.heartbeat.start(() => this.sendHeartbeat(), this.sendHeartbeatTime);
|
|
212
269
|
isRobotMessage = false;
|
|
213
270
|
break;
|
|
@@ -249,21 +306,69 @@ class TelemetryStreamAbstract {
|
|
|
249
306
|
* unsubscribe landing last strands a subscribe and nothing re-adds it.
|
|
250
307
|
*/
|
|
251
308
|
enqueueTelemetryAction(actionType, callsignsLookup, sources, terminate) {
|
|
252
|
-
const
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
309
|
+
const queueState = telemetryActionQueueStates.get(this);
|
|
310
|
+
if (!queueState || queueState.stopped)
|
|
311
|
+
return Promise.resolve();
|
|
312
|
+
const completion = new Promise((resolve, reject) => {
|
|
313
|
+
queueState.pendingActions.push({
|
|
314
|
+
actionType,
|
|
315
|
+
callsignsLookup: callsignsLookup ?? this.callsignsLookup,
|
|
316
|
+
sources: [...(sources ?? this.sources)],
|
|
317
|
+
terminate: terminate ?? false,
|
|
318
|
+
subscriberId: this.subscriberId,
|
|
319
|
+
registration: queueState.registration,
|
|
320
|
+
resolve,
|
|
321
|
+
reject,
|
|
322
|
+
});
|
|
323
|
+
});
|
|
324
|
+
const scheduleDrain = () => {
|
|
325
|
+
if (queueState.stopped ||
|
|
326
|
+
queueState.requestInFlight ||
|
|
327
|
+
queueState.drainScheduled ||
|
|
328
|
+
queueState.pendingActions.length === 0) {
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
queueState.drainScheduled = true;
|
|
332
|
+
this.telemetryActionQueue = this.telemetryActionQueue.then(async () => {
|
|
333
|
+
queueState.drainScheduled = false;
|
|
334
|
+
if (queueState.stopped || queueState.requestInFlight)
|
|
335
|
+
return;
|
|
336
|
+
const activeBatch = takeNextTelemetryBatch(queueState);
|
|
337
|
+
const [firstAction] = activeBatch;
|
|
338
|
+
if (!firstAction)
|
|
339
|
+
return;
|
|
340
|
+
queueState.activeBatch = activeBatch;
|
|
341
|
+
queueState.requestInFlight = true;
|
|
342
|
+
try {
|
|
343
|
+
await this.takeTelemetryAction(firstAction.actionType, firstAction.callsignsLookup, firstAction.sources);
|
|
344
|
+
resolveTelemetryActions(activeBatch);
|
|
345
|
+
}
|
|
346
|
+
catch (error) {
|
|
347
|
+
activeBatch.forEach((action) => action.reject(error));
|
|
348
|
+
}
|
|
349
|
+
finally {
|
|
350
|
+
queueState.activeBatch = [];
|
|
351
|
+
queueState.requestInFlight = false;
|
|
352
|
+
scheduleDrain();
|
|
353
|
+
}
|
|
354
|
+
});
|
|
355
|
+
};
|
|
356
|
+
scheduleDrain();
|
|
357
|
+
return completion;
|
|
258
358
|
}
|
|
259
|
-
async takeTelemetryAction(actionType, callsignsLookup, sources
|
|
359
|
+
async takeTelemetryAction(actionType, callsignsLookup, sources) {
|
|
260
360
|
this.logger.debug('takeTelemetryAction', actionType);
|
|
361
|
+
const queueState = telemetryActionQueueStates.get(this);
|
|
362
|
+
const activeBatch = queueState?.activeBatch ?? [];
|
|
363
|
+
const [firstAction] = activeBatch;
|
|
261
364
|
// A config fault, not a race: this stream can never address a request.
|
|
262
365
|
if (!this.projectId) {
|
|
263
366
|
this.logger.warn('takeTelemetryAction', actionType, `${actionType} without a projectId - dropped`);
|
|
264
367
|
return Promise.resolve();
|
|
265
368
|
}
|
|
266
|
-
if (!
|
|
369
|
+
if (!firstAction?.subscriberId ||
|
|
370
|
+
firstAction.subscriberId !== this.subscriberId ||
|
|
371
|
+
firstAction.registration !== queueState?.registration) {
|
|
267
372
|
// Deferred, not lost: `onData` re-asserts `this.sources` when the subscriberId arrives.
|
|
268
373
|
this.logger.debug('takeTelemetryAction', actionType, `${actionType} before the receiver was registered - deferred to the post-registration re-assert`);
|
|
269
374
|
return Promise.resolve();
|
|
@@ -271,20 +376,20 @@ class TelemetryStreamAbstract {
|
|
|
271
376
|
const actionSources = sources ?? this.sources;
|
|
272
377
|
const actionCallsigns = callsignsLookup ?? this.callsignsLookup;
|
|
273
378
|
if (!actionCallsigns.isQuery()) {
|
|
274
|
-
// Compose the request message
|
|
275
379
|
const req = teletubby_pb_1.TelemetryRequest.create({
|
|
276
|
-
subscriberId:
|
|
277
|
-
requestedActions:
|
|
278
|
-
{
|
|
279
|
-
operation: actionType,
|
|
280
|
-
callsigns:
|
|
281
|
-
sources:
|
|
282
|
-
}
|
|
283
|
-
|
|
380
|
+
subscriberId: firstAction.subscriberId,
|
|
381
|
+
requestedActions: activeBatch.map((action) => {
|
|
382
|
+
const telemetryAction = teletubby_pb_1.TelemetryAction.create({
|
|
383
|
+
operation: action.actionType,
|
|
384
|
+
callsigns: action.callsignsLookup.getCallsigns(),
|
|
385
|
+
sources: action.sources,
|
|
386
|
+
});
|
|
387
|
+
if (action.actionType === 'unsubscribe' && action.terminate) {
|
|
388
|
+
this.addTerminateToAction(telemetryAction);
|
|
389
|
+
}
|
|
390
|
+
return telemetryAction;
|
|
391
|
+
}),
|
|
284
392
|
});
|
|
285
|
-
if (actionType === 'unsubscribe' && terminate) {
|
|
286
|
-
this.addTerminateToAction(req.requestedActions[0]);
|
|
287
|
-
}
|
|
288
393
|
this.logger.debug('takeTelemetryAction', actionType, 'client.requestTelemetry', this.subscriberId);
|
|
289
394
|
// Send the message to back-end.
|
|
290
395
|
return this.requestTelemetry(req);
|
|
@@ -309,7 +414,7 @@ class TelemetryStreamAbstract {
|
|
|
309
414
|
});
|
|
310
415
|
// Compose the request message
|
|
311
416
|
const req = teletubby_pb_1.TelemetryQueryRequest.create({
|
|
312
|
-
subscriberId:
|
|
417
|
+
subscriberId: firstAction.subscriberId,
|
|
313
418
|
sources: actionSources,
|
|
314
419
|
operation: actionType,
|
|
315
420
|
callsignQuery: {
|
|
@@ -40,6 +40,7 @@ class TelemetryStreamConnect extends TelemetryStreamAbstract_1.TelemetryStreamAb
|
|
|
40
40
|
this.client = (0, connect_1.createClient)(teletubby_pb_1.TelemetryGateway, transport);
|
|
41
41
|
}
|
|
42
42
|
stopInternal() {
|
|
43
|
+
this.subscriberId = undefined;
|
|
43
44
|
if (this.abortController) {
|
|
44
45
|
this.logger.info('Starting stop stream process');
|
|
45
46
|
this.abortController.abort();
|
|
@@ -48,6 +48,7 @@ class TelemetryStreamNode extends TelemetryStreamAbstract_1.TelemetryStreamAbstr
|
|
|
48
48
|
this.credentials = config.insecure ? grpc.credentials.createInsecure() : grpc.credentials.createSsl();
|
|
49
49
|
}
|
|
50
50
|
stopInternal() {
|
|
51
|
+
this.subscriberId = undefined;
|
|
51
52
|
try {
|
|
52
53
|
this.logger.info('Starting stop stream process');
|
|
53
54
|
this.logger.info('Stopping stream');
|
package/cjs/models/maps/Map.d.ts
CHANGED
|
@@ -41,3 +41,26 @@ export interface RobotStatus {
|
|
|
41
41
|
connectivity: RobotConnectivityStatus;
|
|
42
42
|
colour: RobotStatusColour;
|
|
43
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* One contributor to a reported-status axis, from the agent's
|
|
46
|
+
* `robot/status/contributors` topic. `status` is the contributor's own verdict
|
|
47
|
+
* for that axis — the integer wire value mapped onto the enum.
|
|
48
|
+
*/
|
|
49
|
+
export interface RobotStatusContributor<TStatus = RobotHealth | RobotReadiness> {
|
|
50
|
+
/** Identifier of the contributor, e.g. `battery`. */
|
|
51
|
+
id: string;
|
|
52
|
+
/** The agent component that raised it, e.g. `dji`. */
|
|
53
|
+
component: string;
|
|
54
|
+
/** Human-readable explanation, e.g. `battery below 20%`; empty when none was given. */
|
|
55
|
+
reason: string;
|
|
56
|
+
/** The contributor's verdict for its axis. */
|
|
57
|
+
status: TStatus;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* The per-contributor breakdown behind a robot's reported health and readiness,
|
|
61
|
+
* from the agent's `robot/status/contributors` topic.
|
|
62
|
+
*/
|
|
63
|
+
export interface RobotStatusContributors {
|
|
64
|
+
health: RobotStatusContributor<RobotHealth>[];
|
|
65
|
+
readiness: RobotStatusContributor<RobotReadiness>[];
|
|
66
|
+
}
|
|
@@ -30,13 +30,22 @@ export declare enum RobotReadiness {
|
|
|
30
30
|
BUSY = "busy"
|
|
31
31
|
}
|
|
32
32
|
/**
|
|
33
|
-
* Whether the robot is connected to a single cloud service
|
|
34
|
-
* service's `connections` endpoint
|
|
35
|
-
*
|
|
33
|
+
* Whether the robot is connected to a single cloud service — reported by that
|
|
34
|
+
* service's `connections` endpoint, derived from heartbeat freshness (for
|
|
35
|
+
* `telemetry-liveness`), or supplied by a caller-injected signal
|
|
36
|
+
* (`RobotStatusOptions.extraServices`), the only source that can report {@link FAILED}.
|
|
36
37
|
*/
|
|
37
38
|
export declare enum ServiceConnection {
|
|
38
39
|
CONNECTED = "connected",
|
|
39
40
|
DISCONNECTED = "disconnected",
|
|
41
|
+
/**
|
|
42
|
+
* The service reports an explicit fault — a stronger signal than {@link DISCONNECTED} (which just
|
|
43
|
+
* means "not connected", as expected of a robot that is simply off). A failed virtual-robot
|
|
44
|
+
* deployment is the motivating case. Any `FAILED` service forces the aggregate to
|
|
45
|
+
* {@link RobotConnectivity.DEGRADED}, so a fault raises attention even when nothing else is
|
|
46
|
+
* connected — see `aggregateConnectivity`.
|
|
47
|
+
*/
|
|
48
|
+
FAILED = "failed",
|
|
40
49
|
/** The service's connectivity has not yet been determined (e.g. not polled). */
|
|
41
50
|
UNKNOWN = "unknown"
|
|
42
51
|
}
|
|
@@ -45,10 +54,13 @@ export declare enum ServiceConnection {
|
|
|
45
54
|
* expected services.
|
|
46
55
|
*/
|
|
47
56
|
export declare enum RobotConnectivity {
|
|
48
|
-
/** At least one expected service is connected and none is disconnected. */
|
|
57
|
+
/** At least one expected service is connected and none is disconnected or failed. */
|
|
49
58
|
ONLINE = "online",
|
|
50
|
-
/**
|
|
59
|
+
/**
|
|
60
|
+
* Any service reports a fault ({@link ServiceConnection.FAILED}), or a genuine mix — some
|
|
61
|
+
* connected, some disconnected. A `FAILED` service forces this even when nothing is connected.
|
|
62
|
+
*/
|
|
51
63
|
DEGRADED = "degraded",
|
|
52
|
-
/**
|
|
64
|
+
/** Nothing connected, and nothing failed. */
|
|
53
65
|
OFFLINE = "offline"
|
|
54
66
|
}
|
|
@@ -35,14 +35,23 @@ var RobotReadiness;
|
|
|
35
35
|
RobotReadiness["BUSY"] = "busy";
|
|
36
36
|
})(RobotReadiness || (exports.RobotReadiness = RobotReadiness = {}));
|
|
37
37
|
/**
|
|
38
|
-
* Whether the robot is connected to a single cloud service
|
|
39
|
-
* service's `connections` endpoint
|
|
40
|
-
*
|
|
38
|
+
* Whether the robot is connected to a single cloud service — reported by that
|
|
39
|
+
* service's `connections` endpoint, derived from heartbeat freshness (for
|
|
40
|
+
* `telemetry-liveness`), or supplied by a caller-injected signal
|
|
41
|
+
* (`RobotStatusOptions.extraServices`), the only source that can report {@link FAILED}.
|
|
41
42
|
*/
|
|
42
43
|
var ServiceConnection;
|
|
43
44
|
(function (ServiceConnection) {
|
|
44
45
|
ServiceConnection["CONNECTED"] = "connected";
|
|
45
46
|
ServiceConnection["DISCONNECTED"] = "disconnected";
|
|
47
|
+
/**
|
|
48
|
+
* The service reports an explicit fault — a stronger signal than {@link DISCONNECTED} (which just
|
|
49
|
+
* means "not connected", as expected of a robot that is simply off). A failed virtual-robot
|
|
50
|
+
* deployment is the motivating case. Any `FAILED` service forces the aggregate to
|
|
51
|
+
* {@link RobotConnectivity.DEGRADED}, so a fault raises attention even when nothing else is
|
|
52
|
+
* connected — see `aggregateConnectivity`.
|
|
53
|
+
*/
|
|
54
|
+
ServiceConnection["FAILED"] = "failed";
|
|
46
55
|
/** The service's connectivity has not yet been determined (e.g. not polled). */
|
|
47
56
|
ServiceConnection["UNKNOWN"] = "unknown";
|
|
48
57
|
})(ServiceConnection || (exports.ServiceConnection = ServiceConnection = {}));
|
|
@@ -52,10 +61,13 @@ var ServiceConnection;
|
|
|
52
61
|
*/
|
|
53
62
|
var RobotConnectivity;
|
|
54
63
|
(function (RobotConnectivity) {
|
|
55
|
-
/** At least one expected service is connected and none is disconnected. */
|
|
64
|
+
/** At least one expected service is connected and none is disconnected or failed. */
|
|
56
65
|
RobotConnectivity["ONLINE"] = "online";
|
|
57
|
-
/**
|
|
66
|
+
/**
|
|
67
|
+
* Any service reports a fault ({@link ServiceConnection.FAILED}), or a genuine mix — some
|
|
68
|
+
* connected, some disconnected. A `FAILED` service forces this even when nothing is connected.
|
|
69
|
+
*/
|
|
58
70
|
RobotConnectivity["DEGRADED"] = "degraded";
|
|
59
|
-
/**
|
|
71
|
+
/** Nothing connected, and nothing failed. */
|
|
60
72
|
RobotConnectivity["OFFLINE"] = "offline";
|
|
61
73
|
})(RobotConnectivity || (exports.RobotConnectivity = RobotConnectivity = {}));
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { IBaseService, IRocosSDKConfig, RobotConnectivityStatus, RobotStatus, RocosError } from '../models';
|
|
1
|
+
import { IBaseService, IRocosSDKConfig, RobotConnectivityStatus, RobotStatus, RobotStatusContributors, RocosError, ServiceConnection } from '../models';
|
|
2
2
|
import { Observable } from 'rxjs';
|
|
3
3
|
import { ReportedRobotStatus } from './robot-status/reportedStatus';
|
|
4
4
|
import { BaseServiceAbstract } from './BaseServiceAbstract';
|
|
@@ -10,6 +10,23 @@ export interface RobotStatusOptions {
|
|
|
10
10
|
heartbeatTimeoutMs?: number;
|
|
11
11
|
/** How often the connections endpoints are polled (ms, floored internally). */
|
|
12
12
|
connectivityPollMs?: number;
|
|
13
|
+
/**
|
|
14
|
+
* Extra per-robot connectivity signals, keyed by service id, folded into
|
|
15
|
+
* {@link RobotConnectivityStatus.services} and the aggregate on equal footing with the built-in
|
|
16
|
+
* services. Lets a caller inject a signal the SDK can't derive itself (e.g. virtual-robot
|
|
17
|
+
* deployment status); emit {@link ServiceConnection.FAILED} to surface a fault as `DEGRADED`.
|
|
18
|
+
*
|
|
19
|
+
* A key that collides with a built-in service id (`telemetry-liveness`, `robot-configs`,
|
|
20
|
+
* `robot-services`) is ignored in favour of the built-in.
|
|
21
|
+
*
|
|
22
|
+
* An error from an injected observable is contained as {@link ServiceConnection.UNKNOWN} so it
|
|
23
|
+
* can't tear down the overall status stream — but it is *terminal* for that signal: unlike the
|
|
24
|
+
* built-in HTTP poller (which recovers on its next tick), the errored observable is not
|
|
25
|
+
* resubscribed, so the service stays `UNKNOWN` for the life of the stream (and `getConnectivity`
|
|
26
|
+
* then waits out its timeout). To report a recoverable fault, emit `FAILED` / `UNKNOWN` as a
|
|
27
|
+
* value rather than letting the observable error.
|
|
28
|
+
*/
|
|
29
|
+
extraServices?: Record<string, Observable<ServiceConnection>>;
|
|
13
30
|
}
|
|
14
31
|
/**
|
|
15
32
|
* The single source of truth for a robot's status. It composes the reported
|
|
@@ -22,6 +39,8 @@ export declare class RobotStatusService extends BaseServiceAbstract implements I
|
|
|
22
39
|
getStatus(): boolean;
|
|
23
40
|
/** Health + readiness as reported by the robot (new topic, legacy heartbeat fallback). */
|
|
24
41
|
getReportedStatusChanges(projectId: string, callsign: string): Observable<ReportedRobotStatus>;
|
|
42
|
+
/** Per-contributor breakdown behind the reported health and readiness, with the reason for each. */
|
|
43
|
+
getStatusContributorsChanges(projectId: string, callsign: string): Observable<RobotStatusContributors>;
|
|
25
44
|
/** Per-service connectivity plus the aggregate verdict, polled. */
|
|
26
45
|
getConnectivityChanges(projectId: string, callsign: string, opts?: RobotStatusOptions): Observable<RobotConnectivityStatus>;
|
|
27
46
|
/** One-shot connectivity snapshot: resolves once every expected service is known, or after a timeout. */
|
|
@@ -42,6 +42,10 @@ class RobotStatusService extends BaseServiceAbstract_1.BaseServiceAbstract {
|
|
|
42
42
|
getReportedStatusChanges(projectId, callsign) {
|
|
43
43
|
return (0, reportedStatus_1.getReportedStatusChanges)(this.telemetryService, projectId, callsign);
|
|
44
44
|
}
|
|
45
|
+
/** Per-contributor breakdown behind the reported health and readiness, with the reason for each. */
|
|
46
|
+
getStatusContributorsChanges(projectId, callsign) {
|
|
47
|
+
return (0, reportedStatus_1.getStatusContributorsChanges)(this.telemetryService, projectId, callsign);
|
|
48
|
+
}
|
|
45
49
|
/** Per-service connectivity plus the aggregate verdict, polled. */
|
|
46
50
|
getConnectivityChanges(projectId, callsign, opts) {
|
|
47
51
|
return (0, connectivity_1.getConnectivityChanges)(this.connectivityDeps(projectId, callsign, opts), projectId, callsign, opts?.expectedServices, opts?.connectivityPollMs);
|
|
@@ -88,6 +92,7 @@ class RobotStatusService extends BaseServiceAbstract_1.BaseServiceAbstract {
|
|
|
88
92
|
baseUrl: this.config.url,
|
|
89
93
|
insecure: this.config.insecure,
|
|
90
94
|
telemetryLiveness$: (0, reportedStatus_1.getTelemetryLiveness)(this.telemetryService, projectId, callsign, opts?.heartbeatTimeoutMs),
|
|
95
|
+
extraServices: opts?.extraServices,
|
|
91
96
|
};
|
|
92
97
|
}
|
|
93
98
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.SearchService = void 0;
|
|
4
|
-
const models_1 = require("../models");
|
|
5
4
|
const rxjs_1 = require("rxjs");
|
|
6
5
|
const BaseStreamService_1 = require("./BaseStreamService");
|
|
7
6
|
const identifier_1 = require("../constants/identifier");
|
|
@@ -47,12 +46,8 @@ class SearchService extends BaseStreamService_1.BaseStreamService {
|
|
|
47
46
|
port: this.config.port,
|
|
48
47
|
insecure: this.config.insecure,
|
|
49
48
|
});
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
this.status = msg === models_1.SubscriberStatusEnum.STOPPED || msg === models_1.SubscriberStatusEnum.ALIVE;
|
|
53
|
-
this.status$.next(msg);
|
|
54
|
-
});
|
|
55
|
-
}
|
|
49
|
+
// The status forwarder is wired once per stream by BaseStreamService.acquireStream's factory
|
|
50
|
+
// (and released now that the stream completes statusStream$ on stop), so no per-service one here.
|
|
56
51
|
return newStream.stream;
|
|
57
52
|
}
|
|
58
53
|
getStream(config) {
|
|
@@ -3,7 +3,6 @@ import { CallsignStatus, IRocosSDKConfig, IRocosTelemetryMessage, IStreamStatusM
|
|
|
3
3
|
import { BaseStreamService } from './BaseStreamService';
|
|
4
4
|
export declare class TelemetryService extends BaseStreamService<ITelemetryStream, ITelemetryStreamConfig> {
|
|
5
5
|
private static grpcWebDeprecationWarned;
|
|
6
|
-
private statusSubscription;
|
|
7
6
|
private changeStream$;
|
|
8
7
|
private streamStatusSubject$;
|
|
9
8
|
constructor(config: IRocosSDKConfig);
|
|
@@ -29,6 +28,10 @@ export declare class TelemetryService extends BaseStreamService<ITelemetryStream
|
|
|
29
28
|
getStatusChanges(): Observable<IStreamStatusMessage>;
|
|
30
29
|
/**
|
|
31
30
|
* Subscribes to the heartbeat telemetry and checks we receive one at least every 5s
|
|
31
|
+
*
|
|
32
|
+
* @deprecated Superseded by {@link RobotStatusService.getRobotStatusChanges}, which reports the
|
|
33
|
+
* normalised robot status (reported health/readiness plus platform connectivity) rather than only
|
|
34
|
+
* heartbeat-derived online/offline liveness.
|
|
32
35
|
* @param projectId
|
|
33
36
|
* @param callsign
|
|
34
37
|
* @param heartbeatTimeoutMs optionally override the deadline for heartbeats (in ms). Default 5000ms
|
|
@@ -67,7 +67,9 @@ class TelemetryService extends BaseStreamService_1.BaseStreamService {
|
|
|
67
67
|
const { stream, isNew, ready, release } = this.acquireStream(identifier_1.IDENTIFIER_NAME_TELEMETRY, this.buildStreamConfig(params.projectId, callsignsLookup, sources, scope));
|
|
68
68
|
try {
|
|
69
69
|
stream.addSubscription(subscriptionParams);
|
|
70
|
-
|
|
70
|
+
// Forward the stream's status onto the per-scope subject once, at creation; it ends when the
|
|
71
|
+
// stream stops (statusStream$ completes), so it is not re-created or leaked across subscribers.
|
|
72
|
+
if (isNew) {
|
|
71
73
|
stream.statusStream$.subscribe((msg) => {
|
|
72
74
|
this.streamStatusSubject$.next({
|
|
73
75
|
scope,
|
|
@@ -81,20 +83,19 @@ class TelemetryService extends BaseStreamService_1.BaseStreamService {
|
|
|
81
83
|
release();
|
|
82
84
|
throw e;
|
|
83
85
|
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
this.statusSubscription = stream.statusStream$.subscribe((msg) => {
|
|
87
|
-
this.status = msg === models_1.SubscriberStatusEnum.STOPPED || msg === models_1.SubscriberStatusEnum.ALIVE;
|
|
88
|
-
this.status$.next(msg);
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
-
return (0, rxjs_1.from)(stream.messageStream$);
|
|
92
|
-
});
|
|
86
|
+
// status$ / getStatus() are driven by the acquireStream factory (BaseStreamService), not here.
|
|
87
|
+
const messages$ = ready.then(() => (0, rxjs_1.from)(stream.messageStream$));
|
|
93
88
|
return (0, rxjs_1.from)(messages$).pipe((0, rxjs_1.mergeAll)(), (0, operators_1.filter)((message) => {
|
|
94
89
|
return callsignsLookup.includesCallsign(message.callsign) && sources.includes(message.source);
|
|
95
90
|
}), (0, operators_1.finalize)(() => {
|
|
96
|
-
|
|
97
|
-
|
|
91
|
+
if (params.terminateReceiverGroupOnUnsubscribe) {
|
|
92
|
+
const removal = stream.removeSubscription(subscriptionParams, true);
|
|
93
|
+
void Promise.resolve(removal).finally(release);
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
release();
|
|
97
|
+
void stream.removeSubscription(subscriptionParams);
|
|
98
|
+
}
|
|
98
99
|
}));
|
|
99
100
|
});
|
|
100
101
|
}
|
|
@@ -116,6 +117,10 @@ class TelemetryService extends BaseStreamService_1.BaseStreamService {
|
|
|
116
117
|
}
|
|
117
118
|
/**
|
|
118
119
|
* Subscribes to the heartbeat telemetry and checks we receive one at least every 5s
|
|
120
|
+
*
|
|
121
|
+
* @deprecated Superseded by {@link RobotStatusService.getRobotStatusChanges}, which reports the
|
|
122
|
+
* normalised robot status (reported health/readiness plus platform connectivity) rather than only
|
|
123
|
+
* heartbeat-derived online/offline liveness.
|
|
119
124
|
* @param projectId
|
|
120
125
|
* @param callsign
|
|
121
126
|
* @param heartbeatTimeoutMs optionally override the deadline for heartbeats (in ms). Default 5000ms
|