SerialPort en ccTalk

Los dispositivos de pago se comunican con el ordenador por comunicación serial o USB. En este post os contaré como hacerlo por comunicación serial.

Desde luego que desarrollar toda una clase de comunicación serial puede ser complejo dependiendo del control que se quiera tener. Como este post es para principiantes solo voy a considerar lo básico para enviar y recibir datos.

Los dispositivo de pago nunca envían ninguna respuesta si no reciben primero un comando, esto sucede con ccTalk y CCNet. La comunicación es Half Duplex (la comunicación es bidireccional pero no simultánea). Si el dispositivo no responde no necesariamente el problema es de conexión, puede que sea un error en la construcción de la trama que se envía.

En .Net tenemos la clase SerialPort, la cual trae todo lo necesario para establecer una comunicación. Os muestro código en C# muy básico para enviar y recibir datos.

Creamos el objeto Serial Port, configurando que escuche los eventos (respuestas) que el dispositivo envía al ordenador.

using System.IO.Ports;

...
moSerialPort = new SerialPort(puertoCOM, 9600,
                                             Parity.None,
                                             8, StopBits.One);
moSerialPort.DataReceived += onDataReceivedEvent;
moSerialPort.Open();
...

Para enviar datos utilizaremos el método Write del objeto SerialPort:

public bool sendData(byte[] datos)
{
    bool lbResultado = false;
    try
    {
        if (moSerialPort.IsOpen)
        {
            moSerialPort.Write(datos, 0, datos.Length);
            lbResultado = true;
        }
    }
    catch
    {
        //TODO: Loginar error
    }
    return lbResultado;
}

Si lo que enviamos es correcto entonces el dispositivo debe responder mediante el evento.

private void onDataReceivedEvent(object sender, SerialDataReceivedEventArgs e)
{
    try
    {
        // Es necesario dar unos milisegundo antes de verificar cuantos
        // bytes tenemos en el buffer. Este es el secreto para obtener la respuesta completa.
        // En un ambiente real esto es mas complejo. Aca escribo algo simple pero funciona
        Thread.Sleep(50);

        int longRespuesta= moSerialPort.BytesToRead;
        byte[] arrayRespuesta = new byte[longRespuesta];

        moSerialPort.Read(arrayRespuesta , 0, longRespuesta);
    }
    catch
    {
        // TODO: Loginar errores
    }
}

En la variable arrayRespuesta tendras la trama respuesta. Para el caso de ccTalk la primera parte de la respuesta es el eco (ósea la misma trama que hemos enviado) seguido de la trama que recibimos, esto es para garantizar que la respuesta corresponde al comando enviado.

Con este ejemplo muy básico tenéis todo lo necesario para comunicar con el dispositivo. En ambientes de desarrollo que estoy trabajando la clase de comunicaciones es más compleja. Espero os sirva.

Hopper: Request hopper status

Después de hacer un dispense al Hopper debemos controlar las monedas que el dispositivo va pagando. Este control lo haremos en un bucle enviando el comando Request Hoppers Status (Header: 166) de forma continua.

Los datos que envía y recibe este comando son:
Transmitted data : <none>
Received data : [ event counter ] [ payout coins remaining ]
[ last payout : coins paid ] [ last payout : coins unpaid ]

  • [ event counter ]: Es un contador de eventos de todos los dispense de monedas que has sido validos (Dispense hopper coins).
  • [ payout coins remaining ]: Numero de monedas que faltan por pagar.
  • [ last payout : coins paid ]: Numero de monedas pagadas.
  • [ last payout : coins unpaid]: Numero de monedas no pagadas.

Este comando se debe repetir continuamente hasta que [payaout coins remaining] = 0. Este es el condicional que nos hará salir del bucle y terminar con el proceso de control.

El comando se debe enviar cada N ms, el valor estará en la documentación del dispositivo. Por ejemplo para el Hopper Universal de Money Controls se debe hacer cada 200 ms.

El punto final cuando [payaout coins remaining] = 0 es muy importante porque en ese momento tendremos la información final de cuantas monedas se han pagado ([ last payout : coins paid ]) y cuantas no se ha pagado ([ last payout : coins unpaid]).

Hopper: Dispense monedas

Uno de los comandos mas importantes del Hopper es Dispense hopper coins (Header 167), con el cual podemos dispensar monedas (1 a 255).

