Sei sulla pagina 1di 153

Sinopsis: Nadie duda de que uno de los futuros más prometedores

de nuestra industria se centrará en torno a las comunica-


ciones; en sintonía a ello y como preámbulo e iniciación al
desarrollo de aplicaciones con Visual Studio 2005, con Ro-
bot dispensador para MSDN Vídeo introducimos al lector
al fascinante munco de comunicaciones entre dispositivos,
aportando la parte más robótica del dispensador de vídeo
en Desarrolla con MSDN.

Los Cuadernos Técnicos de dotNetManía son una serie de


pequeños libros enfocados a temas concretos para progra-
madores y arquitectos de software de la plataforma .NET.
Cubren el hueco existente entre artículos muy específicos
en una revista especializada como dotNetManía o los gran-
des libros sobre temas genéricos.

Pep Lluís Baño desarrolla su actividad como


responsable de informática y jefe de proyecto
de un sistema de supervisión, control y adquisi-
ción de datos, es analista de sistemas y progra-
mador. Obtuvo el reconocimiento de Micro-
soft MVP Visual Developer en 2006. Es
presidente de SpainNet, un marco excelente
donde propiciar charlas y laboratorios. Ha se-
guido el programa DCE2003 hasta la tercera
estrella; colabora con artículos en las revistas del
sector, participa estrechamente y es miembro
del comité de mercadeo de INETA Latam (In-
ternatinal .NET Association). También es un pé-
simo escritor de literatura técnica (ver muestra
presente).
Luarna

Robot dispensador para MSDN Vídeo


© Pep Lluís Baño
© De esta edición: 2009, Luarna Ediciones, S.L.
www.luarna.com

Madrid, junio de 2009

Versión: 1.0 (23-06-2009)

«Cualquier forma de reproducción, distribución, comunica-


ción pública o transformación de esta obra solo puede ser rea-
lizada con la autorización de sus titulares, salvo excepción pre-
vista por la ley. Diríjase a CEDRO (Centro Español de Derechos
Reprográficos, www.cedro.org) si necesita fotocopiar o escane-
ar algún fragmento de esta obra».

Edición en papel

Primera edición en papel: 2005


Robot dispensador para MSDN Vídeo
© Pep Lluís Baño
© Netalia, S.L., 2005
Robot dispensador
para MSDN Vídeo

Pep Lluis Baño

nº 1
Índice

Introducción ..............................................................................7
1. Entregon+ ............................................................................11
1.1. Descripción..........................................................................11
1.2. Ensamblado .........................................................................13
1.3. Puesta en servicio ...............................................................14
2. El puerto serie .....................................................................15
2.1. El puerto serie .....................................................................15
2.2. Protocolo de comunicaciones DispeDataLink ................18
2.3. OSI Layer 1. La capa física.................................................19
2.4. OSI Layer 2. Enlace a datos ...............................................20
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! ............................................28
4. Programando .......................................................................29
4.1. Nuestra primera aplicación ................................................29
5.Programando con técnica ....................................................38
5.1. Programando con técnica ..................................................38
6. ServidorComm. La clase......................................................50
6.1. ServidorComm. Introducción a la clase ...........................50
7. ServidorCom.dll. Nuestro laboratorio ..............................59
7.1. ServidorCom.dll ..................................................................59
7.2. Formato tramas envío/recepción ......................................60
7.3. Petición/respuesta entrega de cápsula - 01........................61
7.4. Petición/respuesta introducción de cápsula - 02 ..............62
7.5. Petición/respuesta estatus del sistema - 03 .......................65
7.6. Códigos retorno y operación .............................................67
7.7. Códigos Indicadores de Incidencias...................................68
7.8. Cálculo del CRC (pasos previos)........................................69
7.9. El simulador.........................................................................88
7.10. Mensajes en el EventLog .................................................97
7.11. Avisos de incidenciasautomatizados ................................98
7.12. Los archivos de configuración........................................103
7.13. La gestión del puerto serie ............................................106
7.14. Acceso a una base de datos Access ................................116
8. El ClienteEPlus. La solución ...........................................122
8.1. ClienteEPlus .....................................................................122
8.2. [F5] ....................................................................................146
9. De beta 1 a beta 2 ..............................................................147
10. Conclusiones ....................................................................150
10.1. Conclusiones ...................................................................150
10.2. La reflexión (pero en serio) ............................................150
10.2. Dedicatorias ....................................................................151
10.2. Agradecimientos..............................................................151
Introducción

La idea de este “mini-libro”, nace a tren de dos iniciati-


vas. La primera de ellas era escribir un artículo que habla-
ra del puerto serie. Pocas veces desarrollamos temas rela-
cionados 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 dis-
positivos. O sea que somos una minoría. Desconozco el
interés que pueda representar para la audiencia en gene-
ral, sin embargo técnicamente me resulta insuficiente
escribir un pequeño artículo de cinco o seis páginas, repa-
sando y dando ejemplos genéricos e impersonales hablan-
do 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 apli-
cación que interactúe con un dispositivo electro-mecáni-
co 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 desenvolver-
se dentro de la plataforma .NET, entorno a una impeca-
ble aplicación práctica y en castellano.
Soy consciente que realizar esta puesta en escena, no
va a tener interés práctico para el gran grueso de desarro-
lladores, sin embargo quiero llamar su atención a un
mundo desconocido para la mayoría de ellos. Los acostum-
brados a las aplicaciones típicas de gestión con bases de
datos, conoceréis en los siguientes capítulos aspectos curio-
sos 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 simula-
dor 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.
En nuestro oficio y englobando a la totalidad de des-
arrolladores, 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 anti-
gua 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 con-
vencimiento 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 dis-
posición de explicaros, que todo el material que encontra-
reis 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 nece-
sita entender como funciona alguna de las nuevas presta-
ciones que aporta la plataforma.
En este punto me excuso, pues como digo, no he cui-
dado 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 esti-
lo de programación para los más sencillos, donde el obje-
tivo primordial es conseguir que los algoritmos funcionen
a buen ritmo, dejando de lado algunos purismos o cono-
cimientos profundos.
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 ejecu-
ció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 pre-
sentaros 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 can-
tantes 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, sim-
plemente es un “desparrame” de borradores. Os pido dis-
culpas, si a pesar de todo no me salvo de la quema y ¡ter-
mino siendo culpable!
Pep
capítulo 1
Entregon+

Descripción
Entregon+ pertenece a la familia de los prestigiosos
dispensadores del grupo Dispensotron, potentes robots dis-
pensadores 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 aburri-
das secretarias encargadas del tedioso control manual de
entradas/salidas de material, siendo un almacén inteligen-
te, sin llegar a serlo.
Fabricado en base a una potente aleación de alumi-
nio/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 incor-
porados en el cabezal.
• Juego de 1.000 (EE) preparadas para más de 1
millón de grabaciones.
• Localizador/dispensador de carcasas de alta velo-
cidad, 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.
Antes de dar alimentación al robot debe haber com-
pletado 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 tor-
nillos 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 dis-
tancia.
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án-
dolos 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
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 usi-


llo recoge cápsulas se empiezan a desplazar a lo largo de
los ejes (X, Y) iniciando a su velocidad mínima (una loca-
lización de cápsula por minuto) y finalizando a su veloci-
dad máxima (10 localizaciones minuto).
Esta prueba tiene una duración de unos 10 minutos,
al finalizar deberemos visualizar los resultados en la pan-
talla LCD, Test passed (80 seeks performet without errors).
Para otros mensajes consulte el apéndice de
“Resolución de problemas”.
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 pri-
mer procesador disponible conmuta las líneas de entra-
da/salida del conector serie RS232/RS485 a unos circui-
tos que convierten directamente las tensiones +/- 9/12V a
lógica TTL, permitiendo que el sistema continue traba-
jando 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

gráfico 2.1

Pin Entrada/Salida Nombre Descripción En inglés

1 Entrada DCD Detección de Portadora Data Carrier Detect

2 Entrada RD Recepción Receive Data

3 Salida SD Transmisión Send Data

4 Salida DTR Terminal Datos Apunto Data Terminal Ready

5 - GND Tierra Ground

6 Entrada DSR Datos Apunto Data Set Ready

7 Salida RTS Solicitud de Envió Request to Send

8 Entrada CTS Listo para enviar Clear to Send

9 Entrada RI Indicador de Llamada Ring Indicator

tabla 2.1 Patillas del conector serie


