1+ /* eslint-disable max-depth */
2+ import {
3+ BinaryReader ,
4+ FileDescriptorProto ,
5+ MethodOptions ,
6+ } from '@bufbuild/protobuf' ;
17import {
28 Code ,
39 ConnectError ,
410 createClient ,
511 type Transport ,
612} from '@connectrpc/connect' ;
13+ import { createAsyncIterable } from '@connectrpc/connect/protocol' ;
14+ import { safety_heartbeat_monitored as safteyHeartbeatMonitored } from '../gen/common/v1/common_pb' ;
15+ import { ServerReflection } from '../gen/grpc/reflection/v1/reflection_connect' ;
16+ import {
17+ FileDescriptorResponse ,
18+ ListServiceResponse ,
19+ ServerReflectionRequest ,
20+ } from '../gen/grpc/reflection/v1/reflection_pb' ;
721import { RobotService } from '../gen/robot/v1/robot_connect' ;
822import {
923 SendSessionHeartbeatRequest ,
@@ -22,29 +36,14 @@ const timeoutBlob = new Blob(
2236) ;
2337
2438export default class SessionManager {
25- public readonly transport : Transport ;
39+ public static heartbeatMonitoredMethods : Record < string , boolean > = { } ;
2640
27- public static readonly heartbeatMonitoredMethods = new Set < string > ( [
28- '/viam.component.arm.v1.ArmService/MoveToPosition' ,
29- '/viam.component.arm.v1.ArmService/MoveToJointPositions' ,
30- '/viam.component.arm.v1.ArmService/MoveThroughJointPositions' ,
31- '/viam.component.base.v1.BaseService/MoveStraight' ,
32- '/viam.component.base.v1.BaseService/Spin' ,
33- '/viam.component.base.v1.BaseService/SetPower' ,
34- '/viam.component.base.v1.BaseService/SetVelocity' ,
35- '/viam.component.gantry.v1.GantryService/MoveToPosition' ,
36- '/viam.component.gripper.v1.GripperService/Open' ,
37- '/viam.component.gripper.v1.GripperService/Grab' ,
38- '/viam.component.motor.v1.MotorService/SetPower' ,
39- '/viam.component.motor.v1.MotorService/GoFor' ,
40- '/viam.component.motor.v1.MotorService/GoTo' ,
41- '/viam.component.motor.v1.MotorService/SetRPM' ,
42- '/viam.component.servo.v1.ServoService/Move' ,
43- ] ) ;
41+ public readonly transport : Transport ;
4442
4543 private currentSessionID = '' ;
4644 private sessionsSupported : boolean | undefined ;
4745 private heartbeatIntervalMs : number | undefined ;
46+ private host = '' ;
4847
4948 private starting : Promise < void > | undefined ;
5049
@@ -53,7 +52,11 @@ export default class SessionManager {
5352 return createClient ( RobotService , transport ) ;
5453 }
5554
56- constructor ( private deferredTransport : ( ) => Transport ) {
55+ constructor (
56+ host : string ,
57+ private deferredTransport : ( ) => Transport
58+ ) {
59+ this . host = host ;
5760 this . transport = new SessionTransport ( this . deferredTransport , this ) ;
5861 }
5962
@@ -185,6 +188,7 @@ export default class SessionManager {
185188 ( Number ( heartbeatWindow . seconds ) * 1e3 +
186189 heartbeatWindow . nanos / 1e6 ) /
187190 5 ;
191+ await this . applyHeartbeatMonitoredMethods ( ) ;
188192 resolve ( ) ;
189193 this . heartbeat ( ) . catch ( console . error ) ; // eslint-disable-line no-console
190194 } ) ( )
@@ -198,4 +202,59 @@ export default class SessionManager {
198202
199203 return this . getSessionMetadataInner ( ) ;
200204 }
205+
206+ private async applyHeartbeatMonitoredMethods ( ) : Promise < void > {
207+ const client = createClient ( ServerReflection , this . transport ) ;
208+ const request = new ServerReflectionRequest ( {
209+ host : this . host ,
210+ messageRequest : { case : 'listServices' , value : '' } ,
211+ } ) ;
212+ const responseStream = client . serverReflectionInfo (
213+ createAsyncIterable ( [ request ] ) ,
214+ { timeoutMs : 10_000 }
215+ ) ;
216+ for await ( const serviceResponse of responseStream ) {
217+ const fdpRequests = (
218+ serviceResponse . messageResponse . value as ListServiceResponse
219+ ) . service . map ( ( service ) => {
220+ return new ServerReflectionRequest ( {
221+ messageRequest : { case : 'fileContainingSymbol' , value : service . name } ,
222+ } ) ;
223+ } ) ;
224+ const fdpResponseStream = client . serverReflectionInfo (
225+ createAsyncIterable ( fdpRequests ) ,
226+ { timeoutMs : 10_000 }
227+ ) ;
228+ for await ( const fdpResponse of fdpResponseStream ) {
229+ for ( const fdp of (
230+ fdpResponse . messageResponse . value as FileDescriptorResponse
231+ ) . fileDescriptorProto ) {
232+ const protoFile = FileDescriptorProto . fromBinary ( fdp ) ;
233+ for ( const service of protoFile . service ) {
234+ for ( const method of service . method ) {
235+ SessionManager . heartbeatMonitoredMethods [
236+ `/${ protoFile . package } .${ service . name } /${ method . name } `
237+ ] = SessionManager . hasHeartbeatOption ( method . options ) ;
238+ }
239+ }
240+ }
241+ }
242+ }
243+ }
244+
245+ private static hasHeartbeatOption ( options ?: MethodOptions ) : boolean {
246+ if ( ! options ) {
247+ return false ;
248+ }
249+ const reader = new BinaryReader ( options . toBinary ( ) ) ;
250+ while ( reader . pos < reader . len ) {
251+ const tag = reader . tag ( ) ;
252+ const [ fieldNumber ] = tag ;
253+ if ( fieldNumber === safteyHeartbeatMonitored . field . no ) {
254+ return true ;
255+ }
256+ reader . string ( ) ;
257+ }
258+ return false ;
259+ }
201260}
0 commit comments