From 45aa3255c1acc3ff9b7d15bcd041b899ef495926 Mon Sep 17 00:00:00 2001 From: Nicole Jung <31713368+purplenicole730@users.noreply.github.com> Date: Thu, 15 Jun 2023 14:56:40 -0400 Subject: [PATCH] RSDK-2866: add disconnection and reconnection events (#119) Co-authored-by: Maxim Pertsov --- examples/vanilla/src/main.ts | 18 ++++++++-- src/events.ts | 3 ++ src/robot/client.ts | 68 ++++++++++++++++++++++-------------- src/robot/robot.ts | 18 ++++++++++ 4 files changed, 78 insertions(+), 29 deletions(-) diff --git a/examples/vanilla/src/main.ts b/examples/vanilla/src/main.ts index 4fb2a615e..f30a3e77b 100644 --- a/examples/vanilla/src/main.ts +++ b/examples/vanilla/src/main.ts @@ -34,8 +34,8 @@ function button() { return document.getElementById('main-button'); } -// This function runs a motor component with a given named on your robot. -// Feel free to replace it whatever logic you want to test out! +// This function runs a motor component with a given name on your robot. +// Feel free to replace it with whatever logic you want to test out! async function run(client: VIAM.RobotClient) { // Replace with the name of a motor on your robot. const name = ''; @@ -52,12 +52,26 @@ async function run(client: VIAM.RobotClient) { } } +// This function is called when the robot is disconnected. +// Feel free to replace it with whatever logic you want to test out! +async function disconnected(event) { + console.log('The robot has been disconnected. Trying reconnect...'); +} + +// This function is called when the robot is reconnected. +// Feel free to replace it with whatever logic you want to test out! +async function reconnected(event) { + console.log('The robot has been reconnected. Work can be continued.'); +} + async function main() { // Connect to client let client: VIAM.RobotClient; try { client = await connect(); console.log('connected!'); + client.on('disconnected', disconnected); + client.on('reconnected', reconnected); } catch (error) { console.log(error); return; diff --git a/src/events.ts b/src/events.ts index add1ca7c1..d69c32182 100644 --- a/src/events.ts +++ b/src/events.ts @@ -1,5 +1,8 @@ type Callback = (args: unknown) => void; +export const RECONNECTED = 'reconnected'; +export const DISCONNECTED = 'disconnected'; + export class EventDispatcher { listeners: Record> = {}; diff --git a/src/robot/client.ts b/src/robot/client.ts index a246c39e8..04d99e054 100644 --- a/src/robot/client.ts +++ b/src/robot/client.ts @@ -4,6 +4,7 @@ import type { Credentials, DialOptions } from '@viamrobotics/rpc/src/dial'; import { Duration } from 'google-protobuf/google/protobuf/duration_pb'; import { dialDirect, dialWebRTC } from '@viamrobotics/rpc'; import type { grpc } from '@improbable-eng/grpc-web'; +import { DISCONNECTED, EventDispatcher, events, RECONNECTED } from '../events'; import proto from '../gen/robot/v1/robot_pb'; import type { PoseInFrame, @@ -29,7 +30,6 @@ import { SLAMServiceClient } from '../gen/service/slam/v1/slam_pb_service'; import { SensorsServiceClient } from '../gen/service/sensors/v1/sensors_pb_service'; import { ServoServiceClient } from '../gen/component/servo/v1/servo_pb_service'; import { VisionServiceClient } from '../gen/service/vision/v1/vision_pb_service'; -import { events } from '../events'; import { ViamResponseStream } from '../responses'; import SessionManager from './session-manager'; import type { Robot, RobotStatusStream } from './robot'; @@ -55,7 +55,7 @@ abstract class ServiceClient { * * @group Clients */ -export class RobotClient implements Robot { +export class RobotClient extends EventDispatcher implements Robot { private readonly serviceHost: string; private readonly webrtcOptions: WebRTCOptions | undefined; private readonly sessionOptions: SessionOptions | undefined; @@ -114,6 +114,7 @@ export class RobotClient implements Robot { webrtcOptions?: WebRTCOptions, sessionOptions?: SessionOptions ) { + super(); this.serviceHost = serviceHost; this.webrtcOptions = webrtcOptions; this.sessionOptions = sessionOptions; @@ -126,6 +127,35 @@ export class RobotClient implements Robot { return this.transportFactory(opts); } ); + + events.on(RECONNECTED, () => { + this.emit(RECONNECTED, {}); + }); + events.on(DISCONNECTED, () => { + this.emit(DISCONNECTED, {}); + if (this.webrtcOptions?.noReconnect) { + return; + } + + let retries = 0; + // eslint-disable-next-line no-console + console.debug('connection closed, will try to reconnect'); + void backOff(() => + this.connect().then( + () => { + // eslint-disable-next-line no-console + console.debug('reconnected successfully!'); + events.emit(RECONNECTED, {}); + }, + (error) => { + // eslint-disable-next-line no-console + console.debug(`failed to reconnect - retries count: ${retries}`); + retries += 1; + throw error; + } + ) + ); + }); } get sessionId() { @@ -284,6 +314,10 @@ export class RobotClient implements Robot { this.sessionManager.reset(); } + public isConnected(): boolean { + return this.peerConn?.iceConnectionState === 'connected'; + } + public async connect( authEntity = this.savedAuthEntity, creds = this.savedCreds @@ -353,31 +387,11 @@ export class RobotClient implements Robot { * connection getting closed, so restarting ice is not a valid way to * recover. */ - if (this.peerConn?.iceConnectionState === 'closed') { - if (this.webrtcOptions?.noReconnect) { - return; - } - - let retries = 0; - // eslint-disable-next-line no-console - console.debug('connection closed, will try to reconnect'); - void backOff(() => - this.connect().then( - () => { - // eslint-disable-next-line no-console - console.debug('reconnected successfully!'); - events.emit('reconnected', {}); - }, - (error) => { - // eslint-disable-next-line no-console - console.debug( - `failed to reconnect - retries count: ${retries}` - ); - retries += 1; - throw error; - } - ) - ); + if (this.peerConn?.iceConnectionState === 'connected') { + events.emit(RECONNECTED, {}); + } else if (this.peerConn?.iceConnectionState === 'closed') { + console.log('emit disconnected'); + events.emit(DISCONNECTED, {}); } }); diff --git a/src/robot/robot.ts b/src/robot/robot.ts index 519d515df..e1c92d479 100644 --- a/src/robot/robot.ts +++ b/src/robot/robot.ts @@ -4,11 +4,14 @@ import type { Transform, } from '../gen/common/v1/common_pb'; import type { StructType } from '../types'; +import { DISCONNECTED, RECONNECTED } from '../events'; import type proto from '../gen/robot/v1/robot_pb'; import type { ResponseStream } from '../gen/robot/v1/robot_pb_service'; export type RobotStatusStream = ResponseStream; +type Callback = (args: unknown) => void; + export interface Robot { /** * Get the list of operations currently running on the robot. @@ -146,4 +149,19 @@ export interface Robot { resourceNames?: ResourceName.AsObject[], durationMs?: number ): RobotStatusStream; + + /** + * Call a function when an event of either 'reconnected' or 'disconnected' is + * triggered. Note that these events will only be triggered on WebRTC + * connections. + * + * @param type - The event ('reconnected' or 'disconnected') that was + * triggered. + * @param listener - The function to call + * @alpha + */ + on: ( + type: typeof RECONNECTED | typeof DISCONNECTED, + listener: Callback + ) => void; }