En el desarrollo, y a efectos de prueba, deberemos dis-
poner de un cable Test para efectuar la emulación de acce-
so 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 recep-
ció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
Un rápido análisis del gráfico 2.2 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 espe-
ra el bit de inicio (star), después del cual inicia la des-
seriacion de datos, marcada en intervalos exactos defini-
dos 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 corres-
pondiente 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 sobres-
crito 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.
En caso de no disponer de estos medios diríjase a una
tienda especializada de electrónica y pida este servicio.

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 conti-
nue 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.
No olvide colocar el conector de 9 contactos dentro
de sus tapas originales, ello contribuirá a una fácil inser-
ció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 encadena-
mos 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 man-
dar 7 u 8 bits de datos). Observaremos que la velocidad se
dobla en cada salto, ello se debe a la naturaleza del con-
trolador de comunicaciones que usa como patrón un cris-
tal 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 des-
carta algunos errores producidos por el ruido en la trans-
misió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”.
1
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.
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 can-
tidad de tiempo de parada al final de cada transmisión,
siendo sus valores los comprendidos entre 1, 1,5 ó 2 bits.
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 caracteri-
za 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 infe-
rior 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, pari-
dad, bits de datos y paridad. Entregon+, trabaja de prede-
terminadamente a la velocidad definida en los conmu-
tadores, sin paridad, ocho bits de datos y un bit de para-
da (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+, des-
arrolladas 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.
CONEXIÓN A EL SERVIDOR

gráfico 2.6

Elem. Componente Elem. Componente


Conector del cable de alimenta- Botón/indicador LED de ID
1 7
ción de la unidad
Indicador LED de fuente de ali-
2 8 Conector de vídeo
mentación
Compartimiento para fuente de
3 alimentación redundante de cone- 9 Conector de puerto paralelo
xión en caliente (opcional)
4 Conector Ethernet RJ-45 10 Conector de puerto serie
Pestillos extraíbles del conector
5 11 Conector del teclado
SCSI
6 Conectores del puerto USB (2) 12 Conector del ratón

tabla 2.2 Componentes del panel posterior


capítulo 3
La aplicación

OSI Layer 7. La aplicación


Una vez establecidos los parámetros de comunica-
ciones 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; anterior-
mente 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 blo-
ques, que a su vez conforman paquetes de información.
Nuestra dificultad “asíncrona” se produce al efec-
tuar una transmisión de datos, vinculada a una respues-
ta. Efectivamente cuando nosotros solicitamos informa-
ción a un dispositivo, esperamos una respuesta, he aquí
donde se complica nuestra tarea. Deberemos contemplar
diversas situaciones inherentes a este tipo de comunica-
ciones, 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.

¿Alguien desconecto el cable?,


¿Estará averiado el Entregon+?,
¿Se fue la Luz?

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 pers-
pectiva 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 desatendi-
da 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 sen-
cilla pero sólida estructura, que gestione la entrada y sali-
da de tramas. Veamos el siguiente ejemplo:

EJEMPLO

gráfico 3.1
System.IO.Ports ¡El Espacio!
Detalle del espacio de nombres:

Clases Descripción

SerialErrorEventArgs Especifica los argumentos enviados al evento ErrorEvent.

SerialPinChangedEventArgs Especifica los argumentos enviados al evento PinChangedEvent.

SerialReceivedEventArgs Representa el recurso del puerto serie.

SerialReceivedEventArgs Especifica los argumentos enviados al evento ReceivedEvent.

Enumeraciones Descripción

Especifica el protocolo de control usado para establecer al objecto


Handshake
SerialPort en las comunicaciones con el puerto serie.

Parity Especifica el tipo de paridad utilizada en el objeto SerialPort.


SerialErrors Especifica los errores que pueden ocurrir en el objecto SerialPort.
SerialPinChanges Especifica cambios de señal detectados en el objecto SerialPort.
SerialReceived Especifica los caracteres que se han recibido en el puerto serie.
StopBit 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 Espacio de nombres para la utilización de los puertos serie. Framework 2.01

1
Información del Longhorn SDK Beta 1
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 funcio-
nes 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 comple-
jidad 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 apli-
cación.
Objetivo
Una vez completado este laboratorio seremos capa-
ces 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
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”).
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ña-
dor 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 acce-
so 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.
#Region "Carga / Descarga del Formulario"
#End Region
.
.
#Region "Envio / Recepcion de tramas"
. etc.

Fuente 4.1

figura 4.2
figura 4.3

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, eviden-
temente, cerrarlo al descargar el formulario.
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.
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.5

figura 4.4

Verifiquemos las salidas serie de nuestro equipo.


Como veis, olvidamos de verificar la asignación de nues-
tros puertos en el equipo.
Vayamos al “administrador del equipo”/“administra-
dor de dispositivos”, situémonos en el apartado de “Ports”
y veamos… (ver figura 4.6)
Obviamente COM1 no existe, entonces deberemos cam-
biar nuestra instrucción de OpenSerialPort("COM3"). De nuevo
pulsamos [F5]. Y de nuevo ¡OPPSS! (ver figura 4.7)
¡El puerto está siendo utilizado por otra aplicación!
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.
Vamos probar con el COM4, ¡parece ser que todo va a ir
bien! (ver figura 4.8)
figura 4.6

figura 4.7

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 cade-
na “¡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 conec-
tor 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 sali-
da serie asignada (en nuestro laboratorio, COM4), este conec-
tor 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 nues-
tro formulario form1.
En el Sub Form_Load, añadiremos dos líneas de inicializa-
ción del timer de manera que leamos:

'Ejecutar cuando se Carga el formulario


Private Sub Form1_Load(ByVal sender A s Object, ByVal e A s . . .
Try
'A brir el puerto numero 3
Puerto = My.Computer.Ports.OpenSerialPort("COM4") 'Llamar constructor
...
Timer1.Interval = 1000
Timer1.Enabled = True
...
...

En la región de A uxiliares añadiremos el siguiente código:


#Region "Rutinas A uxiliares"
'disparo de Tareas a realizar cada Segundo
Private Sub Timer1_Tick(ByVal sender A s System.Object,
ByVal e A s ...system.EventA rgs) Handles Timer1.Tick
'
Label1.Text = Puerto.BytesToRead.ToString
'Leer nº bytes recibidos
End Sub

Pulsemos de nuevo [F5], cliqueando el button1... Veamos


¿qué ocurre?

figura 4.9

Increíble, ¡tengo 12 bytes para leer en el búffer!, real-


mente 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 caracte-
res 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…
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.

#Region "Rutinas A uxiliares"


'disparo de Tareas a realizar cada Segundo
Private Sub Timer1_Tick(ByVal sender A s System.Object, ByVal e A s 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

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 comu-
nicaciones 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 capítulo
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 demos-
trar 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 verifi-
que si hay caracteres para procesar; esto último podría
verse como una técnica, pero está claro que no es dema-
siado técnico.
En un medio donde lanzamos nuestras peticiones de
comunicaciones a una nube, la cual nos devuelve asíncro-
namente el resultado o la respuesta y de una manera impre-
decible, 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 conti-
nuar 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).
La primera etiqueta Label1, nos servirá para visualizar
los caracteres recibidos, las dos barras de proceso junta-
mente con las etiquetas Label2 y Label3 nos indicarán la can-
tidad de bits transmitidos y recibidos. El botón Button1'
simplemente para iniciar o detener la transmisión en
cadena.

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 con-
trol y la gestión de excepciones, así como otras formalida-
des 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 A s String 'Cadena con los caracteres recibidos
Public enviados A s Integer = 0 'Numero de bits enviados (11 bits por Byte)
Public Recibidos A s 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:

Public Event Respuesta() 'Evento disparo respuesta


WithEvents Serie A s System.IO.Ports.SerialPort 'Eventos del SerialPort

Fuente 5.2

Terminaremos de completar nuestra clase con las


subfunciones de Enviar, Recibir, New 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 A s 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 A s Object, ByVal e A s ...
... system.IO.Ports.SerialReceivedEventA rgs)
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
'
'A brir 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

Fuente 5.3
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 res-
ponder, 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 respues-
ta… ¡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 con-
ceptos 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 buscan-
do 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”, ima-
gen 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 eje-
cución; dicho esto me confieso un aterrizado miembro
del grupo 1, y es un orgullo contribuir en la rápida adhe-
sió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 impi-
de que a partir de nuestro hilo, creemos otros hilos de
ejecución que cuelguen del nuestro “hilos hijos”, la inter-
acció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.
La pregunta sería ¿y a qué viene todo esto? La res-
puesta 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 ins-
tancia al objeto puerto:
Public Class Form1
'
Private Comunica A s Puerto = New Puerto 'Instancia nuestra clase Puerto
Private InicioFin A s Boolean = False 'Encadenar las peticiones (si/no)
Private DatosRecibidos A s String 'Datos recibidos

Fuente 5.4

Ahora el famoso delegado que nos servirá para el


Invokeque permitirá al control recibir datos desde un thre-
at diferente al que fue creado.

'
'delegado para manejar la recepción de datos desde un 'threat' externo
Delegate Sub Refrescar()

Fuente 5.5

Construimos un temporizador con el que controlare-


mos la actualización de datos en pantalla una vez cada
segundo.
'
'temporización de un segundo
WithEvents segundos A s 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 A s Object, ByVal e A s System.EventA rgs ...
'dirección de ejecución para el evento 'Respuesta' una vez recibidos los datos
A ddHandler Comunica.Respuesta, A ddressOf 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 A s System.Object, ByVal e A s System.Eve ...
Comunica.Enviar(System.DateTime.Now) 'Enviamos la fecha/Hora
InicioFin = Not InicioFin 'Memorizamos Inicio/Fin
End Sub

Fuente 5.8

Cada segundo (tick del timer)...


'
'Cada Segundo efectuaremos un refresco de la información en
pantalla
Public Sub tiempo(ByVal sender A s 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"
'A ctualizar 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.9

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(A ddressOf actualizar_Datos))
'
'enviar otra trama, Si la trama contiene el caracter CR y queremos bucle.
If DatosRecibidos.Contains(Chr(13)) A nd 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

Fuente 5.10

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

¿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 reali-
za disparando un evento desde el delegado receivedevent, uti-
lizando 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á echan-
do 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 sobrescri-
tura, 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 reci-
bo otros tantos, pues el modo full duplex me da la posibili-
dad de enviar y recibir simultáneamente.
Este efecto no ocurrirá si utilizamos un dispositivo
real que conteste a nuestras peticiones.
Es la hora del descanso, antes de entrar en trance con
nuestras librerías de acceso a nuestro Entregon+ y el pro-
grama Dispensador de Vídeo, tómense un merecido café.
capítulo 6
ServidorComm
La Clase

ServidorComm. Introducción a la clase


La primera tarea del siguiente laboratorio, se centra-
rá en efectuar un análisis preliminar de nuestras necesida-
des, a continuación lo plasmaremos gráficamente y final-
mente, siguiendo dicho esquema, abordaremos la progra-
mación módulo a módulo.
Para entender mejor este proyecto es necesario acla-
rar 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 perso-
nal, 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 digitado-
ras, 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 remo-
tas a unas velocidades de vértigo si las comparamos con
los 300 baudios de los primeros modems.
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 vecto-
res de interrupción (int3/int4) del chip controlador para
hacerse con la UART y ¡ciertamente complicadísimo!
Versiones posteriores de lenguajes trataban el acceso al
puerto de comunicaciones como si de un archivo se trata-
ra. 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ó solucio-
narlo usando las instrucciones out/in atacando directamen-
te 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 prime-
ra vez, da un cierto respiro para poder acceder a los puer-
tos, ya que podemos desarrollar aplicaciones suficiente-
mente fiables con una sencillez justa. Finalmente, la cosa
se vuelve a complicar con la llegada de .NET, las prime-
ras betas permiten explotar los puertos con el uso de stre-
ams, 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 alter-
nativa son las antiguas llamadas a las API. ¡Eso en plena
era .NET! Efectivamente, este es el motivo de tanto albo-
roto. ¡Visual Studio 2005 incorpora un espacio de nom-
bres 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 que-
darnos descansados, presentando y describiendo la fun-
cionalidad básica y prestaciones de este espacio de nom-
bres. 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 des-
arrolladores aún no han cambiado a Visual Studio 2003.
A pesar de que el salto de 2003 a 2005 no es tan especta-
cular como el de Visual Studio 6 a 2003, vale la pena dar-
nos la oportunidad de iniciar nuestras futuras aplicacio-
nes 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”.

