@@ -2329,7 +2329,8 @@ export class Client extends EventEmitter {
23292329 const method = { } as AdsRpcMethodEntry ;
23302330
23312331 //0..3 method entry length
2332- //let len = data.readUInt32LE(pos)
2332+ let entryLength = data . readUInt32LE ( pos )
2333+ let endPos = pos + entryLength ;
23332334 pos += 4 ;
23342335
23352336 //4..7 Version
@@ -2363,7 +2364,7 @@ export class Client extends EventEmitter {
23632364
23642365 //44..47 Flags
23652366 method . flags = data . readUInt32LE ( pos ) ;
2366- method . flagsStr = ADS . ADS_DATA_TYPE_FLAGS . toStringArray ( method . flags ) ;
2367+ method . flagsStr = ADS . ADS_RCP_METHOD_FLAGS . toStringArray ( method . flags ) ;
23672368 pos += 4 ;
23682369
23692370 //48..49 Name length
@@ -2421,7 +2422,7 @@ export class Client extends EventEmitter {
24212422
24222423 //16..19 Flags
24232424 param . flags = data . readUInt16LE ( pos ) ;
2424- param . flagsStr = ADS . RCP_METHOD_PARAM_FLAGS . toStringArray ( param . flags ) ;
2425+ param . flagsStr = ADS . ADS_RCP_METHOD_PARAM_FLAGS . toStringArray ( param . flags ) ;
24252426 pos += 4 ;
24262427
24272428 //20..23 Reserved
@@ -2460,6 +2461,37 @@ export class Client extends EventEmitter {
24602461 param . comment = ADS . decodePlcStringBuffer ( data . subarray ( pos , pos + commentLength + 1 ) ) ;
24612462 pos += commentLength + 1 ;
24622463
2464+ //Attributes
2465+ param . attributes = [ ] ;
2466+
2467+ if ( ( param . flags & ADS . ADS_RCP_METHOD_PARAM_FLAGS . Attributes ) === ADS . ADS_RCP_METHOD_PARAM_FLAGS . Attributes ) {
2468+ const attributeCount = data . readUInt16LE ( pos ) ;
2469+ pos += 2 ;
2470+
2471+ //Attributes
2472+ for ( let i = 0 ; i < attributeCount ; i ++ ) {
2473+ const attr = { } as AdsAttributeEntry ;
2474+
2475+ //Name length
2476+ const nameLength = data . readUInt8 ( pos ) ;
2477+ pos += 1 ;
2478+
2479+ //Value length
2480+ const valueLength = data . readUInt8 ( pos ) ;
2481+ pos += 1 ;
2482+
2483+ //Name
2484+ attr . name = ADS . decodePlcStringBuffer ( data . subarray ( pos , pos + nameLength + 1 ) ) ;
2485+ pos += nameLength + 1 ;
2486+
2487+ //Value
2488+ attr . value = ADS . decodePlcStringBuffer ( data . subarray ( pos , pos + valueLength + 1 ) ) ;
2489+ pos += valueLength + 1 ;
2490+
2491+ method . attributes . push ( attr ) ;
2492+ }
2493+ }
2494+
24632495 if ( pos - beginPosition > entryLength ) {
24642496 //There is some additional data left
24652497 param . reserved2 = data . subarray ( pos ) ;
@@ -2468,6 +2500,39 @@ export class Client extends EventEmitter {
24682500 method . parameters . push ( param ) ;
24692501 }
24702502
2503+ //Attributes
2504+ method . attributes = [ ] ;
2505+
2506+ if ( ( method . flags & ADS . ADS_RCP_METHOD_FLAGS . Attributes ) === ADS . ADS_RCP_METHOD_FLAGS . Attributes ) {
2507+ const attributeCount = data . readUInt16LE ( pos ) ;
2508+ pos += 2 ;
2509+
2510+ //Attributes
2511+ for ( let i = 0 ; i < attributeCount ; i ++ ) {
2512+ const attr = { } as AdsAttributeEntry ;
2513+
2514+ //Name length
2515+ const nameLength = data . readUInt8 ( pos ) ;
2516+ pos += 1 ;
2517+
2518+ //Value length
2519+ const valueLength = data . readUInt8 ( pos ) ;
2520+ pos += 1 ;
2521+
2522+ //Name
2523+ attr . name = ADS . decodePlcStringBuffer ( data . subarray ( pos , pos + nameLength + 1 ) ) ;
2524+ pos += nameLength + 1 ;
2525+
2526+ //Value
2527+ attr . value = ADS . decodePlcStringBuffer ( data . subarray ( pos , pos + valueLength + 1 ) ) ;
2528+ pos += valueLength + 1 ;
2529+
2530+ method . attributes . push ( attr ) ;
2531+ }
2532+ }
2533+
2534+ //Using endPos to fix issues if new data is added by Beckhoff
2535+ pos = endPos ;
24712536 dataType . rpcMethods . push ( method )
24722537 }
24732538 }
@@ -2876,7 +2941,7 @@ export class Client extends EventEmitter {
28762941 rawValue = Buffer . alloc ( dataType . size ) ;
28772942
28782943 for ( const subItem of dataType . subItems ) {
2879- //Does that javascript object contain this field/subItem?
2944+ //Does value object contain this field/subItem?
28802945 let key : undefined | string = undefined ;
28812946
28822947 //First, try the easy way (5-20x times faster)
@@ -4256,7 +4321,7 @@ export class Client extends EventEmitter {
42564321 try {
42574322 handle = await this . createVariableHandle ( path ) ;
42584323 await this . writeRawByHandle ( handle , value ) ;
4259-
4324+
42604325 } finally {
42614326 if ( handle ) {
42624327 await this . deleteVariableHandle ( handle ) ;
@@ -4820,7 +4885,7 @@ export class Client extends EventEmitter {
48204885 *
48214886 * @param symbol Symbol information (acquired using `getSymbolInfo()`)
48224887 * @param value Data to write
4823- * @param targetOpts Optional target settings that override values in `settings` (NOTE: If used, no caching is available -> worse performance)
4888+ * @param targetOpts Optional target settings that override values in `settings`
48244889 */
48254890 public async writeRawBySymbol ( symbol : AdsSymbolInfo , value : Buffer , targetOpts : Partial < AmsAddress > = { } ) : Promise < void > {
48264891 if ( ! this . connection . connected ) {
@@ -4839,4 +4904,186 @@ export class Client extends EventEmitter {
48394904 throw new ClientError ( `writeRawBySymbol(): Writing raw data by symbol ${ symbol . name } failed` , err ) ;
48404905 }
48414906 }
4907+
4908+ /**
4909+ * Invokes/calls a function block RPC method from PLC
4910+ *
4911+ * Returns method return value and/or outputs (if any)
4912+ *
4913+ * For RPC support, the method needs to have `{attribute 'TcRpcEnable'}` attribute above the `METHOD` definition
4914+ *
4915+ * @param path Full function block instance path in the PLC (such as `GVL_Test.ExampleBlock`)
4916+ * @param method Function block method name to call
4917+ * @param parameters Function block method parameters (inputs) (if any)
4918+ * @param targetOpts Optional target settings that override values in `settings`
4919+ *
4920+ * @template T Typescript data type of the method return value, for example `invokeRpcMethod<number>(...)` or `invokeRpcMethod<ST_TypedStruct>(...)`
4921+ * @template U Typescript data type of the method outputs object, for example `invokeRpcMethod<number, ST_TypedStruct>(...)`
4922+ */
4923+ public async invokeRpcMethod < T = any , U = Record < string , any > > ( path : string , method : string , parameters : Record < string , any > = { } , targetOpts : Partial < AmsAddress > = { } ) : Promise < RpcMethodCallResult < T , U > > {
4924+ if ( ! this . connection . connected ) {
4925+ throw new ClientError ( `invokeRpcMethod(): Client is not connected. Use connect() to connect to the target first.` ) ;
4926+ }
4927+
4928+ const debugPath = `${ path } .${ method } ()` ;
4929+ this . debug ( `invokeRpcMethod(): Calling RPC method ${ debugPath } ` ) ;
4930+
4931+ try {
4932+ //Getting the symbol information
4933+ let symbolInfo : AdsSymbolInfo ;
4934+ try {
4935+ this . debugD ( `invokeRpcMethod(): Getting symbol information for ${ path } ` ) ;
4936+ symbolInfo = await this . getSymbolInfo ( path , targetOpts ) ;
4937+
4938+ } catch ( err ) {
4939+ this . debug ( `invokeRpcMethod(): Getting symbol information for ${ path } failed: %o` , err ) ;
4940+ throw new ClientError ( `invokeRpcMethod(): Getting symbol information for ${ path } failed` , err ) ;
4941+ }
4942+
4943+ //Getting the symbol data type
4944+ let dataType : AdsDataType ;
4945+ try {
4946+ this . debugD ( `invokeRpcMethod(): Getting data type for ${ path } ` ) ;
4947+ dataType = await this . buildDataType ( symbolInfo . type , targetOpts ) ;
4948+
4949+ } catch ( err ) {
4950+ this . debug ( `invokeRpcMethod(): Getting symbol information for ${ path } failed: %o` , err ) ;
4951+ throw new ClientError ( `invokeRpcMethod(): Getting symbol information for ${ path } failed` , err ) ;
4952+ }
4953+
4954+
4955+ //Finding the RPC method
4956+ const rpcMethod = dataType . rpcMethods . find ( m => m . name . toLowerCase ( ) === method . toLowerCase ( ) ) ;
4957+
4958+ if ( ! rpcMethod ) {
4959+ throw new ClientError ( `invokeRpcMethod(): Method ${ method } was not found from symbol ${ path } . Available RPC methods are: ${ dataType . rpcMethods . map ( m => m . name ) . join ( ', ' ) } ` ) ;
4960+ }
4961+
4962+ //Method inputs and outputs
4963+ const inputs = rpcMethod . parameters . filter ( p => p . flags === ADS . ADS_RCP_METHOD_PARAM_FLAGS . In ) ;
4964+ const outputs = rpcMethod . parameters . filter ( p => p . flags === ADS . ADS_RCP_METHOD_PARAM_FLAGS . Out ) ;
4965+
4966+ //Creating data buffer for inputs
4967+ let inputsData = Buffer . alloc ( 0 ) ;
4968+
4969+ for ( const input of inputs ) {
4970+ //Does parameter object contain this field/subItem?
4971+ let key : undefined | string = undefined ;
4972+
4973+ //First, try the easy way (5-20x times faster)
4974+ if ( parameters [ input . name ] !== undefined ) {
4975+ key = input . name ;
4976+
4977+ } else {
4978+ //Not found, try case-insensitive way (this is slower -> only doing if needed)
4979+ try {
4980+ key = Object . keys ( parameters ) . find ( objKey => objKey . toLowerCase ( ) === input . name . toLowerCase ( ) ) ;
4981+
4982+ } catch ( err ) {
4983+ //value is null/ not an object/something else went wrong
4984+ key = undefined ;
4985+ }
4986+ }
4987+
4988+ if ( key === undefined ) {
4989+ throw new ClientError ( `invokeRpcMethod(): Missing RPC method input parameter "${ input . name } "` ) ;
4990+ }
4991+
4992+ //Creating raw data from object
4993+ //NOTE: Using internal convertObjectToBuffer() instead of convertToRaw() to achieve better error message with missing properties
4994+ const type = await this . buildDataType ( input . type , targetOpts ) ;
4995+ let res = await this . convertObjectToBuffer ( parameters [ key ] , type ) ;
4996+
4997+ if ( res . missingProperty ) {
4998+ throw new ClientError ( `invokeRpcMethod(): RPC method parameter "${ input . name } " is missing at least key/value "${ res . missingProperty } "` ) ;
4999+ }
5000+
5001+ inputsData = Buffer . concat ( [ inputsData , res . rawValue ] ) ;
5002+ }
5003+
5004+ //Creating a handle to the RPC method
5005+ let handle : VariableHandle ;
5006+ try {
5007+ this . debugD ( `invokeRpcMethod(): Creating a variable handle for ${ debugPath } ` ) ;
5008+ //NOTE: #
5009+ handle = await this . createVariableHandle ( `${ path } #${ method } ` , targetOpts ) ;
5010+
5011+ } catch ( err ) {
5012+ this . debug ( `invokeRpcMethod(): Creating a variable handle for ${ debugPath } failed: %o` , err ) ;
5013+ throw new ClientError ( `invokeRpcMethod(): Creating a variable handle for ${ debugPath } failed` , err ) ;
5014+ }
5015+
5016+ //Allocating bytes for request
5017+ const data = Buffer . alloc ( 16 + inputsData . byteLength ) ;
5018+ let pos = 0 ;
5019+
5020+ //0..3 IndexGroup
5021+ data . writeUInt32LE ( ADS . ADS_RESERVED_INDEX_GROUPS . SymbolValueByHandle , pos ) ;
5022+ pos += 4 ;
5023+
5024+ //4..7 IndexOffset
5025+ data . writeUInt32LE ( handle . handle , pos ) ;
5026+ pos += 4 ;
5027+
5028+ //8..11 Read data length
5029+ data . writeUInt32LE ( rpcMethod . returnTypeSize + outputs . reduce ( ( total , output ) => total + output . size , 0 ) , pos ) ;
5030+ pos += 4 ;
5031+
5032+ //12..15 Write data length
5033+ data . writeUInt32LE ( inputsData . byteLength , pos ) ;
5034+ pos += 4 ;
5035+
5036+ //16..n Data
5037+ inputsData . copy ( data , pos ) ;
5038+
5039+ try {
5040+ const res = await this . sendAdsCommand < AdsReadWriteResponse > ( {
5041+ adsCommand : ADS . ADS_COMMAND . ReadWrite ,
5042+ targetAmsNetId : targetOpts . amsNetId ,
5043+ targetAdsPort : targetOpts . adsPort ,
5044+ payload : data
5045+ } ) ;
5046+
5047+ this . debug ( `invokeRpcMethod(): Calling RPC method ${ debugPath } done!` ) ;
5048+
5049+ let pos = 0 ;
5050+ const result : RpcMethodCallResult < T , U > = {
5051+ returnValue : undefined as T ,
5052+ outputs : { } as U
5053+ } ;
5054+
5055+ //Method return value
5056+ if ( rpcMethod . returnTypeSize > 0 ) {
5057+ result . returnValue = await this . convertFromRaw < T > ( res . ads . payload . subarray ( pos , pos + rpcMethod . returnTypeSize ) , rpcMethod . retunDataType ) ;
5058+ pos += rpcMethod . returnTypeSize ;
5059+ }
5060+
5061+ //Outputs (VAR_OUTPUT)
5062+ for ( const output of outputs ) {
5063+ ( result . outputs as Record < string , any > ) [ output . name ] = await this . convertFromRaw ( res . ads . payload . subarray ( pos , pos + output . size ) , output . type ) ;
5064+ pos += output . size ;
5065+ }
5066+
5067+ return result ;
5068+
5069+ } catch ( err ) {
5070+ this . debug ( `invokeRpcMethod(): Calling RPC method ${ debugPath } failed: %o` , err ) ;
5071+ throw new ClientError ( `invokeRpcMethod(): Calling RPC method ${ debugPath } failed` , err ) ;
5072+
5073+ } finally {
5074+ //Delete handle in all cases
5075+ if ( handle ) {
5076+ try {
5077+ await this . deleteVariableHandle ( handle ) ;
5078+ } catch ( err ) {
5079+ this . debug ( `invokeRpcMethod(): Deleting variable handle to RPC method failed: %o` , err ) ;
5080+ }
5081+ }
5082+ }
5083+
5084+ } catch ( err ) {
5085+ this . debug ( `invokeRpcMethod(): Calling RPC method ${ debugPath } failed: %o` , err ) ;
5086+ throw new ClientError ( `invokeRpcMethod(): Calling RPC method ${ debugPath } failed` , err ) ;
5087+ }
5088+ }
48425089}
0 commit comments