Simulador de balanza

Todos los que nos dedicamos, como yo, a pelearnos con programas para tiendas, supermercados, etc ... alguna vez hemos tenido que comunicar nuestros programas con una balanza para obtener el peso. No resulta demasiado complicado conociendo el protocolo de comunicación de la balanza, así que no voy a explicar como hacerlo. Al contrario, la siguiente aplicación es un simulador de una balanza, es decir suplanta a la balanza durante la comunicación permitiéndonos así depurar nuestros programas de venta sin tener que tener conectada ninguna balanza a nuestro PC.

Para este simulador he escogido el protocolo $ (puede recibir otros nombres), que es mas simple que el mecanismo de un chupete. Para obtener el peso el PC solamente tiene que enviar el carácter $ por el puerto serie y la balanza le devuelve el peso en caracteres legibles.

Por ejemplo:

PC: $
Balanza: 001.123

Este protocolo puede variar ligeramente de una marca de balanza a otra, por ejemplo, puede usar una coma en vez de un punto para separar los decimales o puede variar el numero de dígitos. Detalles que también debemos tener en cuenta cuando estemos diseñando un el software para un modelo de balanza en concreto.

Al ejecutar el programa nos encontramos con una ventana como esta:

Una vez que hemos seleccionado el puerto serie, solo tenemos que pulsar en el botón iniciar. Para escoger el peso se utiliza el control deslizable que se encuentra debajo del display, de este modo cuando el programa de venta solicite el peso se le enviara el valor que se encuentre ese momento en el display.

En este punto podemos pensar que el programa no es muy útil, o al menos un poco lioso, ya que si el simulador esta usando un puerto serie, el programa de venta tendrá que estar usando otro diferente, y para comunicarse entre ambos necesitaríamos unirlos físicamente por medio de un cable serie, lo que resultara muy engorroso. Pero otra vez la "virtualizacion" nos vuelve a resolver este problema, solo tenemos que crear un par de puertos serie virtuales interconectados entre si, conectando el programa de venta y el simulador en ambos puertos.

Para crear ese "par" de puertos extra yo recomiendo este software, que funciona de maravilla, es fácil de configurar y ademas es gratuito y de código abierto, no se puede pedir más.
http://com0com.sourceforge.net/

Una vez esta un poco mas claro el funcionamiento del programa vamos a explicar un poco el código.

La pieza principal es la unidad "uEnlace":

unit uEnlace;
 
interface
 
uses Windows, Sysutils, Classes;
 
type
  EThreadEnlace = class(Exception);
 
  TThreadEnlaceEvent = procedure(Sender: TObject; Command: String;
    var Response: String) of object;
 
  TThreadEnlace = class(TThread)
  private
    FHandle: THandle;
    FCommand: String;
    FPort: String;
    FRaw: Boolean;
    FOnCommand: TThreadEnlaceEvent;
    procedure DoOnCommand;
    procedure Enviar(Str: String);
    procedure Procesar(Str: String);
  protected
    procedure Execute; override;
  public
    constructor Create(APort: String; Raw: Boolean);
    destructor Destroy; override;
    property OnCommand: TThreadEnlaceEvent read FOnCommand write FOnCommand;
  end;
 
implementation
 
{ TThreadEnlace }
 
constructor TThreadEnlace.Create(APort: String; Raw: Boolean);
var
  DCB: TDCB;