Nuestro proyecto…(cuaderno de carga)


Descripción
Desarrollar una clase que enlace la aplicación del dis-
pensador de vídeo y nuestro Entregon+ para atender a las
peticiones de alquiler, venta y devolución, intercambian-
do tramas serie con el formato descrito en el protocolo
DispeDataLink, utilizaremos el puerto serie local, con-
trolaremos y supervisaremos los mensajes de incidencias,
así como el estado de nuestro dispensador en tiempo real.

Detalles
Nuestra clase deberá estar compilada en un proyec-
to Class Library, para poder usar su funcionalidad desde
dispensador, simplemente añadiendo una referencia a la
misma.
Para poder desarrollar este laboratorio, es imprescin-
dible disponer de un equipo con un puerto serie y el conec-
tor 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 apa-
sioná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 descontro-
ladamente, dando forma y sentido a un montón de zambu-
llidos neuronales, que poco a poco van componiendo ese
fantástico puzzle al que terminamos llamando solución.
Una primera imagen de nuestro trabajo, es diferen-
ciar sus dos elementos esenciales: el mecanismo
Entregon+ con su equipo controlador y nuestra aplica-
ción. A pesar de las grandes habilidades, tanto mecáni-
cas 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 libre-
rías que sean capaces de dotar al conjunto de la funcio-
nalidad suficiente, para exponer toda la gestión del con-
junto 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).
A primera vista puede parecer que no olvidamos nada
y como dibujito no está nada mal, sin embargo, un análi-
sis más detallado nos conducirá a descubrir que debemos
ser mucho más rigurosos y efectuar una descripción más
concreta.
Esta descripción detallada debe conducirnos directa-
mente a la definición de las clases necesarias que confor-
maran nuestras librerías.
Me gustaría insistir en el hecho de ser minuciosa-
mente 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 “mantenimien-
to cero”.
ENLACE DE COMUNICACIONES–DISPENSADOR/ENTREGON+

gráfico 6.1

Nuestra librería deberá contener una clase capaz de


reportar todos los mensajes al eventlog del sistema, debe-
rá 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 Servi-dorComm.


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 inter-
cambio 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 inter-
cambio alguno. La topología de estas redes acostumbra a
no sobrepasar los 32 esclavos y ello es debido a las limita-
ciones de impedancia de los dispositivos electrónicos.
Normalmente RS485. En la actualidad existen numero-
sos conversores de señal RS232 a RS485.
En nuestro caso, Entregon+ y el protocolo son escla-
vos a nivel de aplicación. Por lo que podemos enlazar un
único dispensador de vídeo a varios Entregon+ (ver gráfi-
co 6.2).

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
capítulo 7
ServidorCom.dll
Nuestro laboratorio

ServidorCom.dll
Por fin nos adentraremos en los placeres de la codi-
ficació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 deta-
llados sean nuestros modelos de formato y su descripción,
más fácil será nuestra labor posterior.
A continuación vamos a detallar el formato de las tra-
mas que intercambiaran el Entregon+ y nuestro sistema,
es importante entender su estructura, pues será hilo con-
ductor que sustentara nuestra aplicación... “son el cora-
zón de nuestro proyecto”.

Nota del equipo de desarrollo: ésta es la versión lite shared opensour-


nota

ce freeware en la que el equipo técnico de desarrollo sólo ha imple-


mentado tres operaciones básicas. Para la versión completa full equip-
ment diríjase al departamento comercial, o bien copie/modifique y
distribúyanos libremente su aportación a: entregon@dispensotron.com.

Diferentes asociaciones de usuarios advierten que leer código “a


advertencia

pelo” puede producir somnolencia y aburrimientos de diversas índo-


les. 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éu-
tico, 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.

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
Petición/respuesta entrega de cápsula - 01
Para poder cumplir los requisitos del protocolo, debe-
remos 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

Código ISBN para encapsular y/o


ISBN ASCII 13 X(13)
expulsar

NIFCIF ASCII 9 X(9) NIF usuario

Cálculo para la verificación


CRC ASCII 2 00-FF
de redundancia cíclica

$ ASCII 1 Na Fin de trama


CR ASCII 1 Na Fin de transmisión

tabla 7.1

La siguiente trama solicita la expulsión del DVD con


ISBN=0809101112134 y el NIF=77666555X.

1
CRC = Cálculo de redundancia cíclica
CR = Carriage return
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 ASCII 2 01-32 Código de fin1

Cálculo para la verificación de redundancia


CRC ASCII 2 00-FF
cíclica

$ ASCII 1 Na Fin de trama

CR ASCII 1 Na Fin de transmisión

tabla 7.2

Respuesta

#020100FF$(Cr)

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

Cálculo para la verifica-


CRC ASCII 2 00-FF ción de redundancia cícli-
ca

$ ASCII 1 Na Fin de trama

CR ASCII 1 Na Fin de transmisión

tabla 7.3

El código expulsión es “02”, entonces para compo-


ner 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

Código ISBN para encap-


ISBN ASCII 13 X(13)
sular y/o expulsar

NIFCIF ASCII 9 x(9) NIF usuario

Cálculo para la verificación


CRC ASCII 2 00-FF
de redundancia cíclica

$ ASCII 1 Na Fin de trama

CR ASCII 1 Na Fin de transmisión

tabla 7.4

Respuesta
#020100080910111213477666555FF$(Cr)
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

Cálculo para la verificación de


CRC ASCII 2 00-FF
redundancia cíclica

$ ASCII 1 Na Fin de trama

CR ASCII 1 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

16 Bits de estatus (indicadores


BIE ASCII 4 0-FFFF
de incidencias)

Cálculo para la verificación de


CRC ASCII 2 00-FF
redundancia cíclica

$ ASCII 1 Na Fin de trama

CR ASCII 1 Na Fin de transmisión

tabla 7.6

Respuesta
#020100EEEEFF$(Cr)
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ódigos de fin de operación

Código Descripción
01 Petición de expulsión de cápsula
02 Petición de Introducción de cápsula
03 Petición de banderas de estado controlador

Alertas
10 Todo Bien
11 Existe una alarma en el sistema leer estatus

Avisos
20 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
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 veri-
ficación de error, que permite a la aplicación determinar
si la trama recibida está libre de errores.
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ál-
culo del CRC del remitente coincide con el del destinata-
rio, podemos casi afirmar que los datos recibidos están
libres de errores.
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á sufi-
ciente entendiendo que a partir de una cadena de carac-
teres 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 olvide-
mos 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.
(ver Fuente 7.1)
Este fragmento de programa esta íntegramente des-
arrollado y diseñado en el año 89, por Pep. Utiliza la téc-
nica “Lo+Retorcida”, en esos años era la única forma de
proteger intelectualmente los desarrollos, se caracteriza-
ba porque “no había quien la entendiera”.
'
'
' Calcular el crc de la variable Var
'
'
Shared Function Crc(ByVal Var) A s String
Dim i A s Integer, j A s Integer
Dim CrcTmp A s Integer, Ct A s Integer, Ch A s Integer, Cl A s Integer
Dim C1 A s Integer, C2 A s Integer

CrcTmp = &HFFFF
For i = 1 To Len(Var)
CrcTmp = CrcTmp Xor A sc(Mid(Var, i, 1))
For j = 1 To 8
Ct = CrcTmp A nd &H1
If CrcTmp < 0 Then Ch = 1 Else Ch = 0
CrcTmp = CrcTmp A nd &H7FFF
CrcTmp = CrcTmp \ 2
If Ch = 1 Then CrcTmp = CrcTmp Or &H4000
If Ct = 1 Then CrcTmp = CrcTmp Xor &HA 001
Next j, i
If CrcTmp < 0 Then Cl = 1 : CrcTmp = CrcTmp A nd &H7FFF Else Cl = 0
C1 = CrcTmp A nd &HFF& : C2 = (CrcTmp A nd &H7F00) \ 256
If Cl = 1 Then C2 = C2 Or &H80
Return (Chr("&h" + Hex(C1)) + Chr("&h" + Hex(C2)))
End Function

Fuente 7.1

Componiendo estructuras básicas


Una de las labores más agradecidas en estos, los
menesteres del codificador, son la definición de estructu-
ras. 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 conse-
cuente 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 capa-
cidad para encapsular estructuras que contengan propie-
dades, funciones,... es magnífico ver qué bien recom-
pensado 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 genera-
remos una clase a la que llamaremos EntrgnEstructuras.vb, la
cual va a contener el formato exacto requerido para efec-
tuar 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 entendere-
mos cómo cualquier protocolo empaqueta diversos blo-
ques 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.
El primer bloque encapsula la cabecera de unidad
destino a la que va dirigida la trama y qué unidad a lanza-
do 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 pro-
piedades!, realmente organizado.
#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 A s String
Private Unidad_Origen A s String
'
' Propiedad UnidadDestino
Public Property UnidadDestino() A s String
Get
Return Unidad_Destino
End Get
Set(ByVal Value A s String)
' se A dmiten valores entre 00 y 32
Unidad_Destino = Format(Val(Value), "00")
End Set
End Property
'
' Propiedad UnidadOrigen
Public Property UnidadOrigen() A s String
Get
Return Unidad_Origen
End Get
Set(ByVal Value A s String)
' se A dmiten 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 blo-