Los datos que se envian y que se reciben pueden tomar la siguiente forma.
Transmitted data : [ sec 1 ] [ sec 2 ] … [ sec 8 ] [ N coins ]
Received data : [ event counter ]

Es necesario revisar la documentación del Hopper, los datos que se envían y se reciben dependen del modelo de Hopper. Existen por ejemplo un modelo en el que el Received data puede ser ACK o NAK.

Yo he trabajado con el Hopper Universal de Money Controls. Para este modelo los ocho primeros byte de datos (Transmitted data) se obtienen de un algoritmo de encriptación proporcionado por el fabricante, luego se agrega el numero de monedas a dispensar para completar los 9 bytes de datos.

Si es un Hopper que recibe un ACK entonces el dispense ha iniciado correctamente. Como mencione anteriormente esto depende de la información técnica de cada fabricante. El código que programéis siempre debe verificar el checksum de los datos que se envían y reciben (el algoritmo ya fue publicado en este blog).

La respuesta a un dispense es inmediata, por lo que si el dispositivo responde correctamente entonces os aseguro que el dispositivo ha iniciado a dispensar monedas.

Luego del dispense inmediatamente empezamos a enviar el comando Request hopper status (Header 166) en un bucle continuo, para ir controlando si se expulsan o no todas las monedas solicitadas en el dispense.

CRC Checksum para el protocolo ccTalk

Como sabéis tengo muchos años desarrollando con protocolos ccTalk y CCNet, en dispositivos de pago, para cajeros automáticos de Parkings.

Cuando inicias con ccTalk una de las primeras cosas que con las que te encuentras esta relacionado al Checksum. Ya no tienes que buscar, acá te entrego una función en C#:

private static byte getChecksum(byte destinationAddress,
                                byte sourceAddress,
                                byte header,
                                byte[] data)
{
    int sumaData = 0;
    int sumaPaquete = 0;
    int numeroBytesData = 0;
    int validaSuma = 0;
    byte checksumCalculado = 0;

    if (data != null && data.Length > 0)
    {
        // Si existen datos obtenemos la suma de todos los bytes de Data
        numeroBytesData = data.Length;
        for (int i = 0; i < numeroBytesData; ++i)
            sumaData += data[i];
    }

    // Suma total de todos los bytes del mensaje
    sumaPaquete = destinationAddress + numeroBytesData +
                 sourceAddress + header + sumaData;

    // Valida el resultado
    validaSuma = ((int)(sumaPaquete / 256) + 1) * 256 - sumaPaquete;

    // Verifica si es valido, caso contrario retorna cero
    if (validaSuma != 256)
        checksumCalculado = Convert.ToByte(((int)(sumaPaquete / 256) + 1) * 256 - sumaPaquete);

    return checksumCalculado;
}

CCNET CRC en C#

Además de los proyectos que he completado a nivel de ccTalk (Hopper, Pelicano, Monedero) también he trabajado mucho con el protocolo CCNET.
En la documentación de CCNET encontrareis el código de CRC para verificar la trama, pero el código de ejemplo esta en C y en Pascal.
He trabajado durante muchos años con los billeteros de CashCode y funcionan perfectamente.

Acá os dejo el código en C# para el CRC:

private byte[] getCRC(byte[] paquete)
{    
    const ushort POLYNOMIAL = 0x08408;     
    byte[] arrayCRC = new byte[4];    
    int CRC = 0;

    for (int i = 0; i < paquete.Length; ++i)
    {
        CRC ^= paquete[i];

        for (int j = 0; j < 8; ++j)
        {
            if ((CRC & 0x0001) != 0)
            {
                CRC >>= 1;
                CRC ^= POLYNOMIAL;
            }
            else
            {
                CRC >>= 1;
            }
        }
    }

    arrayCRC = BitConverter.GetBytes(CRC);

    // Los dos primeros bytes corresponden al CRC
    return new byte[] { arrayCRC[0], arrayCRC[1]};
}

ACK y NAK en ccTalk

Es importante tener claro que es ACK y NAK cuando trabajas con protocolos de comunicación. Explicaré como se trabaja estos temas en el protocolo ccTalk:

ACK: Viene de la palabra ACKnowledgement. En términos simples el dispositivo después de recibir el comando responde “Ha llegado el comando y además ha llegado bien”.
La trama de la respuesta ACK en ccTalk siempre será:

