3 de septiembre de 2009

Comunicación OPC entre un PLC software WinAC RTX y Visual Basic (Parte 2ª y última)

En la 1ª parte hemos dejado lista la parte del Step7, ahora vamos a meternos con el Visual Basic.

Creamos un proyecto nuevo con el Visual Basic 2008 Express y vamos a la pestaña de 'Referencias' para añadir el componente de Siemens para acceder al OPC. Pulsamos sobre Agregar... => Referencia


En la ventana de 'Agregar referencia' vamos a la pestaña 'COM' y buscamos la línea que pone 'Siemens OPC DAAutomation 2.0'. La seleccionamos y pulsamos el botón 'Aceptar'.


Nos aseguramos que bajo el epígrafe 'Espacios de nombres importados' esté marcado 'OPCSiemensDAAutomation'.


Y listo, nuestro proyecto ya tiene acceso a las funciones y componentes para realizar una comunicación OPC.

Ahora vamos a la programación propiamente dicha. Al principio todos los ejemplos que encontré estaban basados en Visual Basic 6.0 y al incorporarlos a Visual Basic .NET no funcionaban. Después de buscar un poco (recomiendo el foro de la OPC foundation) encontré que había una incongruencia en los límites de las tablas (arrays) y hay que crearlas forzando que el primer elemento sea el 1 y no el 0. Después se me ocurrió que tal vez hubiese ejemplos de programación en el DVD de Simatic NET y había uno en Visual Basic .NET (sí, debí haber empezado por ahí). Échale un vistazo, está muy clarito y mi ejemplo está basado en él.

Después de las primeras pruebas exitosas decidí meter todas las variables y funciones para acceder al OPC en una clase y así simplificar el código de mi aplicación.

Creé la clase ConexionOPC, cuyo código pongo a continuación. Para usarla debes adaptar la función 'Conectar' para que acceda a los items (variables) de tu proyecto. En mi caso no llego a cien items y el velocidad de acceso es óptima, pero si lo que quieres es hacer un envío masivo de datos no creo que esta sea la mejor solución.

NOTA: Para especificar un ítem se debe hacer refiriéndose a su simbólico, p. ej. SIMATIC.WinLC.OPCCom.VB_PLC00, donde SIMATIC es el nombre del equipo, WinLC el nombre del componente, OPCCom el nombre del bloque de datos y VB_PLC00 el simbólico de la variable. Cuando introduces las variables a transmitir en el servidor OPC puedes ver como direccionarlas (ver la siguiente captura de pantalla sacada de la primera parte).


Vamos con el código:
Public Class ConexionOPC

'Objectos para establecer la comunicación OPC
Private WithEvents ServidorOPC As OPCServer
Private WithEvents GrupoOPC As OPCGroup
Private WithEvents GruposOPC As OPCGroups
Private ItemOPC() As OPCItem

Private Conectado As Boolean 'Para saber si la conexión está activada

'Si hay algún error se indica en estas variables
Public Mensaje As String
Public Detalle_Error As String

'Constructor
Public Sub New()
'Al crear el objeto, no estamos conectados
Conectado = False
End Sub

'Función para activar la conexión OPC
Public Function Conectar() As Boolean

'Si ya estoy conectado aviso y salgo.
If Conectado Then
Mensaje = "Error conexión OPC."
Detalle_Error = "Se ha intentado crear una conexión OPC cuando ya hay una creada."
Conectar = False
Exit Function
End If

Try
Mensaje = "Conectando con el servidor OPC..."
ServidorOPC = New OPCSiemensDAAutomation.OPCServer
ServidorOPC.Connect("OPC.SimaticNET")

Mensaje = "Añadiendo grupo al servidor OPC..."
GruposOPC = ServidorOPC.OPCGroups
GrupoOPC = GruposOPC.Add("GrupoG")

Mensaje = "Añadiendo Items al grupo..."
ReDim ItemOPC(100) 'Dimensionar según las necesidades

'Introducir un ítem por cada variable del PLC en la que queramos leer o escribir
'A cada ítem le asignamos un número, que debemos recordar para referirnos a él en el programa
ItemOPC(0) = GrupoOPC.OPCItems.AddItem("SIMATIC.WinLC.OPCCom.VB_PLC00", 0)
' Añadir más items...
'...

Catch ex As Exception

Detalle_Error = "Error: " & ex.ToString
Conectado = False
Conectar = False
Exit Function

End Try