que 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 infor-
mació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:
El primer bloque de datos encapsula información a
nivel de dispositivos, el segundo tan solo contiene infor-
mació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 quitar-
nos ciertos complejos, desvelar que incluso los famosos
y misteriosos protocolos TCP conforman diferentes
formatos definidos y divididos en bloques para el inter-
cambio 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 A s Integer
Private Codigo_Fin A s Integer
Private Indicador_Estado A s Integer
Private Datos_ISBN A s String
Private Datos_CIF A s String
'
' Propiedad CodigoOperacion
Public Property CodigoOperacion() A s String
Get
Return Format(Codigo_Operacion, "00")
End Get
Set(ByVal value A s String)
Codigo_Operacion = Val(value)
End Set
End Property
'
' Propiedad CodigoFin
Public Property CodigoFin() A s String
Get
Return Format(Codigo_Fin, "00")
End Get
Set(ByVal value A s String)
Codigo_Fin = value
End Set
End Property
'
' Propiedad IndicadorEstado
Public Property IndicadorEstado() A s String
Get
Return Hex(Indicador_Estado).PadLeft(4, "0")
End Get
Set(ByVal value A s String)
Indicador_Estado = Val(value)
End Set
End Property

'
' Propiedad DatosISBN
Public Property DatosISBN() A s String
Get
Return Datos_ISBN
End Get

Set(ByVal value A s String)


Datos_ISBN = value.PadRight(13)
End Set
End Property
'
' Propiedad DatosCIF
Public Property DatosCIF() A s String
Get
Return Datos_CIF
End Get
Set(ByVal value A s String)
Datos_CIF = value.PadRight(9)
End Set
End Property
End Structure
#End Region

Fuente 7.3
¡Eureka!, nuestros dos bloques de información defini-
dos en el primer apartado, ya están listos para ser usados.
Debemos componer una estructura para cada mode-
lo de solicitud, a saber: Expulsión, Introducción, Estado.
Así que compuestos los bloques de datos, abordare-
mos 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 concen-
trarte 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 A s 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 A s String, ByVal UOrig A s String,_


ByVal ISBN A s String, ByVal CIF A s String) A s 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

Supongo que nadie tendrá dificultad en seguir la lec-


tura del código y pueda distinguir cómo en las primeras
líneas asignamos los datos recibidos en la llamada de la fun-
ció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 lec-
tura de las etiquetas electrónicas.

#Region "** Trama de Transmision - Sol.licitud de Introducción"


Public Structure SolicitarIntroduccion
Private miTrama A s 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 A s String, _
ByVal UOrig A s String) A s 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 A s String, _
ByVal UOrig A s String, _
ByVal ISBN A s String, _
ByVal CIF A s String) A s 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 + _
blDatos1.UnidadOrigen + _
blDatos2.CodigoOperacion + _
blDatos2.DatosISBN + _
blDatos2.DatosCIF
Return miTrama + calcular_Crc(miTrama) + Fin.ToString
End Function
End Structure
#End Region

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 A s String
'
'Componer la trama de Estado
'Retornamos una estructura compuesta segun protocolo DispeDataLink
Public Function Trama(ByVal UDest A s String, ByVal UOrig A s String) A s 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 imple-
mentar un simulador del dispensador.
Siguiendo el esquema descrito en el gráfico 6.1 (enla-
ce de comunicaciones), veremos cómo cada solicitud se
convierte en un envío que recibe el simulador, generando
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 respues-
tas 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 A s 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 A s String, _
ByVal UOrig A s String, _
ByVal CodigoFin A s String, _
ByVal CodigoOperacion A s String) A s 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 A s String, _
ByVal UOrig A s String, _
ByVal CodigoFin A s String, _
ByVal IndicadorEstatus A s Integer) A s String
'
blDatos1.UnidadDestino = UDest 'Unidad Destino
blDatos1.UnidadOrigen = UOrig 'Unidad Origen
blDatos2.CodigoFin = CodigoFin
blDatos2.IndicadorEstado = IndicadorEstatus 'Cod.Retorno Operacion
'
miTrama = Ini.ToString + _
blDatos1.UnidadDestino + _
blDatos1.UnidadOrigen + _
blDatos2.CodigoFin + _
blDatos2.IndicadorEstado
Return miTrama + calcular_Crc(miTrama) + Fin.ToString
End Function
End Structure
#End Region

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 descompone-
mos o desempaquetamos los diferentes campos que com-
ponen 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 A s String
Private TramaCrc, CrcRecv, CrcCalc A s String

Public Property CamposDeRecepcion() A s String


Get
Return miTrama
End Get
Set(ByVal value A s String)
miTrama = value
End Set
End Property

Public Overloads ReadOnly Property Destino(ByVal Trama A s String) A s Integer


Get
Return Trama.Substring(1, 2)
End Get
End Property

Public Overloads ReadOnly Property Destino() A s Integer


Get
Return miTrama.Substring(1, 2)
End Get
End Property

Public Overloads ReadOnly Property Origen(ByVal Trama A s String) A s Integer


Get
Return Trama.Substring(3, 2)
End Get
End Property

Public Overloads ReadOnly Property Origen() A s Integer


Get
Return miTrama.Substring(3, 2)
End Get
End Property

Public Overloads ReadOnly Property CodigoOpe(ByVal Trama A s String) A s Integer


Get
Return Trama.Substring(5, 2)
End Get
End Property

Public Overloads ReadOnly Property CodigoOpe() A s Integer


Get
Return miTrama.Substring(5, 2)
End Get
End Property

Public Overloads ReadOnly Property CodigoFin(ByVal Trama A s String) A s String


Get
Return Trama.Substring(7, 2)
End Get
End Property

Public Overloads ReadOnly Property CodigoFin() A s String


Get
Return miTrama.Substring(7, 2)
End Get
End Property

Public Overloads ReadOnly Property CheckSum(ByVal Trama A s String) A s 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() A s 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) A s String


Get
Return Trama.Substring(7, 13)
End Get
End Property

Public Overloads ReadOnly Property ISBN() A s String


Get
Return miTrama.Substring(7, 13)
End Get
End Property

Public Overloads ReadOnly Property Indicadores(ByVal Trama A s String) A s String


Get
Return Trama.Substring(7, 4)
End Get
End Property

Public Overloads ReadOnly Property Indicadores() A s String


Get
Return miTrama.Substring(7, 4)
End Get
End Property

Public Overloads ReadOnly Property NIFCIF(ByVal Trama A s String) A s String


Get
Return Trama.Substring(20, 9)
End Get
End Property

Public Overloads ReadOnly Property NIFCIF() A s String


Get
Return miTrama.Substring(20, 9)
End Get
End Property

Public ReadOnly Property LaTramaEstaCompleta(ByVal Trama A s String) A s Boolean


Get
miTrama = Trama
If (Left(Trama, 1) = Ini) A nd (Right(Trama, 2) = "$" + Chr(13)) Then
Return True
Else
Return False
End If
End Get
End Property
End Structure

#End Region

Fuente 7.8

Vale la pena observar en todo este código de desglo-


se, 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 cal-
cular_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 devol-
ver un “Verdadero” si la suma de verificación del remiten-
te coincide con la calculada por el destinatario y la segun-
da 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 disposi-
ció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:
#Region "** Enumeraciones"
'
'Enumerar las posibles Operaciones
Public Enum Operacion
'A cciones
Expulsion = 1
Introduccion = 2
Estatus = 3
'A lertas
TodoBien = 10
ExisteA larma = 11
'A visos
StockMinimo = 20
UltimoProducto = 21
UltimasCapsulas = 22
ErroresEnDispensador = 23
ProductoIlocalizable = 24
ImposibleRecoger = 25
ImposibleA pilar = 26
EjeCabezalRoto = 27
A tascoDistribuidor = 28
FalloLectorEtiquetas = 29
FalloLecturaEtiqueta = 30
'Fin de operacion
TransmisionOk = 0
TransmisionKo = 99
End Enum
'
'Enumerar los Indicadores de estado (4 hex)
Public Enum Indicadores
A veriaEnPlacaControladora1 = &H1&
A veriaEnPlacaControladora2 = &H2&
FalloEnProcesador1 = &H4&
FalloEnProcesador2 = &H8&
A veriaEjeCabezal = &H10&
A veriaEnPinzaDispensadora = &H20&
LectorEtiquetasA veriado = &H40&
DisparoDetectorA ntivandalico = &H80&
A lmacenCapsulasVacio = &H100&
A lmacenProductoVacio = &H200&
SuciedadOpticaPosicionadorLaser = &H400&
FalloVentiladorSistemaRefrigeracion = &H800&
A tascoDispensadorSalida = &H1000&
A tascoDispensadorEntrada = &H2000&
A tascoPinzaDispensadora = &H4000&
SobreTemperatura = &H8000&
End Enum

#End Region

