@dronedeploy/rocos-js-sdk 4.4.1 → 4.4.3
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 +9 -3
- package/cjs/models/ConfigGroupResponse.d.ts +20 -0
- package/cjs/models/ConfigGroupResponse.js +2 -0
- package/cjs/models/command/CommandV2.d.ts +28 -0
- package/cjs/models/command/CommandV2.js +2 -0
- package/cjs/models/command/index.d.ts +1 -0
- package/cjs/models/command/index.js +1 -0
- package/cjs/models/gamepad/GamepadConfig.d.ts +3 -16
- package/cjs/models/index.d.ts +1 -0
- package/cjs/models/index.js +1 -0
- package/cjs/services/BaseStreamService.d.ts +1 -1
- package/cjs/services/BaseStreamService.js +21 -2
- package/cjs/services/ProfileService.d.ts +3 -2
- package/cjs/services/ProfileService.js +0 -4
- package/cjs/services/RobotService.d.ts +3 -3
- package/cjs/services/RobotService.js +0 -4
- package/cjs/services/TelemetryService.js +21 -14
- package/esm/api/StreamRegister.js +9 -3
- package/esm/models/ConfigGroupResponse.d.ts +20 -0
- package/esm/models/ConfigGroupResponse.js +1 -0
- package/esm/models/command/CommandV2.d.ts +28 -0
- package/esm/models/command/CommandV2.js +1 -0
- package/esm/models/command/index.d.ts +1 -0
- package/esm/models/command/index.js +1 -0
- package/esm/models/gamepad/GamepadConfig.d.ts +3 -16
- package/esm/models/index.d.ts +1 -0
- package/esm/models/index.js +1 -0
- package/esm/services/BaseStreamService.d.ts +1 -1
- package/esm/services/BaseStreamService.js +21 -2
- package/esm/services/ProfileService.d.ts +3 -2
- package/esm/services/ProfileService.js +0 -4
- package/esm/services/RobotService.d.ts +3 -3
- package/esm/services/RobotService.js +0 -4
- package/esm/services/TelemetryService.js +22 -15
- package/package.json +1 -1
|
@@ -19,8 +19,10 @@ class StreamRegister {
|
|
|
19
19
|
}
|
|
20
20
|
addStream(stream) {
|
|
21
21
|
try {
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
// Stop any existing occupant of this key before overwriting it.
|
|
23
|
+
const existing = this.teleStreams.get(stream.identifier);
|
|
24
|
+
if (existing && existing !== stream) {
|
|
25
|
+
existing.stopStream();
|
|
24
26
|
}
|
|
25
27
|
this.teleStreams.set(stream.identifier, stream);
|
|
26
28
|
}
|
|
@@ -40,7 +42,11 @@ class StreamRegister {
|
|
|
40
42
|
removeStream(stream) {
|
|
41
43
|
try {
|
|
42
44
|
stream.stopStream();
|
|
43
|
-
this
|
|
45
|
+
// Only clear the entry if this instance still owns the key: a replacement may already hold it,
|
|
46
|
+
// and evicting that would leave subscribers forking duplicate streams for the scope.
|
|
47
|
+
if (this.teleStreams.get(stream.identifier) === stream) {
|
|
48
|
+
this.teleStreams.delete(stream.identifier);
|
|
49
|
+
}
|
|
44
50
|
}
|
|
45
51
|
catch (e) {
|
|
46
52
|
this.logger.error(`Failed to remove stream from list. identifier: ${stream.identifier}`, e);
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/** One stored item in a config group, scoped by whatever the group's `type` represents (e.g. one gamepad, one command). */
|
|
2
|
+
export interface ConfigGroupItem<T> {
|
|
3
|
+
id: string;
|
|
4
|
+
value: T;
|
|
5
|
+
/** Robot-scoped response only: whether this robot's config overrides its robot definition's. */
|
|
6
|
+
_isOverridden?: boolean;
|
|
7
|
+
/** Robot-scoped response only: the robot definition's item this one overrides, when it does. */
|
|
8
|
+
_globalItem?: ConfigGroupItem<T>;
|
|
9
|
+
}
|
|
10
|
+
/** Response shape shared by RobotService/ProfileService config-group endpoints (gamepads, commands-v2, ...). */
|
|
11
|
+
export interface ConfigGroupResponse<T> {
|
|
12
|
+
version?: string;
|
|
13
|
+
type: string;
|
|
14
|
+
items: Array<ConfigGroupItem<T>>;
|
|
15
|
+
/** Robot-scoped response only. */
|
|
16
|
+
robotDefinitionId?: string;
|
|
17
|
+
username?: string;
|
|
18
|
+
createdAt?: string;
|
|
19
|
+
updatedAt?: string;
|
|
20
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { ConfigGroupItem, ConfigGroupResponse } from '../ConfigGroupResponse';
|
|
2
|
+
/** Declares one named parameter a command accepts. `default` is always a string, whatever `type` names. */
|
|
3
|
+
export interface CommandV2ParameterDeclaration {
|
|
4
|
+
type: string;
|
|
5
|
+
default: string;
|
|
6
|
+
}
|
|
7
|
+
export interface CommandV2Settings {
|
|
8
|
+
target: string;
|
|
9
|
+
/** Stored as a number on newer configs, a numeric string on older ones. Absent on some legacy configs. */
|
|
10
|
+
timeoutMs?: number | string;
|
|
11
|
+
requiresClockSync: boolean;
|
|
12
|
+
payloadTemplate: string;
|
|
13
|
+
/** Should be an object, but a known editor round-trip bug can persist it as a raw JSON string. */
|
|
14
|
+
payloadMeta?: Record<string, unknown> | string | null;
|
|
15
|
+
}
|
|
16
|
+
export interface CommandV2 {
|
|
17
|
+
schemaVersion: number;
|
|
18
|
+
id: string;
|
|
19
|
+
name: string;
|
|
20
|
+
type: string;
|
|
21
|
+
/** Absent for commands declaring no parameters, not an empty object. */
|
|
22
|
+
parameters?: Record<string, CommandV2ParameterDeclaration>;
|
|
23
|
+
settings: CommandV2Settings;
|
|
24
|
+
}
|
|
25
|
+
/** One stored command, scoped to a specific command id (CommandV2.id). */
|
|
26
|
+
export type CommandV2Item = ConfigGroupItem<CommandV2>;
|
|
27
|
+
/** Response shape of RobotService/ProfileService getCommandsV2 — one item per declared command. */
|
|
28
|
+
export type CommandV2Response = ConfigGroupResponse<CommandV2>;
|
|
@@ -15,6 +15,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
17
|
__exportStar(require("../ResponseLevelEnum"), exports);
|
|
18
|
+
__exportStar(require("./CommandV2"), exports);
|
|
18
19
|
__exportStar(require("./IRocosCommandMessageHeartbeat"), exports);
|
|
19
20
|
__exportStar(require("./IRocosCommandMessageResponse"), exports);
|
|
20
21
|
__exportStar(require("./RocosCommandResultStatus"), exports);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ConfigGroupItem, ConfigGroupResponse } from '../ConfigGroupResponse';
|
|
1
2
|
export declare enum GamepadButtonType {
|
|
2
3
|
UNKNOWN = 0,
|
|
3
4
|
UP = 1,
|
|
@@ -81,20 +82,6 @@ export interface GamepadConfig {
|
|
|
81
82
|
axisExpos?: GamepadAxisValueMap;
|
|
82
83
|
}
|
|
83
84
|
/** One stored gamepad config, scoped to a specific physical controller model (GamepadConfig.gamepad). */
|
|
84
|
-
export
|
|
85
|
-
id: string;
|
|
86
|
-
value: GamepadConfig;
|
|
87
|
-
/** Robot-scoped response only: whether this robot's config overrides its robot definition's. */
|
|
88
|
-
_isOverridden?: boolean;
|
|
89
|
-
}
|
|
85
|
+
export type GamepadConfigItem = ConfigGroupItem<GamepadConfig>;
|
|
90
86
|
/** Response shape of RobotService/ProfileService getGamepads — one item per registered controller model. */
|
|
91
|
-
export
|
|
92
|
-
version?: string;
|
|
93
|
-
type: string;
|
|
94
|
-
items: GamepadConfigItem[];
|
|
95
|
-
/** Robot-scoped response only. */
|
|
96
|
-
robotDefinitionId?: string;
|
|
97
|
-
username?: string;
|
|
98
|
-
createdAt?: string;
|
|
99
|
-
updatedAt?: string;
|
|
100
|
-
}
|
|
87
|
+
export type GamepadConfigResponse = ConfigGroupResponse<GamepadConfig>;
|
package/cjs/models/index.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ export * from './callsigns/CallsignsLookup';
|
|
|
6
6
|
export * from './callsigns/CallsignsQuery';
|
|
7
7
|
export * from './callsigns/CallsignsQueryPredicate';
|
|
8
8
|
export * from './command';
|
|
9
|
+
export * from './ConfigGroupResponse';
|
|
9
10
|
export * from './device-credentials/DeviceCredentials';
|
|
10
11
|
export * from './Dashboard';
|
|
11
12
|
export * from './ExportDataQuery';
|
package/cjs/models/index.js
CHANGED
|
@@ -22,6 +22,7 @@ __exportStar(require("./callsigns/CallsignsLookup"), exports);
|
|
|
22
22
|
__exportStar(require("./callsigns/CallsignsQuery"), exports);
|
|
23
23
|
__exportStar(require("./callsigns/CallsignsQueryPredicate"), exports);
|
|
24
24
|
__exportStar(require("./command"), exports);
|
|
25
|
+
__exportStar(require("./ConfigGroupResponse"), exports);
|
|
25
26
|
__exportStar(require("./device-credentials/DeviceCredentials"), exports);
|
|
26
27
|
__exportStar(require("./Dashboard"), exports);
|
|
27
28
|
__exportStar(require("./ExportDataQuery"), exports);
|
|
@@ -10,7 +10,7 @@ export declare abstract class BaseStreamService<T extends IBaseStream, C extends
|
|
|
10
10
|
protected constructor(name: string, config: IRocosSDKConfig);
|
|
11
11
|
getStatus(): boolean;
|
|
12
12
|
protected initStream(stream: T): Promise<void>;
|
|
13
|
-
protected createStreamFromConfig(identifier: string, config: C): Promise<{
|
|
13
|
+
protected createStreamFromConfig(identifier: string, config: C, onAcquire?: (stream: T) => void): Promise<{
|
|
14
14
|
stream: T;
|
|
15
15
|
isNew: boolean;
|
|
16
16
|
}>;
|
|
@@ -32,7 +32,7 @@ class BaseStreamService {
|
|
|
32
32
|
}
|
|
33
33
|
authService.startTokenRefreshChecker();
|
|
34
34
|
}
|
|
35
|
-
async createStreamFromConfig(identifier, config) {
|
|
35
|
+
async createStreamFromConfig(identifier, config, onAcquire) {
|
|
36
36
|
const identifierWithScope = StreamRegister_1.StreamRegister.getIdentifier(identifier, config.scope);
|
|
37
37
|
const streamRegister = StreamRegister_1.StreamRegister.getInstance();
|
|
38
38
|
let stream = streamRegister.getStream(identifierWithScope);
|
|
@@ -45,7 +45,26 @@ class BaseStreamService {
|
|
|
45
45
|
this.status$.next(msg);
|
|
46
46
|
});
|
|
47
47
|
streamRegister.addStream(stream);
|
|
48
|
-
|
|
48
|
+
}
|
|
49
|
+
// Acquire synchronously with the lookup/registration above, before any await. A stream is
|
|
50
|
+
// stopped and deregistered the instant its refcount hits zero, so a deferred acquire can land on
|
|
51
|
+
// a dead instance — including a freshly-registered stream torn down during the init await below.
|
|
52
|
+
onAcquire?.(stream);
|
|
53
|
+
if (isNew) {
|
|
54
|
+
try {
|
|
55
|
+
await this.initStream(stream);
|
|
56
|
+
}
|
|
57
|
+
catch (e) {
|
|
58
|
+
// Evict eagerly, before the error reaches the subscriber, so a synchronous resubscribe
|
|
59
|
+
// can't re-acquire this uninitialised instance.
|
|
60
|
+
streamRegister.removeStream(stream);
|
|
61
|
+
throw e;
|
|
62
|
+
}
|
|
63
|
+
// If a concurrent teardown deregistered this stream during init, init() has opened a receiver
|
|
64
|
+
// on an instance nothing can reach; close it.
|
|
65
|
+
if (streamRegister.getStream(identifierWithScope) !== stream) {
|
|
66
|
+
stream.stopStream();
|
|
67
|
+
}
|
|
49
68
|
}
|
|
50
69
|
return { stream, isNew };
|
|
51
70
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { AgentSettingsRequestItem, AgentSettingsResponse } from '../models/IRobotSettings';
|
|
2
|
+
import { CommandV2Item, CommandV2Response } from '../models/command/CommandV2';
|
|
2
3
|
import { CreateRobotDefinitionRequest, RobotProfile } from '../models/RobotProfile';
|
|
3
4
|
import { GamepadConfigItem, GamepadConfigResponse } from '../models/gamepad/GamepadConfig';
|
|
4
5
|
import { RocosError } from '../models/RocosError';
|
|
@@ -168,7 +169,7 @@ export declare class ProfileService extends BaseServiceAbstract implements IBase
|
|
|
168
169
|
* @param definitionId - Robot definition Id
|
|
169
170
|
* @description - Renamed from robotDefGetCommandsV2
|
|
170
171
|
*/
|
|
171
|
-
getCommandsV2(projectId: string, definitionId: string): Promise<
|
|
172
|
+
getCommandsV2(projectId: string, definitionId: string): Promise<CommandV2Response>;
|
|
172
173
|
/**
|
|
173
174
|
* Update command V2 for a robot definition
|
|
174
175
|
*
|
|
@@ -177,7 +178,7 @@ export declare class ProfileService extends BaseServiceAbstract implements IBase
|
|
|
177
178
|
* @param model - Payload
|
|
178
179
|
* @description - Renamed from robotDefUpdateCommandsV2
|
|
179
180
|
*/
|
|
180
|
-
updateCommandsV2(projectId: string, definitionId: string, model:
|
|
181
|
+
updateCommandsV2(projectId: string, definitionId: string, model: CommandV2Item[]): Promise<void>;
|
|
181
182
|
/**
|
|
182
183
|
* Get command actions for a robot definition
|
|
183
184
|
*
|
|
@@ -238,7 +238,6 @@ class ProfileService extends BaseServiceAbstract_1.BaseServiceAbstract {
|
|
|
238
238
|
* @param definitionId - Robot definition Id
|
|
239
239
|
* @description - Renamed from robotDefGetCommandsV2
|
|
240
240
|
*/
|
|
241
|
-
// TODO: map the response object
|
|
242
241
|
async getCommandsV2(projectId, definitionId) {
|
|
243
242
|
return this.callGet((0, formatServiceUrl_1.formatServiceUrl)(api_1.API_PROJECT_DEFINITION_COMMAND2_URL, { url: this.config.url, projectId, definitionId }, this.config.insecure), `Failed to get command list for ${projectId}, definitionId ${definitionId}.`);
|
|
244
243
|
}
|
|
@@ -250,9 +249,6 @@ class ProfileService extends BaseServiceAbstract_1.BaseServiceAbstract {
|
|
|
250
249
|
* @param model - Payload
|
|
251
250
|
* @description - Renamed from robotDefUpdateCommandsV2
|
|
252
251
|
*/
|
|
253
|
-
// TODO: map the request model
|
|
254
|
-
// TODO: map the response object
|
|
255
|
-
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
|
256
252
|
async updateCommandsV2(projectId, definitionId, model) {
|
|
257
253
|
return this.callPost((0, formatServiceUrl_1.formatServiceUrl)(api_1.API_PROJECT_DEFINITION_COMMAND2_URL, { url: this.config.url, projectId, definitionId }, this.config.insecure), model, `Failed to update command list for ${projectId}, definitionId ${definitionId}.`);
|
|
258
254
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AgentSettingsResponse, GamepadConfigItem, GamepadConfigResponse, IBaseService, IConnectedCallsign, IRobot, IRobotListItem, IRobotSettings, IRobotTemplate, IRocosSDKConfig, RocosError } from '../models';
|
|
1
|
+
import { AgentSettingsResponse, CommandV2Item, CommandV2Response, GamepadConfigItem, GamepadConfigResponse, IBaseService, IConnectedCallsign, IRobot, IRobotListItem, IRobotSettings, IRobotTemplate, IRocosSDKConfig, RocosError } from '../models';
|
|
2
2
|
import { BaseServiceAbstract } from './BaseServiceAbstract';
|
|
3
3
|
export declare class RobotService extends BaseServiceAbstract implements IBaseService {
|
|
4
4
|
constructor(config: IRocosSDKConfig);
|
|
@@ -227,7 +227,7 @@ export declare class RobotService extends BaseServiceAbstract implements IBaseSe
|
|
|
227
227
|
* @param projectId - Project Id
|
|
228
228
|
* @param callsign - Robot callsign
|
|
229
229
|
*/
|
|
230
|
-
getCommandsV2(projectId: string, callsign: string): Promise<
|
|
230
|
+
getCommandsV2(projectId: string, callsign: string): Promise<CommandV2Response>;
|
|
231
231
|
/**
|
|
232
232
|
* Update command V2 for a robot
|
|
233
233
|
*
|
|
@@ -235,7 +235,7 @@ export declare class RobotService extends BaseServiceAbstract implements IBaseSe
|
|
|
235
235
|
* @param callsign - Robot callsign
|
|
236
236
|
* @param model - Payload
|
|
237
237
|
*/
|
|
238
|
-
updateCommandsV2(projectId: string, callsign: string, model:
|
|
238
|
+
updateCommandsV2(projectId: string, callsign: string, model: CommandV2Item[]): Promise<void>;
|
|
239
239
|
/**
|
|
240
240
|
* Get buttons for a robot
|
|
241
241
|
*
|
|
@@ -380,7 +380,6 @@ class RobotService extends BaseServiceAbstract_1.BaseServiceAbstract {
|
|
|
380
380
|
* @param projectId - Project Id
|
|
381
381
|
* @param callsign - Robot callsign
|
|
382
382
|
*/
|
|
383
|
-
// TODO: map the response object
|
|
384
383
|
async getCommandsV2(projectId, callsign) {
|
|
385
384
|
return this.callGet((0, formatServiceUrl_1.formatServiceUrl)(api_1.API_PROJECT_ROBOT_COMMAND2_URL, { url: this.config.url, projectId, callsign }, this.config.insecure), `Failed to get command list for ${projectId}, callsign ${callsign}.`);
|
|
386
385
|
}
|
|
@@ -391,9 +390,6 @@ class RobotService extends BaseServiceAbstract_1.BaseServiceAbstract {
|
|
|
391
390
|
* @param callsign - Robot callsign
|
|
392
391
|
* @param model - Payload
|
|
393
392
|
*/
|
|
394
|
-
// TODO: map the request model
|
|
395
|
-
// TODO: map the response object
|
|
396
|
-
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
|
397
393
|
async updateCommandsV2(projectId, callsign, model) {
|
|
398
394
|
return this.callPost((0, formatServiceUrl_1.formatServiceUrl)(api_1.API_PROJECT_ROBOT_COMMAND2_URL, { url: this.config.url, projectId, callsign }, this.config.insecure), model, `Failed to update commands for ${projectId}, callsign ${callsign}.`);
|
|
399
395
|
}
|
|
@@ -61,19 +61,26 @@ class TelemetryService extends BaseStreamService_1.BaseStreamService {
|
|
|
61
61
|
callsigns: callsignsLookup?.lookupType === models_1.CallsignsLookupType.List ? callsignsLookup.lookupValue : [],
|
|
62
62
|
sources,
|
|
63
63
|
};
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
}
|
|
72
|
-
|
|
64
|
+
// `defer` acquires the stream only when subscribed; `finalize` releases it on unsubscribe, error
|
|
65
|
+
// and complete — so acquire and release stay balanced on every path.
|
|
66
|
+
return (0, rxjs_1.defer)(() => {
|
|
67
|
+
let acquired;
|
|
68
|
+
const stream$ = this.createStream(params.projectId, callsignsLookup, sources, scope, (stream) => {
|
|
69
|
+
acquired = stream;
|
|
70
|
+
stream.addSubscription(subscriptionParams);
|
|
71
|
+
}).then((stream) => {
|
|
72
|
+
if (!this.statusSubscription) {
|
|
73
|
+
this.statusSubscription = stream.statusStream$.subscribe((msg) => {
|
|
74
|
+
this.status = msg === models_1.SubscriberStatusEnum.STOPPED || msg === models_1.SubscriberStatusEnum.ALIVE;
|
|
75
|
+
this.status$.next(msg);
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
return (0, rxjs_1.from)(stream.messageStream$);
|
|
79
|
+
});
|
|
80
|
+
return (0, rxjs_1.from)(stream$).pipe((0, rxjs_1.mergeAll)(), (0, operators_1.filter)((message) => {
|
|
81
|
+
return callsigns.includes(message.callsign) && sources.includes(message.source);
|
|
82
|
+
}), (0, operators_1.finalize)(() => acquired?.removeSubscription(subscriptionParams, params.terminateReceiverGroupOnUnsubscribe)));
|
|
73
83
|
});
|
|
74
|
-
return (0, rxjs_1.from)(stream).pipe((0, rxjs_1.mergeAll)(), (0, operators_1.filter)((message) => {
|
|
75
|
-
return callsigns.includes(message.callsign) && sources.includes(message.source);
|
|
76
|
-
}));
|
|
77
84
|
}
|
|
78
85
|
/**
|
|
79
86
|
* A method to keep track of the current subscriptions
|
|
@@ -152,7 +159,7 @@ class TelemetryService extends BaseStreamService_1.BaseStreamService {
|
|
|
152
159
|
return `${params.projectId}-default-${(0, flattenCallsignsLookup_1.flattenCallsignsLookup)(callsignsLookup).join()}`;
|
|
153
160
|
return `${params.projectId}-default`;
|
|
154
161
|
}
|
|
155
|
-
async createStream(projectId, callsignsLookup, sources, scope) {
|
|
162
|
+
async createStream(projectId, callsignsLookup, sources, scope, onAcquire) {
|
|
156
163
|
const newStream = await this.createStreamFromConfig(identifier_1.IDENTIFIER_NAME_TELEMETRY, {
|
|
157
164
|
url: this.config.url,
|
|
158
165
|
projectId,
|
|
@@ -165,7 +172,7 @@ class TelemetryService extends BaseStreamService_1.BaseStreamService {
|
|
|
165
172
|
insecure: this.config.insecure,
|
|
166
173
|
transport: this.config.transport,
|
|
167
174
|
fetch: this.config.fetch,
|
|
168
|
-
});
|
|
175
|
+
}, onAcquire);
|
|
169
176
|
if (!newStream.isNew) {
|
|
170
177
|
newStream.stream.statusStream$.subscribe((msg) => {
|
|
171
178
|
this.streamStatusSubject$.next({
|
|
@@ -16,8 +16,10 @@ export class StreamRegister {
|
|
|
16
16
|
}
|
|
17
17
|
addStream(stream) {
|
|
18
18
|
try {
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
// Stop any existing occupant of this key before overwriting it.
|
|
20
|
+
const existing = this.teleStreams.get(stream.identifier);
|
|
21
|
+
if (existing && existing !== stream) {
|
|
22
|
+
existing.stopStream();
|
|
21
23
|
}
|
|
22
24
|
this.teleStreams.set(stream.identifier, stream);
|
|
23
25
|
}
|
|
@@ -37,7 +39,11 @@ export class StreamRegister {
|
|
|
37
39
|
removeStream(stream) {
|
|
38
40
|
try {
|
|
39
41
|
stream.stopStream();
|
|
40
|
-
this
|
|
42
|
+
// Only clear the entry if this instance still owns the key: a replacement may already hold it,
|
|
43
|
+
// and evicting that would leave subscribers forking duplicate streams for the scope.
|
|
44
|
+
if (this.teleStreams.get(stream.identifier) === stream) {
|
|
45
|
+
this.teleStreams.delete(stream.identifier);
|
|
46
|
+
}
|
|
41
47
|
}
|
|
42
48
|
catch (e) {
|
|
43
49
|
this.logger.error(`Failed to remove stream from list. identifier: ${stream.identifier}`, e);
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/** One stored item in a config group, scoped by whatever the group's `type` represents (e.g. one gamepad, one command). */
|
|
2
|
+
export interface ConfigGroupItem<T> {
|
|
3
|
+
id: string;
|
|
4
|
+
value: T;
|
|
5
|
+
/** Robot-scoped response only: whether this robot's config overrides its robot definition's. */
|
|
6
|
+
_isOverridden?: boolean;
|
|
7
|
+
/** Robot-scoped response only: the robot definition's item this one overrides, when it does. */
|
|
8
|
+
_globalItem?: ConfigGroupItem<T>;
|
|
9
|
+
}
|
|
10
|
+
/** Response shape shared by RobotService/ProfileService config-group endpoints (gamepads, commands-v2, ...). */
|
|
11
|
+
export interface ConfigGroupResponse<T> {
|
|
12
|
+
version?: string;
|
|
13
|
+
type: string;
|
|
14
|
+
items: Array<ConfigGroupItem<T>>;
|
|
15
|
+
/** Robot-scoped response only. */
|
|
16
|
+
robotDefinitionId?: string;
|
|
17
|
+
username?: string;
|
|
18
|
+
createdAt?: string;
|
|
19
|
+
updatedAt?: string;
|
|
20
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { ConfigGroupItem, ConfigGroupResponse } from '../ConfigGroupResponse';
|
|
2
|
+
/** Declares one named parameter a command accepts. `default` is always a string, whatever `type` names. */
|
|
3
|
+
export interface CommandV2ParameterDeclaration {
|
|
4
|
+
type: string;
|
|
5
|
+
default: string;
|
|
6
|
+
}
|
|
7
|
+
export interface CommandV2Settings {
|
|
8
|
+
target: string;
|
|
9
|
+
/** Stored as a number on newer configs, a numeric string on older ones. Absent on some legacy configs. */
|
|
10
|
+
timeoutMs?: number | string;
|
|
11
|
+
requiresClockSync: boolean;
|
|
12
|
+
payloadTemplate: string;
|
|
13
|
+
/** Should be an object, but a known editor round-trip bug can persist it as a raw JSON string. */
|
|
14
|
+
payloadMeta?: Record<string, unknown> | string | null;
|
|
15
|
+
}
|
|
16
|
+
export interface CommandV2 {
|
|
17
|
+
schemaVersion: number;
|
|
18
|
+
id: string;
|
|
19
|
+
name: string;
|
|
20
|
+
type: string;
|
|
21
|
+
/** Absent for commands declaring no parameters, not an empty object. */
|
|
22
|
+
parameters?: Record<string, CommandV2ParameterDeclaration>;
|
|
23
|
+
settings: CommandV2Settings;
|
|
24
|
+
}
|
|
25
|
+
/** One stored command, scoped to a specific command id (CommandV2.id). */
|
|
26
|
+
export type CommandV2Item = ConfigGroupItem<CommandV2>;
|
|
27
|
+
/** Response shape of RobotService/ProfileService getCommandsV2 — one item per declared command. */
|
|
28
|
+
export type CommandV2Response = ConfigGroupResponse<CommandV2>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ConfigGroupItem, ConfigGroupResponse } from '../ConfigGroupResponse';
|
|
1
2
|
export declare enum GamepadButtonType {
|
|
2
3
|
UNKNOWN = 0,
|
|
3
4
|
UP = 1,
|
|
@@ -81,20 +82,6 @@ export interface GamepadConfig {
|
|
|
81
82
|
axisExpos?: GamepadAxisValueMap;
|
|
82
83
|
}
|
|
83
84
|
/** One stored gamepad config, scoped to a specific physical controller model (GamepadConfig.gamepad). */
|
|
84
|
-
export
|
|
85
|
-
id: string;
|
|
86
|
-
value: GamepadConfig;
|
|
87
|
-
/** Robot-scoped response only: whether this robot's config overrides its robot definition's. */
|
|
88
|
-
_isOverridden?: boolean;
|
|
89
|
-
}
|
|
85
|
+
export type GamepadConfigItem = ConfigGroupItem<GamepadConfig>;
|
|
90
86
|
/** Response shape of RobotService/ProfileService getGamepads — one item per registered controller model. */
|
|
91
|
-
export
|
|
92
|
-
version?: string;
|
|
93
|
-
type: string;
|
|
94
|
-
items: GamepadConfigItem[];
|
|
95
|
-
/** Robot-scoped response only. */
|
|
96
|
-
robotDefinitionId?: string;
|
|
97
|
-
username?: string;
|
|
98
|
-
createdAt?: string;
|
|
99
|
-
updatedAt?: string;
|
|
100
|
-
}
|
|
87
|
+
export type GamepadConfigResponse = ConfigGroupResponse<GamepadConfig>;
|
package/esm/models/index.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ export * from './callsigns/CallsignsLookup';
|
|
|
6
6
|
export * from './callsigns/CallsignsQuery';
|
|
7
7
|
export * from './callsigns/CallsignsQueryPredicate';
|
|
8
8
|
export * from './command';
|
|
9
|
+
export * from './ConfigGroupResponse';
|
|
9
10
|
export * from './device-credentials/DeviceCredentials';
|
|
10
11
|
export * from './Dashboard';
|
|
11
12
|
export * from './ExportDataQuery';
|
package/esm/models/index.js
CHANGED
|
@@ -6,6 +6,7 @@ export * from './callsigns/CallsignsLookup';
|
|
|
6
6
|
export * from './callsigns/CallsignsQuery';
|
|
7
7
|
export * from './callsigns/CallsignsQueryPredicate';
|
|
8
8
|
export * from './command';
|
|
9
|
+
export * from './ConfigGroupResponse';
|
|
9
10
|
export * from './device-credentials/DeviceCredentials';
|
|
10
11
|
export * from './Dashboard';
|
|
11
12
|
export * from './ExportDataQuery';
|
|
@@ -10,7 +10,7 @@ export declare abstract class BaseStreamService<T extends IBaseStream, C extends
|
|
|
10
10
|
protected constructor(name: string, config: IRocosSDKConfig);
|
|
11
11
|
getStatus(): boolean;
|
|
12
12
|
protected initStream(stream: T): Promise<void>;
|
|
13
|
-
protected createStreamFromConfig(identifier: string, config: C): Promise<{
|
|
13
|
+
protected createStreamFromConfig(identifier: string, config: C, onAcquire?: (stream: T) => void): Promise<{
|
|
14
14
|
stream: T;
|
|
15
15
|
isNew: boolean;
|
|
16
16
|
}>;
|
|
@@ -29,7 +29,7 @@ export class BaseStreamService {
|
|
|
29
29
|
}
|
|
30
30
|
authService.startTokenRefreshChecker();
|
|
31
31
|
}
|
|
32
|
-
async createStreamFromConfig(identifier, config) {
|
|
32
|
+
async createStreamFromConfig(identifier, config, onAcquire) {
|
|
33
33
|
const identifierWithScope = StreamRegister.getIdentifier(identifier, config.scope);
|
|
34
34
|
const streamRegister = StreamRegister.getInstance();
|
|
35
35
|
let stream = streamRegister.getStream(identifierWithScope);
|
|
@@ -42,7 +42,26 @@ export class BaseStreamService {
|
|
|
42
42
|
this.status$.next(msg);
|
|
43
43
|
});
|
|
44
44
|
streamRegister.addStream(stream);
|
|
45
|
-
|
|
45
|
+
}
|
|
46
|
+
// Acquire synchronously with the lookup/registration above, before any await. A stream is
|
|
47
|
+
// stopped and deregistered the instant its refcount hits zero, so a deferred acquire can land on
|
|
48
|
+
// a dead instance — including a freshly-registered stream torn down during the init await below.
|
|
49
|
+
onAcquire?.(stream);
|
|
50
|
+
if (isNew) {
|
|
51
|
+
try {
|
|
52
|
+
await this.initStream(stream);
|
|
53
|
+
}
|
|
54
|
+
catch (e) {
|
|
55
|
+
// Evict eagerly, before the error reaches the subscriber, so a synchronous resubscribe
|
|
56
|
+
// can't re-acquire this uninitialised instance.
|
|
57
|
+
streamRegister.removeStream(stream);
|
|
58
|
+
throw e;
|
|
59
|
+
}
|
|
60
|
+
// If a concurrent teardown deregistered this stream during init, init() has opened a receiver
|
|
61
|
+
// on an instance nothing can reach; close it.
|
|
62
|
+
if (streamRegister.getStream(identifierWithScope) !== stream) {
|
|
63
|
+
stream.stopStream();
|
|
64
|
+
}
|
|
46
65
|
}
|
|
47
66
|
return { stream, isNew };
|
|
48
67
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { AgentSettingsRequestItem, AgentSettingsResponse } from '../models/IRobotSettings';
|
|
2
|
+
import { CommandV2Item, CommandV2Response } from '../models/command/CommandV2';
|
|
2
3
|
import { CreateRobotDefinitionRequest, RobotProfile } from '../models/RobotProfile';
|
|
3
4
|
import { GamepadConfigItem, GamepadConfigResponse } from '../models/gamepad/GamepadConfig';
|
|
4
5
|
import { RocosError } from '../models/RocosError';
|
|
@@ -168,7 +169,7 @@ export declare class ProfileService extends BaseServiceAbstract implements IBase
|
|
|
168
169
|
* @param definitionId - Robot definition Id
|
|
169
170
|
* @description - Renamed from robotDefGetCommandsV2
|
|
170
171
|
*/
|
|
171
|
-
getCommandsV2(projectId: string, definitionId: string): Promise<
|
|
172
|
+
getCommandsV2(projectId: string, definitionId: string): Promise<CommandV2Response>;
|
|
172
173
|
/**
|
|
173
174
|
* Update command V2 for a robot definition
|
|
174
175
|
*
|
|
@@ -177,7 +178,7 @@ export declare class ProfileService extends BaseServiceAbstract implements IBase
|
|
|
177
178
|
* @param model - Payload
|
|
178
179
|
* @description - Renamed from robotDefUpdateCommandsV2
|
|
179
180
|
*/
|
|
180
|
-
updateCommandsV2(projectId: string, definitionId: string, model:
|
|
181
|
+
updateCommandsV2(projectId: string, definitionId: string, model: CommandV2Item[]): Promise<void>;
|
|
181
182
|
/**
|
|
182
183
|
* Get command actions for a robot definition
|
|
183
184
|
*
|
|
@@ -235,7 +235,6 @@ export class ProfileService extends BaseServiceAbstract {
|
|
|
235
235
|
* @param definitionId - Robot definition Id
|
|
236
236
|
* @description - Renamed from robotDefGetCommandsV2
|
|
237
237
|
*/
|
|
238
|
-
// TODO: map the response object
|
|
239
238
|
async getCommandsV2(projectId, definitionId) {
|
|
240
239
|
return this.callGet(formatServiceUrl(API_PROJECT_DEFINITION_COMMAND2_URL, { url: this.config.url, projectId, definitionId }, this.config.insecure), `Failed to get command list for ${projectId}, definitionId ${definitionId}.`);
|
|
241
240
|
}
|
|
@@ -247,9 +246,6 @@ export class ProfileService extends BaseServiceAbstract {
|
|
|
247
246
|
* @param model - Payload
|
|
248
247
|
* @description - Renamed from robotDefUpdateCommandsV2
|
|
249
248
|
*/
|
|
250
|
-
// TODO: map the request model
|
|
251
|
-
// TODO: map the response object
|
|
252
|
-
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
|
253
249
|
async updateCommandsV2(projectId, definitionId, model) {
|
|
254
250
|
return this.callPost(formatServiceUrl(API_PROJECT_DEFINITION_COMMAND2_URL, { url: this.config.url, projectId, definitionId }, this.config.insecure), model, `Failed to update command list for ${projectId}, definitionId ${definitionId}.`);
|
|
255
251
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AgentSettingsResponse, GamepadConfigItem, GamepadConfigResponse, IBaseService, IConnectedCallsign, IRobot, IRobotListItem, IRobotSettings, IRobotTemplate, IRocosSDKConfig, RocosError } from '../models';
|
|
1
|
+
import { AgentSettingsResponse, CommandV2Item, CommandV2Response, GamepadConfigItem, GamepadConfigResponse, IBaseService, IConnectedCallsign, IRobot, IRobotListItem, IRobotSettings, IRobotTemplate, IRocosSDKConfig, RocosError } from '../models';
|
|
2
2
|
import { BaseServiceAbstract } from './BaseServiceAbstract';
|
|
3
3
|
export declare class RobotService extends BaseServiceAbstract implements IBaseService {
|
|
4
4
|
constructor(config: IRocosSDKConfig);
|
|
@@ -227,7 +227,7 @@ export declare class RobotService extends BaseServiceAbstract implements IBaseSe
|
|
|
227
227
|
* @param projectId - Project Id
|
|
228
228
|
* @param callsign - Robot callsign
|
|
229
229
|
*/
|
|
230
|
-
getCommandsV2(projectId: string, callsign: string): Promise<
|
|
230
|
+
getCommandsV2(projectId: string, callsign: string): Promise<CommandV2Response>;
|
|
231
231
|
/**
|
|
232
232
|
* Update command V2 for a robot
|
|
233
233
|
*
|
|
@@ -235,7 +235,7 @@ export declare class RobotService extends BaseServiceAbstract implements IBaseSe
|
|
|
235
235
|
* @param callsign - Robot callsign
|
|
236
236
|
* @param model - Payload
|
|
237
237
|
*/
|
|
238
|
-
updateCommandsV2(projectId: string, callsign: string, model:
|
|
238
|
+
updateCommandsV2(projectId: string, callsign: string, model: CommandV2Item[]): Promise<void>;
|
|
239
239
|
/**
|
|
240
240
|
* Get buttons for a robot
|
|
241
241
|
*
|
|
@@ -377,7 +377,6 @@ export class RobotService extends BaseServiceAbstract {
|
|
|
377
377
|
* @param projectId - Project Id
|
|
378
378
|
* @param callsign - Robot callsign
|
|
379
379
|
*/
|
|
380
|
-
// TODO: map the response object
|
|
381
380
|
async getCommandsV2(projectId, callsign) {
|
|
382
381
|
return this.callGet(formatServiceUrl(API_PROJECT_ROBOT_COMMAND2_URL, { url: this.config.url, projectId, callsign }, this.config.insecure), `Failed to get command list for ${projectId}, callsign ${callsign}.`);
|
|
383
382
|
}
|
|
@@ -388,9 +387,6 @@ export class RobotService extends BaseServiceAbstract {
|
|
|
388
387
|
* @param callsign - Robot callsign
|
|
389
388
|
* @param model - Payload
|
|
390
389
|
*/
|
|
391
|
-
// TODO: map the request model
|
|
392
|
-
// TODO: map the response object
|
|
393
|
-
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
|
394
390
|
async updateCommandsV2(projectId, callsign, model) {
|
|
395
391
|
return this.callPost(formatServiceUrl(API_PROJECT_ROBOT_COMMAND2_URL, { url: this.config.url, projectId, callsign }, this.config.insecure), model, `Failed to update commands for ${projectId}, callsign ${callsign}.`);
|
|
396
392
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BehaviorSubject, catchError, combineLatest, distinctUntilChanged, from, interval, map, mergeAll, of, startWith, } from 'rxjs';
|
|
1
|
+
import { BehaviorSubject, catchError, combineLatest, defer, distinctUntilChanged, from, interval, map, mergeAll, of, startWith, } from 'rxjs';
|
|
2
2
|
import { CallsignStatus, CallsignsLookup, CallsignsLookupType, RocosError, SubscriberStatusEnum, TelemetryMonitorStatus, errorCodes, } from '../models';
|
|
3
3
|
import { filter, finalize } from 'rxjs/operators';
|
|
4
4
|
import { BaseStreamService } from './BaseStreamService';
|
|
@@ -58,19 +58,26 @@ export class TelemetryService extends BaseStreamService {
|
|
|
58
58
|
callsigns: callsignsLookup?.lookupType === CallsignsLookupType.List ? callsignsLookup.lookupValue : [],
|
|
59
59
|
sources,
|
|
60
60
|
};
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
}
|
|
69
|
-
|
|
61
|
+
// `defer` acquires the stream only when subscribed; `finalize` releases it on unsubscribe, error
|
|
62
|
+
// and complete — so acquire and release stay balanced on every path.
|
|
63
|
+
return defer(() => {
|
|
64
|
+
let acquired;
|
|
65
|
+
const stream$ = this.createStream(params.projectId, callsignsLookup, sources, scope, (stream) => {
|
|
66
|
+
acquired = stream;
|
|
67
|
+
stream.addSubscription(subscriptionParams);
|
|
68
|
+
}).then((stream) => {
|
|
69
|
+
if (!this.statusSubscription) {
|
|
70
|
+
this.statusSubscription = stream.statusStream$.subscribe((msg) => {
|
|
71
|
+
this.status = msg === SubscriberStatusEnum.STOPPED || msg === SubscriberStatusEnum.ALIVE;
|
|
72
|
+
this.status$.next(msg);
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
return from(stream.messageStream$);
|
|
76
|
+
});
|
|
77
|
+
return from(stream$).pipe(mergeAll(), filter((message) => {
|
|
78
|
+
return callsigns.includes(message.callsign) && sources.includes(message.source);
|
|
79
|
+
}), finalize(() => acquired?.removeSubscription(subscriptionParams, params.terminateReceiverGroupOnUnsubscribe)));
|
|
70
80
|
});
|
|
71
|
-
return from(stream).pipe(mergeAll(), filter((message) => {
|
|
72
|
-
return callsigns.includes(message.callsign) && sources.includes(message.source);
|
|
73
|
-
}));
|
|
74
81
|
}
|
|
75
82
|
/**
|
|
76
83
|
* A method to keep track of the current subscriptions
|
|
@@ -149,7 +156,7 @@ export class TelemetryService extends BaseStreamService {
|
|
|
149
156
|
return `${params.projectId}-default-${flattenCallsignsLookup(callsignsLookup).join()}`;
|
|
150
157
|
return `${params.projectId}-default`;
|
|
151
158
|
}
|
|
152
|
-
async createStream(projectId, callsignsLookup, sources, scope) {
|
|
159
|
+
async createStream(projectId, callsignsLookup, sources, scope, onAcquire) {
|
|
153
160
|
const newStream = await this.createStreamFromConfig(IDENTIFIER_NAME_TELEMETRY, {
|
|
154
161
|
url: this.config.url,
|
|
155
162
|
projectId,
|
|
@@ -162,7 +169,7 @@ export class TelemetryService extends BaseStreamService {
|
|
|
162
169
|
insecure: this.config.insecure,
|
|
163
170
|
transport: this.config.transport,
|
|
164
171
|
fetch: this.config.fetch,
|
|
165
|
-
});
|
|
172
|
+
}, onAcquire);
|
|
166
173
|
if (!newStream.isNew) {
|
|
167
174
|
newStream.stream.statusStream$.subscribe((msg) => {
|
|
168
175
|
this.streamStatusSubject$.next({
|