Mensaje = "Conexión OPC realizada correctamente."
Detalle_Error = ""
Conectado = True
Conectar = True

End Function

'Función para deshacer la conexión OPC
Public Function Desconectar() As Boolean
Try
Mensaje = "Desconectando..."
ItemOPC = Nothing

If Not IsNothing(ServidorOPC) Then
ServidorOPC.OPCGroups.RemoveAll()
ServidorOPC.Disconnect()
ServidorOPC = Nothing
End If
GrupoOPC = Nothing
GruposOPC = Nothing

Catch ex As Exception

Detalle_Error = "Error: " & ex.ToString
Desconectar = False
Exit Function

End Try

Mensaje = "Desconexión realizada correctamente."
Detalle_Error = ""
Conectado = False
Desconectar = True

End Function

'Función para escribir en un ítem que representa una variable entera
'Se le pasa el índice del ítem y el valor que vamos a escribir
'Si todo va bien devuelve True
Public Function EscribirItemInt(ByVal Indice As Integer, ByVal Entero As Integer) As Boolean
' The typelibrary OPCSiemensDAAutomation was imported with /sysarray by default,
' thus we have to create arrays with lower bound = 1
Dim Dims() As Integer = New Integer() {1}
Dim Bounds() As Integer = New Integer() {1}
Dim Serverhandles As Array = Array.CreateInstance(GetType(Integer), Dims, Bounds)
Dim Errores As Array = Array.CreateInstance(GetType(Integer), Dims, Bounds)
Dim Valores As Array = Array.CreateInstance(GetType(Object), Dims, Bounds)

If Not Conectado Then
Mensaje = "Error, conexión OPC ausente."
Detalle_Error = "Se ha intentado acceder a un Ítem sin haber establecido una conexión OPC."
EscribirItemInt = False
Exit Function
End If

Try
Serverhandles.SetValue(ItemOPC(Indice).ServerHandle, 1)
Errores.SetValue(0, 1)
Valores.SetValue(Entero, 1)

GrupoOPC.SyncWrite(1, Serverhandles, Valores, Errores)

Catch ex As Exception

Detalle_Error = ex.ToString
Mensaje = "¡Error al escribir Item! [Int, Índice " & Indice & "]"
EscribirItemInt = False
Exit Function

End Try

Mensaje = ""
Detalle_Error = ""
EscribirItemInt = True

End Function

'Función para escribir en un ítem que representa una variable booleana
'Se le pasa el índice del ítem y el valor que vamos a escribir
'Si todo va bien devuelve True
Public Function EscribirItemBool(ByVal indice As Integer, ByVal Bit As Boolean) As Boolean
' The typelibrary OPCSiemensDAAutomation was imported with /sysarray by default,
' thus we have to create arrays with lower bound = 1
Dim Dims() As Integer = New Integer() {1}
Dim Bounds() As Integer = New Integer() {1}
Dim Serverhandles As Array = Array.CreateInstance(GetType(Integer), Dims, Bounds)
Dim Errores As Array = Array.CreateInstance(GetType(Integer), Dims, Bounds)
Dim Valores As Array = Array.CreateInstance(GetType(Object), Dims, Bounds)

If Not Conectado Then
Mensaje = "Error, conexión OPC ausente."
Detalle_Error = "Se ha intentado acceder a un Ítem sin haber establecido una conexión OPC."
EscribirItemBool = False
Exit Function
End If

Try
Serverhandles.SetValue(ItemOPC(indice).ServerHandle, 1)
Errores.SetValue(0, 1)
Valores.SetValue(Bit, 1)

GrupoOPC.SyncWrite(1, Serverhandles, Valores, Errores)

Catch ex As Exception

Detalle_Error = ex.ToString
Mensaje = "¡Error al escribir Item! [Bool, Índice " & indice & "]"
EscribirItemBool = False
Exit Function

End Try

Mensaje = ""
Detalle_Error = ""
EscribirItemBool = True

End Function

'Función para leer un ítem que representa una variable entera
'Se le pasa el índice del ítem que vamos a leer
'Si todo va bien devuelve el valor de la variable
Public Function LeerItemInt(ByVal Indice) As Integer

Dim Valor As Object = Nothing
Dim Calidad As Object = Nothing
Dim TimeStamp As Object = Nothing

If Not Conectado Then
Mensaje = "Error, conexión OPC ausente."
Detalle_Error = "Se ha intentado acceder a un Ítem sin haber establecido una conexión OPC."
LeerItemInt = 0
Exit Function
End If