Fuente 7.9

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ódi-
go sin poderle dar al [F5], sin embargo, es importante
seguir un orden, a pesar del desespero, como en la mayo-
ría de disciplinas, deberemos construir la infraestructura
que nos va a permitir elevar nuestro edificio hasta conse-
guir la altura diseñada.
Bien, una vez hemos completado la parte más aburri-
da (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.
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 nece-
sarios 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, cual-
quier operación externa será dirigida y distribuida desde
este punto; la función llamará a cada uno de los procedi-
mientos contemplados, o sea, solicitud de introducción,
expulsión y estado. Codificaremos una función que com-
pondrá el modelo de respuesta. Finalizaremos nuestra
clase con la función A larmaGrave encargada de simular y
reportar los problemas relacionados con mensajes de
advertencia, informativos o de alarma.

figura 7.2

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 ejecuta-
remos para procesar dicha solicitud:
'
'Procesar las tramas recibidas por el simulador
Public Function ProcesoDeSolicitudes(ByVal TramaRecibida A s String) A s String
If Not Campos.LaTramaEstaCompleta(TramaRecibida) Then 'Trama no completa
TramaDeRespuesta = "" 'No procesamos Nada
Else
_Origen = Campos.Origen.ToString 'A signacion 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ódi-
go 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 conten-
ga las cadenas de inicio/fin y la segunda que coincidan los
cálculos de verificación.
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
'
'SIMULA DOR!!
_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 = A ctualizarEtiquetaElectronica(_ISBN,_NIFCIF):VerCodFinDeOpe
' CodigoDeRetorno = PosicionProductoA lmacen() : VerificarCodigosFinDeOperacion()
' CodigoDeRetorno = DejarProducto(): VerificarCodigosFinDeOperacion
'else
' RechazarIntroduccionPorObjetoDesconocido
' LanzarA lertaA lUsuario
' ReportarCodigoErrorLecturaEtiqueta
'end if
'
_CodigoDeRetorno = SimuladorCodigoFin() '... ?
_CodigoFin = campos.CodigoOpe 'Operacion completada correctamente
'
_ISBN = campos.ISBN
_NIFCIF = campos.NIFCIF
ComponerRespuestaModelo()
End Sub

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 A s 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 introduc-
ció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 A s Tramas.RespuestaModelo = New Tramas.RespuestaModelo
TramaDeRespuesta = Respuesta.Trama(_Destino.ToString, _Origen.ToString, _
_CodigoFin.ToString, _CodigoDeRetorno.ToString)
End Sub

Fuente 7.14

Terminaremos nuestro recorrido, completando la


clase Simulador con dos procedimientos más que gestio-
nen el código de retorno y las alarmas.
Del siguiente procedimiento destacaremos la simu-
lación de mensajes del ingenio, para dotar a la simula-
ción de un cierto grado de realidad. Evidentemente es
necesario que de una manera eventual se disparen erro-
res 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 cam-
pos 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 A laramaGrave rellena con men-
sajes de texto que corresponden a los códigos de alarma.

'Simulador de codigo fin de operación


Private Function SimuladorCodigoFin() A s Integer
Dim indice A s Integer
' Simular Codigos de error usando el ISBN y el DNI/CIF
'
_ISBN = campos.ISBN.Replace(" ", "")
_NIFCIF = campos.NIFCIF.Replace(" ", "")

If (_ISBN.Length = 0) A nd (_NIFCIF.Length = 0) Then


'error = 29 si los campos ISBN y CIF aparecen vacios
indice = 29
Else
If _ISBN.Length < 13 A nd _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 A leatorio
'entre 1 y 11
If indice = 11 Then
_IndicadoresEstado = _IndicadoresEstado Xor (32767 * Rnd())
A larmaGrave()
Else
_IndicadoresEstado = 0
End If
End If
End If
End If
End If
'
Select Case indice
Case 11 : Return Tramas.Operacion.ExisteA larma
'Case 20 : Return Tramas.Operacion.StockMinimo
'Case 21 : Return Tramas.Operacion.UltimoProducto
'Case 22 : Return Tramas.Operacion.UltimasCapsulas
Case 23 : Return Tramas.Operacion.ErroresEnDispensador
Case 24 : Return Tramas.Operacion.ProductoIlocalizable
'Case 25 : Return Tramas.Operacion.ImposibleRecoger
'Case 26 : Return Tramas.Operacion.ImposibleA pilar
'Case 27 : Return Tramas.Operacion.EjeCabezalRoto
'Case 28 : Return Tramas.Operacion.A tascoDistribuidor
Case 29 : Return Tramas.Operacion.FalloLectorEtiquetas
Case 30 : Return Tramas.Operacion.FalloLecturaEtiqueta
Case Else
Return Tramas.Operacion.TodoBien
End Select
End Function

Fuente 7.15

Con ganas de finalizar, completaremos la clase simula-


dor con la codificación de las alarmas.

'Tratamiento de Incidencias Graves


Private Sub A larmaGrave()
Dim Codigo A s Tramas.Indicadores = New Tramas.Indicadores
Dim LfCr A s String = Chr(10) + Chr(13)
texto = ""
If _IndicadoresEstado A nd Codigo.A lmacenCapsulasVacio Then
texto += "RESPONSA BLE DE PRODUCCION! - CA PSULA S A GOTA DA S EN EL DISPENSA DOR "
End If
If _IndicadoresEstado A nd Codigo.A lmacenProductoVacio Then
texto += "RESPONSA BLE DE PRODUCCION! - DISPENSA DOR nºxx, SIN PRODUCTO" + LfCr
End If
If _IndicadoresEstado A nd Codigo.A tascoDispensadorEntrada Then
texto += "RESPONSA BLE DE MA NTENIMIENTO! - A TA SCO EN EL MECA NISMO DE ENTRA DA "
End If
If _IndicadoresEstado A nd Codigo.A tascoDispensadorSalida Then
texto += "RESPONSA BLE DE MA NTENIMIENTO! - A TA SCO EN EL MECA NISMO DE SA LIDA "
End If
If _IndicadoresEstado A nd Codigo.A tascoPinzaDispensadora Then
texto += "RESPONSA BLE DE MA NTENIMIENTO! - PINZA DISPENSA DORA A TA SCA DA "
End If
If _IndicadoresEstado A nd Codigo.A veriaEjeCabezal Then
texto += "RESPONSA BLE DE MA NTENIMIENTO! - EJE CA BEZA L A VERIA DO UNIDA D nºxx"
End If
If _IndicadoresEstado A nd Codigo.A veriaEnPinzaDispensadora Then
texto += "RESPONSA BLE DE MA NTENIMIENTO! - PINZA DISPENSA DORA A VERIA DA "
End If
If _IndicadoresEstado A nd Codigo.A veriaEnPlacaControladora1 Then
texto += "INGENIERO ELECTRONICO! - SUBSTITUIR PLA CA CONTROLA DORA 1 "
End If
If _IndicadoresEstado A nd Codigo.A veriaEnPlacaControladora2 Then
texto += "INGENIERO ELECTRONICO! - SUBSTITUIR PLA CA CONTROLA DORA 2 "
End If
If _IndicadoresEstado A nd Codigo.DisparoDetectorA ntivandalico Then
texto += "JEFE DE SEGURIDA D! - DISPA RO DE LA A LA RMA DE A NTIVA NDA LICA " + LfCr
End If
If _IndicadoresEstado A nd Codigo.FalloEnProcesador1 Then
texto += "INGENIERO DE SISTEMA S!,FA LLA EL PROCESA DOR PRINCIPA L ENTREGON+ "
End If
If _IndicadoresEstado A nd Codigo.FalloEnProcesador2 Then
texto += "INGENIERO DE SISTEMA S!,FA LLA EL PROCESA DOR SECUNDA RIO ENTREGON+"
End If
If _IndicadoresEstado A nd Codigo.SobreTemperatura Then
texto += "OPERA RIO DE A .A CONDICIONA DO!, SOBRETEMPERA TURA ENTREGON+" + LfCr
End If
If _IndicadoresEstado A nd Codigo.SuciedadOpticaPosicionadorLaser Then
texto += "DEPA RTA MENTO DE LA SERES!, LIMPIA R/A LINEA R LA SER 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
A larmas.A visar(texto)
End If
End Sub

Fuente 7.16

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 ori-
gen, 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 : Entregon+


' Nombre : EntrgnEventLog.vb
' Tipo : Clase
' Clase : RegistroA lertas
' Creado : 29.04.2005
' A utor : Pep Lluis
' Version : 1.0
'
' Historia y Revisiones -----------------------------------------------------------
' 05.2005 - Migrar el codigo a visual Studio 2005 - Beta 2

Public Class RegistroA lertas


'
Private UltimoIdentificador A s Integer = 0
Public Shared MensajesEnRegistro A s Boolean = False
'
Public Sub Registra(ByVal Origen A s String, ByVal Mensaje A s String, _
ByVal Tipo A s System.Diagnostics.EventLogEntryType, _
ByVal Identificador A s 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 A s Exception
'A lertar de que no podemos registrar eventos al log
System.Diagnostics.EventLog.WriteEntry("RegistroA lertas", _
ex.Message, EventLogEntryType.Error, 0)
End Try
End Sub
End Class

Fuente 7.17

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 varia-
bles. 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 ver-
satilidad 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 dis-
pensador, 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 reci-
bir 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 manteni-
miento de la empresa que alquila y vende las películas.
Organizar una cadena de establecimientos que tiene cen-
tralizados sus servicios de mantenimiento de sus instala-
ciones, será cosa de niños. A partir de ahora cada depar-
tamento 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.
Recuerdo en antaño cuando los cajeros de las entida-
des bancarias, tenían un interfono para que el usuario lla-
mara 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!
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 embar-
go, nos irá bien mencionar esta primera parte donde
dimensionamos las variables que usaremos para nuestro
aviso por correo, cargándolas con la configuración alma-
cenada en el archivo app.config, para tal cometido usare-
mos la clase XmlConfig que se describe posteriormente. A
continuación crearemos el objeto correo. Como mensaje
de mail, asignaremos los valores del mismo y sencilla-
mente llamaremos al send.

'
'A visar de una A lerta utilizando el correo electonico
Public Sub A visar(ByVal emailMensa A s String)
'
'Lectura de las variables de configuracion del archivo app.config.
Dim email_From A s String = _
A plicacion.LeerConfig("app.config", "email_From", "emailOrigen").InnerText
Dim email_To A s String = _
A plicacion.LeerConfig("app.config", "email_To", "emailDestino").InnerText
A plicacion.LeerConfig("app.config", "email_Cc", "emailCopia").InnerText
Dim email_User A s String = _
A plicacion.LeerConfig("app.config", "email_User", "CuentaUsuario").InnerText
Dim email_Pwd A s String = _
A plicacion.LeerConfig("app.config", "email_Pwd", "password").InnerText
Dim email_smtp A s String = _
A plicacion.LeerConfig("app.config","email_Smtp","servidor").InnerText
'
'Conformar un mensage de mail
Dim correo A s MailMessage = New MailMessage
'Definir el esquema CDO
Dim Esquema A s String = "http://schemas.microsoft.com/cdo/configuration/"
'Llenar de contenido el mensage
correo.From = email_From
correo.To = email_To
correo.Cc = email_Cc
correo.Subject = "ENTREGON+, A LA RMA - REQUIERE INTERVENCION"
correo.Body = emailMensa
'A signar las credendiales
correo.Fields.A dd(Esquema + "smtpauthenticate", "1")
correo.Fields.A dd(Esquema + "sendusername", email_User)
correo.Fields.A dd(Esquema + "sendpassword", email_Pwd)
'A signar el nombre del servidor de correo
SmtpMail.SmtpServer = email_smtp
Try
SmtpMail.Send(correo) 'Enviar el correo
Catch ex A s Exception
'reportar problemas al eventlog
Mensaje.Registra("A lertas", ex.Message, EventLogEntryType.Error, 4)
End Try

End Sub

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 instruc-
ciones. Así podemos seguir el rastro de lo que está suce-
diendo 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 A s MailMessage = New MailMessage(email_From, email_To)
'correo.Subject = "ENTREGON+, A LA RMA - REQUIERE INTERVENCION"
'correo.Body = email_Mensa

'Dim ClienteSmtp A s New SmtpClient(email_smtp)


'Try
' ClienteSmtp.UseDefaultCredentials = True
' ClienteSmtp.Send(correo)
' Catch ex A s Exception
' Mensaje.Registra("A lertas", ex.Message, EventLogEntryType.Error, 4)
' End Try

Fuente 7.19

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: A plicacion.LeerConfig, no te sorprendas, aún
no hemos hablado de él. Cualquier aplicación que se pre-
cie debe almacenar y leer sus valores de configuración.
Como veremos, esta clase nos permite realizar esta fun-
ción utilizando un fichero en XML. Hemos decido usar
el app.config aunque quizás no sea la mejor forma, lo hace-
mos para entender que podemos concentrar toda la infor-
macion 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.
Según se describe en la figura 7.4, podemos distin-
guir tres funciones :
Existe, LeerConfig y SalvarConfig.

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 A s String) A s Boolean
Dim Doc A s XmlDocument = New Xml.XmlDocument 'Documento XML
Try '
Doc.Load(Nombre) 'Intentar Cargar el nombre
Doc = Nothing 'Liberar el Doc
Return True 'A rchivo disponible
Catch ex A s Exception 'En otro caso
Return False 'A rchivo no disponible
End Try
End Function

Fuente 7.20

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 A rchivo en formato XML
'Parametros de la funcion :
Fichero = Nombre del A rchivo en formato nnn.eee (Ejemplo: app.config)
' Raiz = Elemento
' Defecto = Valor que retornara por defecto
'
Public Function LeerConfig(ByVal Fichero A s String, ByVal Raiz A s String, _
ByVal Defecto A s String) A s Xml.XmlElement

Dim Documento A s XmlDocument = New Xml.XmlDocument 'Constructor Documento


Dim Elemento A s 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.A ppendChild(Elemento)
Documento.Save(Fichero)
Else
'no creamos el elemento por inexistencia del archivo
'Reportamos el problema al EventLog
Mensaje.Registra("XmlConfig", "A rchivo :" + Fichero + _
"Inexistente", EventLogEntryType.Warning, 1)
End If
End If
Catch ex A s 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 simplemente guarda el valor del elemento


modificado y salva el documento.

'
' Guardar un valor en el documento xml de configuracion
'
Public Function SalvarConfig(ByVal Fichero A s String, ByVal Raiz A s String, _
ByVal Valor A s String) A s Boolean
Dim Documento A s XmlDocument = New Xml.XmlDocument 'Constructo del Documento
Dim Elemento A s 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 'A signa el valor a elemento
Documento.Save(Fichero) 'Guarda el Documento
Catch ex A s 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. Con-


tinuamos 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 aflo-
jas, 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 posterior-
mente escribiremos para esta clase.
Estos son los mandatos e instrucciones más frecuen-
tes para utilizar el puerto serie:

Private Sub EjemploDeLasPrincipalesInstruccionesDeSystem.IO.Port()


Dim Contador A s Integer
Serie = My.Computer.Ports.OpenSerialPort("COM1") 'Llamar al constructor
'
'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 'A signar paridad(enumeracion parity)
Serie.StopBits = StopBits.Two 'Bits parada despues byte de datos
'
'A brir/Control/Liberar Puerto
Serie.Open() 'A brir el puerto Serie
Serie.Close() 'Cerrar el Puerto Serie
Serie.Dispose() 'Liberar objecto
Dim SiNo A s Integer
SiNo = Serie.IsOpen 'El Puerto esta abierto?
Dim Puerto A s String
Puerto = Serie.PortName 'Nombre del puerto
'
'Manejo y Control de señales
Dim Estado A s Boolean 'True=A ctiva / 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 'A ctivar de Data Terminal Ready
Serie.RtsEnable = True 'A ctivar 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

Fuente 7.23

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 aparta-
dos. El primero de ellos crea las instancias que utilizare-
mos 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 configura-
ción, banderas y proceso. Es una agrupación muy prácti-
ca, facilitando la comprensión, sobre todo cuando esta-
mos en proyectos desarrollados en equipo. Salta a la vista
que el grupo de configuración definirá las variables usa-
das en la misma, las banderas son flags usados en el con-
trol del proceso y finalmente variables utilizadas para
contabilizar o memorizar datos generados en el mismo.
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.
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, Dispose y Tiempo, para continuar con
los procedimientos que formarán nuestra columna verte-
bral: Enviar, Recibir y A brirPuertoSerie.
Por sí solos, todos los procedimientos descritos, con-
tienen 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ñadi-
ré cualquier explicación que ayude a su entendimiento.

'A l 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 'A rrancar el temportizador
'
A brirPuertoSerie() 'A brir puerto serie
End Sub

Fuente 7.24. Sub New, Cuando creamos una instancia a la clase.

'Liberar el puerto
Protected Overrides Sub Finalize()
'cerrar el objecto serie
If PuertoSerieA bierto 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 A s Object, ByVal e A s Timers.ElapsedEventA rgs) _
Handles Temporizador.Elapsed
Static Seg1m A s Integer, VecesEnvioEnCurso A s 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

