Since it seems to work I will be releasing R4-33 in the new few days.
Mark
Mark:
Thanks muchly for your fast response to this. You fixed it faster than I could test it, and based on a fairly quick test, it is now working with streamDevice just as I was hoping from the outset.
Thank you!
Will that change constitute a new release, or should I use the branch until I new release is rolled out?
--- rod.
From: Mark Rivers <[email protected]>
Sent: Friday, January 26, 2018 1:28 PM
To: Rod Nussbaumer; 'EPICS Mailing list'
Subject: RE: asyn IP server port driver with streamDevice
Hi Rod,
I have now tested drvAsynIPServerPort with StreamDevice and a stringin record with SCAN=I/O Intr. It seems to work fine.
This is the database testIPServer.db:
**********************
record(stringin, "$(P)stringInput") {
field(DTYP, "stream")
field(INP, "@testIPServer.proto read $(PORT)")
field(SCAN, "I/O Intr")
}
record(stringout, "$(P)stringOutput") {
field(DTYP, "stream")
field(OUT, "@testIPServer.proto write $(PORT)")
}
**********************
This is the protocol file testIPServer.proto:
**********************
InTerminator = CR LF;
OutTerminator = CR LF;
read {
in "%#s";
}
write {
out "%s";
}
**********************
This is the startup script:
**********************
< envPaths
dbLoadDatabase("../../dbd/CARSLinux.dbd")
CARSLinux_registerRecordDeviceDriver(pdbbase)
# This is the local TCP port that for clients to connect to
epicsEnvSet LOCAL_IP_PORT 5001
# This is the name of the asyn port driver that listens for connections
epicsEnvSet ASYN_LISTEN_PORT P5001
# This is the name of the first drvAsynIPPort TCP driver that will be created
epicsEnvSet ASYN_TCP_PORT P5001:0
# The following command starts a server on port 5001
drvAsynIPServerPortConfigure("$(ASYN_LISTEN_PORT)","localhost:$(LOCAL_IP_PORT)",1,0,0,0)
#asynSetTraceFile("$(ASYN_LISTEN_PORT)", -1, "")
asynSetTraceIOMask("$(ASYN_LISTEN_PORT)", -1, 0x2)
#asynSetTraceMask("$(ASYN_LISTEN_PORT)", -1, 0xff)
asynSetTraceIOMask("$(ASYN_TCP_PORT)", -1, 0x2)
asynSetTraceMask("$(ASYN_TCP_PORT)", -1, 0x9)
#asynOctetSetInputEos("$(ASYN_TCP_PORT)", 0, "\r\n")
#asynOctetSetOutputEos("$(ASYN_TCP_PORT)", 0, "\r\n")
dbLoadRecords("testIPServer.db", "P=testIPServer:,PORT=$(ASYN_TCP_PORT)")
iocInit()
**********************
This is a telnet session to localhost:5001. I typed the first 4 lines.
**********************
hello there
hello again
hello once more
this is a test
**********************
This is camonitor running on the stringin record which has SCAN=I/O Intr:
**********************
corvette:asyn/iocBoot/ioctestIPServer>camonitor testIPServer:stringInput
testIPServer:stringInput 2018-01-26 15:11:00.530954 hello there
testIPServer:stringInput 2018-01-26 15:11:09.641859
testIPServer:stringInput 2018-01-26 15:11:12.745610 hello again
testIPServer:stringInput 2018-01-26 15:11:28.865169 hello once more
**********************
So it is correctly processing with each of the 4 lines I typed in the telnet session.
This is running caput to the stringout record:
**********************
corvette:simDetectorIOC/iocBoot/iocSimDetector>caput testIPServer:stringOutput "this is a test"
Old : testIPServer:stringOutput this is a test
New : testIPServer:stringOutput this is a test
**********************
Note that the line "this is a test" is the last line in the telnet session, so it did get written to that port.
The only thing that does not seem to work correctly is the way StreamDevice is handling the stringin record with SCAN=I/O Intr. I see these problems:
If I set the terminators in the startup script and not in the protocol file:
1) The record process again 0.1 second after it gets input, and this time the VAL field contains nothing.
2) If I type lines quickly then sometimes the \r\n are not removed, they are in the VAL field.
3) If I type lines quickly then sometimes it misses lines.
If I set the terminators in the protocol file and not in the startup script then I don't see problems 1 or 2 above, but I still see problem 3.
I don't think this has anything to do with drvAsynIPServerPort, but is something I am doing wrong with StreamDevice, or perhaps some problem with StreamDevice.
Mark
From: Mark Rivers
Sent: Thursday, January 25, 2018 5:07 PM
To: Rod Nussbaumer <[email protected]>; EPICS Mailing list <[email protected]>
Subject: RE: asyn IP server port driver with streamDevice
Hi Rod,
I have changed the drvAsynIPServer port driver so that it should work for your application.
This is the commit log:
*********************************
commit 94e018a7e52b94a01a78ad246d9d97ac87744d59
Author: Mark Rivers <mailto:[email protected]>
Date: Thu Jan 25 16:34:25 2018 -0600
Changed the logic so it creates maxClients drvAsynIPPort drivers at initialization rather than on-demand
when clients connect. This makes it possible to use these ports before iocInit, which is much more useful.
They can be used in input and output links in EPICS records, for example.
This change should be backwards compatible, since it was transparent when the ports were created.
The first drvAsynIPPort created is now named PORT_NAME:0 rather than PORT_NAME:1, where PORT_NAME is the name of the
drvAsynIPServer port created by this driver.
This might break backwards compatibility, but most clients were getting the name from a callback, so probably not.
Set the socket option SO_REUSEADDR when creating the listening socket. Previously if a client was connected
and the IOC exited it could not be restarted again immediately. One needed to wait for the OS to time out
the connection. This change allows the IOC to be run again with the same listening port immediately,
without waiting for the OS timeout.
Added an exit handler to close the listen socket and delete the private structure.
Improved the report() function so it shows all the drvAsynIPPort drivers that were created.
*********************************
I added a new database, testIPServerApp/Db/testIPServer1.db. It contains a stringin and a stringout record.
record(stringin,"$(P)stringInput") {
field(DTYP, "asynOctetRead")
field(INP, "@asyn($(PORT),0,1)")
}
record(stringout,"$(P)stringOutput") {
field(DTYP, "asynOctetWrite")
field(OUT, "@asyn($(PORT),0,1)")
I added a new startup script, iocBoot/ioctestIPServer/st.cmd.testIPServer1. This creates a drvAsynIPServerPort called P5001 that listens for connections on localhost:5001. It creates a normal drvAsynIPPort called P5001:0 that will be connected to a TCP client
that connects to localhost:5000. It loads the testIPServer1.db database shown above.
I have tested that it works correctly. If I telnet to localhost:5001, type some text and process the stringInput record it contains the text I typed in telnet. Similarly if I do a caput of some text to the stringOutput record then that text appears in the
telnet session.
I have not yet had a chance to test with StreamDevice, but I am quite sure it will work, since P5001:0 is just a normal drvAsynIPPort.
The new code is on Github https://github.com/epics-modules/asyn in the new_drvAsynIPServerPort branch,
Please test when you get a chance and let me know if it works for you.
Mark
From: Mark Rivers
Sent: Thursday, January 25, 2018 11:28 AM
To: Rod Nussbaumer <mailto:[email protected]>; EPICS Mailing list <mailto:[email protected]>
Subject: RE: asyn IP server port driver with streamDevice
Hi Rod,
The existing implementation of drvAsynIPServer port works as follows, as documented in
https://epics.anl.gov/modules/soft/asyn/R4-32/asynDriver.html
***************************************
This driver implements the asynOctet interface. The only methods it supports are registerInterruptUser and cancelInterruptUser. Calling the other asynOctet methods will result in an error. The following happens when a new connection is received on the port
specified in drvAsynIPServerPortConfigure:
. If there are no registered asyn clients (who have called registerInterruptUser on the asynOctet interface of the listener port) then the incoming connection is immediately closed, since there are no IP servers available to service it. If there is at least
one registered client, then the following steps are executed.
. The list of drvAsynIPPort ports that this listener thread has created is searched to see if there is an existing port that is currently disconnected because the remote IP client disconnected.
. If there is an existing disconnected port, then it is reconnected with the file descriptor from the new IP connection.
. If there is no available existing port, then a new one is created by calling drvAsynIPPortConfigure. The name of the new port is of the form portName:1, portName:2, etc., where portName is the name of the listener port.
. The asynTraceMask and asynTraceIOMask of the newly connected port are set to the current values of the listener thread port. This makes it possible to trace the early stages of execution of the callbacks to the registered clients, before one could enable
tracing at iocsh.
. All registered asyn clients (who have called registerInterruptUser on the asynOctet interface of the listener port) are called back with the name of the newly connected port.
. The clients then will connect to this new asyn port and perform I/O using the asynOctet methods.
***************************************
So the way it works is that drvAsynIPServer port creates a thread that listens for incoming connections on the specified TCP port. The first time a connection is received it creates a drvAsynIPPort that is connected to the file description for that connection.
It is this drvAsynIPPort that you need to pass to StreamDevice.
The problem is that in the current implementation that devAsynIPPort is only created when the first connection is received. This is not what you want, because typically that won't happen until after iocInit, when it is too late for your application. You need
the port to exist before iocInit the StreamDevice INP and OUT links will connect at iocInit.
Here is a simple demonstration of the existing implementation using the testAsynIPServerApp application in asyn. It creates a drvAsynIPServer port called P5001 listening on TCP port 5001. It accepts a single incoming connection.
corvette:asyn/iocBoot/ioctestIPServer>../../bin/linux-x86_64/testIPServer st.cmd.test
< envPaths
epicsEnvSet("IOC","ioctestIPServer")
epicsEnvSet("TOP","/home/epics/devel/asyn-4-33")
epicsEnvSet("SUPPORT","/corvette/home/epics/devel")
epicsEnvSet("IPAC","/corvette/home/epics/devel/ipac-2-14")
epicsEnvSet("SNCSEQ","/corvette/home/epics/devel/seq-2-2-5")
epicsEnvSet("EPICS_BASE","/corvette/usr/local/epics-devel/base-7.0.1")
dbLoadDatabase("../../dbd/testIPServer.dbd")
testIPServer_registerRecordDeviceDriver(pdbbase)
#The following command starts a server on port 5001
drvAsynIPServerPortConfigure("P5001","localhost:5001",1,0,0,0)
serverPort: 5001
#asynSetTraceFile("P5001",-1,"")
asynSetTraceIOMask("P5001",-1,0x2)
#asynSetTraceMask("P5001",-1,0xff)
iocInit()
Starting iocInit
############################################################################
## EPICS R7.0.1.1
## EPICS Base built Jan 3 2018
############################################################################
iocRun: All initialization complete
ipEchoServer("P5001")
epics>
I now run asynReport to show all asynPortDrivers in this IOC. Note that there is only 1, P5001.
epics> asynReport 1
P5001 multiDevice:No canBlock:Yes autoConnect:Yes
enabled:Yes connected:Yes numberConnects 1
nDevices 0 nQueued 0 blocked:No
asynManagerLock:No synchronousLock:No
exceptionActive:No exceptionUsers 0 exceptionNotifys 0
traceMask:0x1 traceIOMask:0x2 traceInfoMask:0x1
Port P5001: Connected
fd: 4
Max. clients: 1
Num. clients: 0
In another terminal session I executed the command " telnet localhost 5001". That caused a new drvAsynIPPort port (P5001:1) to be created, as shown by asynReport.
epics> asynReport 1
P5001 multiDevice:No canBlock:Yes autoConnect:Yes
enabled:Yes connected:Yes numberConnects 1
nDevices 0 nQueued 0 blocked:No
asynManagerLock:No synchronousLock:No
exceptionActive:No exceptionUsers 0 exceptionNotifys 0
traceMask:0x1 traceIOMask:0x2 traceInfoMask:0x1
Port P5001: Connected
fd: 4
Max. clients: 1
Num. clients: 1
P5001:1 multiDevice:No canBlock:Yes autoConnect:No
enabled:Yes connected:Yes numberConnects 1
nDevices 0 nQueued 0 blocked:No
asynManagerLock:No synchronousLock:Yes
exceptionActive:No exceptionUsers 1 exceptionNotifys 0
traceMask:0x1 traceIOMask:0x2 traceInfoMask:0x1
Port localhost:5001: Connected
StreamDevice could now connect to that port and it should do what you want. But as I said above, it is too late, that port needed to exist before iocInit.
I think the solution is simple. drvAsynIPServer port should be changed so that it creates the drvAsynIPPorts in its constructor, before any connections are made. It will be in the "disconnected" state. Once a connection comes in it will connect to that file
description. I think this change makes a lot of sense, since it makes things simpler.
I will make this change and push to a branch on Github where you can test it. Once we are happy I will poll tech-talk to see if anyone is using drvAsynIPServerPort and would object to this change. It would require a small change to their application. If
there is no objection I will replace drvAsynIPServerPort. If there is objection I will call the new driver something else.
Mark
From: Mark Rivers
Sent: Tuesday, January 23, 2018 11:11 PM
To: Rod Nussbaumer <mailto:[email protected]>; EPICS Mailing list <mailto:[email protected]>
Subject: Re: asyn IP server port driver with streamDevice
Hi Rod,
I've looked at this more closely, and my initial answer is incorrect. This is not how it works. I need to think about how it could work for your use case.
Mark
Sent from my iPhone
On Jan 23, 2018, at 6:46 PM, Mark Rivers <mailto:[email protected]> wrote:
Hi Rod,
I have not tested what you are trying to do, but I think it should work. You are just missing one step.
1) You need to create a drvAsynIPServer port to listen for incoming connections. You did this.
2) You then need to create a normal client drvAsynIPort that connects to the server created in step 1.
3) Stream device connects to the client port, not to the server port.
For example:
drvAsynIPServerPortConfigure("CYC_server", "localhost:9999",10,1,1)
drvAsynIPPortConfigure("CYC_client", "localhost:9999",0,0,0)
dbLoadRecords( "/mnt/icfileserv/usr1/common/db/ccs2epics01.db", PORT="CYC_client" )
Give it a try and let me know if it does or does not work.
Mark
From: mailto:[email protected] [mailto:[email protected]] On Behalf Of Rod Nussbaumer
Sent: Tuesday, January 23, 2018 5:43 PM
To: EPICS Mailing list <mailto:[email protected]>
Subject: asyn IP server port driver with streamDevice
Hi All.
I'm trying to do something that seems to have many moving pieces, but I think it should work, with a little guidance here. I have database and accompanying protocols that are attempting to use the I/O Intr methods documented in the streamDevice docs, which
say that I can have a series of records receive the input stream to attempt a matching scan. I'm doing this with an async port that is a TCP Server port. The port configuration seems to work; I can make a telnet connection to it. I was hoping that I could
type the text matching the streamDevice 'in' protocol, and that the record attached to the protocol would process the input. I don't seem to be getting any data through the asyn TCP server port. The record doesn't process (stays UDF). Moreover, the asynReport
data always stays at 0 bytes sent, and 0 bytes received.
I'm using the latest streamDevice from github (hard to figure out version numbers from there) and asyn4-22 with EPICS 3.14.12.2
Following are some excerpts from startup script, IOC DB and streamDevice protocol files.
-------------------------< IOC startup script >---------------------------
drvAsynIPServerPortConfigure("CYC", "localhost:9999",10,1,1)
dbLoadRecords( "/mnt/icfileserv/usr1/common/db/ccs2epics01.db", PORT="CYC" )
# asynSetTraceIOMask(FGC3,10,0x0005)
# asynSetTraceMask (FGC3,10,0x001f)
# var streamDebug 1
iocInit()
-------------------------< IOC start >---------------------------
asynSetTraceIOMask(CYC,0,0x0005)
asynSetTraceMask (CYC,0,0x001f)
# var streamDebug 1
iocInit()
Starting iocInit
############################################################################
## EPICS R3.14.12.2 $Date: Mon 2011-12-12 14:09:32 -0600$
## EPICS Base built Jan 18 2018
############################################################################
2018/01/23 15:17:06.452 CYC -1 registerInterruptUser
2018/01/23 15:17:06.452 CYC -1 registerInterruptUser
2018/01/23 15:17:06.452 CYC -1 registerInterruptUser
2018/01/23 15:17:06.452 CYC -1 registerInterruptUser
2018/01/23 15:17:06.452 CYC -1 registerInterruptUser
2018/01/23 15:17:06.452 CYC -1 registerInterruptUser
2018/01/23 15:17:06.452 CYC -1 registerInterruptUser
2018/01/23 15:17:06.452 CYC -1 registerInterruptUser
2018/01/23 15:17:06.452 CYC -1 registerInterruptUser
2018/01/23 15:17:06.452 CYC -1 registerInterruptUser
2018/01/23 15:17:06.452 CYC -1 registerInterruptUser
2018/01/23 15:17:06.452 CYC -1 registerInterruptUser
2018/01/23 15:17:06.452 CYC -1 registerInterruptUser
2018/01/23 15:17:06.452 CYC -1 registerInterruptUser
2018/01/23 15:17:06.452 CYC -1 registerInterruptUser
2018/01/23 15:17:06.452 CYC -1 registerInterruptUser
2018/01/23 15:17:06.452 CYC -1 registerInterruptUser
2018/01/23 15:17:06.452 CYC -1 registerInterruptUser
2018/01/23 15:17:06.452 CYC -1 registerInterruptUser
2018/01/23 15:17:06.452 CYC -1 registerInterruptUser
iocRun: All initialization complete
# exit
mmpsioc1>2018/01/23 15:17:31.110 drvAsynIPServerPort: new connection, socket=5 on localhost:9999
---------------------------------< IOC runtime DB >------------------------------------
record(ai,"CYC:START"){
field(DESC,"analog input record")
field(DTYP,"stream")
field(SCAN,"Passive")
field(INP,"@ccs2epics.proto WR_START $(PORT) 0")
}
record(ai,"ABC:01:DEVTYPE"){
field(DESC,"analog input record")
field(DTYP,"stream")
field(SCAN,"I/O Intr")
field(INP,"@ccs2epics.proto RD_ABC_01 $(PORT) 0")
}
record(ai,"ABC:23:DEVTYPE"){
field(DESC,"analog input record")
field(DTYP,"stream")
field(SCAN,"I/O Intr")
field(INP,"@ccs2epics.proto RD_ABC_23 $(PORT) 0")
}
(There are 18 more I/O Intr scanned records, which seems to match the reports from asyn registerInterruptUser)
---------------------------< protocol file >------------------------------------
One protocol per record. I added the 'out' commands as a diagnostic to see if I could make it respond.
WR_START {
out "START\n";
in "PLEASE %d";
}
RD_ABC_01 {
in "ABC,01,%d";
out "thanks\n";
}
RD_ABC_23 {
in "ABC,23,%d";
out "thanks\n";
}
RD_ABC_45 {
in "ABC,45,%d";
out "thanks\n";
}
Processing the Passive record 'CYC:START' by using dbtr in the IOC shell or by setting it to periodic scan causes streamDevice to emit:
2018/01/23 15:25:32.022206 _main_ CYC:START lockRequest: pasynManager->queueRequest: port CYC not connected
PV: CYC:START dbtr(dbProcess)
The connected telnet session doesn't complain, but characters typed don't seem to go anywhere that shows up in places I can inspect:
mmpsioc1>asynReport,3
CYC multiDevice:No canBlock:Yes autoConnect:No
enabled:Yes connected:No numberConnects 0
nDevices 0 nQueued 0 blocked:No
asynManagerLock:No synchronousLock:No
exceptionActive:No exceptionUsers 0 exceptionNotifys 0
traceMask:0x1f traceIOMask:0x5 traceInfoMask:0x1
interposeInterfaceList
asynOctet pinterface 0x760b40 drvPvt 0x247dc90
interfaceList
asynCommon pinterface 0x760b10 drvPvt 0x247a7b0
asynInt32 pinterface 0x767ce0 drvPvt 0x247a7b0
asynOctet pinterface 0x767c80 drvPvt 0x247a7b0
Port CYC: Connected
fd: 4
Max. clients: 10
Num. clients: 1
CYC:1 multiDevice:No canBlock:Yes autoConnect:No
enabled:Yes connected:Yes numberConnects 1
nDevices 0 nQueued 0 blocked:No
asynManagerLock:No synchronousLock:No
exceptionActive:No exceptionUsers 1 exceptionNotifys 0
traceMask:0x1f traceIOMask:0x5 traceInfoMask:0x1
interposeInterfaceList
asynOctet pinterface 0x760d00 drvPvt 0x7f9b00001790
interfaceList
asynCommon pinterface 0x757210 drvPvt 0x7f9b000008e0
asynOctet pinterface 0x7f9b000009c8 drvPvt 0x7f9b000008e0
Port localhost:9999: Connected
fd: 5
Characters written: 0
Characters read: 0
What is the meaning of the CYC:1 section in the above? Is there some kind of new pseudo port created by each client connection? Am I supposed to use that somehow? How? What is the meaning of 'autoConnect' in the context of a TCP server? It seems conceptually
meaningless to me.
Is this even supposed to work? At the end of the day, I want to have data that is uniquely tagged and sent spontaneously on an irregular basis by the client to be associated with particular EPICS records, according to the tags, which I'm attempting to match
in each streamDevice protocol. Any suggestions for alternative solutions are quite welcome.
Thanks.
Rod Nussbaumer
TRIUMF
Vancouver, Canada.
|