Informe - Escuela de Ingeniería Eléctrica

Anuncio
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ón</th>
<th>Descripció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&unit_id=[%
unit.unit_id %]">[Tabla]</a>
<a href="[% script %]?a=get_data_from_unit&unit_id=[%
unit.unit_id %]&fmt=csv">[CSV]</a>
<a href="[% script %]?a=get_data_from_unit&unit_id=[%
unit.unit_id %]&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ó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ña
invá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ón finalizada correctamente.";
inicio();
}
sub check_session {
if ( $s->is_empty ) {
$$vars{msg_error} = "Debe iniciar sesió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&height=$height&data=[% url %]" /><param name="quality"
value="high" /><param name="bgcolor" value="#FFFFFF" />
<embed src="$url_root/open-flash-chart.swf?width=[% width %]&height=[% height
%]&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ón</td>
<td><input type="text" name="location"></td>
</tr>
<tr>
<td class="item">Descripció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ó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í, sí, 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ón</th>
<th>Descripció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&unit_id=[% unit.unit_id %]">[Tabla]</a>
<a href="[% script %]?a=get_data_from_unit&unit_id=[% unit.unit_id
%]&fmt=csv">[CSV]</a>
<a href="[% script %]?a=get_data_from_unit&unit_id=[% unit.unit_id
%]&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í[% 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
Descargar