@blueyerobotics/blueye-ts 4.0.3 → 5.0.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.
@@ -0,0 +1,34 @@
1
+ import * as semver from "semver";
2
+ export const MULTIBEAM_DEVICE_IDS = [13, 16, 18, 20, 29, 30, 41, 42];
3
+ const GUEST_PORTS = ["gp1", "gp2", "gp3"];
4
+ /**
5
+ * The sonar telemetry endpoint exists on Blunux >= 4.7.0; "-dev" builds are
6
+ * assumed to be newer than any release.
7
+ */
8
+ export const hasSonarEndpoint = (version) => {
9
+ const coercedVersion = semver.coerce(version);
10
+ return coercedVersion
11
+ ? semver.satisfies(coercedVersion, ">=4.7.0") || version.endsWith("-dev")
12
+ : false;
13
+ };
14
+ /**
15
+ * Decide from a DroneInfoTel whether a multibeam sonar is connected and
16
+ * reachable: the firmware must expose the sonar endpoint and a known
17
+ * multibeam device ID must be present in one of the guest-port device lists.
18
+ * Pure — no sockets, no side effects.
19
+ */
20
+ export const detectSonar = (msg) => {
21
+ const version = msg.droneInfo?.blunuxVersion;
22
+ if (!hasSonarEndpoint(version ?? "")) {
23
+ return { detected: false, reason: "incompatible-firmware", version };
24
+ }
25
+ for (const port of GUEST_PORTS) {
26
+ const devices = msg.droneInfo?.gp?.[port]?.deviceList?.devices ?? [];
27
+ for (const device of devices) {
28
+ if (MULTIBEAM_DEVICE_IDS.includes(device.deviceId)) {
29
+ return { detected: true, deviceId: device.deviceId };
30
+ }
31
+ }
32
+ }
33
+ return { detected: false, reason: "no-multibeam-device" };
34
+ };
@@ -0,0 +1,31 @@
1
+ export type SocketKind = "sub" | "req" | "pub";
2
+ export type TransportFrame = Uint8Array | string;
3
+ export type TransportEvents = {
4
+ ready: [];
5
+ lost: [];
6
+ message: [topic: Uint8Array<ArrayBuffer>, payload: Uint8Array<ArrayBuffer>];
7
+ };
8
+ export type TransportSocket = {
9
+ connect(url: string): void;
10
+ disconnect(url: string): void;
11
+ close(): void;
12
+ /**
13
+ * Force-drop the live connection(s) without forgetting the endpoints:
14
+ * emits "lost" and lets the transport's normal reconnect machinery try to
15
+ * re-establish. Used to convert silently-dead links into explicit loss.
16
+ */
17
+ dropConnection(): void;
18
+ send(frames: TransportFrame[]): void;
19
+ subscribe(topic: string): void;
20
+ unsubscribe(topic: string): void;
21
+ setReconnectInterval(ms: number): void;
22
+ on<E extends keyof TransportEvents>(event: E, listener: (...args: TransportEvents[E]) => void): void;
23
+ once<E extends keyof TransportEvents>(event: E, listener: (...args: TransportEvents[E]) => void): void;
24
+ off<E extends keyof TransportEvents>(event: E, listener: (...args: TransportEvents[E]) => void): void;
25
+ };
26
+ export type Transport = {
27
+ createSocket(kind: SocketKind): TransportSocket;
28
+ };
29
+ export declare class JszmqTransport implements Transport {
30
+ createSocket(kind: SocketKind): TransportSocket;
31
+ }
@@ -0,0 +1,69 @@
1
+ import { Pub as JszmqPub, Req as JszmqReq, Sub as JszmqSub, } from "@blueyerobotics/jszmq";
2
+ class JszmqSocket {
3
+ socket;
4
+ constructor(socket) {
5
+ this.socket = socket;
6
+ // A jszmq REQ socket is lockstep: once a request is sent it refuses to
7
+ // send another until the reply arrives, and that flag survives connection
8
+ // loss — wedging the socket forever if the server died mid-request.
9
+ // Losing the connection means the reply can no longer arrive, so reset.
10
+ if (socket instanceof JszmqReq) {
11
+ socket.on("lost", () => {
12
+ socket.receivingReply = false;
13
+ });
14
+ }
15
+ }
16
+ connect(url) {
17
+ this.socket.connect(url);
18
+ }
19
+ disconnect(url) {
20
+ this.socket.disconnect(url);
21
+ }
22
+ close() {
23
+ this.socket.close();
24
+ }
25
+ dropConnection() {
26
+ // Close each endpoint's raw WebSocket while leaving the endpoint
27
+ // registered: jszmq treats an unexpected close as a loss — it emits
28
+ // "lost" and schedules its own reconnect (webSocketEndpoint.onClose).
29
+ // endpoint.close()/disconnect(url) would instead terminate the endpoint
30
+ // permanently, which is exactly what we don't want here.
31
+ const { endpoints } = this.socket;
32
+ for (const endpoint of endpoints ?? []) {
33
+ endpoint.socket?.close();
34
+ }
35
+ }
36
+ send(frames) {
37
+ this.socket.send(frames);
38
+ }
39
+ subscribe(topic) {
40
+ this.socket.subscribe(topic);
41
+ }
42
+ unsubscribe(topic) {
43
+ this.socket.unsubscribe(topic);
44
+ }
45
+ setReconnectInterval(ms) {
46
+ this.socket.options.reconnectInterval = ms;
47
+ }
48
+ on(event, listener) {
49
+ this.socket.on(event, listener);
50
+ }
51
+ once(event, listener) {
52
+ this.socket.once(event, listener);
53
+ }
54
+ off(event, listener) {
55
+ this.socket.removeListener(event, listener);
56
+ }
57
+ }
58
+ export class JszmqTransport {
59
+ createSocket(kind) {
60
+ switch (kind) {
61
+ case "sub":
62
+ return new JszmqSocket(new JszmqSub());
63
+ case "req":
64
+ return new JszmqSocket(new JszmqReq());
65
+ case "pub":
66
+ return new JszmqSocket(new JszmqPub());
67
+ }
68
+ }
69
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blueyerobotics/blueye-ts",
3
- "version": "4.0.3",
3
+ "version": "5.0.0",
4
4
  "description": "A TypeScript client for interacting with Blueye underwater drones.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -21,21 +21,20 @@
21
21
  "license": "LGPL-3.0-only",
22
22
  "dependencies": {
23
23
  "@blueyerobotics/jszmq": "^0.3.0",
24
- "@blueyerobotics/protocol-definitions": "3.2.0-2e3e3a62",
25
- "@bufbuild/protobuf": "^2.11.0",
24
+ "@blueyerobotics/protocol-definitions": "3.2.0-d6c9260a",
25
+ "@bufbuild/protobuf": "^2.13.0",
26
26
  "consola": "^3.4.2",
27
- "fflate": "^0.8.2",
28
- "semver": "^7.7.4",
29
- "strict-event-emitter": "^0.5.1",
30
- "zod": "^4.3.6"
27
+ "fflate": "^0.8.3",
28
+ "semver": "^7.8.5",
29
+ "strict-event-emitter": "^0.5.1"
31
30
  },
32
31
  "devDependencies": {
33
- "@biomejs/biome": "2.3.14",
34
- "@types/node": "^25.4.0",
32
+ "@biomejs/biome": "2.5.6",
33
+ "@types/node": "^26.1.2",
35
34
  "@types/semver": "^7.7.1",
36
- "tsx": "^4.20.7",
37
- "typescript": "^5.9.3",
38
- "vitest": "^4.0.5"
35
+ "tsx": "^4.23.1",
36
+ "typescript": "^7.0.2",
37
+ "vitest": "^4.1.10"
39
38
  },
40
39
  "scripts": {
41
40
  "start": "tsx example.ts",
@@ -1,4 +0,0 @@
1
- export declare class AsyncQueue {
2
- private lastPromise;
3
- enqueue<T>(fn: () => Promise<T>): Promise<T>;
4
- }
@@ -1,8 +0,0 @@
1
- export class AsyncQueue {
2
- lastPromise = Promise.resolve();
3
- enqueue(fn) {
4
- const run = this.lastPromise.then(() => fn());
5
- this.lastPromise = run.finally(() => { });
6
- return run;
7
- }
8
- }
@@ -1,11 +0,0 @@
1
- import z from "zod";
2
- export declare const responseSchema: z.ZodObject<{
3
- key: z.ZodPipe<z.ZodCustom<Uint8Array<ArrayBuffer>, Uint8Array<ArrayBuffer>>, z.ZodTransform<string, Uint8Array<ArrayBuffer>>>;
4
- data: z.ZodCustom<Uint8Array<ArrayBuffer>, Uint8Array<ArrayBuffer>>;
5
- }, z.core.$strip>;
6
- export declare const telemetrySchema: z.ZodObject<{
7
- payload: z.ZodObject<{
8
- typeUrl: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
9
- value: z.ZodCustom<Uint8Array<ArrayBuffer>, Uint8Array<ArrayBuffer>>;
10
- }, z.core.$strip>;
11
- }, z.core.$strip>;
@@ -1,14 +0,0 @@
1
- import z from "zod";
2
- const utf8 = new TextDecoder();
3
- export const responseSchema = z.object({
4
- key: z
5
- .instanceof(Uint8Array)
6
- .transform((val) => utf8.decode(val).split(".").at(-1) ?? ""),
7
- data: z.instanceof(Uint8Array),
8
- });
9
- export const telemetrySchema = z.object({
10
- payload: z.object({
11
- typeUrl: z.string().transform((val) => val.split(".").at(-1) ?? ""),
12
- value: z.instanceof(Uint8Array),
13
- }),
14
- });