C# SerialConsoleApp1: Gestión básica de la línea serie

Conector DB9 (9 pines). Para conexiones en serie, ya que permite una transmisión asíncrona de datos según lo establecido en la norma RS-232

Hoy en día la mayoría de los temarios y contenidos de cursos de lenguajes de programación se dirigen a aplicaciones y servicios en la nube o interfaces gráficos de escritorio, pocas veces se tratan temas como protocolos de comunicación entre máquinas a bajo nivel.

El protocolo serie (RS232) aún hoy es un sistema de comunicaciones muy común en maquinas industriales, es conocido, confiable y además ya existe un montón de código desarrollado en cualquier lenguaje de programación.

Por desgracia hoy día la mayoría de máquinas no traen por defecto este puerto (en los PCs industriales sin embargo es común que vengan con varios puertos COM), en su lugar usan un puerto COM emulado mediante controladores del sistema operativo sobre un cable USB (por ejemplo un dispositivo Arduino).

Inclusión del espacio de nombres

Para trabajar con los puertos COM debemos añadir las siguientes líneas al comienzo del programa (más sobre System.IO en MSDN).

Primer paso: Obtención de puertos COM del sistema

Lo primero es crear una clase básica que albergara todas nuestras funciones para trabajar con el puerto serie. La clase inicial sólo contendrá un método público para imprimir por consola una enumeración de los puertos disponibles en el sistema operativo.

Para obtener los puertos hemos empleado el método SerialPort.GetPortNames. Este método estático retorna un array de cadenas con los nombres de los puertos serie.

Como no tengo otra cosa he conectado mi placa Arduino al PC y este es el resultado.

Abriendo el puerto COM

Lo siguiente es diseñar un método para configurar y abrir el puerto serie, la función recibe los parámetros de configuración de la línea.

Lo más interesante de esta función es la definición del evento DataReceived. Hacemos que DataReceived apunte a un método nuestro (EvtCOMDataRx) que será invocado cada vez que la línea serie indique que ha recibido nuevos datos (handle o manejador), de esta forma no tenemos que estar constantemente preocupados de si han llegado nuevos datos o no.

Enviando datos de prueba de Arduino a nuestro programa

Para no perder el tiempo he usado un proyecto super básico que ilustra como enviar una cadena desde nuestra placa Arduino al PC.

 

Cuando ejecuto mi aplicación C# puedo ver los datos enviados en mi PC!

Posts relacionados

Enlaces externos

Anuncios

C# RabbitMQClient: Productor de mensajes básico (con WSO2) usando .NET/C# en VS2012

RabbitMQ es un broker de mensajería (ver entrada RabbitMQ primeros pasos con colas de mensajería). Vamos a tratar de usar el cliente para .NET en  un proyecto con MS Visual Studio 2012 para conectar a un broker de mensajería WSO2 (ver WSO2: Broker de mensajería IoT (WSO2 IoTS))

Para instalar el paquete RabbitMQ.Client en VS2012  es recomendable usar el gestor de paquetes incorporado NuGet (RabbitMQ.Client 4.1.3) ejecutando el siguiente comando:

PM> Install-Package RabbitMQ.Client -Version 4.1.3

Para instalar el paquete previamente hemos creado un nuevo proyecto C# para consola.

Importamos la librería con la línea


using RabbitMQ.Client;

La clase ConnectionFactory construye instancias de IConnection y permite configurar el nombre el host, el virtual host y las credenciales de acceso. En vez usar las propiedades  factory.UserName, factory.Password… Compactamos la cadena de conexión en una sola línea usando la propiedad URI.


ConnectionFactory factory = new ConnectionFactory();
factory.Uri = "amqp://admin:admin@localhost:5672/carbon";

WSO2 usa el puerto TCP 5672 como broker de mensajería con el protocolo AMQP, podemos cambiar en cualquier momento el puerto editando el fichero C:\wso2mb-3.1.0\repository\conf\broker.xml.


