Closed freefly42 closed 1 year ago
Interesting. ReadRawByName works. I have packet captures, I may post if they're helpful. The indexGroup and indexOffset from getSymbolInfo() differ from the indexGroup and indexOffset that are used in the successful read in node-ads. From below, ads-client is getting "61462" (0xF016) and "76" (0x4c) where node-ads winds up with 61445 (0xf005) and 2357199484(0x8c80027c) respectively.
info for Main.fbScreening.fbAlicatSerial.allStatus[1].pressure: {"indexGroup":61462,"indexOffset":76,"size":4,"adsDataType":4,"adsDataTypeStr":"ADST_REAL32","flags":8,"flagsStr":["TypeGuid"],"arrayDimension":0,"nameLength":53,"typeLength":4,"commentLength":0,"name":"Main.fbScreening.fbAlicatSerial.allStatus[1].pressure","type":"REAL","comment":"","arrayData":[],"typeGuid":"9519071800000000000000000000000d","attributes":[],"reserved":{"type":"Buffer","data":[]}} ads-client readRawByName(): Reading data from Main.fbScreening.fbAlicatSerial.allStatus[1].pressure using ADS command READ_SYMVAL_BYNAME)} +81ms ads-client:details _sendAdsCommand(): Sending an ads command ReadWrite (70 bytes): { amsTcp: { command: 0, commandStr: 'AMS_TCP_PORT_AMS_CMD' }, ams: { targetAmsNetId: '5.91.185.48.1.1', targetAdsPort: 851, sourceAmsNetId: '10.0.0.99.1.1', sourceAdsPort: 32751, adsCommand: 9, adsCommandStr: 'ReadWrite', stateFlags: 4, stateFlagsStr: 'AdsCommand, Tcp, Request', dataLength: 70, errorCode: 0, invokeId: 8 }, ads: { rawData: <Buffer 04 f0 00 00 00 00 00 00 ff ff ff ff 36 00 00 00 4d 61 69 6e 2e 66 62 53 63 72 65 65 6e 69 6e 67 2e 66 62 41 6c 69 63 61 74 53 65 72 69 61 6c 2e 61 6c ... 20 more bytes> } } +1ms ads-client:details _createAmsHeader(): AMS header created (32 bytes) +0ms ads-client:raw-data _createAmsHeader(): AMS header created: '055bb930010153030a0000630101ef7f09000400460000000000000008000000' +4ms ads-client:details _createAmsTcpHeader(): AMS/TCP header created (6 bytes) +1ms ads-client:raw-data _createAmsTcpHeader(): AMS/TCP header created: '000066000000' +1ms ads-client:details _createAmsTcpRequest(): AMS/TCP request created (108 bytes) +0ms ads-client:raw-data IO out ------> 108 bytes : 000066000000055bb930010153030a0000630101ef7f0900040046000000000000000800000004f0000000000000ffffffff360000004d61696e2e666253637265656e696e672e6662416c6963617453657269616c2e616c6c5374617475735b315d2e707265737375726500 +0ms ads-client:raw-data IO in <------ 50 bytes: 00002c0000000a0000630101ef7f055bb93001015303090005000c000000000000000800000000000000040000009af17442 +78ms ads-client:details _parseAmsTcpHeader(): Starting to parse AMS/TCP header +79ms ads-client:details _parseAmsTcpHeader(): AMS/TCP header parsed: { command: 0, commandStr: 'AMS_TCP_PORT_AMS_CMD', dataLength: 44 } +0ms ads-client:details _parseAmsHeader(): Starting to parse AMS header +0ms ads-client:details _parseAmsHeader(): AMS header parsed: { targetAmsNetId: '10.0.0.99.1.1', targetAdsPort: 32751, sourceAmsNetId: '5.91.185.48.1.1', sourceAdsPort: 851, adsCommand: 9, adsCommandStr: 'ReadWrite', stateFlags: 5, stateFlagsStr: 'Response, AdsCommand, Tcp', dataLength: 12, errorCode: 0, invokeId: 8, error: false, errorStr: '' } +0ms ads-client:details _parseAdsData(): Starting to parse ADS data +1ms ads-client:details _parseAdsData(): ADS data parsed: { rawData: <Buffer 00 00 00 00 04 00 00 00 9a f1 74 42>, errorCode: 0, dataLength: 4, data: <Buffer 9a f1 74 42>, error: false } +0ms ads-client:details _onAmsTcpPacketReceived(): A parsed AMS packet received with command 0 +0ms ads-client:details _onAdsCommandReceived(): A parsed ADS command received with command 9 +0ms ads-client:details _sendAdsCommand(): Response received for command ReadWrite with invokeId 8 +0ms ads-client readRawByName(): Data read - 4 bytes received for Main.fbScreening.fbAlicatSerial.allStatus[1].pressure +82ms value read is: {"type":"Buffer","data":[154,241,116,66]} 61.235939025878906
read from ads-client (unsuccessful): read from node-ads (successful):
I have figured out that the issue is with a REFERENCE type, so I can actually subscribe to the original object as a workaround.
Hi @freefly42
Thanks for reporting. Good that you found the reason.
Indeed reading REFERENCE and POINTER variables are not possible with subscribe(), readSymbol() and writeSymbol() methods. See: https://github.com/jisotalo/ads-client#reading-and-writing-pointer-to-and-reference-to-variables
I haven't tested if subscribeRaw() would work if acquiring the indexGroup and indexOffset from the reference using ^ operator.
It would be nice if this just worked, but I can certainly live with the workaround. As I looked through the ADS calls from ads-node it appears as though it is creating a handle and possibly subscribing to the handle? Makes sense whey the ADS calls were different. But the ones I posted weren't from subscribe they were from read/write, which was definitely being done with a handle. Anyway, the ads-client implementation does a much better job of managing the PLC state and being stable. Had I been using it in node-red from be beginning I may not find myself directly writing a node client now! Thanks for your help.
@freefly42: Well actually I just tried the following and it works:
getSymbolInfo()
subscribeRaw
So here is a solution for you. Maybe I will add to the library too.
const { ADS, Client } = require('ads-client');
const client = new Client({
targetAmsNetId: 'localhost',
targetAdsPort: 851,
});
client.connect()
.then(async res => {
console.log(`Connected to the ${res.targetAmsNetId}`);
/**
* Subscribes to target variable using handles (works with pointers and references)
* @param {*} variableName
* @param {*} callback
* @param {*} cycleTime
* @param {*} onChange
* @param {*} initialDelay
* @returns
*/
const subscribeByHandle = async (variableName, callback, cycleTime = 10, onChange = true, initialDelay = 0) => {
//Getting symbol info
const info = await client.getSymbolInfo(variableName);
//Creating handle to variable
const handle = await client.createVariableHandle(variableName);
//Finally subscribing
const sub = await client.subscribeRaw(
ADS.ADS_RESERVED_INDEX_GROUPS.SymbolValueByHandle,
handle.handle,
info.size,
async (data, sub) => {
//Converting raw data to object and passing forward
callback(await client.convertFromRaw(data.value, info.type), sub);
},
cycleTime,
onChange,
initialDelay
);
return {
unsubscribe: async () => {
await client.unsubscribe(sub.notificationHandle);
await client.deleteVariableHandle(handle);
}
};
}
//Testing with REFERENCE
const subREF = await subscribeByHandle('GVL_Test.TestREFERENCE', (data, sub) => {
console.log('TestREFERENCE data changed:', data);
}, 100);
console.log('TestREFERENCE subscribed');
setTimeout(async () => {
await subREF.unsubscribe();
console.log('TestREFERENCE unsubscribed');
}, 5000);
//Testing with POINTER
const subPTR = await subscribeByHandle('GVL_Test.TestPOINTER^', (data, sub) => {
console.log('TestPOINTER data changed:', data);
}, 100);
console.log('TestPOINTER subscribed');
setTimeout(async () => {
await subPTR.unsubscribe();
console.log('TestPOINTER unsubscribed');
}, 5000);
})
.catch(err => {
console.log('Something failed:', err)
})
@freefly42 did you test the code above?
@jisotalo , do you have an example where you use the convertFromRaw method to convert an array of pointers? When I use your example above I get an array of buffers. Thanks!
@BrianVanlerberghe, could you provide an example PLC code?
@BrianVanlerberghe, could you provide an example PLC code?
GVL:
example1: ST_Example; example2: ST_Example; example3: ST_Example;
exampleArray : ARRAY [0..2] OF POINTER TO ST_Example;
PRG: GVL.exampleArray[0] := ADR(GVL.example1); GVL.exampleArray[1] := ADR(GVL.example2); GVL.exampleArray[2] := ADR(GVL.example3);
This is an example of how we create an array of pointers. We have a lot of nested structs of the same type and flatlist them by creating an array of pointers to that type.
Thanks for your reply!
The following works fine (see previous example) but you need to read array indexes one-by-one:
const subPTR = await subscribeByHandle('GVL.exampleArray[0]^', (data, sub) => {
console.log('exampleArray data changed:', data);
}, 100);
console.log('exampleArray subscribed');
setTimeout(async () => {
await subPTR.unsubscribe();
console.log('exampleArray unsubscribed');
}, 5000);
Or reading instead of subscribing:
const info = await client.getSymbolInfo('GVL.exampleArray[0]^');
const handle = await client.createVariableHandle('GVL.exampleArray[0]^');
const value = await client.convertFromRaw(await client.readRawByHandle(handle, info.size), info.type);
await client.deleteVariableHandle(handle);
console.log('Value is:', value);
@freefly42 did you test the code above?
Yes, it works. Thanks!
Original title: Can't subscribe to data points that are member fields in an object that's part of an array of objects
I have a FunctionBlock that creates an array of structs to match an I/O process image for a device. I am trying to subscribe to one of fields in the struct, and can't subscribe to any of the fields. The address is: Main.fbScreening.fbAlicatSerial.allStatus[1].status The error that is returned is 1793 'Service is not supported by server'. However, I know it is supported by the server as it works with node-ads. I think the issue has to do with the '[]' in the name, possibly with the cache[symbol-name]. I think it's winding up with the wrong group and offset when it subscribes to the PLC, and that's why the PLC gives the not supported error. It successfully reads the symbol info:
I've tried reading directly with the indexGroup and indexOffset, but it's not allowed. I think the index offset is not correct as what I'm getting for the index offset in node-ads is much larger (in the 44000 range).