Apertura de gavetas de dinero desde un sistema POS remoto

En este artículo quiero escribir sobre una de esas tantas soluciones rebuscadas que he tenido que encontrar, para resolver uno de esos problemas curiosos que se presentan muy a menudo en esta profesión.

El artículo trata acerca de la implementación de la apertura de gavetas de dinero en puntos de ventas de un local comercial, pero como verán los lectores más adelante, el escenario dista mucho de ser el "tradicional" punto de ventas de un supermercado.

Normalmente, las gavetas de dinero utilizadas en los puntos de ventas (POS, Point of sale) de los supermercados suelen estar conectadas a impresoras de tickets matriciales. Cuando las impresoras reciben un comando ESC/POS enviadas desde el sistema, la misma envía un pulso eléctrico de 24v a través de su interfaz RJ12 a la gaveta, y la misma procede a su apertura.

En este caso el objetivo era que al finalizar la transacción comercial en el punto de venta, el sistema se encargara de imprimir el comprobante legal y de abrir automáticamente la gaveta de dinero al cajero así como en un supermercado, pero en un entorno de trabajo con las siguientes características:
  1. Las impresoras de comprobantes eran las tradicionales Epson LX-300 o bien impresoras Láser para imprimir facturas de gran tamaño, tipos de impresoras que no llevan el puerto RJ12 presente en cualquier impresora de tickets de los supermercados.
  2. Los puntos de ventas eran computadores normales con un sistema operativo openSUSE Linux.
  3. El sistema de punto de venta no era una aplicación gráfica instalada en el sistema operativo, sino un sistema de tipo terminal que se ejecutaba en un servidor remoto al cual se accedía a través de conexiones SSH.

