How to develop OPC Server (Hair Month)

xiaoxiao2021-03-06  62

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 ServerTickRate)) Actualrate = PAPP-> ServerTickRate;

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

转载请注明原文地址:https://www.9cbs.com/read-117220.html

New Post(0)