Fuente 7.26. Función Tiempo, cada segundo…

La función Tiempo implementa la supervisión de las res-


puestas. 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 consiguien-
te 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 des-
tacar el uso del SemaforoSimulador, 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 con-
trario corresponde al envio/recepcion de la simulación; a
tal fin destaquen el uso de Serie.Write en la rutina de recep-
ción.
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 A s 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 A s 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

'
'Recibir una trama
Public Sub Recibir(ByVal sender A s Object, ByVal e A s _
System.IO.Ports.SerialDataReceivedEventA rgs) 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 A s 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 A s 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 A brirPuertoSerie.


Es evidente que antes de poder realizar cualquier opera-
ció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 lle-
vamos a cabo es la lectura de los valores de configuración
almacenados en el archivo app.config. Es interesante, fijar-
nos en un mandato muy especial:

Serie = My.Computer.Ports.OpenSerialPort _
(Puerto, VelocidadPuerto, IO.Ports.Parity.Even, 7, IO.Ports.StopBits.Two)

Nuestro objeto Serie llama al constructor del espacio


de nombres System.IO.Ports usando My, es una manera muy ele-
gante de encontrar y usar los recursos en nuestros espa-
cios 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 A brirPuertoSerie() A s Boolean
'
If PuertoSerieA bierto Then Serie.Close()
'constructor del objecto serie
Try
'Leer los valores para abrir el puerto desde archivo app.config
NombreDelPuerto = A plicacion.LeerConfig _
("app.config", "PuertoSerie_Nombre", NombreDelPuerto).InnerText
VelocidadPuerto = A plicacion.LeerConfig _
("app.config", "PuertoSerie_Velocidad", VelocidadPuerto).InnerText
'
NumeroPuertoA bierto = 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"
PuertoSerieA bierto = True
Catch ex A s Exception
'registrar mensage en caso de error
Mensaje.Registra("ComSerie", ex.Message, EventLogEntryType.Error, 5)
NumeroPuertoA bierto = 0
UltimoMensajeError = ex.Message
PuertoSerieA bierto = False
End Try
Return PuertoSerieA bierto
End Function

Fuente 7.29. Función A brirPuertoSerie


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 dispo-
ner 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 correspon-
diente ISBN y NIFCIF, anotando simplemente si la cap-
sula en cuestión está "Dentro" o "Fuera" del dispensador.
En esta clase simplemente necesitaremos diseñar dos
funciones, la de expulsar y la de introducir.
figura 7.7

Para el acceso a un archivo Access deberemos dispo-


ner de una conexión a OleDbConnection y un adaptador a
OleDbA dapter.
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 FA LSE (ver SET DENTRO = FA LSE). Si lo consigue
(ExecuteNonQuery nos devuelve un valor distinto de 0) la opera-
ción se considera satisfactoria, en caso contrario reporta-
remos 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 A s String, ByVal NIFCIF A s String) A s Integer
Try
Conexion.Open()
A daptador.UpdateCommand = New System.Data.OleDb.OleDbCommand
A daptador.UpdateCommand.Connection = Conexion
A daptador.UpdateCommand.CommandText = _
"UPDA TE Capsulas SET DENTRO = False WHERE (ISBN = '" + ISBN + "') _
A ND (NIFCIF = '" + NIFCIF + "') A ND (DENTRO = True)"
A daptador.UpdateCommand.CommandType = System.Data.CommandType.Text
Expulsar = A daptador.UpdateCommand.ExecuteNonQuery()
Conexion.Close()
Catch ex A s Exception
Return 0
End Try
End Function

Fuente 7.30

Abordaremos nuestra recta final, con la función


Introducir. No podremos evitar incrementar la com-
plejidad, pues en esta operación necesitaremos realizar
algunas operaciones más que en la anterior. A diferen-
cia de cuando expulsamos una cápsula, que esta se
encuentra registrada o no, cuando la introducimos
pueden producirse otras situaciones. Como por ejem-
plo 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 situacio-
nes dotando a esta función de la habilidad suficiente
para 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 manda-
tos de INSERT y UPDA TE. 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 A s String, _
ByVal NIFCIF A s String) A s 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 A s DataSet = New DataSet()


