Usuario
Utilizar o no Timers para realizar preguntas en puerto COM

Pregunta
-
Tengo un sistema PLC que produce una respuesta a una pregunta por puerto COM, esta pregunta esta continuamente realizandose mediante timer. Solamente deja de preguntar cuando se produce el evento de recepcion. He detectado que la comunicacion con el tiempo se detiene y la aplicacion queda bloqueada. Este sistema debe trabajar casi las 24 horas al dia y necesito eliminar el mayor numero de errores posible.
Me gustaria saber cuales son las mejores practicas para poder establecer una comunicacion serie estable y fiable. Adjunto la parte del codigo en la que aparecen el envio y la recepcion de datos.
<pre lang="x-vbnet">#Region " TICK TIMER PREGUNTA " Private Sub Timer_Pregunta_Evento_Tick(ByVal source As Object, ByVal e As ElapsedEventArgs) If a_leer = False Then If j <= Tabla_Nodos_Activos.Rows.Count - 1 Then NODOS(j) = Tabla_Nodos_Activos.Rows(j)("NUM_NODO").ToString ID_NODOS(j) = Tabla_Nodos_Activos.Rows(j)("ID").ToString ' Se realizan tres preguntas If k = 0 Then pregunta = "PREGUNTA1" dm_inicial = "0003" numero_de_datos = "0002" End If If k = 1 Then pregunta = "PREGUNTA2" dm_inicial = "0000" numero_de_datos = "0003" End If If k = 2 Then pregunta = "PREGUNTA3" dm_inicial = "0005" numero_de_datos = "0012" End If comando = "@" & NODOS(j) & "RD" & dm_inicial & numero_de_datos & Obtener_checksum("@" & NODOS(j) & "RD" & dm_inicial & numero_de_datos) & "*" a_leer = True Me.Puerto_Serie.Write(comando & Chr(13)) Timer_TimeOut.Enabled = True End If End If If j = Tabla_Nodos_Activos.Rows.Count Then 'PARA FINALIZAR LA CONSULTA j = 0 End If End Sub #End Region #Region " TICK TIMER TIMEOUT " Private Sub Timer_TimeOut_Evento_Tick(ByVal source As Object, ByVal e As ElapsedEventArgs) If ID_NODOS(j) = 1 Then ESCRIBE_LABEL_SAFE_MODE("ERROR DE COMUNICACIONES NODO: " & NODOS(j), LB_ERROR_COMUNICACIONES_NODO_0) 'DESACTIVAR_CONTROLES_NODO(0) FALLO_COMUNICACIONES_NODO_0 = True NODOS_FALLO(j) = NODOS(j) End If If ID_NODOS(j) = 2 Then ESCRIBE_LABEL_SAFE_MODE("ERROR DE COMUNICACIONES NODO: " & NODOS(j), LB_ERROR_COMUNICACIONES_NODO_1) 'DESACTIVAR_CONTROLES_NODO(1) FALLO_COMUNICACIONES_NODO_1 = True NODOS_FALLO(j) = NODOS(j) End If If ID_NODOS(j) = 3 Then ESCRIBE_LABEL_SAFE_MODE("ERROR DE COMUNICACIONES NODO: " & NODOS(j), LB_ERROR_COMUNICACIONES_NODO_2) 'DESACTIVAR_CONTROLES_NODO(2) FALLO_COMUNICACIONES_NODO_2 = True NODOS_FALLO(j) = NODOS(j) End If If ID_NODOS(j) = 4 Then ESCRIBE_LABEL_SAFE_MODE("ERROR DE COMUNICACIONES NODO: " & NODOS(j), LB_ERROR_COMUNICACIONES_NODO_3) 'DESACTIVAR_CONTROLES_NODO(3) FALLO_COMUNICACIONES_NODO_3 = True NODOS_FALLO(j) = NODOS(j) End If If k = 2 Then j = j + 1 k = 0 Else k = k + 1 End If Timer_TimeOut.Enabled = False a_leer = False End Sub #End Region #Region " EVENTO RECEPCION DE DATOS " Private Sub Puerto_Serie_DataReceived(ByVal sender As Object, ByVal e As System.IO.Ports.SerialDataReceivedEventArgs) Handles Puerto_Serie.DataReceived If a_leer = True Then Timer_TimeOut.Enabled = False palabra_recibida = "" Do caracter_leido = Chr(Puerto_Serie.ReadChar()) palabra_recibida += caracter_leido Loop While caracter_leido <> Chr(13) checsum_calculado = Obtener_checksum(Mid(palabra_recibida, 1, Len(palabra_recibida) - 4)) checsum_recibido = Mid(palabra_recibida, Len(palabra_recibida) - 3, 2) valor_nodo = Mid(palabra_recibida, 2, 2) If valor_nodo = NODOS(j) Then If checsum_calculado = checsum_recibido Then Dim dato_numero As Integer = CType(dm_inicial, Integer) For Me.i = 1 To (CType(numero_de_datos, Integer) * 4) Step 4 valor_dm = Mid(palabra_recibida, 7 + i, 4) If valor_dm <> "" Then array_datos(dato_numero) = CLng("&H" & valor_dm) dato_numero += 1 End If Next If pregunta = "PREGUNTA1" Then 'FUNCIÓN QUE REALIZA INSERCIÓNES EN BD Y ACTUALIZA TEXTBOX TAREAS_PREGUNTA1() End If If pregunta = "PREGUNTA2" Then 'FUNCIÓN QUE REALIZA INSERCIÓNES EN BD Y ACTUALIZA TEXTBOX TAREAS_PREGUNTA2() End If If pregunta = "PREGUNTA3" Then 'FUNCIÓN QUE REALIZA INSERCIÓNES EN BD Y ACTUALIZA TEXTBOX TAREAS_PREGUNTA3() End If Panel_Led_PC.BackColor = Color.Red Panel_LED_Pesos.BackColor = Color.Lime Else 'si el checsum es incorrecto End If End If If k = 2 Then 'SE IGUALA A DOS CUANDO TERMINA DE HACER LAS TRES PREGUNTAS POR NODO j = j + 1 k = 0 Else k = k + 1 End If 'PARA QUE UNA VEZ QUE EL TIME OUT A DEFINIDO EL NODO COMO FALLO EN COMUNICACIONES 'SI SE RESTABLECE LA COMUNICACION CON ESE NODO SE VUELVA A TENER GRAFICO EN TIEMPO REAL If NODOS_FALLO(0) = valor_nodo Then 'ACTIVAR_CONTROLES_NODO(0) NODOS_FALLO(0) = "" ESCRIBE_LABEL_SAFE_MODE("", LB_ERROR_COMUNICACIONES_NODO_0) FALLO_COMUNICACIONES_NODO_0 = False End If If NODOS_FALLO(1) = valor_nodo Then 'ACTIVAR_CONTROLES_NODO(1) NODOS_FALLO(1) = "" ESCRIBE_LABEL_SAFE_MODE("", LB_ERROR_COMUNICACIONES_NODO_1) FALLO_COMUNICACIONES_NODO_1 = False End If If NODOS_FALLO(2) = valor_nodo Then 'ACTIVAR_CONTROLES_NODO(2) NODOS_FALLO(2) = "" ESCRIBE_LABEL_SAFE_MODE("", LB_ERROR_COMUNICACIONES_NODO_2) FALLO_COMUNICACIONES_NODO_2 = False End If If NODOS_FALLO(3) = valor_nodo Then 'ACTIVAR_CONTROLES_NODO(3) NODOS_FALLO(3) = "" ESCRIBE_LABEL_SAFE_MODE("", LB_ERROR_COMUNICACIONES_NODO_3) FALLO_COMUNICACIONES_NODO_3 = False End If a_leer = False End If End Sub #End Region
No se si esta bien no introducir en subprocesos las consultas a bd y las llamadas a funciones, desconozco si es conveniente vaciar el buffer del puerto en cada lectura.
Muchas Gracias.
- Editado Yawes jueves, 11 de noviembre de 2010 21:34
Todas las respuestas
-
El código no lo entiendo muy bien porque es VB y yo soy de asteriscos :-), pero no se te tiene que colgar. Perderte bytes del puerto sí, pero colgarse no.
Lo que no puedes hacer es ocupar mucho tiempo en el timer o todo se detendrá... Ten en cuenta que si tienes un timeout de 1 segundo, y vas a leer desde el timer y no hay nada en el puerto, tu aplicación se quedará pillada 1 segundo.
Por otro lado, el buffer sólo deberías vaciarlo cuando te interese. Cuando haya algún error de trama (te falte un byte o falle el checksum y cosas así. Pero ten en cuenta que si vacías el buffer porque hay un error y lo haces a medio entrarte el siguiente comando, tendrás un nuevo error...
MVP Visual C++ - Visita mi blog sobre desarrollo: http://geeks.ms/blogs/rfog/ -
Muchas Gracias por la respuesta, lo del buffer me ha quedado claro y tienes toda la razón, pero ya no se que puede ser.
La aplicacion realmente no se detiene por un tiempo, si no que se bloquea y es necesario cerrarla desde el administrador de tareas. Tengo la sensación que este no es el método correcto para realizar un sistema de preguntas continuas a los dispositivos.
No se como plantearlo para que pueda estar seguro que cada x tiempo se va ha producir la pregunta y que se recogerá la información independientemente de lo que el resto de la aplicación este haciendo, escritura en bd, mostrando los gráficos, etc...
Me imagino que este tipo de sistemas para adquisición de datos en tiempo real sera una solución mas o menos utilizada y me gustaría que me dieran un consejo sobre como plantearlo de una manera eficiente, con procesos, con hilos etc... la aplicación hace muchas cosas a la vez y muestra bastante información en pantalla.
Basicamente lo que hace el codigo del inicio de la pregunta, es realizar secuancialmente tres preguntas a cada uno de los nodos del sistema. Esta pregunta se realiza bajo la temporizacion de un timer de modo que cuando pregunta al NODO(0) la "PREGUNTA 1" en el evento de recepcion realizo las actuaciones necesarias sobre el NODO(0) dependiendo de la respuesta provocada por la "PREGUNTA 1", ocurriria lo mismo con la "PREGUNTA 2" hasta llegar a la n pregunta (en este caso 3) y pasaria al NODO(1), "PREGUNTA 1", respuesta al 1, "PREGUNTA 2"..... el bloqueo para que la pregunta no se vuelva a realizar lo hago con la variable "a_leer".
El protocolo que se utiliza en este sistema, es el HOSTLINK de OMRON y hay muchos disposivos que lo utilizan Pantallas Tactiles, Modem....
-
Lo que comentas el algo típico de los sistemas de polling para periféricos, y la forma más aceptada de hacer eso es mediante un thread.
Es decir, necesitas poner todo ese código dentro de un thread, con los controles adecuados.
Aquí tienes un hilo con una discusión parecida: http://social.msdn.microsoft.com/Forums/es-ES/vcppdotnetes/thread/841b44ae-4c46-45db-8b04-c2ff36c537eb
Si luego ese thread tiene que acceder a bases de datos aparte de la UI, es algo que deberás comprobar si tu gestor lo permite o no...
MVP Visual C++ - Visita mi blog sobre desarrollo: http://geeks.ms/blogs/rfog/ -
-
Me refiero a que si el gestor permite consultas desde un hilo diferente al principal de la aplicación, y si también realizas consultas desde el principal (o no). La forma de gestionar la conexión y todo eso, y si tendrá problemas cuando le hagan accesos desde dos threads de una misma aplicación, y cómo hay que hacerlo.
Son temas que desconozco completamente y que al menos deberías mirar en la documentación.
Básicamente tienes que ver si es "thread safe" o no, y si no lo es, ver en qué medida no.
MVP Visual C++ - Visita mi blog sobre desarrollo: http://geeks.ms/blogs/rfog/ -
Creo que si, ya que ahora mismo mientras desde el formulario principal se esta accediendo a BD desde otro Formulario puedo sacar informes desde el mismo origen y tablas.
Por lo que veo lo interesante en este tipo de comunicaciones es realizar el tratamiento de datos aparte y dejar el hilo de la aplicacion libre e independiente de la comunicacion. Buscando en la web de elGuille he visto lo siguiente.
Public Class Form1 Delegate Sub delegado(ByVal data As String) Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load For Each s As String In My.Computer.Ports.SerialPortNames lista.Items.Add(s) Next End Sub Private Sub lista_SelectedIndexChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lista.SelectedIndexChanged Try sp3.Close() sp.Close() consola.AppendText("Puerto " & sp.PortName & " Cerrado" & vbCrLf) sp.PortName = lista.SelectedItem consola.AppendText("Abriendo Puerto : " & sp.PortName & vbCrLf) sp.Open() sp3.Open() consola.AppendText("Puerto : " & sp.PortName & " Abierto" & vbCrLf) Catch ex As Exception MessageBox.Show(ex.ToString, "", MessageBoxButtons.OK, MessageBoxIcon.Error) End Try End Sub Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click Try sp.NewLine = vbCrLf sp.WriteLine(Me.TextBox1.Text) consola.AppendText("Se ha enviado : " & Me.TextBox1.Text & vbCrLf) Catch ex As Exception MessageBox.Show(ex.ToString) consola.AppendText("Error al Enviar : " & ex.ToString) End Try End Sub Private Sub sp3_DataReceived(ByVal sender As Object, ByVal e As System.IO.Ports.SerialDataReceivedEventArgs) Handles sp3.DataReceived Dim s As String = sp3.ReadExisting Dim escribeport3 As New delegado(AddressOf Me.mostar) Me.Invoke(escribeport3, s) End Sub Sub mostar(ByVal d As String) consola.AppendText("El Puerto 3 Recibe : " & d) End Sub End Class
Codigo de: Ing. Ruben Alexander More Valencia Universidad Alas Peruanas Piura Peru
La parte interesante de esto es en la recepción donde saca fuera del evento de recepción el tratamiento de los datos.
Con el tema de los hilos estoy muy verde, creo y digo creo que entiendo el concepto de trabajar con un hilo independiente del que utiliza la aplicacion es una buena opcion para independizar "tareas".
Pero poder plasmarlo en mi app creo me va llevar tiempo o al menos eso es lo que pienso desde mi punto de vista de novato empedernido y perpetuo que me hace verlo mas complicado de lo que a lo mejor realmente es.
Gracias de nuevo ya que con tus respuesta ya tengo una linea de por donde pueden ir los tiros.
-
Hola Yawes,
No he leido al completo toda la conversacion... pero entiendo la problematica. De hecho como estas intuyendo en tu primera pregunta... no es una buena practica disparar el envio de una peticion por timer y en todo caso tienes que conducirlo a traves de un 'flag' que indique si recibiste o no respuesta a la anterior pregunta, ahi es donde surge el problema! pues si ese flag "a_leer" se queda en un estado permanente, la comunicacion se interrumpe para siempre.
Una solucion, es asegurarte de que el 'flag' que determina si se ha completado un 'Envio/Recepcion' tenga los mecanismos necesarios para reestablecerlo a su estado que le permita continuar enviando tramas, pasado un tiempo.
No se si me he sabido explicar :-)
Saludos,
Pep Lluis,
MVP - Visual Developer -
Muchas Gracias PepLluis, he estado leyendo el tu Blog sobre los puertos serie y me ha servido de gran ayuda. Entiendo que a lo que te refieres es que en lugar de realizar una pregunta cada x tiempo, la idea seria lanzar una primera pregunta y que sea el sistema el que inicie la siguiente pregunta una vez que haya respondido y que haya transcurrido un tiempo determinado.
Mediante este sistema la velocidad de refresco de datos vendra dada por la velocidad de respuesta mas el tiempo de margen que le demos. Voy a probarlo ya porque últimamente tengo hasta problemas para cerrar el puerto correctamente, ya que se queda en Puerto_Serie.Close() y no hay nada que hacer.
Despues de comprobar que cuentas con gran experiencia con las comunicaciones serie me gustaria hacerte unas preguntas:
- Para sistemas de adquisición de datos en tiempo real, ¿este es el sistema?
- ¿Habría que trabajar con hilos distintos?
- ¿Hay algun modelo a seguir?
-
Yawes,
Efectivamente tu primera afirmación es correcta.
En la segunda parte comentas que se queda en el puerto ‘Close’, tu comentario me recuerda la problemática con ciertos adaptadores USB/Serie con defectos de adaptación en los drives… por ejemplo : un conversor con un driver de XP instalado en Vista L, entonces en determinadas situaciones se producen efectos indeseados… perdida de bytes, quedarse sin respuesta, en infinitas esperas cuando abres o cierras… asegurate de que no se trata de este tipo de problemas utilizando adaptadores de diferentes fabricantes.
En cuanto a tu pregunta sobre las técnicas o modelos a seguir, creo que lo adecuado o al menos lo que acostumbra a dar mayores resultados es ejecutar desde un hilo una petición completa. Me explicare mejor.
Si el programa de adquisición incluye entorno a una aplicación con interface de usuario, cada vez que ejecutamos un “ReadLine” la aplicación deja de responder hasta que no se completa la recepción de los datos correspondientes a nuestra petición. Lo evitaremos lanzando la escritura y la lectura desde un thread aparte siguiendo algo similar a esto :
…. Inicializacion
Dim PuertoSerie As New IO.Ports.SerialPort
PuertoSerie = My.Computer.Ports.OpenSerialPort("COM1")
…. Dentro del HiloPuertoSerie.Write("Peticion")
Dim Resultado() As Byte(Longitud)
PuertoSerie.Read(Resultado, 0, Longitud, 10000)
Entendiendo que en este caso conocemos la longitud de la respuesta y le damos 10 segundos de tiempo para que el dispositivo nos responda, en caso contrario nos disparara una excepción por ‘TimeOut’ y nos retornara del thread… de esa forma podremos iniciar tranquilamente una nueva petición, temporizada por tiempo o enlazada continuamente.Soy consciente de que será mas o menos fácil de implementar en función al escenario, por lo que te aconsejaría que si continuas teniendo dudas o problemas, situemos tu necesidad en contexto y desarrollemos un ejemplo especifico.
Saludos,
Pep Lluis,
MVP - Visual Developer -
Gracias de nuevo y enhorabuena por la intucion, efectivamente se trata de un dispositivo USB/Serial y el so de trabajo es XP y Windows 7. para forzar el cierre del puerto y que no me bloqueara la aplicacion he recurrido a esto.
Private Sub BT_Salir_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles BT_Salir.Click PARAR_COMUNICACIONES() 'funcion que detiene los timer's de pregunta System.Threading.Thread.Sleep(5000) Dim deleg As d_CERRAR_PUERTO deleg = New d_CERRAR_PUERTO(AddressOf CERRAR_PUERTO) deleg.BeginInvoke(Nothing, Nothing) End Sub Private Delegate Sub d_CERRAR_PUERTO() Private Sub CERRAR_PUERTO() If Puerto_Serie.IsOpen = True Then Puerto_Serie.DiscardInBuffer() Puerto_Serie.DiscardOutBuffer() System.Threading.Thread.Sleep(500) Puerto_Serie.Close() Puerto_Serie.Dispose() End If End Sub
Seguro que se pueden reducir los tiempos pero no me importa que tarde 5sg en ejecutarse.
Seguire tus recomendaciones y buscare algun tipo de dispositivo para PC portatil que recomienden para estos menesteres.
Para el segundo punto que me propones, intentare en estos dias simplificar y eliminar el codigo que no interese para subirlo y ver si con vuestra ayuda podemos dejar un sistema generico y fiable de comunicacion COM para este tipo de sistemas de adquisicion de datos, pues creo que sera de gran utilidad a la comunidad. Se trata de crear una app que se comunique con cualquier dispositivo que utilize el protocolo HOSTLINK de OMRON, y en el mercado creedme que hay muchos de este tipo.
De nuevo muchas gracias por la ayuda.
-
Yo tengo amplia experiencia con esos adaptadores, y como dice Pep, suele ndar algunos quebraderos de cabeza, y de hecho los únicos que no me estándando problemas son aquellos que llevan el chip y drivers de FTDI. Enconcreto los FT212R y FT232B.On Sun, 14 Nov 2010 01:40:31 +0100, <Yawes> wrote:> Gracias de nuevo y enhorabuena por la intucion, efectivamente se trata> de un> dispositivo USB/Serial y el so de trabajo es XP y Windows 7. para forz ar> el> cierre del puerto y que no me bloqueara la aplicacion he recurrido a> esto.>> Private Sub BT_Salir_Click(ByVal sender As System.Object, ByVal e As> System.EventArgs) Handles BT_Salir.Click> PARAR_COMUNICACIONES() 'funcion que detiene los timer's de pregunta>> System.Threading.Thread.Sleep(5000)>> Dim deleg As d_CERRAR_PUERTO> deleg = New d_CERRAR_PUERTO(AddressOf CERRAR_PUERTO)> deleg.BeginInvoke(Nothing, Nothing)>> End Sub>> Private Delegate Sub d_CERRAR_PUERTO()>> Private Sub CERRAR_PUERTO()>> If Puerto_Serie.IsOpen = True Then> Puerto_Serie.DiscardInBuffer()> Puerto_Serie.DiscardOutBuffer()> System.Threading.Thread.Sleep(500)> Puerto_Serie.Close()> Puerto_Serie.Dispose()> End If>> End Sub>>>> Seguro que se pueden reducir los tiempos pero no me importa que tarde> 5sg en> ejecutarse.>> Seguire tus recomendaciones y buscare algun tipo de dispositivo para P C> portatil> que recomienden para estos menesteres.>> Para el segundo punto que me propones, intentare en estos dias> simplificar y> eliminar el codigo que no interese para subirlo y ver si con vuestra> ayuda> podemos dejar un sistema generico y fiable de comunicacion COM para es te> tipo de> sistemas de adquisicion de datos, pues creo que sera de gran utilidad a> la> comunidad. Se trata de crear una app que se comunique con cualquier> dispositivo> que utilize el protocolo HOSTLINK de OMRON, y en el mercado creedme qu e> hay> muchos de este tipo.>> De nuevo muchas gracias por la ayuda.>--Microsoft Visual C++ MVP => http://geeks.ms/blogs/rfog======================== =============== Llegará un día que nuestros recuerdos serán nuestra rique za.-- Paul Geraldy. (1855-1954) Escritor francés.
MVP Visual C++ - Visita mi blog sobre desarrollo: http://geeks.ms/blogs/rfog/ -
-
Eso: US232R.
Lapsus teclae.
No olvides instalar la últma versión del driver desde la web del fabricante.
Además, ese chip te dará la opción de trabajar con él no como un puerto serie, sino también con llamadas a un API específico. Busca en esa web información sobre Direct Drivers. Te dan una DLL con la que incluso puedes consultar el número de serie de cada chip.
MVP Visual C++ - Visita mi blog sobre desarrollo: http://geeks.ms/blogs/rfog/ -
-
Yo tengo hecho un sistema anticopia (para el cliente, no para nosotros)aprovechando que una de nuestras placas usa uno de esos chips. Una vez q ueel cliente ha "firmado" su exe, placa y programa van indisolublementejuntos. :-)Y no, no es fácil de saltar porque no sólo se parchea el exe e n un sitio,sino que hay "pistas falsas" (o más bien las pistas falsas no son t an"falsas" como parecen) :-POn Sat, 20 Nov 2010 23:48:19 +0100, <Yawes> wrote:> La verdad que estos dispositivos, parecen mas profesionales que los qu e> he> estado probando. Interesante....>> Gracias de nuevo.--Microsoft Visual C++ MVP => http://geeks.ms/blogs/rfog======================== =============== Llegará un día que nuestros recuerdos serán nuestra rique za.-- Paul Geraldy. (1855-1954) Escritor francés.
MVP Visual C++ - Visita mi blog sobre desarrollo: http://geeks.ms/blogs/rfog/ -
- Propuesto como respuesta Metaconta miércoles, 15 de diciembre de 2010 16:15
-
-