El último parámetro del URI es el VHost, en WSO2 podemos encontrar información del VHost en C:\wso2mb-3.1.0\repository\conf\advanced\qpid-virtualhosts.xml. El VHost por defecto se llama “carbon”. Dentro del fichero también encontramos información sobre el exchange y queue (“amq.direct" is the default exchange that should be used for queue creation.).


IConnection conn = factory.CreateConnection();

IModel channel = conn.CreateModel();
channel.ExchangeDeclare("amq.direct","direct",true);

Le pasamos la conexión a la instancia IConnection empleado para crear nuevos canales.  La llamada a CreateConnection produce una excepción si el servidor WSO2 está parado por ejemplo.

El interface IModel se usa para el modelo AMQP le pasamos tres parámetros al método ExchangeDeclare. 

  • exchangeName: Un productor nunca envía directamente los mensajes directamente a la cola, en su lugar los envía a un exchange.

 

  • type: Debe ser uno de los siguientes “direct” (los mensajes van sólo a la cola especificada en routingKey), “topic”, “headers” o “fanout” (broadcast a todas las colas).
  • El tercer parámetro “durable” a true mantiene la cola persistente en el servidor.

La última parte es publicar el mensaje usando el método BasicPublish.

 var message = GetMessage(args);
 var body = Encoding.UTF8.GetBytes(message);
 
 channel.BasicPublish(exchange: "amq.direct",
 routingKey: "test-queue",
 basicProperties: null,
 body: body);

 channel.Close();
 conn.Close();

Código completo

Crear la cola en “test-queue” en WSO2

En el servidor WSO2 creamos una cola llamada “test-queue” a media que ejecutemos nuestro cliente publicador irá aumentando el contador de mensajes.

 

Referencias

C# FletcherChecksum16: Suma de comprobación CRC para tramas de datos

Introducción

El checksum de Fletcher es un algoritmo para implementar una suma de comprobación que permita detectar un error en alguno de los campos de un mensaje entre su emisión y recepción. Es muy común en protocolos de mensajería de bajo nivel, la máquina que genera el mensaje añade al final de la trama que envía otro campo con el CRC calculado con los bytes del cuerpo del mensaje. El receptor a su vez cuando recibe la trama calcula usando el mismo algoritmo el CRC del cuerpo del mensaje y lo compara con el campo que ha añadido el emisor, evidentemente si los resultados no son el mismo con toda probabilidad la trama se ha corrompido en algún momento de la transmisión.

Las NDCAN de Kimaldi son tarjetas de entradas salidas que permiten la interacción con una red sensores conectadas entre si en batería (fotocelulas de presencia como entradas digitales para detectar la presencia de un objeto por ejemplo) sobre un bus CAN industrial. Al final de la red de tarjetas un concentrador de comunicaciones del mismo fabricante recibe / envía los datos a las tarjetas conectado a un PC usando un protocolo sería basado en tramas con un CRC al final.

can1

El algoritmo empleado para generar el CRC de las tramas serie es muy sencillo y el resultado solo ocupa 1 byte. Las tramas son cadenas en representación hexadecimal (un valor de un byte se representa como dos hexadecimales). Mr Robot usa la aplicación Web ASCII to Hex para convertir entre diferentes bases numéricas, nosotros no vamos a ser menos, por el ejemplo el número decimal 255 se representa en base 16 o hexadecimal como 0xAA (la notación “0x” que precede es para indicar que es un número en hexadecimal):

can2

Ahora vamos a lo que realmente importa, ¿como calcula el CRC? Supongamos que recibimos la siguiente trama:

<STX>02015102000A<CRC><ETX>

Los valores <STX> y <ETX> son valores especiales para indicar donde debemos comenzar a leer la trama y donde finaliza (caracteres de control Start/End of transmission) y no forman parte del calculo del CRC (protocolo MAGSTRIPE usado en tarjetas de banda magnética), para calcular el CRC empleamos exclusivamente el cuerpo del mensaje, en este caso “02015102000A” se calcula de la siguiente forma:

 El valor de CRC se calcula sumando los valores Ascii del cuerpo de la trama:

  • El ascii del 0 es 48
  • El ascii del 2 es 50
  • El ascii del 0 es 48
  • El ascii del 1 es 49
  • El ascii del 5 es 53
  • El ascii del 1 es 49
  • El ascii del 0 es 48
  • El ascii del 2 es 50
  • El ascii del 0 es 48
  • El ascii del 0 es 48
  • El ascii del 0 es 48
  • El ascii del A es 65

Que suman: 604. Su equivalente hexadecimal es 0x25C. Tomando los dos últimos dígitos del resultado hexadecimal obtenemos el valor de la suma en módulo 256, es decir 0x5C.
Así púes la trama queda:

<STX>02015102000A5C<ETX>

Aplicación C# para calcular el CRC de una trama serie

Vamos a diseñar una aplicación super sencilla que permita calcular el CRC de una cadena en formato hexadecimal.

can3

Sólo usamos 3 tipos de controles: TextBox para los campos de edición (uno de ellos de solo lectura con el resultado CRC), Labels para las etiquetas de texto y Button para los 3 botones:

  • txtTrmHex: TextBox donde introducimos cadena hexadecimal.
  • btnAceptar: Button para realizar el cálculo y mostrar el resultado.
  • txtCRC: Campo de edición de solo lectura con el resultado.
  • btnReset: Botón con leyenda “Limpiar” para vaciar contenido de los campos de edición.
  • btnQuitApp: Botón para abandonar la aplicación.

Primero ajustamos el diseño de los controles a nuestro gusto (diseño plano, colores de texto, posicionamiento,…).

Lo primero es crear el método dentro de nuestra clase para calcular el CRC, recibe una cadena de caracteres y retorna como salida un byte:

can4

En el constructor del formulario vamos a poner el foco del cursor sobre el campo de edición para comenzar a introducir la cadena inmediatamente:

can5

Ahora vamos a programar el evento de pulsación del botón aceptar:

can6

También queremos realizar el mismo cálculo cuando pulsamos la tecla “Enter” en el cuadro de edición:

can7

El resto de los eventos ya no tienen ningún misterio:

can8

Aquí esta el código completo:


using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace FletcherChecksum16
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
txtTrmHex.Focus();
}