Dim Encontrados() A s DataRow
Try
A daptador.Fill(Datos, "Capsulas")
Encontrados = Datos.Tables("Capsulas").Select("(ISBN = '" + ISBN + "') _
A ND (NIFCIF = '" + NIFCIF + "')")
If Encontrados.Length = 0 Then
If ISBN.Length = 13 A nd NIFCIF.Length = 9 Then
Conexion.Open()
A daptador.InsertCommand = New System.Data.OleDb.OleDbCommand
A daptador.InsertCommand.Connection = Conexion
A daptador.InsertCommand.CommandText = _
"INSERT INTO Capsulas (ISBN, NIFCIF, DENTRO) VA LUES ('" + _
ISBN + "'," + " '" + NIFCIF + "', True)"
A daptador.InsertCommand.CommandType=System.Data.CommandType.Text
Introducir = A daptador.InsertCommand.ExecuteNonQuery()
Conexion.Close()
End If
Else
If Encontrados.Length > 1 Then
Introducir = 888
Else
If Encontrados(0).Item(3) = True Then
Introducir = 999
Else
Conexion.Open()
A daptador.UpdateCommand = New System.Data.OleDb.OleDbCommand
A daptador.UpdateCommand.Connection = Conexion
A daptador.UpdateCommand.CommandText = _
"UPDA TE Capsulas SET DENTRO = True WHERE (ISBN = '" + _
ISBN + "') A ND (NIFCIF = '" + NIFCIF + "')"
A daptador.UpdateCommand.CommandType = _
System.Data.CommandType.Text
Introducir = A daptador.UpdateCommand.ExecuteNonQuery()
Conexion.Close()
End If
End If
End If
Catch ex A s Exception
Return
End Try
End Function

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?

figura 7.8
capítulo 8
El ClienteEplus

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 gran-
des 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 desarro-
llo 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 exco-
mulgen 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 mane-
ra 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 des-
arrollo 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 cons-
truiendo nuestro cliente y nuestra librería de una manera
paralela.
Sin embargo, debemos consolarnos pensando en la
existencia de un montón de “teóricas” y “buenas prácti-
cas” que quedan muy bien entre comillas. Por de pronto,
lo interesante es avanzar en un increíble entorno integra-
do 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 apli-
caciones. Eso sí, siendo conscientes de las limitaciones de
migracion de grandes aplicaciones.
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 pasa-
do media vida programando y es increíble ver cómo conse-
guimos nuestros objetivos practicamente codificando cua-
tro líneas, recordando la aureola de los programadores y su
mito me cuesta creer que yo soy uno de ellos, pues real-
mente con estos entornos más bien me asemejo a un usua-
rio 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 vere-
mos un formulario repleto de cositas…

figura 8.1
Debo hacer hincapié y destacar la utilidad del
Document Outline (ver a la izquierda del diseñador),
imprescindible para llevar a cabo una nomenclatura efi-
ciente 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 pri-
mer marco que contiene los botones de solicitud de intro-
ducció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.
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 infe-
rior están siempre visibles.

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 futu-
ro anexo. A grandes rasgos nos interesa crear una solu-
ción que contenga dos proyectos: ServidorComm y por supues-
to 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 afron-
tar 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 to-
dos 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 entrega-
das. Además como recurso también incluiremos el icono
de la aplicación A pp.ico.
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.

figura 8.6
Y este es su icono:

figura 8.7

Es un gustazo poder editar los recursos en Visual Ba-


sic 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 con-
figuración que detalla la información mas relevante en cuan-
to al curso del desarrollo. Ni más ni menos que nuestro
historial clínico.

' Proyecto : Entregon+


' Nombre : ClienteEPlus.vb
' Tipo : A plicacion Windows
' Clase : frmClienteEPlus
' Creado : 8.05.2005
' A utor : Pep Lluis
' Version : 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

En la figura 8.8 veremos una vista general del código.


Quiero resaltar la importancia de las primeras definiciones,
donde creamos instancias a los objetos expuestos por nues-
tra clase ServidorComm. Vista general del código Cliente-
EPlus.vb para el formulario de nuestro cliente.

figura 8.8
Tal como comentábamos anteriormente es importan-
te el uso de cabeceras que describen nuestro “Historial Clí-
nico”, tan importante como agrupar las funciones dentro
de nuestras regiones de la aplicación.
Fijaros que son dos aspectos muy simples de observar,
sin embargo, de vital importancia para el trabajo en equi-
po. Marcar un modelo que identifica un estilo de progra-
mación, compromete a los ingenieros involucrados en el
proyecto a usar una metodología común, que facilita ex-
traordinariamente la lectura y localización del código. Con-
diciones primordiales para conseguir un desarrollo eficien-
te y un mantenimiento de aplicaciones asequible. Pero
subrayo: aplicaciones hechas en equipo.
No es de extrañar que haga uso de esta receta agru-
pando los procedimientos en torno a cuatro regiones: Car-
gar/liberar formulario, recepción y procesado del evento respues-
ta, 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 normal-
mente 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 su-
brutina que recibirá el disparo del evento en tanto dispo-
nemos de una respuesta a nuestra petición. Esta respuesta
será analizada y posteriormente procesada, no sin compro-
bar 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 in-
cluir 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 car-
ga/descarga del formulario.

'
' A l cargar el formulario
'
Private Sub frmClienteEPlus_Load(ByVal sender A s Object, ByVal e A s _
System.EventA rgs) Handles Me.Load
'imagen estado ko. del puerto serie Visible
Me.pbxKo.Visible = True
'direccionar los eventos de respuesta a la sub DatosRecibidos
A ddHandler PuertoSerie.Respuesta, A ddressOf DatosRecibidos
End Sub

Fuente 8.2

'
' Liberar objetos instanciados
'
Private Sub frmClienteEPlus_Disposed(ByVal sender A s Object, ByVal e A s _
System.EventA rgs) Handles Me.Disposed
PuertoSerie = Nothing 'Liberar puerto serie
Campos = Nothing 'liberar constructor formato tramas
A plicacion = Nothing 'liberar acceso archivo configuraciones app.config
End Sub

Fuente 8.3

Podría parecer exceso de recelo, pero no liberar el


puerto serie, probablemente provocaría que éste no pudie-
ra 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 apli-
cación y no en su anfitrión.
Continuaremos describiendo cómo el cliente va a pro-
cesar 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(A ddressOf ProcesolblRespuesta))
If Campos.LaTramaEstaCompleta(miRespuesta) Then 'Si la trama es completa
'procesar la respuesta, mostrando incidencias
Me.lblInicidencia.Invoke(New EntreHilos(A ddressOf 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 Da-
tosRecibidos invocamos al procedimiento ProcesolblRespuesta y,
si además la trama esta completa, al procesolblInicidencias.
¡Salta a la vista! Si la respuesta recibida está comple-
ta, 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”.
Las dos primeras tienen la importante función de ac-
tualizar nuestra base de datos para que ésta refleje el esta-
do 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 LlenarLis-
taIndicadores que traducirá los códigos de incidencia en reci-
bidos en hexadecimal a cadenas de texto que sean interpre-
tables por los humanos.

'
' 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
'
CRA ccesoBDS = MarcarBD.Expulsar(Me.txtISBN.Text, Me.txtNIFCIF.Text)
Me.lblRespuesta.Text = "(" + miRespuesta + ")"
Case "02" 'Codigo 02 - Introducción
'
CRA ccesoBDS = 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()
'
' A signar 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

Select Case Campos.CodigoFin '


Case "10"
If Campos.CodigoOpe = "01" Then
If CRA ccesoBDS = 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 CRA ccesoBDS '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) + _
"A tencion: 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"
Case "27" : Me.lblInicidencia.Text = "Problemas con el eje del cabezal"
Case "28" : Me.lblInicidencia.Text = "A tasco 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
'
' A signar 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

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 realmen-
te 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 etique-
ta de incidencias el texto de error que corresponde a ese
código y finalmente asigna el color amarillo, rojo naran-
ja, azul o naranja al fondo del panel, según sea la grave-
dad 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 reci-
bida y que están activos.
'
' Rellenar la lista de indicadores despues de una solicitud de Estatus
'
Private Sub LlenarListaIndicadores()
Static MemoriaIndicadores A s 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 A nd Codigo.A lmacenCapsulasVacio Then
Me.lstIndicadores.Items.A dd("CA PSULA S A GOTA DA S EN EL DISPENSA DOR")
If Indicadores A nd Codigo.A lmacenProductoVacio Then
Me.lstIndicadores.Items.A dd("DISPENSA DOR, SIN PRODUCTO")
If Indicadores A nd Codigo.A tascoDispensadorEntrada Then
Me.lstIndicadores.Items.A dd("A TA SCO MECA NISMO DE ENTRA DA S DISPENSA DOR")
If Indicadores A nd Codigo.A tascoDispensadorSalida Then
Me.lstIndicadores.Items.A dd("A TA SCO MECA NISMO DE SA LIDA S DISPENSA DOR")
If Indicadores A nd Codigo.A tascoPinzaDispensadora Then
Me.lstIndicadores.Items.A dd("PINZA DISPENSA DORA A TA SCA DA DISPENSA DOR")
If Indicadores A nd Codigo.A veriaEjeCabezal Then
Me.lstIndicadores.Items.A dd("EJE CA BEZA L A VERIA DO UNIDA D")
If Indicadores A nd Codigo.A veriaEnPinzaDispensadora Then
Me.lstIndicadores.Items.A dd("PINZA DISPENSA DORA A VERIA DA UNIDA D")
If Indicadores A nd Codigo.A veriaEnPlacaControladora1 Then
Me.lstIndicadores.Items.A dd("SUBSTITUIR PLA CA CONTROLA DORA 1")
If Indicadores A nd Codigo.A veriaEnPlacaControladora2 Then
Me.lstIndicadores.Items.A dd("SUBSTITUIR PLA CA CONTROLA DORA 2")
If Indicadores A nd Codigo.DisparoDetectorA ntivandalico Then
Me.lstIndicadores.Items.A dd("SE HA DISPA RA DO LA A LA RMA DE A NTIVA NDA LICA ")
If Indicadores A nd Codigo.FalloEnProcesador1 Then
Me.lstIndicadores.Items.A dd("HA FA LLA DO PROCESA DOR PRINCIPA L ENTREGON+")
If Indicadores A nd Codigo.FalloEnProcesador2 Then
Me.lstIndicadores.Items.A dd("HA FA LLA DO PROCESA DOR SECUNDA RIO ENTREGON+")
If Indicadores A nd Codigo.SobreTemperatura Then
Me.lstIndicadores.Items.A dd("SOBRETEMPERA TURA ENTREGON+")
If Indicadores A nd Codigo.SuciedadOpticaPosicionadorLaser Then
Me.lstIndicadores.Items.A dd("LIMPIA R/A LINEA R LA SE DEL ENTREGON+")
End Sub

Fuente 8.7
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, despué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 con-
centrarnos en el cómo.

'
' A ctualizar los datos de nuestro formulario
'
Private Sub tmpRefresco_Tick(ByVal sender A s System.Object, ByVal e A s _
System.EventA rgs) Handles tmpRefresco.Tick
On Error Resume Next
Static Veces A s Integer, Mensaje A s String, VecesRegistro A s 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.TextA lign = ContentA lignment.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.TextA lign = ContentA lignment.TopLeft
End If
Else
Veces = 0
End If
'
If Me.PuertoSerie.PuertoSerieA bierto Then
'Permitir solicitudes y dejar visible la imagen de puerto OK.
Me.btnIntroducir.Enabled = True
Me.btnExpulsar.Enabled = True
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

'
'A signar 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
'
'A lertar de que existen mensajes en el EventLog
If ServidorComm.RegistroA lertas.MensajesEnRegistro Then
VecesRegistro += 1
Me.lblTextoMensaje.Text = Me.lblTextoMensaje.Text + _
Chr(10) + Chr(13) + "**Mensajes en Registro A plicacion del Sistema"
If VecesRegistro > 10 Then
VecesRegistro = 0
ServidorComm.RegistroA lertas.MensajesEnRegistro = False
End If
End If
'
'Si no exiten mensajes, esconder la etiqueta visualizacion
If Me.PuertoSerie.UltimoMensajeError.Length = 0 A nd _
ServidorComm.RegistroA lertas.MensajesEnRegistro = False Then
Me.lblTextoMensaje.BorderStyle = BorderStyle.None
Me.lblMensaje.Visible = False
Else
Me.lblTextoMensaje.BorderStyle = BorderStyle.Fixed3D
Me.lblMensaje.Visible = True
End If
'A signar 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

Fuente 8.8.

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.

'
' A l Incrementar / Disminuir seleccion de puerto de trabajo
'
Private Sub budPuerto_ValueChanged(ByVal sender A s System.Object, ByVal e A s _
System.EventA rgs) Handles nudPuerto.ValueChanged
'Siempre que el valor se la seleccion se encuentre entre 1 i 8
If (Me.nudPuerto.Value < 9) A nd (Me.nudPuerto.Value > 0) Then
'guardar la seleccion de puerto en archivo app.config
'asi lo abriremos por defecto la proxima session
A plicacion.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.

'
' A brir el puerto de comunicaciones
'
Private Sub btnA brir_Click(ByVal sender A s System.Object, ByVal e A s _
System.EventA rgs) Handles btnA brir.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.A brirPuertoSerie() 'A brir 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

'
' Enviar un correo de pruebas
'
Private Sub btnPrueba_Click(ByVal sender A s System.Object, ByVal e A s _
System.EventA rgs) Handles btnPrueba.Click
'a la pulsacion de btnPruebas
'Instanciar la clase para envio de correo
Dim Enviar A s A lertasPorCorreo = New A lertasPorCorreo
Enviar.A visar("Prueba de Correo A utomatizado") '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 A s System.Object, ByVal e A s _
System.EventA rgs) Handles btnLeer.Click
'a la pulsacion de btnLeer, asignar los contenidos guardados en app.config
Dim email_From A s String = _
A plicacion.LeerConfig("app.config", "email_From", "emailOrigen").InnerText
Dim email_To A s String = _
A plicacion.LeerConfig("app.config", "email_To", "emailDestino").InnerText
Dim email_Cc A s String = _
A plicacion.LeerConfig("app.config", "email_Cc", "emailCopia").InnerText
Dim email_User A s String = _
A plicacion.LeerConfig("app.config", "email_User", "CuentaUsuario").InnerText
Dim email_Pwd A s String = _
A plicacion.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

Fuente 8.12. Al pulsar el botón leer (leemos la configuración para envío automatizado)
panel “AutoeMail”

'
'Guardar valores de configuracion personal, para el envio de alertas por email
'
Private Sub btnSalvar_Click(ByVal sender A s Object, ByVal e A s _
System.EventA rgs) Handles btnSalvar.Click
'a la pulsacion de btnSalvar, guardar valores de configuracion de los textbox
A plicacion.SalvarConfig("app.config", "email_From", Me.txtEmail_From.Text)
A plicacion.SalvarConfig("app.config", "email_To", Me.txtEmail_To.Text)
A plicacion.SalvarConfig("app.config", "email_Cc", Me.txtEmail_Cc.Text)
A plicacion.SalvarConfig("app.config", "email_User", Me.txtEmail_User.Text)
A plicacion.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 A s System.Object, ByVal e A s _
System.EventA rgs) Handles btnExpulsar.Click
'a la pulsacion del btnExpulsar
Dim peticion A s Tramas.SolicitarExpulsion = New Tramas.SolicitarExpulsion
'Trama de expulsion
Dim miPregunta A s 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 A s 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.

