@blueyerobotics/blueye-ts 3.9.0 → 3.11.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/binlog-parser.d.ts +14 -0
- package/dist/src/binlog-parser.js +22 -1
- package/dist/src/client.d.ts +16 -3
- package/dist/src/client.js +148 -28
- 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();
|
|
@@ -35,4 +35,18 @@ export declare const parseMessages: (decompressed: Uint8Array, fixTimes?: boolea
|
|
|
35
35
|
* @param messages The messages to fix the times for.
|
|
36
36
|
* @returns The messages with corrected times.
|
|
37
37
|
*/
|
|
38
|
+
/**
|
|
39
|
+
* Parse a single varint-prefixed BinlogRecord from raw (uncompressed) bytes.
|
|
40
|
+
* Used for streaming playback where individual frames are fetched via HTTP Range requests.
|
|
41
|
+
* @param bytes Raw bytes containing a single varint-length-prefixed BinlogRecord.
|
|
42
|
+
* @returns The parsed message, or null if the record is not a known protocol type.
|
|
43
|
+
*/
|
|
44
|
+
export declare const parseFrame: (bytes: Uint8Array) => Message | null;
|
|
45
|
+
/**
|
|
46
|
+
* Parse multiple contiguous varint-prefixed BinlogRecords from raw (uncompressed) bytes.
|
|
47
|
+
* Used for streaming playback where frame batches are fetched via HTTP Range requests.
|
|
48
|
+
* @param bytes Raw bytes containing one or more varint-length-prefixed BinlogRecords.
|
|
49
|
+
* @returns An array of parsed messages.
|
|
50
|
+
*/
|
|
51
|
+
export declare const parseFrames: (bytes: Uint8Array) => Message[];
|
|
38
52
|
export declare const fixMessageTimes: (messages: Message[]) => Message[];
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.fixMessageTimes = exports.parseMessages = exports.decompress = exports.parse = void 0;
|
|
3
|
+
exports.fixMessageTimes = exports.parseFrames = exports.parseFrame = exports.parseMessages = exports.decompress = exports.parse = void 0;
|
|
4
4
|
const protocol_definitions_1 = require("@blueyerobotics/protocol-definitions");
|
|
5
5
|
const wire_1 = require("@bufbuild/protobuf/wire");
|
|
6
6
|
const buffer_1 = require("buffer");
|
|
@@ -115,6 +115,27 @@ exports.parseMessages = parseMessages;
|
|
|
115
115
|
* @param messages The messages to fix the times for.
|
|
116
116
|
* @returns The messages with corrected times.
|
|
117
117
|
*/
|
|
118
|
+
/**
|
|
119
|
+
* Parse a single varint-prefixed BinlogRecord from raw (uncompressed) bytes.
|
|
120
|
+
* Used for streaming playback where individual frames are fetched via HTTP Range requests.
|
|
121
|
+
* @param bytes Raw bytes containing a single varint-length-prefixed BinlogRecord.
|
|
122
|
+
* @returns The parsed message, or null if the record is not a known protocol type.
|
|
123
|
+
*/
|
|
124
|
+
const parseFrame = (bytes) => {
|
|
125
|
+
const messages = (0, exports.parseFrames)(bytes);
|
|
126
|
+
return messages.length > 0 ? messages[0] : null;
|
|
127
|
+
};
|
|
128
|
+
exports.parseFrame = parseFrame;
|
|
129
|
+
/**
|
|
130
|
+
* Parse multiple contiguous varint-prefixed BinlogRecords from raw (uncompressed) bytes.
|
|
131
|
+
* Used for streaming playback where frame batches are fetched via HTTP Range requests.
|
|
132
|
+
* @param bytes Raw bytes containing one or more varint-length-prefixed BinlogRecords.
|
|
133
|
+
* @returns An array of parsed messages.
|
|
134
|
+
*/
|
|
135
|
+
const parseFrames = (bytes) => {
|
|
136
|
+
return (0, exports.parseMessages)(bytes, false);
|
|
137
|
+
};
|
|
138
|
+
exports.parseFrames = parseFrames;
|
|
118
139
|
const fixMessageTimes = (messages) => {
|
|
119
140
|
if (messages.length === 0)
|
|
120
141
|
return messages;
|
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,25 +28,34 @@ 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>;
|
package/dist/src/client.js
CHANGED
|
@@ -1,102 +1,218 @@
|
|
|
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
|
-
|
|
103
|
+
this.handleTelemetryMessage("sub", topic, msg);
|
|
104
|
+
});
|
|
105
|
+
this.sonarSub.on("message", (topic, msg) => {
|
|
106
|
+
this.handleTelemetryMessage("sonar", topic, msg);
|
|
107
|
+
});
|
|
108
|
+
this.once("DroneInfoTel", (msg) => {
|
|
109
|
+
const version = msg.droneInfo?.blunuxVersion;
|
|
110
|
+
if (!hasSonarEndpoint(version ?? "")) {
|
|
111
|
+
this.logger.warn(`[sonar] incompatible Blunux version detected in DroneInfoTel: ${version}; sonar telemetry may not be available`);
|
|
49
112
|
return;
|
|
50
113
|
}
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
114
|
+
const devices = [
|
|
115
|
+
...(msg.droneInfo?.gp?.gp1?.deviceList?.devices ?? []),
|
|
116
|
+
...(msg.droneInfo?.gp?.gp2?.deviceList?.devices ?? []),
|
|
117
|
+
...(msg.droneInfo?.gp?.gp3?.deviceList?.devices ?? []),
|
|
118
|
+
].map((device) => device.deviceId);
|
|
119
|
+
if (devices.some((deviceId) => exports.MULTIBEAM_DEVICE_IDS.includes(deviceId))) {
|
|
120
|
+
this.logger.info("[sonar] multibeam device detected in DroneInfoTel:", devices);
|
|
121
|
+
this.isSonarDetected = true;
|
|
122
|
+
this.sonarSub.connect(this.sonarUrl);
|
|
123
|
+
}
|
|
55
124
|
});
|
|
56
125
|
if (autoConnect) {
|
|
57
126
|
this.connect();
|
|
58
127
|
}
|
|
59
128
|
}
|
|
60
|
-
|
|
61
|
-
this.
|
|
62
|
-
|
|
63
|
-
this.
|
|
129
|
+
get state() {
|
|
130
|
+
if (!this.shouldBeConnected)
|
|
131
|
+
return "disconnected";
|
|
132
|
+
const { sub, rpc, pub } = this.socketState;
|
|
133
|
+
if (sub === "connected" &&
|
|
134
|
+
rpc === "connected" &&
|
|
135
|
+
pub === "connected" &&
|
|
136
|
+
(this.isSonarDetected ? this.socketState.sonar === "connected" : true)) {
|
|
137
|
+
return "connected";
|
|
138
|
+
}
|
|
139
|
+
return "connecting";
|
|
64
140
|
}
|
|
65
|
-
|
|
66
|
-
if (this.
|
|
67
|
-
|
|
141
|
+
updateSocketState(name, newState) {
|
|
142
|
+
if (this.socketState[name] === newState) {
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
this.socketState[name] = newState;
|
|
146
|
+
this.logger.info(`[${name}] ${newState}`);
|
|
147
|
+
this.emit(`${name}-${newState}`);
|
|
148
|
+
// If all sockets are connected, emit "connected"
|
|
149
|
+
this.emit(this.state);
|
|
150
|
+
}
|
|
151
|
+
handleTelemetryMessage(socketName, topic, msg) {
|
|
152
|
+
const { key, data } = schema_1.responseSchema.parse({ key: topic, data: msg });
|
|
153
|
+
if (!(0, exports.isInProtocol)(key) || !key.endsWith("Tel")) {
|
|
154
|
+
this.logger.warn(`[${socketName}] unknown protocol:`, key);
|
|
68
155
|
return;
|
|
69
156
|
}
|
|
70
|
-
|
|
71
|
-
|
|
157
|
+
const protocol = protocol_definitions_1.blueye.protocol[key];
|
|
158
|
+
const message = protocol.decode(data);
|
|
159
|
+
this.logger.verbose(`[${socketName}] message:`, key, message);
|
|
160
|
+
this.emit(key, message);
|
|
161
|
+
}
|
|
162
|
+
bindSocketLifecycle(name, socket) {
|
|
163
|
+
socket.on("ready", () => {
|
|
164
|
+
if (!this.shouldBeConnected)
|
|
165
|
+
return;
|
|
166
|
+
this.updateSocketState(name, "connected");
|
|
167
|
+
});
|
|
168
|
+
socket.on("lost", () => {
|
|
169
|
+
if (!this.shouldBeConnected)
|
|
170
|
+
return;
|
|
171
|
+
this.updateSocketState(name, "connecting");
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
ensureConnected(operation) {
|
|
175
|
+
if (this.state !== "connected") {
|
|
176
|
+
throw new Error(`[client] cannot send ${operation} while ${this.state}; call connect() and wait for "connected"`);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
connect() {
|
|
180
|
+
if (this.shouldBeConnected) {
|
|
181
|
+
this.logger.warn("[client] already connecting or connected");
|
|
72
182
|
return;
|
|
73
183
|
}
|
|
74
184
|
this.sub.options.reconnectInterval = this.reconnectInterval;
|
|
75
185
|
this.rpc.options.reconnectInterval = this.reconnectInterval;
|
|
76
186
|
this.pub.options.reconnectInterval = this.reconnectInterval;
|
|
77
|
-
this.
|
|
187
|
+
this.sonarSub.options.reconnectInterval = this.reconnectInterval;
|
|
188
|
+
this.shouldBeConnected = true;
|
|
189
|
+
for (const name of ["sub", "rpc", "pub", "sonar"]) {
|
|
190
|
+
this.updateSocketState(name, "connecting");
|
|
191
|
+
}
|
|
78
192
|
this.sub.subscribe("");
|
|
79
193
|
this.sub.connect(this.subUrl);
|
|
80
194
|
this.rpc.connect(this.rpcUrl);
|
|
81
195
|
this.pub.connect(this.pubUrl);
|
|
82
|
-
this.
|
|
196
|
+
this.sonarSub.subscribe("");
|
|
83
197
|
}
|
|
84
198
|
disconnect() {
|
|
85
|
-
if (this.
|
|
199
|
+
if (!this.shouldBeConnected) {
|
|
86
200
|
this.logger.warn("[client] already disconnected");
|
|
87
201
|
return;
|
|
88
202
|
}
|
|
89
|
-
|
|
90
|
-
this.logger.warn("[client] cannot disconnect while connecting");
|
|
91
|
-
return;
|
|
92
|
-
}
|
|
203
|
+
this.shouldBeConnected = false;
|
|
93
204
|
this.sub.unsubscribe("");
|
|
94
205
|
this.sub.disconnect(this.subUrl);
|
|
95
206
|
this.rpc.disconnect(this.rpcUrl);
|
|
96
207
|
this.pub.disconnect(this.pubUrl);
|
|
97
|
-
this.
|
|
208
|
+
this.sonarSub.unsubscribe("");
|
|
209
|
+
this.sonarSub.disconnect(this.sonarUrl);
|
|
210
|
+
for (const name of ["sub", "rpc", "pub", "sonar"]) {
|
|
211
|
+
this.updateSocketState(name, "disconnected");
|
|
212
|
+
}
|
|
98
213
|
}
|
|
99
214
|
async sendRequest(req, opts = {}) {
|
|
215
|
+
this.ensureConnected("request");
|
|
100
216
|
if (!(0, exports.isInProtocol)(req) || !req.endsWith("Req")) {
|
|
101
217
|
throw new Error(`[rpc] unknown protocol: ${req}`);
|
|
102
218
|
}
|
|
@@ -132,6 +248,9 @@ class BlueyeClient extends strict_event_emitter_1.Emitter {
|
|
|
132
248
|
const response = await this.sendRequest("GetTelemetryReq", {
|
|
133
249
|
messageType: type,
|
|
134
250
|
});
|
|
251
|
+
if (!response) {
|
|
252
|
+
throw new Error(`[rpc] no response for telemetry request: ${type}`);
|
|
253
|
+
}
|
|
135
254
|
const { payload } = schema_1.telemetrySchema.parse(response);
|
|
136
255
|
const { typeUrl, value } = payload;
|
|
137
256
|
if (!(0, exports.isInProtocol)(typeUrl) || !typeUrl.endsWith("Tel")) {
|
|
@@ -142,6 +261,7 @@ class BlueyeClient extends strict_event_emitter_1.Emitter {
|
|
|
142
261
|
return result;
|
|
143
262
|
}
|
|
144
263
|
async sendControl(ctrl, opts = {}) {
|
|
264
|
+
this.ensureConnected("control");
|
|
145
265
|
if (!(0, exports.isInProtocol)(ctrl) || !ctrl.endsWith("Ctrl")) {
|
|
146
266
|
throw new Error(`[pub] unknown protocol: ${ctrl}`);
|
|
147
267
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blueyerobotics/blueye-ts",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.11.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 --test-concurrency=1 test/**/*.test.js",
|
|
39
|
+
"test:one": "node --test --test-concurrency=1"
|
|
35
40
|
}
|
|
36
41
|
}
|