1) 用makeBaseApp.pl新建一个EPICS IOC的目录结构:
root@orangepi5:/usr/local/EPICS/program/testConnect# ls
configure iocBoot Makefile testConnectApp
2) 编辑configure目录下的RELEASE文件,定义asyn模块所在的路径:
...
SUPPORT=/usr/local/EPICS/synApps/support
ASYN=$(SUPPORT)/asyn
...
3) 进入testConnectApp目录下的src目录,编写用于连接测试的c++源程序myConnect.cpp:
此程序运行时,每个1秒,向网络输出一个"Hello World"字符串并且从网络回读一个字符串。
/* myConnect derived from asynPortDriver to test Connect */
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "epicsThread.h"
#include "epicsString.h"
#include "iocsh.h"
#include "asynPortDriver.h"
#include "asynOctetSyncIO.h"
#include "epicsExport.h"
static const char * driverName = "myConnect";
#define POLLER_PERIOD 1
#define MAX_RESPONSE_LEN 256
class myConnect : public asynPortDriver {
public:
myConnect(const char * portName, const char * IPPortName, const char * outputString);
void pollerTask(void);
private:
asynUser * pasynUserIPPort_; // asynUser to connect to this driver
const char * outputString_; // used for storing the input string
};
static void pollerTask(void * drvPvt);
myConnect::myConnect(const char * portName, const char * IPPortName, const char * outputString)
: asynPortDriver(portName, 1, asynOctetMask, 0, ASYN_CANBLOCK, 1, 0, 0)
{
asynStatus status;
const char * functionName = "myConnect";
// copy the input string, and point to the copy
outputString_ = epicsStrDup(outputString);
// connect to the driver and get the asynUser
// parameters:1) char *port, 2)int addr, 3)asynUser **ppasynUser, 4) const char *drvInfo
status = pasynOctetSyncIO->connect(IPPortName, 0, &pasynUserIPPort_, NULL);
if (status)
{
printf("%s:%s:pasynOctetSyncIO->connect failure, status = %d\n", driverName, functionName, status);
return;
}
// if NULL, create epicsThread failed
status = (asynStatus)(epicsThreadCreate("myConnectTask", epicsThreadPriorityMedium,
epicsThreadGetStackSize(epicsThreadStackMedium),
(EPICSTHREADFUNC)::pollerTask, this) == NULL);
if (status){
printf("%s:%s: epicsThreadCreate failure, status: %d\n", driverName, functionName, status);
return;
}
}
static void pollerTask(void * drvPvt)
{
myConnect * pPvt = (myConnect *)drvPvt;
pPvt->pollerTask();
}
void myConnect::pollerTask(void)
{
asynStatus status;
char response[MAX_RESPONSE_LEN];
size_t numWrite, numRead;
int isConnected;
int eomReason;
static const char * functionName = "pollerTask";
while (1){
lock();
status = pasynManager->isConnected(pasynUserIPPort_, &isConnected);
if (status){
// pasynUserSelf used to connect to ourselves for asynTrace, it defined in asynPortDriver
asynPrint(pasynUserSelf, ASYN_TRACE_ERROR, "%s:%s: error calling pasynManager->isConnected, status=%d, error=%s\n",
driverName, functionName, status, pasynUserIPPort_->errorMessage);
}
asynPrint(pasynUserSelf, ASYN_TRACEIO_DRIVER, "%s:%s: isConnected = %d\n",
driverName, functionName, isConnected);
status = pasynOctetSyncIO->writeRead(pasynUserIPPort_, outputString_, strlen(outputString_),
response, sizeof(response), 1.0, &numWrite, &numRead, &eomReason);
if (status){
asynPrint(pasynUserSelf, ASYN_TRACE_ERROR, "%s:%s: error calling pasynOctetSyncIO->writeRead, status=%d, error=%s\n",
driverName, functionName, status, pasynUserIPPort_->errorMessage);
}
else{
asynPrint(pasynUserSelf, ASYN_TRACEIO_DRIVER, "%s:%s: numWrite=%ld, wrote:%s, numRead=%ld, response=%s\n",
driverName, functionName, (long)numWrite, outputString_, (long)numRead, response);
}
unlock();
epicsThreadSleep(POLLER_PERIOD);
}
}
extern "C"{
int myConnectConfigure(const char * portName, const char * IPPortName, const char * outputString)
{
new myConnect(portName, IPPortName, outputString);
return asynSuccess;
}
/* EPICS iocsh shell commands */
static const iocshArg initArg0 = {"portName", iocshArgString};
static const iocshArg initArg1 = {"IPPortName", iocshArgString};
static const iocshArg initArg2 = {"output string", iocshArgString};
static const iocshArg * const initArgs[] = {&initArg0, &initArg1, &initArg2};
static const iocshFuncDef initFuncDef = {"myConnectConfigure", 3, initArgs};
static void initCallFunc(const iocshArgBuf * args)
{
myConnectConfigure(args[0].sval, args[1].sval, args[2].sval);
}
void myConnectRegister(void)
{
iocshRegister(&initFuncDef, initCallFunc);
}
epicsExportRegistrar(myConnectRegister);
}
4)编写3)步骤中编写源程序对应的支持文件myConnect.dbd:
registrar(myConnectRegister)
5)编写与3)和4)相同路径下的Makefile文件,添加以下内容:
...
# support dbd file
testConnect_DBD += asyn.dbd
testConnect_DBD += drvAsynIPPort.dbd
testConnect_DBD += myConnect.dbd
# Add all the support libraries needed by this IOC
testConnect_LIBS += asyn
# source files to be compiled
testConnect_SRCS += myConnect.cpp
...
6) 回到顶层目录,执行make,进行编译。
7)进入IOC启动目录,编辑启动文件st.cmd:
#!../../bin/linux-aarch64/testConnect
#- You may have to change testConnect to something else
#- everywhere it appears in this file
< envPaths
cd "${TOP}"
## Register all support components
dbLoadDatabase "dbd/testConnect.dbd"
testConnect_registerRecordDeviceDriver pdbbase
# This is a real IP address that will connect
drvAsynIPPortConfigure("IPPort", "192.168.3.208:9999", 0, 0, 1);
myConnectConfigure("MYPORT", "IPPort", "Hello World")
#asynSetTraceMask("PORT1",0,9)
asynSetTraceIOMask("MYPORT",0,0x2)
dbLoadRecords("$(ASYN)/db/asynRecord.db","P=testConnect:,R=asyn1,PORT=MYPORT,ADDR=0,OMAX=80,IMAX=80")
cd "${TOP}/iocBoot/${IOC}"
iocInit
8) 编写一个python tcp服务程序,用于对测试以上连接程序:
此程序运行时,监听网络,接收一个套接字连接后建立TCP连接,从连接套接字读取字符串,并且在这个字符串前加上Hello后,重新发送给连接套接字。
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import socket
import threading
import time
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('0.0.0.0', 9999))
s.listen(5)
print('Waiting for connection...')
def tcplink(sock, addr):
print('Accept new connection from %s:%s...' %addr)
sock.send(b'Welcome!')
while True:
data = sock.recv(1024)
print(data)
time.sleep(0.2)
if not data or data.decode('utf-8') == 'exit':
break
sock.send(('Hello, %s!' % data.decode('utf-8')).encode('utf-8'))
sock.close()
print('Connection from %s:%s closed.' %addr)
while True:
sock, addr = s.accept()
t = threading.Thread(target=tcplink, args=(sock, addr))
t.start()
9)启动以上python程序,等待连接:
orangepi@orangepizero2:~/python$ ./tcpserver.py
Waiting for connection...
10) 启动IOC程序:
root@orangepi5:/usr/local/EPICS/program/testConnect/iocBoot/ioctestConnect# ../../bin/linux-aarch64/testConnect st.cmd
#!../../bin/linux-aarch64/testConnect
< envPaths
epicsEnvSet("IOC","ioctestConnect")
epicsEnvSet("TOP","/usr/local/EPICS/program/testConnect")
epicsEnvSet("SUPPORT","/usr/local/EPICS/synApps/support")
epicsEnvSet("ASYN","/usr/local/EPICS/synApps/support/asyn")
epicsEnvSet("EPICS_BASE","/usr/local/EPICS/base")
cd "/usr/local/EPICS/program/testConnect"
## Register all support components
dbLoadDatabase "dbd/testConnect.dbd"
testConnect_registerRecordDeviceDriver pdbbase
# This is a real IP address that will connect
drvAsynIPPortConfigure("IPPort", "192.168.3.208:9999", 0, 0, 1);
myConnectConfigure("MYPORT", "IPPort", "Hello World")
#asynSetTraceMask("PORT1",0,9)
asynSetTraceIOMask("MYPORT",0,0x2)
dbLoadRecords("/usr/local/EPICS/synApps/support/asyn/db/asynRecord.db","P=testConnect:,R=asyn1,PORT=MYPORT,ADDR=0,OMAX=80,IMAX=80")
cd "/usr/local/EPICS/program/testConnect/iocBoot/ioctestConnect"
iocInit
Starting iocInit
############################################################################
## EPICS R7.0.7
## Rev. 2023-05-26T09:07+0000
## Rev. Date build date/time:
############################################################################
iocRun: All initialization complete
## Start any sequence programs
#seq sncxxx,"user=orangepi"
epics> dbl
testConnect:asyn1
epics>
11) 观察python tcp服务程序的输出,不断接收到"Hello World"
orangepi@orangepizero2:~/python$ ./tcpserver.py
Waiting for connection...
Accept new connection from 192.168.3.191:59798...
b'Hello World'
b'Hello World'
b'Hello World'
b'Hello World'
b'Hello World'
b'Hello World'
...
13) 启动asynRecord.adl界面:
medm -x -macro "P=testConnect:,R=asyn1" asynRecord.adl &
启动traceIODriver选项,可以观察到服务器发送给这个连接测试程序的应答字符串:
epics> 2023/08/24 11:13:15.131 myConnect:pollerTask: isConnected = 1
2023/08/24 11:13:15.334 myConnect:pollerTask: numWrite=11, wrote:Hello World, numRead=19, response=Hello, Hello World!
2023/08/24 11:13:16.335 myConnect:pollerTask: isConnected = 1
2023/08/24 11:13:16.537 myConnect:pollerTask: numWrite=11, wrote:Hello World, numRead=19, response=Hello, Hello World!
2023/08/24 11:13:17.538 myConnect:pollerTask: isConnected = 1
2023/08/24 11:13:17.741 myConnect:pollerTask: numWrite=11, wrote:Hello World, numRead=19, response=Hello, Hello World!
...
启动traceError选项后,断开IOC所在主机的网络后,可以观察到IOC的报错信息:
2023/08/24 11:15:33.378 myConnect:pollerTask: error calling pasynOctetSyncIO->writeRead, status=1, error=192.168.3.208:9999 timeout: Resource temporarily unavailable
2023/08/24 11:15:35.380 myConnect:pollerTask: error calling pasynOctetSyncIO->writeRead, status=1, error=192.168.3.208:9999 timeout: Resource temporarily unavailable
...