[ Destination Address ]
[ 0 ]
[ Source Address ]
[ 0 ]
[ Checksum ]

Como vemos el Header es siempre 0 y no tenemos datos.

NAK: Es lo contrario a ACKnowledgement (Negative ACKnowledgement). En el protocolo ccTalk el uso de NAK esta restringido, porque como sabemos una trama ccTalk tiene dirección de origen y destino, por lo que si el dispositivo recibe una trama errónea no sabe si el destino u origen son correctos. El dispositivo simplemente no responde.

Así que cuando inicies en este mundillo de ccTalk asegúrate que la trama (especialmente el checkSum) sea correcta. Si el dispositivo no responde no pienses inmediatamente que es un problema de comunicación.

Como mencione el uso de NAK esta restringido. Algunos comandos pueden retornar NAK por ejemplo: Hacemos un dispense a un Hopper que tiene un opto estropeado, entonces el dispense no se podrá ejecutar y se retornará un NAK.

La documentación de cada comando siempre especifica si la respuesta puede ser un NAK. Por ejemplo:
Header 167 – Dispense hopper coins
Transmitted data : <variable> [ no. of coins ]
Received data : [ event counter ] or NAK

Que es el Protocolo ccTalk?

Es un protocolo de comunicación serial, desarrollado por Money Controls, diseñado para la comunicación con dispositivos de pago (Aceptadores de monedas, Hoppers, Billeteros, Pelicano, etc.). La comunicación requiere solo un cable de 3 hilos (alimentación, data y tierra).

Actualmente estoy trabajando con todos estos dispositivos para cajeros automáticos de parkings, además de ccTalk es muy frecuente utilizar el protocolo ccNet en billeteros. Desconozco que otros protocolos se utilizan por ejemplo en cajeros automáticos del sector bancario.

La documentación oficial de ccTalk (ccTalk Generic Specification) es libre y esta disponible en: http://cctalk.org/

La comunicación serial con los dispositivos se hacen con los siguientes valores:

9600 baud, 1 start bit, 8 data bits, no parity bit, 1 stop bit.

Los dispositivos ya vienen configurados de esta forma y es la que debes asumir por defecto, a menos que la documentación del dispositivo diga lo contrario. El handshaking no es soportado.

La estructura Standard del mensaje ccTak es:
[Destination Address]
[No. of Data Bytes]
[Source Address]
[Header]
[Data 1]

[Data N]
[Checksum],

Por lo que si no tuviéramos datos el mensaje seria:
[Destination Address]
[0]
[Source Address]
[Header]
[Checksum]

Esto se entiende mucho mejor con un ejemplo:
Vamos a enviar un comando desde nuestra aplicación (Host con dirección 1) a un aceptador de monedas (con dirección 2).

Utilizando la documentación ccTalk vamos a enviar el comando “Request equipment category Id”. La información de este comando dice:
Header 245 – Request equipment category id
Transmitted data : <none>
Received data: ASCII

Esto quiere decir que el mensaje que se va trasmitir no tiene datos y será:

  • Destination Address: 2 (Dirección de fabrica del aceptador de monedas).
  • No. of Data Bytes: 0 (este comando no requiere de datos).
    Source Address: 1 (dirección Host de nuestra aplicación, por defecto siempre es 1).
  • Header: 245 (código del comando).
  • Checksum: 8 (este es el valor del checksum. publicaré en un futuro post la función para calcular el Checksum).

El mensaje que enviaremos quedara entonces asi:

002 000 001 245 008

El dispositivo nos debe responder de forma inmediata lo siguiente:

001 013 002 000 067 111 105 110 032 065 099 099 101 112 116 111 114 022
Donde observamos que nos envía 13 datos de respuesta. La documentación del comando decía que nos devolvería ASCII, por lo que si obtenemos el equivalente ASCII de los datos de respuesta obtenemos la cadena: Coin Acceptor

Cada vez que vayas a un parking, y antes de salir te acerques a un cajero automático a pagar, ten presente que existe la posibilidad que todos los dispositivos por donde ingresas las monedas y/o billetes, y los que retornan el cambio están comunicándose con protocolo ccTalk.

El grupo Crane, compro MoneyControls, Cash Code y Telequip en el 2011, que unido a su marca NRI, toma el nombre de Crane Payments Solutions.