@blueyerobotics/blueye-ts 1.5.0 → 3.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.
package/README.md CHANGED
@@ -15,13 +15,23 @@ import { BlueyeClient } from "@blueyerobotics/blueye-ts";
15
15
 
16
16
  const client = new BlueyeClient();
17
17
 
18
- const rep = await client.sendRequest("GetBatteryReq");
19
- const tel = await client.getTelemetry("BatteryTel");
18
+ client.on("connected", async () => {
19
+ // request battery information
20
+ const batteryRep = await client.sendRequest("GetBatteryReq");
21
+ console.log("batteryRep:", batteryRep);
20
22
 
21
- console.log("Rep:", rep);
22
- console.log("Tel:", tel);
23
+ // get latest battery telemetry
24
+ const batteryTel = await client.getTelemetry("BatteryTel");
25
+ console.log("batteryTel:", batteryTel);
23
26
 
24
- client.sub.on("BatteryTel", data => {
25
- console.log("Received BatteryTel:", data);
27
+ // send a control message to change the light intensity to 1
28
+ await client.sendControl("LightsCtrl", { lights: { value: 1 } });
26
29
  });
30
+
31
+ // subscribe to battery telemetry updates
32
+ client.on("BatteryTel", data => {
33
+ console.log("received BatteryTel:", data);
34
+ });
35
+
36
+ client.connect();
27
37
  ```
package/dist/example.js CHANGED
@@ -3,12 +3,20 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const index_1 = require("./index");
4
4
  const main = async () => {
5
5
  const client = new index_1.BlueyeClient();
6
- const rep = await client.sendRequest("GetBatteryReq");
7
- const tel = await client.getTelemetry("BatteryTel");
8
- console.log("Rep:", rep);
9
- console.log("Tel:", tel);
10
- client.sub.on("BatteryTel", data => {
11
- console.log("Received BatteryTel:", data);
6
+ client.on("connected", async () => {
7
+ // request battery information
8
+ const batteryRep = await client.sendRequest("GetBatteryReq");
9
+ console.log("batteryRep:", batteryRep);
10
+ // get latest battery telemetry
11
+ const batteryTel = await client.getTelemetry("BatteryTel");
12
+ console.log("batteryTel:", batteryTel);
13
+ // send a control message to change the light intensity to 1
14
+ await client.sendControl("LightsCtrl", { lights: { value: 1 } });
12
15
  });
16
+ // subscribe to battery telemetry updates
17
+ client.on("BatteryTel", data => {
18
+ console.log("received BatteryTel:", data);
19
+ });
20
+ client.connect();
13
21
  };
14
22
  main();
@@ -9,23 +9,41 @@ export type Rep = keyof Pick<Protocol, Extract<ProtocolKey, `${string}Rep`>>;
9
9
  export type Tel = keyof Pick<Protocol, Extract<ProtocolKey, `${string}Tel`>>;
10
10
  export type Ctrl = keyof Pick<Protocol, Extract<ProtocolKey, `${string}Ctrl`>>;
11
11
  export type ReqToRep<T extends Req> = T extends `${infer Prefix}Req` ? `${Prefix}Rep` extends ProtocolKey ? Protocol[`${Prefix}Rep`] : never : never;
12
- export type MsgHandler<T extends Req> = Protocol[T];
13
- export type CreateArgs<T extends Req> = Parameters<MsgHandler<T>["create"]>[0];
12
+ export type MsgHandler<T extends Req | Ctrl> = Protocol[T];
13
+ export type CreateArgs<T extends Req | Ctrl> = Parameters<MsgHandler<T>["create"]>[0];
14
14
  export type DecodedOutput<T extends Req> = ReturnType<ReqToRep<T>["decode"]>;
15
15
  export type DecodedTelOutput<T extends Tel> = ReturnType<Protocol[T]["decode"]>;
16
+ type State = "connecting" | "connected" | "disconnected";
16
17
  export type Events = {
18
+ [K in State]: [];
19
+ } & {
17
20
  [K in Tel]: [DecodedTelOutput<K>];
18
21
  };
19
22
  export declare const isInProtocol: (key: string) => key is keyof typeof blueye.protocol;
20
- export declare class BlueyeClient {
23
+ type Options = Partial<{
24
+ subUrl: string;
25
+ rpcUrl: string;
26
+ pubUrl: string;
21
27
  timeout: number;
22
- private wsPubSub;
23
- private wsReqRep;
24
- private isReqRepConnected;
28
+ logLevel: LogLevel;
29
+ autoConnect: boolean;
30
+ }>;
31
+ export declare class BlueyeClient extends Emitter<Events> {
32
+ state: State;
33
+ timeout: number;
34
+ private subUrl;
35
+ private rpcUrl;
36
+ private pubUrl;
37
+ private sub;
38
+ private rpc;
39
+ private pub;
25
40
  private logger;
26
- private pendingRequests;
27
- sub: Emitter<Events>;
28
- constructor(timeout?: number, logLevel?: LogLevel);
41
+ constructor({ subUrl, rpcUrl, pubUrl, timeout, logLevel, autoConnect }?: Options);
42
+ private updateState;
43
+ connect(): void;
44
+ disconnect(): void;
29
45
  sendRequest<T extends Req>(req: T, opts?: CreateArgs<T>): Promise<DecodedOutput<T> | null>;
30
46
  getTelemetry<T extends Tel>(type: T): Promise<DecodedTelOutput<T>>;
47
+ sendControl<T extends Ctrl>(ctrl: T, opts?: CreateArgs<T>): Promise<void>;
31
48
  }
49
+ export {};
@@ -4,93 +4,136 @@ exports.BlueyeClient = exports.isInProtocol = void 0;
4
4
  const protocol_definitions_1 = require("@blueyerobotics/protocol-definitions");
5
5
  const buffer_1 = require("buffer");
6
6
  const consola_1 = require("consola");
7
+ const jszmq_1 = require("jszmq");
7
8
  const strict_event_emitter_1 = require("strict-event-emitter");
8
- const uuid_1 = require("uuid");
9
9
  const schema_1 = require("./schema");
10
- const WS_PUBSUB_URL = "ws://192.168.1.101:8765";
11
- const WS_REQREP_URL = "ws://192.168.1.101:8766";
10
+ const DEFAULT_SUB_URL = "ws://192.168.1.101:9985";
11
+ const DEFAULT_RPC_URL = "ws://192.168.1.101:9986";
12
+ const DEFAULT_PUB_URL = "ws://192.168.1.101:9987";
12
13
  const isInProtocol = (key) => {
13
14
  return key in protocol_definitions_1.blueye.protocol;
14
15
  };
15
16
  exports.isInProtocol = isInProtocol;
16
- class BlueyeClient {
17
+ class BlueyeClient extends strict_event_emitter_1.Emitter {
18
+ state = "disconnected";
17
19
  timeout;
18
- wsPubSub;
19
- wsReqRep;
20
- isReqRepConnected = false;
20
+ subUrl;
21
+ rpcUrl;
22
+ pubUrl;
23
+ sub;
24
+ rpc;
25
+ pub;
21
26
  logger;
22
- pendingRequests = new Map();
23
- sub = new strict_event_emitter_1.Emitter();
24
- constructor(timeout = 2000, logLevel = consola_1.LogLevels.info) {
27
+ constructor({ subUrl = DEFAULT_SUB_URL, rpcUrl = DEFAULT_RPC_URL, pubUrl = DEFAULT_PUB_URL, timeout = 2000, logLevel = consola_1.LogLevels.info, autoConnect = false } = {}) {
28
+ super();
25
29
  this.timeout = timeout;
26
30
  this.logger = (0, consola_1.createConsola)({ level: logLevel, formatOptions: { colors: true, compact: false } });
27
- this.wsPubSub = new WebSocket(WS_PUBSUB_URL);
28
- this.wsReqRep = new WebSocket(WS_REQREP_URL);
29
- this.wsPubSub.addEventListener("open", () => {
30
- this.logger.info("[WS] PubSub connected");
31
- });
32
- this.wsReqRep.addEventListener("open", () => {
33
- this.isReqRepConnected = true;
34
- this.logger.info("[WS] ReqRep connected");
35
- });
36
- this.wsPubSub.addEventListener("message", event => {
37
- const { key, data } = schema_1.responseSchema.parse(JSON.parse(event.data));
31
+ this.subUrl = subUrl;
32
+ this.rpcUrl = rpcUrl;
33
+ this.pubUrl = pubUrl;
34
+ this.sub = new jszmq_1.Sub();
35
+ this.rpc = new jszmq_1.Req();
36
+ this.pub = new jszmq_1.Pub();
37
+ // @ts-ignore
38
+ this.sub.on("message", (topic, msg) => {
39
+ const { key, data } = schema_1.responseSchema.parse({ key: topic, data: msg });
40
+ if (!(0, exports.isInProtocol)(key) || !key.endsWith("Tel")) {
41
+ this.logger.warn("[sub] unknown protocol:", key);
42
+ return;
43
+ }
38
44
  const protocol = protocol_definitions_1.blueye.protocol[key];
39
45
  const message = protocol.decode(data);
40
- this.logger.verbose("[WS] PubSub message:", key, message);
41
- this.sub.emit(key, message);
42
- });
43
- this.wsReqRep.addEventListener("message", event => {
44
- this.logger.debug("Response:", event.data);
45
- const { id, key, data } = schema_1.responseSchema.parse(JSON.parse(event.data));
46
- if (!id)
47
- throw new Error("Response id is missing");
48
- this.pendingRequests.get(id)?.({ key, data });
46
+ this.logger.verbose("[sub] message:", key, message);
47
+ this.emit(key, message);
49
48
  });
49
+ if (autoConnect) {
50
+ this.connect();
51
+ }
52
+ }
53
+ updateState(newState) {
54
+ this.state = newState;
55
+ this.logger.info(`[client] ${newState}`);
56
+ this.emit(newState);
57
+ }
58
+ connect() {
59
+ if (this.state === "connected") {
60
+ this.logger.warn("[client] already connected");
61
+ return;
62
+ }
63
+ if (this.state === "connecting") {
64
+ this.logger.warn("[client] already connecting");
65
+ return;
66
+ }
67
+ this.updateState("connecting");
68
+ this.sub.subscribe("");
69
+ this.sub.connect(this.subUrl);
70
+ this.rpc.connect(this.rpcUrl);
71
+ this.pub.connect(this.pubUrl);
72
+ this.updateState("connected");
73
+ }
74
+ disconnect() {
75
+ if (this.state === "disconnected") {
76
+ this.logger.warn("[client] already disconnected");
77
+ return;
78
+ }
79
+ if (this.state === "connecting") {
80
+ this.logger.warn("[client] cannot disconnect while connecting");
81
+ return;
82
+ }
83
+ this.sub.unsubscribe("");
84
+ this.sub.disconnect(this.subUrl);
85
+ this.rpc.disconnect(this.rpcUrl);
86
+ this.pub.disconnect(this.pubUrl);
87
+ this.updateState("disconnected");
50
88
  }
51
89
  async sendRequest(req, opts = {}) {
90
+ if (!(0, exports.isInProtocol)(req) || !req.endsWith("Req")) {
91
+ throw new Error(`[rpc] unknown protocol: ${req}`);
92
+ }
52
93
  const protocol = protocol_definitions_1.blueye.protocol[req];
53
94
  const message = protocol.create(opts);
54
95
  const encoded = protocol.encode(message).finish();
55
- while (!this.isReqRepConnected) {
56
- this.logger.debug("Waiting...");
57
- await new Promise(res => setTimeout(res, 50));
58
- }
59
- const id = (0, uuid_1.v4)();
60
96
  const { key, data } = await Promise.race([
61
- new Promise((_, reject) => setTimeout(() => reject(new Error("Request timed out")), this.timeout)),
97
+ new Promise((_, reject) => setTimeout(() => reject(new Error("[rpc] request timed out")), this.timeout)),
62
98
  new Promise(resolve => {
63
- const request = JSON.stringify({
64
- id,
65
- key: `blueye.protocol.${req}`,
66
- data: buffer_1.Buffer.from(encoded).toString("base64")
99
+ // @ts-ignore
100
+ this.rpc.once("message", (topic, msg) => {
101
+ resolve({ key: topic.toString().split(".").at(-1), data: msg });
67
102
  });
68
- this.pendingRequests.set(id, resolve);
69
- this.wsReqRep.send(request);
103
+ this.rpc.send([buffer_1.Buffer.from(`blueye.protocol.${req}`), buffer_1.Buffer.from(encoded)]);
70
104
  })
71
105
  ]);
72
- this.pendingRequests.delete(id);
73
106
  if (key === "Empty") {
74
107
  return null;
75
108
  }
109
+ if (!(0, exports.isInProtocol)(key) || !key.endsWith("Rep")) {
110
+ throw new Error(`[rpc] unknown response protocol: ${key}`);
111
+ }
76
112
  const rep = protocol_definitions_1.blueye.protocol[key];
77
113
  const result = rep.decode(data);
78
- this.logger.debug("Decoded:", result);
114
+ this.logger.debug("[rpc] decoded:", result);
79
115
  return result;
80
116
  }
81
117
  async getTelemetry(type) {
82
118
  const response = await this.sendRequest("GetTelemetryReq", { messageType: type });
83
119
  const { payload } = schema_1.telemetrySchema.parse(response);
84
120
  const { typeUrl, value } = payload;
85
- this.logger.debug(typeUrl);
86
- if ((0, exports.isInProtocol)(typeUrl)) {
87
- const result = protocol_definitions_1.blueye.protocol[typeUrl].decode(value);
88
- this.logger.debug("Result:", result);
89
- return result;
121
+ if (!(0, exports.isInProtocol)(typeUrl) || !typeUrl.endsWith("Tel")) {
122
+ throw new Error(`[rpc] unknown telemetry typeUrl: ${typeUrl}`);
90
123
  }
91
- else {
92
- throw new Error("Unknown typeUrl");
124
+ const result = protocol_definitions_1.blueye.protocol[typeUrl].decode(value);
125
+ this.logger.debug("[rpc] result:", result);
126
+ return result;
127
+ }
128
+ async sendControl(ctrl, opts = {}) {
129
+ if (!(0, exports.isInProtocol)(ctrl) || !ctrl.endsWith("Ctrl")) {
130
+ throw new Error(`[pub] unknown protocol: ${ctrl}`);
93
131
  }
132
+ const protocol = protocol_definitions_1.blueye.protocol[ctrl];
133
+ const message = protocol.create(opts);
134
+ const encoded = protocol.encode(message).finish();
135
+ this.logger.debug("[pub] sending control:", ctrl, message);
136
+ this.pub.send([buffer_1.Buffer.from(`blueye.protocol.${ctrl}`), buffer_1.Buffer.from(encoded)]);
94
137
  }
95
138
  }
96
139
  exports.BlueyeClient = BlueyeClient;
@@ -1,17 +1,14 @@
1
1
  import { Buffer } from "buffer";
2
2
  import z from "zod";
3
3
  export declare const responseSchema: z.ZodObject<{
4
- id: z.ZodOptional<z.ZodString>;
5
- key: z.ZodEffects<z.ZodString, string, string>;
6
- data: z.ZodEffects<z.ZodString, Buffer, string>;
4
+ key: z.ZodEffects<z.ZodType<Buffer, z.ZodTypeDef, Buffer>, string, Buffer>;
5
+ data: z.ZodType<Buffer, z.ZodTypeDef, Buffer>;
7
6
  }, "strip", z.ZodTypeAny, {
8
7
  key: string;
9
8
  data: Buffer;
10
- id?: string | undefined;
11
9
  }, {
12
- key: string;
13
- data: string;
14
- id?: string | undefined;
10
+ key: Buffer;
11
+ data: Buffer;
15
12
  }>;
16
13
  export declare const telemetrySchema: z.ZodObject<{
17
14
  payload: z.ZodObject<{
@@ -7,12 +7,8 @@ exports.telemetrySchema = exports.responseSchema = void 0;
7
7
  const buffer_1 = require("buffer");
8
8
  const zod_1 = __importDefault(require("zod"));
9
9
  exports.responseSchema = zod_1.default.object({
10
- id: zod_1.default.string().uuid().optional(),
11
- key: zod_1.default.string().transform(val => val.split(".").at(-1)),
12
- data: zod_1.default
13
- .string()
14
- .base64()
15
- .transform(val => buffer_1.Buffer.from(val, "base64"))
10
+ key: zod_1.default.instanceof(buffer_1.Buffer).transform(val => val.toString().split(".").at(-1)),
11
+ data: zod_1.default.instanceof(buffer_1.Buffer)
16
12
  });
17
13
  exports.telemetrySchema = zod_1.default.object({
18
14
  payload: zod_1.default.object({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blueyerobotics/blueye-ts",
3
- "version": "1.5.0",
3
+ "version": "3.0.0",
4
4
  "description": "A TypeScript client for interacting with Blueye underwater drones.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -22,8 +22,8 @@
22
22
  "@blueyerobotics/protocol-definitions": "3.2.0-0f78a956",
23
23
  "buffer": "^6.0.3",
24
24
  "consola": "^3.4.2",
25
+ "jszmq": "^0.1.2",
25
26
  "strict-event-emitter": "^0.5.1",
26
- "uuid": "^11.1.0",
27
27
  "zod": "^3.25.67"
28
28
  },
29
29
  "devDependencies": {