private byte Calcula_CRC(string cmd)
{
ulong suma=0;
int i=0;
for (i = 0; i < cmd.Length; i++)
{
suma+=cmd[i];
}

return (byte)(suma & 0xFF);
}

private void btnAceptar_Click(object sender, EventArgs e)
{
if (txtTrmHex.Text.Equals(""))
{
MessageBox.Show(this, "No puedes dejar el campo vacio", "Error",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
else
{
txtCRC.Text = String.Format("{0,2:X}", Calcula_CRC(txtTrmHex.Text));
}
}

private void btnReset_Click(object sender, EventArgs e)
{
txtCRC.Text = "";
txtTrmHex.Text = "";

}

private void btnQuitApp_Click(object sender, EventArgs e)
{
this.Close();
}

private void txtTrmHex_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Enter)
{
if (txtTrmHex.Text.Equals(""))
{
MessageBox.Show(this, "No puedes dejar el campo vacio", "Error",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
else
{
txtCRC.Text = String.Format("{0,2:X}", Calcula_CRC(txtTrmHex.Text));
}

}
}

}
}

 

C# KimaldiOpenPort: Abre el puerto de comunicaciones serie

Ahora hay que abrir el puerto COM al que esta conectado el concentrador de comunicaciones Kimaldi. Para ello usaremos el método OpenPort (si va todo correctamente se recomienda usar la llamada TestCtrLink para comprobar las comunicaciones). Este post viene del artículo previo “C# GetKimaldiDllVer: DLL de fabricante para trabajar con tarjetas de E/S“.

kimaldi8

la sintaxis del método a continuación:

kimaldi9

