@blueyerobotics/blueye-ts 3.10.0 → 3.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -0
- package/dist/example.js +18 -8
- package/dist/sonar-example.d.ts +1 -0
- package/dist/sonar-example.js +41 -0
- package/dist/src/client.d.ts +17 -3
- package/dist/src/client.js +184 -30
- package/package.json +9 -4
package/README.md
CHANGED
|
@@ -35,3 +35,33 @@ client.on("BatteryTel", data => {
|
|
|
35
35
|
|
|
36
36
|
client.connect();
|
|
37
37
|
```
|
|
38
|
+
|
|
39
|
+
## Connection states
|
|
40
|
+
|
|
41
|
+
`BlueyeClient` manages four sockets: `sub`, `rpc`, `pub`, and `sonar`. Global state events (`connecting`, `connected`, `disconnected`) are emitted when the derived state changes. Per-socket events use the `${socket}-${state}` format (e.g. `sonar-connected`, `rpc-connecting`):
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
client.on("connected", () => {
|
|
45
|
+
console.log("all required sockets ready");
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
client.on("sonar-connected", () => {
|
|
49
|
+
console.log("sonar socket ready");
|
|
50
|
+
});
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
The derived `client.state` reflects the aggregate of the core sockets (`sub`, `rpc`, `pub`). If a multibeam sonar is detected via `DroneInfoTel`, the sonar socket is also required for `connected`.
|
|
54
|
+
|
|
55
|
+
- `disconnected`: `connect()` has not been called.
|
|
56
|
+
- `connecting`: one or more required sockets are not yet ready.
|
|
57
|
+
- `connected`: all required sockets are ready — safe to call `sendRequest()`, `getTelemetry()`, and `sendControl()`.
|
|
58
|
+
|
|
59
|
+
`client.sonarState` reflects the sonar socket independently. If the client loses one or more sockets after being connected, the derived state moves back to `connecting`. `sendRequest()` and `sendControl()` reject unless the client is in the `connected` state.
|
|
60
|
+
|
|
61
|
+
## Sonar support
|
|
62
|
+
|
|
63
|
+
`BlueyeClient` connects the sonar websocket endpoint at `ws://192.168.1.101:9988` when a supported multibeam device is detected in a `DroneInfoTel` message.
|
|
64
|
+
|
|
65
|
+
- On `connect()`, the sonar socket subscribes but only connects when a known multibeam device ID is found in the guest-port device list.
|
|
66
|
+
- Once detected, the sonar socket connects and the global `connected` state requires it to be ready.
|
|
67
|
+
- Sonar telemetry such as `MultibeamPingTel`, `MultibeamConfigTel`, and `MultibeamDiscoveryTel` is emitted through the same typed event interface as other telemetry messages.
|
package/dist/example.js
CHANGED
|
@@ -4,14 +4,24 @@ const index_1 = require("./index");
|
|
|
4
4
|
const main = async () => {
|
|
5
5
|
const client = new index_1.BlueyeClient();
|
|
6
6
|
client.on("connected", async () => {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
7
|
+
try {
|
|
8
|
+
// request battery information
|
|
9
|
+
const batteryRep = await client.sendRequest("GetBatteryReq");
|
|
10
|
+
console.log("batteryRep:", batteryRep);
|
|
11
|
+
// get latest battery telemetry
|
|
12
|
+
const batteryTel = await client.getTelemetry("BatteryTel");
|
|
13
|
+
console.log("batteryTel:", batteryTel);
|
|
14
|
+
}
|
|
15
|
+
catch (error) {
|
|
16
|
+
// console.error("Error:", error);
|
|
17
|
+
}
|
|
18
|
+
// send a control message to change the light intensity to 0.1
|
|
19
|
+
console.log("setting light intensity to 0.1 for 1 second...");
|
|
20
|
+
await client.sendControl("LightsCtrl", { lights: { value: 0.1 } });
|
|
21
|
+
setTimeout(async () => {
|
|
22
|
+
console.log("setting light intensity back to 0...");
|
|
23
|
+
await client.sendControl("LightsCtrl", { lights: { value: 0 } });
|
|
24
|
+
}, 1000);
|
|
15
25
|
});
|
|
16
26
|
// subscribe to battery telemetry updates
|
|
17
27
|
client.on("BatteryTel", (data) => {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const index_1 = require("./index");
|
|
4
|
+
const main = async () => {
|
|
5
|
+
const client = new index_1.BlueyeClient();
|
|
6
|
+
client.on("connected", () => {
|
|
7
|
+
console.log("client connected");
|
|
8
|
+
});
|
|
9
|
+
client.on("connecting", () => {
|
|
10
|
+
console.log("client connecting...");
|
|
11
|
+
});
|
|
12
|
+
client.on("disconnected", () => {
|
|
13
|
+
console.log("client disconnected");
|
|
14
|
+
});
|
|
15
|
+
client.on("sonar-connected", () => {
|
|
16
|
+
console.log("sonar connected");
|
|
17
|
+
});
|
|
18
|
+
client.on("sonar-connecting", () => {
|
|
19
|
+
console.log("sonar connecting...");
|
|
20
|
+
});
|
|
21
|
+
client.on("MultibeamDiscoveryTel", (data) => {
|
|
22
|
+
console.log("received MultibeamDiscoveryTel:", data.discovery);
|
|
23
|
+
});
|
|
24
|
+
client.on("MultibeamConfigTel", (data) => {
|
|
25
|
+
console.log("received MultibeamConfigTel:", data.config);
|
|
26
|
+
});
|
|
27
|
+
client.on("MultibeamPingTel", (data) => {
|
|
28
|
+
const ping = data.ping;
|
|
29
|
+
if (!ping) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
console.log("received MultibeamPingTel:", {
|
|
33
|
+
deviceId: ping.deviceId,
|
|
34
|
+
range: ping.range,
|
|
35
|
+
beams: ping.numberOfBeams,
|
|
36
|
+
ranges: ping.numberOfRanges,
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
client.connect();
|
|
40
|
+
};
|
|
41
|
+
main();
|
package/dist/src/client.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { blueye } from "@blueyerobotics/protocol-definitions";
|
|
2
2
|
import { type LogLevel } from "consola";
|
|
3
3
|
import { Emitter } from "strict-event-emitter";
|
|
4
|
+
export declare const MULTIBEAM_DEVICE_IDS: number[];
|
|
4
5
|
export type Protocol = typeof blueye.protocol;
|
|
5
6
|
export type ProtocolType = "Req" | "Rep" | "Tel" | "Ctrl";
|
|
6
7
|
export type ProtocolKey = Extract<keyof Protocol, `${string}${ProtocolType}`>;
|
|
@@ -14,8 +15,11 @@ export type CreateArgs<T extends Req | Ctrl> = Parameters<MsgHandler<T>["create"
|
|
|
14
15
|
export type DecodedOutput<T extends Req> = ReturnType<ReqToRep<T>["decode"]>;
|
|
15
16
|
export type DecodedTelOutput<T extends Tel> = ReturnType<Protocol[T]["decode"]>;
|
|
16
17
|
type State = "connecting" | "connected" | "disconnected";
|
|
18
|
+
export type SocketName = "sub" | "rpc" | "pub" | "sonar";
|
|
17
19
|
export type Events = {
|
|
18
20
|
[K in State]: [];
|
|
21
|
+
} & {
|
|
22
|
+
[K in `${SocketName}-${State}`]: [];
|
|
19
23
|
} & {
|
|
20
24
|
[K in Tel]: [DecodedTelOutput<K>];
|
|
21
25
|
};
|
|
@@ -24,29 +28,39 @@ type Options = Partial<{
|
|
|
24
28
|
subUrl: string;
|
|
25
29
|
rpcUrl: string;
|
|
26
30
|
pubUrl: string;
|
|
31
|
+
sonarUrl: string;
|
|
27
32
|
timeout: number;
|
|
28
33
|
reconnectInterval: number;
|
|
29
34
|
logLevel: LogLevel;
|
|
30
35
|
autoConnect: boolean;
|
|
31
36
|
}>;
|
|
32
37
|
export declare class BlueyeClient extends Emitter<Events> {
|
|
33
|
-
state: State;
|
|
34
38
|
timeout: number;
|
|
35
39
|
reconnectInterval: number;
|
|
36
40
|
private subUrl;
|
|
37
41
|
private rpcUrl;
|
|
38
42
|
private pubUrl;
|
|
43
|
+
private sonarUrl;
|
|
39
44
|
private sub;
|
|
40
45
|
private rpc;
|
|
41
46
|
private pub;
|
|
47
|
+
private sonarSub;
|
|
42
48
|
private queue;
|
|
43
49
|
private logger;
|
|
44
|
-
|
|
45
|
-
private
|
|
50
|
+
private shouldBeConnected;
|
|
51
|
+
private isSonarDetected;
|
|
52
|
+
private socketState;
|
|
53
|
+
constructor({ subUrl, rpcUrl, pubUrl, sonarUrl, timeout, reconnectInterval, logLevel, autoConnect, }?: Options);
|
|
54
|
+
get state(): State;
|
|
55
|
+
private updateSocketState;
|
|
56
|
+
private handleTelemetryMessage;
|
|
57
|
+
private bindSocketLifecycle;
|
|
58
|
+
private ensureConnected;
|
|
46
59
|
connect(): void;
|
|
47
60
|
disconnect(): void;
|
|
48
61
|
sendRequest<T extends Req>(req: T, opts?: CreateArgs<T>): Promise<DecodedOutput<T> | null>;
|
|
49
62
|
getTelemetry<T extends Tel>(type: T): Promise<DecodedTelOutput<T>>;
|
|
63
|
+
waitForTelemetry<T extends Tel>(type: T, timeout?: number | null): Promise<DecodedTelOutput<T>>;
|
|
50
64
|
sendControl<T extends Ctrl>(ctrl: T, opts?: CreateArgs<T>): Promise<void>;
|
|
51
65
|
}
|
|
52
66
|
export {};
|
package/dist/src/client.js
CHANGED
|
@@ -1,102 +1,227 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.BlueyeClient = exports.isInProtocol = void 0;
|
|
36
|
+
exports.BlueyeClient = exports.isInProtocol = exports.MULTIBEAM_DEVICE_IDS = void 0;
|
|
4
37
|
const protocol_definitions_1 = require("@blueyerobotics/protocol-definitions");
|
|
5
38
|
const buffer_1 = require("buffer");
|
|
6
39
|
const consola_1 = require("consola");
|
|
7
|
-
const jszmq_1 = require("jszmq");
|
|
40
|
+
const jszmq_1 = require("@blueyerobotics/jszmq");
|
|
8
41
|
const strict_event_emitter_1 = require("strict-event-emitter");
|
|
9
42
|
const async_queue_1 = require("./async-queue");
|
|
10
43
|
const schema_1 = require("./schema");
|
|
44
|
+
const semver = __importStar(require("semver"));
|
|
11
45
|
const DEFAULT_SUB_URL = "ws://192.168.1.101:9985";
|
|
12
46
|
const DEFAULT_RPC_URL = "ws://192.168.1.101:9986";
|
|
13
47
|
const DEFAULT_PUB_URL = "ws://192.168.1.101:9987";
|
|
48
|
+
const DEFAULT_SONAR_URL = "ws://192.168.1.101:9988";
|
|
49
|
+
exports.MULTIBEAM_DEVICE_IDS = [13, 16, 18, 20, 29, 30, 41, 42];
|
|
14
50
|
const isInProtocol = (key) => {
|
|
15
51
|
return key in protocol_definitions_1.blueye.protocol;
|
|
16
52
|
};
|
|
17
53
|
exports.isInProtocol = isInProtocol;
|
|
54
|
+
const hasSonarEndpoint = (version) => {
|
|
55
|
+
const coercedVersion = semver.coerce(version);
|
|
56
|
+
return coercedVersion
|
|
57
|
+
? semver.satisfies(coercedVersion, ">=4.7.0") || version.endsWith("-dev")
|
|
58
|
+
: false;
|
|
59
|
+
};
|
|
18
60
|
class BlueyeClient extends strict_event_emitter_1.Emitter {
|
|
19
|
-
state = "disconnected";
|
|
20
61
|
timeout;
|
|
21
62
|
reconnectInterval;
|
|
22
63
|
subUrl;
|
|
23
64
|
rpcUrl;
|
|
24
65
|
pubUrl;
|
|
66
|
+
sonarUrl;
|
|
25
67
|
sub;
|
|
26
68
|
rpc;
|
|
27
69
|
pub;
|
|
70
|
+
sonarSub;
|
|
28
71
|
queue;
|
|
29
72
|
logger;
|
|
30
|
-
|
|
73
|
+
shouldBeConnected = false;
|
|
74
|
+
isSonarDetected = false;
|
|
75
|
+
socketState = {
|
|
76
|
+
sub: "disconnected",
|
|
77
|
+
rpc: "disconnected",
|
|
78
|
+
pub: "disconnected",
|
|
79
|
+
sonar: "disconnected",
|
|
80
|
+
};
|
|
81
|
+
constructor({ subUrl = DEFAULT_SUB_URL, rpcUrl = DEFAULT_RPC_URL, pubUrl = DEFAULT_PUB_URL, sonarUrl = DEFAULT_SONAR_URL, timeout = 2000, reconnectInterval = 2000, logLevel = consola_1.LogLevels.info, autoConnect = false, } = {}) {
|
|
31
82
|
super();
|
|
32
83
|
this.timeout = timeout;
|
|
33
84
|
this.reconnectInterval = reconnectInterval;
|
|
34
85
|
this.subUrl = subUrl;
|
|
35
86
|
this.rpcUrl = rpcUrl;
|
|
36
87
|
this.pubUrl = pubUrl;
|
|
88
|
+
this.sonarUrl = sonarUrl;
|
|
37
89
|
this.sub = new jszmq_1.Sub();
|
|
38
90
|
this.rpc = new jszmq_1.Req();
|
|
39
91
|
this.pub = new jszmq_1.Pub();
|
|
92
|
+
this.sonarSub = new jszmq_1.Sub();
|
|
40
93
|
this.queue = new async_queue_1.AsyncQueue();
|
|
41
94
|
this.logger = (0, consola_1.createConsola)({
|
|
42
95
|
level: logLevel,
|
|
43
96
|
formatOptions: { colors: true, compact: false },
|
|
44
97
|
});
|
|
98
|
+
this.bindSocketLifecycle("sub", this.sub);
|
|
99
|
+
this.bindSocketLifecycle("rpc", this.rpc);
|
|
100
|
+
this.bindSocketLifecycle("pub", this.pub);
|
|
101
|
+
this.bindSocketLifecycle("sonar", this.sonarSub);
|
|
45
102
|
this.sub.on("message", (topic, msg) => {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
}
|
|
51
|
-
const protocol = protocol_definitions_1.blueye.protocol[key];
|
|
52
|
-
const message = protocol.decode(data);
|
|
53
|
-
this.logger.verbose("[sub] message:", key, message);
|
|
54
|
-
this.emit(key, message);
|
|
103
|
+
this.handleTelemetryMessage("sub", topic, msg);
|
|
104
|
+
});
|
|
105
|
+
this.sonarSub.on("message", (topic, msg) => {
|
|
106
|
+
this.handleTelemetryMessage("sonar", topic, msg);
|
|
55
107
|
});
|
|
108
|
+
this.emit(this.state);
|
|
109
|
+
this.logger.info(`[client] ${this.state}`);
|
|
56
110
|
if (autoConnect) {
|
|
57
111
|
this.connect();
|
|
58
112
|
}
|
|
59
113
|
}
|
|
60
|
-
|
|
61
|
-
this.
|
|
62
|
-
|
|
63
|
-
this.
|
|
114
|
+
get state() {
|
|
115
|
+
if (!this.shouldBeConnected)
|
|
116
|
+
return "disconnected";
|
|
117
|
+
const { sub, rpc, pub } = this.socketState;
|
|
118
|
+
if (sub === "connected" &&
|
|
119
|
+
rpc === "connected" &&
|
|
120
|
+
pub === "connected" &&
|
|
121
|
+
(this.isSonarDetected ? this.socketState.sonar === "connected" : true)) {
|
|
122
|
+
return "connected";
|
|
123
|
+
}
|
|
124
|
+
return "connecting";
|
|
64
125
|
}
|
|
65
|
-
|
|
66
|
-
if (this.
|
|
67
|
-
this.logger.warn("[client] already connected");
|
|
126
|
+
updateSocketState(name, newState) {
|
|
127
|
+
if (this.socketState[name] === newState) {
|
|
68
128
|
return;
|
|
69
129
|
}
|
|
70
|
-
|
|
71
|
-
|
|
130
|
+
const oldState = this.state;
|
|
131
|
+
this.socketState[name] = newState;
|
|
132
|
+
this.logger.info(`[${name}] ${newState}`);
|
|
133
|
+
this.emit(`${name}-${newState}`);
|
|
134
|
+
// If all sockets are connected, emit "connected"
|
|
135
|
+
this.emit(this.state);
|
|
136
|
+
if (oldState !== this.state) {
|
|
137
|
+
this.logger.info(`[client] ${this.state}`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
handleTelemetryMessage(socketName, topic, msg) {
|
|
141
|
+
const { key, data } = schema_1.responseSchema.parse({ key: topic, data: msg });
|
|
142
|
+
if (!(0, exports.isInProtocol)(key) || !key.endsWith("Tel")) {
|
|
143
|
+
this.logger.warn(`[${socketName}] unknown protocol:`, key);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
const protocol = protocol_definitions_1.blueye.protocol[key];
|
|
147
|
+
const message = protocol.decode(data);
|
|
148
|
+
this.logger.verbose(`[${socketName}] message:`, key, message);
|
|
149
|
+
this.emit(key, message);
|
|
150
|
+
}
|
|
151
|
+
bindSocketLifecycle(name, socket) {
|
|
152
|
+
socket.on("ready", () => {
|
|
153
|
+
if (!this.shouldBeConnected)
|
|
154
|
+
return;
|
|
155
|
+
this.updateSocketState(name, "connected");
|
|
156
|
+
});
|
|
157
|
+
socket.on("lost", () => {
|
|
158
|
+
if (!this.shouldBeConnected)
|
|
159
|
+
return;
|
|
160
|
+
this.updateSocketState(name, "connecting");
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
ensureConnected(operation) {
|
|
164
|
+
if (this.socketState[operation] !== "connected") {
|
|
165
|
+
throw new Error(`[client] cannot send ${operation} while ${this.state}; call connect() and wait for "connected"`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
connect() {
|
|
169
|
+
if (this.shouldBeConnected) {
|
|
170
|
+
this.logger.warn("[client] already connecting or connected");
|
|
72
171
|
return;
|
|
73
172
|
}
|
|
173
|
+
this.once("connected", async () => {
|
|
174
|
+
const msg = await this.waitForTelemetry("DroneInfoTel");
|
|
175
|
+
const version = msg.droneInfo?.blunuxVersion;
|
|
176
|
+
if (!hasSonarEndpoint(version ?? "")) {
|
|
177
|
+
this.logger.warn(`[sonar] incompatible Blunux version detected in DroneInfoTel: ${version}; sonar telemetry may not be available`);
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
const devices = [
|
|
181
|
+
...(msg.droneInfo?.gp?.gp1?.deviceList?.devices ?? []),
|
|
182
|
+
...(msg.droneInfo?.gp?.gp2?.deviceList?.devices ?? []),
|
|
183
|
+
...(msg.droneInfo?.gp?.gp3?.deviceList?.devices ?? []),
|
|
184
|
+
].map((device) => device.deviceId);
|
|
185
|
+
if (devices.some((deviceId) => exports.MULTIBEAM_DEVICE_IDS.includes(deviceId))) {
|
|
186
|
+
this.logger.info("[sonar] multibeam device detected in DroneInfoTel");
|
|
187
|
+
this.isSonarDetected = true;
|
|
188
|
+
this.sonarSub.connect(this.sonarUrl);
|
|
189
|
+
}
|
|
190
|
+
});
|
|
74
191
|
this.sub.options.reconnectInterval = this.reconnectInterval;
|
|
75
192
|
this.rpc.options.reconnectInterval = this.reconnectInterval;
|
|
76
193
|
this.pub.options.reconnectInterval = this.reconnectInterval;
|
|
77
|
-
this.
|
|
194
|
+
this.sonarSub.options.reconnectInterval = this.reconnectInterval;
|
|
195
|
+
this.shouldBeConnected = true;
|
|
196
|
+
for (const name of ["sub", "rpc", "pub"]) {
|
|
197
|
+
this.updateSocketState(name, "connecting");
|
|
198
|
+
}
|
|
199
|
+
this.logger.info(`[client] ${this.state}`);
|
|
200
|
+
this.emit(this.state);
|
|
78
201
|
this.sub.subscribe("");
|
|
79
202
|
this.sub.connect(this.subUrl);
|
|
80
203
|
this.rpc.connect(this.rpcUrl);
|
|
81
204
|
this.pub.connect(this.pubUrl);
|
|
82
|
-
this.
|
|
205
|
+
this.sonarSub.subscribe("");
|
|
83
206
|
}
|
|
84
207
|
disconnect() {
|
|
85
|
-
if (this.
|
|
208
|
+
if (!this.shouldBeConnected) {
|
|
86
209
|
this.logger.warn("[client] already disconnected");
|
|
87
210
|
return;
|
|
88
211
|
}
|
|
89
|
-
|
|
90
|
-
this.logger.warn("[client] cannot disconnect while connecting");
|
|
91
|
-
return;
|
|
92
|
-
}
|
|
212
|
+
this.shouldBeConnected = false;
|
|
93
213
|
this.sub.unsubscribe("");
|
|
94
214
|
this.sub.disconnect(this.subUrl);
|
|
95
215
|
this.rpc.disconnect(this.rpcUrl);
|
|
96
216
|
this.pub.disconnect(this.pubUrl);
|
|
97
|
-
this.
|
|
217
|
+
this.sonarSub.unsubscribe("");
|
|
218
|
+
this.sonarSub.disconnect(this.sonarUrl);
|
|
219
|
+
for (const name of ["sub", "rpc", "pub", "sonar"]) {
|
|
220
|
+
this.updateSocketState(name, "disconnected");
|
|
221
|
+
}
|
|
98
222
|
}
|
|
99
223
|
async sendRequest(req, opts = {}) {
|
|
224
|
+
this.ensureConnected("rpc");
|
|
100
225
|
if (!(0, exports.isInProtocol)(req) || !req.endsWith("Req")) {
|
|
101
226
|
throw new Error(`[rpc] unknown protocol: ${req}`);
|
|
102
227
|
}
|
|
@@ -132,6 +257,9 @@ class BlueyeClient extends strict_event_emitter_1.Emitter {
|
|
|
132
257
|
const response = await this.sendRequest("GetTelemetryReq", {
|
|
133
258
|
messageType: type,
|
|
134
259
|
});
|
|
260
|
+
if (!response) {
|
|
261
|
+
throw new Error(`[rpc] no response for telemetry request: ${type}`);
|
|
262
|
+
}
|
|
135
263
|
const { payload } = schema_1.telemetrySchema.parse(response);
|
|
136
264
|
const { typeUrl, value } = payload;
|
|
137
265
|
if (!(0, exports.isInProtocol)(typeUrl) || !typeUrl.endsWith("Tel")) {
|
|
@@ -141,7 +269,33 @@ class BlueyeClient extends strict_event_emitter_1.Emitter {
|
|
|
141
269
|
this.logger.debug("[rpc] result:", result);
|
|
142
270
|
return result;
|
|
143
271
|
}
|
|
272
|
+
async waitForTelemetry(type, timeout = null) {
|
|
273
|
+
// Tries to get the latest telemetry via RPC first, in case we already have it cached in Blunux
|
|
274
|
+
try {
|
|
275
|
+
return await this.getTelemetry(type);
|
|
276
|
+
}
|
|
277
|
+
catch (error) {
|
|
278
|
+
this.logger.trace(`[client] failed to get latest ${type} via RPC:`, error);
|
|
279
|
+
}
|
|
280
|
+
// If that fails, wait for the next telemetry message to arrive via SUB
|
|
281
|
+
return new Promise((resolve, reject) => {
|
|
282
|
+
const listener = (...data) => {
|
|
283
|
+
this.off(type, listener);
|
|
284
|
+
if (timer)
|
|
285
|
+
clearTimeout(timer);
|
|
286
|
+
resolve(data[0]);
|
|
287
|
+
};
|
|
288
|
+
const timer = timeout
|
|
289
|
+
? setTimeout(() => {
|
|
290
|
+
this.off(type, listener);
|
|
291
|
+
reject(new Error(`[client] timed out waiting for ${type} telemetry`));
|
|
292
|
+
}, timeout)
|
|
293
|
+
: null;
|
|
294
|
+
this.on(type, listener);
|
|
295
|
+
});
|
|
296
|
+
}
|
|
144
297
|
async sendControl(ctrl, opts = {}) {
|
|
298
|
+
this.ensureConnected("pub");
|
|
145
299
|
if (!(0, exports.isInProtocol)(ctrl) || !ctrl.endsWith("Ctrl")) {
|
|
146
300
|
throw new Error(`[pub] unknown protocol: ${ctrl}`);
|
|
147
301
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blueyerobotics/blueye-ts",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.12.0",
|
|
4
4
|
"description": "A TypeScript client for interacting with Blueye underwater drones.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -15,22 +15,27 @@
|
|
|
15
15
|
"author": "Blueye <contact@blueye.no> (https://blueye.no/)",
|
|
16
16
|
"license": "LGPL-3.0-only",
|
|
17
17
|
"dependencies": {
|
|
18
|
+
"@blueyerobotics/jszmq": "^0.2.0",
|
|
18
19
|
"@blueyerobotics/protocol-definitions": "3.2.0-98aafe92",
|
|
19
20
|
"@bufbuild/protobuf": "^2.11.0",
|
|
20
21
|
"buffer": "^6.0.3",
|
|
21
22
|
"consola": "^3.4.2",
|
|
22
23
|
"fflate": "^0.8.2",
|
|
23
|
-
"
|
|
24
|
+
"semver": "^7.7.4",
|
|
24
25
|
"strict-event-emitter": "^0.5.1",
|
|
25
26
|
"zod": "^4.3.6"
|
|
26
27
|
},
|
|
27
28
|
"devDependencies": {
|
|
28
29
|
"@biomejs/biome": "2.3.14",
|
|
29
30
|
"@types/node": "^25.4.0",
|
|
31
|
+
"@types/semver": "^7.7.1",
|
|
30
32
|
"typescript": "^5.9.3"
|
|
31
33
|
},
|
|
32
34
|
"scripts": {
|
|
33
|
-
"start": "node dist/example.js",
|
|
34
|
-
"
|
|
35
|
+
"start": "pnpm build && node dist/example.js",
|
|
36
|
+
"start:sonar": "pnpm build && node dist/sonar-example.js",
|
|
37
|
+
"build": "tsc",
|
|
38
|
+
"test": "pnpm build && node --test --experimental-test-isolation=none --test-concurrency=1 test/**/*.test.js",
|
|
39
|
+
"test:one": "node --test --test-concurrency=1"
|
|
35
40
|
}
|
|
36
41
|
}
|