' Introducir una capsula


Private Sub btnIntroducir_Click(ByVal sender A s System.Object, ByVal e A s _
System.EventA rgs) Handles btnIntroducir.Click
'a la pulsacion del btnIntroducir
'Trama de introduccion
Dim peticion A s Tramas.SolicitarIntroduccion = New Tramas.SolicitarIntroduccion
'Inicializar variables
Dim misDatos A s 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 A s 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 A s System.Object, ByVal e A s _
System.EventA rgs) 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 A s Tramas.SolicitarEstatus = New Tramas.SolicitarEstatus
'Componer peticion
Dim misDatos A s String = peticion.Trama(1, 2)
Try
PuertoSerie.Enviar(misDatos) 'Enviar peticion
Me.lblPregunta.Text = "(" + misDatos + ")" 'testigo de la peticion
Catch ex A s Exception
Me.lblInicidencia.Text = ex.Message 'En caso de problemas..
Me.lblPregunta.Text = "(" + misDatos + ")"
End Try
End If
End Sub

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+.
[F5]
¡No me lo puedo creer! En un tristrás hemos des-
arrollado una librería de enlace para el Entregon+ y ade-
má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 pregun-
ta relacionada con Entregon+, podréis contactar conmigo
en peplluis@dotnetmania.com.
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 siem-
pre la respuesta: ¡pues no lo se!
Sabemos que cualquier producto en fase beta o relea-
ses para la comunidad, no deben usarse con fines produc-
tivos, 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 reporta-
das en la pestaña de errores.
Y ellos fueron los siguientes:

figura 9.1

Como veréis a continuación ¡nada grave! SerialReceivedE-


ventA rgs
pasó a llamarse SerialDataReceivedEventA rgs.

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 sé lo que sig-
nifica exactamente).

figura 9.4

Nos recomienda encarecidamente que usemos el nue-


vo espacio de nombres System.Net.Mail.
capítulo 10
Conclusiones

Conclusiones
En mis años de vida, he observado que existen algu-
nos lectores que empiezan leyendo el final de libro. Tam-
bién he observado que muchos otros lectores, sienten tal
impaciencia por iniciar la lectura que omiten las primeras
páginas del libro, para irse directamente al primer capítu-
lo.
Siguiendo la inteligencia que me ha caracterizado du-
rante todo este tiempo y después del riguroso análisis an-
terior, aprovechando esta situación de omitir las primeras
hojas y empezar por el final, voy a colocar lo mejor del li-
bro (dedicatorias, agradecimientos, pensamientos filosófi-
cos)… ¡AQUÍ!
La reflexión (pero en serio)
Con la mano en el corazón, a todos los que empeza-
ron leyendo el libro en sus primeras páginas y han llegado
a este su final, mi más sincero agradecimiento.
Mi trabajo ha sido simple y muy divertido, tal como
los exploradores, he iniciado un viaje cuyo destino era Vi-
sual Studio 2005. Ciertamente he ido y he vuelto con la sa-
tisfacción de poder relataros mi experiencia, sin dudar vues-
tra lectura ha dado sentido a mis esfuerzos. Otra cosa serán
las opiniones, los que hayan sintonizado gozarán de haber
adquirido algún que otro conocimiento útil y desearán más,
otros opinarán que hoy en día cualquier tres al cuarto se
atreve a escribir, pero lo importante para mí, aunque sue-
ne cursi, es que lo he escrito pensando en los que como yo,
tenéis ganas de aprender.
Realmente ha sido un buen viaje, repleto de retos, aven-
turas, dificultades, aunque lo único que haya echado en fal-
ta es la presencia de algún compañero.
Empecé a escribir con la ilusión de que me ocurriera
lo que a la Wooling, ¡sí hombre!... la del Jarry Poster, que
mi obra se convirtiera en un Guest Seler, para poder dedi-
carme a hacer películas... ¡eso sí! películas informáticas. Sin
embargo, el mercado laboral, mi precaria situación técni-
ca y el futuro poco esperanzador, me ha llevado a presen-
tar una solicitud en regla para cubrir una plaza de monje
informático en la abadía de Montserrat. A todo ello…
Dedicatorias
Toda mi gratitud a las Gemmas (hija y esposa) por ha-
cerme la vida imposible todos los fines de semana, mientras
intentaba escribir y poner un poco de orden a todo esto que
habéis leído. Mi reconocimiento a tan ardua tarea, no es fácil
sembrar de obstáculos el camino de un escritor tan prolífico.
Sin embargo, ellas lo han conseguido.
Agradecimientos
Mi absoluto rechazo a los esfuerzos de Alfonso Rodrí-
guez y David Carmona, responsables directos de “Desarro-
lla con MSDN”, y de Paco Marín de dotNetManía, que ha-
ciéndome caso omiso, me han ayudado para que este
“montón de hojas” a las que ellos llaman “aportación” vean
la luz en dotNetManía. ¡Espero que sea la última vez!
Este libro pertenece a:

Sergio Gallardo Rdz.


Ojo
20190 - Ags
gars711006v33
sergiogrdz@hotmail.com

Potrebbero piacerti anche