Closed wanglin86769 closed 1 year ago
I'll look into this in a couple days ;)
Thanks for your interest in following up this problem. EPICS (Experimental Physics and Industrial Control System) is a powerful but complicated large-scale control system software toolkit.
I also reproduced this problem on Debian Linux 10.3 with the following steps:
Download a version of EPICS base and unzip https://epics.anl.gov/download/base/base-3.15.9.tar.gz
Install EPICS base to generate EPICS tools and libraries
$ cd base-3.15.9/
$ make
Start an EPICS IOC (Input Output Controller)
$ export PATH=$PATH:/home/debian/epics/base-3.15.9/bin/linux-x86_64
$ softIoc -d dbExample1.db
vi dbExample1.db
record(ai, "aiExample")
{
field(DESC, "Analog input")
# field(INP, "calcExample.VAL NPP NMS")
field(EGUF, "10")
field(EGU, "Counts")
field(HOPR, "10")
field(LOPR, "0")
field(HIHI, "8")
field(HIGH, "6")
field(LOW, "4")
field(LOLO, "2")
field(HHSV, "MAJOR")
field(HSV, "MINOR")
field(LSV, "MINOR")
field(LLSV, "MAJOR")
field(VAL, "0.123456789")
}
record(calc, "calcExample")
{
field(DESC, "Counter")
field(SCAN,"1 second")
field(FLNK, "aiExample")
field(CALC, "(A<B)?(A+C):D")
field(INPA, "calcExample.VAL NPP NMS")
field(INPB, "70")
field(INPC, "1")
field(INPD, "30")
field(EGU, "Counts")
field(HOPR, "10")
field(HIHI, "8")
field(HIGH, "6")
field(LOW, "4")
field(LOLO, "2")
field(HHSV, "MAJOR")
field(HSV, "MINOR")
field(LSV, "MINOR")
field(LLSV, "MAJOR")
}
record(calc, "calcExample1")
{
field(DESC, "Counter")
field(SCAN,"2 second")
field(CALC, "(A<B)?(A+C):D")
field(INPA, "calcExample1.VAL NPP NMS")
field(INPB, "80")
field(INPC, "1")
field(INPD, "20")
field(EGU, "Counts")
field(HOPR, "10")
field(HIHI, "8")
field(HIGH, "6")
field(LOW, "4")
field(LOLO, "2")
field(HHSV, "MAJOR")
field(HSV, "MINOR")
field(LSV, "MINOR")
field(LLSV, "MAJOR")
}
record(waveform, "wfExample")
{
field(DTYP, "Soft Channel")
field(FTVL, "DOUBLE")
field(NELM, "1024")
}
Test if the EPICS IOC is started successfully and PV (Process Variable) is accessible
$ export PATH=$PATH:/home/debian/epics/base-3.15.9/bin/linux-x86_64
$ node ca.js
vi ca.js
const koffi = require('koffi');
const libca = koffi.load('/home/debian/epics/base-3.15.9/lib/linux-x86_64/libca.so');
let pointer = koffi.pointer('pointer', koffi.opaque(), 2); let chanId = koffi.pointer('chanId', koffi.opaque());
const evargs_t = koffi.struct('evargs_t', { usr: 'void ', chid: chanId, type: 'long', count: 'long', dbr: 'void ', status: 'int' }); const MonitorCallback = koffi.callback('MonitorCallback', 'void', [evargs_t]); let cb1 = koffi.register(args => { console.log(' cb1 '); }, koffi.pointer(MonitorCallback));
const evargs1_t = koffi.struct('evargs1_t', { chid: chanId, op: 'long' }); const ConnectionCallback = koffi.callback('ConnectionCallback', 'void', ['evargs1_t']); let cb2 = koffi.register(function(args) { console.log(' cb2 '); console.log(args.chid); console.log(args.op); }, 'ConnectionCallback *');
const ca_context_create = libca.func('ca_context_create', 'int', ['int']); const ca_pend_event = libca.func('ca_pend_event', 'int', ['double']); const ca_pend_io = libca.func('ca_pend_io', 'int', ['double']); const ca_create_channel = libca.func('ca_create_channel', 'int', ['string',koffi.pointer(ConnectionCallback),'pointer','int',koffi.out(pointer)]); const ca_field_type = libca.func('ca_field_type', 'short', [chanId]); const ca_element_count = libca.func('ca_element_count', 'int', [chanId]); const ca_array_get = libca.func('ca_array_get', 'int', ['int','ulong',chanId,koffi.out('void *')]); const ca_array_get_callback = libca.func('ca_array_get_callback', 'int', ['int','ulong',chanId,'pointer','pointer']); const ca_create_subscription = libca.func('ca_create_subscription', 'int', ['int','ulong',chanId,'long',koffi.pointer(MonitorCallback),'pointer','pointer']); const ca_clear_channel = libca.func('ca_clear_channel', 'int', [chanId]);
let chidPtr = [null]; let priority = 0; ca_context_create(1); ca_create_channel('calcExample', null, null, priority, chidPtr); ca_pend_io(1.0); let chid = chidPtr[0]; fieldType = ca_field_type(chidPtr[0]); count = ca_element_count(chidPtr[0]); console.log(fieldType); console.log(count); console.log(' Before subscription '); ca_create_subscription(fieldType, count, chid, 1, cb1, null, null); console.log(' After subscription '); ca_pend_io(1.0); ca_pend_event(1.0);
If the EPICS PV can be connected, the error information is as follows:
![image](https://user-images.githubusercontent.com/62229607/215817969-b46beeca-d633-4f78-9dac-51a475a81fef.png)
There are already two node.js implementations of EPICS CA (Channel Access):
node-epics using ffi https://github.com/RobbieClarken/node-epics/blob/master/lib/ca.js
epics-ioc-connection using ffi-napi https://github.com/onichandame/epics-ioc-connection/blob/master/src/ca/channel.ts
However, both these two implementations as well as the ffi libraries seem to be unmaintained.
Thanks for the instructions, that'll make it much easier for me :) I'll look into this soon.
Similar to Issue https://github.com/Koromix/koffi/issues/39 After adding the following lines to prevent the JavaScript main thread from exiting too early, the callback can execute.
setTimeout(function() {
console.log("Good Night!");
}, 5000);
I have one more question. Some arguments of the callback are Napi::External data type, how can I extract the actual data from it in JavaScript code? For example, the "dbr" field as follows,
{
usr: null,
chid: [External: 264d728e750],
type: 6,
count: 1,
dbr: [External: 264d72a5a80],
status: 1
}
Indeed, sorry for not answering. Node.js is single-threaded, you cannot execute JS code in anything other than the main thread.
When a callback is called from a secondary thread, Koffi just asks the main thread to execute it, and blocks until it's done. Obviously if the main thread is busy this does not work and hangs. It needs to run the event loop, which is what happens when you can setTimeout, or if there is an Electron window running, or the main thread is awaiting for something, etc.
There is some info about this here: https://koffi.dev/callbacks#asynchronous-callbacks Also, note that warning at the end of the section.
You can use koffi.decode()
to convert the external value to a JS value (an object, an int, whatever).
Koffi does not do it automatically because it does not have enough information at this point: the pointer could be invalid, or it could point to many values, etc.
More info and an example here: https://koffi.dev/callbacks#decoding-pointer-arguments
Hello, if I add callback as a function argument, the node.js will crash and the callback does not execute. Am I doing something wrong? Is there any way to troubleshoot this problem? Thanks.
In the following code, if callback is added to ca_create_subscription(), node.js will crash.
Node.js code
Output:
https://github.com/epics-base/epics-base/blob/7.0/modules/ca/src/client/oldChannelNotify.cpp
https://github.com/epics-base/epics-base/blob/7.0/modules/ca/src/client/cadef.h