hemos añadido varios controles:

  • txtStatus: Un TextBox en la parte superior donde mostrar el estado, con texto alineado en el centro y una línea de borde (BorderStyle: FixedSingle, TextAlign: Center,
  • numCOMSel: Un elemento de tipo NumericUpDown para seleccionar el número de puerto COM.
  • btnOpenCOM: Botón para abrir el puerto COM selecccionado.

Añadimos el código necesario asociado al evento de pulsación de btnOpenCOM:

kimaldi10

Cuando el puerto COM se abre correctamente vamos a deshabilitar el botón, también añadimos un nuevo botón llamado btnCloseCOM para cerrar el puerto serie, inicialmente deshabilitado.

kimaldi11

 kimaldi12

</p>
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

using PrKBUSXdll;

namespace KimaldiOpenCOM
{
public partial class Form1 : Form
{
KBUSXdll objKimaldi = new KBUSXdll();

//-----------------------------------------------------------------

/**
*
*
*
*/
public Form1()
{
InitializeComponent();
this.DisableBtnCloseCOM();
}

//-----------------------------------------------------------------

/**
*
*
*/
private void ClearTxtStatus()
{
txtStatus.Clear();
}

//-----------------------------------------------------------------

private void EnableBtnCloseCOM()
{
btnCloseCOM.Enabled = true;
}

//-----------------------------------------------------------------

private void DisableBtnCloseCOM()
{
btnCloseCOM.Enabled = false;
}

//-----------------------------------------------------------------

private void DisableBtnOpenCOM()
{
btnOpenCOM.Enabled = false;
}

//-----------------------------------------------------------------

private void EnableBtnOpenCOM()
{
btnOpenCOM.Enabled = true;
}

//-----------------------------------------------------------------

/**
* Abre el puerto de comunicaciones.
*
*/
private void btnOpenCOM_Click(object sender, EventArgs e)
{
byte bRetCode = objKimaldi.OpenPort(Convert.ToByte(numCOMSel.Value), "", 0);

switch (bRetCode)
{
case 0: //Ok.
txtStatus.Text = "ok";
txtStatus.ForeColor = Color.Green;
this.DisableBtnOpenCOM();
this.EnableBtnCloseCOM();
break;

case 1: //El puerto está abierto.
txtStatus.Text = "El puerto está abierto.";
txtStatus.ForeColor = Color.Green;
this.DisableBtnOpenCOM();
this.EnableBtnCloseCOM();
break;
case 2: //Nº del puerto incorrecto.
txtStatus.Text = "Nº del puerto incorrecto.";
txtStatus.ForeColor = Color.Red;
this.EnableBtnOpenCOM();
this.DisableBtnCloseCOM();
break;

case 3: //Dirección TCP incorrecta.
txtStatus.Text = "Dirección TCP incorrecta.";
txtStatus.ForeColor = Color.Red;
this.EnableBtnOpenCOM();
this.DisableBtnCloseCOM();
break;

case 4: //Error de apertura del puerto.
txtStatus.Text = "Error de apertura del puerto.";
txtStatus.ForeColor = Color.Red;
this.EnableBtnOpenCOM();
this.DisableBtnCloseCOM();
break;

case 255:
txtStatus.Text = "Otros errores.";
txtStatus.ForeColor = Color.Red;
this.EnableBtnOpenCOM();
this.DisableBtnCloseCOM();
break;

default:
txtStatus.Text = "Retorno desconocido";
txtStatus.ForeColor = Color.Red;
this.EnableBtnOpenCOM();
this.DisableBtnCloseCOM();
break;
}
}

//-----------------------------------------------------------------

/**
* Cierra puerto de comunicaciones COM.
* Empleo a partir de la fase 6.
*
*/
private void btnCloseCOM_Click(object sender, EventArgs e)
{
//No retorna nada...
objKimaldi.ClosePort();

}

//-----------------------------------------------------------------

/**
* Comprueba las comunicaciones con el concentrador.
* Si las comunicaciones funcionan correctamente se disparará el
* evento AnsTestCtrLink. En caso contrario se disparará el evento CtrTimeout
* A partir de la fase 3
*
*/
private void TestComsConcentrador()
{
byte bRet = objKimaldi.TestCtrLink();
switch (bRet)
{
case 0: //OK
txtStatus.Text = "ok probando comunicaciones con concentrador";
txtStatus.ForeColor = Color.Green;
break;

case 1: //No se ha abierto el puerto.
txtStatus.Text = "No se ha abierto el puerto";
txtStatus.ForeColor = Color.Red;
break;

case 2: //Otros errores.
txtStatus.Text = "Otros errores";
txtStatus.ForeColor = Color.Red;
break;

default: //Retorno desconocido
txtStatus.Text = "Retorno desconocido";
txtStatus.ForeColor = Color.Red;
break;
}
}

}
}
<p style="text-align: justify;">

C# GetKimaldiDllVer: DLL de fabricante para trabajar con tarjetas de E/S

Creamos un nuevo proyecto GetKimaldiDllVer en Microsoft Visual Studio Professional 2012 de tipo Windows Form Application para Visual C#.

Primero copiamos PrKBUSXdll.dll a la carpeta Debug junto al ejecutable de nuestra nueva aplicación.

En el explorador de solución debemos añadir la referencia a la DLL

kimaldi1

Ahora ya deberíamos ver una nueva referencia a PrKBUSXdll.dll:

kimaldi2

 

En el código de nuestra aplicación añadimos la siguiente línea:

kimaldi3

Ahora instanciamos la clase KBUSXdll:

kimaldi4

 

Ahora diseñamos el formulario con un botón que al pulsarlo mostrará la versión de la Dll en un campo de edición de texto, añadimos 2 controles:

  • btnGetDllVer: Botón con estilo plano (FlatStyle) y cambiamos color del texto con la propiedad ForeColor.
  • txtDllVer: TextBox de sólo lectura (ReadOnly a True) y con el mismo ForColor.

El resultado final es este:

kimaldi5

Ahora creamos un evento en respuesta a la pulsación del botón que obtenga la versión de la Dll llamando a su función GetVersion().

kimaldi6

Resultado final cuando pulsamos el botón:

kimaldi7