begin
  FCommand:= EmptyStr;
  FPort:= APort;
  FRaw:= Raw;
  FHandle:= CreateFile(PChar('\\.\' + APort), GENERIC_READ or
    GENERIC_WRITE, 0, nil, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
  if FHandle = INVALID_HANDLE_VALUE then
    raise EThreadEnlace.Create('Error en ' + APort);
  DCB.DCBlength:= Sizeof(DCB);
  if not GetCommState(FHandle,DCB) then
    raise EThreadEnlace.Create('Error en ' + APort);
  // Aqui esta la configuracion del puerto. Se la podriamos pasar por parametros ...
  with DCB do
  begin
    BaudRate := CBR_9600;
    ByteSize := 8;
    Parity   := NOPARITY;
    StopBits := ONESTOPBIT;
    Flags    := $01;
  end;
  if not SetCommState(FHandle, DCB) then
    raise EThreadEnlace.Create('Error en ' + APort);
  inherited Create(FALSE);
end;
 
destructor TThreadEnlace.Destroy;
begin
  if FHandle <> INVALID_HANDLE_VALUE then
    CloseHandle(FHandle);
  inherited;
end;
 
procedure TThreadEnlace.DoOnCommand;
var
  Str: String;
begin
  if Assigned(FOnCommand) then
  begin
    Str:= EmptyStr;
    FOnCommand(Self,FCommand,Str);
    if Str <> EmptyStr then
      Enviar(Str);
  end;
end;
 
const
  CR = #13;
  LF = #10;
  BUFFERSIZE = 1024;
 
procedure TThreadEnlace.Enviar(Str: String);
begin
  if FHandle <> INVALID_HANDLE_VALUE then
    FileWrite(FHandle,PChar(Str)^,Length(Str));
end;
 
procedure TThreadEnlace.Execute;
var
  Err: DWORD;
  COMSTAT: TCOMSTAT;
  Buffer: PChar;
  Str: String;
  i: Integer;
begin
  Getmem(Buffer,BUFFERSIZE);
  try
    Str:= EmptyStr;
    while not Terminated do
      if ClearCommError(FHandle,Err,@COMSTAT) then
        if COMSTAT.cbInQue > 0 then
        begin
          if COMSTAT.cbInQue >= BUFFERSIZE then
            Err:= BUFFERSIZE - 1
          else
            Err:= COMSTAT.cbInQue;
          FillChar(Buffer^,BUFFERSIZE,#0);
          if ReadFile(FHandle,Buffer^,Err,Err,nil) then
          begin
            Str:= Str + String(Buffer);
            if FRaw then
            begin
              Procesar(Str);
              Str:= EmptyStr;
            end else
            begin
              i:= Pos(CR,Str);
              while i > 0 do
              begin
                Procesar(Trim(Copy(Str,1,i-1)));
                Delete(Str,1,i);
                i:= Pos(CR,Str);
              end;
            end;
          end else
          begin
            // Aqui podemos guardar un error en el log
          end;
        end else
          Sleep(10);
  finally
    FreeMem(Buffer);
  end;
end;
 
procedure TThreadEnlace.Procesar(Str: String);
begin
  FCommand:= Str;
  Synchronize(DoOnCommand);
end;
 
end.

Esta es una unit que desarrolle hace algún tiempo para otro proyecto, y que basicamente permite enviar y recibir comandos por el puerto serie en paralelo con el hilo principal de la aplicación, de esta manera no se queda congelada esperando datos. Tiene dos modos de funcionamiento, el normal y "RAW". En el modo normal los datos se reciben en lineas de texto terminadas por un carácter separador, en este caso CR (Carriage return), y los datos no se procesan hasta que se recibe una linea completa. En cambio en el modo RAW, el que utilizamos en esta aplicación, los datos se procesa según van llegando al puerto serie.

Dentro de esta unit encontramos la clase TThreadEnlace que es la que se encarga de manejar el puerto serie. Esta clase tiene un evento llamado "OnCommand" que se ejecuta cada vez que se reciben datos por el puerto serie, asi que practicamente hace todo el trabajo, nosotros solo tenemos que añadir un poco de codigo a este evento para crear nuestro programa:

procedure TfrmMain.OnCommand(Sender: TObject; Command: String;
  var Response: String);
begin
  if Pos('$',Command) > 0  then
    Response:= FormatFloat('000.000',tbPeso.Position / 1000) + #13;
end;

El resto del código simplemente sirve para crear y destruir la clase TThreadEnlace y para obtener los parámetros de configuración.

El programa y el código fuente se puede descargar aquí

Enlaces interesantes:
http://com0com.sourceforge.net/ (Un programa imprescindible)

Comentarios

Hola Domingo.
Lástima que no lo publicaras hace un tiempo cuando me estuve peleando con este mismo tema... ;-)
Muy interesante como siempre y muy útil.

Un saludo.

Hola Germán,

La verdad es que voy publicando estas utilidades según las voy necesitando. Ahora mismo me encuentro programando un "teclado autónomo" conectado a una balanza, y me canse de tener todos los aparatos encima de mi mesa, así que conecte el teclado al puerto serie de mi ordenador y guarde todo lo demás.

Saludos

Que tal Domingo

Siempre interesantes tus apuntes y tus 'inventos', como dice el dicho 'la necesidad es la madre de todos los inventos'. :)

Saludos

Que tal colega...
Donde puedo descarga la balanza, solamente vi el enlace de la aplicacion de puertos virtuales

Pues donde dice:

El programa y el código fuente se puede descargar aquí

Pulsa donde pone "aquí"

Hola a todos,

Probaron este simulador con su programa? Estoy haciendo un programa en java para recibir datos de una báscula y me es de mucha utilidad, pero no consigo recibir datos no sé pero seguro que es problema de mi programa que no hago comunicarlo bien. Aparte del simulador necesito algo más?
Saludos.

Si lo he probado y funciona bien.

Para probarlo, primero crea una pareja de puertos virtuales, por ejemplo COM4 y COM5. Luego abre el simulador y escoge el puerto COM4, entonces puedes utilizar el HyperTerminal si estas en Windows XP o el PuTTY si estas en Windows 7 para conectarse con el simulador usando el puerto COM5 (el otro extremo del par de puertos). Ahora solo tienes que mandar el carácter $ y te devolverá el peso.

Saludos

Se me olvidaba, el programa abre el puerto serie sin "Control de flujo", si tu programa abre, por ejemplo, el puerto con control de flujo por hardware no recibirá nada porque estará esperando que mi programa le de permiso para enviar cosa que nunca sucedera.

Hola, he probado su programa con Hiperterminal y putty y funciona a la perfección! Así que le felicito por su gran trabajo.

Estoy haciendo un programa para enviar a través del puerto serie y recibir datos (para ejecutarlo en una báscula real también con protocolo $), estoy escribiendo un programa en c/c++ y logro escribir en el puerto pero no leer, no sé exactamente si puede ser por tema de control de flujo como indicas pero se queda esperando sin recibir nada. Parte del código es el siguiente:

           ....
	/* abrimos el puerto */
	h=CreateFile("COM9",GENERIC_READ|GENERIC_WRITE,0,NULL,OPEN_EXISTING,0,NULL);
           ....
 
/* Configuramos el puerto */
	dcb.BaudRate = 9600;
	dcb.ByteSize = 8;
	dcb.Parity = NOPARITY;
	dcb.StopBits = ONESTOPBIT;
	dcb.fBinary = TRUE;
	dcb.fParity = TRUE;
	//CONTROL DE FLUJO.
	dcb.fOutX = FALSE;
	dcb.fInX=FALSE;
    dcb.fOutxCtsFlow= FALSE;
    dcb.fOutxDsrFlow=FALSE;
    dcb.fDtrControl = DTR_CONTROL_DISABLE;
    dcb.fRtsControl = RTS_CONTROL_DISABLE;
 
	/* Establecemos la nueva configuracion */
	if(!SetCommState(h, &dcb)) {
		/* Error al configurar el puerto */
		printf("Error Nueva Configuracion");
	}
        ....
DWORD n;
		DWORD x;
		char enviar;
		char recibido;
		char *cad;
		COMSTAT cs;
		/* Enviamos... */
		enviar = '$';
		//printf("$");
		if(!WriteFile(h, &enviar/*puntero al buffer*/, 1/* 1 byte*/, &n, NULL)){
			/* Error al enviar */
			printf("Error al enviar");
		}
		else { printf("Enviado $\n");}
               ....
           ClearCommError (h, &x, &cs);
               ....
           ReadFile(h, cad, 1, &x, NULL);
               ....

Le agradecería si me pudiera ayudar.

Gracias!

Así, a simple vista, solo veo "fParity=TRUE" mientras que en mi caso no utilizo paridad.

Algo me falta ya que genere los dos puertos virtuales con com0com, asigno respectivamente uno de ellos a la aplicacion y el otro mediante hyperterminal, pero cuando envio el caracter "$" por hyperterminal (porque me imagino que por ahi lo envian para hacer las pruebas), no recibo nada :-/

No se si me estara faltando algo ya que en hyperterminal seteo el puerto a 9600,8,N,1,N tal cual como se menciona aca, pero no pasa nada... me gustaria me pudiesen responder ya que trabajo con balanzas y seria de gran ayuda tener una aplicacion como esta.

Saludos.

Prueba a abrir dos hyperterminal y conecta cada uno a un puerto virtual ¿lo que tecleas en uno te aparece en el otro?

Si es así, comprueba entonces que no estas usando "control de flujo". También prueba a cerrar todo y abrir primero el programa simulador y luego el hypertrminal. Hay veces que el puerto virtual se queda como bloqueado.

No se me ocurren mas motivos por los que puede fallar.

Siiii... al parecer era una cuestion de orden de apertura, probe la comunicacion entre dos hyperterminal y no hubo problema, despues abri primero el programa y luego el hyperterminal, con eso la comunicacion se establecio sin ningun problema :D

Muchas gracias!!!

Hola, gente
Quería saber como emular las balanzas que no usan protocolo alguno, que tienen lectura continua, como puedo emular este tipo de balanzas?

amigo, este programa es sin duda lo que estaba buscando, pero de verdad que no puedo hacer ese par de puertos, me he descargado el programita, y solo viene en versión DOS, pero no logro hacer que me lea los datos programo en vb6 y uso una báscula mediante com, estoy programando en una maquina sin puertos físicos, los emulo con la maquina virtual, y tu programa funciona muy bien, pero no logro obtener los datos. será posible saber como lograste crear ese par de puertos y unirlos con el programa? de antemano muchas gracias

En la pagina de sourceforge del proyecto "com0com" se puede descargar la versión completa del emulador de puerto serie, con interface gráfico incluido

Aquí la descarga directa
http://surfnet.dl.sourceforge.net/project/com0com/com0com/3.0.0.0/com0co...

Aquí la web de sourceforge
http://sourceforge.net/projects/com0com/

Saludos

me tome la molestia de preguntarte, y ahora me tomo la libertad de agradecerte, si este link que me pasaste es el bueno!!!, Magnifico!!! tengo un POS que tenia sus detalles en la bascula, manejo vb6 y ps las laptops no tienen puerto serial ya ni hablar de mi mac. ESTO SIN DUDA me resuelve mucho de los problemas. Imaginaba una solucion asi pero no pense que alguien mas las padeciera. un saludote amigo. gracias

Gracias por el simulador, me funciona muy bien.
Ahora estoy haciendo unas pruebas desde java con la librería RXTXcomm, pero al momento de abrir el puerto me pide "nombre de propietario", no se que ponerle :S

Se puede realizar esto?

Gracias de antemano.

Lo siento, pero no conozco es librería, así que no puedo ayudarte. De todos modos me resulta extraño que te pida un "nombre de propietario", no tengo ni idea de a que se puede estar refiriendo.

Igual que Gabriel,
Quería saber como emular las balanzas que no usan protocolo (Ej. $) , y que tienen lectura continua, necesito emular este tipo de balanzas