Try
ItemOPC(Indice).Read(OPCDataSource.OPCDevice, Valor, Calidad, TimeStamp)
LeerItemInt = CInt(Valor.ToString)

Catch ex As Exception

Detalle_Error = ex.ToString
Mensaje = "¡Error al leer Item! [Int, Índice " & Indice & "]"
LeerItemInt = 0
Exit Function

End Try

Mensaje = ""
Detalle_Error = ""

End Function

'Función para leer un ítem que representa una variable booleana
'Se le pasa el índice del ítem que vamos a leer
'Si todo va bien devuelve el valor de la variable
Public Function LeerItemBool(ByRef Indice) As Boolean

Dim Valor As Object = Nothing
Dim Calidad As Object = Nothing
Dim TimeStamp As Object = Nothing

If Not Conectado Then
Mensaje = "Error, conexión OPC ausente."
Detalle_Error = "Se ha intentado acceder a un Ítem sin haber establecido una conexión OPC."
LeerItemBool = 0
Exit Function
End If

Try
ItemOPC(Indice).Read(OPCDataSource.OPCDevice, Valor, Calidad, TimeStamp)
If StrComp(Valor.ToString, "True") = 0 Then
LeerItemBool = True
Else
LeerItemBool = False
End If

Catch ex As Exception

Detalle_Error = ex.ToString
Mensaje = "¡Error al leer Item! [Bool, Índice " & Indice & "]"
LeerItemBool = False
Exit Function

End Try

Mensaje = ""
Detalle_Error = ""

End Function

End Class

Para usar esta clase en un programa primero debemos declarar un objeto:
'Objecto para establecer la comunicación OPC
Public OPC As ConexionOPC
OPC = New ConexionOPC
y luego llamamos a la función que realiza la conexión:
OPC.Conectar()
Viendo lo que nos devuelve la función y el contenido de las cadenas de texto OPC.Mensaje y OPC.Detalle_Error podemos saber si todo ha ido bien.

