Robot dispensador para MSDN Vídeo Pep Lluis Baño 1 nº ADVERTENCIA LEGAL Todos los derechos de esta obra están reservados a Pep Lluis Baño y a Netalia, S.L. El editor prohibe cualquier tipo de fijación, reproducción, transformación o distribución de esta obra, ya sea mediante venta, alquiler o cualquier otra forma de cesión de uso o comunicación pública de la misma, total o parcialmente, por cualquier sistema o en cualquier soporte, ya sea por fotocopia, medio mecánico o electrónico, incluido el tratamiento informático de la misma, en cualquier lugar del mundo. La vulneración de cualesquiera de estos derechos podrá ser considerada como una actividad penal tipificada en los artículos 270 y siguientes del Código Penal. La protección de esta obra se extiende al mundo entero, de acuerdo con las leyes y convenios internacionales. © Pep Lluis Baño, 2005 © Netalia, S.L., 2005 Robot dispensador para MSDN Vídeo Cuaderno Técnico de dotNetManía nº1 Autor: Pep Lluis Baño Responsable editorial: Paco Marín Diseño de cubierta: Silvia Gil (Éride, S.L.) Editado por Netalia S.L. c/ Robledal, 135 28529 - Rivas Vaciamadrid (Madrid -España) Tel. (34) 91 666 74 77 - Fax (34) 91 499 13 64 - http://www.dotnetmania.com EJEMPLAR GRATUITO Este ejemplar es gratuito gracias a la cesión de los derechos de autor de Pep Lluis Baño, a Microsoft que ha sufragado los costes de impresión y a Netalia que ha costeado el diseño, maquetación, revisión y distribución a los lectores de dotNetManía. Déposito Legal: M-47915-2005 Impreso en Madrid (España) en noviembre de 2005 índice Robot dispensador para MSDN Vídeo 1 nº Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 1. Entregon+ . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13 1.1. Descripción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13 1.2. Ensamblado . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14 1.3. Puesta en servicio . . . . . . . . . . . . . . . . . . . . . . . . . . 15 2. El puerto serie . . . . . . . . . . . . . . . . . . . . . . . . 17 2.1. El puerto serie . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17 2.2. Protocolo de comunicaciones DispeDataLink . . . 19 2.3. OSI Layer 1. La capa física . . . . . . . . . . . . . . . . . . . . 19 2.4. OSI Layer 2. Enlace a datos . . . . . . . . . . . . . . . . . . . 21 2.5. OSI Layer 2. Definir características del enlace de datos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22 3. La aplicación . . . . . . . . . . . . . . . . . . . . . . . . . . 25 3.1. OSI Layer 7. La aplicación . . . . . . . . . . . . . . . . . . . 25 3.2.Tratamiento de eventos, recepción y errores . . . 26 3.3. System.IO.Ports ¡El Espacio! . . . . . . . . . . . . . . . . . 27 4. Programando . . . . . . . . . . . . . . . . . . . . . . . . . 29 4.1. Nuestra primera aplicación . . . . . . . . . . . . . . . . . . 29 5.Programando con técnica . . . . . . . . . . . . . . . 37 5.1. Programando con técnica . . . . . . . . . . . . . . . . . . . 37 6. ServidorComm. La clase . . . . . . . . . . . . . . . . 47 6.1. ServidorComm. Introducción a la clase . . . . . . . . 47 7. ServidorCom.dll. Nuestro laboratorio . . . . 53 7.1. ServidorCom.dll . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53 7.2. Formato tramas envío/recepción . . . . . . . . . . . . . 54 7.3. Petición/respuesta entrega de cápsula - 01 . . . . . . 55 7.4. Petición/respuesta introducción de cápsula - 02 . 56 7.5. Petición/respuesta estatus del sistema - 03 . . . . . 57 7.6. Códigos retorno y operación . . . . . . . . . . . . . . . . 58 7.7. Códigos Indicadores de Incidencias . . . . . . . . . . . . 59 7.8. Cálculo del CRC (pasos previos) . . . . . . . . . . . . . . 59 7.9. El simulador . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74 7.10. Mensajes en el EventLog . . . . . . . . . . . . . . . . . . . 82 7.11.Avisos de incidencias automatizados . . . . . . . . . . 83 7.12. Los archivos de configuración . . . . . . . . . . . . . . . 86 7.13. La gestión del puerto serie . . . . . . . . . . . . . . . . . 89 7.14.Acceso a una base de datos Access . . . . . . . . . . 97 8. El ClienteEPlus. La solución . . . . . . . . . . . . . 103 8.1. ClienteEPlus . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103 8.2. [F5] . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122 9. De beta 1 a beta 2 . . . . . . . . . . . . . . . . . . . . . 123 10. Conclusiones . . . . . . . . . . . . . . . . . . . . . . . . . 127 10.1. Conclusiones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127 10.2. La reflexión (pero en serio) . . . . . . . . . . . . . . . . . 127 10.2. Dedicatorias . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 128 10.2.Agradecimientos . . . . . . . . . . . . . . . . . . . . . . . . . . . 128 Introducción La idea de este “mini-libro”, nace a tren de dos iniciativas. La primera de ellas era escribir un artículo que hablara del puerto serie. Pocas veces desarrollamos temas relacionados con él, entre otras cosas porque son muy pocos, los programadores que se ven involucrados en diseños que trabajen las comunicaciones serie entre equipos y dispositivos. O sea que somos una minoría. Desconozco el interés que pueda representar para la audiencia en general, sin embargo técnicamente me resulta insuficiente escribir un pequeño artículo de cinco o seis páginas, repasando y dando ejemplos genéricos e impersonales hablando de las excelencias de la nueva versión del entorno. De ahí y pensando en colaborar con “Desarrolla con MSDN” y su aplicación de “Vídeo”, pude reconciliar las dos necesidades: desarrollar un ejemplo práctico de aplicación que interactúe con un dispositivo electro-mecánico ficticio unido al dispensador de vídeo, usando una comunicación serie con la electrónica que lo controla. Por cierto, os animo a descargaros y aprender de la aplicación MSDN Vídeo en www.desarrollaconmsdn.com, uno de los mejores recursos para aprender a desenvolverse dentro de la plataforma .NET, entorno a una impecable aplicación práctica y en castellano. página Soy consciente que realizar esta puesta en escena, no va a tener interés práctico para el gran grueso de desarrolladores, sin embargo quiero llamar su atención a un mundo desconocido para la mayoría de ellos. Los acostumbrados a las aplicaciones típicas de gestión con bases de datos, conoceréis en los siguientes capítulos aspectos curiosos y desconocidos de estas nuestras “atípicas” aplicaciones de comunicaciones; será divertido hablar de tramas y sus formatos, hablar de envíos, recepciones o crear un simulador del mecanismo. Puede ser una oportunidad para intuir el lado más industrial de un sector tan amplio como el nuestro. Para los que ya estáis en el pastel, quiero animaros a encontrar entre líneas la suficiente información para introduciros en el uso Visual Studio 2005, sin duda y hasta la fecha, el mejor de los entornos de desarrollo rápido. 9 << Robot dispensador para MSDN Vídeo En nuestro oficio y englobando a la totalidad de desarrolladores, existen muchísimos niveles en la asimilación a los avances que estamos expuestos. Mientras os estoy hablando del nuevo Framework 2.0 y Visual Studio 2005, muchos de nosotros aun estamos trabajando con la antigua versión 6 del entorno. Sólo nosotros, los programadores, sabemos lo que significa un cambio en el versionado del lenguaje, no es de extrañar que cada vez que afrontamos una migración, padezcamos de “migraña”. En una mano tenemos el convencimiento de las grandes ventajas en utilizar el nuevo entorno, en la otra mano tenemos nuestra aplicación en la versión anterior, el corazón nos dice “¡salta!” y el cerebro nos aconseja “¡Espérate!”. Los que hemos pasado varias veces por esto, sabemos que determinadas versiones de producto marcan un camino que cuaja durante años, no por nada hemos hablado de la versión 6 de Visual Studio. A pesar de que no todos estarán de acuerdo, faltaría más, mi opinión es que con Visual Studio 2005 afrontamos y estamos frente a uno de los entorno de desarrollo de esta plataforma, que volverá a marcar un punto de referencia dentro de la comunidad de desarrolladores. Hablando de la asimilación de esos avances, me lleva a pensar en Visual Studio 2005, como una herramienta avanzada, robusta y madura, que cubre en su alcance una extensa diversidad de niveles, complejidades y ámbitos, ayudándonos a encontrar el camino mas fácil para llevar a cabo cualquier reto que se nos plantee en nuestro trabajo diario. Después de la argumentación anterior, estoy en disposición de explicaros, que todo el material que encontrareis a continuación, está escrito simulando o pensando en un manual de uso, como el que te entregan al comprar la máquina (Entregon+), no tardareis mucho en saber que se trata de nuestro robot dispensador de MSDN Vídeo. He puesto un gran esfuerzo en escribir los ejemplos y el código de una manera llana y sencilla, o sea, que no os preocupéis, pues somos muchos los que pertenecemos al grupo que necesita lanzar la ayuda cada vez que debe usar alguna de esas instrucciones complicadas y que sin complejos tira de beta de los 101 ejemplos cuando necesita entender como funciona alguna de las nuevas prestaciones que aporta la plataforma. En este punto me excuso, pues como digo, no he cuidado las expresiones o formas siguiendo las conocidas recomendaciones y buenas practicas de los manuales del “Buen Programador” o la programación eXtrema (¡qué miedo!). En todo momento he dado preferencia a un estilo de programación para los más sencillos, donde el objetivo primordial es conseguir que los algoritmos funcionen a buen ritmo, dejando de lado algunos purismos o conocimientos profundos. página 10 Robot dispensador para MSDN Vídeo >> A pesar de que nos cueste reconocerlo, hablando entre nosotros en confianza, a muchos de los que aún no hemos saltado a .NET, nos incomoda hablar de tipos, delegados, sobrecargas , polimorfismos, hilos de ejecución, invocaciones, herencias, abstracciones e incluso de clases. Es evidente que no estamos acostumbrados a ellas, la intención de estos laboratorios no es otra que ayudar a PERDER EL MIEDO, para que en un futuro muy corto, podamos saltar sobre la gran tabla .NET, sin complejos ni vergüenzas! Finalmente os pido licencia y benevolencia, por presentaros un trabajo escrito a horas intempestivas entre sábados, domingos y jueves hasta la una de la madrugada mientras intentaba evitar seguir esos concursos para cantantes que tan de moda están en televisión. Aclarar que nadie ha revisado concienzudamente ni el texto ni el código, está hecho con cariño pero sin encargo, lo que hay, es lo que veis. Que nadie espere ver un libro en la forma habitual, no pretende serlo, simplemente es un “desparrame” de borradores. Os pido disculpas, si a pesar de todo no me salvo de la quema y ¡termino siendo culpable!. página Pep, 11 capítulo 1 Entregon+ Descripción Entregon+ pertenece a la familia de los prestigiosos dispensadores del grupo Dispensotron, potentes robots dispensadores de encapsulados o carcasas que incorporan un mecanismo de lectores grabadores con etiquetas electrónicas que permiten construir un sistema flexible y versátil, controlando tanto el contenido como el destinatario y recepción del mismo. Elimina la necesidad de las aburridas secretarias encargadas del tedioso control manual de entradas/salidas de material, siendo un almacén inteligente, sin llegar a serlo. Fabricado en base a una potente aleación de aluminio/titanio, para paliar la acidez palmar de los usuarios de este tipo de contenedores, así como la incorporación de su bandeja de entrega antivandálica, hacen que Entregon+ sea uno de los dispensadores de películas de video o CD más potentes del mercado. Su sencillez es alarde de robustez, un simple sistema de coordenadas de eje horizontal con la movilidad del cabezal en su eje vertical, permiten una sorprendente simplicidad de estructura mecánica, tolerante a fallos y libre de mantenimiento. Su concepción permite dispensar encapsulados, tanto en cajeros automatizados como en mostrador, siendo de uso compartido y eliminando la inoperativa concepción de dispensadores exclusivos (cajero/dispensador). Además de optimizar el rendimiento, organización y coste por cápsula dispensada. Sus principales características son: Carcasa de aleación exclusiva Entregon+. Mecanizado de barras cabezal, niqueladas con auto engrase. Lector/grabador de etiquetas electrónicas incorporados en el cabezal. Juego de 1.000 (EE) preparadas para más de 1 millón de grabaciones. Localizador/dispensador de carcasas de alta velocidad, 3ª generación. Matriz de carcasas, con un máximo de 4m de largo por 2m de alto. Sistema de doble procesador (RISC), tolerante a fallo. Comunicaciones RS232/485. página • • • • • • • • 13 << Robot dispensador para MSDN Vídeo Antes de dar alimentación al robot debe haber completado todos los pasos y recomendaciones de instalación. Preparación: Desembale y desempaquete todos los elementos que componen el dispensador, según le indican las instrucciones detalladas en el lateral de las cajas. Una vez localizadas y ordenadas por orden ascendente siga a su ensamblaje siguiendo los pasos detallados en el primer apartado. Ensamblado Matriz 1. Preparar los soportes de manera que los agüeros mecanizados queden fijados a la pared con los tornillos de disparo, preparados para soportar un mínimo de 50Kg. 2. Utilizando los pernos 50, 51, 52 y 53, fijar las dos barras de manera que observemos una paralela, visualizándolo desde al menos dos metros de distancia. 3. Fijar las barras laterales a la guía porta cabezales. 4. Asegurar el anclaje que fija el usillo, no olvide introducir los tornillos 23 y 24 de 12mm, apretándolos con una presión de torque mínima de 12nw. 5. Realice la operación de test antes de integrarlo en sistema alguno (siga las instrucciones del apartado de puesta en funcionamiento). MATRIZ gráfico 1.1 página 14 Robot dispensador para MSDN Vídeo >> Puesta en servicio Prueba 1. Conectar el cable de alimentación a la toma de 220v. 2. Verificar que la luz “PowerLED” de color verde, está encendida (consultar el apéndice “Resolución de problemas” en caso contrario). 3. Observar la intermitencia del indicador naranja de ignición, durante un tiempo aproximado de 30 segundos. 4. Esperar a que el indicador de “Ready” quede encendido. 5. Pulsar el botón “Test”. En la pantalla LCD observará lo siguiente: • • • • • • Test RISC processors. Performing Memory test. Align horizontal arm. Align vertical head. Dual Processing RTC system Startup. Perform seeker test. En este punto observaremos como el cabezal y el usillo recoge cápsulas se empiezan a desplazar a lo largo de los ejes (X, Y) iniciando a su velocidad mínima (una localización de cápsula por minuto) y finalizando a su velocidad máxima (10 localizaciones minuto). Esta prueba tiene una duración de unos 10 minutos, al finalizar deberemos visualizar los resultados en la pantalla LCD, Test passed (80 seeks performet without errors). página Para otros mensajes consulte el apéndice de “Resolución de problemas”. 15 capítulo 2 El puerto serie El puerto serie Características El dispensador Entregon+, incorpora dos UART 16C450 (Universal Asincronous Receiver Transmiter) de Texas. Compartidas por los dos procesadores RISC. Su avanzado diseño es tolerante a fallos, en cuyo caso, el primer procesador disponible conmuta las líneas de entrada/salida del conector serie RS232/RS485 a unos circuitos que convierten directamente las tensiones +/- 9/12V a lógica TTL, permitiendo que el sistema continue trabajando sin interrumpir sus comunicaciones y lanzando una alerta al sistema central, avisando de la anomalía en uno de los controladores. La tarjeta de comunicaciones serie es intercambiable en caliente (HotSwap) de manera que el responsable de mantenimiento podrá sustituir dicha tarjeta sin tener que detener o reiniciar el sistema. Pida su pieza de recambio PN=PS23MSDNVideo/3. PATILLAS DEL CONECTOR SERIE página gráfico 2.1 17 << Robot dispensador para MSDN Vídeo Pin Entrada/Salida 1 Entrada 2 Entrada 3 Salida 4 Salida 5 6 Entrada 7 Salida 8 Entrada 9 Entrada Nombre DCD RD SD DTR GND DSR RTS CTS RI Descripción Detección de Portadora Recepción Transmisión Terminal Datos Apunto Tierra Datos Apunto Solicitud de Envió Listo para enviar Indicador de Llamada tabla 2.1 En inglés Data Carrier Detect Receive Data Send Data Data Terminal Ready Ground Data Set Ready Request to Send Clear to Send Ring Indicator Patillas del conector serie En el desarrollo, y a efectos de prueba, deberemos disponer de un cable Test para efectuar la emulación de acceso de nuestra aplicación al dispensador, o bien podremos usar los dispensadores conectados al servidor de MSDN Vídeo, usando los servicios construidos a tal propósito. Las comunicaciones serie, cubren la necesidad de intercambiar información entre dos dispositivos (de tú a tú), a baja velocidad, con líneas de transmisión y recepción diferenciadas, en distancias de hasta un máximo de 15 metros y sin necesidad de implementar protocolo, si bien, como ya hemos precisado, es necesario definir los parámetros de la misma en términos de velocidad, paridad, bits de datos, bits de parada... En el gráfico 1 vemos cómo enviamos un byte: ESQUEMA DE SERIALIZACIÓN DE UN BYTE gráfico 2.2 página 18 Robot dispensador para MSDN Vídeo >> Un rápido análisis de la figura anterior nos clarifica los siguientes elementos: Pensando en la unión de un transmisor y un receptor, el transmisor dispone de una salida de datos SD, por la que entrega un voltaje comprendido entre -3 y -15 Vcc, para la señal de marca (Mark) y un voltaje entre +3 y +15 Vcc para la señal de espacio (Space). El proceso se inicia con una transición de reposo a espacio, el receptor espera el bit de inicio (star), después del cual inicia la des-seriacion de datos, marcada en intervalos exactos definidos por la velocidad fijada en la operación de apertura del puerto. Una vez recibidos los 8 bits, comprueba que la paridad sea correcta y espera un periodo de paro correspondiente al definido en bits de parada, para continuar con la siguiente entrada de datos. El byte recibido se almacena en un área tampón (búfer) del controlador de comunicaciones que dispara una interrupción a la CPU para que ésta recoja el dato antes de que éste sea sobrescrito por la próxima recepción. Llevar a cabo una comunicación serie nos obliga a concentrarnos en: a) Características del enlace a datos. b) Envío/recepción asíncrona. c) Tratamiento de eventos de recepción y errores. Protocolo de comunicaciones DispeDataLink Descripción Entregon+ dispone del protocolo de comunicaciones DDL (DispeDataLink) exclusivo de este dispensador. Este protocolo le permite realizar las principales funciones de posicionamiento, utilizando una sencilla comunicación de intercambio de tramas. Por lo que incluso podemos hacerlo trabajar mandándole órdenes a través del sencillo Hyperterminal. Como cualquier otro protocolo de comunicaciones, DDL define su estructura OSI siguiendo el esquema del gráfico 2.3. OSI Layer 1. La capa física Laboratorio 1 Para llevar a cabo este laboratorio deberá disponer de un conector hembra de 9 contactos (ver gráfico 2.1), así como de un soldador con estaño y diversos hilos de cobre para realizar los puentes que se detallan en el gráfico 2.4. página En caso de no disponer de estos medios diríjase a una tienda especializada de electrónica y pida este servicio. 19 << Robot dispensador para MSDN Vídeo ISO/OSI (OPEN SYSTEMS INTERCONNECT) gráfico 2.3 ESQUEMA DEL CABLE DE BUCLES gráfico 2.4 Corte un hilo de cobre de aproximadamente 4 cm, quite el recubrimiento plástico de los extremos y continue soldándolo en el terminal número 2, el otro extremo del hilo deberá soldarlo en terminal número 3. (en argot técnico “hacer un puente”). A continuación efectue los dos puentes restantes soldando los terminales 4 con 6 y 7 con 8, según se indica en el dibujo anterior. página 20 Robot dispensador para MSDN Vídeo >> No olvide colocar el conector de 9 contactos dentro de sus tapas originales, ello contribuirá a una fácil inserción y extracción del conector, así como una correcta manipulación. OSI Layer 2. Enlace a datos En la velocidad definimos a qué frecuencia encadenamos los bits, habitualmente a 9600, 19200 y 38400. La velocidad es indicativa de bits por segundo (bps), por lo tanto a 19200bps, en un segundo podremos enviar unos 1,7Kb (necesitamos un espacio de 11 bits para mandar 7 u 8 bits de datos). Observaremos que la velocidad se dobla en cada salto, ello se debe a la naturaleza del controlador de comunicaciones que usa como patrón un cristal de cuarzo que va dividiendo por dos la frecuencia de su reloj para cada salto de velocidad. El primer nivel en la detección de errores está cubierto con el control de paridad; el transmisor efectúa una operación de apareamiento de bits dando como bit de paridad que cuadre con la seleccionada1. Por ejemplo, si hemos seleccionado paridad par, el bit de paridad se ajustará para que coincidan por pares la cantidad de ceros y de unos. Paridad par: Datos - 0011 011, Bit de Paridad = 0 Datos - 1011 011, Bit de Paridad = 1 Este bit de paridad se utilizará para verificar la correcta recepción de los datos por el receptor, siendo “dato bueno” si la paridad es coincidente. La paridad descarta algunos errores producidos por el ruido en la transmisión de datos, pero no es un sistema 100% efectivo, por lo que más adelante hablaremos de la aplicación de otros sistemas de detección y corrección de errores, tales como el CRC o verificación de redundancia cíclica. Se puede seleccionar entre paridad “par”, “impar” o simplemente “sin”. Los bits de datos, son la longitud que conformara el dato tanto para su transmisión como para su recepción y está comprendida habitualmente entre 7 u 8 bits. (con 7 bits dispondremos de la primera tabla de 127 caracteres ASCII y con 8 el juego completo de 255). Finalmente, deberemos indicar al puerto serie la cantidad de tiempo de parada al final de cada transmisión, siendo sus valores los comprendidos entre 1, 1,5 ó 2 bits. El control de paridad se efectúa a nivel de controlador y de una manera automática, así pues no va a ser necesario efectuar operación alguna desde nuestra aplicación. página 1 21 << Robot dispensador para MSDN Vídeo A modo de comentario, también deberemos saber que existen un conjunto de caracteres de control que coinciden con las primeras posiciones de la tabla ASCII; éstos eran muy útiles en entornos de terminal. Veremos más de ellas en al apéndice. OSI Layer 2. Definir características del enlace de datos Descripción En ayuda a los integradores de este sistema, vamos a definir la capa de enlace de datos. Este nivel se caracteriza por llevar a cabo el intercambio de tramas entre dos o más dispositivos usando una comunicación tú a tú o bien de enlace anfitrión. Una vez iniciado el sistema, el Entregon+ se autoinicializa a las características definidas en los microinterruptores accesibles desde la parte inferior del ingenio (gráfico 2.5). Para establecer cualquier comunicación serie, deberemos antes definir las características de la misma en término de los siguientes parámetros: velocidad, paridad, bits de datos y paridad. Entregon+, trabaja de predeterminadamente a la velocidad definida en los conmutadores, sin paridad, ocho bits de datos y un bit de parada (vvvv,N,8,1). MICROINTERRUPTORES SELECTORES DE VELOCIDAD gráfico 2.5 La aplicación cliente, puede utilizar las librerías de comunicaciones suministradas con el Entregon+, desarrolladas para su uso en sistemas operativos de Microsoft que soporten de, .NET Framework 2.0. El siguiente laboratorio explicará cómo efectuar una pequeña aplicación de enlace usando el Visual Studio 2005 y su lenguaje Visual Basic 2005. Conectáremos el Entregon+ utilizando un cable serie cruzado (ver esquema en el “Apéndice A”), al conector macho de 9 pins de nuestro servidor (elemento 10), al conector hembra de nuestro ingenio, según observamos en el gráfico 2.6 y la tabla 2.2. página 22 Robot dispensador para MSDN Vídeo >> CONEXIÓN A EL SERVIDOR gráfico 2.6 1 2 3 4 5 6 Componente Elemento Componente Conector del cable de alimentaBotón/indicador LED de ID de 7 ción la unidad Indicador LED de fuente de ali8 Conector de vídeo mentación Compartimiento para fuente de alimentación redundante de cone9 Conector de puerto paralelo xión en caliente (opcional) Conector Ethernet RJ-45 10 Conector de puerto serie Pestillos extraíbles del conector 11 Conector del teclado SCSI Conectores del puerto USB (2) 12 Conector del ratón tabla 2.2 Componentes del panel posterior página Elemento 23 capítulo 3 La aplicación OSI Layer 7. La aplicación Una vez establecidos los parámetros de comunicaciones y antes de intercambiar información alguna, será necesario abrir el puerto. Esta operación de apertura es exclusiva, pues una vez efectuada, la aplicación toma el control del puerto de comunicaciones y, a diferencia de otros recursos del sistema, ninguna otra aplicación podrá utilizarlo hasta que nuestra aplicación lo haya liberado. El envío de datos es relativamente sencillo; anteriormente hablamos de envío de un byte, sin embargo, en una comunicación entre dispositivos, hablaremos de tramas. Las tramas son conjuntos de bytes que conforman bloques, que a su vez conforman paquetes de información. Nuestra dificultad “asíncrona” se produce al efectuar una transmisión de datos, vinculada a una respuesta. Efectivamente cuando nosotros solicitamos información a un dispositivo, esperamos una respuesta, he aquí donde se complica nuestra tarea. Deberemos contemplar diversas situaciones inherentes a este tipo de comunicaciones, tales como: ¿El otro dispositivo recibió la petición?, ¿He recibido algún dato, en respuesta?, ¿Porque no contesta?, ¿Si no contesta cuando vuelvo a preguntar?, ¿Los datos recibidos están libres de errores?... Como veréis, una simple pregunta/respuesta, puede llenarse de interrogantes, cuando los datos no llegan... o llegan de manera incorrecta. página ¿Alguien desconecto el cable?, ¿Estará averiado el Entregon+?, ¿Se fue la Luz? 25 << Robot dispensador para MSDN Vídeo En ese sentido, no es cuestión de ver las cosas desde el lado complicado sino todo lo contrario, se trata de conocer el medio y su problemática para poder construir una aplicación robusta y eficiente. Sólo desde esta perspectiva conseguiremos enfocar nuestra aplicación de una manera adecuada. Tratamiento de eventos, recepción y errores Sería una equivocación plantear nuestra aplicación sin la ayuda de eventos y a base de bucles en espera de caracteres. El tratamiento de los eventos de recepción y errores va a permitir gestionar de una manera desatendida las respuestas de nuestras peticiones. El nuevo espacio de nombres System.IO.Port.SerialPort dispone de los eventos ReceivedEvent y ErrorEvent que nos permitirá construir una sencilla pero sólida estructura, que gestione la entrada y salida de tramas. Veamos el siguiente ejemplo: EJEMPLO gráfico 3.1 página 26 Robot dispensador para MSDN Vídeo >> System.IO.Ports ¡El Espacio! Detalle del espacio de nombres: Clases SerialErrorEventArgs SerialPinChangedEventArgs SerialReceivedEventArgs SerialReceivedEventArgs Descripción Especifica los argumentos enviados al evento ErrorEvent. Especifica los argumentos enviados al evento PinChangedEvent. Representa el recurso del puerto serie. Especifica los argumentos enviados al evento ReceivedEvent. Enumeraciones Descripción Handshake Especifica el protocolo de control usado para establecer al objecto SerialPort en las comunicaciones con el puerto serie. Parity SerialErrors SerialPinChanges SerialReceived StopBit Especifica el tipo de paridad utilizada en el objeto SerialPort. Especifica los errores que pueden ocurrir en el objecto SerialPort. Especifica cambios de señal detectados en el objecto SerialPort. Especifica los caracteres que se han recibido en el puerto serie. Especifica los bits de parada usados en el objecto SerialPort. Delegados Descripción Representa el método que manipula el evento error del SerialErrorEventHandler SerialPort. Representa el método que manipula el cambio de señales SerialPinChangedEventHandler de SerialPort. Representa el método que manipula el ReceivedEvent SerialReceivedEventHandler del SerialPort. tabla 3.1 Información del Longhorn SDK Beta 1 página 1 Espacio de nombres para la utilización de los puertos serie. Framework 2.01 27 capítulo 4 Programando Nuestra primera aplicación Bien, una vez adquiridos los conocimientos básicos teóricos, llega la hora de ponerlos en práctica. Nos concentraremos en la clase SerialPort. Esta clase, del espacio de nombres System.IO.Ports, aglutina todas las funciones necesarias para manejar el puerto de comunicaciones. Una lección bien aprendida para los que trabajamos en comunicaciones, es realizar una simple operación de envío/recepción antes de dotar de cualquier otra complejidad a nuestro proyecto. La función de este laboratorio, es la de recorrer y verificar el correcto funcionamiento de las tres capas del modelo OSI, implicadas en nuestra aplicación. Objetivo Una vez completado este laboratorio seremos capaces de: • • • • • Abrir un puerto serie de nuestro servidor. Configurar y diferenciar los diferentes puertos de nuestra máquina. Escribir (enviar) información al puerto. Leer el búfer de caracteres recibidos. Recuperar los caracteres del búfer de recepción. Preparación y requerimientos página Está claro que para poder efectuar este laboratorio, deberemos disponer de un equipo con un mínimo de un puerto serie y el conector detallado en el gráfico 2.3 (capítulo 2, “El puerto serie”). 29 << Robot dispensador para MSDN Vídeo Abrir una instancia de Visual Studio 2005 y empezar con un simple proyecto de Windows Forms, añadiendo una etiqueta y un botón (label1, button1), según sigue en la figura 4.1: figura 4.1 En Visual Studio 2005, tenemos el código del diseñador separado del código del formulario, por lo tanto nos interesa editar el código del formulario (Form1.vb)… [F7] para los avanzados. A continuación declararemos nuestro objeto de acceso al puerto serie Puerto como clase de System.IO.Ports.SerialPort. Para darle un aspecto más organizado, podemos incluir tres regiones. Es de suponer que la mayoría de vosotros sabéis que directiva #Region, nos ayuda a agrupar el código (ver el fuente 4.1). Su aspecto sería el de la figura 4.2. página 30 Robot dispensador para MSDN Vídeo >> #Region "Carga / Descarga del Formulario" #End Region . . #Region "Envio / Recepcion de tramas" . etc. Fuente 4.1 figura 4.2 Lo primero es ocuparnos de las tareas a realizar en tiempo de carga/descarga del formulario (ver figura 4.3). Dos operaciones aparentemente simples: abrir el puerto en el momento de carga del formulario y, evidentemente, cerrarlo al descargar el formulario. página Con esto ya casi estamos a punto de conseguir enviar un dato, sólo nos falta la orden de envío o escritura, para ello asociaremos el evento click del button1 a la acción de escribir una trama. 31 << Robot dispensador para MSDN Vídeo figura 4.3 Al pulsar el button1 llamaremos a la función WriteLine de nuestro Puerto (ver figura 4.4). Bien, ¡ha llegado el momento!, pulsaremos [F5]. Una vez compilado, aparecerá cuadro de diálogo del formulario en ejecución… OPPSS… nuestro primer mensaje “COM1: does not exist”… (ver figura 4.5) ¿Como que does not exist!? figura 4.4 página 32 figura 4.5 Robot dispensador para MSDN Vídeo >> Verifiquemos las salidas serie de nuestro equipo. Como veis, olvidamos de verificar la asignación de nuestros puertos en el equipo. Vayamos al “administrador del equipo”/“administrador de dispositivos”, situémonos en el apartado de “Ports” y veamos… figura 4.6 Obviamente COM1 OpenSerialPort("COM3"). no existe, entonces deberemos cambiar nuestra instrucción de De nuevo pulsamos [F5]. Y de nuevo ¡OPPSS! figura 4.7 ¡El puerto está siendo utilizado por otra aplicación! página A simple vista puede parecer una tontería, pero obviar este tipo de detalles, a menudo nos acarrea un montón de quebraderos de cabeza. 33 << Robot dispensador para MSDN Vídeo Vamos probar con el COM4, ¡parece ser que todo va a ir bien! figura 4.8 Pero la ilusión se va a desvanecer tan rápido como pulsemos el Button1… pues, ¡no ocurre nada! Si aparentemente no ocurre nada, lo cierto es que han pasado un montón de cosas, pues realmente la cadena “¡Hola Mundo!” ha sido enviada a nuestro controlador serie que con una escrupulosa paciencia ha seriado todos sus bits convirtiéndolos en voltajes a la salida del conector del COM4. Eso será muy bonito en teoría, pero el pragmatismo me obliga a decir que si no lo veo, no lo creo. Enchufaremos el conector de pruebas, a nuestra salida serie asignada (en nuestro laboratorio, COM4), este conector actúa como bucle, así pues cualquier dato enviado será inmediatamente recibido. Deberemos también añadir un temporizador timer arrastrándolo de la caja de herramientas (toolbox) a nuestro formulario form1. En el Sub Form_Load, añadiremos dos líneas de inicialización del timer de manera que leamos: 'Ejecutar cuando se Carga el formulario Private Sub Form1_Load(ByVal sender As Object, ByVal e As . . . Try 'Abrir el puerto numero 3 Puerto = My.Computer.Ports.OpenSerialPort("COM4") 'Llamar constructor ... Timer1.Interval = 1000 Timer1.Enabled = True ... ... Fuente 4.2 página 34 Robot dispensador para MSDN Vídeo >> En la región de Auxiliares añadiremos el siguiente código: #Region "Rutinas Auxiliares" 'disparo de Tareas a realizar cada Segundo Private Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As ...system.EventArgs) Handles Timer1.Tick ' Label1.Text = Puerto.BytesToRead.ToString 'Leer nº bytes recibidos End Sub Fuente 4.3 Pulsemos de nuevo [F5], cliqueando el button1... Veamos ¿qué ocurre? figura 4.9 Increíble, ¡tengo 12 bytes para leer en el búffer!, realmente la cadena “¡Hola Mundo!" ha dado la vuelta… y ¡a la vista esta!... en menos de ochenta días. Bien, no lo demoremos más, vayamos a por ellos, modifiquemos nuestro código. Para poder leer los caracteres recibidos en el búfer, verificaremos a cada latido del timer si nuestro Puerto tiene bytes para leer. Puerto.BytesToRead, en cuyo caso procederemos a su recolección con la instrucción Puerto.ReadExisting, convirtiéndola en un string para poder pasar su valor al texto de la Label1 (ver fuente 4.4 y figura 4.10). ¡Conseguido!, pero… página Hemos mandado una trama por el puerto serie, su bucle nos ha reenviado la información, la hemos leído y visualizando en la etiqueta Label1. 35 << Robot dispensador para MSDN Vídeo #Region "Rutinas Auxiliares" 'disparo de Tareas a realizar cada Segundo Private Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.Ev ' 'verificaremos que tenemos bytes para leer If Puerto.BytesToRead > 0 Then Label1.Text = Puerto.ReadExisting.ToString 'Leer bytes recibidos End If End Sub Fuente 4.4 figura 4.10 Sin embargo, estamos muy lejos de una verdadera aplicación. Simplemente hemos conseguido el objetivo de nuestro primer laboratorio: ¡verificar el circuito de comunicaciones ISO/OSI a nivel de las tres capas ya es mucho!, pero no olvidemos que no estamos siguiendo un esquema de gestión de eventos. Espero pues que me acompañéis en el siguiente laboratorio. página 36 capítulo 5 Programando con técnica Programando con técnica El laboratorio anterior nos demuestra que somos capaces de hacerlo, pero en el siguiente vamos a demostrar que además somos capaces de hacerlo bien. Siguiendo el organigrama descrito en el gráfico 3.1 (capítulo 3, “La aplicación”), abordaremos el desarrollo desde un planteamiento más técnico. De hecho ninguna aplicación de comunicaciones que se precie, comprobará la recepción de datos usando un temporizador que verifique si hay caracteres para procesar; esto último podría verse como una técnica, pero está claro que no es demasiado técnico. En un medio donde lanzamos nuestras peticiones de comunicaciones a una nube, la cual nos devuelve asíncronamente el resultado o la respuesta y de una manera impredecible, se tercia utilizar los eventos de recepción. A pesar del miedo y desconfianza que a priori puedan suscitarnos, el hecho es que el uso de eventos nos permitirá construir una fiable y robusta aplicación de comunicaciones. El anterior laboratorio era cuidadoso en añadir al código regiones y comentarios ricos, que nos ayuden a su comprensión. Este laboratorio, más técnico, va a continuar con ellos pero se concentrará en la ejecución y la técnica, más que en la forma y descripción. Empezaremos de nuevo iniciando Visual Studio 2005, crearemos un nuevo proyecto de Windows Application, añadiendo tres etiquetas Labels, dos barras de progreso progressbar y un botón button (ver figura 5.1). página La primera etiqueta Label1, nos servirá para visualizar los caracteres recibidos, las dos barras de proceso juntamente con las etiquetas Label2 y Label3 nos indicarán la cantidad de bits transmitidos y recibidos. El botón Button1' simplemente para iniciar o detener la transmisión en cadena. 37 << Robot dispensador para MSDN Vídeo figura 5.1 Empezando con el segundo Lab El siguiente paso será añadir a nuestro proyecto una clase que gestione nuestras comunicaciones, por lo tanto, desde el explorador de soluciones, añadiremos un nuevo ítem Class. Cambiaremos el nombre de la clase Class1 a Puerto ... Public Class Puerto. Para hacerlo fácil, vamos a obviar el control y la gestión de excepciones, así como otras formalidades aplicadas por cualquier buen programador. Nuestra clase empezará definiendo tres variables que contendrán la trama recibida, así como dos enteros que contarán los bits enviados y recibidos: Public Class Puerto Public Recepcion As String 'Cadena con los caracteres recibidos Public enviados As Integer = 0 'Numero de bits enviados (11 bits por Byte) Public Recibidos As Integer = 0 'Numero de bits recibidos (11 bits por Byte) Fuente 5.1. Crearemos nuestro enlace receptor de los eventos del System.IO.Ports.SerialPort, además de declarar nuestro evento de Respuesta para avisar a nuestras instancias de que se ha producido una recepción: página 38 Robot dispensador para MSDN Vídeo Public Event Respuesta() WithEvents Serie As System.IO.Ports.SerialPort >> 'Evento disparo respuesta 'Eventos del SerialPort Fuente 5.2 New Terminaremos de completar nuestra clase con las subfunciones de Enviar, Recibir, y Finalize. Su aspecto será: ' 'Enviar una trama (el Chr(13) es el carácter que identifica el fin de trama) Public Sub Enviar(ByVal Trama As String) Serie.WriteLine(Trama + Chr(13)) 'La trama sera enviada al objeto serie enviados += Trama.Length * 11 'necesito 11 bits para mandar un byte End Sub ' 'Recibir una trama Public Sub Recibir(ByVal sender As Object, ByVal e As ... ... system.IO.Ports.SerialReceivedEventArgs) Handles Serie.ReceivedEvent Recepcion = Serie.ReadExisting.ToString 'Leer los caracteres recibidos Recibidos += Recepcion.Length * 11 'contabilizarlos para estadistica RaiseEvent Respuesta() 'Lanzar el evento de recepcion End Sub ' 'Abrir el puerto Public Sub New() 'constructor del objecto serie Serie = My.Computer.Ports.OpenSerialPort("COM4", 19200) End Sub ' 'Liberar el puerto Protected Overrides Sub Finalize() 'cerrar el objecto serie Serie.Close() MyBase.Finalize() End Sub End Class página Fuente 5.3 39 << Robot dispensador para MSDN Vídeo Destaquemos la importancia de la función Recibir en nuestra clase. ¿Veis cómo es la receptora de Serie.ReceivedEvent?, la aplicación que utilice nuestra clase podrá continuar su ejecución a pesar de tener una petición pendiente de responder, por lo tanto, estará utilizando el puerto serie de una manera desatendida. Nuestra clase recibirá el evento una vez se haya completado una recepción y a continuación lo notificara a sus instancias, disparándoles el evento respuesta… ¡Maravilloso! Sobre el resto sólo comentar, que en New creamos el objeto serie que nos dará acceso a todos sus métodos, eventos y propiedades, en finalize liberamos el puerto serie y que Enviar es tan simple como hacer escribir la trama al objeto Serie. Pasemos ahora a la funcionalidad en casa del cliente Class Form. He elegido un ejemplo que trabaje un par de conceptos muy importantes, los eventos (events) y los hilos (threats). Existen dos maneras de afrontar los cambios en el versionado de lenguajes: 1) A bofetones (sensación de torpe, siempre buscando y sin haber perdido nada). 2) Esperar que los demás se peguen los bofetones y te lo cuenten (sensación “seguro de ti mismo”, imagen limpia, siempre con el traje impecable). Como ya sabéis Visual Basic .NET, incorpora una serie de avances cualitativos, inherentes a los lenguajes orientados a objeto, además del mejorado manejo de eventos y entre otras mil, poder utilizar hilos para la ejecución; dicho esto me confieso un aterrizado miembro del grupo 1, y es un orgullo contribuir en la rápida adhesión a esta tecnología para los del grupo 2. A los acostumbrados a las anteriores versiones de Visual Basic, no nos termina de encajar del todo este modelo; antes de todo se ejecutaba sobre un plano, sin embargo con el nuevo Framework, podemos crear hilos hijos o llamar a otros hilos… Menuda potencia… pero congones qué leches con lo de los hilos... lo único que yo quiero es recibir la llamada de un evento y leer sus datos. Bien aquí tenemos la parte formal, según nuestro modelo de ejecución disponemos de un árbol de hilos del que cuelgan nuestros procesos, una peculiaridad de los formularios es que se ejecutan en un hilo, lo que no impide que a partir de nuestro hilo, creemos otros hilos de ejecución que cuelguen del nuestro “hilos hijos”, la interacción con ellos resulta ciertamente asequible. Sin embargo, las complicaciones llegan solas. Acabamos de crear una clase que recibe la llamada de un evento que se ejecuta en el espacio delegado de SerialReceivedEventHandler, o sea, de otro hilo. página 40 Robot dispensador para MSDN Vídeo >> La pregunta sería ¿y a qué viene todo esto? La respuesta sería: “illegal cross-threat operation, control accessed from a threat other than the threat it was created on” Resumiendo: ¡que no podemos tocar con los hilos de otra guitarra! Muchas veces me sorprendo a mí mismo intentando dar explicación a todo esto de la OOP y siempre termino igual, pensando en el ensamblador; al fin y al cabo es lo que el procesador entiende, una secuencia que recorre la memoria de arriba hasta abajo. Rápidamente me repongo, con ánimo y ayuda entiendo que efectivamente estas son excelencias que requieren que nuestra mentalidad se adapte a los tiempos, no por nada se inventaron los delegados. ¡Ya va siendo hora de que los usemos! La mayoría de nosotros tiene un vago concepto sobre los delegados y su aplicación. Creo que este es un buen ejemplo sobre todo para entender el modelo de ejecución de nuestras aplicaciones, ¡vamos pues a coger el hilo por sus extremos! En las declaraciones deberemos crear ahora una instancia al objeto puerto: Public Class Form1 ' Private Comunica As Puerto = New Puerto 'Instancia nuestra clase Puerto Private InicioFin As Boolean = False 'Encadenar las peticiones (si/no) Private DatosRecibidos As String 'Datos recibidos Fuente 5.4 Ahora el famoso delegado que nos servirá para el Invoke que permitirá al control recibir datos desde un threat diferente al que fue creado. ' 'delegado para manejar la recepción de datos desde un 'threat' externo Delegate Sub Refrescar() página Fuente 5.5 41 << Robot dispensador para MSDN Vídeo Construimos un temporizador con el que controlaremos la actualización de datos en pantalla una vez cada segundo. ' 'temporización de un segundo WithEvents segundos As System.Windows.Forms.Timer = New System.Windows.Forms.Timer Fuente 5.6 En tiempo de carga del formulario... ' 'Preparamos el entorno Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs ... 'dirección de ejecución para el evento 'Respuesta' una vez recibidos los datos AddHandler Comunica.Respuesta, AddressOf recibedatos segundos.Interval = 1000 'Fijar el intervalo de disparo del temporizador segundos.Enabled = True 'Temporizador en Marcha Me.ProgressBar1.Maximum = 19200 'Fijar el valor maximo del progress bar 1 Me.ProgressBar2.Maximum = 19200 'Fijar el valor maximo del progress bar 2 End Sub Fuente 5.7 Cuando nos pulsen el botón... ' 'Cuando nos pulsen el botón Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.Eve ... Comunica.Enviar(System.DateTime.Now) 'Enviamos la fecha/Hora InicioFin = Not InicioFin 'Memorizamos Inicio/Fin End Sub Fuente 5.8 página 42 Robot dispensador para MSDN Vídeo >> Cada segundo (tick del timer)... ' 'Cada Segundo efectuaremos un refresco de la información en pantalla Public Sub tiempo(ByVal sender As Object, ByVal e ...) Handles segundos.Tick 'Visualizar el estado en el texto del botón If InicioFin Then Button1.Text = "Fin Bucle" Else Button1.Text = "Inicio Bucle" 'Actualizar los valores actuales / estadísticas por segundo ProgressBar1.Value = Comunica.enviados Label2.Text = ProgressBar1.Value.ToString ProgressBar2.Value = Comunica.Recibidos Label3.Text = ProgressBar2.Value.ToString Comunica.enviados = 0 Comunica.Recibidos = 0 End Sub Fuente 5.8 Esta es la función que recibe el disparo del evento de trama recibida. Aquí es donde utilizando el metodo invoke, llamamos al delegado refrescar que apunta a la función actualizar_datos, la cual simplemente mueve el valor de DatosRecibidos a su propiedad de texto. ' 'Disparo de la instancia del 'Puerto' llamada Comunica, cuando recibe una trama Private Sub recibedatos() DatosRecibidos = Comunica.Recepcion 'leer los datos recibidos ' 'invocar al delegado que rellene con los datosRecibidos el texto del label1 Me.Label1.Invoke(New Refrescar(AddressOf actualizar_Datos)) ' 'enviar otra trama, Si la trama contiene el caracter CR y queremos bucle. If DatosRecibidos.Contains(Chr(13)) And InicioFin Then DatosRecibidos = "" 'Limpiar los datos recibidos Comunica.Enviar(System.DateTime.Now) End If End Sub ' 'delegado para leer y asignar los datos de otro hilo Public Sub actualizar_Datos() Me.Label1.Text = DatosRecibidos End Sub End Class página Fuente 5.9 43 << Robot dispensador para MSDN Vídeo Si intentáramos asignar directamente el valor de DatosRecibidos en la función recibeDatos. Me.Label1.Text = DatosRecibidos, seríamos obsequiados con la original excepción: figura 5.2 Uff... no sé si atreverme a pulsar [F5]... ¡Bien! figura 5.3 Ahora pulsemos el botón de “Inicio Bucle”… ¡Bien! figura 5.4 página 44 Robot dispensador para MSDN Vídeo >> ¿Que es lo que está sucediendo? Después de tanto esfuerzo hemos conseguido que pulsando el botón “Inicio Bucle”, mandemos una trama que contiene la fecha y la hora a la salida del puerto serie, el bucle en el conector del mismo hace que se reciban instantáneamente los mismos datos enviados, pero con la peculiaridad de que el retorno para la recepción se realiza disparando un evento desde el delegado receivedevent, utilizando la técnica de invoke para poder rellenar el texto del Label1 desde un hilo diferente al que fue creado. Aunque a primera vista complejo, si lo comparamos con las técnicas de overlap usando llamadas a las API32, esto es ¡simplemente genial! Con cuatro líneas somos capaces de recibir llamadas desatendidas desde el puerto serie. Para quien le parezca poco, explicaré que la anterior versión, Visual Studio 2003, necesitaba entorno a las mil líneas de código para hacer algo similar. Sí, he dicho bien… MIL, 1.070 para ser exactos. Seguro que alguno de los participantes, estará echando remiendos… los progressbar sumados dan 29.249 bits… ¡No funciona!, hemos abierto el puerto a 19.200bps, entonces ¡no cuadra! Les explicaré que el puerto serie puede trabajar en dos modalidades, en full duplex o half duplex. Es probable que el bucle en la salida produzca un efecto reflejo de sobrescritura, que traducido significa el envío de una trama antes de recibir respuesta completa, por lo que antes de finalizar la recepción ya estoy enviando otra, argumentando esta razón sería capaz de mandar al filo de 19.200 bits mientras recibo otros tantos, pues el modo full duplex me da la posibilidad de enviar y recibir simultáneamente. Este efecto no ocurrirá si utilizamos un dispositivo real que conteste a nuestras peticiones. página Es la hora del descanso, antes de entrar en trance con nuestras librerías de acceso a nuestro Entregon+ y el programa Dispensador de Vídeo, tómense un merecido café. 45 capítulo 6 ServidorComm La Clase ServidorComm. Introducción a la clase La primera tarea del siguiente laboratorio, se centrará en efectuar un análisis preliminar de nuestras necesidades, a continuación lo plasmaremos gráficamente y finalmente, siguiendo dicho esquema, abordaremos la programación módulo a módulo. Para entender mejor este proyecto es necesario aclarar ciertos detalles: Durante años hemos observado que todos nuestros equipos incorporaban unos conectores de 9 contactos (antaño 25), que usamos para enchufar los módems que nos permitían conectarnos a Internet. Esos conectores escondían tras de sí las comunicaciones, esa herencia se remonta a los primeros equipos de informática personal, por lo tanto, son anteriores a la aparición de las redes locales y su gran importancia se debe a que son los responsables directos del intercambio de datos con el exterior. Además, durante años, los puertos serie han sido el único medio disponible para conectar cientos de pequeños dispositivos. No olvidemos: lectores de códigos de barras, impresoras, ratones, módems, tabletas digitadoras, mini controladores, automatismos, programadores de chips..., etc. Gracias a la rápida evolución electrónica, sobre los pares trenzados de ethernet y las líneas digitales, hoy en día disfrutamos de conexiones entre redes locales y remotas a unas velocidades de vértigo si las comparamos con los 300 baudios de los primeros modems. página Os preguntareis ¿a qué viene todo esto?, la respuesta es simple: la historia de los entornos de programación demuestran la dificultad en realizar proyectos que usarán estos puertos serie; para explicarlo mejor os diré que era una práctica habitual para los antiguos programadores en DOS, usar directamente técnicas de captura de los vectores de interrupción (int3/int4) del chip controlador para hacerse con la UART y ¡cier- 47 << Robot dispensador para MSDN Vídeo tamente complicadísimo! Versiones posteriores de lenguajes trataban el acceso al puerto de comunicaciones como si de un archivo se tratara. open "COM1" for radom as # 1 con su input # 1 y su print # 1, pero lo cierto es que no eran formas demasiado eficientes. Más tarde, las primeras versiones de entornos visuales salieron desprovistas de acceso al puerto serie, nos alivió solucionarlo usando las instrucciones out/in atacando directamente a los controladores serie. Más tarde, con la aparición de Win32, tuvimos la posibilidad de personalizar nuestro acceso a los puertos utilizando llamadas a las API, su Kernel32.dll y la odiosa estructura Win32Com.DCB. La llegada de los OCX, nos regala un control, el MSCOMM32 que, por primera vez, da un cierto respiro para poder acceder a los puertos, ya que podemos desarrollar aplicaciones suficientemente fiables con una sencillez justa. Finalmente, la cosa se vuelve a complicar con la llegada de .NET, las primeras betas permiten explotar los puertos con el uso de streams, pero su versión final nos deja a dos velas de nuevo y sin acceso a los puertos serie; una vez más la única alternativa son las antiguas llamadas a las API. ¡Eso en plena era .NET! Efectivamente, este es el motivo de tanto alboroto. ¡Visual Studio 2005 incorpora un espacio de nombres para el acceso a los puertos serie!, ni más ni menos, una excelente noticia para todos aquellos que necesitan continuar usando dispositivos con la herencia del puerto serie. Después de la explicación anterior, deberíamos quedarnos descansados, presentando y describiendo la funcionalidad básica y prestaciones de este espacio de nombres. Sin embargo, vamos a tratar este caso como un auténtico reto, vamos a desarrollar una aplicación al completo en torno al uso de los puertos serie. La idea es ayudar a dar el salto y todos conocemos las dificultades asociadas al versionado en los entornos de programación. En plena ebullición de Visual Studio 2005, muchos desarrolladores aún no han cambiado a Visual Studio 2003. A pesar de que el salto de 2003 a 2005 no es tan espectacular como el de Visual Studio 6 a 2003, vale la pena darnos la oportunidad de iniciar nuestras futuras aplicaciones con laboratorios que abarquen diferentes aspectos en el desarrollo, más que concretar en profundidad los aspectos mas relevantes de la implementación de nuevas características. Creo sinceramente que este sencillo ejemplo debe servir para perder el miedo a este increíble entorno de programación, sin duda, el mejor de todos los tiempos. Empezaremos escribiendo un conjunto de clases que además de cubrir el acceso al puerto serie, añade clases con operaciones tan interesantes como el acceso a bases de datos Access, archivos XML, apuntes al registro de eventos o envío automatizado de correos electrónicos. Cabe destacar una interesante introducción al uso de enumeraciones y estructuras; no es entonces de extrañar que nuestra librería ServidorComm tenga mucho de “Servidor” y algo de “Comm”. página 48 Robot dispensador para MSDN Vídeo >> Nuestro proyecto…(cuaderno de carga) Descripción Desarrollar una clase que enlace la aplicación del dispensador de vídeo y nuestro Entregon+ para atender a las peticiones de alquiler, venta y devolución, intercambiando tramas serie con el formato descrito en el protocolo DispeDataLink, utilizaremos el puerto serie local, controlaremos y supervisaremos los mensajes de incidencias, así como el estado de nuestro dispensador en tiempo real. Detalles Nuestra clase deberá estar compilada en un proyecto Class Library, para poder usar su funcionalidad desde dispensador, simplemente añadiendo una referencia a la misma. Para poder desarrollar este laboratorio, es imprescindible disponer de un equipo con un puerto serie y el conector descrito en el gráfico 2.3 (capítulo 2, “El puerto serie”). Es aconsejable utilizar Windows 2003 Server o Windows XP Profesional con SP2 y Visual Studio 2005 Beta 2. Después de muchos años de trabajo, continuo apasionándome cada vez que afrontamos la fase de definición del proyecto. Es emocionante sentir nuestra imaginación en un torrente desbordado de ideas que fluyen descontroladamente, dando forma y sentido a un montón de zambullidos neuronales, que poco a poco van componiendo ese fantástico puzzle al que terminamos llamando solución. Una primera imagen de nuestro trabajo, es diferenciar sus dos elementos esenciales: el mecanismo Entregon+ con su equipo controlador y nuestra aplicación. A pesar de las grandes habilidades, tanto mecánicas como de control de nuestro Entregon+, debemos entender la importancia de esta simbiosis ya que dichas versatilidades sólo afloraran con la ayuda de unas librerías que sean capaces de dotar al conjunto de la funcionalidad suficiente, para exponer toda la gestión del conjunto de una manera simple y eficiente. Dediquemos unos minutos a recorrer visualmente la representación gráfica de nuestro objetivo (ver gráfico 6.1). página A primera vista puede parecer que no olvidamos nada y como dibujito no está nada mal, sin embargo, un análisis más detallado nos conducirá a descubrir que debemos ser mucho más rigurosos y efectuar una descripción más concreta. 49 << Robot dispensador para MSDN Vídeo ENLACE DE COMUNICACIONES–DISPENSADOR/ENTREGON+ gráfico 6.1 Esta descripción detallada debe conducirnos directamente a la definición de las clases necesarias que conformaran nuestras librerías. Me gustaría insistir en el hecho de ser minuciosamente escrupulosos al diseñar una librería en la que se fundamentara el éxito de las aplicaciones que la exploten. No podemos dejar nada al azar, debemos ser capaces de construir un conjunto de funcionalidades “mantenimiento cero”. página 50 Robot dispensador para MSDN Vídeo >> Nuestra librería deberá contener una clase capaz de reportar todos los mensajes al eventlog del sistema, deberá incluir una clase tenga en cuenta las estructuras de intercambio de datos, otra que efectúe la simulación del ingenio, también diseñaremos una clase que encapsule las notificaciones de incidencias por correo electrónico y, ¡como no!, naturalmente deberá gestionar el puerto serie… Por lo pronto, un montón de cosas. Un último detalle: no estaría mal que además guardáramos todos los valores de configuración y trabajo dentro del app.config de cada aplicación, que como ya sabéis es nuevo en Visual Studio 2005, por lo que podríamos sugerir agrupar bajo los siguientes módulos: figura 6.1 Todo, bajo el nombre de la librería ServidorComm. Antes de adentrarnos en tan ardua tarea, demos una ojeada a las diferentes maneras de conectar nuestro Entregon+ a una aplicación de dispensador, así como para hacer un repaso a nuestro uso del protocolo DispeDataLink. El protocolo DispeDataLink, está fundamentado en el intercambio de tramas usando el juego de caracteres ASCII. Está diseñado en base a una comunicación enlace anfitrión. Este tipo de enlaces se caracteriza por el intercambio de tramas bajo demanda del anfitrión (Host o Master), el resto de componentes de la red se consideran esclavos y su única misión es contestar a las peticiones del Maestro o Anfitrión, no toman nunca la iniciativa en intercambio alguno. La topología de estas redes acostumbra a no sobrepasar los 32 esclavos y ello es debido a las limitaciones de impedancia de los dispositivos electrónicos. Normalmente RS485. En la actualidad existen numerosos conversores de señal RS232 a RS485. página En nuestro caso, Entregon+ y el protocolo son esclavos a nivel de aplicación. Por lo que podemos enlazar un único dispensador de vídeo a varios Entregon+ (ver gráfico 6.2). 51 << Robot dispensador para MSDN Vídeo gráfico 6.2 También utilizando ciertas técnicas avanzadas, podríamos direccionarlos a través de un servidor, mezclando diferentes redes. Quizás estas técnicas puedan ser objeto de desarrollo utilizando las librerías de remoting, ¡si es que después de WCF (antes Indigo) no se rompe éste y puedo continuar escribiendo el libro! gráfico 6.3 página 52 capítulo 7 ServidorCom.dll Nuestro laboratorio ServidorCom.dll Por fin nos adentraremos en los placeres de la codificación, no sin antes recordar que necesitaremos tener muy cerca la descripción de todos los formatos y códigos que intervienen en nuestros procesos. Cuanto más detallados sean nuestros modelos de formato y su descripción, más fácil será nuestra labor posterior. Diferentes asociaciones de usuarios advierten que leer código “a pelo” puede producir somnolencia y aburrimientos de diversas índoles. Queda terminantemente prohibido leer este material mientras se conduce o utiliza maquinaria pesada. En caso de sufrir cualquiera de los síntomas descritos, no visite al médico ni tampoco al farmacéutico, pues está considerado como un “cuadro sintomático habitual”. Si por el contrario la lectura del código descrito a continuación le mantiene despierto, le impide conciliar el sueño, o le provoca ganas de POgramar, resérvese urgentemente una sesión de reflexoterapia con un especialista que sea de su confianza. página nota Nota del equipo de desarrollo: ésta es la versión lite shared opensource freeware en la que el equipo técnico de desarrollo sólo ha implementado tres operaciones básicas. Para la versión completa full equipment diríjase al departamento comercial, o bien copie/modifique y distribúyanos libremente su aportación a: [email protected]. advertencia A continuación vamos a detallar el formato de las tramas que intercambiaran el Entregon+ y nuestro sistema, es importante entender su estructura, pues será hilo conductor que sustentara nuestra aplicación.. “son el corazón de nuestro proyecto”. 53 << Robot dispensador para MSDN Vídeo Formato tramas envío/recepción El protocolo DispeDataLink, está fundamentado en el intercambio de tramas usando el juego de caracters ASCII. gráfico 7.1 página 54 Robot dispensador para MSDN Vídeo >> Petición/respuesta entrega de cápsula - 01 Para poder cumplir los requisitos del protocolo, deberemos fundamentarnos en la siguiente estructura de datos: Sinónimo Tipo Posiciones Rango Descripción # ASCII 1 Na Trama de inicio UD ASCII 2 01-32 Unidad de destino UO ASCII 2 01-32 Unidad de origen COD ISBN NIFCIF CRC $ CR ASCII ASCII ASCII ASCII ASCII ASCII 2 13 9 2 1 1 00-99 X(13) X(9) 00-FF Na Na Código de operación Código ISBN para encapsular y/o expulsar NIF usuario Cálculo para la verificación de redundancia cíclica Fin de trama Fin de transmisión tabla 7.1 La siguiente trama solicita la expulsión del DVD con ISBN=0809101112134 y el NIF=77666555X. Petición #010201080910111213477666555XFF$(Cr) Sinónimo Tipo Posiciones Rango Descripción # ASCII 1 Na Trama de inicio UD ASCII 2 01-32 Unidad de destino UO ASCII 2 01-32 Unidad de origen COD CRC $ CR ASCII ASCII ASCII ASCII 2 2 1 1 01-32 00-FF Na Na Código de fin1 Cálculo para la verificación de redundancia cíclica Fin de trama Fin de transmisión tabla 7.2 Respuesta 1 CRC = Cálculo de redundancia cíclica CR = Carriage return página #020100FF$(Cr) 55 << Robot dispensador para MSDN Vídeo Petición/respuesta introducción de cápsula - 02 Para poder cumplir los requisitos del protocolo, deberemos fundamentarnos en la siguiente estructura de datos: Sinónimo Tipo Posiciones Rango Descripción # ASCII 1 Na Trama de inicio UD ASCII 2 01-32 Unidad de destino UO ASCII 2 01-32 Unidad de origen COD ASCII 2 00-99 Código de operación CRC $ CR ASCII ASCII ASCII 2 1 1 00-FF Cálculo para la verificación de redundancia cíclica Na Fin de trama Na Fin de transmisión tabla 7.3 El código expulsión es “02”, entonces para componer una trama que acepte la introducción de la cápsula deberemos enviar la siguiente trama: Petición #010202FF$(Cr) Sinónimo Tipo Posiciones Rango Descripción # ASCII 1 Na Trama de inicio UD ASCII 2 01-32 Unidad de destino UO ASCII 2 01-32 Unidad de origen CF ASCII 2 00-99 Código de fin1 ISBN NIFCIF ASCII ASCII 13 9 X(13) Código ISBN para encapsular y/o expulsar x(9) NIF usuario CRC $ CR ASCII ASCII ASCII 2 1 1 00-FF Cálculo para la verificación de redundancia cíclica Na Fin de trama Na Fin de transmisión tabla 7.4 Respuesta #020100080910111213477666555FF$(Cr) página 56 Robot dispensador para MSDN Vídeo >> Petición/respuesta estatus del sistema - 03 Para poder cumplir los requisitos del protocolo, deberemos fundamentarnos en la siguiente estructura de datos: Sinónimo Tipo Posiciones Rango Descripción # ASCII 1 Na Trama de inicio UD ASCII 2 01-32 Unidad de destino UO ASCII 2 01-32 Unidad de origen COD ASCII 2 00-99 Código de operación CRC $ CR ASCII ASCII ASCII 2 1 1 00-FF Cálculo para la verificación de redundancia cíclica Na Fin de trama Na Fin de transmisión tabla 7.5 El código expulsión es “03”. Petición #010203FF$(Cr) Sinónimo Tipo Posiciones Rango Descripción # ASCII 1 Na Trama de inicio UD ASCII 2 01-32 Unidad de destino UO ASCII 2 01-32 Unidad de origen CF ASCII 2 00-32 Código de fin1 BIE ASCII 4 CRC $ CR ASCII ASCII ASCII 2 1 1 0-FFFF 16 Bits de estatus (indicadores de incidencias) 00-FF Cálculo para la verificación de redundancia cíclica Na Fin de trama Na Fin de transmisión tabla 7.6 Respuesta página #020100EEEEFF$(Cr) 57 << Robot dispensador para MSDN Vídeo Códigos retorno y operación Código Descripción 00 Operación de comunicaciones completada con éxito 99 Operación de comunicación incompleta tabla 7.7 Código Descripción 01 Petición de expulsión de cápsula 02 03 Petición de Introducción de cápsula Petición de banderas de estado controlador 10 11 Alertas Todo Bien Existe una alarma en el sistema leer estatus 20 Avisos Producto solicitado en stock mínimo 21 Último producto 22 Últimas cápsulas 23 Errores intermitentes en el dispensador 24 No es posible localizar el producto 25 Imposible recoger producto 26 Imposible apilar cápsula en almacén 27 Eje del cabezal localizador estropeado 28 Atasco en el circuito de distribución 29 Falla el lector de etiquetas electrónicas 30 Falla la lectura de la etiqueta electrónica tabla 7.8 página 58 Códigos de fin de operación Códigos datos operación Robot dispensador para MSDN Vídeo >> Códigos Indicadores de Incidencias Hex Binario Descripción 0000 0000 0000 0000 0000 Sin incidencias 0001 0000 0000 0000 0001 Avería en la placa controladora 16C450 # 1 0002 0000 0000 0000 0010 Avería en la placa controladora 16C450 # 2 0004 0000 0000 0000 0100 Fallo en el procesador # 1 0008 0000 0000 0000 1000 Fallo en el procesador # 2 0010 0000 0000 0001 0000 Avería en el eje del cabezal 0020 0000 0000 0010 0000 Avería en la pinza dispensadora 0040 0000 0000 0100 0000 Lector de etiquetas averiado (lectura/escritura) 0080 0000 0000 1000 0000 Disparo del detector anti vandálico 0100 0000 0001 0000 0000 Almacén de cápsulas vacío 0200 0000 0010 0000 0000 Almacén de producto vacío 0400 0000 0100 0000 0000 Suciedad en la óptica del láser posicionador 0800 0000 1000 0000 0000 Paro en el ventilador del sistema de refrigeración 1000 0001 0000 0000 0000 Atasco en el dispensador de salida 2000 0010 0000 0000 0000 Atasco en el dispensador de entrada 4000 0100 0000 0000 0000 Atasco en pinza dispensadora 8000 1000 0000 0000 0000 Sobretemperatura del sistema ALARMA GRAVE tabla 7.9 Cálculo del CRC (pasos previos) El Cyclic Redundancy Check es un mecanismo de verificación de error, que permite a la aplicación determinar si la trama recibida está libre de errores. página Este checksum es generado por el remitente, sin embargo, el destinatario debe volver a efectuar el cálculo del CRC con los datos recibidos; es evidente que si el cálculo del CRC del remitente coincide con el del destinatario, podemos casi afirmar que los datos recibidos están libres de errores. 59 << Robot dispensador para MSDN Vídeo Es necesario precisar que este sistema no es efectivo en la detección del 100% de errores que se produzcan, aunque tampoco son frecuentes los errores en este tipo de transmisiones. Por lo que podríamos considerar que para nuestro cometido es más que suficiente. Tampoco es preciso analizar este código, será suficiente entendiendo que a partir de una cadena de caracteres ASCII efectúa una suma binaria vertical y nos devuelve dos bytes en formato ASCII que corresponden al CRC que incluiremos al final de la trama. No olvidemos que su complejidad añadida se debe la necesidad de entregar el resultado de una suma binaria de 16 bits en dos caracteres ASCII que como ya sabéis son de 8 bits. ' ' ' Calcular el crc de la variable Var ' ' Shared Function Crc(ByVal Var) As String Dim i As Integer, j As Integer Dim CrcTmp As Integer, Ct As Integer, Ch As Integer, Cl As Integer Dim C1 As Integer, C2 As Integer CrcTmp = &HFFFF For i = 1 To Len(Var) CrcTmp = CrcTmp Xor Asc(Mid(Var, i, 1)) For j = 1 To 8 Ct = CrcTmp And &H1 If CrcTmp < 0 Then Ch = 1 Else Ch = 0 CrcTmp = CrcTmp And &H7FFF CrcTmp = CrcTmp \ 2 If Ch = 1 Then CrcTmp = CrcTmp Or &H4000 If Ct = 1 Then CrcTmp = CrcTmp Xor &HA001 Next j, i If CrcTmp < 0 Then Cl = 1 : CrcTmp = CrcTmp And &H7FFF Else Cl = 0 C1 = CrcTmp And &HFF& : C2 = (CrcTmp And &H7F00) \ 256 If Cl = 1 Then C2 = C2 Or &H80 Return (Chr("&h" + Hex(C1)) + Chr("&h" + Hex(C2))) End Function Fuente 7.1 Este fragmento de programa esta íntegramente desarrollado y diseñado en el año 89, por Pep. Utiliza la técnica “Lo+Retorcida”, en esos años era la única forma de proteger intelectualmente los desarrollos, se caracterizaba porque “no había quien la entendiera”. página 60 Robot dispensador para MSDN Vídeo >> Componiendo estructuras básicas Una de las labores más agradecidas en estos, los menesteres del codificador, son la definición de estructuras. Cuanto más precisas, definidas y concretas sean, más sencillo será conformar una fluida comunicación en el código. Unas estructuras desordenadas desembocaran en un caos de información difícil de manejar, con el consecuente fracaso de nuestra aplicación. En clases que manejan protocolos son de vital importancia ya que en ellas se fundamenta el éxito; de hecho no por menos son los cimientos de todo lo que construyamos después. Visual Studio 2005 sorprende por su elevada capacidad para encapsular estructuras que contengan propiedades, funciones,... es magnífico ver qué bien recompensado queda tu código cuando lo pones a trabajar. Según la definición del protocolo DispeDataLink de Entregon+, debemos ceñirnos a las tramas definidas tanto en el envío como en la recepción. Para tal efecto generaremos una clase a la que llamaremos EntrgnEstructuras.vb, la cual va a contener el formato exacto requerido para efectuar el intercambio de tramas entre el sistema y el Entregon+. El siguiente es un ejercicio de estructuras, intenta jugar con diferentes posibilidades para ofrecer al lector una buena perspectiva de su uso, por lo que se aleja de lo estrictamente necesario. No se sorprenda si le parece una puesta en escena un tanto exagerada. A menudo hablamos de encapsular, pero a menudo ese termino nos confunde. En este ejemplo entenderemos cómo cualquier protocolo empaqueta diversos bloques de información. La unidad y distribución de estos bloques de datos en un formato concreto listo para ser interpretado por los interlocutores, las conocemos con el nombre de TRAMA. Por lo tanto, la trama es al bit, como la unidad básica de intercambio de datos entre dispositivos. A tal efecto, anterior a la construcción de la trama, deberemos conformar las estructuras de los bloques de datos que constituirían el contenido de las mismas. página El primer bloque encapsula la cabecera de unidad destino a la que va dirigida la trama y qué unidad a lanzado la petición. Tal como se observa, la única función del BloqueDatos1, es devolvernos, o memorizar en formato de una cadena de dos dígitos, la UnidadDestino y la UnidadOrigen… Como veréis ¡con el simple uso de propiedades!, realmente organizado. 61 << Robot dispensador para MSDN Vídeo #Region "** Datos de Transmision Bloque 1" ' ' Encapsular estructura y funcionalidad del bloque de datos 1 ' 9999 9999 ' -+-- -+-' | | ' | +----Unidad de Origen ' +---------Unidad de Destino ' Private Structure BloqueDatos1 Private Unidad_Destino As String Private Unidad_Origen As String ' ' Propiedad UnidadDestino Public Property UnidadDestino() As String Get Return Unidad_Destino End Get Set(ByVal Value As String) ' se Admiten valores entre 00 y 32 Unidad_Destino = Format(Val(Value), "00") End Set End Property ' ' Propiedad UnidadOrigen Public Property UnidadOrigen() As String Get Return Unidad_Origen End Get Set(ByVal Value As String) ' se Admiten valores entre 00 y 32 Unidad_Origen = Format(Val(Value), "00") End Set End Property End Structure #End Region Fuente 7.2 A continuación pasaremos a definir el segundo bloque de datos que ayudará a conformar la trama final. Usando la misma filosofía que en el bloque 1, definimos una propiedad para cada una de las unidades de información que van a ser intercambiadas. También es importante ver cómo los protocolos estructuran sus capas, empaquetando información de diferentes niveles. En nuestro caso ¿por qué dos bloques de datos?, la respuesta reza muy sencilla: página 62 Robot dispensador para MSDN Vídeo >> El primer bloque de datos encapsula información a nivel de dispositivos, el segundo tan solo contiene información a nivel de aplicación. ¿Quizás nos esté recordando a un modelo de capas como el OSI? ¿o quizás NO? Simplemente para desmitificar un poquito y quitarnos ciertos complejos, desvelar que incluso los famosos y misteriosos protocolos TCP conforman diferentes formatos definidos y divididos en bloques para el intercambio de su información... ¡son los hermanos mayores! #Region "** Datos de Transmision Bloque 2" ' ' Encapsular estructura y funcionalidad del bloque de datos 2 ' 99 9999999999999 999999999 ' +---------+------------+---' | | | ' | | +---- NIF ' | +----------------- ISBN ' +--------------------------- 01 - Expulsar Capsula (ISBN+CIF) ' 02 - Introducción Capsula (Retorna ISBN+CIF) ' 99 ' --(Retorno de Codigo operacion en respuesta) 03 - Status del Dispensador ' Private Structure BloqueDatos2 Private Codigo_Operacion As Integer Private Codigo_Fin As Integer Private Indicador_Estado As Integer Private Datos_ISBN As String Private Datos_CIF As String ' ' Propiedad CodigoOperacion Public Property CodigoOperacion() As String Get Return Format(Codigo_Operacion, "00") End Get Set(ByVal value As String) Codigo_Operacion = Val(value) End Set End Property ' ' Propiedad CodigoFin Public Property CodigoFin() As String Get Return Format(Codigo_Fin, "00") End Get página Fuente 7.3 (continua...) 63 << Robot dispensador para MSDN Vídeo Set(ByVal value As String) Codigo_Fin = value End Set End Property ' ' Propiedad IndicadorEstado Public Property IndicadorEstado() As String Get Return Hex(Indicador_Estado).PadLeft(4, "0") End Get Set(ByVal value As String) Indicador_Estado = Val(value) End Set End Property ' ' Propiedad DatosISBN Public Property DatosISBN() As String Get Return Datos_ISBN End Get Set(ByVal value As String) Datos_ISBN = value.PadRight(13) End Set End Property ' ' Propiedad DatosCIF Public Property DatosCIF() As String Get Return Datos_CIF End Get Set(ByVal value As String) Datos_CIF = value.PadRight(9) End Set End Property End Structure #End Region (...continuación) Fuente 7.3 ¡Eureka!, nuestros dos bloques de información definidos en el primer apartado, ya están listos para ser usados. Debemos componer una estructura para cada modelo de solicitud, a saber: Expulsión, Introducción, Estado. página 64 Robot dispensador para MSDN Vídeo >> Así que compuestos los bloques de datos, abordaremos la construcción de las tramas dándoles el formato adecuado. Lo que pretende la siguiente estructura es abstraer el rígido protocolo DispeDataLink de la aplicación, lo que permite llamar a esta estructura sin necesidad de concentrarte en los aspectos formales (CRC/CR/Carácter de Inicio/Fin..., etc.) de esa forma la aplicación se concentra únicamente en los datos que maneja. Esta sencilla fórmula es la responsable de componer los bloques, ponerlos en orden, añadirles las cabeceras, calcular los bytes de redundancia cíclica y entregarnos una cadena que contiene la trama lista para enviar. ¿A alguien le parece poco? #Region "** Trama de Transmision - Sol.licitud de Expulsion" Public Structure SolicitarExpulsion Private miTrama As String ' 'Componer la trama de Expulsión partiendo del ISBN y el CIF, Unidad de Origen / Unidad Destino, Retornamos una estructura compuesta según Protocolo DispeDataLink Public Function Trama(ByVal UDest As String, ByVal UOrig As String,_ ByVal ISBN As String, ByVal CIF As String) As String ' blDatos1.UnidadDestino = UDest 'Unidad Destino blDatos1.UnidadOrigen = UOrig 'Unidad Origen blDatos2.CodigoOperacion = Operacion.Expulsion 'Codigo Expulsion blDatos2.DatosISBN = ISBN 'Codigo ISBN blDatos2.DatosCIF = CIF 'Codigo CIF ' miTrama = Ini.ToString + _ blDatos1.UnidadDestino + _ blDatos1.UnidadOrigen + _ blDatos2.CodigoOperacion + _ blDatos2.DatosISBN + _ blDatos2.DatosCIF Return miTrama + calcular_Crc(miTrama) + Fin.ToString End Function End Structure #End Region Fuente 7.4 página Supongo que nadie tendrá dificultad en seguir la lectura del código y pueda distinguir cómo en las primeras líneas asignamos los datos recibidos en la llamada de la 65 << Robot dispensador para MSDN Vídeo función a su correspondiente formateador de bloque y en su parte final los asignamos a una cadena llamada “miTrama”. Para continuar calculando y añadiendo el CRC a la cadena de fin de trama. En el formato de trama de introducción hemos implementado una sobrecarga para poder simular la lectura de las etiquetas electrónicas. #Region "** Trama de Transmision - Sol.licitud de Introducción" Public Structure SolicitarIntroduccion Private miTrama As String ' 'Componer la trama de Expulsión partiendo del ISBN y el CIF, Unidad de Origen / Unidad Destino 'Retornamos una estructura compuesta segun protocolo DispeDataLink Public Overloads Function Trama(ByVal UDest As String, _ ByVal UOrig As String) As String ' blDatos1.UnidadDestino = UDest 'Unidad Destino blDatos1.UnidadOrigen = UOrig 'Unidad Origen blDatos2.CodigoOperacion = Operacion.Introduccion 'Cod.Introducción ' miTrama = Ini.ToString + _ blDatos1.UnidadDestino + _ blDatos1.UnidadOrigen + _ blDatos2.CodigoOperacion Return miTrama + calcular_Crc(miTrama) + Fin.ToString End Function ' 'Sobrecarga de la funcion para poder simular la lectura de la etiqueta electronica ' Public Overloads Function Trama(ByVal UDest As String, _ ByVal UOrig As String, _ ByVal ISBN As String, _ ByVal CIF As String) As String ' blDatos1.UnidadDestino = UDest 'Unidad Destino blDatos1.UnidadOrigen = UOrig 'Unidad Origen blDatos2.CodigoOperacion = Operacion.Introduccion 'Cod.Introducción blDatos2.DatosISBN = ISBN 'Codigo ISBN blDatos2.DatosCIF = CIF 'Codigo CIF ' miTrama = Ini.ToString + _ blDatos1.UnidadDestino + _ Fuente 7.5 (continua...) página 66 Robot dispensador para MSDN Vídeo >> blDatos1.UnidadOrigen + _ blDatos2.CodigoOperacion + _ blDatos2.DatosISBN + _ blDatos2.DatosCIF Return miTrama + calcular_Crc(miTrama) + Fin.ToString End Function End Structure #End Region (...continuación) Fuente 7.5 Por supuesto, no olvidaremos implementar la trama para la solicitar el estado del dispensador. #Region "** Trama de Transmision - Sol.licitud de Estado" Public Structure SolicitarEstatus Private miTrama As String ' 'Componer la trama de Estado 'Retornamos una estructura compuesta segun protocolo DispeDataLink Public Function Trama(ByVal UDest As String, ByVal UOrig As String) As String ' blDatos1.UnidadDestino = UDest 'Unidad Destino blDatos1.UnidadOrigen = UOrig 'Unidad Origen blDatos2.CodigoOperacion = Operacion.Estatus 'Codigo Estatus ' miTrama = Ini.ToString + _ blDatos1.UnidadDestino + _ blDatos1.UnidadOrigen + _ blDatos2.CodigoOperacion Return miTrama + calcular_Crc(miTrama) + Fin.ToString End Function End Structure #End Region Fuente 7.6 Debido a la naturaleza de nuestro proyecto, debemos también contemplar las estructuras de respuesta, esta sobre-tarea nos viene impuesta por la necesidad de implementar un simulador del dispensador. página Siguiendo el esquema descrito en el gráfico 6.1 (enlace de comunicaciones), veremos cómo cada solicitud se convierte en un envío que recibe el simulador, gene- 67 << Robot dispensador para MSDN Vídeo rando este último otro envío que recibe el solicitante en modo respuesta. O sea, dos transmisiones y dos recepciones. Iniciemos la codificación de las estructuras de respuestas que corresponderían a las construidas en el ámbito del dispensador. En el modelo de trama para las repuestas incluiremos un formato común con las sobrecargas necesarias para dotarlas de todas sus formas. #Region "** Trama de Respuesta - Modelo Comun" Public Structure RespuestaModelo Private miTrama As String ' 'Componer la trama de Expulsión partiendo del ISBN y el CIF, Unidad de Origen / Unidad Destino 'Retornamos una estructura compuesta segun protocolo DispeDataLink Friend Overloads Function Trama(ByVal UDest As String, _ ByVal UOrig As String, _ ByVal CodigoFin As String, _ ByVal CodigoOperacion As String) As String ' blDatos1.UnidadDestino = UDest 'Unidad Destino blDatos1.UnidadOrigen = UOrig 'Unidad Origen blDatos2.CodigoFin = CodigoFin blDatos2.CodigoOperacion = CodigoOperacion 'Cod.Retorno Operacion ' miTrama = Ini.ToString + _ blDatos1.UnidadDestino + _ blDatos1.UnidadOrigen + _ blDatos2.CodigoFin + _ blDatos2.CodigoOperacion Return miTrama + calcular_Crc(miTrama) + Fin.ToString End Function ' Friend Overloads Function Trama(ByVal UDest As String, _ ByVal UOrig As String, _ ByVal CodigoFin As String, _ ByVal IndicadorEstatus As Integer) As String ' blDatos1.UnidadDestino = UDest 'Unidad Destino blDatos1.UnidadOrigen = UOrig 'Unidad Origen blDatos2.CodigoFin = CodigoFin blDatos2.IndicadorEstado = IndicadorEstatus 'Cod.Retorno Operacion Fuente 7.7 (continua...) página 68 Robot dispensador para MSDN Vídeo >> ' miTrama = Ini.ToString + _ blDatos1.UnidadDestino + _ blDatos1.UnidadOrigen + _ blDatos2.CodigoFin + _ blDatos2.IndicadorEstado Return miTrama + calcular_Crc(miTrama) + Fin.ToString End Function End Structure #End Region (...continuación) Fuente 7.7 Para los que no tengan demasiado claro, el tema de sobrecargas, justo comentar que son diferentes maneras de llamar a una misma funcionalidad, o dicho de otro modo sirven para llamar a una función con el mismo nombre pasando diferentes juegos de parámetros. Desde luego debo pedir disculpas a los expertos por este pésimo ejemplo de implementación de sobrecargas. En esta región englobamos la parte dura, el desglose de campos... ¿alguien recuerda la instrucción Field...? pues aquí estamos. Del mismo modo con esta estructura descomponemos o desempaquetamos los diferentes campos que componen la trama. Fijémonos en el uso de substring para extraer la posición exacta reservada a cada valor. #Region "** Recepcion de Tramas - Desglose de Respuestas" Public Structure Recepcion Private miTrama As String Private TramaCrc, CrcRecv, CrcCalc As String Public Property CamposDeRecepcion() As String Get Return miTrama End Get Set(ByVal value As String) miTrama = value End Set End Property página Fuente 7.8 (continua...) 69 << Robot dispensador para MSDN Vídeo Public Overloads ReadOnly Property Destino(ByVal Trama As String) As Integer Get Return Trama.Substring(1, 2) End Get End Property Public Overloads ReadOnly Property Destino() As Integer Get Return miTrama.Substring(1, 2) End Get End Property Public Overloads ReadOnly Property Origen(ByVal Trama As String) As Integer Get Return Trama.Substring(3, 2) End Get End Property Public Overloads ReadOnly Property Origen() As Integer Get Return miTrama.Substring(3, 2) End Get End Property Public Overloads ReadOnly Property CodigoOpe(ByVal Trama As String) As Integer Get Return Trama.Substring(5, 2) End Get End Property Public Overloads ReadOnly Property CodigoOpe() As Integer Get Return miTrama.Substring(5, 2) End Get End Property Public Overloads ReadOnly Property CodigoFin(ByVal Trama As String) As String Get Return Trama.Substring(7, 2) End Get End Property Public Overloads ReadOnly Property CodigoFin() As String Get Fuente 7.8 (continua...) página 70 Robot dispensador para MSDN Vídeo >> Return miTrama.Substring(7, 2) End Get End Property Public Overloads ReadOnly Property CheckSum(ByVal Trama As String) As Boolean Get TramaCrc = Trama.Substring(0, Trama.Length - 4) CrcRecv = Trama.Substring(Trama.Length - 4, 2) CrcCalc = calcular_Crc(TramaCrc) If CrcRecv = CrcCalc Then Return True Else Return False End Get End Property Public Overloads ReadOnly Property CheckSum() As Boolean Get TramaCrc = miTrama.Substring(0, miTrama.Length - 4) CrcRecv = miTrama.Substring(miTrama.Length - 4, 2) CrcCalc = calcular_Crc(TramaCrc) If CrcRecv = CrcCalc Then Return True Else Return False End Get End Property Public Overloads ReadOnly Property ISBN(Trama as String) As String Get Return Trama.Substring(7, 13) End Get End Property Public Overloads ReadOnly Property ISBN() As String Get Return miTrama.Substring(7, 13) End Get End Property Public Overloads ReadOnly Property Indicadores(ByVal Trama As String) As String Get Return Trama.Substring(7, 4) End Get End Property Public Overloads ReadOnly Property Indicadores() As String Get Return miTrama.Substring(7, 4) End Get End Property página Fuente 7.8 (continua...) 71 << Robot dispensador para MSDN Vídeo Public Overloads ReadOnly Property NIFCIF(ByVal Trama As String) As String Get Return Trama.Substring(20, 9) End Get End Property Public Overloads ReadOnly Property NIFCIF() As String Get Return miTrama.Substring(20, 9) End Get End Property Public ReadOnly Property LaTramaEstaCompleta(ByVal Trama As String) As Boolean Get miTrama = Trama If (Left(Trama, 1) = Ini) And (Right(Trama, 2) = "$" + Chr(13)) Then Return True Else Return False End If End Get End Property End Structure #End Region (...continuación) Fuente 7.8 Vale la pena observar en todo este código de desglose, el uso de sobrecargas y las propiedades de solo lectura (Overloads, ReadOnly, Property). En las estructuras de recepción cabe destacar el alto grado de integración, usando llamadas a la función de calcular_Crc desde la propiedad de CheckSum, es evidente que vamos a disfrutar empaquetando código. También destacar el uso de la propiedad CheckSum y LaTramaEstaCompleta, la función de la primera es devolver un “Verdadero” si la suma de verificación del remitente coincide con la calculada por el destinatario y la segunda de ellas comprueba si la trama contiene las cadenas identificadoras de “Inicio” y “Final”. Después de verificar ambas propiedades en una trama, estaremos en disposición de procesar sus datos. La clase EntrgnEstructuras, incluirá ciertas enumeraciones, que en ciertos contextos pueden ayudarnos en tener todas las descripciones muy bien ordenadas. Veamos: página 72 Robot dispensador para MSDN Vídeo >> #Region "** Enumeraciones" ' 'Enumerar las posibles Operaciones Public Enum Operacion 'Acciones Expulsion = 1 Introduccion = 2 Estatus = 3 'Alertas TodoBien = 10 ExisteAlarma = 11 'Avisos StockMinimo = 20 UltimoProducto = 21 UltimasCapsulas = 22 ErroresEnDispensador = 23 ProductoIlocalizable = 24 ImposibleRecoger = 25 ImposibleApilar = 26 EjeCabezalRoto = 27 AtascoDistribuidor = 28 FalloLectorEtiquetas = 29 FalloLecturaEtiqueta = 30 'Fin de operacion TransmisionOk = 0 TransmisionKo = 99 End Enum ' 'Enumerar los Indicadores de estado (4 hex) Public Enum Indicadores AveriaEnPlacaControladora1 = &H1& AveriaEnPlacaControladora2 = &H2& FalloEnProcesador1 = &H4& FalloEnProcesador2 = &H8& AveriaEjeCabezal = &H10& AveriaEnPinzaDispensadora = &H20& LectorEtiquetasAveriado = &H40& DisparoDetectorAntivandalico = &H80& AlmacenCapsulasVacio = &H100& AlmacenProductoVacio = &H200& SuciedadOpticaPosicionadorLaser = &H400& FalloVentiladorSistemaRefrigeracion = &H800& AtascoDispensadorSalida = &H1000& AtascoDispensadorEntrada = &H2000& AtascoPinzaDispensadora = &H4000& SobreTemperatura = &H8000& End Enum #End Region página Fuente 7.9 73 << Robot dispensador para MSDN Vídeo Después de todo este esfuerzo, tenemos a punto nuestra primera clase, con este magnífico aspecto: figura 7.1 El simulador A los más impacientes se nos hace difícil teclear código sin poderle dar al [F5], sin embargo, es importante seguir un orden, a pesar del desespero, como en la mayoría de disciplinas, deberemos construir la infraestructura que nos va a permitir elevar nuestro edificio hasta conseguir la altura diseñada. Bien, una vez hemos completado la parte más aburrida (definir estructuras), pasaremos a conformar una vital. Acabamos de construir un instrumento dispensador, sin embargo, a todos los efectos requerimos disponer de un simulador que emule el funcionamiento de nuestra máquina. página 74 Robot dispensador para MSDN Vídeo >> La naturaleza y topología de las comunicaciones entre nuestro dispensador y el equipo al que está unido, nos obliga a intercambiar tramas en ambos sentidos, debemos pues proveernos de todos los mecanismos necesarios para poder desarrollar nuestra aplicación sin el soporte del dispensador. Con la ayuda de este simulador podremos construir una aplicación resistente. Además podemos perfeccionar el simulador hasta tal punto en que cambiar el simulador por el dispensador sea simplemente eso: ¡cambiar! ¡… Bienvenido a la virtualidad! Conseguiremos nuestro propósito dotando a nuestro simulador de una función distribuidora de solicitudes, cualquier operación externa será dirigida y distribuida desde este punto; la función llamará a cada uno de los procedimientos contemplados, o sea, solicitud de introducción, expulsión y estado. Codificaremos una función que compondrá el modelo de respuesta. Finalizaremos nuestra clase con la función AlarmaGrave encargada de simular y reportar los problemas relacionados con mensajes de advertencia, informativos o de alarma. página figura 7.2 75 << Robot dispensador para MSDN Vídeo Su aspecto: En el proceso de solicitudes, recibiremos la llamada a la función incluyendo la trama recibida. Como vemos asignaremos a la variable CodigoOpe la operación solicitada, siendo éste el que decida en la Select qué función ejecutaremos para procesar dicha solicitud: ' 'Procesar las tramas recibidas por el simulador Public Function ProcesoDeSolicitudes(ByVal TramaRecibida As String) As String If Not Campos.LaTramaEstaCompleta(TramaRecibida) Then 'Trama no completa TramaDeRespuesta = "" 'No procesamos Nada Else _Origen = Campos.Origen.ToString 'Asignacion campos recibidos _Destino = Campos.Destino.ToString _CodigoOpe = Campos.CodigoOpe.ToString If Not Campos.CheckSum Then 'el checksum coincide.. error ? 'Componer respuesta fallo verificacion del CRC _CodigoFin = 99 ComponerRespuestaModelo() Else Select Case Campos.CodigoOpe 'ejecutar proceso según operacion Case 1 'Procesar solicitud de Expulsion SolicitarExpulsion() Case 2 'Procesar solicitud de Introduccion SolicitarIntroduccion() Case 3 'Procesar solicitud de Estado SolicitarEstatus() End Select End If End If Return TramaDeRespuesta End Function Fuente 7.10 Anteriormente estuvimos viendo como implementábamos dos propiedades ChekSum y la LaTramaEstaCompleta; bien, entonces analicemos su utilidad. Hemos estado hablando de empaquetar, encapsular... y aquí tenemos el resultado, si leemos detenidamente el código descrito en este fuente, nos daremos cuenta que de una manera elegante o dicho de otra forma, con una sintaxis entendible, estamos procesando sólo la trama que cumpla con dos condiciones básicas, la primera de ellas que contenga las cadenas de inicio/fin y la segunda que coincidan los cálculos de verificación. página 76 Robot dispensador para MSDN Vídeo >> Ahora nos toca la tarea de crear los procedimientos que ejecutaran la simulación del proceso del dispensador. ' 'Simular la expulsion Private Sub SolicitarExpulsion() ' 'Simular ordenes del mecanismo 'If LocalizarCapsula(_ISBN) then ' CodigoDeRetorno = PosicionarPinzasCabezal(): VerificarCodigosFinDeOperacion ' CodigoDeRetorno = RecogerProductoEnPinzas(): VerificarCodigosFinDeOperacion ' CodigoOperacion = GrabarEtiquetaElectronica(_ISBN,_NIFCIF):VerificarCodFinDeOp ' CodigoDeRetorno = DejarProductoEnBandejas(): VerificarCodigosFinDeOperacion 'else ' ReportarProblemaDelSistemaLocalizador 'end if ' 'SIMULADOR!! _CodigoDeRetorno = SimuladorCodigoFin() 'Simular retorno incidencia entregon+ _CodigoFin = campos.CodigoOpe 'Operacion completada correctamente ComponerRespuestaModelo() ' 'Insertar el ISBN al banco local de peliculas que podemos dispensar End Sub Fuente 7.11. Simular el proceso de expulsión de una cápsula: SolicitarExpulsion ' 'Simular la introduccion Private Sub SolicitarIntroduccion() ' 'Simular ordenes del mecanismo ' Disparo interrupcion por deteccion de introduccion de capsula ' CodigoOperacion = LeerEtiquetaElectronica(_ISBN, _NIFCIF) 'If CodigoOperacion then ' CodigoDeRetorno = PosionCabezalEnRecogida(): VerificarCodigosFinDeOperacion ' CodigoDeRetorno = RecogerProductoDePinzas(): VerificarCodigosFinDeOperacion ' CodigoDeRetorno = ActualizarEtiquetaElectronica(_ISBN,_NIFCIF):VerCodFinDeOpe ' CodigoDeRetorno = PosicionProductoAlmacen() : VerificarCodigosFinDeOperacion() ' CodigoDeRetorno = DejarProducto(): VerificarCodigosFinDeOperacion 'else página Fuente 7.12. Simular el proceso de introducción de una cápsula: SolicitarIntroduccion 77 << Robot dispensador para MSDN Vídeo ' RechazarIntroduccionPorObjetoDesconocido ' LanzarAlertaAlUsuario ' ReportarCodigoErrorLecturaEtiqueta 'end if ' _CodigoDeRetorno = SimuladorCodigoFin() '... ? _CodigoFin = campos.CodigoOpe 'Operacion completada correctamente ' _ISBN = campos.ISBN _NIFCIF = campos.NIFCIF ComponerRespuestaModelo() End Sub (...continuación) Fuente 7.12. Simular el proceso de introducción de una cápsula: SolicitarIntroduccion ' 'Simular el estado del mecanismo Private Sub SolicitarEstatus() '_CodigoDeRetorno = SimuladorCodigoFin() '... ? ' 'ComponerRespuestaModelo() Dim _Respuesta As Tramas.RespuestaModelo = New Tramas.RespuestaModelo TramaDeRespuesta = _Respuesta.Trama(_Destino.ToString,_Origen.ToString, "03",IndicadoresEstado) End Sub Fuente 7.13. Simular el proceso de verificación de estado del dispensador: SolicitarEstatus Si hemos seguido el código, habremos observado que tanto el procedimiento de expulsión como el de introducción, efectúan una llamada a la composición de la trama de respuesta. Su nombre “Componer Respuesta Modelo”. Ésta es la encargada de devolver una respuesta en la forma definida para las respuestas. Pues entonces : ' 'Formato de la respuesta Private Sub ComponerRespuestaModelo() Dim _Respuesta As Tramas.RespuestaModelo = New Tramas.RespuestaModelo TramaDeRespuesta = Respuesta.Trama(_Destino.ToString, _Origen.ToString, _ _CodigoFin.ToString, _CodigoDeRetorno.ToString) End Sub Fuente 7.14 página 78 Robot dispensador para MSDN Vídeo >> Terminaremos nuestro recorrido, completando la clase Simulador con dos procedimientos más que gestionen el código de retorno y las alarmas. Del siguiente procedimiento destacaremos la simulación de mensajes del ingenio, para dotar a la simulación de un cierto grado de realidad. Evidentemente es necesario que de una manera eventual se disparen errores o alarmas tal como ocurriría en la realidad con el mecanismo. Para tal efecto siguiendo el código observaremos cómo simulamos tres errores diferentes en función de si los campos de NIFCIF o ISBN están parcialmente completos o incompletos en cualquiera de los dos casos. En condiciones normales se fuerza la generación de un número aleatorio entre el 1 y el 11, y a posteriori se rellena asignando un número aleatorio a los indicadores de estado. La última función de la clase AlaramaGrave rellena con mensajes de texto que corresponden a los códigos de alarma. 'Simulador de codigo fin de operación Private Function SimuladorCodigoFin() As Integer Dim indice As Integer ' Simular Codigos de error usando el ISBN y el DNI/CIF ' _ISBN = campos.ISBN.Replace(" ", "") _NIFCIF = campos.NIFCIF.Replace(" ", "") If (_ISBN.Length = 0) And (_NIFCIF.Length = 0) Then 'error = 29 si los campos ISBN y CIF aparecen vacios indice = 29 Else If _ISBN.Length < 13 And _NIFCIF.Length < 9 Then 'error = 24 si ninguno de los dos campos no esta completo indice = 24 Else If _NIFCIF.Length < 9 Then 'error = 30 si el campo NIF no tiene su longitud mínima indice = 30 Else If _ISBN.Length < 13 Then 'error = 23 si el campo ISBN no tiene su longitud mínima indice = 23 Else ' Simulador de detección de error en Entregon+ indice = CInt(Int((11 * Rnd()) + 1)) 'Generar código Aleatorio 'entre 1 y 11 página Fuente 7.15 (continua...) 79 << Robot dispensador para MSDN Vídeo If indice = 11 Then _IndicadoresEstado = _IndicadoresEstado Xor (32767 * Rnd()) AlarmaGrave() Else _IndicadoresEstado = 0 End If End If End If End If End If ' Select Case indice Case 11 : 'Case 20 : 'Case 21 : 'Case 22 : Case 23 : Case 24 : 'Case 25 : 'Case 26 : 'Case 27 : 'Case 28 : Case 29 : Case 30 : Case Else Return Return Return Return Return Return Return Return Return Return Return Return Tramas.Operacion.ExisteAlarma Tramas.Operacion.StockMinimo Tramas.Operacion.UltimoProducto Tramas.Operacion.UltimasCapsulas Tramas.Operacion.ErroresEnDispensador Tramas.Operacion.ProductoIlocalizable Tramas.Operacion.ImposibleRecoger Tramas.Operacion.ImposibleApilar Tramas.Operacion.EjeCabezalRoto Tramas.Operacion.AtascoDistribuidor Tramas.Operacion.FalloLectorEtiquetas Tramas.Operacion.FalloLecturaEtiqueta Return Tramas.Operacion.TodoBien End Select End Function (...continuacion) Fuente 7.15 Con ganas de finalizar, completaremos la clase simulador con la codificación de las alarmas. 'Tratamiento de Incidencias Graves Private Sub AlarmaGrave() Dim Codigo As Tramas.Indicadores = New Tramas.Indicadores Dim LfCr As String = Chr(10) + Chr(13) texto = "" If _IndicadoresEstado And Codigo.AlmacenCapsulasVacio Then texto += "RESPONSABLE DE PRODUCCION! - CAPSULAS AGOTADAS EN EL DISPENSADOR " End If If _IndicadoresEstado And Codigo.AlmacenProductoVacio Then Fuente 7.16 (continua...) página 80 Robot dispensador para MSDN Vídeo >> texto += "RESPONSABLE DE PRODUCCION! - DISPENSADOR nºxx, SIN PRODUCTO" + LfCr End If If _IndicadoresEstado And Codigo.AtascoDispensadorEntrada Then texto += "RESPONSABLE DE MANTENIMIENTO! - ATASCO EN EL MECANISMO DE ENTRADA" End If If _IndicadoresEstado And Codigo.AtascoDispensadorSalida Then texto += "RESPONSABLE DE MANTENIMIENTO! - ATASCO EN EL MECANISMO DE SALIDA" End If If _IndicadoresEstado And Codigo.AtascoPinzaDispensadora Then texto += "RESPONSABLE DE MANTENIMIENTO! - PINZA DISPENSADORA ATASCADA" End If If _IndicadoresEstado And Codigo.AveriaEjeCabezal Then texto += "RESPONSABLE DE MANTENIMIENTO! - EJE CABEZAL AVERIADO UNIDAD nºxx" End If If _IndicadoresEstado And Codigo.AveriaEnPinzaDispensadora Then texto += "RESPONSABLE DE MANTENIMIENTO! - PINZA DISPENSADORA AVERIADA " End If If _IndicadoresEstado And Codigo.AveriaEnPlacaControladora1 Then texto += "INGENIERO ELECTRONICO! - SUBSTITUIR PLACA CONTROLADORA 1 " End If If _IndicadoresEstado And Codigo.AveriaEnPlacaControladora2 Then texto += "INGENIERO ELECTRONICO! - SUBSTITUIR PLACA CONTROLADORA 2 " End If If _IndicadoresEstado And Codigo.DisparoDetectorAntivandalico Then texto += "JEFE DE SEGURIDAD! - DISPARO DE LA ALARMA DE ANTIVANDALICA" + LfCr End If If _IndicadoresEstado And Codigo.FalloEnProcesador1 Then texto += "INGENIERO DE SISTEMAS!,FALLA EL PROCESADOR PRINCIPAL ENTREGON+ " End If If _IndicadoresEstado And Codigo.FalloEnProcesador2 Then texto += "INGENIERO DE SISTEMAS!,FALLA EL PROCESADOR SECUNDARIO ENTREGON+" End If If _IndicadoresEstado And Codigo.SobreTemperatura Then texto += "OPERARIO DE A.ACONDICIONADO!, SOBRETEMPERATURA ENTREGON+" + LfCr End If If _IndicadoresEstado And Codigo.SuciedadOpticaPosicionadorLaser Then texto += "DEPARTAMENTO DE LASERES!, LIMPIAR/ALINEAR LASER DEL ENTREGON+ nºxx" End If 'Solo enviar en caso de Incidencia If texto.Length > 0 Then Mensaje.Registra("Simulador",texto, EventLogEntryType.Error, 100 + _ _CodigoDeRetorno) 'lanzar un correo electronico con la incidencia Alarmas.Avisar(texto) End If End Sub página (...continuación) Fuente 7.16 81 << Robot dispensador para MSDN Vídeo Mensajes en el EventLog Por fin, para nuestra librería, una clase “facilonga”. Básicamente se compone de una función que genera una entrada en el registro del sistema, determinada por su origen, su tipo de entrada y un identificador. Todo esto se materializa en una única llamada a la función WriteEntry del espacio System.Diagnostics.EventLog. ' ' ' ' ' ' ' ' ' ' Proyecto Nombre Tipo Clase Creado Autor Version : : : : : : : Entregon+ EntrgnEventLog.vb Clase RegistroAlertas 29.04.2005 Pep Lluis 1.0 Historia y Revisiones ----------------------------------------------------------05.2005 - Migrar el codigo a visual Studio 2005 - Beta 2 Public Class RegistroAlertas ' Private UltimoIdentificador As Integer = 0 Public Shared MensajesEnRegistro As Boolean = False ' Public Sub Registra(ByVal Origen As String, ByVal Mensaje As String, _ ByVal Tipo As System.Diagnostics.EventLogEntryType, _ ByVal Identificador As Integer) ' MensajesEnRegistro = True Try 'Nunca repetir el ultimo mensaje If UltimoIdentificador <> Identificador Then System.Diagnostics.EventLog.WriteEntry(Origen, Mensaje, Tipo, _ Identificador) UltimoIdentificador = Identificador End If Catch ex As Exception 'Alertar de que no podemos registrar eventos al log System.Diagnostics.EventLog.WriteEntry("RegistroAlertas", _ ex.Message, EventLogEntryType.Error, 0) End Try End Sub End Class Fuente 7.17 página 82 Robot dispensador para MSDN Vídeo >> En la mayoría del código anterior hemos omitido la captura de excepciones, daros cuenta que prácticamente la función del código era asignar o manejar valores y variables. Las siguientes clases incorporan llamadas a funciones de librerías externas, pensando en los imprevistos, a partir de ahora incluiremos los bloques Try/Catch para registrar un mensaje al EventLog, reportando el problema, cuyo detalle en forma de texto lo encontraremos en ex.Message. Avisos de incidencias automatizados Un truco de viejo programador, es dotar a nuestros desarrollos de esos pequeños detalles que diferencian nuestras aplicaciones de las de los aficionados. ¡Este es uno de ellos! A menudo no es difícil buscar utilidades que den versatilidad al conjunto. A simple vista puede parecer una pérdida inútil de tiempo dotar a nuestro sistema de una gestión de avisos automatizados. Sin embargo, si lo analizamos, nos daremos cuenta de que estamos desarrollando una aplicación pobre en interacción con el usuario. No olvidemos que hacemos de enlace entre un mecanismo y una aplicación de un dispensador, por lo que no disponemos de una interacción directa con el usuario; sería ridículo caer en el error de presentar mensajes de error al usuario del vídeo club que simplemente está interesado en recoger o devolver una película. Se imaginan la cara de estupor al leer: “Falla el procesador principal Entregon+”... ¡y a quién diablos le importa!... pues al departamento de sistemas donde están los ingenieros de soporte. Realmente el usuario del dispensador sólo debe recibir los mensajes relacionados con las operaciones en curso y en caso de problemas, sólo uno: “Dispensador Fuera de Servicio”. Este mensaje encubrirá el resto que serán automáticamente lanzados vía correo electrónico a cada uno de los departamentos que realizan el mantenimiento de la empresa que alquila y vende las películas. Organizar una cadena de establecimientos que tiene centralizados sus servicios de mantenimiento de sus instalaciones, será cosa de niños. A partir de ahora cada departamento recibirá las alarmas que se van produciendo en los dispensadores que tienen a su cargo. Además una vez disponemos de esta implementación, no importa si los dispensadores son 10 o si son 100, la infraestructura nos cubrirá cualquier dimensión. página Recuerdo en antaño cuando los cajeros de las entidades bancarias, tenían un interfono para que el usuario llamara en caso de problemas, pero antes de detectar estos problemas algún afortunado cliente tenía que caer en las garras del frío aluminio de los mecanismos… encalladas de tarjetas, lectores, dispensadores que se bloqueaban, sistemas se quedaban hang... ¡qué horror! 83 << Robot dispensador para MSDN Vídeo Es un alivio pensar que las máquinas actuales son capaces de detectar problemas incluso antes de que se produzcan, llegando a ser tolerantes a fallos, pero no nos engañemos, alguien deberá diseñar o implementar los algoritmos que realicen estas tareas… ¡cuestión de hacer las cosas bien! Y qué menos: nosotros no tan solo lo hacemos bien sino que... ¡somos los mejores! Por lo tanto, aviso de incidencias automatizadas. Casi no es necesario comentar el código, sin embargo, nos irá bien mencionar esta primera parte donde dimensionamos las variables que usaremos para nuestro aviso por correo, cargándolas con la configuración almacenada en el archivo app.config, para tal cometido usaremos la clase XmlConfig que se describe posteriormente. A continuación crearemos el objeto correo. Como mensaje de mail, asignaremos los valores del mismo y sencillamente llamaremos al send. ' 'Avisar de una Alerta utilizando el correo electonico Public Sub Avisar(ByVal emailMensa As String) ' 'Lectura de las variables de configuracion del archivo app.config. Dim email_From As String = _ Aplicacion.LeerConfig("app.config", "email_From", "emailOrigen").InnerText Dim email_To As String = _ Aplicacion.LeerConfig("app.config", "email_To", "emailDestino").InnerText Dim email_Cc As String = _ Aplicacion.LeerConfig("app.config", "email_Cc", "emailCopia").InnerText Dim email_User As String = _ Aplicacion.LeerConfig("app.config", "email_User", "CuentaUsuario").InnerText Dim email_Pwd As String = _ Aplicacion.LeerConfig("app.config", "email_Pwd", "password").InnerText Dim email_smtp As String = _ Aplicacion.LeerConfig("app.config","email_Smtp","servidor").InnerText ' 'Conformar un mensage de mail Dim correo As MailMessage = New MailMessage 'Definir el esquema CDO Dim Esquema As String = "http://schemas.microsoft.com/cdo/configuration/" 'Llenar de contenido el mensage correo.From = email_From correo.To = email_To correo.Cc = email_Cc Fuente 7.18 (continua...) página 84 Robot dispensador para MSDN Vídeo >> correo.Subject = "ENTREGON+, ALARMA - REQUIERE INTERVENCION" correo.Body = emailMensa 'Asignar las credendiales correo.Fields.Add(Esquema + "smtpauthenticate", "1") correo.Fields.Add(Esquema + "sendusername", email_User) correo.Fields.Add(Esquema + "sendpassword", email_Pwd) 'Asignar el nombre del servidor de correo SmtpMail.SmtpServer = email_smtp Try SmtpMail.Send(correo) 'Enviar el correo Catch ex As Exception 'reportar problemas al eventlog Mensaje.Registra("Alertas", ex.Message, EventLogEntryType.Error, 4) End Try End Sub (...continuación) Fuente 7.18 Como ya he comentado anteriormente es una buena práctica incluir la llamada al registro del EventLog, bajo el Catch, de esta forma reportamos los errores capturados cuando falla algo mientras se ejecutan nuestras instrucciones. Así podemos seguir el rastro de lo que está sucediendo tan solo visualizando en el EventLog. Para la versión final se recomienda no usar el System.Web.Mail, el envío de correos con el nuevo Framework debe realizarse con System.Net.Mail, según podemos ver en el siguiente fuente. '-------------------------------------------------------------------'Version con nombre de espacios system.net.mail '-------------------------------------------------------------------'Dim correo As MailMessage = New MailMessage(email_From, email_To) 'correo.Subject = "ENTREGON+, ALARMA - REQUIERE INTERVENCION" 'correo.Body = email_Mensa 'Dim ClienteSmtp As New SmtpClient(email_smtp) 'Try ' ClienteSmtp.UseDefaultCredentials = True ' ClienteSmtp.Send(correo) ' Catch ex As Exception ' Mensaje.Registra("Alertas", ex.Message, EventLogEntryType.Error, 4) ' End Try página Fuente 7.19 85 << Robot dispensador para MSDN Vídeo Para no perder costumbre, he aquí el aspecto vista desde nuestro IDE. figura 7.3 Los archivos de configuración Si hemos estado atentos, la clase anterior llamaba a un procedimiento: Aplicacion. te sorprendas, aún no hemos hablado de él. Cualquier aplicación que se precie debe almacenar y leer sus valores de configuración. Como veremos, esta clase nos permite realizar esta función utilizando un fichero en XML. Hemos decido usar el app.config aunque quizás no sea la mejor forma, lo hacemos para entender que podemos concentrar toda la informacion de configuración de la aplicación en un mismo archivo. Para no complicar excesivamente el código, sólo trabajaremos el documento XML a nivel de elemento. LeerConfig, no Según se describe en la siguiente figura, podemos distinguir tres funciones : Existe, LeerConfig página 86 y SalvarConfig. Robot dispensador para MSDN Vídeo >> figura 7.4 Existe simplemente verifica que el fichero solicitado se encuentre en la carpeta de la aplicación. ' 'Verificar la existencia del archivo Private Function Existe(ByVal Nombre As String) As Boolean Dim Doc As XmlDocument = New Xml.XmlDocument 'Documento XML Try ' Doc.Load(Nombre) 'Intentar Cargar el nombre Doc = Nothing 'Liberar el Doc Return True 'Archivo disponible Catch ex As Exception 'En otro caso Return False 'Archivo no disponible End Try End Function página Fuente 7.20 87 << Robot dispensador para MSDN Vídeo LeerConfig lee un elemento del documento XML y si no existe crea uno nuevo con el valor pasado por defecto. ' 'Leer un valor de Configuración de un Archivo en formato XML 'Parametros de la funcion : Fichero = Nombre del Archivo en formato nnn.eee (Ejemplo: app.config) ' Raiz = Elemento ' Defecto = Valor que retornara por defecto ' Public Function LeerConfig(ByVal Fichero As String, ByVal Raiz As String, _ ByVal Defecto As String) As Xml.XmlElement Dim Documento As XmlDocument = New Xml.XmlDocument 'Constructor Documento Dim Elemento As XmlElement = Documento.CreateElement("Raiz") Try 'Intentar abrir documento Documento.Load(Fichero) 'Cargar el documento Elemento = Documento.DocumentElement(Raiz) 'Cargar el elemento If Elemento Is Nothing Then 'Si Elemento INExistente ' Crear el elemento con el valor por defecto If Me.Existe(Fichero) Then Elemento = Documento.CreateElement(Raiz) Elemento.InnerText = Defecto Documento.DocumentElement.AppendChild(Elemento) Documento.Save(Fichero) Else 'no creamos el elemento por inexistencia del archivo 'Reportamos el problema al EventLog Mensaje.Registra("XmlConfig", "Archivo :" + Fichero + _ "Inexistente", EventLogEntryType.Warning, 1) End If End If Catch ex As Exception 'Solo reportar el problema Elemento.InnerText = Defecto Mensaje.Registra("XmlConfig", ex.Message, EventLogEntryType.Error, 2) End Try Return Elemento End Function Fuente 7.21 SalvarConfig documento. página 88 simplemente guarda el valor del elemento modificado y salva el Robot dispensador para MSDN Vídeo >> ' ' Guardar un valor en el documento xml de configuracion ' Public Function SalvarConfig(ByVal Fichero As String, ByVal Raiz As String, _ ByVal Valor As String) As Boolean Dim Documento As XmlDocument = New Xml.XmlDocument 'Constructo del Documento Dim Elemento As XmlElement = Documento.CreateElement("Raiz") Try 'Intenta abrir el documento Documento.Load(Fichero) 'Cargar el documento Elemento = Documento.DocumentElement(Raiz) 'Cargar el elemento Elemento.InnerText = Valor 'Asigna el valor a elemento Documento.Save(Fichero) 'Guarda el Documento Catch ex As Exception 'Solo reportar el problema Mensaje.Registra("XmlConfig", ex.Message, EventLogEntryType.Error, 2) End Try End Function Fuente 7.22 Hablando sobre los archivos de configuración. Continuamos disponiendo de llamadas al Registro del Sistema 'RegEdit'. Sin embargo parece ser que las buenas practicas no aconsejan el uso intensivo que muchas las aplicaciones hacen de el. Muchos discuten y es cuestión de largos estiras y aflojas, intentando ponerse de acuerdo entre si el 'RegEdit' o si el 'app.config'. Lo que esta claro es la apuesta de 'ellos' y como estaréis intuyendo termina en '.config'. La gestión del puerto serie Antes de empezar, os recomiendo el estudio de este glosario con las principales instrucciones del objeto Port, nos ayudara a comprender mejor el código que posteriormente escribiremos para esta clase. Estos son los mandatos e instrucciones más frecuentes para utilizar el puerto serie: Private Sub EjemploDeLasPrincipalesInstruccionesDeSystem.IO.Port() Dim Contador As Integer 'Llamar al constructor Fuente 7.23 (continua...) página Serie = My.Computer.Ports.OpenSerialPort("COM1") ' 89 << Robot dispensador para MSDN Vídeo 'Definir las caracteristicas de la comunicacion Serie.BaudRate = 19200 'Fijar velocidad de comunicaciones Serie.DataBits = 8 'Longitud en bits para Byte de datos Serie.Parity = Parity.Even 'Asignar paridad(enumeracion parity) Serie.StopBits = StopBits.Two 'Bits parada despues byte de datos ' 'Abrir/Control/Liberar Puerto Serie.Open() 'Abrir el puerto Serie Serie.Close() 'Cerrar el Puerto Serie Serie.Dispose() 'Liberar objecto Dim SiNo As Integer SiNo = Serie.IsOpen 'El Puerto esta abierto? Dim Puerto As String Puerto = Serie.PortName 'Nombre del puerto ' 'Manejo y Control de señales Dim Estado As Boolean 'True=Activa / False=Inactiva Estado = Serie.CDHolding 'Estado de la señal carrier detect Estado = Serie.CtsHolding 'Señal Clear to Send Estado = Serie.DsrHolding 'Señal Data Set Ready Serie.DtrEnable = True 'Activar de Data Terminal Ready Serie.RtsEnable = True 'Activar Request To Send ' 'Control Transmission/Recepcion Serie.ReadBufferSize = 1024 'Dimensionar tamaño buffer recepcion Serie.WriteBufferSize = 1024 'Dimensionar tamaño buffer envio Serie.ReadTimeout = 10 'Fuera de tiempo para las lecturas Serie.WriteTimeout = 10 'Fuera de tiempo para las escrituras Serie.Handshake = Handshake.XOnXOff 'Tipo control para recepcion/envio Serie.DiscardInBuffer() 'Borrar el buffer de entrada Serie.DiscardOutBuffer() 'Borrar el buffer de salida ' 'Enviar datos Contador = Serie.BytesToWrite 'Bytes en espera de ser escritos Serie.Write("Hola Mundo") 'Enviar una cadena de caracteres Serie.WriteLine("Hola Mundo") 'Enviar una linea ' 'Leer datos Contador = Serie.BytesToRead 'Bytes en espera de ser leidos Serie.ReadByte() 'Leer un byte Serie.ReadChar() 'Leer un char Serie.ReadLine() 'Leer una linea Serie.ReadExisting() 'Leer los datos existentes en buffer End Sub (...continuación) Fuente 7.23 página 90 Robot dispensador para MSDN Vídeo >> Finalmente llegamos a la clase insignia. Está claro que va a ser la más compleja, no olvidemos que todo este desarrollo gira entorno a los puertos serie. Vale la pena detenernos en la lectura de la cabecera de la clase EntrgnComSerie. figura 7.5 Observaremos cómo se han ordenado cinco apartados. El primero de ellos crea las instancias que utilizaremos para acceder a los objetos de la librería (instancias a clases). El segundo, tercero y cuarto, ordena las variables que utilizaremos en la aplicación en grupos de configuración, banderas y proceso. Es una agrupación muy práctica, facilitando la comprensión, sobre todo cuando estamos en proyectos desarrollados en equipo. Salta a la vista que el grupo de configuración definirá las variables usadas en la misma, las banderas son flags usados en el control del proceso y finalmente variables utilizadas para contabilizar o memorizar datos generados en el mismo. página El último grupo... ¡importante! es el de los eventos. En este punto asignaremos los nombres que manipularán los eventos que nuestra aplicación deba conducir. 91 << Robot dispensador para MSDN Vídeo La siguiente figura nos ilustra las clases necesarias para atender la transmisión y recepción de tramas. figura 7.6 Empezaremos detallando las operaciones a realizar en tiempo de carga: New, y Tiempo, para continuar con los procedimientos que formarán nuestra columna vertebral: Enviar, Recibir y AbrirPuertoSerie. Dispose Por sí solos, todos los procedimientos descritos, contienen suficientes comentarios y son lo suficientemente sugerentes para poder seguir el hilo del código. Por lo que animo al lector a leerlos detenidamente. Sin dudar añadiré cualquier explicación que ayude a su entendimiento. 'Al crear la clase Public Sub New() 'Temporizador para la puesta a cero de los contadores Temporizador.Interval = 1000 'Base de 1 Segundo Temporizador.Enabled = True 'Arrancar el temportizador ' AbrirPuertoSerie() 'Abrir puerto serie End Sub Fuente 7.24. Sub New, Cuando creamos una instancia a la clase. página 92 Robot dispensador para MSDN Vídeo >> 'Liberar el puerto Protected Overrides Sub Finalize() 'cerrar el objecto serie If PuertoSerieAbierto Then Serie.Close() MyBase.Finalize() End Sub Fuente 7.25. Función Finalize, cuando nuestra aplicación deja de usarla y liberamos la clase. 'Estadisticas de bits recebidos/enviados Private Sub Tiempo(ByVal sender As Object, ByVal e As Timers.ElapsedEventArgs) _ Handles Temporizador.Elapsed Static Seg1m As Integer, VecesEnvioEnCurso As Integer ' 'Responder fuera de tiempo al cliente, si no se obtiene respuesta entregon+ If EnvioEnCurso Then 'Transmissión en curso sin respuesta If (VecesEnvioEnCurso > 2) Then 'Durante dos segundos EnvioEnCurso = False UltimoMensajeError = "Sin Repuesta" Recepcion = "" RaiseEvent Respuesta() 'Disparar Repuesta de un time out Else VecesEnvioEnCurso += 1 'contabilizar disparo time out End If Else VecesEnvioEnCurso = 0 'Resetear indicador fuera de tiempo End If ' 'Temporizacion de 1 minuto Seg1m += 1 If Seg1m > 59 Then Seg1m = 0 Recibidos = CuentaBitsR 'Bits Recibidos por Minuto Enviados = CuentaBitsE 'Bits Enviados por Minuto CuentaBitsR = 0 'Puesta a cero de los contadores CuentaBitsE = 0 'para almacenar el siguiente periodo UltimoMensajeError = "" 'Borra mensaje error End If End Sub página Fuente 7.26. Función Tiempo, cada segundo… 93 << Robot dispensador para MSDN Vídeo La función Tiempo implementa la supervisión de las respuestas. Nuestra premisa es recibir una respuesta para cada solicitud, técnicamente cuando se lanza una solicitud y no recibimos una respuesta, estamos hablando de un time out. El System.IO.Port, dispone de potentes mecanismos para implementar y controlar estas situaciones, sin embargo, he considerado oportuno, siguiendo las directrices de la simplicidad, no complicar ni profundizar excesivamente en el uso de System.IO.Ports, simplemente lo manejaremos a un nivel tan básico que cualquier iniciado pueda seguir su funcionamiento. Por lo que sería una buena excusa para poder escribir un “librillo” con el sugerente título: “¡System.IO.Ports a FONDO!” (la idea... ¡es mía!). Por el momento nos conformaremos en controlar este tipo de incidencias desde la librería. Por consiguiente la función Tiempo forzará un “Sin respuesta” en el caso de mantener durante dos segundos una situación de envío en curso. ¡Viva la simplicidad! De las operaciones de envío y recepcion, debemos destacar el uso del alzado a verdadero por la función de envío, para tratarlo en la función de recepción como bandera que indica si el envío/recepcion forma parte de una solicitud de nuestra aplicación o por el contrario corresponde al envio/recepcion de la simulación; a tal fin destaquen el uso de Serie.Write en la rutina de recepción. SemaforoSimulador, Observen también cómo disparamos el evento de respuesta al cliente con RaiseEvent una vez completada toda la operación de solicitud/simulación/repuesta. Tampoco debemos perder la pista de cómo recibimos la notificación de los datos recibidos por el puerto serie, ello se debe al manejador del evento Serie.DataReceived en la función de recepción. O dicho de otro modo, System.IO.Ports nos dispara el evento DataReceived siempre que el puerto serie reciba algún carácter en su entrada. 'Enviar una trama Public Sub Enviar(ByVal Trama As String) SemaforoSimulador = True 'La proxima trama cierra el circuito simulacion Try Serie.Write(Trama) 'Escribimos la trama al puerto serie CuentaBitsE += Trama.Length * 11 'Contabilizamos los bits enviados EnvioEnCurso = True 'Señalizar el estado de envio Catch ex As Exception 'registrar mensage en caso de error Mensaje.Registra("ComSerie", ex.Message, EventLogEntryType.Error, 6) UltimoMensajeError = ex.Message End Try End Sub Fuente 7.27. Función Enviar página 94 Robot dispensador para MSDN Vídeo >> ' 'Recibir una trama Public Sub Recibir(ByVal sender As Object, ByVal e As _ System.IO.Ports.SerialDataReceivedEventArgs) Handles Serie.DataReceived Try EnvioEnCurso = False 'Limpiar memoria estado Envio Recepcion = Serie.ReadExisting.ToString 'Leer chars del buffer recepcion CuentaBitsR += Recepcion.Length * 11 'contabilizar bits recibidos Catch ex As Exception 'registrar mensage en caso de error Mensaje.Registra("ComSerie", ex.Message, EventLogEntryType.Error, 7) UltimoMensajeError = ex.Message End Try ' 'Simulador Envio/Recepcion entre Entregon y Servidor conectado a Dispensador ' If SemaforoSimulador Then 'Debemos simular la operacion? Try 'Simular peticion Serie.Write(miSimulador.ProcesoDeSolicitudes(Recepcion)) CuentaBitsE += Recepcion.Length * 11 'Contabilizar bits recibidos EnvioEnCurso = 'Señalizador de envio en curso Catch ex As Exception 'registrar mensage en caso de error Mensaje.Registra("ComSerie", ex.Message, EventLogEntryType.Error, 8) UltimoMensajeError = ex.Message End Try SemaforoSimulador = False 'Borrar indicador de simulacion Else RaiseEvent Respuesta() 'Disparar respuesta al cliente End If End Sub Fuente 7.28. Función Recibir En la función New llamamos a la función AbrirPuertoSerie. Es evidente que antes de poder realizar cualquier operación sobre el puerto serie, es necesario abrirlo, ¡ah! y como veréis en la primera línea, ¡lo cerramos si es que está abierto! Como ya estamos acostumbrados, lo primero que llevamos a cabo es la lectura de los valores de configuración almacenados en el archivo app.config. Es interesante, fijarnos en un mandato muy especial: página Serie = My.Computer.Ports.OpenSerialPort _ (Puerto, VelocidadPuerto, IO.Ports.Parity.Even, 7, IO.Ports.StopBits.Two) 95 << Robot dispensador para MSDN Vídeo Nuestro objeto Serie llama al constructor del espacio de nombres System.IO.Ports usando My, es una manera muy elegante de encontrar y usar los recursos en nuestros espacios de nombres. Vale la pena entretenerse un poquito en descubrir todo lo que nos esconde My.Computer a toque de punto y click, todo ello en una sola línea. ¡Es espectacular! El resto es rutinario, las formalidades de siempre, si hemos conseguido o no disponer de acceso al puerto serie que teníamos configurado. Casi no me atrevo a comentar que devolvemos un boolean que responde a verdadero si la operación se ha realizado con éxito y falso si ha fracasado. Cómo no, cualquier excepción quedará registrada en el apartado de aplicación del visor de sucesos. ' ' Crear el objecto de acceso Serie Public Function AbrirPuertoSerie() As Boolean ' If PuertoSerieAbierto Then Serie.Close() 'constructor del objecto serie Try 'Leer los valores para abrir el puerto desde archivo app.config NombreDelPuerto = Aplicacion.LeerConfig _ ("app.config", "PuertoSerie_Nombre", NombreDelPuerto).InnerText VelocidadPuerto = Aplicacion.LeerConfig _ ("app.config", "PuertoSerie_Velocidad", VelocidadPuerto).InnerText ' NumeroPuertoAbierto = Val(NombreDelPuerto.Substring(3, 1)) Serie = My.Computer.Ports.OpenSerialPort _ (NombreDelPuerto, VelocidadPuerto, IO.Ports.Parity.Even, _ 7, IO.Ports.StopBits.Two) UltimoMensajeError = NombreDelPuerto & "/" & VelocidadPuerto & "Open" PuertoSerieAbierto = True Catch ex As Exception 'registrar mensage en caso de error Mensaje.Registra("ComSerie", ex.Message, EventLogEntryType.Error, 5) NumeroPuertoAbierto = 0 UltimoMensajeError = ex.Message PuertoSerieAbierto = False End Try Return PuertoSerieAbierto End Function Fuente 7.29. Función AbrirPuertoSerie página 96 Robot dispensador para MSDN Vídeo >> Acceso a una base de datos Access Llegados a este punto podríamos pensar que ya hemos completado la codificación de todas las clases que serán necesarias, tales como enviar correos, leer y grabar valores en documentos XML de configuración, simular los mecanismos del ingenio, etc. Sin embargo, es habitual tener necesidad de intercambiar datos con algún otro tipo de archivo… ¡cómo no!, los MDB. Sabemos su utilidad en ciertos proyectos donde no se requieren complejas estructuras o gran cantidad de datos. Además las bases de datos de Access nos permiten disponer un buen nivel de intercambio para compatibilizar aplicaciones que comparten datos. Si bien, más a título de ejemplo, que como necesidad básica de esta librería, vamos también a implementar una clase que nos añada la funcionalidad de acceso a datos usando OLEDB. En nuestra aplicación realizaremos simplemente el registro de las cápsulas introducidas, con su correspondiente ISBN y NIFCIF, anotando simplemente si la capsula en cuestión está "Dentro" o "Fuera" del dispensador. figura 7.7 página En esta clase simplemente necesitaremos diseñar dos funciones, la de expulsar y la de introducir. 97 << Robot dispensador para MSDN Vídeo Para el acceso a un archivo Access deberemos disponer de una conexión a y un adaptador a OleDbAdapter. OleDbConnection Completado este punto será tan sencillo como usar el UpdateCommand del adaptador para definir el Update del CommandText y su correspondiente ExecuteNonQuery. La función expulsar nos pide dos valores: el ISBN y el NIFCIF. Cuando lanzamos la expulsión de la cápsula desde el dispensador, éste intenta actualizar el valor de la misma a FALSE (ver SET DENTRO = FALSE). Si lo consigue (ExecuteNonQuery nos devuelve un valor distinto de 0) la operación se considera satisfactoria, en caso contrario reportaremos un mensaje alertando de que no existe la cápsula solicitada al dispensador. De nuevo, ¡destacar la gran sencillez! ' 'Expulsar capsula y actualizar estado a 'false'(solo si esta en el dispensador) ' Public Function Expulsar(ByVal ISBN As String, ByVal NIFCIF As String) As Integer Try Conexion.Open() Adaptador.UpdateCommand = New System.Data.OleDb.OleDbCommand Adaptador.UpdateCommand.Connection = Conexion Adaptador.UpdateCommand.CommandText = _ "UPDATE Capsulas SET DENTRO = False WHERE (ISBN = '" + ISBN + "') _ AND (NIFCIF = '" + NIFCIF + "') AND (DENTRO = True)" Adaptador.UpdateCommand.CommandType = System.Data.CommandType.Text Expulsar = Adaptador.UpdateCommand.ExecuteNonQuery() Conexion.Close() Catch ex As Exception Return 0 End Try End Function Fuente 7.30 Abordaremos nuestra recta final, con la función Introducir. No podremos evitar incrementar la complejidad, pues en esta operación necesitaremos realizar algunas operaciones más que en la anterior. A diferencia de cuando expulsamos una cápsula, que esta se encuentra registrada o no, cuando la introducimos pueden producirse otras situaciones. Como por ejemplo que la cápsula no esté registrada en la base, que la cápsula ya se encuentre dentro del dispensador o que la cápsula no pueda actualizar su estado. Para cubrir todas las posibilidades deberemos prever estas situaciones dotando a esta función de la habilidad suficiente para página 98 Robot dispensador para MSDN Vídeo >> insertar un nuevo registro cuando no exista el mismo o bien actualizarlo en caso de encontrarse en la base. Por este el motivo deberemos contemplar los mandatos de INSERT y UPDATE. Veamos también cómo usamos el viejo recurso de retornar un valor “888” o “999” para señalizar una situación de error. En nuestra función Introducir. ' ' Introducir una capsula, comprovando si esta existe para marcarla a 'true' o creando registro en caso contrario ' Public Function Introducir(ByVal ISBN As String, _ ByVal NIFCIF As String) As Integer '--------------------------------------------------------------'No se contempla que un cliente pueda alquilar la misma pelicula ' Retorna : ' 0 = Si no puede actualizar la Capsula ' 1 = Si el proceso se ha efectuado con normalidad ' 888 = Existe duplicacion de capsulas ' 999 = Si existe un conflicto de estado o capula registrada dentro Dim Datos As DataSet = New DataSet() Dim Encontrados() As DataRow Try Adaptador.Fill(Datos, "Capsulas") Encontrados = Datos.Tables("Capsulas").Select("(ISBN = '" + ISBN + "') _ AND (NIFCIF = '" + NIFCIF + "')") If Encontrados.Length = 0 Then If ISBN.Length = 13 And NIFCIF.Length = 9 Then Conexion.Open() Adaptador.InsertCommand = New System.Data.OleDb.OleDbCommand Adaptador.InsertCommand.Connection = Conexion Adaptador.InsertCommand.CommandText = _ "INSERT INTO Capsulas (ISBN, NIFCIF, DENTRO) VALUES ('" + _ ISBN + "'," + " '" + NIFCIF + "', True)" Adaptador.InsertCommand.CommandType=System.Data.CommandType.Text Introducir = Adaptador.InsertCommand.ExecuteNonQuery() Conexion.Close() End If Else If Encontrados.Length > 1 Then página Fuente 7.30 (continua...) 99 << Robot dispensador para MSDN Vídeo Introducir = 888 Else If Encontrados(0).Item(3) = True Then Introducir = 999 Else Conexion.Open() Adaptador.UpdateCommand = New System.Data.OleDb.OleDbCommand Adaptador.UpdateCommand.Connection = Conexion Adaptador.UpdateCommand.CommandText = _ "UPDATE Capsulas SET DENTRO = True WHERE (ISBN = '" + _ ISBN + "') AND (NIFCIF = '" + NIFCIF + "')" Adaptador.UpdateCommand.CommandType = _ System.Data.CommandType.Text Introducir = Adaptador.UpdateCommand.ExecuteNonQuery() Conexion.Close() End If End If End If Catch ex As Exception Return End Try End Function (... continuación) Fuente 7.30 Bien, queda claro que todo tiene un final y por fin podemos decir que nuestra librería contiene todas las clases. Sólo nos restará ejecutar el proceso de “Build” para obtener nuestra elaborada y merecida ServidorComm.dll. Antes de generar nuestra librería, resulta de mucha utilidad rellenar la información del ensamblado, donde se detallan los datos más relevantes de nuestro desarrollo, tales como el título, descripción, compañía, producto, copyright, marca, versión y el identificador GUID. Todo ello podemos encontrarlo en la pestaña de “Application” dentro de “My Project” (ver figura 7.8). ¡Tengo la librería! Pero ¿cómo la puedo probar antes de usarla con el dispensador real de MSDN Vídeo? página 100 Robot dispensador para MSDN Vídeo >> página figura 7.8 101 capítulo 8 El ClienteEPlus La solución ClienteEPlus A estas alturas más de uno estará pensando que tiene una sensación un tanto extraña, nos hemos dedicado a plantear una librería entera ¡sin tan siquiera probarla!, me confesaré: esta situación idílica es sólo posible para unos pocos privilegiados, quizás sólo algunos de nuestros grandes maestros como..., por no hablar de otros. Pero aún y así me resisto a pensar que sea cierto. Los libros como las películas nos presentan una situación perfecta dendro de un escenario donde no cabe posibilidad de error. Es evidente que a pesar de que muchos arquitectos sean capaces de planificar su desarrollo a niveles tan elevados que incluso puedan codificar una librería a pelo, tal como si estubiéramos hablando de unos cimientos, la mayoría de mortales necesitamos construir las bases de nuestros cimientos desde el primer piso. Sé que esto suena muy mal y aún con riesgo de que me excomulgen de mi apreciada comunidad de MVP, es cierto que antes de abordar la organización de la librería resulta muy útil contruirla y probrala desde el cliente, de manera que vamos organizando y encapsulando los conjuntos de código o spnnipeds que resultan útiles y comunes al resto de la aplicación. Por lo tanto, a estas alturas debo confesarme, el desarrollo práctico lo hubiéramos empezado definiendo las necesidades del cliente y, a partir de éstas, codificando el contenido de la librería, resumiendo, estaríamos construiendo nuestro cliente y nuestra librería de una manera paralela. página Sin embargo, debemos consolarnos pensando en la existencia de un montón de “teóricas” y “buenas prácticas” que quedan muy bien entre comillas. Por de pronto, lo interesante es avanzar en un increíble entorno integrado de desarrollo, tampoco era nuestra intención abordar técnicas avanzadas de codificación, me gustará entonces reforzar la idea de que este libro debe servirnos como modelo para iniciar o migrar sin complejos nuestras aplicaciones. Eso sí, siendo conscientes de las limitaciones de migracion de grandes aplicaciones. 103 << Robot dispensador para MSDN Vídeo Me gustaría pues desde aquí dar ánimos para iniciar nuestra aventura a todos aquellos que dudan y se sienten atrapados en antiguos diseños; no conozco ningún entorno de programacion mas productivo que Visual Studio 2005 y os aseguro que no estoy haciendo marketing. Me he pasado media vida programando y es increíble ver cómo conseguimos nuestros objetivos practicamente codificando cuatro líneas, recordando la aureola de los programadores y su mito me cuesta creer que yo soy uno de ellos, pues realmente con estos entornos más bien me asemejo a un usuario avanzado. Os estoy diciendo que ¡es muy fácil! Bien, vamos a darle forma a nuestro cliente, será una buena experiencia iniciar la construccion de nuestros contenedores con el nuevo diseñador de WinForms (impresionante la colección de controles, listos y a punto para ser usados). La gran difierencia con otras versiones es que ¡menudos controles!, nada parecido hasta la fecha, por lo tanto, ¡venga! Con cuatro arrastrar y soltar veremos un formulario repleto de cositas… figura 8.1 página 104 Robot dispensador para MSDN Vídeo >> Debo hacer hincapié y destacar la utilidad del Document Outline (ver a la izquierda del diseñador), imprescindible para llevar a cabo una nomenclatura eficiente y coherente. Su navegación permite asignar los nombres con una facilidad y agilidad suficiente para que nunca más utilicemos los por defecto Label1, Command1... figura 8.2 La aplicación del cliente se ha divido en tres partes: una superior conteniendo un control de tablas con un primer marco que contiene los botones de solicitud de introducción y expulsión, juntamente con las cajas de texto para informar el ISBN y el NIFCIF; la segunda pestaña del control tab contiene una lista que se llenará con los textos correspondientes a la solicitud de estado del Entregon+. Finalmente la última pestaña contiene los textboxes necesarios para informar de la configuración de envío de correos electrónicos automáticos al detectar una alarma grave. página La parte media contiene un marco con las etiquetas que se utilizan para visualizar todos los mensajes de la aplicación, así como las específicas para monitorizar las tramas enviadas y recibidas. Este marco así como el inferior están siempre visibles. 105 << Robot dispensador para MSDN Vídeo figura 8.3 figura 8.4 La parte inferior contiene el control del puerto serie y sus mensajes así como la posibilidad de seleccionar el puerto con el que queremos trabajar. Deberán permitirme no detallar paso a paso cómo crear este proyecto con Visual Studio, pues está fuera del alcance de nuestro propósito, quizás motivo de un futuro anexo. A grandes rasgos nos interesa crear una solución que contenga dos proyectos: ServidorComm y por supuesto ClienteEPlus. He decido incluir la librería ServidorComm pues nos ayudará a depurar los errores cometidos al diseñar o teclear nuestras clases, sin embargo, podríamos afrontar el desarrollo del cliente, simplemente añadiendo la referencia a nuestra ServidorComm.dll… ¡pero es menester ser prácticos! Nuestro proyecto ClienteEPlus va a incluir dos archivos vitales para su funcionamiento, estos son el app.config con todos los valores de configuración de la aplicación (incluidos los nuestros) y el Dispensador.mdb que no es más que la base de datos que contiene la referencia de las películas que están en el interior del dispensador o las que han sido entregadas. Además como recurso también incluiremos el icono de la aplicación App.ico. página 106 Robot dispensador para MSDN Vídeo >> Seguramente, por coherencia, estos ficheros deberían estar bajo el control de las librerías ServidorComm.dll. Sin embargo, por cuestiones prácticas, he decidido dejarlas bajo el proyecto del cliente, al fin y al cabo, el resultado de la compilación dejará todos sus componentes en la misma carpeta. Este va a ser el aspecto de nuestra solución: figura 8.5 Esta es la estructura de la base de datos Dispensador.mdb. página figura 8.6 107 << Robot dispensador para MSDN Vídeo Y este es su icono: figura 8.7 Es un gustazo poder editar los recursos en Visual Basic tal como hacen los buenos en C++. No olvidar el uso de un modelo de cabecera común en nuestros items, que identifique cada pieza dentro de nuestro desarrollo. En esta solución, como ya habréis observado, he optado por configuración que detalla la información mas relevante en cuanto al curso del desarrollo. Ni más ni menos que nuestro historial clínico. ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' Proyecto Nombre Tipo Clase Creado Autor Version : : : : : : : Entregon+ ClienteEPlus.vb Aplicacion Windows frmClienteEPlus 8.05.2005 Pep Lluis 1.0 Historia y Revisiones -----------------------------------------------------08.05.2005 - Ejemplo cliente, aplicacion Entregon+ (Incluye librerias) para enlazar con la aplicación del Dipensador DesarrollaConMsdn. Esta aplicacion se entiende unica y exclusivamente como modelo de utilizacion y pruebas de las librerias ServidorCom.dll 14/15.05.05- Fin de Escritura y testeo de la aplicacion 24.07.05- Incorporar control disponibilidad capsulas Introduccion/expulsion 08.05- Liberar la aplicacion al completo Pendiente : Revisar el tratamiento de errores de la libreria ServidorComm Fuente 8.1 página 108 Robot dispensador para MSDN Vídeo >> En la siguiente figura veremos una vista general del código. Quiero resaltar la importancia de las primeras definiciones, donde creamos instancias a los objetos expuestos por nuestra clase ServidorComm. Vista general del código ClienteEPlus.vb para el formulario de nuestro cliente. figura 8.8 Tal como comentábamos anteriormente es importante el uso de cabeceras que describen nuestro “Historial Clínico”, tan importante como agrupar las funciones dentro de nuestras regiones de la aplicación. página Fijaros que son dos aspectos muy simples de observar, sin embargo, de vital importancia para el trabajo en equipo. Marcar un modelo que identifica un estilo de programación, compromete a los ingenieros involucrados en el proyecto a usar una metodología común, que facilita extraordinariamente la lectura y localización del código. Condiciones primordiales para conseguir un desarrollo eficiente y un mantenimiento de aplicaciones asequible. Pero subrayo: aplicaciones hechas en equipo. 109 << Robot dispensador para MSDN Vídeo No es de extrañar que haga uso de esta receta agrupando los procedimientos en torno a cuatro regiones: Cargar/liberar formulario, recepción y procesado del evento respuesta, interacción con el usuario y, finalmente, rutinas de uso general. El primer grupo contiene la ejecución del código en tiempo de carga y descarga del formulario, que normalmente realiza operaciones de preparación efectuando las inicializaciones necesarias para la puesta en marcha de la aplicación. La segunda, recepción y procesado, contiene la subrutina que recibirá el disparo del evento en tanto disponemos de una respuesta a nuestra petición. Esta respuesta será analizada y posteriormente procesada, no sin comprobar la validez de sus datos. El tercer grupo contiene el código que se ejecuta a petición del usuario, a saber, cuando pulsa el botón para la solicitud de expulsión, el botón de abrir el puerto serie, etc. Finalmente no es mala idea incluir una región que contiene todos los procedimientos de uso general en nuestro formulario y que no son parientes directos de ninguno de los anteriores. Veamos todo esto con mayor detalle. Repasaremos las operaciones que el cliente debe realizar en tiempo de carga/descarga del formulario. ' ' Al cargar el formulario ' Private Sub frmClienteEPlus_Load(ByVal sender As Object, ByVal e As _ System.EventArgs) Handles Me.Load 'imagen estado ko. del puerto serie Visible Me.pbxKo.Visible = True 'direccionar los eventos de respuesta a la sub DatosRecibidos AddHandler PuertoSerie.Respuesta, AddressOf DatosRecibidos End Sub Fuente 8.2 ' ' Liberar objetos instanciados ' Private Sub frmClienteEPlus_Disposed(ByVal sender As Object, ByVal e As _ System.EventArgs) Handles Me.Disposed PuertoSerie = Nothing 'Liberar puerto serie Campos = Nothing 'liberar constructor formato tramas Aplicacion = Nothing 'liberar acceso archivo configuraciones app.config End Sub Fuente 8.3 página 110 Robot dispensador para MSDN Vídeo >> Podría parecer exceso de recelo, pero no liberar el puerto serie, probablemente provocaría que éste no pudiera ser usado por otra aplicación; son detalles aparentemente ligeros pero de cierta relevancia si tenemos en cuenta una buena interacción con el sistema. A menudo caemos en la equivocación de desarrollar pensando solamente en la aplicación y no en su anfitrión. Continuaremos describiendo cómo el cliente va a procesar las respuesta a sus solicitudes. ' ' Recepcion de respuestas ' Public Sub DatosRecibidos() miRespuesta = Me.PuertoSerie.Recepcion 'los datos recibidos 'Procesar y Visualizar la recepcion en la etiqueta Me.lblRespuesta.Invoke(New EntreHilos(AddressOf ProcesolblRespuesta)) If Campos.LaTramaEstaCompleta(miRespuesta) Then 'Si la trama es completa 'procesar la respuesta, mostrando incidencias Me.lblInicidencia.Invoke(New EntreHilos(AddressOf procesolblInicidencias)) End If End Sub Fuente 8.4 Destacar el enlace definido con el manipulador del evento PuertoSerie.Respuesta y la función DatosRecibidos en tiempo de carga. También es importante observar cómo en el DatosRecibidos invocamos al procedimiento ProcesolblRespuesta y, si además la trama esta completa, al procesolblInicidencias (fuente 8.5). ¡Salta a la vista! Si la respuesta recibida está completa, continuaremos leyendo y procesando la misma según su código de operación; en caso contrario, visualizaremos la respuesta mandando el “fuera de servicio” a la etiqueta de incidencias. Lo sabíamos de antemano, pero aquí lo plasmamos. Sólo hemos implementado tres operaciones: “01”, “02”, “03”. página Las dos primeras tienen la importante función de actualizar nuestra base de datos para que ésta refleje el estado de nuestras capsulas... DENTRO=Verdadero o Falso, en función de si se expulsa o se introduce, además asigna la trama recibida a la etiquetas de respuesta para que ésta sea visualizada en nuestra aplicación cliente. La tercera operación, además de visualizar la trama recibida efectúa una llamada LlenarListaIndicadores que traducirá los códigos de incidencia en recibidos en hexadecimal a cadenas de texto que sean interpretables por los humanos. 111 << Robot dispensador para MSDN Vídeo ' ' Procesar la recepcion segun los codigos de respuesta ' Private Sub ProcesolblRespuesta() If Campos.LaTramaEstaCompleta(miRespuesta) Then 'Con la respuesta completa Select Case Campos.CodigoOpe Case "00" 'Codigos operacion 00 ' Simplemente visualizamos respuesta Me.lblRespuesta.Text = "(" + miRespuesta + ")" Case "01" 'Codigo 01 - Expulsar ' CRAccesoBDS = MarcarBD.Expulsar(Me.txtISBN.Text, Me.txtNIFCIF.Text) Me.lblRespuesta.Text = "(" + miRespuesta + ")" Case "02" 'Codigo 02 - Introducción ' CRAccesoBDS = MarcarBD.Introducir(Me.txtISBN.Text, Me.txtNIFCIF.Text) Me.lblRespuesta.Text = "(" + miRespuesta + ")" Case "03" 'Codigo 03 - Estado 'Llenamos la lista de indicadores estado Me.lblRespuesta.Text = "(" + miRespuesta + ")" Indicadores = "&h" + Campos.Indicadores LlenarListaIndicadores() Me.lblInicidencia.BackColor = Me.BackColor Case Else 'Ignorar los codigos de operacion desconocidos Me.lblRespuesta.Text = "Error Codigo :" & Campos.CodigoOpe End Select Else 'Sin respuesta completa reportamos error Me.PnlExpulsion.BackColor = Color.Red Me.lblRespuesta.Text = "(" + miRespuesta + ")" Me.lblInicidencia.Text = "El mecanismo entrega, esta fuera de Servicio" End If End Sub Fuente 8.5. ProcesoRespuesta ' ' Procesar los codigos fin de operacion ' Private Sub procesolblInicidencias() ' ' Asignar texto segun la correspondencia de codigos / literales ' Nota: este codigo deberia ser leido de una base externa, no se incluye ' aquí para simpificar su entendimiento Fuente 8.6. ProcesolblInicidencia (continua...) página 112 Robot dispensador para MSDN Vídeo >> Select Case Campos.CodigoFin ' Case "10" If Campos.CodigoOpe = "01" Then If CRAccesoBDS = 1 Then 'Codigo retorno acceso a la BD Me.txtISBN.Text = "" Me.txtNIFCIF.Text = "" Me.lblInicidencia.Text = "La Capsula ha sido Entregada." Else Me.lblInicidencia.Text = "Capsula no se encuentra en el _ dispensador." End If Else If Campos.CodigoOpe = "02" Then Select Case CRAccesoBDS 'Codigo retorno acceso a la BD Case 0 Me.lblInicidencia.Text = "Imposibe actualizar la BD_ Vaya al mostrador." Case 888 Me.lblInicidencia.Text = "Existen capsulas _ Duplicadas." Case 999 Me.lblInicidencia.Text = "Conflicto, la capsula _ ya esta en el dispensador" Case Else Me.txtISBN.Text = "" Me.txtNIFCIF.Text = "" Me.lblInicidencia.Text = "La Capsula ha sido _ Introducida." End Select Else Me.lblInicidencia.Text = "Codigo retorno: " + Campos.CodigoOpe End If End If Case "11" : Me.lblInicidencia.Text = "Existe una alarma en el sistema, _ Lea Su estado!" + Chr(10) + Chr(13) + _ "Atencion: se ha enviado una alerta por email" Case "20" : Me.lblInicidencia.Text = "Producto en Stock Minimo" Case "21" : Me.lblInicidencia.Text = "Entregado el Ultimo producto" Case "22" : Me.lblInicidencia.Text = "Se estan terminando las capsulas" Case "23" : Me.lblInicidencia.Text = "Se han detectado errores _ intermitentes en el Dispensador" Case "24" : Me.lblInicidencia.Text = "No es posible localizar el _ producto" Case "25" : Me.lblInicidencia.Text = "Imposible recoger el producto" Case "26" : Me.lblInicidencia.Text = "Imposible apilar capsula almacen" página Fuente 8.6. ProcesolblInicidencia (continua...) 113 << Robot dispensador para MSDN Vídeo Case "27" : Me.lblInicidencia.Text = "Problemas con el eje del cabezal" Case "28" : Me.lblInicidencia.Text = "Atasco en el circuito de Distribucion" Case "29" : Me.lblInicidencia.Text = "Falla Lector Etiquetas electronicas" Case "30" : Me.lblInicidencia.Text = "Falla lectura etiqueta electronica" Case Else : Me.lblInicidencia.Text = "" End Select ' ' Asignar colores de mensages segun categoria e incidencia Select Case Campos.CodigoFin Case "10" Me.PnlExpulsion.BackColor = Color.GreenYellow Case "11", "24", "25", "26", "27", "28" Me.PnlExpulsion.BackColor = Color.OrangeRed Case "21", "22" Me.PnlExpulsion.BackColor = Color.Blue Case Else Me.PnlExpulsion.BackColor = Color.Orange End Select (...continuación) Fuente 8.6. ProcesolblInicidencia En el proceso de incidencias implementamos la interpretación del código de fin de operación, sabemos que el Entregon+ nos devuelve este valor después del intercambio de tramas. Este código nos informa realmente de lo que ha sucedido con nuestra solicitud. Destacar la verificación que se efectúa con CRAccesoBDS (código de retorno después del acceso a la base de datos) y siempre dentro del CodigoFin=10, pues como bien sabéis representa que la operación se ha realizado con éxito. El resto de códigos simplemente asignan a la etiqueta de incidencias el texto de error que corresponde a ese código y finalmente asigna el color amarillo, rojo naranja, azul o naranja al fondo del panel, según sea la gravedad del mensaje. La distribución del código dentro del formulario se ha llevado a cabo según las funciones del mismo. Observaremos cuatro regiones: carga/descarga, interacción con el usuario, proceso y rutinas generales. Les toca el turno a las rutinas generales. Evidentemente ésta respondería al proceso de una solicitud de estado “03” (fuente 8.7). Simplemente rellenamos la lista de indicadores con los mensajes de error reportados en la última trama recibida y que están activos. página 114 Robot dispensador para MSDN Vídeo >> ' ' Rellenar la lista de indicadores despues de una solicitud de Estatus ' Private Sub LlenarListaIndicadores() Static MemoriaIndicadores As Integer 'Evitar refrescos si no ha cambiado el contenido If MemoriaIndicadores = Indicadores Then Exit Sub MemoriaIndicadores = Indicadores 'memorizar el estado actual Me.lstIndicadores.Items.Clear() If Indicadores And Codigo.AlmacenCapsulasVacio Then Me.lstIndicadores.Items.Add("CAPSULAS AGOTADAS EN EL DISPENSADOR") If Indicadores And Codigo.AlmacenProductoVacio Then Me.lstIndicadores.Items.Add("DISPENSADOR, SIN PRODUCTO") If Indicadores And Codigo.AtascoDispensadorEntrada Then Me.lstIndicadores.Items.Add("ATASCO MECANISMO DE ENTRADAS DISPENSADOR") If Indicadores And Codigo.AtascoDispensadorSalida Then Me.lstIndicadores.Items.Add("ATASCO MECANISMO DE SALIDAS DISPENSADOR") If Indicadores And Codigo.AtascoPinzaDispensadora Then Me.lstIndicadores.Items.Add("PINZA DISPENSADORA ATASCADA DISPENSADOR") If Indicadores And Codigo.AveriaEjeCabezal Then Me.lstIndicadores.Items.Add("EJE CABEZAL AVERIADO UNIDAD") If Indicadores And Codigo.AveriaEnPinzaDispensadora Then Me.lstIndicadores.Items.Add("PINZA DISPENSADORA AVERIADA UNIDAD") If Indicadores And Codigo.AveriaEnPlacaControladora1 Then Me.lstIndicadores.Items.Add("SUBSTITUIR PLACA CONTROLADORA 1") If Indicadores And Codigo.AveriaEnPlacaControladora2 Then Me.lstIndicadores.Items.Add("SUBSTITUIR PLACA CONTROLADORA 2") If Indicadores And Codigo.DisparoDetectorAntivandalico Then Me.lstIndicadores.Items.Add("SE HA DISPARADO LA ALARMA DE ANTIVANDALICA") If Indicadores And Codigo.FalloEnProcesador1 Then Me.lstIndicadores.Items.Add("HA FALLADO PROCESADOR PRINCIPAL ENTREGON+") If Indicadores And Codigo.FalloEnProcesador2 Then Me.lstIndicadores.Items.Add("HA FALLADO PROCESADOR SECUNDARIO ENTREGON+") If Indicadores And Codigo.SobreTemperatura Then Me.lstIndicadores.Items.Add("SOBRETEMPERATURA ENTREGON+") If Indicadores And Codigo.SuciedadOpticaPosicionadorLaser Then Me.lstIndicadores.Items.Add("LIMPIAR/ALINEAR LASE DEL ENTREGON+") End Sub Fuente 8.7 página Nuestro formulario contiene un temporizador de un segundo, por lo tanto después de este intervalo se ejecuta esta función que básicamente se encargará de refrescar la información más relevante para el usuario, tales como la presentación de los bits transmitidos/recibidos, así como hacer visibles o invisibles los paneles de errores, des- 115 << Robot dispensador para MSDN Vídeo pués de haberlos visualizados durante un tiempo determinado, como también el control de pequeñas filigranas que hacen más atractiva la interacción de la aplicación con el usuario. No es necesario analizar el código con más detalle, pues a pesar de su aspecto estético, no lo considero de interés técnico... recordemos que nuestro objetivo es el correcto uso de las librerías del Entregon+. Tampoco quiero aconsejar estas técnicas para mantener el aspecto y refresco de información en pantalla; como siempre es una solución de compromiso y, como digo, nos interesa concentrarnos en el cómo. ' ' Actualizar los datos de nuestro formulario ' Private Sub tmpRefresco_Tick(ByVal sender As System.Object, ByVal e As _ System.EventArgs) Handles tmpRefresco.Tick On Error Resume Next Static Veces As Integer, Mensaje As String, VecesRegistro As Integer 'Filigranas relacionadas con refresco, intermitencia de colores y mensajes If Me.lblInicidencia.Text.Length > 0 Then If Mensaje <> Me.lblInicidencia.Text Then Veces = 0 Veces += 1 If Veces < 11 Then 'Durante 10 segundos visualizar mensaje en el centro e intermitente Mensaje = Me.lblInicidencia.Text Me.lblInicidencia.TextAlign = ContentAlignment.MiddleCenter If Me.lblInicidencia.ForeColor = Me.PnlExpulsion.BackColor Then Me.lblInicidencia.ForeColor = Color.Black Me.lblInicidencia.BackColor = Me.BackColor Else Me.lblInicidencia.ForeColor = Me.PnlExpulsion.BackColor Me.lblInicidencia.BackColor = Color.Black End If Else 'Pasados 10 segundos apagar intermitencia y alinear a la izquierda Me.lblInicidencia.TextAlign = ContentAlignment.TopLeft End If Else Veces = 0 End If ' If Me.PuertoSerie.PuertoSerieAbierto Then 'Permitir solicitudes y dejar visible la imagen de puerto OK. Me.btnIntroducir.Enabled = True Me.btnExpulsar.Enabled = True Fuente 8.8. (continua...) página 116 Robot dispensador para MSDN Vídeo >> Me.pbxKo.Visible = False Me.pbxOk.Visible = True Else 'Denegar solicitudes y dejar visible la imagen de puerto KO. Me.btnIntroducir.Enabled = False Me.btnExpulsar.Enabled = False Me.pbxKo.Visible = True Me.pbxOk.Visible = False End If Me.pbxTry.Visible = False 'Solo visible en el momento de abrir el puerto ' 'Asignar mensajes del PuertoSerie a las etiquetas de nuestro 'Form' Me.lblTextoMensaje.Text = Me.PuertoSerie.UltimoMensajeError Me.lblBitsRecibidos.Text = Me.PuertoSerie.Recibidos.ToString Me.lblBitsEnviados.Text = Me.PuertoSerie.Recibidos.ToString ' 'Alertar de que existen mensajes en el EventLog If ServidorComm.RegistroAlertas.MensajesEnRegistro Then VecesRegistro += 1 Me.lblTextoMensaje.Text = Me.lblTextoMensaje.Text + _ Chr(10) + Chr(13) + "**Mensajes en Registro Aplicacion del Sistema" If VecesRegistro > 10 Then VecesRegistro = 0 ServidorComm.RegistroAlertas.MensajesEnRegistro = False End If End If ' 'Si no exiten mensajes, esconder la etiqueta visualizacion If Me.PuertoSerie.UltimoMensajeError.Length = 0 And _ ServidorComm.RegistroAlertas.MensajesEnRegistro = False Then Me.lblTextoMensaje.BorderStyle = BorderStyle.None Me.lblMensaje.Visible = False Else Me.lblTextoMensaje.BorderStyle = BorderStyle.Fixed3D Me.lblMensaje.Visible = True End If 'Asignar el valor del puerto al numeric Up/Down If Me.PuertoSerie.UltimoMensajeError.Substring(0, 3) = "COM" Then Me.nudPuerto.Value = Val(Me.PuertoSerie.UltimoMensajeError.Substring(3, 1)) End If If Me.nudPuerto.Enabled = False Then Me.nudPuerto.Enabled = True End Sub página (...continua) Fuente 8.8 117 << Robot dispensador para MSDN Vídeo En la recta final, sólo nos queda codificar la parte de interacción con el usuario, todas las rutinas detalladas a continuación, tienen en común la ejecución de código bajo demanda del usuario, o sea que sólo se ejecutan cuando el usuario ha llevado a cabo una determinada acción sobre los controles. ' ' Al Incrementar / Disminuir seleccion de puerto de trabajo ' Private Sub budPuerto_ValueChanged(ByVal sender As System.Object, ByVal e As _ System.EventArgs) Handles nudPuerto.ValueChanged 'Siempre que el valor se la seleccion se encuentre entre 1 i 8 If (Me.nudPuerto.Value < 9) And (Me.nudPuerto.Value > 0) Then 'guardar la seleccion de puerto en archivo app.config 'asi lo abriremos por defecto la proxima session Aplicacion.SalvarConfig("app.config", "PuertoSerie_Nombre", "COM" & _ Me.nudPuerto.Value.ToString) Else 'forzar a 1 cuando los valores esten fuera de rango nudPuerto.Value = 1 End If End Sub Fuente 8.9. Cuando el usuario pulsa el Up/Down del control numérico o introduce un valor en su entrada. ' ' Abrir el puerto de comunicaciones ' Private Sub btnAbrir_Click(ByVal sender As System.Object, ByVal e As _ System.EventArgs) Handles btnAbrir.Click 'a la pulsacion del boton de abrir Me.pbxTry.Visible = True 'visualizar imagen : intento abrir puerto Me.pbxKo.Visible = False 'esconder imagen Ko Me.pbxOk.Visible = False 'esconder imagen Ok Me.PuertoSerie.AbrirPuertoSerie() 'Abrir el puerto Serie End Sub Fuente 8.10. Cuando el usuario pulsa el botón de abrir el puerto seleccionado en el control Up/Down página 118 Robot dispensador para MSDN Vídeo >> ' ' Enviar un correo de pruebas ' Private Sub btnPrueba_Click(ByVal sender As System.Object, ByVal e As _ System.EventArgs) Handles btnPrueba.Click 'a la pulsacion de btnPruebas 'Instanciar la clase para envio de correo Dim Enviar As AlertasPorCorreo = New AlertasPorCorreo Enviar.Avisar("Prueba de Correo Automatizado") 'enviar el correo End Sub Fuente 8.11. Cuando el usuario pulsa el botón de prueba en el panel “AutoeMail” ' ' Leer la configuracion personal para el envio de alertas por email ' Private Sub btnLeer_Click(ByVal sender As System.Object, ByVal e As _ System.EventArgs) Handles btnLeer.Click 'a la pulsacion de btnLeer, asignar los contenidos guardados en app.config Dim email_From As String = _ Aplicacion.LeerConfig("app.config", "email_From", "emailOrigen").InnerText Dim email_To As String = _ Aplicacion.LeerConfig("app.config", "email_To", "emailDestino").InnerText Dim email_Cc As String = _ Aplicacion.LeerConfig("app.config", "email_Cc", "emailCopia").InnerText Dim email_User As String = _ Aplicacion.LeerConfig("app.config", "email_User", "CuentaUsuario").InnerText Dim email_Pwd As String = _ Aplicacion.LeerConfig("app.config", "email_Pwd", "password").InnerText ' Me.txtEmail_From.Text = email_From 'enseñar la cuenta de usuario If txtEmail_Pwd.Text = email_Pwd Then 'enseñar resto de datos si coinciden Me.txtEmail_To.Text = email_To Me.txtEmail_Cc.Text = email_Cc Me.txtEmail_User.Text = email_User Else Me.txtEmail_User.Text = "No coincide la clave" End If End Sub página Fuente 8.12.Al pulsar el botón leer (leemos la configuración para envío automatizado) panel “AutoeMail” 119 << Robot dispensador para MSDN Vídeo ' 'Guardar valores de configuracion personal, para el envio de alertas por email ' Private Sub btnSalvar_Click(ByVal sender As Object, ByVal e As _ System.EventArgs) Handles btnSalvar.Click 'a la pulsacion de btnSalvar, guardar valores de configuracion de los textbox Aplicacion.SalvarConfig("app.config", "email_From", Me.txtEmail_From.Text) Aplicacion.SalvarConfig("app.config", "email_To", Me.txtEmail_To.Text) Aplicacion.SalvarConfig("app.config", "email_Cc", Me.txtEmail_Cc.Text) Aplicacion.SalvarConfig("app.config", "email_User", Me.txtEmail_User.Text) Aplicacion.SalvarConfig("app.config", "email_Pwd", Me.txtEmail_Pwd.Text) End Sub Fuente 8.13. Guardaremos las entradas de texto informadas en el panel “Autoemail” al pulsar el botón “Salvar” ' ' Expulsar una capsula ' Private Sub btnExpulsar_Click(ByVal sender As System.Object, ByVal e As _ System.EventArgs) Handles btnExpulsar.Click 'a la pulsacion del btnExpulsar Dim peticion As Tramas.SolicitarExpulsion = New Tramas.SolicitarExpulsion 'Trama de expulsion Dim miPregunta As String = ""'Inicializar variables Me.lblInicidencia.Text = "" Me.lblRespuesta.Text = "" 'Componer la peticion utilizando los campos de entrada ISBN/NIFCIF miPregunta = peticion.Trama(2, 1, Me.txtISBN.Text, Me.txtNIFCIF.Text) Try Me.PuertoSerie.Enviar(miPregunta) 'Enviar peticion Me.lblPregunta.Text = "(" + miPregunta + ")" 'testigo peticion etiqueta Catch ex As Exception Me.lblInicidencia.Text = ex.Message 'En caso de problema.. Me.lblPregunta.Text = "(" + miPregunta + ")" End Try End Sub Fuente 8.14.Al pulsar el botón de expulsión, generaremos una solicitud de entregar una cápsula. página 120 Robot dispensador para MSDN Vídeo >> ' Introducir una capsula Private Sub btnIntroducir_Click(ByVal sender As System.Object, ByVal e As _ System.EventArgs) Handles btnIntroducir.Click 'a la pulsacion del btnIntroducir 'Trama de introduccion Dim peticion As Tramas.SolicitarIntroduccion = New Tramas.SolicitarIntroduccion 'Inicializar variables Dim misDatos As String = "" Me.lblInicidencia.Text = "" Me.lblRespuesta.Text = "" ' 'Sobrecarga para emular los datos (ISBN/DNI) del lector de etiquetas misDatos = peticion.Trama(2, 1, Me.txtISBN.Text, Me.txtNIFCIF.Text) 'Componer la peticion Try Me.PuertoSerie.Enviar(misDatos) 'Enviar peticion Me.lblPregunta.Text = "(" + misDatos + ")" 'testigo de la peticion Catch ex As Exception Me.lblInicidencia.Text = ex.Message 'En caso de problemas.. Me.lblPregunta.Text = "(" + misDatos + ")" End Try End Sub Fuente 8.15.Al pulsar el botón de introducción, generaremos la solicitud de devolución de una cápsula. ' Solicitar el estado del Entregon+, por aviso de incidencia Private Sub verEstatus(ByVal sender As System.Object, ByVal e As _ System.EventArgs) Handles tabControl.Click, tabControl.Enter 'al efectuar click sobre la pestaña de estatus del tabcontrol If Me.tabControl.SelectedIndex = 1 Then 'ver estado seleccionado 'Trama de estado Dim peticion As Tramas.SolicitarEstatus = New Tramas.SolicitarEstatus 'Componer peticion Dim misDatos As String = peticion.Trama(1, 2) Try PuertoSerie.Enviar(misDatos) 'Enviar peticion Me.lblPregunta.Text = "(" + misDatos + ")" 'testigo de la peticion Catch ex As Exception Me.lblInicidencia.Text = ex.Message 'En caso de problemas.. Me.lblPregunta.Text = "(" + misDatos + ")" End Try End If End Sub página Fuente 8.16.Al hacer un clic en la pestaña “Estatus” del control tab, generaremos una solicitud “03” para conocer el estado del Entregon+. 121 << Robot dispensador para MSDN Vídeo [F5] ¡No me lo puedo creer! En un tristrás hemos desarrollado una librería de enlace para el Entregon+ y además nos hemos entretenido implementado un cliente para probar y explotar sus funciones. Sí amigos, si hemos seguido los pasos correctamente, no hay nada que temer, he aquí nuestra recompensa, nos merecemos un [F5]. figura 8.9 Espero que esto sea todo y no me esté olvidando de nada, pero por si acaso, a los que después de sufrir, no obtengáis este resultado... me pongo a vuestra disposición para “lo que haga farta”, para cualquier consulta o pregunta relacionada con Entregon+, podréis contactar conmigo en [email protected]. página 122 capítulo 9 De beta 1 a beta 2 Después de este laboratorio, me gustaría comentaros unas sutiles diferencias con los anteriores laboratorios. Los tres primeros están desarrollados con la versión beta 1 de Visual Studio 2005, sin embargo, abordaremos la construcción de este último con la beta 2. Como siempre nos surgirá la pregunta: ¿qué ha cambiado ? y como siempre la respuesta: ¡pues no lo se! Sabemos que cualquier producto en fase beta o releases para la comunidad, no deben usarse con fines productivos, o sea nuestro caso, pero nos podemos permitir el lujo de juguetear con nuestro pequeño proyecto. La verdad es que este proyecto se inició con la Beta 1 y me gustaría comentaros a título de curiosidad qué partes quedaron afectadas en esta transición. Lo que es cierto y tal como estamos acostumbrados, la beta 2 no fue capaz de hacerlo rodar, sin solucionar un par de tonterías reportadas en la pestaña de errores. Y ellos fueron los siguientes: página figura 9.1 123 << Robot dispensador para MSDN Vídeo Como veréis a continuación ¡nada grave! SerialReceivedEventArgs pasó a llamarse SerialDataReceivedEventArgs. figura 9.2 ReceivedEvent paso a llamarse DataReceived figura 9.3 Lo más significativo fueron los warnings avisando de que el espacio de nombres dedicado a System.Web.Mail.SmtpMail está obsoleta y ésta va a ser deprecated (que no se lo que significa exactamente). página 124 Robot dispensador para MSDN Vídeo >> figura 9.4 página Nos recomienda encarecidamente que usemos el nuevo espacio de nombres System.Net.Mail. 125