Universidad de Costa Rica Facultad de Ingeniería Escuela de Ingeniería Eléctrica IE – 0502 Proyecto Eléctrico Conteo remoto de asistentes a la Feria Vocacional de la UCR Por: Elías Gerardo Torres Arroyo Ciudad Universitaria Rodrigo Facio Julio de 2008 Conteo remoto de asistentes a la Feria Vocacional de la UCR Por: Elías Gerardo Torres Arroyo Sometido a la Escuela de Ingeniería Eléctrica de la Facultad de Ingeniería de la Universidad de Costa Rica como requisito parcial para optar por el grado de: BACHILLER EN INGENIERÍA ELÉCTRICA Aprobado por el Tribunal: _________________________________ Dr. Jorge Arturo Romero Chacón Profesor Guía _________________________________ Ing. Roberto Rodríguez Rodríguez Profesor lector _________________________________ Ing. Mauricio Ávila Duarte Profesor lector ii DEDICATORIA A mi Familia, a mi Novia y a mis buenos amigos a quienes agradezco todo el apoyo que me han dado. iii RECONOCIMIENTOS Un agradecimiento muy especial a Rodrigo García por su paciencia al explicarme el funcionamiento de su proyecto y a Patricia Ruh y todas las personas organizadoras de la Feria Vocacional que decidieron confiar en estudiantes de nuestra carrera para llevar a cabo el proyecto. iv ÍNDICE GENERAL ÍNDICE DE FIGURAS ................................................................................. vii NOMENCLATURA .....................................................................................viii RESUMEN ...................................................................................................... ix CAPÍTULO 1: Introducción........................................................................... 1 1.1 1.2 Justificación ............................................................................................................1 Objetivos.................................................................................................................2 1.2.1 Objetivo general..............................................................................................2 1.2.2 Objetivos específicos ......................................................................................2 1.3 Metodología ............................................................................................................3 CAPÍTULO 2: Desarrollo Teórico................................................................. 4 2.1 Aspectos Relacionados al Transmisor ....................................................................4 Funcionamiento del Contador de García ........................................................4 Programación del microcontrolador ...............................................................6 Protocolo RS232 .............................................................................................8 2.2 Aspectos Relacionados al Protocolo.....................................................................11 2.2.1 TCP/IP ..........................................................................................................11 2.3 Aspectos Relacionados al Receptor......................................................................14 2.3.1 Lenguaje de Programación Perl....................................................................14 2.4 Aspectos Relacionados al Software de Procesamiento de Datos .........................15 2.4.1 Base de datos MySQL ..................................................................................15 2.4.2 Servidor Web Apache...................................................................................15 2.1.1 2.1.2 2.1.3 CAPÍTULO 3: Implementación de los módulos......................................... 16 3.1 Transmisor ............................................................................................................17 3.1.1 Modificación de los datos almacenados .......................................................17 3.1.2 Transmisión de datos a un DCE ...................................................................19 3.1.3 Implementación del DCE con una PC ..........................................................20 3.2 Protocolo...............................................................................................................21 3.3 Receptor ................................................................................................................24 3.4 Software para desplegar y analizar los datos ........................................................24 CAPÍTULO 4: Conclusiones y recomendaciones....................................... 28 v BIBLIOGRAFÍA ........................................................................................... 30 APÉNDICES .................................................................................................. 32 Código Fuente ................................................................................................ 32 vi ÍNDICE DE FIGURAS Figura 2.2. Transmisión del dato 11010010 por RS232. Tomado de [3] ...........................9 Figura 2.3. Asignación de señales en un conector de 25 pines. Tomado de [6].................9 Figura 2.4. Asignación de pines para un conector de 9 pines. Tomado de [6].................10 Figura 2.5. Cableado mínimo para comunicación vía RS-232. Tomado de [6] ...............10 Figura 2.6. Modelo de capas de TCP/IP. Tomado de [9] .................................................12 Figura 2.7. Ejemplo del encapsulado realizado en cada capa de TCP/IP .........................14 Figura 3.1 Diagrama funcional del sistema de conteo, transmisión y análisis. ................16 Figura 3.2 Uso de una PC como un DCE con tcpser........................................................21 Figura 3.4 Visualización de datos de un contador en interfaz Web en modo tabla..........27 vii NOMENCLATURA ACD Convertidor Analógico Digital V Volts EEPROM Memoria solo lectura eléctricamente borrable LED Diodo Emisor de Luz PC Computadora Personal KHz Kilo Hertz RS232 Estándar Recomendado 232 DTE Equipo Terminal de datos DCE Equipo terminador de circuito de datos TX Transmisión RX Recepción IP Protocolo de Internet viii RESUMEN Este trabajo consiste en el desarrollo de un sistema que cuenta la cantidad de personas que asisten a la Feria Vocacional de la UCR con sensores ubicados diferentes puntos, y transmite esos datos de forma remota a un servidor el cual los analiza en tiempo real. El sistema utilizó el contador diseñador por Rodrigo García como práctica profesional durante el segundo semestre de 2007, este contador fue modificado para medir múltiples valores y asociarlos al tiempo en el que fueron medidos, para así transmitirlos de manera remota utilizando una computadora como interfaz entre el servidor y el contador. Se diseñó además un protocolo de comunicación para el intercambio de información entre el contador y el servidor que permita transmitir los datos relevantes de forma eficiente. Del lado del servidor se creó un conjunto de herramientas para la recepción de los datos, el almacenaje de estos en una base de datos relacional, el posterior acceso a estos mediante una interfaz web que presenta y analiza estos datos y brinda una capa de seguridad para protegerlos. El resultado final es un sistema completo de medición remota de personas que es económicamente factible para la realidad de la Universidad de Costa Rica. ix CAPÍTULO 1: Introducción Este proyecto consiste en el diseño de un sistema de conteo remoto de asistentes a la feria vocacional de la Universidad de Costa Rica, este sistema será capaz de capturar los datos de múltiples contadores instalados en diferentes puntos, transmitir estos datos a través de una plataforma de telecomunicaciones estándar, almacenarlos en una base de datos relacional, analizarlos para posteriormente desplegarlos en una computadora. El sistema de conteo está basado en el trabajo realizado por Rodrigo García, estudiante de Ingeniería Eléctrica como práctica profesional en el segundo semestre de 2007, por lo que el énfasis de este proyecto no está en el proceso de medición del paso de personas por el contador, sino en el desarrollo de un sistema que se encargue de transmitir, almacenar, procesar y analizar este dato en función de otras variables importantes como tiempo y localidad, para así poder obtener un conjunto de información valiosa para los organizadores de la feria vocacional. 1.1 Justificación La organización de eventos de asistencia masiva, tal como la feria vocacional de la Universidad de Costa Rica, requiere de un planeamiento que contemple no solo la cantidad de personas que asistirán, sino su distribución en tiempo y espacio, así se podrá optimizar factores como la distribución de personal administrativo y de seguridad, la producción de horarios eficientes de trabajo, determinación de horas pico, planeamiento de los recorridos, ubicación de las salidas de emergencia y otras previsiones de seguridad ocupacional, 1 2 colocación de material audiovisual y publicitario, medición del efecto de campañas publicitarias y otros aspectos. Por lo tanto, los beneficios del conteo de personas en la feria vocacional justifican el desarrollo de un sistema como el que se hizo en este proyecto. 1.2 Objetivos 1.2.1 • Objetivo general Obtener remotamente el número de asistentes a la Feria Vocacional de la UCR desde los diferentes recintos en que se efectúa. 1.2.2 • Objetivos específicos Estudiar y escoger el medio que se usará para transmitir los datos desde cada estación de conteo (esto define el “cliente” o emisor). • Estudiar y escoger el medio que se usará para recibir los datos desde cada estación de conteo (esto define el “servidor” o receptor). • Plantear y ejecutar el protocolo de comunicación entre cliente y servidor. • Diseñar y ejecutar el software que permitirá la visualización de los diferentes conteos. 3 1.3 Metodología Para el desarrollo de este trabajo se pretende segmentarlo en tareas más sencillas, de esta manera se puede atacar cada problema por separado y tener módulos independientes para unirlos e implementar todo el sistema de conteo. El primer paso es la recopilación de información sobre soluciones comerciales similares y estudiar las opciones en software y protocolos de comunicación estándares disponibles, esto permite tener una base para escoger las mejores tecnologías para desarrollar los distintos bloques. Como segundo paso se estudiará las opciones de intercomunicación entre los sensores y el dispositivo transmisor y se implementará en software cuando se considere necesario, así mismo con la comunicación entre el emisor y el receptor, para esto se creará software que emule los diferentes componentes para implementar y depurar la intercomunicación entre estos bloques. Seguidamente, se estudiará las técnicas de almacenamiento de datos luego de ser recibidos, así como las técnicas de análisis de estos datos, además se desarrollará el software que muestre los datos procesados a un usuario final. Cada paso de estos se ejecutarán a diferentes ritmos y serán estudiados e implementados por separado, en el caso de los dispositivos contadores, se implementará en hardware al menos dos prototipos para así poder tener un sistema de demostración final del sistema funcionando completamente. CAPÍTULO 2: Desarrollo Teórico En este capítulo se desarrollarán algunos conceptos importantes relacionados directamente con el proyecto realizado. Se dividirán las secciones en temas relacionados al transmisor, al receptor, al protocolo y al software. 2.1 Aspectos Relacionados al Transmisor El diseño del transmisor consiste en la modificación del contador realizado por García para que en conjunto con un grupo de elementos externos pueda enviar el conteo hacia el receptor utilizando el protocolo diseñado en este proyecto. En esta sección se estudiará el funcionamiento del contador de García desde un punto de vista funcional. 2.1.1 Funcionamiento del Contador de García Para realizar el conteo de personas, el dispositivo de García mide las interrupciones de una señal ultrasónica que viaja en el aire entre un dispositivo transmisor y uno receptor. Esta señal es generada por un oscilador configurado en modo astable cuya salida es una onda cuadrada de una frecuencia aproximada de 40 KHz. El receptor recibe la señal y la analiza con el fin de detectar el paso de las personas. Este análisis lo lleva a cabo un microcontrolador ATmega16, muestreando la señal del 4 5 sensor utilizando su ADC (convertidor analógico-digital1) y utilizando un algoritmo para determinar si se debe incrementar la cuenta o no. El microcontrolador registra el conteo en su memoria volátil y periódicamente escribe a la memoria EEPROM dicho valor, de tal manera el dato permanece si se apaga el dispositivo. En términos de interacción con el usuario, el contador tiene 6 entradas y 3 salidas (indicaciones), las entradas son 2 interruptores y 4 pulsadores, las salidas son 2 LEDs y un visualizador de 7 segmentos. Las entradas son las siguientes: • Encendido: Está constituido por un interruptor en la entrada del regulador de voltaje, el estado de encendido o apagado se refleja en uno de los LEDs que se enciende sólo en modo de encendido. • Interruptor de Cambio de Modo: En uno de sus estados el dispositivo está en modo de conteo, en el cual se realiza la cuenta como anteriormente se describió mientras que otras entradas de usuario son ignoradas. En el otro estado, el dispositivo entra en modo de atención de comandos: se interrumpe el conteo y se realizan ciertas funciones asociadas a las otras entradas implementadas con pulsadores. • Informe de cuenta: Despliega el valor actual de la cuenta en un visualizador de 7 segmentos. 1 Del inglés Digial-Analog Converter. 6 • Guardar cuenta: Almacena en memoria EEPROM la cuenta actual. • Eliminar cuenta: Elimina la cuenta de la memoria EEPROM. • Calibración: Determina si el ángulo y distancia entre el transmisor y el receptor de la señal ultrasónica son adecuados para un correcto conteo, cuando esto es cierto, uno de los LEDs parpadea indicando que se puede tener un conteo confiable. Cada vez que se cambie la posición del receptor o el emisor se debe volver a calibrar el dispositivo. El microcontrolador ATmega16 tiene 3 contadores internos, de los cuales sólo 2 son utilizados para llevar a cabo la lógica anterior y cuenta con un dispositivo para comunicación serial USART, dicho contador extra y la USART son clave más adelante en este proyecto. 2.1.2 Programación del microcontrolador Es posible programar el microcontrolador ATmega16 utilizando ya sea el lenguaje de programación C o lenguaje ensamblador. El contador de García fue programado en C utilizando un conjunto de herramientas de software libre diseñadas para dicha tarea, entre ellas: • libc-avr Librería de C para procesadores ATMEL de 8 bits, implementa el estándar ANSI X3.159-1989 e ISO/IEC 9899:1990 ("ANSI-C"), también parte de ISO/IEC 9899:1999 ("C99") y algunos agregados basados en el estándar IEEE Std 1003.1-1988 ("POSIX.1"). Su licencia es tipo BSD, la cual es una licencia de software libre permisiva. 7 • gcc-avr Este es el compilador de C de GNU llamado GCC, el cual está configurado como compilador cruzado para poder generar binarios para la plataforma AVR, de tal forma que desde una PC se puede compilar el código para el ATmega16. • uisp Uisp es una herramienta para la programación del microcontrolador utilizando el puerto paralelo o serial de una PC. El contador de García es programado utilizando el puerto paralelo utilizando los pines que el microcontrolador asigna para esta tarea. Uisp es una herramienta de línea de comandos y se puede destacar como acciones comunes: o Borrar los contenidos del dispositivo: $ uisp -dprog=dapa --erase Atmel AVR ATmega16 is found. Erasing device ... Reinitializing device Atmel AVR ATmega16 is found. o Grabar los contenidos de un programa al dispositivo $ uisp -dprog=dapa --upload if=main.hex --verify Atmel AVR ATmega16 is found. Uploading: flash Verifying: flash 8 2.1.3 Protocolo RS232 Para la adaptación del contador de García es importante la selección de un método de transmisión de datos que pueda implementar el microcontrolador y además que sea factible en términos de la disponibilidad de recursos en la Universidad de Costa Rica. El microcontrolador ATmega16 tiene un dispositivo USART, el cual es ideal para operar con otros dispositivos externos de manera estándar y sencilla. El dispositivo USART del microcontrolador está diseñado para transmitir vía RS232, en diferentes modos. El protocolo RS232 (también conocido como Electronic Industries Alliance RS232C, o Recommended Standard 232) es un protocolo de comunicación diseñado para la comunicación de datos en serie entre un equipo DTE (Data Terminal Equipment) y un DCE (Data Circuit-terminating Equipment), este último es el que se encarga de comunicar un dispositivo (el DTE) con un circuito de transmisión de datos. Para representar un cero lógico se utiliza un voltaje entre +5 y +15 V si se trata de un cero que se está enviando. El cero lógico del receptor está representado por un voltaje entre los +3 y los +15 voltios. Un uno lógico es representado por un voltaje entre -5 y -15 V para el emisor, o -3 y -15 V para el receptor. A este ámbito de voltaje se le denomina la “marca”. Dado que se trata de una comunicación serial, los bits son transmitidos uno a uno por las líneas de datos, tal y como se muestra en la siguiente figura. 9 Figura 2.2. Transmisión del dato 11010010 por RS232. Tomado de [3] El estándar RS232 establece un conector de 25 pines para la interconexión de dispositivos a través de este protocolo. Figura 2.3. Asignación de señales en un conector de 25 pines. Tomado de [6]. 10 Sin embargo es común el uso de conectores de 9 pines que tienen un uso más común en aplicaciones comerciales, como en las PC actuales. Figura 2.4. Asignación de pines para un conector de 9 pines. Tomado de [6]. Muchas de las señales del estándar RS-232 son utilizadas para la negociación de la comunicación entre los dispositivos terminales, para la transmisión de datos es indispensable solo el uso de tres pines: RX: Recepción de datos TX: Transmisión de datos Ground: Tierra. Esto quiere decir que se puede utilizar un cableado mínimo entre los dispositivos terminales, a este tipo de cableado se le conoce como NULL-MODEM sin negociación2. Figura 2.5. Cableado mínimo para comunicación vía RS-232. Tomado de [6] 2 Negociación se utiliza en este proyecto para traducir la palabra inglesa handshaking: que literalmente significa apretón de mandos. 11 2.2 Aspectos Relacionados al Protocolo En esta sección se estudian algunos conceptos relacionados al protocolo de comunicación. 2.2.1 TCP/IP TCP/IP es un conjunto de protocolo de red utilizado para la intercomunicación entre computadoras. Estos protocolos son la base de Internet y su diseño está orientado a interconectar computadoras de diversas plataformas y sistemas operativos. La familia de protocolos de TCP/IP están organizados en capas, a saber: Aplicación, Transporte, Internet, Enlace y Capa física. Nivel Físico: Este describe las características físicas asociadas al transporte de datos dependiendo de los elementos de hardware utilizados, por ejemplo cable de cobre, ondas de radio, fibra óptica, etc., así como los niveles de voltaje, el diseño de los conectores, la modulación de datos, características físicas de las ondas electromagnéticas u ópticas a utilizar, etc. 12 Figura 2.6. Modelo de capas de TCP/IP. Tomado de [9] Nivel de Enlace de Datos: Este nivel establece las técnicas de cómo los paquetes serán transportados sobre el medio físico, por ejemplo se encarga de asignar encabezados u otros delimitadores dependiendo del medio físico. Nivel de Red: En esta capa se resuelve el problema de las rutas entre las diferentes computadoras y el control sobre los esquemas de comunicación entre las mismas. Nivel de Transporte: Este se encarga de resolver problemas tales como el orden y la fiabilidad del flujo de datos entre las computadoras, además en esta capa se decide a cuál aplicación se dirigen los paquetes. Acá residen los protocolos TCP y UDP los cuales se encargan de resolver los problemas supra mencionados. TCP es protocolo orientado a la conexión, es decir que antes de transportar datos primero negocia los términos en los cuales éste transporte se dará. Este protocolo se 13 encarga de entregar los paquetes en orden y sin que falte ninguno, en caso de haber alguna pérdida habrá retransmisión. Además, TCP realiza medidas constantemente para determinar si se debe variar parámetros como velocidades de transmisión, con el fin de no saturar las redes. Este es un protocolo de uso extensivo en aplicaciones donde la fiabilidad de los datos transmitidos es imperativa como en transferencias de archivos. UDP es otro protocolo de esta capa, que difiere significativamente con TCP puesto que: No es orientado a la conexión, es decir no se realiza una negociación previa al trasiego de datos; la fiabilidad no es garantizada, es decir puede existir pérdida o duplicidad de paquetes, así como paquetes fuera de orden, este tipo de protocolo es ideal para aplicaciones donde la transmisión en tiempo real lo requiera, tales como aplicaciones multimedia, voz sobre Internet, etcétera. Capa de Aplicación: Es esta capa la que las aplicaciones utilizan con el fin de comunicarse con algún otro proceso en otra aplicación en una máquina remota, en particular, acá se encuentran las aplicaciones o programas que se encargan de traducir los datos de una aplicación de alto nivel para ser utilizados por los estándares comunes en Internet. En cada nivel a los datos originales (conocido como carga útil3) se les agrega un trozo extra de información para el control interno de esa capa, tal y como se muestra en la siguiente figura: 3 O payload en inglés. 14 Figura 2.7. Ejemplo del encapsulado realizado en cada capa de TCP/IP 2.3 Aspectos Relacionados al Receptor 2.3.1 Lenguaje de Programación Perl Perl es un lenguaje dinámico creado por Larry Wall en 1987, es un lenguaje interpretado que toma muchas características de C (lenguaje en que está escrito) y de muchos otros lenguajes de programación como el lenguaje interpretado shell (sh), AWK, sed, Lisp y en menor proporción, otros. Es un lenguaje multiplataforma, y de propósito general, es decir, es posible realizar programas de diversos fines, ya sea que utilicen línea de comandos o Interfaz Gráfica de Usuario (GUI), programas de acceso a red, tanto servidores de Internet como clientes, y muchos otros ambientes. 15 Uno de sus fuertes particulares es que tiene un motor de expresiones regulares muy potente, el cual lo hace un excelente lenguaje para procesar flujos de texto, provenientes de múltiples fuentes, ya sea archivos de texto, bases de datos, red, etc. 2.4 Aspectos Relacionados al Software de Procesamiento de Datos 2.4.1 Base de datos MySQL MySQL es una base de datos relacional que implementa el estándar SQL, esto quiere decir, que utiliza para el almacenamiento de información un paradigma de datos tabulares, en contraste a otros paradigmas de bases de datos como árboles jerárquicos. MySQL es un software libre licenciado por la empresa Sun Microsystems bajo un modelo de licencias: la GPL (Licencia Pública General del proyecto GNU) y la venta de licencia para aplicaciones de software no-libre. 2.4.2 Servidor Web Apache Un servidor Web es un software que opera en la capa de aplicación para proveer texto y otros tipos de archivos a clientes utilizando el protocolo HTTP a través de TCP. El servidor Web Apache es una implementación de software libre de un servidor Web que se apega a los estándares definidos por el W3C (World Wide Web Consortium). HTTP es un protocolo de texto que tiene un modelo tipo pregunta-respuesta, en donde el cliente envía comandos definidos por un estándar y el servidor responderá de acuerdo a lo especificado definido por un estricto conjunto de reglas. 16 Aunque HTTP puede utilizarse para trasegar texto y otros datos estáticos, se puede utilizar un estándar llamado CGI (Common Gateway Interface), el cual permite trasegar información de forma bidireccional entre programas que corren del lado del servidor. Con el estándar CGI, es posible crear páginas Web dinámicas que acceden bases de datos y otros tipos de informaciones que por su naturaleza requieren de ejecución de un software que genere, accede o prepare dicha información para el cliente. CAPÍTULO 3: Implementación de los módulos En este capítulo se trata la implementación de cada uno de los módulos, es decir del transmisor, receptor, protocolo y software analizador. El diagrama general del sistema es el siguiente: Figura 3.1 Diagrama funcional del sistema de conteo, transmisión y análisis. 17 3.1 Transmisor Como ya se ha indicado anteriormente en este proyecto, el transmisor consiste principalmente en la modificación del contador de García. Las modificaciones más relevantes fueron adición de información temporal al conteo y la comunicación serial con un DCE que se encargue de transmitir los datos por Internet. 3.1.1 Modificación de los datos almacenados El diseño del contador de García se enfoca en mantener un valor total, del cual de almacena una copia en memoria no volátil para proteger este dato de una pérdida accidental. Sin embargo carece de información temporal. Muchas de las mejoras a este contador se hicieron utilizando una librería adicional llamada Procyon AVRlib4, ésta es una librería de funciones que extienden la funcionalidad de libc-avr. Provee funciones para acceso a dispositivos periféricos, estructuras de datos en memoria, protocolos de red, funciones extra de entrada y salida, entre otros. Lo primero que se realizó fue la adición de un registro de tiempo, esto puede realizarse de múltiples maneras, la escogida fue el uso del contador de 8 bits COUNTER2 del microcontrolador ATmega16, el cual tiene un prescaler independiente a los otros contadores y activa interrupciones en rebase y en comparación de un valor específico, por 4 De ahora en adelante denominada también en este documento avrlib. 18 lo que se utilizó este para incrementar cada segundo una variable llamada segundosArriba5, la cual guarda la cantidad de segundos en operación del dispositivo. Ahora ya con las dos variables en funcionamiento se procedió a incluir un registro temporal, para esto se implementó una lista6 circular en memoria de tamaño estático, así cada vez que se registra el paso de una persona, no sólo se incrementa el valor total sino que se guarda en la lista tanto el conteo de personas como el tiempo. La programación de esta lista circular se hizo modificando la librería buffer.c, parte de avrlib, dicha librería implementa una lista circular de caracteres en memoria, es decir de datos de 8 bits, lo cual no es capaz de almacenar el tiempo y la cuenta ya que ambos valores son variables de 32 bits. Por esta razón se adaptó dicha librería para crear una nueva con nombre diferente, de tal forma que se pudiera almacenar un par de variables de 32 bits cada vez usando una estructura de datos de C: /* Definicion de Estructura de Datos */ typedef struct struct_sCuenta { volatile uint32_t time; volatile uint32_t count; } sCuenta; 5 Inspirado en el término inglés uptime comúnmente utilizado para designar el tiempo de encendido o tiempo en servicio. 6 O buffer en inglés. 19 3.1.2 Transmisión de datos a un DCE Para esto se utilizó el protocolo RS-232, el cual el contador de García solo lo utilizaba para depuración. En este caso la librería avrlib tiene un conjunto de funciones que facilitaron la adaptación del software para que pudiera comunicarse de manera bidireccional con el DCE. Selección de un DCE: Este proyecto no limita el diseño a la selección de un DCE en particular, simplemente lo que se requiere es que éste pueda utilizar un lenguaje de comandos7 para enviar mensajes a una máquina en Internet utilizando el protocolo TCP, de esta forma comunicarse con el programa receptor. Ejemplos de estos DCE que utilizan este tipo de comunicación son: • Módems GPRS - RS232 con pila TCP/IP: Esta es una opción muy versátil que ofrece el mercado desde un punto de vista técnico, permite que el sistema contador pueda extender su área de cobertura a los lugares que tengan acceso a GPRS, una excelente movilidad y bajo consumo de espacio y energía; sin embargo no es una opción práctica para implementarla en la Universidad de Costa Rica, ya que el costo de los equipos es elevado (considerando que se utilizaría una vez al año) y que es complicado obtener dispositivos SIM para el funcionamiento de los módems en nuestro país. 7 Comúnmente, los comandos Hayes/AT con los que funcionan los módems. 20 • Convertidores RS-232 a IEEE 802.11: Conocido comercialmente como Wi-Fi, ésta es una opción atractiva por la cobertura de IEEE 802.11 que ofrece un ambiente como la Universidad de Costa Rica, sin embargo aunque no se requiere de un servicio externo (como GPRS en el caso anterior) se tiene los mismos inconvenientes con respecto a la relación costo-beneficio que se obtiene de este tipo de dispositivo. • Una PC con un cable RS-232: Esta opción reúne ciertas ventajas muy atractivas, puesto que las computadoras son un recurso muy accesible para la Universidad, el uso de pocos días permite que se pueda aprovechar equipo que ya pertenezca al ente organizador de la Feria Vocacional, y los cables RS-232 son económicos. La desventaja principal es que una PC tiene requerimiento de alimentación y de espacio mucho más demandantes que las opciones anteriores, además de los riesgos de seguridad asociados a tener computadoras en actividades de asistencia masiva, sin embargo estas desventajas, con buena organización, son completamente manejables para el personal de la Feria Vocacional. 3.1.3 Implementación del DCE con una PC Para la realización del DCE se utilizó un software llamado tcpser, el cual convierte cualquier puerto serial en un módem virtual e interconecta dicha conexión RS-232 con un puerto TCP. 21 Figura 3.2 Uso de una PC como un DCE con tcpser. La interfaz de este programa es línea de comandos, en este proyecto los argumentos con los que se ejecutó este programa se muestran a continuación: tcpser -s 9600 -d /dev/ttyS0 -l 1 -t sS Donde 9600 es la velocidad de transmisión de datos por el puerto serial, /dev/ttyS0 es el nombre del puerto y el resto de parámetros son para depuración. 3.2 Protocolo El protocolo de comunicación fue diseñado con dos objetivos en mente: Fácil implementación en un microcontrolador y capacidad para extenderlo a otros tipos de dispositivos. 22 El diseño está basado en recomendaciones de los RFC (Request for comments), en especial, el conocido como Principio de Robustez, el cual dicta: “TCP implementations will follow a general principle of robustness: be conservative in what you do, be liberal in what you accept from others.” [7] Que en español sería: “Implementaciones de TCP seguirán un principio general de robustez: Sea conservativo con lo que hace, sea liberal con lo que acepta de otros.” Los detalles del protocolo están inspirados en el estándar STMP definido por el RFC 821 [7], la comunicación entre el cliente y el servidor (una vez establecida la conexión) utiliza comandos de texto en un modo comando-respuesta. El servidor acepta los siguientes comandos: HELO: Este comando se utiliza para identificar el contador con un numero unico asociado. Sin la ejecución de este, solo los comandos HELP y QUIT funcionarán adecuadamente. HELP: Imprime la lista de comandos disponibles. STATUS: Pregunta al servidor si todo está en orden, el servidor responde con un número identificador de la conexión o simplemente con un OK. COUNT: Con este comando se indica al servidor que se tiene un conteo registrado, se debe pasar como parámetros la cantidad de la cuenta y la cantidad de segundos de antigüedad del dato, por ejemplo: “COUNT 567, TIMEAGO 23” esto indica que se hace 23 segundos se midieron 567 personas. Este comando puede repetirse en una misma sesión con el fin de enviar múltiples datos almacenados en masa. 23 NOOP: Comando No-Operativo que siempre retorna OK, sirve para mantener viva una conexión si así el cliente lo considera necesario. QUIT: Terminación de la sesión. A cada comando de los anteriores, el servidor tendrá una respuesta que consta de 3 dígitos, un espacio en blanco y un texto opcional. Para la versión actual del protocolo (que no tiene requerimientos especiales por ahora) se utiliza el primer dígito en 2 para indicar éxito del comando anterior y 5 para fallo. 24 3.3 Receptor El receptor desarrollado en este proyecto es está elaborado en el lenguaje de programación Perl, este consiste en un programa que abre un puerto TCP en modo escucha y que utiliza el método conocido como forking, para que cada vez que llega una nueva conexión, este proceso crea un programa “hijo” el cual es una copia de sí mismo corriendo en exactamente el mismo punto, se diferencia el proceso “padre” (el inicial) del “hijo” (la copia recién creada) según el valor de retorno de la función fork(). # Codigo para realizar fork # perform the fork or exit die "Can't fork: $!" unless defined ($child = fork()); if ($child == 0) { #Codigo si se es el padre } else { #Codigo si se es un hijo } Este receptor se encarga de almacenar los datos recibidos (número de unidad, conteo y tiempo) en una base de datos de manera sencilla utilizando la librería Class::DBI. 3.4 Software para desplegar y analizar los datos El lenguaje de programación Perl, permite la creación de programas de interfaz web de manera sencilla utilizando la librería CGI y CGI::Session (este último para la autentificación del lado del servidor). 25 El programa fue creado de manera modular, separando todas las posibles acciones en diferentes módulos o por lo menos en diferentes funciones internamente en el código. Por ejemplo, con Class::DBI se creó un módulo que ofrece una interfaz de acceso a base de datos compartida para los diferentes elementos del sistema (por ejemplo, es utilizado tanto en el receptor como en el visor de datos). Para la creación de la parte visual de los CGI, se utilizó el módulo Template, para de esta forma separar la presentación de la lógica del programa de la parte visual, de esta forma, si se desea cambiar el aspecto a la interfaz web, no se deberá alterar código fuente del programa. Template permite crear plantillas parametrizadas, lo que significa que es posible escribir un trozo de texto con el formato deseado y colocarle ciertas palabras que cumplen funciones especiales, por ejemplo: <table> <tr> <th>ID</th> <th>Ubicaci&oacute;n</th> <th>Descripci&oacute;n</th> <th>Datos</th> </tr> [% FOREACH unit = units %] <tr> <td>[% unit.unit_id %]</td> <td>[% unit.location %]</td> <td>[% unit.description %]</td> <td> <a href="[% script %]?a=get_data_from_unit&amp;unit_id=[% unit.unit_id %]">[Tabla]</a> <a href="[% script %]?a=get_data_from_unit&amp;unit_id=[% unit.unit_id %]&amp;fmt=csv">[CSV]</a> <a href="[% script %]?a=get_data_from_unit&amp;unit_id=[% unit.unit_id %]&amp;fmt=graph">[GRAPH]</a> </td> </tr> [% END %] </table> 26 El texto anterior es un ejemplo de una plantilla, la cual es un trozo de código HTML con directivas interpretadas por Template, las cuales generan una tabla con los datos del arreglo “units”. Para graficar los datos, se utilizó la librería Open Flash Chart, la cual es alimentada con texto XML generado a partir de estructuras de datos provenientes de la base de datos. Por ser interfaz web, no se requiere tener ningún programa instalado en las computadoras que deseen accederlo, además de un navegador de Internet moderno. Figura 3.3 Interfaz web con inicio de sesión con usuario y contraseña. El sistema cuenta con autentificación de usuarios, separación básica de roles (administrador vs. no-administrador), soporte para visualización de gráficas y administración de las unidades contadoras y usuarios. 27 Figura 3.4 Visualización de datos de un contador en interfaz Web en modo tabla. El almacenamiento en la base de datos de las contraseñas de los usuarios se realiza de manera segura cifrándolas con el algoritmo MD5, utilizando la técnica llamada conocida como salting, lo que permite que las claves sean muy difíciles de obtener aún con acceso a la base de datos. Este tipo de esquema de seguridad es utilizado por sistemas tipo UNIX modernos, lo cual demuestra que es un sistema bastante confiable. CAPÍTULO 4: Conclusiones y recomendaciones El desarrollo de un proyecto como el expuesto en este trabajo brinda una gran experiencia, no solo por el conjunto de elementos técnicos de cada tema involucrado por separado, sino más bien por el esfuerzo por integrar tecnologías diversas y que aparentemente no tienen relación entre sí. Además el analizar el proyecto desde un punto de vista práctico en términos de los recursos con los que cuenta la Universidad para implementarlos arroja definitivamente a la luz indicaciones de modificaciones que se deben considerar para la ejecución del proyecto. Por tanto, de este trabajo se desprenden las siguientes conclusiones: Los microcontroladores son una herramienta muy poderosa en la elaboración de soluciones a la medida, su accesible documentación, la versatilidad de las herramientas de desarrollo, y su bajo costo hacen de ellos una excelente herramienta de trabajo y de desarrollo académico. Aunque data de la década de los años 1960, el protocolo RS-232 tiene una altísima importancia en el desarrollo de proyectos de bajo presupuesto y altos requerimientos de compatibilidad, es fácil encontrar dispositivos que utilicen de una u otra manera este protocolo. Un convertidor de RS-232 a un protocolo de red como tcpser es la mejor opción para cubrir los requerimientos de la Feria Vocacional. 28 29 El esquema de comunicación utilizado por el protocolo SMTP, el cual inspiró el diseño utilizado en este proyecto, ofrece una alta claridad y simplicidad para implementarlo en plataformas tan distintas como un microcontrolador y una PC. El lenguaje de programación Perl ofrece características que agilizan el desarrollo de elementos complicados como el acceso a base de datos, creación de interfaces vía Web o el receptor de datos. La Universidad de Costa Rica puede utilizar el dar respuesta a algunas de sus necesidades que requieren desarrollo de tecnología de manera interna con proyectos de graduación como este. Como recomendaciones se tienen las siguientes: Estudiar otros tipos de medidores de personas y la factibilidad de integrarlo a este sistema de transmisión, ya sea por análisis de imágenes, múltiples sensores, etcétera. Estudiar la posibilidad de utilizar contadores en las salidas de los edificios y adaptar el sistema para procesar dichos datos para brindar una información mucho más compleja. Adaptar el contador para que tenga entradas que marquen ciertas cuentas como especiales, por ejemplo, se puede implementar un botón que al presionarlo marque la siguiente cuenta como “personal administrativo”, dando así una información más precisa. Evaluar las necesidades por parte del ente que organiza la Feria Vocacional en función de los datos generados por el sistema y así evaluar las posibles mejoras en todas las etapas del sistema. BIBLIOGRAFÍA Libros: 1. Tanenbaum, A. “Redes de computadoras”, 2 edición, traducción David Morales Peake, Prentice Hall Hispanoamericana, México, 1997. 2. Kernighan, B. y Ritchie D. “The C Programming Language”, 2 edición, Prentice Hall, 1988, Estados Unidos. Páginas web: 3. Strangio, C. “The RS232 Standard”. http://www.camiresearch.com/Data_Com_Basics/RS232_standard.htm 4. Wikipedia contributors, "Serial port," Wikipedia, The Free Encyclopedia, http://en.wikipedia.org/w/index.php?title=Serial_port&oldid=221978534 5. Colaboradores de Wikipedia, "8N1," Wikipedia, La enciclopedia libre, http://es.wikipedia.org/w/index.php?title=8N1&oldid=12922938 6. Lammert Bies, “RS232 serial cable pinout information” http://www.lammertbies.nl/comm/cable/RS-232.html#pins 7. Postel, J. “RFC 793: Transmission Control Protocol”. http://tools.ietf.org/html/rfc793#page-13 8. Wikipedia contributors, "Fork (software development)," Wikipedia, The Free Encyclopedia, http://en.wikipedia.org/w/index.php?title=Fork_%28software_development%29&ol did=215710137 30 31 9. Wikipedia contributors, "TCP/IP model," Wikipedia, The Free Encyclopedia, http://en.wikipedia.org/w/index.php?title=TCP/IP_model&oldid=222419714 10. Wikipedia contributors, "Hayes command set," Wikipedia, The Free Encyclopedia, http://en.wikipedia.org/w/index.php?title=Hayes_command_set&oldid=223256316 11. W3C Consortium, “CGI - Common Gateway Interface”, http://www.w3.org/CGI/ 12. Postel, J. “RFC 821: http://tools.ietf.org/html/rfc821 SIMPLE MAIL TRANSFER PROTOCOL”, 32 APÉNDICES Código Fuente A continuación se presenta el código fuente principal utilizado en el desarrollo de este proyecto. Código C para la programación del microcontrolador: Archivo main.c #include #include #include #include <avr/io.h> <avr/interrupt.h> <avr/eeprom.h> <string.h> #include "global.h" #include "global_ucr.h" #include "avrlib/uart.h" #include "avrlib/rprintf.h" #include "countbuffer.h" // Constantes #define BOTON_REESTABLECER 0x08 #define BOTON_GUARDAR 0x10 #define BOTON_INFORMAR 0x20 #define BOTON_ESCANEAR 0x40 #define SWITCH_CONTEO 0x80 #define DIR_EEPROM_PB 0x0000 #define DIR_EEPROM_PA 0x0002 #define TIEMPO_EEPROM 36 #define TIEMPO_PARPADEO 5 #define TIEMPO_RETARDO 10 #define TIEMPO_ESCANEO 1 #define TIEMPO_ESPERA 10 #define TIEMPO_DESPLIEGUE 1 #define NUMERO_DESPLIEGUE 1000 #define CTE_DISPARO 20 // Numeros display 7 segmentos #define NUM_CERO 0xD7 #define NUM_UNO 0x14 #define NUM_DOS 0x5B #define NUM_TRES 0x5E #define NUM_CUATRO 0x9C #define NUM_CINCO 0xCE #define NUM_SEIS 0xCF #define NUM_SIETE 0x54 #define NUM_OCHO 0xDF #define NUM_NUEVE 0xDE // tamanno del buffer para las cuentas #define COUNT_BUFFER_SIZE 31 #define TIMER2_LOOPS 125 #define MODEM_TIMEOUT 1 33 #define DIAL_TIMEOUT 3 #define TCPIP_TIMEOUT 1 #define CR "\015" #define LF "\012" #define UNIT_ID "1" // Variables globales volatile u08 capturaCompleta, entradaUsuario, desbordeTimer0, conteoEEPROM, contandoTiempo, i; volatile u16 datoMuestreado, valorMaximo, conteoDespliegue, umbralDisparo; volatile uint32_t conteoTotal; sCuenta cuentaData[COUNT_BUFFER_SIZE]; volatile sCuenta cuentaTemporal; countcBuffer cuentaBuffer; // uptime volatile uint32_t segundosArriba=0; // 125 comparaciones contra 250 hace ~ 1 segundo volatile u08 conteoSegundos=TIMER2_LOOPS; // estado: 0 comandos, 1 transmitiendo. volatile u08 estado_modem=0; u08 size; u08 answer[UART_RX_BUFFER_SIZE+1]; // strings const u08 modemInitString[] = "ATE0"; const u08 modemDialString[] = "ATDTlocalhost:8000"; // Prototipos de funciones void enc_timer0(void); void enc_timer0_1e6(void); void apg_timer0(void); void escribirEEPROM(void); void leerEEPROM(void); void desplegarDato(uint32_t dato); u08 identificarDigito(u08 digito); u08 sendCommand(u08* command, u08* answer, u08 timeout); // Vectores de interrupcion SIGNAL(SIG_ADC) { ADCSRA|=0x10; // Conversion de dato muestreado a 32 bits u16 parteAlta,parteBaja; parteAlta=ADCH; 34 parteAlta=(parteAlta<<2) & 0x03FC; parteBaja=ADCL; parteBaja=(parteBaja>>6) & 0x0003; datoMuestreado=parteAlta+parteBaja; // Complementacion a dos para valores negativos if(ADCH & 0x80) { datoMuestreado|=0xFC00; datoMuestreado=~datoMuestreado; } capturaCompleta=TRUE; } SIGNAL(SIG_OVERFLOW0) { TIFR|=0x01; if(desbordeTimer0) desbordeTimer0--; } SIGNAL(SIG_OVERFLOW1) { TIFR|=0x04; if(conteoEEPROM) conteoEEPROM--; else { conteoEEPROM=TIEMPO_EEPROM; escribirEEPROM(); } } // Segundero, uptime SIGNAL(SIG_OUTPUT_COMPARE2) { TIFR|=0x80; if(conteoSegundos > 0) conteoSegundos--; else { conteoSegundos=TIMER2_LOOPS; segundosArriba++; } } int main(void) { uint32_t tLastTX=1; // Inicializacion de registros DDRA=0x04; // Puertos de lectura de ADC, LED de indicacion y botones de usuario DDRB=0xFF; // Bus de datos displays 7 segmentos DDRD=0x0C; // Seleccion de displays 7 segmentos ADMUX=0xE9; // ADC diferencial, canales ADC1(+) y ADC0(-), ganancia 10, referencia 2.56 V PORTA=0x00; PORTB=0x00; PORTD=0x00; // Inicializacion de variables entradaUsuario=0; contandoTiempo=FALSE; conteoEEPROM=TIEMPO_EEPROM; leerEEPROM(); if (conteoTotal < 0) conteoTotal=0; 35 // inicializar usart uartInit(); uartSetBaudRate(9600); // set UART speed to 9600 baud rprintfInit(uartSendByte); // configure rprintf to use UART for output //uartFlushReceiveBuffer(); countbufferInit(&cuentaBuffer,cuentaData,COUNT_BUFFER_SIZE); // Redirigir datos recibidos en uart a una funcion // uartSetRxHandler(recibirUART); // Habilitacion de interrupciones sei(); // Inicializacion de timer1 TIFR=0xFF; TIMSK=0x04; TCCR1B=0x05; // inicializacion TCCR2=0x2E; // OCR2=0xFA; // TIMSK|=0x80; // TIFR|=0x80; de timer2 presc = 256, no OC2, modo CTC comparar contra 250 habilitar interrupcion solo contra compare match // Inicializacion de primer captura del ADC capturaCompleta=FALSE; ADCSRA=0xD8; while(!capturaCompleta); while(TRUE) { entradaUsuario=PINA & 0xF8; if(entradaUsuario==SWITCH_CONTEO) { // CAPTURAR! capturar(); } else { apg_timer0(); contandoTiempo=FALSE; switch(entradaUsuario) { case BOTON_REESTABLECER: conteoTotal=0; escribirEEPROM(); // Parpadeo de LED for(i=0;i<3;i++) { PORTA|=0x04; desbordeTimer0=TIEMPO_PARPADEO; enc_timer0(); while(desbordeTimer0); apg_timer0(); PORTA&=0xFB; desbordeTimer0=TIEMPO_PARPADEO; enc_timer0(); while(desbordeTimer0); apg_timer0(); } break; case BOTON_GUARDAR: 36 // // // // // // Si se quiere utilizar el boton de guardar para aumentar la cuenta cuentaTemporal.time = segundosArriba; cuentaTemporal.count = ++conteoTotal; countbufferAddToEnd(&cuentaBuffer, cuentaTemporal ); desplegarDato(conteoTotal); escribirEEPROM(); // // Parpadeo de LED for(i=0;i<1;i++) { // PORTA|=0x04; desbordeTimer0=TIEMPO_PARPADEO; enc_timer0(); while(desbordeTimer0); apg_timer0(); PORTA&=0xFB; desbordeTimer0=TIEMPO_PARPADEO; enc_timer0(); while(desbordeTimer0); apg_timer0(); } break; case BOTON_INFORMAR: desplegarDato(conteoTotal); desbordeTimer0=TIEMPO_RETARDO; enc_timer0(); while(desbordeTimer0); apg_timer0(); break; case BOTON_ESCANEAR: datoMuestreado=0; valorMaximo=0; desbordeTimer0=TIEMPO_ESCANEO; enc_timer0(); // Determinacion de valor maximo while(desbordeTimer0) { capturaCompleta=FALSE; ADCSRA=0xD8; while(!capturaCompleta); if(!valorMaximo) valorMaximo=datoMuestreado; else { if(datoMuestreado>=valorMaximo) valorMaximo=datoMuestreado; } } apg_timer0(); // Determinacion de alineacion de sensores umbralDisparo=valorMaximo/2 + valorMaximo/4; if(umbralDisparo>=CTE_DISPARO) { // Parpadeo de LED for(i=0;i<4;i++) { PORTA|=0x04; desbordeTimer0=TIEMPO_PARPADEO; 37 enc_timer0(); while(desbordeTimer0); apg_timer0(); PORTA&=0xFB; desbordeTimer0=TIEMPO_PARPADEO; enc_timer0(); while(desbordeTimer0); apg_timer0(); } } else { desbordeTimer0=TIEMPO_RETARDO; enc_timer0(); while(desbordeTimer0); apg_timer0(); } break; default: break; } // end case } // end if(entradaUsuario... // // Periodicamente Conectarse al servidor // y enviar los datos que se tienen // // Cada 10 segundos... if ( (segundosArriba - tLastTX) % 10 == 0 ) { while( 1 ) { cuentaTemporal = countbufferGetFromFront(&cuentaBuffer); if (cuentaBuffer.datalength == 0) break; answer[0] = 0; //estado_modem = 0; // inicializar el modem sendCommand(modemInitString, NULL, MODEM_TIMEOUT); // revisar si modem esta listo sendCommand("AT", answer, MODEM_TIMEOUT); if ( strncmp(answer,CR LF "OK" CR LF,6) != 0 ) break; // marcar a host remoto sendCommand(modemDialString, answer, DIAL_TIMEOUT); if ( strncmp(answer,CR LF "CONNECT",9) != 0 ) break; // estamos conectados enviar saludo con numero de unidad sendCommand("HELO " UNIT_ID, answer, TCPIP_TIMEOUT); if ( strncmp(answer,"200 Hello " UNIT_ID ,8) != 0 ) break; // nos reconocieron, ahora a enviar la cuenta while( cuentaBuffer.datalength > 0 ) { rprintf("COUNT %d,", cuentaTemporal.count ); rprintf(" TIMEAGO %d", segundosArriba cuentaTemporal.time ); rprintf("\n"); sendCommand(NULL, answer, TCPIP_TIMEOUT); if ( strncmp(answer,"200 ",4) != 0 ) break; cuentaTemporal = countbufferGetFromFront(&cuentaBuffer); } // end while 38 // Ciao! tLastTX = segundosArriba; rprintf("QUIT\n"); /// XXX esperar un segundo? sendCommand(NULL, NULL, 1); sendCommand("+++", answer, MODEM_TIMEOUT); if ( strncmp(answer,CR LF "OK" CR LF,6) != 0 ) break; sendCommand("ATH", answer, TCPIP_TIMEOUT); if ( strncmp(answer,CR LF "OK",4) != 0 ) break; } // end while( } // end if(segundosArriba % .... //else { desplegarDato(segundosArriba); } } } // end while(TRUE) // end main // Funciones void enc_timer0(void) { TIFR|=0x01; TIMSK|=0x01; TCCR0=0x05; } void enc_timer0_1e6(void) { TIFR|=0x01; TIMSK|=0x01; TCCR0=0x02; } void apg_timer0(void) { TCCR0=0x00; TIMSK&=0xFE; } void escribirEEPROM(void) { uint16_t parteBaja,parteAlta; uint32_t conversor; // Conversion de conteo registrado a dos valores de 16 bits conversor=conteoTotal & 0x0000FFFF; parteBaja=conversor; conversor=(conteoTotal>>16) & 0x0000FFFF; parteAlta=conversor; eeprom_write_word(DIR_EEPROM_PB,parteBaja); eeprom_write_word(DIR_EEPROM_PA,parteAlta); } void leerEEPROM(void) { uint32_t parteBaja,parteAlta; parteBaja=eeprom_read_word(DIR_EEPROM_PB); parteAlta=eeprom_read_word(DIR_EEPROM_PA); conteoTotal=parteBaja & 0x0000FFFF; conteoTotal+=((parteAlta<<16) & 0xFFFF0000); } void desplegarDato(uint32_t dato) { u08 j,digitosConteo[10],digitoDesplegar[2]; 39 uint32_t copiaDato; copiaDato=dato; for(j=0;j<10;j++) { digitosConteo[j]=copiaDato%10; copiaDato/=10; } PORTB=0x00; PORTD&=0xF3; digitoDesplegar[0]=identificarDigito(digitosConteo[0]); digitoDesplegar[1]=identificarDigito(digitosConteo[1]); // Despliegue de valores en displays 7 segmentos for(conteoDespliegue=0;conteoDespliegue<NUMERO_DESPLIEGUE;conteoDespliegue++) { PORTD|=0x04; PORTB=digitoDesplegar[0]; desbordeTimer0=TIEMPO_DESPLIEGUE; enc_timer0_1e6(); while(desbordeTimer0); apg_timer0(); PORTD&=0xFB; PORTB=digitoDesplegar[1]; PORTD|=0x08; desbordeTimer0=TIEMPO_DESPLIEGUE; enc_timer0_1e6(); while(desbordeTimer0); apg_timer0(); PORTD&=0xF7; } PORTB=0x00; PORTD&=0xF3; } u08 identificarDigito(u08 digito) { u08 digitoDesplegar; switch(digito) { case 0: digitoDesplegar=NUM_CERO; break; case 1: digitoDesplegar=NUM_UNO; break; case 2: digitoDesplegar=NUM_DOS; break; case 3: digitoDesplegar=NUM_TRES; break; case 4: digitoDesplegar=NUM_CUATRO; break; case 5: digitoDesplegar=NUM_CINCO; break; case 6: digitoDesplegar=NUM_SEIS; break; case 7: digitoDesplegar=NUM_SIETE; 40 break; case 8: digitoDesplegar=NUM_OCHO; break; case 9: digitoDesplegar=NUM_NUEVE; break; } return digitoDesplegar; } //void recibirUART(unsigned char c) { //u08 buffercito[UART_RX_BUFFER_SIZE]; //while( (c = uartGetByte() != -1 ) ) { // rprintf("recibido: %d",c); // return; //desplegarDato(0); //} //} u08 sendCommand(u08* command, u08* answer, u08 timeout) { volatile unsigned char answerBuff[UART_RX_BUFFER_SIZE+1]; volatile u08 c, index; uint32_t t; // answerBuff[0] = 0; index = 0; // Imprimir el comando if ( command != NULL ) { rprintfStr(command); rprintfCRLF(); } t = segundosArriba; // Esperar timeout segundos while(t > segundosArriba - timeout); // Leer la respuesta del modem if ( uartReceiveBufferIsEmpty == TRUE ) return 0; // // // // // // // // // // // while( uartReceiveByte(&c) ) { answerBuff[index++] = c; Codigo para desplegar en display caracter por cararcter desplegarDato(index); desbordeTimer0=10; enc_timer0(); while(desbordeTimer0); apg_timer0(); desplegarDato(c); desbordeTimer0=100; enc_timer0(); while(desbordeTimer0); apg_timer0(); } uartFlushReceiveBuffer(); // Agregar terminacion con \0 41 //if (index > 0) { answerBuff[index++] = 0; //} if (answer != NULL ) strcpy(answer,answerBuff); entradaUsuario=PINA & 0xF8; if(entradaUsuario==SWITCH_CONTEO) { // CAPTURAR! capturar(); } return index; } void capturar() { capturaCompleta=FALSE; ADCSRA=0xD8; while(!capturaCompleta); // Logica de conteo de personas if((datoMuestreado<umbralDisparo)&&(!contandoTiempo)) { contandoTiempo=TRUE; desbordeTimer0=TIEMPO_ESPERA; enc_timer0(); } else if((datoMuestreado>=umbralDisparo)&&(contandoTiempo)) { contandoTiempo=FALSE; apg_timer0(); } else if((contandoTiempo)&&(!desbordeTimer0)) { apg_timer0(); desbordeTimer0=TIEMPO_ESPERA; conteoTotal++; // Agregar la nueva cuenta a la estructura sCuenta cuentaTemporal2 = { segundosArriba, conteoTotal }; countbufferAddToEnd(&cuentaBuffer, cuentaTemporal2 ); //for(i=0;i<1;i++) { // PORTA|=0x04; // desbordeTimer0=TIEMPO_PARPADEO; // enc_timer0(); // while(desbordeTimer0); // apg_timer0(); // PORTA&=0xFB; // desbordeTimer0=TIEMPO_PARPADEO; // enc_timer0(); // while(desbordeTimer0); // apg_timer0(); //} } } Cambios realizados en este proyecto e formato diff: Index: main.c =================================================================== --- main.c (.../main.c) (revision 1) 42 +++ main.c (.../avr/main.c) @@ -1,11 +1,16 @@ #include <avr/io.h> #include <avr/interrupt.h> #include <avr/eeprom.h> +#include <string.h> +#include #include -#include -#include (revision 8) "global.h" "global_ucr.h" "timer_ucr.h" "uart_ucr.h" +#include "avrlib/uart.h" +#include "avrlib/rprintf.h" + +#include "countbuffer.h" + // Constantes #define BOTON_REESTABLECER 0x08 #define BOTON_GUARDAR 0x10 @@ -35,6 +40,19 @@ #define NUM_OCHO 0xDF #define NUM_NUEVE 0xDE +// tamanno del buffer para las cuentas +#define COUNT_BUFFER_SIZE 31 + +#define TIMER2_LOOPS 125 +#define MODEM_TIMEOUT 1 +#define DIAL_TIMEOUT 3 +#define TCPIP_TIMEOUT 1 + +#define CR "\015" +#define LF "\012" + +#define UNIT_ID "1" + // Variables globales volatile u08 capturaCompleta, entradaUsuario, @@ -48,15 +66,40 @@ umbralDisparo; volatile uint32_t conteoTotal; +sCuenta cuentaData[COUNT_BUFFER_SIZE]; +volatile sCuenta cuentaTemporal; +countcBuffer cuentaBuffer; + + +// uptime +volatile uint32_t segundosArriba=0; +// 125 comparaciones contra 250 hace ~ 1 segundo +volatile u08 conteoSegundos=TIMER2_LOOPS; + +// estado: 0 comandos, 1 transmitiendo. +volatile u08 estado_modem=0; +u08 size; +u08 answer[UART_RX_BUFFER_SIZE+1]; + +// strings +const u08 modemInitString[] = "ATE0"; 43 +const u08 modemDialString[] = "ATDTlocalhost:8000"; + + + + // Prototipos de funciones void enc_timer0(void); void enc_timer0_1e6(void); void apg_timer0(void); +void enc_timer2(void); void escribirEEPROM(void); void leerEEPROM(void); void desplegarDato(uint32_t dato); u08 identificarDigito(u08 digito); +u08 sendCommand(u08* command, u08* answer, u08 timeout); + // Vectores de interrupcion SIGNAL(SIG_ADC) { ADCSRA|=0x10; @@ -94,7 +137,19 @@ } } +// Segundero, uptime +SIGNAL(SIG_OUTPUT_COMPARE2) { + TIFR|=0x80; + if(conteoSegundos > 0) + conteoSegundos--; + else { + conteoSegundos=TIMER2_LOOPS; + segundosArriba++; + } +} + int main(void) { + uint32_t tLastTX=1; // Inicializacion de registros DDRA=0x04; // Puertos de lectura de ADC, LED de indicacion y botones de usuario DDRB=0xFF; // Bus de datos displays 7 segmentos @@ -109,7 +164,20 @@ contandoTiempo=FALSE; conteoEEPROM=TIEMPO_EEPROM; leerEEPROM(); + if (conteoTotal < 0) + conteoTotal=0; + + // inicializar usart + uartInit(); + uartSetBaudRate(9600); // set UART speed to 9600 baud + rprintfInit(uartSendByte); // configure rprintf to use UART for output + //uartFlushReceiveBuffer(); + + + + + countbufferInit(&cuentaBuffer,cuentaData,COUNT_BUFFER_SIZE); // Redirigir datos recibidos en uart a una funcion // uartSetRxHandler(recibirUART); // Habilitacion de interrupciones sei(); 44 @@ -118,6 +186,13 @@ TIMSK=0x04; TCCR1B=0x05; + + + + + + + // inicializacion TCCR2=0x2E; // OCR2=0xFA; // TIMSK|=0x80; // TIFR|=0x80; de timer2 presc = 256, no OC2, modo CTC comparar contra 250 habilitar interrupcion solo contra compare match // Inicializacion de primer captura del ADC capturaCompleta=FALSE; ADCSRA=0xD8; @@ -126,25 +201,8 @@ while(TRUE) { entradaUsuario=PINA & 0xF8; if(entradaUsuario==SWITCH_CONTEO) { capturaCompleta=FALSE; ADCSRA=0xD8; while(!capturaCompleta); // Logica de conteo de personas if((datoMuestreado<umbralDisparo)&&(!contandoTiempo)) { contandoTiempo=TRUE; desbordeTimer0=TIEMPO_ESPERA; enc_timer0(); } else if((datoMuestreado>=umbralDisparo)&&(contandoTiempo)) { contandoTiempo=FALSE; apg_timer0(); } else if((contandoTiempo)&&(!desbordeTimer0)) { apg_timer0(); desbordeTimer0=TIEMPO_ESPERA; conteoTotal++; } + // CAPTURAR! + capturar(); } else { apg_timer0(); @@ -169,10 +227,17 @@ } break; case BOTON_GUARDAR: + + // XXX + // cuentaTemporal.time = segundosArriba; + // cuentaTemporal.count = ++conteoTotal; + // countbufferAddToEnd(&cuentaBuffer, cuentaTemporal ); + + desplegarDato(conteoTotal); escribirEEPROM(); + // // Parpadeo de LED for(i=0;i<3;i++) { + for(i=0;i<1;i++) { // PORTA|=0x04; 45 desbordeTimer0=TIEMPO_PARPADEO; enc_timer0(); @@ -186,8 +251,9 @@ } break; case BOTON_INFORMAR: uart_putstr("Conteo registrado: "); + desplegarDato(conteoTotal); + desbordeTimer0=TIEMPO_RETARDO; enc_timer0(); while(desbordeTimer0); @@ -217,7 +283,7 @@ umbralDisparo=valorMaximo/2 + valorMaximo/4; if(umbralDisparo>=CTE_DISPARO) { // Parpadeo de LED for(i=0;i<3;i++) { for(i=0;i<4;i++) { PORTA|=0x04; desbordeTimer0=TIEMPO_PARPADEO; enc_timer0(); + @@ -239,11 +305,65 @@ break; default: break; -} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + } } } } // end case } // end if(entradaUsuario... // // Periodicamente Conectarse al servidor // y enviar los datos que se tienen // // Cada 10 segundos... if ( (segundosArriba - tLastTX) % 10 == 0 ) { while( 1 ) { cuentaTemporal = countbufferGetFromFront(&cuentaBuffer); if (cuentaBuffer.datalength == 0) break; answer[0] = 0; //estado_modem = 0; // inicializar el modem sendCommand(modemInitString, NULL, MODEM_TIMEOUT); // revisar si modem esta listo sendCommand("AT", answer, MODEM_TIMEOUT); if ( strncmp(answer,CR LF "OK" CR LF,6) != 0 ) break; // marcar a host remoto sendCommand(modemDialString, answer, DIAL_TIMEOUT); if ( strncmp(answer,CR LF "CONNECT",9) != 0 ) break; // estamos conectados enviar saludo con numero de unidad sendCommand("HELO " UNIT_ID, answer, TCPIP_TIMEOUT); if ( strncmp(answer,"200 Hello " UNIT_ID ,8) != 0 ) break; // nos reconocieron, ahora a enviar la cuenta 46 + while( cuentaBuffer.datalength > 0 ) { + rprintf("COUNT %d,", cuentaTemporal.count ); + rprintf(" TIMEAGO %d", segundosArriba cuentaTemporal.time ); + rprintf("\n"); + sendCommand(NULL, answer, TCPIP_TIMEOUT); + if ( strncmp(answer,"200 ",4) != 0 ) + break; + cuentaTemporal = countbufferGetFromFront(&cuentaBuffer); + } // end while + // Ciao! + tLastTX = segundosArriba; + rprintf("QUIT\n"); + /// XXX esperar un segundo? + sendCommand(NULL, NULL, 1); + sendCommand("+++", answer, MODEM_TIMEOUT); + if ( strncmp(answer,CR LF "OK" CR LF,6) != 0 ) + break; + sendCommand("ATH", answer, TCPIP_TIMEOUT); + if ( strncmp(answer,CR LF "OK",4) != 0 ) + break; + + } // end while( + } // end if(segundosArriba % .... + //else { desplegarDato(segundosArriba); } + + } // end while(TRUE) +} // end main + // Funciones void enc_timer0(void) { TIFR|=0x01; @@ -262,6 +382,13 @@ TIMSK&=0xFE; } +void enc_timer2(void) { + TCCR2=0x2E; // presc = 256, no OC2, modo CTC + OCR2=0xFA; // comparar contra 250 + TIMSK|=0x80; // habilitar interrupcion solo contra compare match + TIFR|=0x80; +} + void escribirEEPROM(void) { uint16_t parteBaja,parteAlta; uint32_t conversor; @@ -358,3 +485,107 @@ return digitoDesplegar; } +//void recibirUART(unsigned char c) { + //u08 buffercito[UART_RX_BUFFER_SIZE]; + //while( (c = uartGetByte() != -1 ) ) { +// rprintf("recibido: %d",c); +// return; + //desplegarDato(0); + //} +//} + +u08 sendCommand(u08* command, u08* answer, u08 timeout) { 47 + + volatile unsigned char answerBuff[UART_RX_BUFFER_SIZE+1]; + volatile u08 c, index; + uint32_t t; + // + answerBuff[0] = 0; + index = 0; + + // Imprimir el comando + if ( command != NULL ) { + rprintfStr(command); + rprintfCRLF(); + } + t = segundosArriba; + // Esperar timeout segundos + while(t > segundosArriba - timeout); + + // Leer la respuesta del modem + if ( uartReceiveBufferIsEmpty == TRUE ) + return 0; + + while( uartReceiveByte(&c) ) { + answerBuff[index++] = c; +// Codigo para desplegar en display caracter por cararcter +// desplegarDato(index); +// desbordeTimer0=10; +// enc_timer0(); +// while(desbordeTimer0); +// apg_timer0(); +// desplegarDato(c); +// desbordeTimer0=100; +// enc_timer0(); +// while(desbordeTimer0); +// apg_timer0(); + + + } + + uartFlushReceiveBuffer(); + // Agregar terminacion con \0 + //if (index > 0) { + answerBuff[index++] = 0; + //} + + if (answer != NULL ) + strcpy(answer,answerBuff); + + entradaUsuario=PINA & 0xF8; + if(entradaUsuario==SWITCH_CONTEO) { + // CAPTURAR! + capturar(); + } + return index; +} + + +void capturar() { + capturaCompleta=FALSE; + ADCSRA=0xD8; + while(!capturaCompleta); + 48 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +} // Logica de conteo de personas if((datoMuestreado<umbralDisparo)&&(!contandoTiempo)) { contandoTiempo=TRUE; desbordeTimer0=TIEMPO_ESPERA; enc_timer0(); } else if((datoMuestreado>=umbralDisparo)&&(contandoTiempo)) { contandoTiempo=FALSE; apg_timer0(); } else if((contandoTiempo)&&(!desbordeTimer0)) { apg_timer0(); desbordeTimer0=TIEMPO_ESPERA; conteoTotal++; // Agregar la nueva cuenta a la estructura sCuenta cuentaTemporal2 = { segundosArriba, conteoTotal }; countbufferAddToEnd(&cuentaBuffer, cuentaTemporal2 ); //for(i=0;i<1;i++) { // PORTA|=0x04; // desbordeTimer0=TIEMPO_PARPADEO; // enc_timer0(); // while(desbordeTimer0); // apg_timer0(); // PORTA&=0xFB; // desbordeTimer0=TIEMPO_PARPADEO; // enc_timer0(); // while(desbordeTimer0); // apg_timer0(); //} } 49 Librería countbuffer countbuffer.h #ifndef COUNTBUFFER_H #define COUNTBUFFER_H #include "global.h" #include <stdint.h> typedef struct struct_sCuenta { volatile uint32_t time; volatile uint32_t count; } sCuenta; typedef struct struct_countBuffer { sCuenta *dataptr; is stored unsigned short size; unsigned short datalength; buffer unsigned short dataindex; data starts } countcBuffer; ///< the physical memory address where the buffer ///< the allocated size of the buffer ///< the length of the data currently in the ///< the index into the buffer where the // function prototypes //! initialize a buffer to start at a given address and have given size void countbufferInit(countcBuffer* buffer, sCuenta *start, unsigned short size); //! get the first byte from the front of the buffer sCuenta countbufferGetFromFront(countcBuffer* buffer); //! dump (discard) the first numbytes from the front of the buffer void countbufferDumpFromFront(countcBuffer* buffer, unsigned short numbytes); //! get a byte at the specified index in the buffer (kind of like array access) // ** note: this does not remove the byte that was read from the buffer sCuenta countbufferGetAtIndex(countcBuffer* buffer, unsigned short index); //! add a byte to the end of the buffer unsigned char countbufferAddToEnd(countcBuffer* buffer, sCuenta data); //! check if the buffer is full/not full (returns zero value if full) unsigned short countbufferIsNotFull(countcBuffer* buffer); //! flush (clear) the contents of the buffer void countbufferFlush(countcBuffer* buffer); #endif //@} 50 countbuffer.c #include "countbuffer.h" #include "global.h" #include "avr/io.h" #ifndef CRITICAL_SECTION_START #define CRITICAL_SECTION_START unsigned char _sreg = SREG; cli() #define CRITICAL_SECTION_END SREG = _sreg #endif // global variables // initialization void countbufferInit(countcBuffer* buffer, sCuenta *start, unsigned short size) { // begin critical section CRITICAL_SECTION_START; // set start pointer of the buffer buffer->dataptr = start; buffer->size = size; // initialize index and length buffer->dataindex = 0; buffer->datalength = 0; // end critical section CRITICAL_SECTION_END; } // access routines sCuenta countbufferGetFromFront(countcBuffer* buffer) { sCuenta data = { 0 , 0 }; // begin critical section CRITICAL_SECTION_START; // check to see if there's data in the buffer if(buffer->datalength) { // get the first character from buffer data = buffer->dataptr[buffer->dataindex]; // move index down and decrement length buffer->dataindex++; if(buffer->dataindex >= buffer->size) { buffer->dataindex -= buffer->size; } buffer->datalength--; } // end critical section CRITICAL_SECTION_END; // return return data; } void countbufferDumpFromFront(countcBuffer* buffer, unsigned short numbytes) { // begin critical section CRITICAL_SECTION_START; // dump numbytes from the front of the buffer // are we dumping less than the entire buffer? if(numbytes < buffer->datalength) { // move index down by numbytes and decrement length by numbytes 51 buffer->dataindex += numbytes; if(buffer->dataindex >= buffer->size) { buffer->dataindex -= buffer->size; } buffer->datalength -= numbytes; } else { // flush the whole buffer buffer->datalength = 0; } // end critical section CRITICAL_SECTION_END; } sCuenta countbufferGetAtIndex(countcBuffer* buffer, unsigned short index) { // begin critical section CRITICAL_SECTION_START; // return character at index in buffer sCuenta data = buffer->dataptr[(buffer->dataindex+index)%(buffer->size)]; // end critical section CRITICAL_SECTION_END; return data; } unsigned char countbufferAddToEnd(countcBuffer* buffer, sCuenta data) { // begin critical section CRITICAL_SECTION_START; // make sure the buffer has room if(buffer->datalength < buffer->size) { // save data byte at end of buffer buffer->dataptr[(buffer->dataindex + buffer->datalength) % buffer->size] = data; // increment the length buffer->datalength++; // end critical section CRITICAL_SECTION_END; // return success return -1; } // end critical section CRITICAL_SECTION_END; // return failure return 0; } unsigned short countbufferIsNotFull(countcBuffer* buffer) { // begin critical section CRITICAL_SECTION_START; // check to see if the buffer has room // return true if there is room unsigned short bytesleft = (buffer->size - buffer->datalength); // end critical section CRITICAL_SECTION_END; return bytesleft; } 52 void countbufferFlush(countcBuffer* buffer) { // begin critical section CRITICAL_SECTION_START; // flush contents of the buffer buffer->datalength = 0; // end critical section CRITICAL_SECTION_END; } Código del Servidor #!/usr/bin/perl -w # escuchador.pl use strict; use IO::Socket qw/:crlf/; use Digest::MD5 qw(md5_hex); use lib '../lib/'; use Counter; #get the port to bind to or default to 8000 my $port = $ARGV[0] || 8000; # Para prevenir 'zombies' $SIG{CHLD} = 'IGNORE'; #create the listen socket my $listen_socket = IO::Socket::INET->new(LocalPort => $port, Listen => 10, Proto => 'tcp', Reuse => 1); #make sure we are bound to the port die "Cant't create a listening socket: $@" unless $listen_socket; warn "Server ready. Waiting for connections ... \n"; #wait for connections at the accept call while (my $connection = $listen_socket->accept) { my $child; # perform the fork or exit die "Can't fork: $!" unless defined ($child = fork()); if ($child == 0) { #i'm the child! my $unit_id; #close the child's listen socket, we dont need it. $listen_socket->close; my $con_id = md5_hex($connection->peerhost,time); warn "CH: Conexion desde ", $connection->peerhost," id: $con_id\n"; # Saludar al inicio #print $connection "220-Servidor de Prueba v0.1$CR$LF"; #print $connection "220 Conexion ID: $con_id$CR$LF"; 53 my $buf; my $error_count=0; while (defined ($buf = <$connection>)) { # Convertir nuevas lineas #$buf =~ s/\015?\012/\n/; # Eliminar nuevas lineas $buf =~ s/$CR?$LF//; warn time," Recibido: \"$buf\" de ",$connection->peerhost,"\n"; foreach ($buf) { if ( /^HELP/i ) { print $connection "214-Comandos disponibles:$CR$LF"; print $connection "214-STATUS, UNITID, HELO:$CR$LF"; print $connection "214 QUIT$CR$LF"; last; } if ( /^HELO(.*)/i ) { if ($1 =~ / (\d+)/i) { $unit_id = $1; my @u = (); @u = Counter::get_unit($unit_id); unless ( defined $u[0] and scalar(@u) ) { warn "$con_id: Not found: $unit_id\n"; print $connection "500 Unit $unit_id not found$CR$LF"; last; } # to log warn "$con_id: unit_id: $unit_id\n"; print $connection "200 Hello unit $unit_id$CR$LF" } else { # to log warn "$con_id: Error en unit_id $1\n"; print $connection "500 Syntax error$CR$LF" } last; } # STATUS: Verificar se esta conectado y con que id if ( /^status$/i ) { print $connection "200 CON_ID $con_id OK$CR$LF"; last; } # NOOP: Hacer nada pero mantener la comunicacion fluida if ( /^noop ?(.*)/i ) { #warn "NOOP: \"$1\" from $con_id\n"; print $connection "200 NOOP OK$CR$LF"; last; } if ( /^count (\d+), timeago (\d+)/i ) { unless (defined $unit_id) { print $connection "505 Debe identificarse$CR$LF"; last; } # Magic! Counter::put_count($unit_id,$1,time-$2); print $connection "200 Thank you.$CR$LF"; last; } if ( /^timeago(.*)/i ) { unless (defined $unit_id) { print $connection "505 Debe identificarse$CR$LF"; 54 last; } if ($1 =~ / (\d+)/i) { my $t = time; #warn "Timeago: $1 at $t\n"; print $connection "210 Thank you.$CR$LF"; } last; } if ( /^quit$/i ) { print $connection "221 Bye.$CR$LF"; #$connection->close; #last; # Para LOG warn "Cliente ",$connection->peerhost," id $con_id cerro la conexion\n" if $connection->connected; $connection->close; exit 0; } # Comando no valido if ( $error_count++ > 5 ) { print $connection "500 Muchos errores$CR$LF"; $connection->close; exit 0; } else { print $connection "500 Invalid command \"$buf\" errors: $error_count$CR$LF"; } } } # Para LOG warn "Conexion desde ",$connection->peerhost," cerrada\n" if $connection->connected; exit 0; } else { #i'm the parent! #who connected? warn "Connecton recieved ... ",$connection->peerhost,"\n"; #close the connection, the parent has already passed # it off to a child. $connection->close(); } #go back and listen for the next connection! } Código del CGI principal #!/usr/bin/perl -w # vim:set filetype=perl fileencoding=utf-8: use strict; use warnings; 55 use lib qw(../lib); use Counter; use Counter::Security; use use use use Template; CGI qw/-no_xhtml -private_tempfiles/; CGI::Carp qw(fatalsToBrowser); CGI::Session; use Data::Dumper; use Time::HiRes (); # Autoflush! $| = 1; my @localtime my $time_init = localtime(); = Time::HiRes::time(); my $footer_enabled = 1; END { if ($footer_enabled) { my $time_end = Time::HiRes::time(); my $duracion = $time_end - $time_init; use Sys::Hostname; print "<!-- Generado por " . hostname() ." en $duracion segundos -->\n"; } } my ($config, $q, $s, $msg, $script_name ); my $url_root = '/counter-www'; $q $script_name my $script_query = new CGI; = $q->script_name(); = $ENV{REQUEST_URI}; $s = CGI::Session->load( $q ) or die CGI::Session->errstr; $config = { INCLUDE_PATH INTERPOLATE POST_CHOP PRE_PROCESS POST_PROCESS EVAL_PERL => "../templates", => 1, => 1, => 'header.html', => 'footer.html', => 0, }; my $t = Template->new($config) or die "$Template::ERROR\n"; my $vars = { script => $script_name, query => $script_query, is_logged => $s->param('is_logged'), is_admin => $s->param('is_admin'), }; sub principal { new_page(); $$vars{titulo} = "Contador UCR"; 56 $$vars{subtitulo} = "Subtitulo"; #$$vars{msg_error} = "Uy, que torta!"; #$$vars{msg_warn} = "Pocas horas de suenno"; $$vars{is_logged} = $s->param('is_logged'); $t->process("main.html",$vars) or die $t->error(); exit 0; } sub new_page { my $maintitle = shift; $maintitle .= ' - ' if defined $maintitle; $maintitle .= "Contador UCR";#get_config('maintitle'); my $author = "eliastorres\@gmail.com";#get_config('author'); my $copyright = '(C) ' . scalar(1900+$localtime[5]) . " $author"; my $url_root = '/counter-www'; my @stylesheets = undef ; #("$url_root/style.css"); push @stylesheets, "$url_root/style.css"; # IE fixes #if ($q->user_agent('msie\s(5\.[5-9]|[6]\.[0-9]*).*(win)')) { # IE 5 to 6 # push @stylesheets, "/estilos/$estilo/fix-ie-5-and6.css"; #} my $meta; $$meta{copyright} = $copyright; $$meta{author} = $author; # Se usa Session para obtener la info de las cookies ($s) print $s->header(-charset=>'utf-8', -expires=>'now' ); print $q->start_html( -title => $maintitle, # -author => $author, -meta => $meta, -lang =>'es-CR', -style => { -src => \@stylesheets, -code => undef }, # -head => $q->Link({ -rel=>'shortcut icon', #q -href=>'/media/favicon.ico'}), ); } sub inicio { if ( $s->is_expired ) { $msg = "Sesi&oacute;n Expirada." } elsif ( $s->is_empty ) { $s = $s->new( $q ); } elsif ( defined $s->param('is_logged') and defined $s>param('username') ) { principal; } principal(); exit 0; } sub entrar { 57 my $username = $q->param('username'); my $pass = $q->param('pass'); if (defined $username and defined $pass and Counter::Security::check_password($username,$pass) == 1) { $s->param('is_logged',1); #$s->expire('1m'); $s->param('username',$username); my $id = Counter::get_id_usuario_by_username($username); $s->param('id_usuario',$id); $s>param('is_admin',Counter::Security::is_admin($username)); $$vars{msg_warn} = "Bienvenido, $username"; } else { $$vars{msg_warn} = "Error, usuario o contrase&ntilde;a inv&aacute;lida"; } $$vars{'is_logged'} = $s->param('is_logged'); $$vars{'is_admin'} = $s->param('is_admin'); principal(); exit 0; } sub salir { $s->delete(); $s->flush(); $$vars{msg_warn} = "Sesi&oacute;n finalizada correctamente."; inicio(); } sub check_session { if ( $s->is_empty ) { $$vars{msg_error} = "Debe iniciar sesi&oacute;n"; inicio(); } } sub get_data_from_unit { #://check_session(); my $unit_id = $q->param('unit_id'); die "Error en el unit_id" unless $unit_id =~ /\d+/; my ($unit) = Counter::get_unit($unit_id); my @data = ( defined $unit ) ? $unit->counts : undef ; my $format = $q->param('fmt'); $$vars{'data'} = \@data; $$vars{'unit'} = $unit; $$vars{'unit_id'} = $unit_id; if ( defined $format and $format eq 'csv' ) { $footer_enabled = undef; print $q->header( -attachment => "data$unit_id.csv", -type => 'text/csv', -expires => 'now', ); print "count_id,time_rec,time_ago,count\n"; foreach (@data) { print $_->count_id.",".$_->time_rec.",".$_>time_ago.",".$_->count."\n"; } 58 } elsif ( defined $format and $format eq 'xml' ) { $footer_enabled = undef; use open_flash_chart; my $g = new open_flash_chart(); my @counts; my @times; my $max = 1; foreach (@data) { push @counts,$_->count; $max = $_->count if $_->count > $max; push @times,$_->time_rec; } # Redondear $max a proximo multiplo de 10 $max += 10 - $max % 10; #$g->set_x_labels( @times ); # estilo eje X #$g->set_x_label_style( 10, '#9933CC', 2 ); #$g->set_x_axis_steps( 10 ); $g->set_data( @counts ); $g->set_y_max( $max ); $g->title("Unidad $unit_id " ); $g->line_hollow( 2, 4, '0x809033', "Unidad " . $unit_id , 10 ); print $g->render(); } elsif ( defined $format and $format eq 'graph' ) { new_page("Grafico unidad "); # use open_flash_chart; $$vars{'width'} = '100%' ; $$vars{'height'} = 250 ; $$vars{'url'} = "http://" . $ENV{HTTP_HOST} . "/" . $ENV{SCRIPT_NAME} . "?a=get_data_from_unit;unit_id=$unit_id;fmt=xml" ; $$vars{'url_root'} = "$url_root" ; $t->process("graph_from_unit.html",$vars) or die $t>error(); } else { new_page("Datos de Unidad "); $t->process("data_from_unit.html",$vars) or die $t>error(); } exit 0; }# sub add_unit { check_session(); my $unit_id = $q->param('unit_id'); if( defined $q->param('fromform') ) { my $info; $$info{'location'} = $q->param('location'); $$info{'description'} = $q->param('description'); my $u = Counter::add_unit($info); if ( defined $u ) { $$vars{'msg_info'} = "Unidad Agregado, id:" . $u->unit_id; } else { $$vars{'msg_error'} = "No se pudo agregar unidad"; } } new_page("Nueva Unidad Contadora"); $t->process("add_unit.html",$vars) or die $t->error(); exit 0; 59 } sub ls_users { check_session(); my @users = Counter::ls_users(); $$vars{'users'} = \@users; new_page("Usuarios"); $t->process("ls_users.html",$vars) or die $t->error(); exit 0; } sub add_user { check_session(); if (defined $q->param('fromform')) { my $info = { email => $q->param('email') }; $$info{'name'} = $q->param('name'); $$info{'username'} = $q->param('username'); # revisar que no exista el usuario if (Counter::get_id_usuario_by_username($$info{'username'}) == -1 ) { $$info{'pass'} = Counter::Security::crypt_password($q->param('pass')); $$info{'time_created'} = time; $$info{'time_accessed'} = 0; $$info{'active'} = 1; #$q>param('status'); $$info{'role'} = $q->param('role'); # 0 admin, 1 usuario my $new_id = Counter::add_user($info); if (defined $new_id and $new_id > 0) { $$vars{'msg_info'} = "Usuario " . $$info{'name'} . "agregado con el id $new_id." ; # retornar #ver_usuarios(); } else { $$vars{'msg_info'} = "Error al agregar el usuario " . $$info{'nombre'} . ", result = $new_id." ; } } else { $$vars{'msg_error'} = "Ya existe un usuario con el username " . $$info{'username'}; } $$vars{info} = $info; } new_page("Nuevo Usuario"); $t->process("add_user.html",$vars) or die $t->error(); exit 0; } # ---------------------------------------------------------------------------- # # Welcome to the state machine # ---------------------------------------------------------------------------- # if ( !$q->param() ) { inicio; } elsif ( defined $q->param('a') ) { my $a = $q->param('a'); entrar() salir() ls_units() if $a =~ /^Entrar$/i; if $a =~ /^Salir$/i; if $a =~ /ls_units/i; 60 add_unit() if $a =~ /add_unit|agregar get_data_from_unit() ls_users() add_user() if $a =~ /get_data_from_unit/i; if $a =~ /ls_users/i; if $a =~ /^(add_user|Agregar ?unidad/i; ?Usuario)$/i; # por omision die "Accion no reconocida\na = $a\n" . Dumper([ $q->param() ]); } else { die "Accion No definida\n" . Dumper([ $q->param() ]); } # colorin colorao... Counter.pm package Counter; use Carp; use strict; # vim:set path+=..lib/: use Counter::DBI; sub put_count { my ($unit_id,$count,$time_ago,$time_rec) = @_; # time_rec es el tiempo actual de recepcion del dato $time_rec = time unless defined $time_rec; croak("Faltan parametros de definir") if $#_ < 2; Counter::Count->insert( { unit_id => $unit_id, count => $count, time_rec => $time_rec, time_ago => $time_ago, } ) } sub add_unit { my $info = shift; return Counter::Unit->insert( { location => $$info{'location'}, description => $$info{'description'}, }) } sub add_user { my $info = shift; if ( my $u = Counter::User->insert($info) ) { return $u->user_id; } else { return -1; } 61 } sub get_id_usuario_by_username { my $username = shift; my @foo = Counter::User->search(username => $username); return -1 unless scalar(@foo); return $foo[0]->{'id_usuario'}; } sub ls_units { my @foo = Counter::Unit->retrieve_all; return @foo; } sub get_unit { my $unit_id = shift; my @foo = Counter::Unit->retrieve($unit_id); return @foo; } sub get_data_from_unit { my $unit_id = shift; my ($unit) = Counter::Unit->search(unit_id => $unit_id); my @data = $unit->counts; return @data; } sub ls_users { my @foo = Counter::User->retrieve_all; return @foo; } 1; Counter/DBI.pm package Counter::DBI; use strict; use base 'Class::DBI'; __PACKAGE__->connection('dbi:mysql:counter', 'root', ''); package Counter::Unit; use base 'Counter::DBI'; __PACKAGE__->table('units'); __PACKAGE__->columns( All => qw/unit_id location description/); __PACKAGE__->has_many( counts =>"Counter::Count"=>'unit_id'); package Counter::Count; use base 'Counter::DBI'; __PACKAGE__->table('counts'); __PACKAGE__->columns( All => qw/count_id unit_id time_rec time_ago count/); 62 #__PACKAGE__->has_many( unit=>"Counter::Unit"=>'unit_id'); package Counter::User; use base 'Counter::DBI'; __PACKAGE__->table('users'); __PACKAGE__->columns( All => qw/user_id username name pass email time_created time_accessed active role/); 1; Counter/Security.pm package Counter::Security; use strict; use Crypt::PasswdMD5; use Counter; sub crypt_password { my $passwd = shift; return unix_md5_crypt($passwd); } sub check_password { my $user = shift; my $passwd = shift; my @foo = Counter::User->search(username => $user); if (!scalar(@foo)) { return -1; } my $crypasswd = $foo[0]->pass(); my @param = split(/\$/, $crypasswd); my $salt = $param[2]; my $ck_passwd = unix_md5_crypt($passwd,$salt); return ($ck_passwd eq $crypasswd ? 1: 0); } sub admit_user { my ($user, $passwd) = @_; # Clear text password return check_password($user,$passwd); } sub is_admin { my $user = shift; my @foo = Counter::User->search(username => $user); return -1 unless (scalar(@foo)); return $foo[0]->role == 0 ? 1 : 0; } 63 sub set_password { my ($user, $passwd) = @_; my @users; $passwd = crypt_password($passwd); @users = Counter::User->search(username => $user); # Si el usuario no existe return -1 unless (scalar(@users)); $users[0]->pass($passwd); $users[0]->update; } 1; templates/graph_from_unit.html <p> <object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" codebase="http://fpdownload.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=8,0, 0,0" width="400" height="300" id="graph-2" align="middle"> <param name="allowScriptAccess" value="sameDomain" /> <param name="movie" value="$url_root/open-flashchart.swf?width=$width&amp;height=$height&amp;data=[% url %]" /><param name="quality" value="high" /><param name="bgcolor" value="#FFFFFF" /> <embed src="$url_root/open-flash-chart.swf?width=[% width %]&amp;height=[% height %]&amp;data=[% url %]" quality="high" bgcolor="#FFFFFF" width="$width" height="$height" name="open-flash-chart" align="middle" allowScriptAccess="sameDomain" type="application/xshockwave-flash" pluginspage="http://www.macromedia.com/go/getflashplayer" /> </object> templates/add_unit.html <form id="add_unit" method="post" action="[% script %]"> <table> <tr> <th colspan=2>Agregar Nueva Unidad de Conteo</th> </tr> <tr> <td class="item">Ubicaci&oacute;n</td> <td><input type="text" name="location"></td> </tr> <tr> <td class="item">Descripci&oacute;n</td> <td><input type="text" name="description"></td> </tr> <tr> <td colspan=2> <input type="hidden" name="fromform" value="1"> <input type="submit" id="submit" value="Agregar Unidad" name="a" class="button"> <input type="reset" value="Borrar y volver a empezar" class="button"> 64 </td> </tr> </table> </form> templates/add_user.html <form id="add_user" method="post" action="[% script %]"> <table> <tr> <th colspan=2>Agregar Nuevo Usuario</th> </tr> <tr> <td class="item" id="name">Nombre Completo:</td> <td><input type="text" name="name" id="xname" value="[% info.name %]"></td> </tr> <tr> <td class="item" id="username">nombre de usuario:<br><i>ej. jperez</i></td> <td><input type="text" name="username" id="xusername" value="[% info.username %]"></td> </tr> <tr> <td class="item" id="mail">Correo electr&oacute;nico:</td> <td><input type="text" name="email" id="xmail" value="[% info.email %]"></td> </tr> <tr> <td class="item" id="pass">Clave:</td> <td><input type="password" name="pass" id="xpass"></td> </tr> <tr> <td class="item" id="pass2">Confirmar clave:</td> <td><input type="password" name="pass2" id="xpass2"></td> </tr> <!-<tr> <td class="item" id="status">Estado:</td> <td> <input type="radio" name="status" value="1" checked>Activo<br> <input type="radio" name="status" value="0">Inactivo<br> </td> </tr> --> <tr> <td class="item" id="tipo">Tipo de Usuario:</td> <td><input type="radio" name="role" value="0">Administrador<br> <input type="radio" name="role" value="1" checked>Usuario<br> </td> </tr> <tr> <td colspan=2> <input type="hidden" name="fromform" value="1"> <input type="submit" id="submit" value="Agregar Usuario" name="a" class="button"> <input type="reset" value="Borrar todo y volver a empezar" class="button"> 65 </td> </tr> </table> </form> templates/data_from_unit.html <table> <tr> <th>ID</th> <th>Recibido</th> <th>Tiempo Registro</th> <th>Cuenta</th> </tr> [% USE date %] [% FOREACH count = data %] <tr> <td>[% count.count_id %]</td> <td>[% date.format(count.time_rec) %]</td> <td>[% date.format(count.time_ago) %]</td> <td>[% count.count %]</td> </tr> [% END %] </table> templates/footer.html <!-- inicia footer --> </div> <!-- fin entry --> </div> <!-- fin post-1 --> </div> <!-- fin content --> [% PROCESS sidebar.html %] <div id="footer"> <p><br>Nota al pie, esto es to', esto es to' ....</p> </div> </div> <!-- fin div page --> </body> </html> templates/header.html <!-- inicia header --> 66 <div id="page"> <div id="header"> <div id="headerimg"> <h1>Contador UCR</h1> <div class="description">Contando, 1, 2, 3. s&iacute;, s&iacute;, 1, 2, 3...</div> </div> <!-- fin headerimg --> </div> <!-- fin header --> <div id="content" class="narrowcolumn"> <div class="post" id="post-1"> [% IF msg_error %]<div class=msg id="msg_error">[% msg_error %]</div>[% END %] [% IF msg_warn %]<div class=msg id="msg_warn">[% msg_warn %]</div>[% END %] [% IF msg_info %]<div class=msg id="msg_info">[% msg_info %]</div>[% END %] <h2>[% titulo %]</h2> <small>[% subtitulo %]</small> <div class="entry"> templates/ls_units.html <table> <tr> <th>ID</th> <th>Ubicaci&oacute;n</th> <th>Descripci&oacute;n</th> <th>Datos</th> </tr> [% FOREACH unit = units %] <tr> <td>[% unit.unit_id %]</td> <td>[% unit.location %]</td> <td>[% unit.description %]</td> <td> <a href="[% script %]?a=get_data_from_unit&amp;unit_id=[% unit.unit_id %]">[Tabla]</a> <a href="[% script %]?a=get_data_from_unit&amp;unit_id=[% unit.unit_id %]&amp;fmt=csv">[CSV]</a> <a href="[% script %]?a=get_data_from_unit&amp;unit_id=[% unit.unit_id %]&amp;fmt=graph">[GRAPH]</a> </td> </tr> [% END %] </table> templates/ls_users.html <table> <tr> <th>Nombre</th> <th>username</th> <th>Creado</th> <th>Admin</th> </tr> [% FOREACH usuario = users %] 67 <tr> <td><b>[% usuario.name %]</b></td> <td>[% usuario.username %]</td> <td>[% usuario.time_created %]</td> <td>[% IF usuario.role == 0 %]S&iacute;[% ELSE %]No[% END %]</td> </tr> [% END %] </table> templates/menu.html <div id="menu"> <ul> <li><a href="[% script %]?a=ls_units">Unidades</a></li> <ul> <li><a href="[% script %]?a=add_unit">Nueva Unidad</a></li> </ul> [% IF is_admin %]<li><a href="[% script %]?a=ls_users">Ver Usuarios</a></li> <ul> <li><a href="[% script %]?a=agregarusuario">Nuevo Usuario</a></li> </ul> [% END %] <li><a href="[% script %]?a=Salir">Salir</a></li> </ul> </div> templates/sidebar.html <div id="sidebar"> [% IF is_logged %] [% PROCESS menu.html %] [% ELSE %] <form method="post" id="user" action="[% script %]"> <div> <p>Usuario:</p> <input type="text" class="imagen usuarios" value="" name="username" id="u"> <p>Clave:</p> <input type="password" class="imagen readonly" value="" name="pass" id="p"> <p> <input type="submit" class="imagen inicio" name="a" value="Entrar"> </div> </form> [% END %] </div> Archivo SQL para crear la base de datos DROP DATABASE IF EXISTS counter; CREATE DATABASE counter DEFAULT CHARACTER SET utf8 COLLATE utf8_spanish2_ci; USE counter; 68 GRANT SELECT,INSERT, DELETE ON counter.* TO 'counter'@'localhost' IDENTIFIED BY 'countpw'; DROP TABLE IF EXISTS counts; DROP TABLE IF EXISTS units; DROP TABLE IF EXISTS users; CREATE TABLE units ( unit_id location description PRIMARY KEY ) TYPE=INNODB; MEDIUMINT NOT NULL AUTO_INCREMENT, VARCHAR(50), VARCHAR(255), (unit_id) CREATE TABLE counts ( count_id MEDIUMINT NOT NULL AUTO_INCREMENT, unit_id MEDIUMINT NOT NULL, time_rec INTEGER NOT NULL, time_ago INTEGER NOT NULL, count INTEGER NOT NULL, PRIMARY KEY (count_id), FOREIGN KEY (unit_id) REFERENCES units (unit_id) ) TYPE=INNODB; CREATE TABLE users ( user_id MEDIUMINT NOT NULL AUTO_INCREMENT, username VARCHAR(32) NOT NULL, name VARCHAR(128) NOT NULL, pass VARCHAR(255) NOT NULL, email VARCHAR(32) NOT NULL, time_created INTEGER NOT NULL, time_accessed INTEGER NOT NULL, active TINYINT(1) NOT NULL DEFAULT 1, /* Rol: 0: admin, 1: user */ role TINYINT(1) NOT NULL DEFAULT 1, PRIMARY KEY (user_id) ) TYPE=INNODB; 69