First let's take a look at what is OPC.
OLE for process control is an industrial standard interface based on Microsoft's DNA (Distributed Internet Application) architecture and COM (Component Object Model) technology, which is designed according to easy scalability.
Let's take a look at the use of OPC.
OPC is mainly suitable for application fields such as process control and manufacturing automation. OPC is a communication standard for an OLE / COM mechanism as an application. OLE / COM is a customer / server mode with the advantages of language independence, code reuse, and easy integration. The OPC specifies the interface function, regardless of the form of on-site equipment, customers go to the unified approach, so that the software's transparency to customers, so that users are completely detached from low-level development.
Then let's take a look at the composition of OPC Server
The OPC Server of a device mainly has two components, one is the implementation of the OPC standard interface; the second is the communication module of the hardware device.
Implement OPC standard interface
[figure 1]
In these interfaces, IOPCServer is the primary interface of the OPC Server, which implements installation and registration in OPC Server in the operating system. This interface must be implemented, all of which must also be implemented. Other interfaces are optional, we don't make an introduction, the following mainly introduces how to implement the IOPCServer interface.
There are six laws in the IOPCServer interface:
1, IOPCSERVER :: AddGroup
HRESULT AddGroup ([In, String] lpcwstr szname,
[in] BOOL BACTIVE,
DWORD DWREQUESTEDUPDATERATE,
Opchandle HclientGroup,
[unique, in] long * ptimebias,
[in] float * ppercentdeadband,
DWORD DWLCID,
[OUT] opchandle * phservergroup,
[OUT] DWORD * PREVISEDUPDATERATE,
Refiid Riid,
[OUT, IID_IS (RIID)] LPUNKNOWN * PPUNK);
This method is to create a group on the OPC Server. Under our way to implement this method:
.
.
First check the group name (SZNAME) to see if it is valid or if this group is already.
IF (szname! = NULL)
{
RequestedName = SZNAME;
IF (RequestedName == "")
RequestedName = psvrobject-> defaultgroupname ();
}
Else
RequestedName = psvrobject-> defaultgroupname ();
For (i = 0; i
Numbruns (); i )
{
PGROUP = psvrobject-> getGroup (i);
IF (RequestedName == PGROUP-> NAME)
Return (OPC_E_Duplicatename);
}
This requires the list of OPC Groups (groups) to maintain the list of OPC Groups (list of OPC items).
If the SZNAME is correct and has not been established, the group is set according to the parameters passed, and the group will be added to the list of its own group to prepare.
IF (DWRequestedUpdaterate == 0) || (dwrequestedupdaterate
Else
{
Actualrate = dwrequestedupdaterate;
Minrate = PAPP-> ServerTickRate;
Actualrate = (minrate / 2);
Actualrate / = minrate;
Actualrate * = minrate;
}
AppRisedUpdaterate
* previsedUpdaterate = actualrate;
PGROUP = New (CopcGroup);
IF (PGROUP == NULL)
Return (E_OUTOFMEMORY);
PGROUP-> name = RequestedName
PGROUP-> PSVROBJECT = PSVROBJECT;
PGROUP-> MarkedFordeletion = false;
PGROUP-> ClientGroupHandle = hclientgroup;
PGROUP-> Updaterate = actualrate;
PGROUP-> isactive = BACTIVE;
IF (PPERCENTDEADBAND)
PGROUP-> deadband = * ppercentdeadband;
Else
PGROUP-> deadband = 0.0;
PGROUP-> LCID = DWLCID;
IF (PTIMebias)
PGROUP-> TIMEBIAS = * ptimebias;
Else
{
_ftime (& TimeBuffer);
PGROUP-> TIMEBIAS = TIMEBUFFER.Timezone;
// pGroup-> Timebias = 300L;
}
R1 = pgroup-> queryinterface (riid, (lpvoid *) PPUNK);
IF (Failed (R1))
{
// if error - Delete Group and Return
Delete (pgroup);
Return R1;
}
PSVROBJECT-> AddNewGroup (PGROUP);
Finally, return the new group interface pointer to the client.
* phservergroup = pgroup-> ServerGroupHandle;
2, IOPCSERVER :: getRrorstring
HRESULT GETERRORSTRING ([in] HRESULT DWERROR,
[in] LCID DWLOCALE,
[OUT, STRING] LPWSTR * PPSTRING);
Returns the corresponding error string for the error code for Server.
Char buf [128];
Bool bfound = false;
For (int i = 0; i Opcerror * e = & opcerrors [i ]; IF ((bFound = (HR == E-> HRERR))! = false) { STRCPY (BUF, E-> Errtext); Break; } } IF (! bfound) { DWORD DWSTATUS = FormatMessage (Format_Message_From_System | Format_message_argument_Array, // arguments is not a va_list Null, // lpcvoid pointer to message Source Hr, // dword request message Identifier LANG_NEUTRAL, / / DWORD LANGUAGE IDENTIFIER for Message BUF, // LPTSTR POINTER TO Message Buffer 127, // DWord Maximum size of message buffer NULL); // va_list * arguments address of array of message inserts IF (! dwstatus) { _Snprintf (buf, 127, " "HR, HR); } } * ppstring = papp-> wstrfromcstring (buf, true); 3, IOPCSERVER :: getGroupbyname HRESULT GETGROUPBYNAME ([IN, String] lpcwstr szname, Refiid Riid, [OUT, IID_IS (RIID)] LPUNKNOWN * PPUNK); The interface pointer of the group is found by the specified group name (established by the same client). This method is relatively simple, as long as the interface pointer of the group is found in the group list according to the provided name loop, and return to the client * PPUNK = 0; RequestedName = szgroupname; For (i = 0; i Numbruns (); i ) { PGROUP = psvrobject-> getGroup (i); IF (pgroup-> name == RequestedName) { R1 = pgroup-> queryinterface (riid, (lpvoid *) PPUNK); Return (R1); } } 4, IOPCSERVER :: GetStatus HRESULT GETSTATUS ([OUT] OPCSERVERSTATUS ** PPSERVERSTATUS; Returns the status information of the current Server. This method is relatively simple, but to note that memory allocation is performed before using OPCSerVersTaus. PSERVERSTATUS = (opcserverstatus *) PAPP-> Alloczero (SIZEOF (OPCServerstatus)); IF (pserverstatus == null) Return (E_OUTOFMEMORY); PSERVERSTATUS-> Szvendorinfo = PAPP-> WSTRMCSTRING (PAPP-> Vendorinfo, true); PSERVERSTATUS-> ftstarttime = PAPP-> opcserverstarttime; Cofiletimenow (& PServersTatus-> ftcurrenttime); PSERVERSTATUS-> FTLASTUPDATETIME = PSVROBJECT-> MLASTUPDATE; // rwd allow user to manipulate returned opcserverstatus, Lined Up Layout for Clarity ... PSERVERSTATUS-> dwserverstate = PAPP-> ServerState; // endrwd PSERVERSTATUS-> DWGROUPCOUNT = 0; PServerstatus-> dwbandwidth = 0; PSERVERSTATUS-> Wmajorversion = 0; PSERVERSTATUS-> WMINORVERSION = 0; PServersTatus-> wbuildnumber = 0; PSERVERSTATUS-> WRESERVED = 42; Return to server status * ppserverstatus = pserverstatus; 5, IOPCSERVER :: RemoveGroup HRESULT REMOVEGROUP ([in] opchandle hservergroup, "in] bool bforte); Remove the specified group from the server Find the specified group in the group list and delete it. For (i = 0; i Numbruns (); i ) { PGROUP = psvrobject-> getGroup (i); IF (grouphandleid == pgroup-> servergrouphandle) { PSVROBJECT-> RemoveGroup (i); // if no outstanding References // delete it IF (pgroup-> refcount == 0) { Psvrobject-> lockgrouplist (); Delete (pgroup); Psvrobject-> unlockgrouplist (); } Else if (bforce) { DeletedGroupList.Add (COBJECT *) PGROUP); } Else { PGROUP-> MarkedFordeletion = true; PGROUP-> PSVROBJECT = NULL; Return (OPC_S_INUSE); } Return (S_OK); } } 6, IOPCSERVER :: CreateGroupENUMERATOR HRESULT CREATEGROUPENUMERATOR ([in] opcenumscope dwscope, Refiid Riid, [OUT, IID_IS (RIID)] LPUNKNOWN * PPUNK); Set different enumerations for the group provided on Server. IF (riid == iid_ienumunknown) { Penumerator = New (Copcgroupenum); IF (penumerator == null) Return (E_OUTOFMEMORY); Penumerator-> PSVROBJECT = PSVROBJECT; Penumerator-> addRef (); // Will Increment Reference Count // for Both the Enumerator & Server // CopyGroupList Will AddRef Each Group Enumerated PSVROBJECT-> COPYGROUPLIST (DWScope, & (Penumerator-> Grouplist); * PPUNK = Penumerator; IF (penumerator-> grouplist.getsize ()> 0) Return (S_OK); Else Return (S_FALSE); } IF (riid == iid_ienumstring) { Pstrenumerator = New (Ciopcstringenum); IF (pstrenumerator == null) Return (E_OUTOFMEMORY); Pstrenumerator-> addRef (); PSVROBJECT-> COPYGroupNameList (dwscope, & (pstrenumerator-> namelist); * PPUNK = Pstrenumerator; IF (pstrenumerator-> namelishst.getsize ()> 0) Return (S_OK); Else Return (S_FALSE); } The above is developed directly using COM technology, which requires you to be familiar with COM technology. If you don't matter to COM, you have no relationship. You can choose OPC Server development tools. You only need to simply call the function of the development tool to implement all interfaces in the OPC Server. Although we implemented all the methods in the IOPCServer interface, it is just a bridge with our communications, the most important thing is to maintain the OPC Group and OPC Item list. This allows us to real data communication with the OPC Client. Communication with hardware devices The interface has been implemented, this time we will read data from the hardware device to the OPC Client. There are many ways with hardware devices. If you are a device's manufacturer, you can do direct data directly; you can also operate through the API provided by the device driver and hardware vendors or by TCP, serial port. This is to see what the interface provided by hardware devices and software communications is. No matter what method you use, just use the data from the hardware device and associate with the OPC item, you can implement OPC Server. Due to the limitations of time and the author level, it is inevitable that there is a mistake and improper, please ask everyone to criticize and correct. My email is yanghongtao@thtf.com.cn. Finally, I would like to thank Dr. Zhou Hongbo and Lucheng, Dr. Lu Sheng, a manager of Tsinghua Limited Co., Ltd., to develop and learn OPC, so that I understand OPC and write this article. 2004-2-11 Yang Hongtao