Bajo las condiciones antes mencionadas se decidió proceder con las siguientes soluciones, algunas de ellas "algo rebuscadas":
  1. Impresoras EPSON LX-300 y/o impresoras Láser:
    Era un requerimiento utilizar este tipo de impresoras debido a que en los mencionados puntos de ventas se deseaban imprimir las facturas de gran tamaño. Como las mencionadas impresoras no llevan el Drawer Port RJ12, se optó por comprar unas gavetas de dinero con puertos seriales RS232 que iban conectadas directamente el ordenador.

    Si bien las gavetas funcionan bien, tienen la particularidad de que necesitan un pequeño integrado que entienda el comando que se le está enviando y algunos modelos incluso un transformador adicional que alimente al interruptor de apertura de la gaveta con la energía necesaria, ya que el puerto serial no tiene el voltaje suficiente para el accionamiento directo del interruptor.

    Hasta aquí la solución fue optar por un hardware diferente, ahora pasemos al siguiente.

  2. Puntos de ventas con sistemas operativos Linux:
    Los entornos tradicionales utilizarían un ordenador con un sistema operativo Windows, desde el cual podemos abrir las gavetas de dinero con puertos seriales RS232 ejecutando los siguientes comandos desde el intérprete de comandos CMD de Windows:
    CMD> mode com1 9600,N,8,1
    CMD> echo "^G" > com1
    
    Básicamente lo que se realiza primero es configurar la velocidad del puerto serial que recomienda el proveedor de la gaveta de dinero y luego enviarle el comando Ctrl+G(^G) al puerto serial com1.

    Desde una distribución Linux el comando tampoco es que varíe demasiado, no necesitamos configurar la velocidad del puerto serial, sino simplemente enviar el siguiente comando desde el usuario root:
    cajero@testsrv:~> su -
    testsrv:~ # echo -en "\07" > /dev/ttyS0
    
    El comando anterior redirecciona la salida del comando echo al puerto serial /dev/ttyS0 (lo que en windows sería el com1). Es necesario que el comando echo se ejecute con los argumentos -en, para que pueda enviar de forma directa al puerto serial el valor hexadecimal 07, o lo que es lo mismo, el valor Ctrl+G (BELL) como podemos apreciar en la tabla ASCII de ésta entrada en Wikipedia.

    Ahora bien, en openSUSE Linux solo el usuario root está autorizado por defecto a acceder de forma directa a los puertos seriales, y como el usuario del sistema operativo encargado de ejecutar el sistema de puntos de ventas era un usuario normal denominado cajero sin los mencionados privilegios (nadie en su sano juicio debería utilizar el usuario root para esto), se le tuvo que agregar al grupo de usuarios del sistema operativo dialout de la siguiente manera:
    cajero@testsrv:~> su -
    testsrv:~ # usermod -G dialout cajero
    
    Para verificar a que grupos se encuentre asociado un usuario de Linux siempre podemos ejecutar el siguiente comando:
    cajero@testsrv:~> su -
    testsrv:~ # groups cajero
    cajero: users dialout
    testsrv:~ #
    
    Bien, hasta aquí logramos estar en condiciones de poder abrir nuestras gavetas desde un usuario normal de un sistema operativo Linux, sigamos adelante.

  3. Monitoreo de flujos de texto para identificación de código patrón de apertura de gaveta:
    Uno de los principales problemas era que los usuarios del punto de venta se conectaban desde la terminal Linux Konsole a un servidor remoto mediante el protocolo SSH para ejecutar el sistema, lo que evidentemente implica que el programa no se ejecutaba en la máquina cliente, sino solamente en el servidor, permitiendo solamente el acceso a las unidades y dispositivos del servidor.

    Para resolver ese "pequeño" inconveniente lo único que se me ocurrió fue desarrollar un servicio en el equipo cliente que sea capaz de procesar el flujo de texto constante generado por la aplicación remota y poder identificar entre dicho caudal de información algún código o patrón previamente convenido que permita saber en que momento se debía abrir la gaveta de dinero.

    Pero un momento, es posible redirigir todo el texto que genera una aplicación remota a un servicio que filtre dicho contenido y lo siga mostrando en la pantalla del equipo cliente? Si, se puede, y la herramienta clave se llama tee. El comando tee permite recibir la salida estándar de un programa, que en este caso sería la conexión remota, y generar dos salidas idénticas, una a la pantalla del usuario (salida estándar) y la otra hacia un archivo, o bien a una tubería.

    Pero antes pasemos primero por el desarrollo del servicio denominado tubgaveta, el mismo se creó de la siguiente manera:
    testsrv:~ # touch /usr/bin/tubgaveta
    testsrv:~ # chmod 755 /usr/bin/tubgaveta
    testsrv:~ # chown root:root /usr/bin/tubgaveta
    testsrv:~ # vi /usr/bin/tubgaveta
    
    El script tubgaveta estaba conformado por el siguiente código, cuya funcionalidad se detalla más abajo.
    #!/bin/bash
    #############################################################
    # SCRIT GENERADOR DE PULSO PARA APERTURA DE GAVETA DE DINERO
    # ----------------------------------------------------------
    #                   GABRIEL KFR - DIC 2009
    #############################################################
    # Definicion de variables para el script.
    TUBERIA=/dev/puerto_gaveta
    PUERTO_SERIAL=/dev/ttyS0
    PATRON="&&ABRIRGAVETA&&"
    
    # Renueva la tuberia
    rm -f $TUBERIA
    mkfifo $TUBERIA
    chmod 666 $TUBERIA
    
    # Queda en un loop eterno a menos que se pare
    # el servicio.
    while [ 0 ]; do
        # A continuacion se realizan las siguientes tareas:
        # + cat $TUBERIA: Constantemente se va mostrando lo que viene por la tuberia.
        #
        # + grep -o --line-buffered $PATRON: filtra y muestra el patron de caracteres solo si recibe
        #   una cadena cuyo contenido lo tenga. El argumento -o indica que solo saque al output la
        #   porcion correspondiente al patron y no toda la linea, ya que es posible que el patron
        #   venga entre medio de una cadena de caracteres mas extensa. El argumento --line-buffered
        #   le indica a grep que recien cuando reciba una linea completa de la tuberia proceda a 
        #   aplicar el filtro para buscar coincidencias.
        #
        # + sed -u 's/'$PATRON'/\x07/g': La cadena resultante del grep que debe ser igual al patron se
        #   reemplaza en su totalidad por el caracter CTRL+G para la apertura de gaveta.
        cat $TUBERIA | grep -o --line-buffered $PATRON | sed -u 's/'$PATRON'/\x07/g' >> $PUERTO_SERIAL 
    done
    
    En resumen, cuando se ejecuta la aplicación anterior básicamente se crea una tubería con el comando mkfifo, se le asigna los permisos correspondientes y luego la aplicación entra en un loop infinito a la espera de recibir algo desde la misma. Si durante el monitoreo de la tubería (cat $TUBERIA) se recibe un flujo de texto, dicho flujo será derivado al siguiente filtro (grep -o --line-buffered $PATRON) que solo deja pasar el texto detallado en la variable PATRON al siguiente comando (sed 's/'$PATRON'/\x07/g'), que reemplazará el texto del patrón por el comando ascii correspondiente a Ctrl+G (BELL).

    El script tubgaveta se debía iniciar durante el arranque del sistema operativo, para lo cual en aquella ocasión se creó un script de arranque denominado tgaveta, que a día de hoy sigue funcionando de mala gana. Los pasos para su creación fueron los siguientes:
    testsrv:~ # touch /etc/init.d/tgaveta
    testsrv:~ # chown root:root /etc/init.d/tgaveta
    testsrv:~ # chmod 755 /etc/init.d/tgaveta
    testsrv:~ # vi /etc/init.d/tgaveta
    
    Archivo al cual se le agregó el siguiente código:
    #!/bin/sh
    # Author: Gabriel K
    #
    # /etc/init.d/tgaveta
    #
    ### BEGIN INIT INFO
    # Provides: tubgaveta
    # Required-Start: 
    # Required-Stop: 
    # Default-Start: 3 5
    # Default-Stop: 0 1 2 6
    # Short-Description: Analizador Tuberia
    # Description: Analizador de Tuberia para generación de pulso para apertura de gaveta.
    ### END INIT INFO
    PROGRAM=/usr/bin/tubgaveta
    
    . /etc/rc.status
    
    # The echo return value for success (defined in /etc/rc.config).
    return=$rc_done
    case "$1" in
        start)
            echo -n "Starting service Analizador Tuberia para Apertura de Gaveta "
            ##
            ## Start daemon with startproc(8). If this fails
            ## the echo return value is set appropriate.
            startproc -q -s $PROGRAM
            rc_status -v
            ;;
    
        stop)
            echo -n "Shutting down service Analizador Tuberia para Apertura de Gaveta "
            ## Stop daemon with killproc(8) and if this fails
            ## set echo the echo return value.
            killproc -g $PROGRAM
            rc_status -v
            ;;
    
        try-restart)
            $0 status
            if test $? = 0; then
                $0 restart
            else
                rc_reset
            fi
            rc_status
            ;;
    
        restart)
            $0 stop
            $0 start
            rc_status
            ;;
    
        status)
            echo -n "Checking for service Analizador Tuberia: "
            ## Check status with checkproc(8), if process is running
            ## checkproc will return with exit status 0.
            checkproc $PROGRAM
            rc_status -v
            ;;
    
        *)
            echo "Usage: $0 {start|stop|restart|status|try-restart}"
            exit 1
            ;;
    esac
    rc_exit
    
    La activación, iniciado y verificación del estado del servicio es posible mediante los siguientes comandos que se quejan un poco de la vejez del script anterior:
    testsrv:~ # systemctl enable tgaveta.service
    tgaveta.service is not a native service, redirecting to systemd-sysv-install
    Executing /usr/lib/systemd/systemd-sysv-install enable tgaveta
    testsrv:~ # systemctl start tgaveta.service
    testsrv:~ # systemctl status tgaveta.service
     tgaveta.service - LSB: Analizador Tuberia
       Loaded: loaded (/etc/init.d/tgaveta; bad; vendor preset: disabled)
       Active: active (running) since Thu 2017-04-27 21:33:59 PYT; 1s ago
         Docs: man:systemd-sysv-generator(8)
      Process: 2320 ExecStart=/etc/init.d/tgaveta start (code=exited, status=0/SUCCESS)
        Tasks: 4 (limit: 512)
       CGroup: /system.slice/tgaveta.service
               ├─2329 /bin/bash /usr/bin/tubgaveta
               ├─2333 cat /dev/puerto_gaveta
               ├─2334 grep -o --line-buffered &&ABRIRGAVETA&&
               └─2335 sed -u s/&&ABRIRGAVETA&&/\x07/g
    
    Apr 27 21:33:59 testsrv systemd[1]: Starting LSB: Analizador Tuberia...
    Apr 27 21:33:59 testsrv tgaveta[2320]: Starting service Analizador Tuberia para Apertura de Gaveta ..done
    Apr 27 21:33:59 testsrv systemd[1]: Started LSB: Analizador Tuberia.
    testsrv:~ #
    
    ¿Y eso debería ser todo no? pues no, falta lo más importante: ¿Como alimentamos al servicio tubgaveta desde el otro lado de la tubería?

  4. Redirección del flujo de texto de la aplicación remota a la tuberia:
    Redirigir todo el flujo de texto de nuestro sistema de punto de venta al servicio tubgaveta era el siguiente paso. En este punto el comando tee del cual ya hablamos mas arriba viene a ser el pilar fundamental de la solución, instalable fácilmente de la siguiente manera:
    cajero@testsrv:~> su -
    testsrv:~ # zypper in tee
    
    Teniendo el programa tee instalado, solo era necesario crear un acceso directo personalizado para el despliegue de la aplicación Konsole:
    cajero@testsrv:~> touch /home/cajero/Escritorio/posgaveta.desktop
    cajero@testsrv:~> chown cajero:users /home/cajero/Escritorio/posgaveta.desktop
    cajero@testsrv:~> chmod 755 /home/cajero/Escritorio/posgaveta.desktop
    cajero@testsrv:~> vi /home/cajero/Escritorio/posgaveta.desktop
    
    Que como se puede observar en el siguiente código, se le indique en la propiedad Exec que ejecute la aplicación Konsole, pero que a su vez ejecutará un comando sh especificado en comillas simples:
    #!/usr/bin/env xdg-open
    [Desktop Entry]
    Comment[es]=
    Comment=
    Exec=konsole -e sh -c 'ssh mi_servidor | tee -a /dev/puerto_gaveta'
    GenericName[es]=
    GenericName=
    Icon=
    MimeType=
    Name[es]=Sistema POS con Apertura Gaveta
    Name=POS con Apertura Gaveta
    Path=
    StartupNotify=true
    Terminal=false
    TerminalOptions=
    Type=Application
    X-DBUS-ServiceName=
    X-DBUS-StartupType=
    X-KDE-SubstituteUID=false
    X-KDE-Username=
    X-SuSE-translate=true
    
    El comando 'ssh mi_servidor | tee -a /dev/puerto_gaveta' se encarga de establecer una conexión ssh al servidor remoto (mi_servidor), y la salida estándar de dicha conexión que normalmente se mostraría directamente en la pantalla del usuario se vuelve a redirigir al comando tee. Tee muestra el flujo de texto de la conexión ssh en la pantalla del usuario, pero a su vez genera una copia exacta de dicho flujo que lo redirige a la tubería /dev/puerto_gaveta, donde del otro lado estará el servicio tubgaveta escuchando y filtrando todo el texto recibido en búsqueda del patrón que dispare la apertura de la gaveta.

  5. Adaptación del sistema de Punto de Venta (POS) remoto:
    La aplicación del sistema de punto de ventas fue la que menos cambios requirió, solo el despliegue ofuscado y veloz del patrón de texto que permita al equipo cliente realizar la apertura de gaveta.

  6. Prueba de funcionamiento:
    Para comprobar el funcionamiento de toda esta solución rebuscada se creó un script en el servidor remoto que desplegaba de forma imperceptible el código patrón, para obligar a la máquina cliente el disparo del comando de apertura de la gaveta. Para crear el script se siguió el siguiente procedimiento:
    cajero@testsrv:~> touch genera_patron_apertura.sh
    cajero@testsrv:~> chmod 755 genera_patron_apertura.sh
    cajero@testsrv:~> vi genera_patron_apertura.sh
    
    Script al que se le agregó el siguiente contenido:
    #!/bin/bash
    echo "Para la ejecucion"
    read
    echo "&&ABRIRGAVETA&&"
    clear
    echo "Se mostro el patron sin que el operador lo haya visto, se limpio la pantalla y se desplego este mensaje!"
    
    Para comprobar el funcionamiento, desde la máquina cliente se ejecutó el acceso directo posgaveta.desktop previamente configurado para que se conecte automáticamente al servidor remoto. Luego del logueo del usuario solo se tuvo que ejecutar el comando ./genera_patron_apertura.sh, lo que mostró el mensaje y disparó el comando de apertura de gaveta como se esperaba, sin que el operador viera en ningún momento el patrón de apertura, que en este ejemplo se definió como &&ABRIRGAVETA&&.

    Otra forma de probar fue abriendo la terminal Konsole y ejecutando de forma manual los mismos comandos que se pasan como argumentos en el acceso directo:
    cajero@testsrv:~> sh -c 'ssh gabriel@miservidor | tee -a /dev/puerto_gaveta'
    Password:
    Last login: Thu Apr 27 23:39:35 2017 from 192.168.1.50
    Have a lot of fun...
    gabriel@miservidor:~> ./genera_patron_apertura.sh
    Para la ejecucion
    ....
    Se mostro el patron sin que el operador lo haya visto, se limpio la pantalla y se desplego este mensaje!
    
    gabriel@miservidor:~>
    
    Tengo que confesar que el desempeño de toda esta solución me ha sorprendido gratamente, ha funcionado por buen tiempo hasta que finalmente se desechó luego de la implementación de un nuevo sistema gráfico de punto de ventas. Solamente tuvimos problemas con su funcionamiento cuando se paraba por error el servicio tubgaveta mientras la conexión remota estuviese establecida, o cuando la aplicación remota generaba una cantidad exagerada de flujo de texto que el servicio o el comando tee eran incapaces de procesar.
Para finalizar, si ha llegado hasta este punto querido lector y ha comprendido esta historia, o al menos la ha leído, le agradecería que deje un comentario especificando cual fue el motivo que le hizo llegar hasta aquí, si fue alguna necesidad específica tratada en el artículo o simplemente curiosidad (cosa que dudo). Estaré atento!!

Comentarios

Entradas populares