Para escribir variables hacia el PLC lo que he hecho, por ejemplo, es poner un botón y con los eventos MouseDown y MouseUp escribir un uno o un cero con la función OPC.EscribirItemBool (análogo a lo que se haría en el WinCC Flexible con las funciones ActivarBit y DesactivarBit en los eventos Pulsar y Soltar). Ejemplo:
Private Sub ButtonRearme_MouseDown(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles ButtonRearme.MouseDown
OPC.EscribirItemBool(1, True)
End Sub

Private Sub ButtonRearme_MouseUp(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles ButtonRearme.MouseUp
OPC.EscribirItemBool(1, False)
End Sub
La lectura de variables es distinta ya que no tengo la manera de detectar cuando una variable cambia para leerla, así que lo que he hecho es introducir en mi proyecto el componente Timer, que lanza la función Tick cada periodo de tiempo que especifiquemos. Lo que hago en esta función es actualizar los valores de todas las variables que leo del PLC. Temía que hubiese ralentizaciones pero el servidor OPC responde muy rápido y leyendo cada 200 ms funciona perfectamente. Ejemplo:
'Función que se ejecuta periodicamente para actualizar el estado de las variables del programa PLC
Private Sub Temporizador_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Temporizador.Tick

PosicionServomotor = OPC.LeerItemInt(64)
Servomotor_OK = OPC.LeerItemBool(47)

End Sub
Hasta aquí esta guía de comunicación OPC. Si a alguien le sirve le agradecería algún comentario. Si haces cambios o mejoras estaría interesado en verlos.

20 comentarios:

  1. Voy a intentar algo parecido a esto con Omron, desde luego VB.net y un Opc que en principio sera de Matrikon o de Omron. Te enviaré cosas segun vaya haciendolo. Yo antes lo hacia con Vfoxpro y protocolo Host link, va cañon aunque ahora ya me aburre y creo que debo cambiarlo.

    ResponderEliminar
  2. Hola, muchas gracias. Me ha servido de ayuda, soy estudiante y necesito hacer un trabajo de como instalar y configurar un servidor opc para trabajar con un S7-300.

    Aunque yo el tema de visual basic no lo llevo muy bien, intentaré aprender para comprender lo que has hecho.

    Muchas gracias de nuevo

    ResponderEliminar
  3. Miguel, si necesitas que te aclare algo, no tienes más que decirlo.

    Gracias por vuestro interés.

    ResponderEliminar
  4. ¿Y esto hacerlo con PHP? como utilizo la misma librería es lo mismo que en visual? entiendo que siguiendo la manera de declarar de PHP

    ResponderEliminar
  5. ander, siento no poder ayudarte, nunca he trabajado con PHP.

    ResponderEliminar
  6. ¿Crees que esto serviria para un OPC de telemecanique?

    ResponderEliminar
  7. Si tienes los driver de comunicaciones para Schneider te servirá, piensa que en el ejemplo usan los drivers para Siemens, si usas omron usaras las "rutinas LITE", .....

    ResponderEliminar
  8. He copiado el codigo de tu ejemplo en Visual Basic 2005 y seleccionado el servidor opc de telemecanique y no me da ningun error. Alegria!!
    Y justo despues primera duda: en la linea ServidorOPC.Connect("OPC.SimaticNET") ¿SimaticNET es el nombre del servidor?

    ResponderEliminar
  9. Obviamente en lugar de Simatic.Net (nombre del Driver de Siemens) tendrs que utilizar el nombre del Driver que has instalado, en tu caso el de Schneider.

    ResponderEliminar
  10. Gracias C., parece que estás más atento que yo a los comentarios del blog ;)

    ResponderEliminar
  11. Excelente mi estimado...más claro ni el agua...

    ResponderEliminar
  12. Hola, tengo un problema. Estoy creando un programa en VB.Net y me estoy guiando con tu información. El problema es que me sale un ERROR que dice: "No se puede convertir un objeto de tipo 'OPCAutomation.OPCServerClass' al tipo 'OPCAutomation.IOPCGroups'.

    La parte de código importante para el problema es:
    Public WithEvents g_Server As OPCServer
    Public WithEvents g_Group As OPCGroup

    (mas adelante)
    g_Group = g_Server.OPCGroups.Add("hola") -->aqui mes salta el error

    Si te sirve de mas ayuda, este programa ya me funcionaba en VB6 pero al pasar a .Net me da este problema.

    Espero tu respuesta. MUCHAS GRACIASSSS¡¡

    Miguel

    ResponderEliminar
  13. Hola. Muy buena la información. Solo que me da un ERROR creo de librerias pero no se de donde sale. Trabajo en VB.Net y declaro ServidorOPC y GrupoOPC igual que tu. Pero despues me sale el error:
    "No se puede convertir un objeto de tipo 'OPCAutomation.OPCServerClass' al tipo 'OPCAutomation.IOPCGroups'. Que pasa???
    Gracias por tu ayuda. Miquel

    ResponderEliminar
  14. Estos dos ultimos mensajes son mios. Pensaba que no habia colgado el primer comentario.

    Miquel. Graciass:-)

    ResponderEliminar
  15. Hola Miquel, tiene pinta de que tienes algún conflicto de bibliotecas. ¿usas las mismas bibliotecas en .NET que en VB6?

    Yo tuve problemas con alguna versión del Simatic NET, y tuve que volver a versiones anteriores, aunque la última vez que hice algo con OPC,la última versión de Simatic NET no me dio problemas.

    Un saludo y suerte.

    ResponderEliminar
  16. Hola,
    Primero de todo felicitarte por tus aportes!
    Hace poco realize una comunicacion con VBA de excel por OPC con Simatic NET y un S7-1200 en los que leia variables del PLC y las guardaba en un archivo de texto.

    Ahora hace poco me he puesto con VB 2008 express y siguiendo tu ejemplo no he tenido problemas para comunicarme con el mismo PLC.

    Mi pregunta es: como saco por pantalla los mensajes de la variable "Mensaje" a medida que se vayan produciendo? (lo he exo con un MsBox pero me gustaría que fuese como tipo histórico, sin que se abriesen ventanitas... no se si me explico.

    Ante todo muxas gracias y felicitarte de nuevo!!!

    ResponderEliminar
  17. Hola anónimo,

    me alegro que mi ejemplo te haya sido útil.

    Yo lo que suelo hacer es un ListBox donde voy añadiendo los textos de la variable Mensaje.

    Un saludo.

    ResponderEliminar
  18. Muchas gracias, está muy interesante tu aporte, sé que con esta información voy a poder establecer ese tipo de comunicación, pues llevo algun tiempo buscando.

    ResponderEliminar
  19. como funcionaria con el plc es controllogix 1756-l71,
    es para todos igual?

    ResponderEliminar
  20. Excelente!! Justo lo q necesitaba! Se agradece demasiado

    ResponderEliminar

Por favor, no pidas copias de programas comerciales, licencias o números de serie.