这段时间公司项目上需要把数据通过OPC SERVER 开放接口,在网上及CSDN上 SEARCH 了好久,感觉CSDN上的资料还是比较专业的,但也有做广告的,我摸索了一周时间,有点心得,并用在工程上,一下是一周体会;
一:WINTECH 的开发包
WINTECH 网站上可下载开发包,WTOPCSVR.DLL 有VB VC VC#的例程,但是DEMO DLL,可运行30分钟,比较容易上手,但对开发者来说不能从内核上了解OPC SERVER ,而且需要银子。
二:南京一位高手写的 OPC 开发指南,并且提供SERVER 代码,代码是可以编译通过的,但是用客户机程序测试,连接也成功,就是添加TAG时提示找系统保留项目,ERROR,我用远程需跟踪了一下,是ADDTIEM 函数中XXXPAN 指针为空,研究一天,没有结果,放弃。说实话书写的确实挺实用的。
三:开源LIGHTOPC 开发
CSDN上下载俄罗斯高手开源LIGHTOPC源代码,很好能编译过去,两个DLL,头文件及LIB库,同时又下载了高手写的MODBUSTOOPC MFC程序,用的就是LIGHTOPC 支持,说实话,MFC是十分灵活,但开发UI就差强人意了,CLOCLEMODBUS 及CMODBUS 两个RTU方式通讯堪称经典,几乎就是MODBUS 的核心,核心是myClassFactory类及opcsrv.cpp ,详细的代码描述了如何引用LIGHTOPC服务器,关键函数是int driver_init(int lflags)初始化TAG及work_loop(unsigned pause, unsigned *nTerminate) 刷新TAG数据,我们只要更改这两个函数的内容就可以将自己的数据放入OPC SERVER ,其他OPC CLIENT 如KEPWARE 及组态王等就可取得我们的数据了。
下面是两个函数的引用
//初始化ITEM TAGS
int driver_init(int lflags)
{
loDriver ld;
VARIANT var;
int ecode;
setlocale(LC_CTYPE, "");
if (my_service)
{
UL_ERROR((LOGID, "Driver already initialized!"));
return 0;
}
memset(&ld, 0, sizeof(ld)); /* basic server parameters: */
// ld.ldRefreshRate = 3;//10;
ld.ldSubscribe = activation_monitor;
ld.ldWriteTags = WriteTags;
ld.ldReadTags = ReadTags;
ld.ldConvertTags = ConvertTags;
#if 0
ld.ldAskItemID = AskItemID;
#endif
ld.ldFlags = lflags | loDF_IGNCASE | /*loDf_FREEMARSH | loDf_BOTHMODEL | */
/*loDF_NOCOMP| */ loDf_NOFORCE & 0 /*| loDF_SUBSCRIBE_RAW*/;
/*Fix the Bug in ProTool *//*|loDF_IGNCASE */ ;
ld.ldBranchSep = '/'; /* Hierarchial branch separator */
ecode = loServiceCreate(&my_service, &ld, TI_MAX /* number of tags in the cache */);
/* 500000 is ok too */
UL_TRACE((LOGID, "%!e loCreate()=", ecode));
if (ecode) return -1;
InitializeCriticalSection(&lk_values);
memset(tv, 0, sizeof(tv)); /* instead of VariantInit() for each of them */
memset(ta, 0, sizeof(ta)); /* instead of VariantInit() for each of them */
VariantInit(&var);
/* OPTIONAL: Tags creation. */
{
char name[64];
unsigned int ii, i;
memset(name, 0, sizeof(name));
V_I2(&var) = 0;
V_VT(&var) = VT_I2; //all tags are 2-byte integers
ii = i = 0;
for(; ii < i + tnum[TI_holding_register] && !ecode; ii++)
{
sprintf(name, "%s%s%02u", ttn, tcn[TI_holding_register], ii-i);
V_I2(&var) = 0;
V_VT(&var) = VT_I2;
ecode = loAddRealTag(my_service, /* actual service context */
&ti[ii+1], /* returned TagId */
(loRealTag) (ii+1), /* != 0 driver's key */
name, /* tag name */
0, /* loTF_ Flags */
OPC_READABLE | OPC_WRITEABLE, &var, 0, 0);
tg[ii+1] = TI_holding_register;
tin[ii+1] = ii-i;
}
UL_TRACE((LOGID, "%!e num of loAddRealTag(%s) = %u ", ecode, tcn[TI_holding_register], ii-i));
LogMsg( 0, "Added %d tags to group %s ", ii-i, tcn[TI_holding_register] );
i = ii;
for(; ii < i+ tnum[TI_output_coil] && !ecode; ii++)
{
sprintf(name, "%s%s%02u", ttn, tcn[TI_output_coil], ii);
ecode = loAddRealTag(my_service, /* actual service context */
&ti[ii+1], /* returned TagId */
(loRealTag)(ii+1), /* != 0 driver's key */
name, /* tag name */
0, /* loTF_ Flags */
OPC_READABLE | OPC_WRITEABLE, &var, 0, 0);
tg[ii+1] = TI_output_coil;
tin[ii+1] = ii-i;
}
UL_TRACE((LOGID, "%!e num of loAddRealTag(%s) = %u ", ecode, tcn[TI_output_coil], ii-i));
LogMsg( 0, "Added %d tags to group %s ", ii-i, tcn[TI_output_coil] );
i = ii;
for(; ii < i + tnum[TI_input_status] && !ecode; ii++)
{
sprintf(name, "%s%s%02u", ttn, tcn[TI_input_status], ii);
V_I2(&var) = 0;
V_VT(&var) = VT_I2;
ecode = loAddRealTag(my_service, /* actual service context */
&ti[ii+1], /* returned TagId */
(loRealTag) (ii+1), /* != 0 driver's key */
name, /* tag name */
0, /* loTF_ Flags */
OPC_READABLE, &var, 0, 0);
tg[ii+1] = TI_input_status;
tin[ii+1] = ii-i;
}
UL_TRACE((LOGID, "%!e num of loAddRealTag(%s) = %u ", ecode, tcn[TI_input_status], ii-i));
LogMsg( 0, "Added %d tags to group %s ", ii-i, tcn[TI_input_status] );
i = ii;
for(; ii < i + tnum[TI_input_register] && !ecode; ii++)
{
sprintf(name, "%s%s%02u", ttn, tcn[TI_input_register], ii);
V_I2(&var) = 0;
V_VT(&var) = VT_I2;
ecode = loAddRealTag(my_service, /* actual service context */
&ti[ii+1], /* returned TagId */
(loRealTag) (ii+1), /* != 0 driver's key */
name, /* tag name */
0, /* loTF_ Flags */
OPC_READABLE, &var, 0, 0);
tg[ii+1] = TI_input_register;
tin[ii+1] = ii-i;
}
UL_TRACE((LOGID, "%!e num of loAddRealTag(%s) = %u ", ecode, tcn[TI_input_register], ii-i));
LogMsg( 0, "Added %d tags to group %s ", ii-i, tcn[TI_input_register] );
}
return 0;
}
void driver_destroy(void)
{
if (my_service)
{
int ecode = loServiceDestroy(my_service);
UL_INFO((LOGID, "%!e loDelete(%p) = ", ecode));
for(ecode = 0; ecode < sizeof(tv) / sizeof(tv[0]); ecode++)
VariantClear(&tv[ecode].tvValue);
DeleteCriticalSection(&lk_values);
my_service = 0;
}
}
/********* Data workloop stuff ************************************************/
void work_loop(unsigned pause, unsigned *nTerminate)
{
DWORD hitime = 0;
unsigned starttime = GetTickCount();
FILETIME ft;
GetSystemTimeAsFileTime(&ft); /* awoke */
UL_WARNING((LOGID, "Workloop Started..."));
EnterCriticalSection(&lk_values);
/* Set up initial values for the tags we didn't initialized in driver_init(): */
int ii = 0;
for (ii = 0; ii < TI_MAX;ii++)
{
tv[ii+1].tvTi = ti[ii+1];
tv[ii+1].tvState.tsError = S_OK;
tv[ii+1].tvState.tsQuality = OPC_QUALITY_UNCERTAIN;
V_VT(&tv[ii+1].tvValue) = VT_I2;
//V_I2(&tv[ii+1].tvValue) = 0;
V_I2(&tv[ii+1].tvValue) = ii;
tv[ii+1].tvState.tsTime = ft;
}
loCacheUpdate(my_service, TI_MAX, tv, 0);
LeaveCriticalSection(&lk_values);
/**** Then do simulate ***********/
while(0 != my_CF.server_inuse && !(*nTerminate) ) /* still working? */
{
Sleep(10);
GetSystemTimeAsFileTime(&ft); /* awoke */
/* OPTIONAL: reload log's configuration fromtime to time */
if (hitime != (ft.dwLowDateTime & 0xf8000000)) /* 13.5 sec */
{ /* 0xff000000 is about 1.67 sec */
hitime = ft.dwLowDateTime & 0xf8000000;
#if 0
unilog_Refresh(0); /* all logs */
#else
unilog_Refresh("XXX-OPC");
unilog_Refresh("XXX-OPC-MODBUS-EXE");
#endif
}
//The main job: poll data from PLC, update the values
/*int ii = 0;
for (ii = 0; ii < TI_MAX; )
{
if ( !ta[ii+1] )
{
ii++;
continue;
}
//read 10 words a time, speed up polling
EnterCriticalSection(&lk_values);
WORD wa[10]={0};
if ( GetDrvData10( DEVICE_ID, tg[ii+1], tin[ii+1], wa ))
{
for ( int i = 0 ; i < 10; i++)
{
V_I2(&tv[ii+1+i].tvValue) = (short)wa[i];
V_VT(&tv[ii+1+i].tvValue) = VT_I2;
tv[ii+1+i].tvState.tsQuality = OPC_QUALITY_GOOD;
tv[ii+1+i].tvState.tsError = S_OK;
tv[ii+1+i].tvState.tsTime = ft;
}
}
else
{
for ( int i = 0 ; i < 10; i++)
{
V_VT(&tv[ii+1+i].tvValue) = VT_EMPTY;
tv[ii+1+i].tvState.tsQuality = OPC_QUALITY_DEVICE_FAILURE;
tv[ii+1+i].tvState.tsError = S_FALSE;
tv[ii+1+i].tvState.tsTime = ft;
}
}
ii += 10;
// MANDATORY: send all the values into the cache:
loCacheUpdate(my_service, TI_MAX, tv, 0);
LeaveCriticalSection(&lk_values);
Sleep(10); // main delay
}*/
int ii = 0;
for (ii = 0; ii < TI_MAX; )
{
if ( !ta[ii+1] )
{
ii++;
continue;
}
//read 10 words a time, speed up polling
EnterCriticalSection(&lk_values);
WORD wa[10]={0};
for ( int i = 0 ; i < 10; i++)
{
srand(time(NULL));//因为这个地方要以时间为轴作种子
wa[i]=(WORD)rand()%100; //这里表示产生0~9的随机数
V_I2(&tv[ii+1+i].tvValue) = (short)wa[i];
V_VT(&tv[ii+1+i].tvValue) = VT_I2;
tv[ii+1+i].tvState.tsQuality = OPC_QUALITY_GOOD;
tv[ii+1+i].tvState.tsError = S_OK;
tv[ii+1+i].tvState.tsTime = ft;
}
ii += 10;
// MANDATORY: send all the values into the cache:
loCacheUpdate(my_service, TI_MAX, tv, 0);
LeaveCriticalSection(&lk_values);
Sleep(10); // main delay
}
if (pause)
{
/* IMPORTANT: force unloading of an out-of-proc
if not connected during PAUSE millisec */
unsigned tnow = GetTickCount();
if (tnow - starttime >= pause)
{
pause = 0;
my_CF.serverRemove();
}
}
} /* end of loop */
if (0 != my_CF.server_inuse && (*nTerminate) )
{
loSetState( my_service, 0, loOP_SHUTDOWN, 0, "User aborted" );
}
if ( *nTerminate )
UL_MESSAGE((LOGID, "Aborting..."));
else
UL_MESSAGE((LOGID, "All clean. exiting..."));
}
/***************************************************************************
EXE-specefic stuff
***************************************************************************/
const char eClsidName[] = "XXX OPC Server for Modbus";
const char eProgID[] = "XXX.OPC.MODBUS";
HMODULE server_module = 0;
int callsvr(HINSTANCE hInstance, unsigned *nTerminate, BOOL bEmbedding, unsigned nRegister );
int callsvr(HINSTANCE hInstance, unsigned *nTerminate, BOOL bEmbedding, unsigned nRegister )
{
int main_rc = 0;
DWORD objid;
int daemon = 0, pause = 20;
const char *exit_msg = "Exiting...";
server_module = hInstance;
log = unilog_Create("XXX-OPC-exe", "|XXX-OPC-exe", "%!T", -1, /* Max filesize: -1 unlimited, -2 -don't change */
ll_MESSAGE); /* level [ll_FATAL...ll_DEBUG] */
unilog_Redirect("XXX-OPC-exe", "XXX-OPC", 0);
unilog_Delete(log);
log = unilog_Create("XXX-OPC-MODBUS-EXE", "|XXX-OPC-MODBUS-EXE", "", -1, /* Max filesize: -1 unlimited, -2 -don't change */
ll_TRACE); /* level [ll_FATAL...ll_DEBUG] */
//UL_DEBUG((LOGID, "WinMain(%s) invoked...", argv[0]));
if ( 1 == nRegister )
{
char np[FILENAME_MAX + 32];
GetModuleFileName(NULL, np + 1, sizeof(np) - 8);
np[0] = '"'; strcat(np, "/"");
if (loServerRegister(&CLSID_XXXOPCServerEXE,
eProgID, eClsidName, np, 0))
{
UL_ERROR((LOGID, "%!L Reg <%s> <%s> Failed", eProgID, np));
main_rc = 1;
LogMsg( 0, "Failed to register server to system" );
}
else
{
UL_INFO((LOGID, "Reg <%s> <%s> Ok", eProgID, np));
LogMsg( 0, "Registered server to system" );
}
}
else if ( 2 == nRegister )
{
if (loServerUnregister(&CLSID_XXXOPCServerEXE, eProgID))
{
UL_WARNING((LOGID, "%!L UnReg <%s> Failed", eProgID));
main_rc = 1;
}
else
{
UL_DEBUG((LOGID, "UnReg <%s> Ok", eProgID));
}
}
/* Attempt to start the server */
my_CF.is_out_of_proc = my_CF.server_inuse = 1;
if (FAILED(CoInitializeEx(NULL, COINIT_MULTITHREADED)))
{
exit_msg = "CoInitializeEx() failed. Exiting...";
UL_ERROR((LOGID, exit_msg));
goto Finish;
}
if ( bEmbedding )
pause = 6; /* 3 sec can be too short for remote connections */
else
pause = 0;
if (driver_init(0))
{
exit_msg = "driver_init() failed. Exiting...";
UL_ERROR((LOGID, exit_msg));
}
else if (FAILED(CoRegisterClassObject(CLSID_XXXOPCServerEXE, &my_CF,
CLSCTX_LOCAL_SERVER |
CLSCTX_REMOTE_SERVER |
CLSCTX_INPROC_SERVER,
REGCLS_MULTIPLEUSE, &objid)))
{
exit_msg = "CoRegisterClassObject() failed. Exiting...";
UL_ERROR((LOGID, exit_msg));
}
else
{
if (daemon)
pause = 0; // infinite
my_CF.serverAdd(); /* Oops. This will prewent our server from unloading
till PAUSE elapsed and simulate() do my_CF.serverRemove() */
/* STARTing the workloop */
work_loop(pause * 1000, nTerminate); /* sec -> millisec; */
/* FINISHED */
if (FAILED(CoRevokeClassObject(objid)))
UL_WARNING((LOGID, "CoRevokeClassObject() failed..."));
}
driver_destroy();
CoUninitialize();
Finish:
LogMsg( 0, "Exiting...");
unilog_Delete(log);
log = 0;
return main_rc;
}
/******************* This is the end... ************************************/
感谢开源的俄罗斯哥们
感谢MODBUSTOOPC 开发者
感谢CSDN 下载,就是商业味太浓了,积分太少,上CSDN下资料的基本是学习及工作用的,不要太商业了,我反对。
李广,徐州恺尔电子设备公司 开发工程师
OPC CLIENT 比较简单,网上源码一堆,但是C#OPCAUTOMATION.DLL的引用提示创建组错误,按照网上提示安装KEPWARE SERVEREX后正常,是什么原因,同样代码在VB6上很好,这是谁的BUG ,难道是VS2005,????????