Comunicación SerialPort concurrente

Iniciado por K-YreX, 8 Agosto 2021, 19:44 PM

0 Miembros y 1 Visitante están viendo este tema.

K-YreX

Hola a tod@s.
Estoy con un proyecto desde hace un tiempo y aunque lo he planteado de varias formas diferentes, no logró decantarme por una. Os lo comento por si alguien quiere aportar su granito de arena:

Tengo x dispositivos que se comunican con el PC mediante un mismo puerto serie creado por un receptor. Cada dispositivo tiene un ID (2 dígitos) y para enviar un comando (aaaa) al dispositivo bb hay que escribir en el puerto serie: bbaaaa. El dispositivo responde con el mismo comando (bbaaaa) seguido de algún argumento si fuera necesario.

Como todos los dispositivos comparten el mismo puerto serie, creé algo así:
Código (csharp) [Seleccionar]

public class Dispositivo {
  private static SerialPort puertoSerie;

  public static void AbrirConexion(string nombreCOM) {...}

  public int Id { get; }
  public int NumeroSerie { get => ObtenerNumeroSerie(); }
  //...
}


En un principio la comunicación era comando-respuesta por lo que en los métodos utilizados implementé algo así:
Código (csharp) [Seleccionar]

lock(puertoSerie) {
  EnviarComando(comando);
  respuesta = RecibirRespuesta();
}
// gestion de la respuesta


De esta manera me aseguraba de que cuando una instancia de un dispositivo enviaba un comando al puerto serie, ninguno más pudiese enviar nada hasta obtener una respuesta de este (o superar el ReadTimeout del puerto serie y entonces devolver null). Esto funcionaba perfectamente mientras los comandos fuesen todos comando-respuesta.

El problema viene al intentar utilizar un comando que se envía una vez pero genera muchas respuestas seguidas durante x tiempo. Esta implementación para la comunicación ya no me servía así que opté por lo siguiente:
Código (csharp) [Seleccionar]

public class Dispositivo {
  //...
  private static readonly Dictionary<int, Dictionary<string, string>> gestorRespuestas = new Dictionary<int, Dictionary<string, string>>();
  //...
}


Lo que hago con esto es tener un almacén de respuestas. Cada dispositivo, tras enviar un comando se queda esperando una respuesta del puerto serie. Cuando ha obtenido una respuesta o null (si se supera el ReadTimeout) mira si esa respuesta coincide con la que estaba esperando. Si es así la gestiona y se acabó. Si no coincide, mira de qué dispositivo viene esa respuesta y a qué comando está respondiendo y lo almacena en gestorRespuestas[idEmisor][comandoRespondido], una vez hecho esto mira en gestorRespuestas[Id][comandoEnviado] si otro hilo ha almacenado una respuesta para él.

He realizado unas cuantas pruebas y funciona bastante bien excepto cuando escribo múltiples comandos en el puerto serie prácticamente al mismo tiempo que entonces alguno no llega a responderse nunca. Supongo que es porque hay que esperar un poco entre comando y comando (agregando una espera de 50 ms no he conseguido producir este problema).
La sincronización del puerto serie ahora la realizo justo para escribir en el puerto serie y para leer de él.


Me preguntaba si este enfoque es correcto o debería diseñar otra solución. Pensé también en hacer una cola de envíos y tener un único subproceso enviando comandos de esa cola cada x ms y otro subproceso únicamente para obtener las respuestas y guardarlas en el gestorRespuestas. Así cada hilo que trabaja con una instancia de dispositivo sólo tendría que acceder al gestorRespuestas y nunca al puerto serie directamente.
También he pensado en crear una clase ConcurrentSerialPort para no tener que gestionar los bloqueos desde la clase Dispositivo.

Estaré a la espera de vuestras aportaciones. :rolleyes:
Código (cpp) [Seleccionar]

cout << "Todos tenemos un defecto, un error en nuestro código" << endl;

Serapis

Yo haría lo último que señalas... una cola de salida y otra de entrada.

Debes cuidar la cola de salida... para que no se envíen más peticiones por unidad de tiempo de las que está capacitado a recibir, ya que el puerto serie tiene sus limitaciones. Un temporizador que se  encargue de ver si tiene salidas pendientes, y si las hay enviar la primera en curso y si se espera una devolución asignarle un id y pasarlo a la cola de entrada, que cuando sea despachado del dispositivo (con el mismo id), se envía a la cola de entrada (devolución) que seguramente podrá actuar a una mejor velocidad, y una vez servida borrar de esta cola.

Con ambas colas evitas la necesidad del bloqueo que seguramente te haga perder notificaciones.

Es importante que recabes las especificaciones del puerto serie, para aprovecharlo al máximo... no saturarlo ni tampoco dejarlo inactivo y que caiga su eficiencia por debajo de sus capacidades.

Meta

Felices fiestas.  ;-)

¿Tienes claro que comandos envias y qué esperas recibir de él?

Puedes hacer una mini lista sobre comandos aquí para poder hacer ejemplos.

Si usas el Windows Form, añades un botón en el formulario, hablando en C# para este ejemplo, para enviar comandos es así.
Código (csharp) [Seleccionar]
       private void button_Activar_Click(object sender, EventArgs e)
       {
           byte[] mBuffer = Encoding.ASCII.GetBytes("EncenderLed"); // Comando a enviar.
           serialPort1.Write(mBuffer, 0, mBuffer.Length);
       }


Al recibir datos es así.
Código (csharp) [Seleccionar]
       // Al recibir datos.
       private void serialPort1_DataReceived(object sender, SerialDataReceivedEventArgs e)
       {
           // Acumula los caracteres recibidos a nuestro 'buffer' (string).
           recibidos += serialPort1.ReadExisting();

           // Invocar o llamar al proceso de tramas.
           Invoke(new EventHandler(Actualizar));
       }


No olvidar que cada comando recibido, debes vaciar la variable para que no se concatenen, que luego no funciona.

Código (csharp) [Seleccionar]
           // Limpiar.
           recibidos = "";


Ya nos contarás más información sobre ello. Que dispositivos es y si te sabes realmente los comandos y respuestas a enviar o lo haces tuyo propio o viene en documentación del dispositivo.

Saludos.  :D
Tutoriales Electrónica y PIC: http://electronica-pic.blogspot.com/