Capítulo 10 - Universidad de Sevilla

Anuncio
Proyecto Fin de Carrera
Implementación de algoritmos MPC con restricciones en mbed NXP LPC1768
Capítulo 10
Programación de un servidor OPC
Dpto. Ing. de Sistemas y Automática
Universidad de Sevilla
Página 201
Ramón
amón Jesús Cuesta Pérez
Curso 2011/2012
Proyecto Fin de Carrera
Implementación de algoritmos MPC con restricciones en mbed NXP LPC1768
10.1 Introducción al estándar de comunicación OPC
En el capítulo anterior conseguimos comunicar la mbed con un ordenador personal. Sin
embargo, tuvimos que escribir una aplicación específica para suministrare set-points
set
desde el PC. El objetivo de este capítulo es crear un servidor OPC en el ordenador que
reciba los datos de la mbed a través de Ethernet y que nos sirva de puente para conectar
el microcontrolador a cualquier equipo que incluya el estándar
estándar OPC sin necesidad de
realizar ninguna adaptación adicional.
OPC (OLE
OLE for Process Control)
Control es uno de los estándares industriales de comunicación
más populares que existen. Está basado en la tecnología DCOM (Distributed
(
Component Objetc Model)
Model de Microsoft®, una tecnología desarrollada para la
comunicación entre distintas aplicaciones presentes en un mismo computador o en una
red de ellos. Esta tecnología es el resultado de la evolución de OLE (Object
(Object Linking and
Embedding)) en su versión 1.0, que permitía el enlace e incrustación de documentos y
otros objetos entre distintas aplicaciones de Windows.
Windows
Se utiliza OPC para resolver uno de los problemas más habituales e incómodos de la
industria de la automatización: lograr la comunicación entre múltiples dispositivos,
controladores, hardware y aplicaciones software sin sufrir problemas de conectividad
derivados
dos de los drivers propietarios de cada elemento.
El secreto del éxito de OPC,
OPC, desarrollado por la organización sin ánimo de lucro OPC
Foundation™, es crear un estándar de comunicaciones independiente de los fabricantes
que abstrae la implementación en el origen y el destino de los datos de forma que
puedan intercambiar información sin que ninguno de los lados conozca nada sobre el
protocolo de comunicación ya la organización interna de datos del otro lado.
Así pues, el uso de OPC permite eliminar la necesidad
necesidad de emplear drivers propietarios
para interconectar cada elemento de una jerarquía de automatización. En su lugar cada
elemento de la red incluirá un servidor o cliente OPC, de modo que se simplifica
Dpto. Ing. de Sistemas y Automática
Universidad de Sevilla
Página 202
Ramón
amón Jesús Cuesta Pérez
Curso 2011/2012
Proyecto Fin de Carrera
Implementación de algoritmos MPC con restricciones en mbed NXP LPC1768
notablemente la arquitectura de la comunicación. Véase
Véase la siguiente figura para
comprender el gran avance que supone la interconexión de dispositivos vía OPC:
Drivers propietarios
Solución OPC
La solución que no incluye OPC requiere que cada dispositivo implemente un driver
específico para conectarse con cada uno de los otros elementos de la red. Sin embargo,
la solución OPC requiere un único “driver” OPC estandarizado por dispositivo que le
permite la interconexión con cada uno de los otros elementos.
e
Además de simplificar la arquitectura,
arqui
tanto clientes como fabricantes obtienen ventajas
muy notables con esta solución:
-
El cliente no tiene que modificar sus drivers cada vez que realice alguna
modificación o agregue algún nuevo equipo a su instalación.
-
El fabricante no tiene que desarrollar un driver específico para distintos equipos
para hacer a su producto accesible en el mercado. Le basta con desarrollar la
tecnología OPC para ofertar una conectividad sencilla y fiable con todos los
productos del mercado que también incluyen OPC, que en la actualidad son la
mayoría de dispositivos y software del mundo de la automatización.
Dpto. Ing. de Sistemas y Automática
Universidad de Sevilla
Página 203
Ramón
amón Jesús Cuesta Pérez
Curso 2011/2012
Proyecto Fin de Carrera
Implementación de algoritmos MPC con restricciones en mbed NXP LPC1768
Una vez expuesta la motivación de usar OPC vamos a introducir los conceptos
fundamentales de este estándar.
En
primer
lugar,
la
tecnología
OPC
funciona
utilizando
la
conectividad
servidor/cliente.. Un servidor puede recibir la conexión de múltiples clientes, así como
un cliente puede conectarse a distintos servidores a la vez.
•
El servidor OPC se asocia a las fuentes
es de datos, es decir, es un driver
estandarizado que interactúa con la información de los dispositivos (PLC, RTU,
páginas web, bases de datos, etc) y la pone a disposición de otros elementos de
la red. En definitiva, un servidor OPC es un conector entre el
el mundo real y el
mundo OPC. Por ello programaremos un servidor OPC y no un cliente para
conectar a la mbed con cualquier otro elemento.
•
El cliente OPC es un software programado para conectarse a los servidores
OPC. Conceptualmente representan un sumidero de datos, aunque la conexión
servidor/cliente es bidireccional. Técnicamente son aplicaciones incrustadas en
otras como HMI’s (Human-Machine
(Human Machine Interface) o históricos de datos.
Otro punto a tener en cuenta es la existencia de múltiples especificaciones OPC,
OPC que se
escogerán en función del objetivo de la aplicación o sistema. Es habitual que los
servidores puedan soportar varias especificaciones mientras que los clientes solo sean
programados para una de ellas. Las especificaciones existentes de OPC son las
siguientes:
− OPC Data Access (OPC DA): el servidor proporciona acceso a datos en tiempo
real. El cliente puede demandar el valor más reciente de cualquier variable del
proceso.
− OPC Historical Data Access (OPC HDA): se emplea para recuperar y analizar
datos históricos del proceso, que habitualmente estarán almacenados en una base
de datos o una RTU.
− OPC Alarm and Events (OPC A&E): proporciona una interfaz donde los
clientes son informados de alarmas y eventos del proceso.
Dpto. Ing. de Sistemas y Automática
Universidad de Sevilla
Página 204
Ramón
amón Jesús Cuesta Pérez
Curso 2011/2012
Proyecto Fin de Carrera
Implementación de algoritmos MPC con restricciones en mbed NXP LPC1768
− OPC Security:: especificación que
que concierne el uso de seguridad en la
programación de servidores OPC. Está basado en el modelo Windows NT
Security.
− OPC Batch:: especificación muy similar a OPC Data Access (OPC DA), pero
especialmente diseñada y optimizada para procesos de fabricación por
po lotes.
− OPC Data eXchange (OPC DX): esta especificación
ón define un conjunto de
interfaces que hace posible la conexión e intercambio de datos entre servidores.
OPC DX permite a servidores OPC DA intercambiar datos sin necesidad de
clientes intermedios.
− OPC eXtensible Markup Language (OPC XML): encapsula los datos del
proceso de manera que los hace disponibles desde cualquier sistema operativo.
Ofrece además una interfaz SOAP (Simple Object Application Protocol) que
permiten escribir los clientes en otros leguajes de programación distintos a C++
como Java, Perl o Python. Se utiliza HTTP como protocolo de transferencia de
datos para el tráfico con base en Internet, lo cual es más adecuado que DCOM.
− OPC Unified Architecture (OPC UA): es la siguiente generaci
ción de estándar
OPC, en el cual se sustituye la tecnología DCOM por .NET, también de
Microsoft®. Esto permite unificar en esta especificación todas las anteriores:
OPC DA, OPC HDA, OPC DX, OPC XML, etc. Se emplea también
SOAP/HTTP, lo cual permite un sencillo
sencillo desarrollo de clientes y servidores en
entornos que no sean de Microsoft®. Por último .NET proporciona una base más
segura que DCOM. Es de esperar que sea la especificación que se acabe
imponiendo.
Para este proyecto nos vamos a quedar con la primera especificación,
especificaci
OPC Data
Access,, pues nuestra necesidad es la comunicación en tiempo real con la mbed.
Pasamos ahora, pues, a comentar la arquitectura básica de OPC DA.
Dpto. Ing. de Sistemas y Automática
Universidad de Sevilla
Página 205
Ramón
amón Jesús Cuesta Pérez
Curso 2011/2012
Proyecto Fin de Carrera
Implementación de algoritmos MPC con restricciones en mbed NXP LPC1768
Arquitectura OPC Data Access
En primer lugar definimos la jerarquía del servidor OPC. Los datos y propiedades del
proceso representan el nivel de jerarquía más bajo y se asocian a objetos denominados
ítems o tags. A su vez, estos ítems se agrupan en los denominados grupos, que
representan el siguiente nivel de jerarquía.
jerarquí . La agrupación se realiza por motivos lógicos
(mismo tipo de variables, datos de una parte específica del proceso, etc.)
etc o por motivos
dinámicos (variables que cambian a una determinada frecuencia común en el proceso).
proceso)
El nivel superior de jerarquía es el propio servidor,, que es el encargado de obtener y
traducir la información real del proceso y almacenarla en los distintos ítems. Esta
jerarquía puede observarse de forma gráfica en la siguiente figura:
Por su parte, el cliente puede crear varios objetos en el servidor para definir su visión
del proceso, es decir, puede formar sus propios grupos compuestos por los ítems
disponibles en el servidor.
Al crear un grupo y asociar a él algún ítem, el cliente puede establecer los siguientes
parámetros:
-
Frecuencia de actualización (Update Rate): fija el intervalo en el que el cliente
toma datos del servidor.
-
Zona muerta (Dead Band): fija la variación mínima que tiene que tener el ítem
para recibir un nuevo valor
val en el cliente.
Dpto. Ing. de Sistemas y Automática
Universidad de Sevilla
Página 206
Ramón
amón Jesús Cuesta Pérez
Curso 2011/2012
Proyecto Fin de Carrera
Implementación de algoritmos MPC con restricciones en mbed NXP LPC1768
Cada vez que se actualiza un ítem seleccionado por el cliente, éste recibe del servidor
tres datos:
-
El valor del ítem.. Puede ser del tipo char, short, long, boolean, float, double, etc.
-
La hora de actualización del ítem (Time Stamp). Formato fecha y hora.
-
La calidad del dato.. Bueno (Good) , malo (Bad) o incierto (Uncertain).
(Uncertain)
De este modo queda definida la arquitectura básica de la especificación OPC Data
Access. En el siguiente apartado procederemos a la programación del servidor.
Objetivo
Para este proyecto vamos a crear un servidor OPC DA orientado al control de un
depósito de agua perteneciente a la planta de los cuatro tanques ubicado en el
laboratorio de control de la Escuela Superior de Ingenieros de la Universidad de Sevilla.
Necesitaremos monitorizar la altura del agua en el tanque, así
sí como la referencia
marcada por el usuario y el voltaje aplicado a la bomba por el microcontrolador.
Crearemos por tanto cuatro tags o ítems,, uno de entrada (referencia) y tres de salida
(altura, voltaje y referencia actual). Agruparemos los tags de entrada
rada y salida en dos
grupos homónimos. Diferenciamos “referencia” y “referencia
referencia actual”
actual porque al
introducir un nuevo set-point
point a través del cliente OPC, la mbed está programada para
realizar dicho cambio de referencia transcurridos N tiempos de muestreo. De este modo
aprovechamos la capacidad de anticipación al cambio de set-point
set point del control
predictivo. El motivo por el que creamos el tag referencia actual es para poder saber en
tiempo real cuándo ha cambiado realmente el set-point.
set point. Esto nos será fundamental
fundamen
también para la representación de la gráfica de y(t), donde siempre incluimos la
referencia en función del tiempo.
Pasemos ahora a la descripción del servidor OPC programado.
Dpto. Ing. de Sistemas y Automática
Universidad de Sevilla
Página 207
Ramón
amón Jesús Cuesta Pérez
Curso 2011/2012
Proyecto Fin de Carrera
Implementación de algoritmos MPC con restricciones en mbed NXP LPC1768
10.2 Programación de un servidor OPC
Para programar el servidor OPC DA vamos a utilizar el toolkit gratuito de Graybox
Software (se adjunta en el CD, ficheros del capítulo 10),
10), que nos aporta las herramientas
necesarias para la construcción del servidor. Este toolkit nos ofrece una serie de clases
para la implementación OPC, así como una serie de ejemplos que nos sirven de punto
de partida.
Tomaremos el ejemplo GraySim,
GraySim que trabaja además con clases ATL (Active Template
Library). ATL es un conjunto de conjunto de clases basadas en plantillas C++
desarrolladas por Microsoft® para simplificar la programación de objetos COM. Este
ejemplo crea una clase derivada por herencia múltiple de una clase padre ATL y otra
contenida en la librería del toolkit. A partir de ahí vamos a eliminar todos los tags y su
tratamiento por defecto para
para diseñar el servidor a nuestro antojo, utilizando las
funciones elementales de la librería para tal fin.
Para programar este servidor necesitamos usar Microsoft® Visual C++ 2005 o superior,
una versión que además contenga ATL7. Al igual que en el capítulo
lo anterior se utilizará
Microsoft® Visual Studio 2010. Para tener acceso a la librería del toolkit debemos
incluir el fichero gbda3.lib
.lib al proyecto (Proyecto
Proyecto > Propiedades del proyecto >
Propiedades de configuración > Vinculador > Entrada > Dependencias adicionales).
Además habrá que tener el fichero gbda3.dll en el mismo directorio que el ejecutable
del programa. Del mismo modo incluiremos los ficheros ws2_32.lib y ws2_32.dll,
necesarios para la implantación del socket TCP/IP que nos servirá para comunicar
comunic la
mbed con el servidor OPC.
Hay que tener en cuenta que, para que los cambios realizados en el servidor tengan
efecto, no basta con generar un nuevo ejecutable (graysim.exe),
(
), sino que hemos de
volver a registrarlo. Para ello utilizaremos las funciones –unregserver y –service en la
consola del Símbolo del sistema:
Dpto. Ing. de Sistemas y Automática
Universidad de Sevilla
Página 208
Ramón
amón Jesús Cuesta Pérez
Curso 2011/2012
Proyecto Fin de Carrera
Implementación de algoritmos MPC con restricciones en mbed NXP LPC1768
Una vez aclarado este punto pasamos a la explicación del código del servidor OPC. Para
tener acceso a todos los ficheros del proyecto de Visual Studio se recomienda recurrir al
CD, donde se adjunta en su totalidad. Veamos ahora la estructura del programa y sus
funciones más importantes..
El fichero principal del programa (graysim.cpp)
(
) contiene la definición de la clase
CGraySimulatorModule,
la cual incluye las funciones que implementan el servidor OPC,
así como el punto de entrada al programa. Los métodos o funciones de dicha clase se
explicarán a continuación, pero primero vamos a observar cómo empieza a funcionar el
programa. El código de este fichero, omitiendo
omitiendo la definición de la clase, es el siguiente:
#include "stdafx.h"
#include "resource.h"
#include "tagdescr.h"
#include "graysim.h"
#include <GB_OPCDA.h>
#include <stdio.h>
#define _USE_MATH_DEFINES
#include <cmath>
#include <winsock2.h>
#define SCANRATE 50
#define TAGDESCRCOUNT (sizeof
sizeof(TagDescr)/sizeof(TagDescr[0]))
Dpto. Ing. de Sistemas y Automática
Universidad de Sevilla
Página 209
Ramón
amón Jesús Cuesta Pérez
Curso 2011/2012
Proyecto Fin de Carrera
Implementación de algoritmos MPC con restricciones en mbed NXP LPC1768
//Definimos la clase con herencia múltiple
class CGraySimulatorModule :
public CAtlServiceModuleT< CGraySimulatorModule, IDS_SERVICENAME >,
public GBDataAccess
{
/* Definición de los métodos no heredados de la clase CGraySimulatorModule */
};
CGraySimulatorModule _AtlModule; //Declaramos un objeto de la clase CGraySimulatorModule
//Punto de entrada del program
extern "C" int WINAPI _tWinMain(HINSTANCE /*hInstance*/, HINSTANCE /*hPrevInstance*/,
/*hPrevInstance*/
LPTSTR /*lpCmdLine*/, int nShowCmd)
{
return _AtlModule.WinMain(nShowCmd);
}
El punto de entrada del programa se encuentra en
función
WinMain
del objeto
_AtlModule
_tWinMain,
de la clase
que a su vez llama a la
CGraySimulatorModule
que hemos
declarado previamente. Esta
sta función está definida en el fichero atlbase.h,
atlbase.h y a su vez
llama a la función
Start.
Esta función de ATL realiza una serie de inicializaciones
relacionadas con Win32 (Windows) para acabar llamando a la función
Run,
también en
atlbase.h. El código de esta función es el siguiente:
HRESULT Run(_In_ int nShowCmd = SW_HIDE) throw()
{
HRESULT hr = S_OK;
T* pT = static_cast<T*>(this);
static_cast
hr = pT->PreMessageLoop
PreMessageLoop(nShowCmd);
// LLama a RunMessageLoop solo si PreMessageLoop devuelve S_OK
if (hr == S_OK)
{
pT->RunMessageLoop
RunMessageLoop();
}
// Llama a PostMessageLoop si PreMessageLoop sale con éxito
if (SUCCEEDED(hr))
{
hr = pT->PostMessageLoop();
pT
}
ATLASSERT(SUCCEEDED(hr));
return hr;
}
Dpto. Ing. de Sistemas y Automática
Universidad de Sevilla
Página 210
Ramón
amón Jesús Cuesta Pérez
Curso 2011/2012
Proyecto Fin de Carrera
Implementación de algoritmos MPC con restricciones en mbed NXP LPC1768
Esta función es la que gestiona el funcionamiento del servidor: en primer lugar llama a
PreMessageLoop
(inicialización),
luego
invoca
a
RunMessageLoop
(bucle
de
funcionamiento normal del programa) y acaba llamando a PostMessageLoop (finalización
del programa). Estas tres funciones aparecen definidas en graysim.cpp, dentro de la
definición de la clase
CGraySimulatorModule.
Su código será mostrado y explicado un
poco más adelante, junto al resto de métodos de la clase, que pasamos a explicar
e
ahora.
Clase CGraySimulatorModule
Clase definida por herencia múltiple pública de las siguientes clases:
-
GBDataAccess,
derivada de GBOPCDataAccessBase, que a su vez es derivada de
GBClassFactory,
-
todas ellas pertenecientes al toolkit de Graybox.
CAtlServiceModuleT,
derivada de CAtlExeModule, que hereda de CAtlModuleT
que, a su vez, es derivada de CAtlModule, todas ellas pertenecientes a ATL.
Métodos públicos:
InitializeSecurity()
()
Llama a la función
CoInitializeSecurity,
que registra la seguridad y define los
valores por defecto de seguridad de un sistema COM.
HRESULT InitializeSecurity() throw()
{
return CoInitializeSecurity(NULL, -1,
1, NULL, NULL, RPC_C_AUTHN_LEVEL_PKT,
RPC_C_IMP_LEVEL_IDENTIFY, NULL, EOAC_NONE, NULL);
}
Al escribir junto al prototipo de la función
función indicamos que no se permite lanzar
excepciones desde dentro de la función.
InitializeCom( )
Llama a la función CoInitializeEx
CoInitialize , que inicializa la librería COM que gestiona el uso
de hilos, definiendo el modelo
delo de concurrencia de éstos.
Dpto. Ing. de Sistemas y Automática
Universidad de Sevilla
Página 211
Ramón
amón Jesús Cuesta Pérez
Curso 2011/2012
Proyecto Fin de Carrera
Implementación de algoritmos MPC con restricciones en mbed NXP LPC1768
static HRESULT InitializeCom( ) throw( )
{
// COM debe inicializarse como COINIT_MULTITHREADED
return CoInitializeEx(NULL, COINIT_MULTITHREADED);
}
PreMessageLoop(int nShowCmd)
Función que realiza el registro e inicialización del servidor, así como la creación de los
tags a través de la llamada a
Initialize().
En esta función abrimos también un fichero
(Traza.txt) en el que recogeremos los datos que lleguen al servidor. La otra tarea que
realizamos en esta funciónn es la creación de un socket cliente TCP/IP y su conexión al
socket servidor ubicado en la mbed.
El argumento nShowCmd simplemente sirve para especificar cómo se muestra la
aplicación cuando se abre. Este parámetro llega a la función desde el punto de entrada
del programa (_tWinMain) y se define como SW_HIDE (ocultar ventana) en la función Run.
HRESULT PreMessageLoop(int nShowCmd) throw( )
{
//Abrimos el fichero donde guardaremos los datos recibidos
mific=fopen("Traza.txt"
"Traza.txt","wt");
//Inicializamos la variables de recepción de datos
recvbuf = new char[100];
[100];
y_mbed=0;
u_mbed=0;
ref_mbed=10.5;
sprintf(recvbuf,"");
);
//Variables para inicialización del servidor OPC
HRESULT ret;
CLSID clsid;
//Configuración del socket TCP/IP
int iResult = WSAStartup(MAKEWORD(2,2), &wsaData);
if (iResult != NO_ERROR)
printf("TCP:
"TCP: Error en WSAStartup().\n");
WSAStartup().
else
printf("TCP:
"TCP: WSAStartup() OK.\n");
OK.
ConnectSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (ConnectSocket == INVALID_SOCKET)
{
printf("TCP:
"TCP: Error en socket(): %ld\n",
%ld
WSAGetLastError());
WSACleanup();
}
//Especificamos la dirección del servidor TCP/IP
sockaddr_in clientService;
clientService.sin_family = AF_INET;
Dpto. Ing. de Sistemas y Automática
Universidad de Sevilla
Página 212
Ramón
amón Jesús Cuesta Pérez
Curso 2011/2012
Proyecto Fin de Carrera
Implementación de algoritmos MPC con restricciones en mbed NXP LPC1768
clientService.sin_addr.s_addr = inet_addr("192.168.1.33");
inet_addr(
clientService.sin_port = htons(12345);
//Programamos un timeout en la recepción de datos de 0.1 segundos
tv.tv_sec = 0;
tv.tv_usec = 100000;
setsockopt(ConnectSocket, SOL_SOCKET, SO_RCVTIMEO, (char
(
*)&tv, sizeof tv);
//Nos conectamos al servidor
if (connect(ConnectSocket, (SOCKADDR*)&clientService, sizeof(clientService))
(clientService))
== SOCKET_ERROR)
{
WSACleanup();
}
//Fin
Fin de la configuración TCP/IP
//Llamamos a PreMessageLoop de CAtlServiceModule
if FAILED( ret = CAtlServiceModuleT::PreMessageLoop(nShowCmd) ) return ret;
//Obtenemos el CLSID
CLSIDFromString((LPOLESTR)GetAppId(), &clsid);
//Inicializamos el servidor
if FAILED( ret = GBInitialize(&clsid, SCANRATE, SCANRATE,
GB_SRV_NOACCESSPATH, L'.',100)
L
) return ret;
//Creamos los tags
if FAILED( ret = Initialize() ) return ret;
//Registramos el servidor OPC
if FAILED( ret = GBRegisterClassObject() ) return ret;
//Actualizamos el estado del servidor a SERVICE_RUNNING
if (m_bService)
{
m_status.dwControlsAccepted = SERVICE_ACCEPT_STOP |
SERVICE_ACCEPT_PAUSE_CONTINUE;
SetServiceStatus(SERVICE_RUNNING);
}
return S_OK;
}
La
función
CAtlServiceModuleT::PreMessageLoop
InitializeSecurity,
realiza
una
llamada
a
comentada previamente. La inicialización del servidor OPC se
realiza mediante la función GBInitialize, que recibe como primer argumento el puntero
al identificador (CLSID) del objeto COM (Component Object Model) que representará
representa
al servidor. Por su parte, la función GBRegisterClassObject registra el servidor OPC
con OLE (Object Linking and Embedding),
Embedding) de modo que
ue los clientes OPC puedan
conectarse a él. La función Initialize, encargada de la creación de los tags, se presenta
más adelante. Al salir de PreMessageLoop el servidor está listo para empezar a funcionar.
Dpto. Ing. de Sistemas y Automática
Universidad de Sevilla
Página 213
Ramón
amón Jesús Cuesta Pérez
Curso 2011/2012
Proyecto Fin de Carrera
Implementación de algoritmos MPC con restricciones en mbed NXP LPC1768
RunMessageLoop( )
Función en la que discurre la ejecución
ón normal del servidor. Dentro de ella entramos en
un bucle while(1) en el que realizamos las siguientes acciones:
-
Esperar la llegada de un mensaje o evento. Si no llega salta un timeout igual al
SCANRATE.
-
Si hay mensaje, procesarlo mediante TranslateMessage y DispatchMessage. Si el
mensaje que llega es WM_QUIT salimos del bucle while mediante un break.
-
Si no hay mensajes, leemos la información que llega desde la mbed, los
almacenamos y lo registramos en el fichero (Traza.txt),
(Traza.txt) y llamamos a la función
Update,
donde se actualizan los tags. Esta función se explica más adelante.
void RunMessageLoop( ) throw( )
{
MSG msg;
BOOL bMsg;
int bytesRecv;
float y,u;
//Repetiremos este bucle hasta la condición de fin (llegada de WM_QUIT)
while (1)
{
//Esperamos la llegada de mensajes. Esta función tiene timeout
MsgWaitForMultipleObjects(0, NULL, FALSE, SCANRATE, QS_ALLINPUT);
bMsg = PeekMessage(&msg, NULL, 0, 0, PM_REMOVE);
//Si llega algún mensaje lo procesamos
procesam
if (bMsg)
{
if (msg.message == WM_QUIT) break; //Salimos de while(1)
TranslateMessage(&msg);
DispatchMessage(&msg);
}
//Si no, realizamos las acciones habituales
else
{
//Leemos el socket, que nos envía datos desde la mbed
bytesRecv = recv(ConnectSocket, recvbuf, 100, 0);
//Si hay datos los guardamos y escribimos en el fichero
if(bytesRecv)
{
sscanf(recvbuf,"%lf %lf %lf",&y_mbed,&u_mbed,&ref_mbed);
,&y_mbed,&u_mbed,&ref_mbed);
fprintf(mific,"%lf %lf %lf\n",y_mbed,u_mbed,ref_mbed);
,y_mbed,u_mbed,ref_mbed);
}
//Llamamos a Update(), que actualiza el valor de los tags
Update();
}
};
}
Dpto. Ing. de Sistemas y Automática
Universidad de Sevilla
Página 214
Ramón
amón Jesús Cuesta Pérez
Curso 2011/2012
Proyecto Fin de Carrera
Implementación de algoritmos MPC con restricciones en mbed NXP LPC1768
Los datos que recibimos de la mbed se mandan desde el microcontrolador en cada
iteración del algoritmo de control mediante las siguientes sentencias (programa mbed):
mbed)
//Copiamos los datos en el buffer
sprintf(buf,"%lf
"%lf %lf %lf",(m.y[0]+y_eq),(uk+u_eq),(m.w[0]+y_eq));
%lf",(m.y[0]+y_eq),(uk+u_eq),(m.w[0]+y_eq));
//Y los enviamos
pConnectedSock->send
send(buf, strlen(buf));
Como podemos observar, construimos una cadena de caracteres con los datos que
queremos enviar mediante la función sprintf. Cuando recibimos los datos en el PC (en
el servidor OPC) volvemos a separarlos y guardarlos en variables double
independientes mediante la función sscanf.
PostMessageLoop( )
Función a la que llamamos justo antes de cerrar el servidor.
servidor. En ella cerramos el fichero,
liberamos la memoria reservada dinámicamente y cerramos el socket. Finalmente
llamamos a la función GBRevokeClassObject, que informa al OLE de que el servidor
registrado
ado previamente con GBRegisterClassObject ya no está disponible para su uso.
Salimos a través de CAtlServiceModuleT::PostMessageLoop, donde deshabilitamos
también el objeto COM.
HRESULT PostMessageLoop( ) throw( )
{
//Cerramos el fichero
fclose(mific);
//Liberamos la memoria reservada dinámicamente
free(recvbuf);
//Cerramos el socket
closesocket(ConnectSocket);
//Cerramos ws2_32.dll
WSACleanup();
//Suspendemos el uso del servidor
GBRevokeClassObj
GBRevokeClassObject();
//Salimos a través de PostMessageLoop() de CATLServiceModule,
// que finaliza la ejecución
return CAtlServiceModuleT::PostMessageLoop();
}
OnPause( )
Con esta función se indica que el servidor está pausado. Para ello llamamos a las
funciones SetServiceStatus y GBSetState. Cambiamos el valor del flag
Dpto. Ing. de Sistemas y Automática
Universidad de Sevilla
Página 215
m_bPaused
Ramón
amón Jesús Cuesta Pérez
Curso 2011/2012
Proyecto Fin de Carrera
Implementación de algoritmos MPC con restricciones en mbed NXP LPC1768
dentro de una sección crítica (mutex),, de modo que ningún otro hilo pueda cambiar su
valor en ell preciso instante en el que está siendo modificado.
void OnPause( ) throw(
throw )
{
SetServiceStatus(SERVICE_PAUSE_PENDING);
EnterCriticalSection(&m_crit);
m_bPaused = true;
LeaveCriticalSection(&m_crit);
GBSetState(OPC_STATUS_SUSPENDED);
SetServiceStatus(SERVICE_PAUSED);
}
OnContinue( )
Con esta función se indica que la ejecución del servidor ha sido reanudada. Para ello
llamamos a las funciones SetServiceStatus y GBSetState. Cambiamos el valor del flag
m_bPaused
dentro de una sección crítica (mutex), de modo que ningún otro hilo pueda
cambiar su valor en el preciso instante en el que está siendo modificado.
void OnContinue( ) throw(
throw )
{
SetServiceStatus(SERVICE_CONTINUE_PENDING);
EnterCriticalSection(&m_crit);
m_bPaused = false;
LeaveCriticalSection(&m_crit);
GBSetState(OPC_STATUS_RUNNING);
SetServiceStatus(SERVICE_RUNNING);
}
Métodos privados:
Initialize()
Función en la que se crean los tags mediante la función GBCreateItem. Para la creación
de cada tag creamos una estructura de tipo VARIANT, estructura usada por los objetos
COM que pueden almacenar diversos tipos de variables (float, int, char, etc). Esta
variable contendrá el tipo de datos que va a manejar el tag, y su puntero
puntero se pasa como
argumento a GBCreateItem. Otra serie de argumentos los tomamos de una tabla de
estructuras TAGDESCR.
DESCR. Esta estructura está definida en el fichero TagDescr.h y tiene
los siguientes campos:
Dpto. Ing. de Sistemas y Automática
Universidad de Sevilla
Página 216
Ramón
amón Jesús Cuesta Pérez
Curso 2011/2012
Proyecto Fin de Carrera
Implementación de algoritmos MPC con restricciones en mbed NXP LPC1768
struct TAGDESCR
{
const wchar_t*
* Name;
VARTYPE Type;
DWORD Rights;
int Min;
int Max;
DWORD Flags;
const wchar_t*
* Descr;
};
El vector de estructuras donde definimos los tags que vamos a crear es el siguiente:
static const TAGDESCR TagDescr[] =
{
"Altura del deposito"},
deposito"
{L"salida.altura", VT_R8, 3, 0, 0, GBS_Y, L"Altura
{L"salida.voltaje", VT_R8, 3, 0, 0, GBS_U, L"Voltaje
"Voltaje aplicado a la bomba"},
bomba"
{L"salida.ref_actual"
"salida.ref_actual", VT_R8, 3, 0, 0, GBS_CREF, L"Referencial
"Referencial actual"},
actual"
{L"entrada.referencia"
"entrada.referencia", VT_R8, 3, 0, 0, GBS_REF, L"Referencia
"Referencia de altura"},
altura"
};
Para crear los tags pasaremos desde esta tabla como argumentos a la función
GBCreateItem
el nombre del tag,
tag el tipo de dato (VT_R8) y sus derechos
(lectura/escritura, es el valor de 3). El resto de argumentos de GBCreateItem serán sus
identificadores dentroo del programa y un flag de opciones.
Más adelante, dentro de la propia Initialize, usaremos el último campo de la
estructura TAGDESCR para añadir la descripción al tag mediante la función
GBAddProperty.
Sin más dilación, el código de Initialize es el siguiente:
inline HRESULT Initialize()
{
unsigned i;
m_bPaused = false;
m_iActive = 0;
memset(m_pbActive, 0, sizeof(m_pbActive));
InitializeCriticalSection(&m_crit);
//Creamos TAGDESCRCOUNT tags
for (i = 0; i<TAGDESCRCOUNT; i++)
{
//Declaremos una variable tipo VARIANT para crear cada tag
VARIANT v;
memset(&v, 0, sizeof(v));
v.vt = TagDescr[i].Type;
Dpto. Ing. de Sistemas y Automática
Universidad de Sevilla
Página 217
Ramón
amón Jesús Cuesta Pérez
Curso 2011/2012
Proyecto Fin de Carrera
Implementación de algoritmos MPC con restricciones en mbed NXP LPC1768
if (v.vt == VT_BSTR) v.bstrVal = SysAllocString(L"");
SysAllocString(L
//Creamos el tag con GBCreateItem
GBCreateItem(&m_puTagIds[i], i, TagDescr[i].Name, TagDescr[i].Rights,
GB_TAG_DONTCOPYSTR, &v);
//Borramos la variable VARIANT
VariantClear(&v);
v.vt = VT_BSTR;
//Añadimos la descripción del tag a la variable
v.bstrVal = SysAllocString(TagDescr[i].Descr);
//Añadimos la descripción al tag con GBAddProperty
GBAddProperty(m_puTagIds[i], OPC_PROPERTY_DESCRIPTION, &v, NULL, NULL, 0);
//Borramos la variable VARIANT
VariantClear(&v);
}
//Damos valor inicial a la referencia
FILETIME ft;
VARIANT vaux;
DWORD Id = m_puTagIds[3];
HRESULT Error=S_OK;
WORD Quality = OPC_QUALITY_GOOD;
GetSystemTimeAsFileTime(&ft)
GetSystemTimeAsFileTime(&ft);
vaux.dblVal = 10.5;
vaux.vt = VT_R8;
VariantChangeType(&vaux, &vaux, 0, TagDescr[3].Type);
//Actualizamos el tag referencia con los datos especificados
GBUpdateItems(1, &Id, &vaux, &Quality, &ft,
&ft, &Error, FALSE);
return S_OK;
}
Nótese que se ha dado un valor inicial al tercer tag (referencia) mediante la función
GBUpdateItems,
la cual describiremos en dentro de la siguiente función que vamos a
explicar.
Update()
Función en la que se actualiza el valor de los tags de lectura. Para ello hacemos uso de
la función GBUpdateItems. La actualización se realiza dentro de una sección crítica para
evitar que otros hilos cambien el valor de los tags en este intervalo de tiempo.
tiemp
Para actualizar los tags primero vamos a rellenar mediante un bucle for un vector de
estructuras VARIANT donde almacenaremos tanto el nuevo valor (campo dblVal)
como el tipo de variable (campo vt) de cada tag,, distinguiéndolos mediante una
sentencia switch y el flag definido para cada tag en TagDescr.h.
TagDescr.h. En este bucle también
Dpto. Ing. de Sistemas y Automática
Universidad de Sevilla
Página 218
Ramón
amón Jesús Cuesta Pérez
Curso 2011/2012
Proyecto Fin de Carrera
Implementación de algoritmos MPC con restricciones en mbed NXP LPC1768
rellenaremos un vector que contenga la hora de la actualización (Timestamp), otro con la
calidad del dato (Quality) y otros dos últimos con los identificadores de cada tag (Id) y
el estado de error (Error).
Una vez tengamos todos estos vectores rellenos se los pasaremos como argumentos a la
función GBUpdateItems, junto con el número de tags a actualizar (primer argumento) y
un flag (último argumento) indicando si queremos que la operación
operación se realice de forma
síncrona (TRUE) o asíncrona (FALSE). Una vez volvamos de la llamada a la función
los tags habrán sido actualizados. Borramos entonces las estructuras VARIANT y
salimos de la función. El código de este método es el siguiente:
inline void Update()
{
unsigned i, c = 0;
int k, l;
//Creamos un vector de variables VARIANT para el valor
VARIANT Value[TAGDESCRCOUNT];
//Creamos un vector de variables WORD para la calidad
WORD Quality[TAGDESCRCOUNT];
//Creamos
mos un vector de FILETIME para el tiempo
FILETIME Timestamp[TAGDESCRCOUNT], ft;
HRESULT Error[TAGDESCRCOUNT];
DWORD Id[TAGDESCRCOUNT];
double t, A, n, T;
bool clear = false;
//Entramos en la sección crítica (mutex)
EnterCriticalSection(&m_crit);
if (m_iActive <=
= 0 || m_bPaused) goto Skip; //Si el servidor está pausado saltamos a Skip
GetSystemTimeAsFileTime(&ft);
t = (double)GetTickCount()
)GetTickCount() / 1000.0;
for (i = 0; i<TAGDESCRCOUNT; i++)
{
if (!m_pbActive[i]) continue;
//Asociamos el valor y el tipo a la variable VARIANT
switch (TagDescr[i].Flags & GBS_FUNC_MASK)
{
case GBS_CREF: //Referencia actual
Value[c].vt = VT_R8;
Value[c].dblVal=ref_mbed;
VariantChangeType(&Value[c], &Value[c], 0, TagDescr[i].Type);
break;
case GBS_Y: //Altura
Value[c].vt = VT_R8;
Value[c].dblVal=y_mbed;
VariantChangeType(&Value[c], &Value[c], 0, TagDescr[i].Type);
break;
Dpto. Ing. de Sistemas y Automática
Universidad de Sevilla
Página 219
Ramón
amón Jesús Cuesta Pérez
Curso 2011/2012
Proyecto Fin de Carrera
Implementación de algoritmos MPC con restricciones en mbed NXP LPC1768
case GBS_U: //Voltaje
Value[c].vt = VT_R8;
Value[c].dblVal=u_mbed;
VariantChangeType(&Value[c], &Value[c], 0, TagDescr[i].Type);
break;
default: continue;
default
}
//Añadimos la ID, el etado de error, la calidad y el tiempo
Id[c] = m_puTagIds[i];
Error[c] = S_OK;
Timestamp[c] = ft;
Quality[c] = OPC_QUALITY_GOOD;
OPC_QUA
c++;
}
Skip:
//Salimos de la sección crítica (mutex)
LeaveCriticalSection(&m_crit);
if (c)
{
//Actualizamos los tags con GBUpdateItems
GBUpdateItems(c, Id, Value, Quality, Timestamp, Error, FALSE);
if (clear) for (i = 0; i<c;
c; i++) VariantClear(&Value[i]);
}
}
El resto de funciones privadas (private) de la clase
CGraySimulatorModule
son una serie
de manejadores
anejadores de eventos,
eventos entre los que cabe destacar el que gestiona la interrupción
de escritura desde un cliente de un ítem:
GBOnWriteItems
Este manejador salta cuando se escribe en un ítem desde un cliente. En el servidor que
estamos programando trataremos solo el caso de que se escriba en el tag “referencia”.
“refe
El
resto de casos serán ignorados y no producirán ningún efecto en el servidor, ni tampoco
fallo. Además de actualizar el valor del tag “referencia”, enviaremos el nuevo set-point
set
a la mbeb directamente desde esta función.
De la larga lista de argumentos
rgumentos de la función GBOnWriteItems sólo nos quedaremos con
el valor (pValues), que copiaremos a una estructura VARIANT que utilizaremos para
actualizar el tag mediante la función GBUpdateItems del mismo modo que explicamos en
el punto anterior.
Dpto. Ing. de Sistemas y Automática
Universidad de Sevilla
Página 220
Ramón
amón Jesús Cuesta Pérez
Curso 2011/2012
Proyecto Fin de Carrera
Implementación de algoritmos MPC con restricciones en mbed NXP LPC1768
Por su parte, para el envío del nuevo set-point
set point a la mbed, debemos realizar una
conversión de entero a cadena de caracteres mediante la función itoa y enviarlo a través
del socket TCP/IP mediante la función send.
Finalmente, el código del manejador es el siguiente:
siguie
DWORD __stdcall GBOnWriteItems(DWORD dwCount, GBItemID* pTags, VARIANT* pValues,
p
WORD* pwQualities, FILETIME* pftTimestamps, HRESULT* pErrors, HRESULT* pMasterError,
LCID dwLcid, DWORD dwClientID)
{
VARIANT v;
HRESULT hr;
FILETIME ft;
DWORD Id = m_puTagIds[3];
HRESULT Error=S_OK;
WORD Quality = OPC_QUALITY_GOOD;
GetSystemTimeAsFileTime(&ft);
//Creamos un buffer para enviar la referencia a la mbed
char buf[100];
for (unsigned i = 0; i<dwCount; i++)
{
if (!pTags[i].dwTagID) continue;
switch (TagDescr[pTags[i].dwUserID].Flags & GBS_ATTR_MASK)
{
case GBS_REF: //Tag: referencia
v.vt = VT_EMPTY;
//Copiamos el valor recibido (pValues) en v
hr = VariantChangeTypeEx(&v, &pValues[i], dwLcid, 0, VT_R8);
if SUCCEEDED(hr)
{
//Pasamos el dato de entero a string
itoa(
itoa(int(v.dblVal),buf,10);
//Lo enviamos por TCP/IP a la mbed
send(ConnectSocket,buf,strlen(buf),0);
//Actualizamos el tag en el servidor OPC con el nuevo valor
GBUpdateItems(1, &Id, &v, &Quality, &ft, &Error, FALSE);
}
if FAILED(hr)
{
pErrors[i] = hr;
*pMasterError = S_FALSE;
}
}
}
return GB_RET_CACHE;
}
Hasta aquí la clase
CGraySimulatorModule,
donde han quedado explicadas todas las
funciones que intervienen en la programación del servidor. Para consultar el código
Dpto. Ing. de Sistemas y Automática
Universidad de Sevilla
Página 221
Ramón
amón Jesús Cuesta Pérez
Curso 2011/2012
Proyecto Fin de Carrera
Implementación de algoritmos MPC con restricciones en mbed NXP LPC1768
completo consultar el CD (ficheros capítulo 10), donde se adjunta el proyecto de Visual
Studio 2010 que contienee al servidor OPC.
En el siguiente capítulo presentamos los dos clientes OPC que hemos utilizado para
comprobar el correcto funcionamiento de este servidor, así como para realizar los
cambios de referencia en el control de la planta de los cuatro tanques.
10.3 Bibliografía del capítulo
Iwanitz, F. Lange, J. OPC. Fundamentals, Implementation, and Application
Heidelberg : Hüthig, cop. 2002
2nd rev. ed.
Balagurusamy, E. Programación
gramación orientada a objetos con C++
Madrid [etc.] : McGraw-Hill/Interamericana
Hill/Interamericana de España, 2007
3ª ed.
Graybox Software. Graybox OPC Server Toolkit - Reference
http://gray-box.net/opc_server_toolkit.php
box.net/opc_server_toolkit.php
MSDN (Microsoft Developer Network).
Network) Component Object Model (COM)
http://msdn.microsoft.com/en us/library/windows/desktop/ms680573(v=vs.85).aspx
http://msdn.microsoft.com/en-us/library/windows/desktop/ms680573(v=vs.85).aspx
Matrikon OPC. OPC Tutorials
http://www.matrikonopc.com/resources/opc
http://www.matrikonopc.com/resources/opc-tutorials.aspx
OPC Foundation. OPC Fundation Website
http://www.opcfoundation.org
Dpto. Ing. de Sistemas y Automática
Universidad de Sevilla
Página 222
Ramón
amón Jesús Cuesta Pérez
Curso 2011/2012
Descargar