manual curso c net 3raparte
TRANSCRIPT
Autor: Aaron Castro Bazua
Ejemplos con:
Patrones de diseño por capas
Manejo de puerto serial
Control de puerto paralelo
Comunicación con PIC s por RS232
Comunicación con PLC s
Ejemplos con:
Patrones de diseño por capas
Manejo de puerto serial
Control de puerto paralelo
Comunicación con PIC s por RS232
Comunicación con PLC s
Práctica: Chat por puerto serie entre 2 computadoras................................135
Práctica: Comunicación con Microcontrolador PIC 16F887 vía RS232 ......138
Programa para PIC: Comunicación serie bidireccional con C#.NET...........139
Práctica: Control de puerto paralelo...........................................................144
Patrones de diseño por capas: Datos, Negocio y Presentación..................149
Monitoreo de PLC's por medio de aplicaciones en C#.NET.........................166
Configurando un PLC KOYO DL06 de Automation Direct...........................168
Configurando un PLC Siemens S7-300 .....................................................174
Aplicación Cliente para monitorear PLC’s desde C#.NET...........................183
Práctica: Monitoreo y Control con Smartphone...........................................190
Manual 3ra parte
En esta parte del curso nos enfocamos al control de periféricos, la comunicación con otros dispositivos y
la organización de un proyecto por capas.
Temario
135
Práctica: Chat por puerto serie entre 2 computadoras
En este ejercicio vamos a crear un programa de Chat por RS232, para que funcione se ocupan dos computadoras con conector DB9 y el mismo programa, los cables se van a cruzar en los pines RX y TX.
También podemos probarlo cruzando los pines 2 y 3 de nuestro cable DB9 y observaremos como llega el mismo dato que enviamos.
Es básico contar con un puerto COM habilitado en el administrador de dispositivos de la PC, los convertidores USB-Serial son válidos.
5
4
3
2
1
5
4
3
2
1
9
8
7
6
9
8
7
6
ConectorDB9 PC1
ConectorDB9 PC2
En nuestro caso utilizamos el COM3.Ahora creamos un nuevo proyecto de windows forms donde arrastramos los siguientes controles:
ListBoxName: ListBoxChat
TextBoxName: txtMensajeLabelName: lblNick
SerialPortName: serialPortChat
CheckBoxName: chkPuertoSerie
TextBoxName: txtNick
ButtonName: btnEnviarEnabled: False
TextBoxName: txtConversacion
FormularioName: frmChat
136
El puerto serial lo configuramos con los siguientes datos:
En nuestro código declaramos un objeto string con ámbito para toda la clase.
usingusingusingusingusingusingusingusing
namespace
public partial class
stringpublic
System; System.Collections.Generic; System.ComponentModel; System.Data; System.Drawing; System.Linq; System.Text; System.Windows.Forms;
ProyectoChatRS232{ : { datoSerial; frmChat() { InitializeComponent(); }
frmChat Form
Ahora en el evento DataReceived de nuestro puerto serie:
private void object
try
this new
catch
serialPortChat_DataReceived( sender, System.IO.Ports. e) { { serialPortChat.ReadTimeout = 2000; datoSerial = serialPortChat.ReadTo( ); .Invoke( (TextoRecibido)); } { .Show( ); serialPortChat.DiscardInBuffer(); } }}
SerialDataReceivedEventArgs
EventHandler
MessageBox
//intentamos leer el puerto
//espera 2 segundos por el dato//almacena toda la cadena de datos hasta que lee "-FIN-"
//ahora recorta los datos e invoca al manejador
//no llego la cadena completa, interceptamos el error
//borra los datos del buffer de entrada
"-FIN-"
"Error, no recibí el identificador final de la cadena -FIN-"
Creamos una función para manejar el evento TextoRecibido: (se debe teclear manualmente)
TextoRecibido( sender, e) { txtConversacion.Text = ; txtConversacion.AppendText(datoSerial); listBoxChat.Items.Add(datoSerial); }
private void object EventArgs
""
137
Finalmente en el CheckBox que habilita el puerto:
chkPuertoSerie_CheckedChanged( sender, e) { (chkPuertoSerie.Checked) { { (!serialPortChat.IsOpen) { serialPortChat.Open(); } .Enabled = ; chkPuertoSerie.Text = ; chkPuertoSerie.BackColor = .YellowGreen; } { .Show(
); } } { { (serialPortChat.IsOpen) { serialPortChat.Close(); chkPuertoSerie.BackColor = .Red; = ; } } { .Show( ); } } }
private void objectif
try
if
true
catch
else
try
if
false
catch
EventArgs
Color
MessageBox
Color
MessageBox
//si el puerto esta cerrado
//habilita el botón solo si esta abierto el puertobtnEnviar
btnEnviar.Enabled
"Puerto Abierto"
"Algo anda mal, no pude abrir el puerto COM"
"Algo anda mal no pude cerrar el COM"
Ahora podemos probar la aplicación:
Si no contamos con otra PC podemos unir los pines 2 y 3 del DB9 y ver como nos llega el dato que enviamos.
En el evento Click del btnEnviar
private void object
string
btnEnviar_Click( sender, e) { mensaje = txtNick.Text + + txtMensaje.Text; serialPortChat.Write(mensaje + ); listBoxChat.Items.Add(mensaje.ToString()); }
EventArgs
" dice: ""-FIN-"
138
Práctica: Comunicación con Microcontrolador PIC 16f887 vía RS232
En este ejercicio vamos a combinar hardware externo con C#, con la ayuda del PIC vamos a disponer de 8 bits digitales de entrada, 1 canal análogo (0-5 volts) y 8 bits de salida TTL.
La manera de operar será mandando mensajes RS232 que soliciten información al PIC o le ordenen escribir datos, para este circuito ocupamos el siguiente material:
El diagrama es el siguiente:
Microswitch
16F887
VDDVDDVSSVSS
RA0/AN0/ULPWU/C12IN0-RA1/AN1/C12IN1-RA2/AN2/VREF-/CVREF/C2IN+RA3/AN3/VREF+/C1IN+RA4/T0CKI/C1OUTRA5/AN4/SS/C2OUTRA6/OSC2/CLKOUTRA7/OSC1/CLKIN
RE0/AN5/RDRE1/AN6/WRRE2/AN7/CSRE3/MCLR/Vpp/
RB0/AN12/INTRB1/AN10/C12IN3-
RB2/AN8RB3/AN9/PGM/C12IN2-
RB4/AN11RB5/AN13/T1GRB6/ICSPCLKRB7/ICSPDAT
RD0RD1RD2RD3RD4
RD5/P1BRD6/P1CRD7/P1D
RC0/T1OSO/T1CKIRC1/T1OSI/CCP2
RC2/P1A/CCP1RC3/SCK/SCLRC4/SDI/SDA
RC5/SDORC6/TX/CKRC7/RX/DT
+
+
5 Volts
Resistencias 330 ohm
Resistencias 1k ohm
Leds
3334353637383940
1920212227282930
1516171823242526
¡No ocupa oscilador!
323112
234567
1413
89
101
11
DISPLAY LCD
12345678
161514131211109
MA
X2
32
9
8
7
6
VccGndT1OUTR1INR1OUTT1INT2INR2OUT
C1+V+
C1-C2+C2-
V-T2OUT
R2IN
Capacitores 1 ufaradio
+
+
+
+
-
-
-
-
-
ConectorDB9
hembra
5 Volts
ENABLERSRW
D4D5D6D7
Entrada analógicacon potenciómetrode 10k ohm
5
4
3
2
1
Material:1 pic 16F887, 1 conector DB9 hembra,1 microswitch (8 polos), 1 potenciómetro de 10 k ohm, 8 resistencias 1 kOhm, 8 resistencias 330 Ohm, 8 leds, 2 protoboard, 1 max232, 4 capacitores 1uf 63volts, 1 metro de cable de red, 1 display lcd 16x2.
Equipo necesario: 1 Programador de Pics (40 pines), 1 Fuente de voltaje (5 volts), Pinzas, Multímetro.
Programa para PIC: Comunicación serie bidireccional con C#.NETCon este ejercicio el PIC envía el bit RE0/AN5 y un byte proveniente del microswitch a la aplicación de C#, también recibe un byte para escribirlo en el puerto A, se utiliza el LCD para mostrar mensajes sobre lo que esta haciendo el PIC.Requerimientos:Ÿ El lcd debe estar conectado para que funcioneŸ La aplicación en C#.NET que mande los datos debe agregar un Enter al final de la cadena.
139
140
Puede bajar los archivos fuente de este proyecto desde:
Si no cuenta con CCS para compilar el programa puede grabar el hexadecimal directamente con MPLAB.
Como puede observar el PIC esta siempre esperando tres mensajes y responde a esos comandos:
Observe que todos los mensajes PIC PC tienen un final con la cadena: “+Fin”, esto permite hacer mas eficiente la lectura en C# y conocer el final de la trama de datos:
printf("+%u+Fin", valorAnalogo); printf("Se escribio en el puerto B +Fin");
Ahora con su circuito armado y el PIC programado abrimos un nuevo proyecto de windows forms.
http://www.multitecnologia.com/ArchivosFuentePIC16f887CursoC.rar
Arrastramos los siguientes controles:
CheckBoxName: chkPuertoSerie
ListBoxName: listBoxDatosEntrada
NumericUpDownName: numStepByteMinimum: 0Maximum: 255
ButtonName: btnMandaByteEnabled: False
ButtonName: btnRecibeDatDigEnabled: False
ButtonName: btnRecibeAn0Enabled: False
LabelName: lblCanalAn0Text: RA0
FormularioName: frmTarjeta
SerialPortName: serialPort
TextBoxName: txtRecibeDatos
141
"escribe”
“Ra0"
"byteEnt"
: Cuando el PIC recibe esta cadena se queda esperando un número entre 0-255 con el que imprime los datos en el puerto B del PIC.
: Con este mensaje nos responde con un valor con la conversión a decimal del puerto analogico (0-5 volts),
: Esta cadena le dice al PIC que nos responda con los 8 bits de entrada.
la resolución es de 8 bits.
142
El puerto serial debe estar con la misma configuración del PIC:#use rs232(baud=9600,parity=N,xmit=PIN_C6,rcv=PIN_C7,bits=8)
Este ejercicio es similar al Chat, también creamos un objeto string visible para toda la clase:
System; System.Collections.Generic; System.ComponentModel; System.Data; System.Drawing; System.Linq; System.Text; System.Windows.Forms;
ProyectoPIC16f883{ : { datoSerial; frmTarjeta() { InitializeComponent(); }
usingusingusingusingusingusingusingusingnamespace
public partial class
stringpublic
frmTarjeta Form
En el CheckBox escribimos:
chkPuertoSerie_CheckedChanged( sender, e) { (chkPuertoSerie.Checked) { { (!serialPort.IsOpen) { serialPort.Open(); } btnMandaByte.Enabled = ; btnRecibeAn0.Enabled = ; btnRecibeDatDig.Enabled = ; chkPuertoSerie.Text = ; chkPuertoSerie.BackColor = .YellowGreen; } { .Show( ); } } { { (serialPort.IsOpen) { serialPort.Close(); chkPuertoSerie.BackColor = .Red; chkPuertoSerie.Text = ; btnMandaByte.Enabled = ; btnRecibeAn0.Enabled = ; btnRecibeDatDig.Enabled = ; } } { .Show( ); } }
private void object
iftry
if
truetrue
true
catch
elsetry
if
falsefalse
false
catch
EventArgs
Color
MessageBox
Color
MessageBox
//si el puerto esta cerrado
"Puerto Abierto"
"Algo anda mal con el puerto COM"
"Puerto Cerrado"
"Algo anda mal no pude cerrar el COM"
143
En el evento DataReceived del Puerto serie: serialPort_DataReceived( sender,
System.IO.Ports. e) {
}
De nuevo creamos una función que refleje los datos recibidos: TextoRecibido( sender, e) { txtRecibeDatos.Text = ; txtRecibeDatos.AppendText(datoSerial); analisisCadenas(datoSerial); }
Ahora otra nueva función para separar los datos: analisisCadenas( cadena) { cad;
(cadena.Contains( )) {
cad = cadena.Replace( , ); cad = cad.Replace( , ); lblCanalAn0.Text = + cad; } (cadena.Contains( )) { listBoxDatosEntrada.Items.Clear(); cad = cadena.Replace( , ); [] arreglocadena = cad.Split( ); ( st arreglocadena) { listBoxDatosEntrada.Items.Add(st.ToString()); } } }
private void object
private void object
private void string
string
if
if
stringforeach string in
SerialDataReceivedEventArgs
EventArgs
{ serialPort.ReadTimeout = 2000; datoSerial = serialPort.ReadTo( ); .Invoke( (TextoRecibido)); } { .Show( ); serialPort.DiscardInBuffer(); }
try
this new
catch
//intentamos leer el puerto
//espera 2 segundos por el dato//almacena toda la cadena de datos hasta que lee "Fin"
//ahora recorta los datos e invoca al manejador
//no llego la cadena completa, interceptamos el error
//borra los datos del buffer de entrada
"Fin"
"Error, no recibí el identificador final de la cadena: Fin"
EventHandler
MessageBox
// en esta función recibimos los datos para su análisis y reordenamiento.
//ahora llamamos a la función que extrae los datos// MessageBox.Show(datoSerial);
//cuando el PIC nos manda el dato análogo del Ra0 // le quitamos lo que no sirve
//El dato siempre llega de esta forma: “Ra0+ numero +”, // solo nos quedamos con: numero
//cuando nos llega la trama con los bits de entrada
//limpiamos todos los valores antes de actualizar
//con el Split cortamos cada parte de la cadena donde exista un '+'//con cada dato se forma un arreglo de cadenas
//ahora cada miembro del arreglo entra a los items del listbox
""
"Ra0+"
"Ra0+" """+" """Lectura RA0: "
"byteEnt"
"byteEnt" ""
'+'
144
Para cada uno de los botones en su evento Click escribimos: btnMandaByte_Click( sender, e)
{ serialPort.Write( + ( )13); serialPort.Write(numStepByte.Value.ToString() + ( )13); }
btnRecibeDatDig_Click( sender, e) { serialPort.Write( + ( )13); // }
btnRecibeAn0_Click( sender, e) { serialPort.Write( + ( )13);// }
Ahora podemos probar la comunicación con el PIC, cuide no conectar la tierra del DB9 a un voltaje externo.
private void object
char
char
private void object
char
private void object
char
EventArgs
EventArgs
EventArgs
// primero se manda la cadena "escribe" con el Enter para// avisar que viene un numero después, (char)13= Enter
// ahora envía el numero entre 0 y 255 del numStepByte
// Al mandar "byteEnt" al PIC nos regresa los 8 bits de entrada// el orden viene de esta manera:// b0=RA7, b1=RA6, b2=RC0,b3=RC1,b4=RC2,b5=RC3,b6=RC4,b7=RC5
(char)13= Enter
//al mandar "Ra0" el PIC nos responde con el valor decimal de//la conversión del canal análogo a 8 bits
(char)13= Enter
"escribe"
"byteEnt"
"Ra0"
Práctica: Control de Puerto Paralelo
En este ejercicio vamos construir la interfaz electrónica para controlar 8 bits de salida y leer 4 entrada por medio del puerto DB25.Vamos a implementar el siguiente circuito con el Buffer 74L541 para alimentar los leds y reducir la carga de corriente en el puerto, el material necesario es:
1 Protoboard1 Buffer 74LS5411 Microswitch4 resistencias de 1 kohm8 resistencias de 330 ohm8 leds comunes de 5mmCableado
Equipo necesario:1 fuente de voltaje de 5 volts DCPinzas, Multímetro.
Al ejecutar puede comprobar como el Pic digitaliza el dato análogo y los 8 bits de entrada al pulsar los botones en C#, también escribe en el puerto B del pic el valor del Numeric Up Down.
145
IMPORTANTETrabajar con fuentes externas de voltaje y periféricos de la PC presenta un riesgo de quemar componentes de la tarjeta madre, se debe poner en común la tierra de la PC con la fuente externa y tener mucho cuidado en que no entre voltaje por ese punto a la computadora, esto provocaría un daño irreparable.
Como consideración inicial debemos respetar los colores en las líneas del protoboard:Azul = TierraRojo = Voltaje
Ahora implementamos el circuito:
Resistencias 330 ohm
Leds
Registro de Estatus
Registro de Control
Registro de Datos
S0 S1 S2 S3 S4 S5 S6 S7
13 12 11 10 9 8 7 6 5 4 3 2 1
25 24 23 2 2 21 20 19 18 17 16 15 14
D7 D6 D5 D4 D3 D2 D1 D0
C0 C1 C2 C3 C4 C5 C6 C7
Puerto Paralelo - Conector DB25
Diagrama del circuito
123456789
10
20191817161514131211
74
LS
54
1
Resistencias1 kohm
1
2
3
4
5
6
7
8
9
10
11
12
13
Microswitch
14
15
16
17
18
19
20
21
22
23
24
25
5 Volts
Fuente
- ++ -
OE
OE
Vcc
En nuestro conectortenemos el registro dedatos y estatus, falta el registro de control.
D0 D7Tierra
S3 S4 S5 S6 S7
EL S3 lo conectamosdirecto a tierra.Solo utilizamos un nible
15
El circuito lucirá de esta manera
ImportanteEl Bit S7 esta negado internamente, el dato que ingresemos será invertido al entrar al registro.
{
Para los bits S0,S1,S2, no
tenemos acceso,dependiendo la
tarjeta madre estosvalores pueden ser:
000 ó 111
Antes de conectar el protoboard a la PC probamos los las entradas del buffer, observe que por defecto sus salidas están activadas, por lo tanto esta esperando una tierra en su entrada para apagar el led, tomamos cualquier cable largo y lo conectamos a tierra, el otro extremo lo posicionamos en cada entrada del buffer para comprobar como se apaga cada led.
Una vez probado el protoboard verifique que no existan cables sueltos que puedan provocar cortos.También use su multímetro para probar continuidad entre el cable DB25 y las entradas del buffer.Antes de prender la fuente que alimenta el protoboard observe el monitor de la PC, si nota un parpadeo al encenderla apague rápidamente la fuente o desconecte el cable, esto puede salvar su tarjeta madre, si no hay cambio todo esta bien conectado, en caso de apagarse el monitor y la PC lamento decirle que ha dañado su tarjeta madre.
Cuando las tarjetas se dañan se debe a un choque de niveles de voltaje interno, en estos casos se queman integrados básicos que pueden ser mas caros de reparar que comprar una tarjeta nueva.
Considerando que su hardware externo esta listo vamos con la interfaz de la PC.
El puerto paralelo requiere una librería para funcionar: inpout32.dll
La descargamos desde:
Ahora colocamos este archivo en la carpeta C:\Windows\System32\inpout32.dll
Posiblemente se ocupe registrar en la ventana de ejecutar: Escribir: REGSVR32 c:\windows\system32\inpout32.dll
www.multitecnologia.com/inpout32.rar
Con nuestra interfaz lista nos vamos a VisualStudio y creamos un nuevo proyecto de windows forms.
146
El cable DB25 entra al circuito de esta manera:El canal del protoboard separa cada conector de cable plano, por un lado tenemos el bus de datos con la tierra y por el otro el registro de estatus .
D7 D0Tierra
Buffer
Recuerde respetarlos colores del
protoboard:Línea azul: TierraLínea roja: Voltaje
147
Ahora arrastramos los siguientes controles:
Escribimos el código para los eventos de cada control mostrados: System; System.Collections.Generic; System.ComponentModel; System.Data; System.Drawing; System.Linq; System.Text; System.Windows.Forms; System.Runtime.InteropServices;
frmPuertoParalelo{ : { { [ ( , EntryPoint = )] Output( adress, value); [ ( , EntryPoint = )] Input( address); } frmPuertoParalelo() { InitializeComponent(); }
SBDato_Scroll( sender, e) {
.Output(888, SBDato.Value); lblDatos.Text = + SBDato.Value.ToString(); } btnLeerPuerto_Click( sender, e) { lblLecturaPuerto.Text = + .Input(889).ToString(); } }}
Finalmente ejecutamos y al mover el scrollbar los leds deben cambiar su estado, para leer diferentes datos movemos los interruptores del microswitch y damos click en el btnLeerPuerto para actualizar.
usingusingusingusingusingusingusingusingusingnamespace
public partial class
public class
public static extern void int int
public static extern int int
public
private void object
private void object
//esta directiva es necesaria
//Creamos esta clase para importar la DLL y tener acceso a sus comandos
//escribimos un dato de 8 bits (0-255) en el registro de datos //observe que el valor se manda en decimal (int)
//el valor 888 es la dirección del registro de datos
//leemos el registro de status con los bits S4, S5, S6, S7 del diagrama,//el bit S3 se encuentra forzado a tierra sin el microswitch.//los bits S0,S1,S2 no estan disponibles y por lo regular su valor es 0//el valor 889 es la dirección del registro de Status
frmPuertoParalelo Form
PortAccess
DllImport
DllImport
ScrollEventArgs
PortAccess
EventArgs
PortAccess
"inpout32.dll" "Out32"
"inpout32.dll" "Inp32"
"Escritura: "
"Lectura: "
ButtonName: btnLeerPuerto
HSCrollBarName: SBDatoMinimum:0, Maximum: 255
LabelName: lblDatosText: Escritura:
LabelName: lblLecturaPuertoText: Lectura:
FormularioName: frmPuertoParalelo
148
ANEXOS
Código de colorespara resistencias
4N32
149
Práctica:Patrones de diseño por capas:
Datos, Negocio y Presentación.
¿Qué son? ¿Para que sirven?
En este ejercicio vamos a retomar los objetos para toma de muestras para respaldarlos en tres tipos de archivos: texto, xml y base de datos.
La parte importante de este proyecto es comprender el diseño de software mediante las capas de Datos, Negocio y Presentación.
Estos patrones de diseño son metodologías de organización que nos permiten sacar mejor provecho a los objetos, segmentar las tareas e implementar de manera eficiente.
La idea comenzó desde los 60's, uno de los pioneros en el concepto fue Edsger Dijkstra, sin embargo tomo auge hasta los 90's con los primeros lenguajes populares orientados a objetos como Java y Delphi.
Esta metodología también trata de solucionar “La crisis del software” que se hizo presento en una conferencia de la OTAN en 1968, posteriormente comenzaron las doctrinas para la ingeniería de software.
La definición técnica de las capas es:
Capa de presentación: Es la que ve el usuario (también se la denomina "capa de usuario"), presenta el sistema al usuario, le comunica la información y captura la información del usuario en un mínimo de proceso (realiza un filtrado previo para comprobar que no hay errores de formato). También es conocida como interfaz gráfica y debe tener la característica de ser "amigable" (entendible y fácil de usar) para el usuario. Esta capa se comunica únicamente con la capa de negocio.
Capa de negocio: Es donde residen los programas que se ejecutan, se reciben las peticiones del usuario y se envían las respuestas tras el proceso. Se denomina capa de negocio (e incluso de lógica del negocio) porque es aquí donde se establecen todas las reglas que deben cumplirse. Esta capa se comunica con la capa de presentación, para recibir las solicitudes y presentar los resultados, y con la capa de datos, para solicitar al gestor de base de datos almacenar o recuperar datos de él. También se consideran aquí los programas de aplicación.
Capa de datos: Es donde residen los datos y es la encargada de acceder a los mismos. Está formada por uno o más gestores de bases de datos que realizan todo el almacenamiento de datos, reciben solicitudes de almacenamiento o recuperación de información desde la capa de negocio.
CAPA DE DATOS
CAPA DE NEGOCIO
CAPA DE PRESENTACIÓN
150
Ahora en “lenguaje terrícola”…capa de presentación
capa de negocio t
capa de datos
La son los formularios, botones y controles con que interactúa el usuario, el desarrollador que diseña esta capa debe preocuparse por facilitar la navegación del usuario, hacer atractivos los gráficos, presentar la información de manera rápida y eficiente, etc.
Esta metodología permite que el equipo de trabajo avance en paralelo, es conveniente que una persona se encargue de interactuar con el cliente en cuestiones de diseño y la otra parte del equipo se enfoque a la parte lógica y de datos.
iende a confundir en cuanto a su definición, por lo regular asociamos este término con establecimientos comerciales, en este caso “negocio” se refiere a intercambio de información, también se le llama capa lógica y es donde reside realmente todo el modelado del sistema, es importante mencionar que aquí no existen formularios ni controles, todo son clases con diferentes tareas.
Finalmente la es la manera de separar los archivos físicos como bases de datos, documentos xml o txt que almacenen la información, para quien diseña la capa de negocio resulta completamente irrelevante cual es el fabricante de la base de datos ya que solo tiene que mandar sus objetos y esperar recibirlos sin preocuparse por las consultas o la conexión al servidor.
En resumen, las capas nos facilitan el diseño de cualquier sistema y permiten reutilizar código por bloques sacando mejor provecho a los objetos.
Es importante que en nuestras soluciones de VisualStudio se definan las capas por medio de 3 proyectos, lógicamente la capa de presentación es la única que maneja formularios, mientras que la de negocio y datos son bibliotecas de clases.
Por lo que hemos visto la capa de presentación no tiene que ver con la capa datos, esto se refleja en las dependencias del proyecto.
La
Comenzamos abriendo un proyecto del tipo Solution en VisualStudio, elegimos solución en blanco.
En esta solución vamos a agrupar nuestras 3 capas.
Lo nombramos SolucionMuestrasSensor
En el explorador de soluciones nos aparece de esta manera:
151
Agregamos una carpeta dando click en el ícono, la nombramos Proyectos
Estando en la carpeta y dando click derecho elegimos agregar proyecto:
Elegimos un proyecto de windows forms con el nombre: CapaPresentacionSensores:
Nos queda de esta forma:
De nuevo damos click en la carpeta y agregamos otro proyecto del tipo Biblioteca de Clases, lo nombramos CapaNegocioSensores.
152
Nos queda:
Finalmente agregamos otro proyecto de tipo Biblioteca de Clases con el nombre: CapaDatosSensores.
Ahora tenemos la estructura básica de la solución:
Ahora vamos a crear la dependencia entre las capas:Damos click derecho en la carpeta “References” de nuestra capa de presentación, elegimos agregar referencia.
Nos aparece esta ventana donde elegimos la capa de negocio en la pestaña de proyectos:
Ahora nuestra capa de presentación puede acceder a todas las clases de la capa de negocio:
Repetimos el proceso con la capa de negocio y agregamos como referencia la capa de datos.
Nos queda:
153
154
Por lógica la capa de datos no depende de otras capas, es decir no tiene por que heredar de otras bibliotecas, esto crearía una referencia circular que rompe con el concepto que buscamos sobre separar las tareas.
En las capas de Negocio y Datos borramos las clases por defecto Class1.cs
Empezamos con la capa de negocio, agregamos las siguientes clases:Muestra, ArrayListMuestras, Sensor.
Para ahorrar tiempo las descargamos desde:
Damos click derecho agregar elemento existente.
www.multitecnologia.com/CapaNegocioSensores.rar
Seleccionamos las tres y nos queda de esta forma:
Ahora para la capa de datos descargamos este archivo:
Siguiendo el mismo proceso agregamos estos 3 elementos existentes:
Elegimos las 3:
Cada una de estas clases se encarga de convertir en un archivo nuestro objeto sensor.
www.multitecnologia.com/CapaDatosSensores.rar
xx
Nuestra capa de datos nos queda de esta forma:
Aún nos falta algo…En la capa de datos vamos a agregar dos referencias necesarias para crear la base de datos y poder conectarnos a ella:
Ambas se encuentran en la pestaña de los componentes COM.
Las referencias son:
Microsoft ActiveX Data Objects 2.8 Library Microsoft ADO Ext. 2.8 for DDL and Security
Otro detalle importante es que los tres proyectos deben ejecutarse para plataforma x86, para seleccionarlo nos vamos a: Proyecto/Propiedades/Generar.
Recuerde seleccionar cada proyecto en particular y abrir esta ventana para seleccionar X86, hacemos esto por que los componentes COM que acabamos de insertar son para esta plataforma.
155
156
Para que se compile el namespace en cada clase debe tener el mismo nombre que el proyecto, en el caso de la capa de datos las tres clases:
En la capa de negocio las clases también deben tener el mismo nombre de proyecto y namespace.
Volvemos a la capa de presentación y en nuestro formulario arrastramos los siguientes controles:
FormName: frmSensoresText. Generación de archivos para muestras
ButtonName: btnCrearMuestra,Text: Crear Muestra
ButtonName: btnGrabarTxt, Text: Grabar Muestras en Texto
ButtonName: btnGrabarXml, Text: Grabar Muestras en XML
ButtonName: btnGrabarDB, Text: Grabar Muestras en Base de Datos
saveFileDialogName: saveFileDialog
DataGridViewName: dGridViewMuestrasColumns: Colección Hora, Lectura
157
En nuestro código del formulario agregamos la directiva:
CapaNegocioSensores;
Aparte dos instancias de Sensor y Random:
nuestroSensor = (); lecturaSensor = ();
System; System.Collections.Generic; System.ComponentModel; System.Data; System.Drawing; System.Linq; System.Text; System.Windows.Forms; CapaNegocioSensores;
CapaPresentacionSensores{ : { nuestroSensor = (); lecturaSensor = (); frmSensores() { InitializeComponent(); }
En el evento Load del formulario le damos valores a Sensor:
frmSensores_Load( sender, e) { nuestroSensor.Nombre = ; nuestroSensor.Tag = ; }
Para el btnCrearMuestra:
btnCrearMuestra_Click( sender, e) { m = (); m.hora = .Now.ToString(); m.lectura = lecturaSensor.Next(255); nuestroSensor.agregarMuestra(m); dGridViewMuestras.Rows.Insert(0, m.hora, m.lectura); }
using
new
new
usingusingusingusingusingusingusingusingusingnamespace
public partial class
new
newpublic
private void object
private void object
new
Sensor Sensor
Random Random
frmSensores Form
Sensor Sensor
Random Random
EventArgs
EventArgs
Muestra MuestraDateTime
//con este objeto random obtenemos valores aleatorios en la lectura
//importamos la capa con las clases de negocio
//creamos nuestro objeto sensor con ámbito para toda la clase
//con este objeto random obtenemos valores aleatorios en la lectura
//para no estar reescribiendo el nombre y tag en el botón muestra//le damos sus propiedades al objeto sensor desde aqui
//creamos una instancia de muestra
//guardamos la hora//guardamos un entero aleatorio
//agrega una muestra a nuestro objeto sensor
//Imprime la muestra en el datagrid
"SensorTemperatura_5B""PLC5-DB1"
158
En los otros botones el código es el siguiente: btnGrabarTxt_Click( sender, e) { saveFileDialog.DefaultExt = ; saveFileDialog.Filter = ; saveFileDialog.ShowDialog(); ruta = saveFileDialog.FileName; { nuestroSensor.EscribirTxt(ruta); .Show( ); } ( ee) { .Show(ee.Message.ToString()); } } btnGrabarXml_Click( sender, e) { saveFileDialog.DefaultExt = ; saveFileDialog.Filter = ; saveFileDialog.ShowDialog(); ruta = saveFileDialog.FileName; { nuestroSensor.EscribirXml(ruta); .Show( ); } ( ee) { .Show(ee.Message.ToString()); } } btnGrabarDB_Click( sender, e) { saveFileDialog.DefaultExt = ; saveFileDialog.Filter = ; saveFileDialog.ShowDialog(); ruta = saveFileDialog.FileName; { nuestroSensor.EscribirBaseDatos(ruta); .Show( ); } ( ee) { .Show(ee.Message.ToString()); } }
private void object
stringtry
catch
private void object
stringtry
catch
private void object
stringtry
catch
EventArgs
MessageBox
ExceptionMessageBox
EventArgs
MessageBox
ExceptionMessageBox
EventArgs
MessageBox
Exception
MessageBox
"txt""txt files (*.txt)|*.txt"
"Confirmado, ya se guardo el archivo de texto"
"xml""xml files (*.xml)|*.xml"
"Confirmado, ya se guardo el archivo .XML"
"mdb""mdb files (*.mdb)|*.mdb"
"Confirmado, ya se guardo la base de datos Access"
//mandamos la ruta a la capa de negocio//operación de riesgo
//obtenemos cualquier mensaje de error
//mandamos la ruta a la capa de negocio//operación de riesgo
//obtenemos cualquier mensaje de error
//mandamos la ruta a la capa de negocio//operación de riesgo
//obtenemos cualquier mensaje de error
Respetando todos estos detalles no debe existir problema para ejecutar, solo creamos algunas muestras con el botón “crear muestra” y convertimos a diferentes formatos de archivo nuestro objeto Sensor.
159
Para cada caso nos pide el lugar donde vamos a guardar el archivo y nos confirma cuando se generó correctamente.
ANEXOSClases Capa Negocio
Clase Muestra.csusingusingusingusingnamespace
public class
stringpublic string
getreturn
setvalue
System; System.Collections.Generic; System.Linq; System.Text;
CapaNegocioSensores{ { _Hora = ; hora { { _Hora; } {_Hora = ; } }
Muestra
//Lo inicializamos con la cadena vacio para diferenciar cuando//el objeto aún no recibe su datoprivate "vacio"
Finalmente obtenemos tres archivos de respaldo en diferentes formatos.
//lo inicializamos con -1 para simbolizar que no ha sido llenado.
//_Lectura es entero por que las conversiones A-D siempre vienen en enteros
//la propiedad indice es privada,solo con el método asignaIndice se puede modificar
//no se ocupa un set por que no deseamos que se escriba este valor directamente
//este método permite recibir la propiedad .count del arraylist
//Nos permite usar un ArrayList
//con este método agregamos una instancia de muestra a nuestro ArrayList
//le mandamos el indice como propiedad a la muestra
//este método nos trae todo el arreglo completo de muestras
//este método recibe el índice del arreglo y regresa la muestra con ese indice
//busca en todas las muestras de esta colección
_Lectura = -1; lectura { { _Lectura; } {_Lectura = ; } } _Indice; indice { { _Indice; } } asignaIndice( indice) { _Indice = indice; } }}
System; System.Collections.Generic; System.Linq; System.Text; System.Collections;
CapaNegocioSensores{ : { agregarMuestra( m) { m.asignaIndice( .Count); .Add(m); } traerMuestras() { ; } traeMuestra( Indice) { ( m ) { (m.indice == Indice) { m; } } ; } }}
private int
public intget
return
setvalue
private intpublic int
getreturn
public void int
usingusingusingusingusingnamespace
public class
public void
thisthis
public
return this
public int
foreach in this
if
return
return null
Clase ArrayListMuestras.cs
ArrayListMuestras ArrayList
Muestra
ArrayListMuestras
Muestra
Muestra
160
Clase Sensor.csusingusingusingusingusingusingnamespace
public class
private stringpublic string
getreturn
setvalue
private stringpublic string
getreturn
setvalue
private
newnew
foreach in this
return
System; System.Collections.Generic; System.Linq; System.Text; System.Data; CapaDatosSensores;
CapaNegocioSensores{ : { _Nombre= ; Nombre { { _Nombre; } { _Nombre = ; } } _Tag = ; Tag { { _Tag; } { _Tag = ; } } ConversionDataset() { tablaMuestras = (); tabla = (); tablaMuestras.Tables.Add(tabla); tablaMuestras.Tables[0].TableName = ; tablaMuestras.Tables[0].Columns.Add( ); tablaMuestras.Tables[0].Columns.Add( ); tablaMuestras.Tables[0].Columns.Add( ); ( m ) { linea = tablaMuestras.Tables[0].NewRow(); linea[0] = m.indice.ToString(); linea[1] = m.hora; linea[2] = m.lectura.ToString(); tablaMuestras.Tables[0].Rows.Add(linea); } tablaMuestras; }
//nos permite instanciar DataSet
//recordemos que todo el modelado y la lógica de la capa de negocio es//independiente de los datos, nuestras Clases: Sensor, Muestra, ArrayListMuestra//no son instanciables en esta capa, la capa de Negocio depende de la capa de Datos//No viceversa
//valor inicial
//valor inicial
//esta función nos convierte nuestro arraylistMuestras (Indice, Hora, Lectura) en//un DataSet(tabla) que es fácil de mandar a otra capa con los datos ordenados
//agregamos las tres columnas de nuestra tabla: Indice, Hora, Lectura//nombre de la tabla
//columna 0 de la línea//columna 1 de la línea//columna 2 de la línea
Sensor ArrayListMuestras
DataSet
DataSet DataSetDataTable DataTable
Muestra
DataRow
"Sin Nombre"
"Sin Tag"
"Muestras""Indice""Hora""Lectura"
161
//estas funciónes son el intermediario entre la presentación y capa de datos//recibimos la ruta y debemos mandar la colección de muestras en un dataSet
//creamos nuestra instancia de la capa de datos y mandamos//la ruta al constructor como valor inicial
//si nos llega una ruta válida
//ahora convertimos nuestra tabla de muestras en un Dataset
// mandamos todo a la capa de datos para que se convierta en archivo txt
//si nos llega una ruta válida//nos convierte el arraylist en Dataset
// mandamos todo a la capa de datos para que se convierta en archivo XML
//si nos llega una ruta válida//nos convierte el arraylist en Dataset
// mandamos todo a la capa de datos para que se convierta en archivo MDB
EscribirTxt( ruta) { DatosTxt = (); (ruta != ) {
TMuestras = ConversionDataset(); DatosTxt.EscribeTxt(ruta, .Nombre, .Tag, TMuestras); } { ( ); } }
EscribirXml( ruta) { DatosXml = (); (ruta != ) {
TMuestras = ConversionDataset(); DatosXml.EscribeXml(ruta, .Nombre, .Tag, TMuestras); } {
( ); } } EscribirBaseDatos( ruta) { DatosBD = (); (ruta != ) {
TMuestras = ConversionDataset(); DatosBD.EscribeBD(ruta, .Nombre, .Tag, TMuestras); } { ( ); } } }}
public void string
newif null
this this
elsethrow new
public void string
newif null
this this
else
throw new
public void string
newif null
this this
elsethrow new
EscrituraMuestrasTxt EscrituraMuestrasTxt
DataSet
ArgumentException
EscrituraMuestrasXml EscrituraMuestrasXml
DataSet
ArgumentException
EscrituraMuestrasBaseDatos EscrituraMuestrasBaseDatos
DataSet
ArgumentException
"Existe un error en la colección o la ruta"
"Existe un error en la colección o la ruta"
"Existe un error en la colección o la ruta"
Siguiente...Clases Capa Datos
162
Clase EscrituraMuestrasBaseDatos.csusingusingusingusingusingusingusingusingnamespace
public class
public void string string string
new
nullstring
public void string string
string
string
newnew
System; System.Collections.Generic; System.ComponentModel; System.Data; System.Text; System.IO; ADOX; System.Data.OleDb;
CapaDatosSensores{ { EscribeBD( ruta, nombreSensor, tagSensor,
Muestras) { ADOX. cat = ADOX. (); cat.Create( + ruta + + ); cat = ; nombreTabla = nombreSensor; creaTabla(ruta,nombreTabla); insertaDatos(ruta,nombreTabla, Muestras); } creaTabla( ruta, nombreTabla) { conex = +
+ ruta + ; con; crear = + nombreTabla +
; con = (conex); cmd = (crear, con); con.Open(); cmd.ExecuteNonQuery(); con.Close(); }
}
//con esta clase convertimos nuestro Dataset en una Base de datos Access
//creamos las columnas de la tabla//insertamos los valores del Dataset
//ahora nos conectamos para insertar las columnas con sus tipos de datos
//se ejecuta la consulta //cerramos conexión
EscrituraMuestrasBaseDatos
DataSet
CatalogClass CatalogClass
OleDbConnection
OleDbConnectionOleDbCommand OleDbCommand
"Provider=Microsoft.Jet.OLEDB.4.0; Data Source=" ";""Jet OLEDB:Engine Type=5"
"Provider = Microsoft.Jet.OLEDB.4.0;" @"Data Source = " ";"
"CREATE TABLE " " (Indice INT, Hora VARCHAR(50), Lectura INT);"
public void string string
string
stringnew
for int
intstringint
new
insertaDatos( ruta, nombreTabla, Muestras) { conex = +
+ ruta + ; con; inserta; con = (conex); con.Open(); ( i = 0; i < Muestras.Tables[0].Rows.Count; i++) { ID = .ToInt32(Muestras.Tables[0].Rows[i][0]); Hora = Muestras.Tables[0].Rows[i][1].ToString(); Lectura = .ToInt32( Muestras.Tables[0].Rows[i][2]); inserta = + nombreTabla + + ID +
+ Hora + + Lectura + ; cmd = (inserta, con); cmd.ExecuteNonQuery(); } con.Close(); } }}
DataSet
OleDbConnection
OleDbConnection
Convert
Convert
OleDbCommand OleDbCommand
// nos conectamos para insertar las columnas con sus tipos de datos
//abrimos la conexión con la base de datos //con un ciclo vamos insertando cada línea de la tabla
//líneas
//se ejecuta la consulta
//cerramos conexión
"Provider = Microsoft.Jet.OLEDB.4.0;" @"Data Source = “ ";"
"INSERT INTO " " VALUES (" ",'” "'," ")"
163
Clase EscrituraMuestrasTxt.cs
System; System.Collections.Generic; System.Linq; System.Text; System.Collections; System.Data; System.IO;
CapaDatosSensores{ {
EscribeTxt( ruta, nombreSensor, tagSensor, Muestras)
{ [] texto; texto = [Muestras.Tables[0].Rows.Count + 1]; texto[0] = + nombreSensor + + tagSensor +
; ( i = 0; i < Muestras.Tables[0].Columns.Count; i++) { texto[0] += Muestras.Tables[0].Columns[i].ColumnName + ; } linea; ( i = 0; i < Muestras.Tables[0].Rows.Count; i++) { linea = .Empty; ( j = 0; j < Muestras.Tables[0].Columns.Count; j++) { linea += Muestras.Tables[0].Rows[i][j].ToString() + ; } texto[i + 1] = linea; } { .WriteAllLines(ruta, texto); } { (
); } }
}}
usingusingusingusingusingusingusingnamespace
public class
public void string string string
new
for int
for int
for int
try
catch
throw new
//Con esta Clase convertimos un DataSet de las Muestras en un archivo TXT
//La capa de datos solo necesita el nombre, tag y tabla de muestras (Dataset) //para crear un archivo, no le interesa como fueron modelados //los objetos en la capa de negocio
//en este arreglo almacenamos toda la tabla
//Rellenamos la cabecera del archivo
//creamos las tres columnas con el encabezado: Indice, Hora, Lectura
//Rellenamos los valores de la tabla separando por líneas y comillas
//líneas
//limpiamos la línea para reutilizarla//Columnas
//se agrega al arreglo
//Creamos el archivo
EscrituraMuestrasTxt
DataSet
StringString
String
String
File
ArgumentException
"Sensor: " " Tag: " " Estructura: "
";"
";"
"No pude crear el archivo, esta vacío el DataSet"
164
Clase EscrituraMuestrasXml.cs
System; System.Collections.Generic; System.Linq; System.Text; System.Data; System.Xml;
CapaDatosSensores{ { EscribeXml( ruta, nombreSensor, tagSensor,
Muestras) { Muestras.EnforceConstraints = ; Muestras.DataSetName = ;
xmldoc = (Muestras); NodoRaiz = xmldoc.FirstChild; nuevoNodo = xmldoc.CreateNode( .Element, , ); AtributoNombre = xmldoc.CreateAttribute( ); AtributoNombre.Value = nombreSensor; AtributoTag = xmldoc.CreateAttribute( ); AtributoTag.Value = tagSensor; nuevoNodo.Attributes.Append(AtributoNombre); nuevoNodo.Attributes.Append(AtributoTag);
NodoRaiz.AppendChild(nuevoNodo); xmldoc.Save(ruta ); } }}
usingusingusingusingusingusingnamespace
public class
public void string string string
false
new
null
//En esta clase convertimos nuestro dataset en un archivo XML en forma de árbol
//eliminamos las reglas del Dataset para crear el xml
//cambiamos su nombre antes de mandarlo al XML
//grabamos la tabla simplemente asignando el dataset, // C# se encarga de acomodar los datos en forma de árbol XML
//localizamos la raíz del XML //creamos un nuevo nodo para los datos que faltan: Nombre, Tag
//creamos un par de atributos con el nombre y tag que nos manda la capa de negocio
//ahora agregamos esos atributos al nodo recién creado
//finalmente situamos ese nuevo nodo en la raíz
//guardamos nuestro documento
EscrituraMuestrasXml
DataSet
XmlDataDocument XmlDataDocumentXmlNode
XmlNode XmlNodeType
XmlAttribute
XmlAttribute
"MuestrasSensor"
"Sensor"
"Nombre"
"Tag"
En este ejercicio practicamos el concepto de patrones de diseño por capas y la generación
de archivos a partir de un objeto, es importante que aplique esta metodología para sacar real
provecho a C#.NET.
165
MONITOREO DE PLC'S POR MEDIO DE APLICACIONES EN C#.NETEn esta práctica nos vamos a conectar a un equipo Siemens S7-300 y Koyo DL06 por medio de un servidor OPC.
¿Qué es OPC? OLE for Process ControlEl estándar OPC permite la comunicación entre dispositivos industriales para aplicaciones de monitoreo y control, cada marca que soporta este protocolo crea su propio driver para integrarse a la red y compartir sus recursos.
El estándar OPC es regulado por la fundación ( ), su objetivo es facilitar el uso de esta tecnología y proporciona código fuente para que diferentes fabricantes implementen sus servidores y clientes OPC.
http://www.opcfoundation.org/
En nuestro curso vamos trabajar con uno de los fabricantes más populares Kepware.
KepWare es una empresa especializada en conectividad para equipos industriales que maneja las marcas y modelos mas populares y los pone a disposición de cualquier aplicación cliente como LabView, Wonderware o en nuestro caso C#.NET.
Utilizaremos como intermediario una versión demo del servidor KepServer, solo vamos a configurar la conexión con los PLC y KepServer se encargará de mandar los datos a nuestra aplicación cliente en C#.NET
Empezamos entrando a la página de KepWare.
Para descargar la versión demo debemos registrarnos y activar una cuenta.Al entrar como usuario podemos descargar KepServer y operarlo durante dos horas continuas antes que nos pida un reset.
http://www.kepware.com/
166
En caso de ocupar más de dos horas para las pruebas se reinicia el servidor o la PC.
En nuestro panel de usuario descargamos KepServer y ClientAce.
ClientAce nos permite conectarnos desde C#.NET a nuestro servidor.
167
Una vez descargados comenzamos la instalación de KepServer cuidando los siguientes detalles:
Debemos seleccionar todos los modelos de PLC a los que deseamos conectarnos, las casillas que no sean activadas no están incluidas en la instalación.
En nuestro caso debemos activar todos los drivers para Siemens, Allen-Bradley y AutomationDirect.
Continuamos los pasos hasta finalizar.
Al instalar nuestro servidor y activarlo obtenemos un panel de monitoreo como el mostrado, si usted tiene alguna simulación activada posiblemente vea más datos en la tabla.
En Kepserver los datos de acceso se organizan por medio de canales, cada canal tiene diferentes dispositivos que agrupan nuestros datos.
Ahora con nuestro servidor instalado vamos a preparar los PLC's para mandar sus datos.
168
En este ejemplo nos vamos a conectar a un PLC KOYO DL06 con 20 entradas y 16 salidas digitales, por medio de un cable RS232- RJ45 y el protocolo K Sequence vamos a dar de alta los datos en KepServer, una vez disponibles en el servidor podemos consultar las Tags con nuestra aplicación cliente en C#.NET
Al abrir DirectSoft32 nos pregunta por el nombre del proyecto el modelo del PLC, el nombre del proyecto no puede ser largo.
De forma inmediata nos aparece el entorno para programar en Ladder.
Para establecer comunicación con el PLC nos vamos al menú principal: Connect
Elegimos COM1, si estamos usando un convertidor USB-RS232 y se registro un COM mayor a 2 tenemos que irnos a las propiedades de ese puerto en Windows y cambiarle el ID a 1 o 2.
Configurando un PLC KOYO DL06 de Automation Direct
Antes debemos tener nuestro PLC encendido con el cable conectado al puerto COM de la PC.Al aparecer esta ventana damos click en añadir.
169
En el dispositivo elegimos: Not Sure Protocolo: K Sequence
Ahora le damos un nombre a la conexión. Nos queda…
Al seleccionarlo es importante tener el PLC en estado de “terminal” o nos aparecerá el siguiente mensaje.
Elegimos usar “Use Disk” para comenzar con nuestro proyecto en blanco.Para empezar a crear el diagrama DirectSoft debe estar en modo “Program”.
Cuando se crea con éxito la conexión, DirectSoft compara las versiones del programa entre PLC y pantalla, posteriormente salta una ventana donde nos pregunta si deseamos cargar a pantalla el programa grabado en el equipo o viceversa.
Vamos a insertar 3 contactos normalmente abiertos con nuestras entradas digitales y asignaremos la salida física Y0.
En el PLC DL06 las entradas se encuentran en los registros X y las salidas en los registros Y.
Para ver nuestra barra de dispositivo elegimos Edit Mode: ON
170
Ahora se muestran los elementos en la parte inferior.
Vamos a arrastrar 3 contactos normalmente abiertos: F2Nos aparece esta ventana:
Damos click en la lupa:
Por lo pronto solo Le asignamos su dirección (X0) y nickname (encendido)
Nos queda…
Repetimos esta operación para X1 y X2 con los nicknames: confirmación y accionamiento.
Para la salida nos vamos al botón de bobinas manteniendo seleccionado el final del diagrama (NOP).
Con F4 nos aparece el catalogo…
Elegimos Standard Coil: OUT.
171
Nos aparece de esta manera pero falta la dirección física, de nuevo click en la lupa.
Elegimos Y0 con el nickname “salida”.
Para terminar debemos indicar que nuestro diagrama ha terminado, nos vamos al final de la siguiente línea.
Con F5 asignamos un comando END
Finalmente todo el diagrama queda de esta forma:
Con el botón de Status accionado podemos cerrar y ver se activan los contactos en color azul.
Ahora guardamos el diagrama en el PLC.
Al guardarlo y poner el PLC en “RUN” comprobamos en pantalla la activación de la salida.
Muy bien, ahora nos falta dar de alta estas direcciones “Tags” en el servidor IPC (KepServer).Para esto nos vamos menú y en archivo elegimos exportar la documentación de los elementos.
172
Elegimos una ruta para nuestro archivo csv y listo.
Ahora nos vamos a KepServer y agregamos un canal:
Elegimos el mismo protocolo que se uso para programar el PLC: K Sequence
No olvidemos habilitar el diagnostico.
Los datos del COM se mantienen con las mismas características anteriores.
Nos queda:
Ahora agregamos el dispositivo, damos un nombre relativo a la Marca.
Elegimos nuestro modelo:
173
Mantenemos el ID=1 Elegimos los parámetros propuestos:
Importamos nuestro archivo csv generado anteriormente con DirectSoft32.Continuamos hasta finalizar la conexión:
Al principio no aparece nada…
Damos click derecho en el dispositivo y en la ventana elegimos la pestaña “Database Creation” Autocreate.
Ahora podemos ver nuestros Tags separados por entradas “X” y salidas “Y”.
Para conocer su valor damos click en el Quik Client:
Ahora al mover nuestros interruptores X0, X1 y X2 del PLC podemos ver como cambian su valor.
Con este ejercicio comprobamos que es posible acceder a los datos del PLC DL06 por medio de KepServer.
174
Configurando un PLC Siemens S7-300En esta práctica vamos a configurar un PLC Siemens S7-300 CPU 315F -2PN/DP con dos módulos de entradas digitales y análogas: DI16/DO16xDC24V, A14/A02 x8BIT.
La característica principal de este CPU es contar con una interfaz ethernet para comunicarse directamente con PC's o por medio de una red TCP/IP.
Vamos configurar el hardware del S7-300 con el Simatic Manager
Creamos un nuevo proyecto: Asignamos un nombre:
Nos aparece nuestro proyecto vacío.
Agregamos un nuevo objeto S7-300 Ahora damos Click en Hardware
Nos aparece otro programa de nombre HWConfig, aquí con f igu ramos todo e l hardware para que coincida con nuestro dispositivo real.
175
Como primer paso necesitamos un bastidor, lo arrastramos desde el menú derecho.
En caso de que nuestra versión de Simatic no se encuentre actualizada debemos conectarnos a Internet y descargar todos los modelos actuales:
Cuando se ocupan actualizaciones se crea una lista para descargar y Simatic se encarga de poner al día todo el entorno de programación.
Con nuestro software actualizado podemos construir nuestro sistema.
En la localidad 1 agregamos una fuente de poder, primero seleccionamos el 1 y después la fuente.
Ahora seleccionamos el 2 y elegimos el CPU 315-2FJ14-0AB0, posiblemente aparezca la ventana de configuración IP que se muestra, si tenemos el PLC conectado directamente a la PC (sin router) podemos dejar la configuración mostrada con la IP del PLC 192.168.0.1, por lógica nuestra PC debe tener cualquier otra IP que este en el mismo segmento y que no choque con la del PLC ejemplo: 192.168.0.254.
176
Continuando con nuestro bastidor nos saltamos hasta el slot 4 dejando el 3 vacío ya que solo se usa en aplicaciones complejas.Arrastramos la tarjeta: DI16/DO16xDC24V al slot 4
Finalmente en el slot 5 arrastramos la tarjeta A14/A02x8BIT
177
Guardamos y compilamos nuestro sistema.
Para probar la sincronía con nuestro PLC nos vamos al menú y elegimos:
Elegimos la primera opción...
Ahora nos pide una confirmación de la IP del PLC que vamos a asignar.
Cerramos el HWConfig y regresamos al Simatic Manager.
En el nodo de programa elegimos Bloques:
178
Por defecto nos aparece el bloque OB1 donde vamos a crear nuestro programa en lenguaje de escalera (KOP).
Al elegir KOP y Aceptar nos aparece Step7 para programar nuestro PLC recién configurado.
Seleccionando el primer segmento agregamos un contacto normalmente abierto:
En la dirección elegimos una entrada en el registro 1 bit 0
Asignamos la salid: Byte 0, bit 0
Guardamos el proyecto: Lo cargamos al PLC
179
Ahora elegimos una salida:
Si no existen problemas de comunicación podemos probar con los interruptores del PLC.
Ahora nos vamos a KepServer para dar de alta nuestro S7-300
El procedimiento para agregar un “Tag” proveniente del PLC es el siguiente:
Dando click derecho elegimos agregar canal:
Como nos vamos a conectar por Ethernet a nuestro S7-300 elegimos: Siemens
TCPIP/Ethernet y activamos la casilla de diagnostico para localizar errores fácilmente.
180
En el adaptador de red podemos elegir “Default” y en la siguiente ventana aceptamos la opción predeterminada.
Finalizamos la conexión...
Al terminar nos queda un canal de comunicación listo para dar de alta los PLC's.
Damos Click en agregar dispositivos y asignamos un nombre relacionado con el área que se va a automatizar o la marca del equipo:
Ahora elegimos nuestro PLC S7-300
En esta parte es básico elegir la dirección IP del PLC que se le asigno desde Simatic al momento de configurar el CPU.
181
Si deseamos comprobar que nuestro PLC ya tiene una IP podemos hacer un PING desde la ventana de comandos de Windows: teclamos “cmd”
Y ahora escribimos la IP de este ejemplo: ping 192.168.0.1
El PLC debe responder como cualquier dispositivo que maneje TCP/IP.
Continuando con la instalación elegimos los valores de timing por default.
Ahora nos queda nuestro canal apuntando a un dispositivo en particular, solo nos resta asignarle los tags que vamos a monitorear.
Para asignar cada Tag no es necesario importar una biblioteca de símbolos, solo escribimos la dirección del PLC: I1.0
Ahora nos aparece el Tag en el panel del servidor.
Para probar que realmente tenemos acceso a nuestro Tag abrimos el Quick Client
La ventana nos muestra otros accesos a memoria del PLC y propiedades del canal.
Nos vamos a Channel1.EquipoSiemens y nuestro Tag debe aparecer con calidad “Good” en caso contrario debemos reconfigurar la conexión, checar el cableado y que el PLC este operando.
Si desconocemos la localidad del Tag la podemos obtener de la tabla de símbolos del programa HWConfig (Simatic).
182
183
Ahora al activar al QuickClient y variar los potenciómetros del PLC podemos ver los cambios en la variable.
IMPORTANTE:Observe que no fue necesario programar el PLC para tener acceso a sus datos.
Para una entrada analógica solo escribimos la dirección del Tag (IW276) para tener acceso al dato tipo WORD.
Si no conocemos el tipo de dato elegimos Default.
Con KepServer manejando nuestros Tags y comunicándose con los dispositivos ya estamos listos
para programar el cliente en C#.NET.
En este ejercicio vamos a crear un cliente en C# que se conecte con KepServer y pueda monitorear 10 Tags digitales.
Vamos a crear un proyecto sencillo con dos formularios como se muestra:
El frmCliente.cs es donde vamos a registrar las Tags.
El frmVentanaDispositivo.cs es una ventana emergente que se va a disparar cuando algún Tag cambie su valor.
Aplicación Cliente para monitorear PLC’s desde C#.NET
Para trabajar en este ejemplo debemos tener a la mano los controles del ClientAce en la barra e herramientas.
Importante:Si a usted no le aparece ClienAceDa_Junction en el cuadro de herramientas lo debe agregar manualmente siguiendo estos pasos...(siguiente hoja).
Dar click derechosobre el cuadro deherramientas y elegirelementos.
Elegimos examinar y buscamos el archivo Kepware.ClientAce.DA_Junction.dll en
archivos de programa/Kepware Technologies
Al darle click y cerrar estasventanas nos debe apareceren el cuadro de herramientas
Ahora ocupamos agregar otra dll al proyecto, nos vamos a la carpeta de referencias y agregamos elarchivo Kepware.ClientACE.OpcClient.dll
Se debe ver de esta forma, esta librería no es un componente del cuadro de herramientas.
184
En nuestro frmCliente arrastramos y configuramos los siguientes controles:
DataGridViewName:dGridTagsColumns: 1 columnacon Name: DirecciónTagHeaderText: Dirección Tag
ButtonName: btnRegistrarTag, Enabled: False, Backcolor: Red
TextBoxName: txtTag, Enabled: False
LabelName: lblTags, Text: Escriba la dirección del Tag
CheckBoxName: chkConectarse, Text: Conectarse a KepServer, Backcolor: Red
Formulario Name: frmCliente, Text: Cliente para KepServer
185
Agregamos el componente clientAceDA_Junction1 alformulario y conservamossu nombre.
Agregamos una columnaal dGridTags como se muestra.
Para evitar problemas a la hora de compilar en algunas máquinas con Windows 7 vamos a desactivar el “LoaderLock” siguiendo estos pasos: Depurar-> Excepciones->Managed Degubbed assistants-->Loaderlock (desactivarlo).
186
El código:Agregamos estas directivas e instancias.
System; System.Collections.Generic; System.ComponentModel; System.Data; System.Drawing; System.Linq; System.Text; System.Windows.Forms; Kepware.ClientAce.OpcDaClient; Kepware.ClientAce.DA_Junction;
PruebasKepserver{ : { daServerMgt = (); [] itemIdent = [10]; activeServerSubscriptionHandle; numTags = 0;
frmCliente() { InitializeComponent(); daServerMgt.DataChanged += . (DataChanged);
}
En nuestro CheckBox “chkConectarse” chkConectarse_CheckedChanged( sender, e) { (chkConectarse.Checked) { url = ; clientHandle = 1; cInfo = (); cInfo.LocalId = ; cInfo.KeepAliveTime = 1000; cInfo.RetryAfterConnectionError = ; cInfo.RetryInitialConnection = ; connectFailed; { daServerMgt.Connect(url, clientHandle, cInfo, connectFailed); CreaInstancias();
chkConectarse.BackColor = .GreenYellow; btnRegistrarTag.BackColor = .GreenYellow; chkConectarse.Text = ; btnRegistrarTag.Enabled = ; txtTag.Enabled = ; dGridTags.Enabled = ; } ( ex) { connectFailed = ; .Show(ex.ToString()); } }
usingusingusingusingusingusingusingusingusingusingnamespace
public partial class
newnew
intbyte
public
new
private void object
if
stringint
new
truetrue
booltry
ref out
truetrue
true
catch
true
frmCliente Form
DaServerMgt DaServerMgtItemIdentifier ItemIdentifier
DaServerMgt DataChangedEventHandler
EventArgs
ConnectInfo ConnectInfo
ColorColor
Exception
MessageBox
//Instancia del Servidor//capacidad para 10 Tags
//nos sirve para suscribir Tags//contador para crear un ID con cada nuevo Tag
//registramos el evento Datachanged
//Si nuestro servidor es la versión 5 y esta operando usamos esta cadena
//Intentamos conectarnos a KepServer
//crea 10 instancias para tags con valores vacíos//como no salto la excepción cambiamos los gráficos a verde
//y habilitamos los controles
//se registro un error
"opcda:///Kepware.KEPServerEX.V5"
"MiConexion"
"Conectado"
Debe estar escrito exactamente igual, observe que son 3 diagonales ///
187
{ daServerMgt.Disconnect(); btnRegistrarTag.Enabled = ; txtTag.Enabled = ; dGridTags.Enabled = ; chkConectarse.Text = ; txtTag.Text = ; chkConectarse.BackColor = .Red; btnRegistrarTag.BackColor = .Red; } }
Creamos una función para llenar de manera temporal el arreglo para las 10 Tags y evitar errores posteriores.
CreaInstancias() { ( x = 0; x <= 9; x++) { itemIdent[x] = (); itemIdent[x].ItemName = ; itemIdent[x].ClientHandle = x; itemIdent[x].DataType = ; } }
Ahora otra función para suscribir los datos desde nuestro formulario.
SuscribirDatos( nombreTag) { (numTags < 10) { txtTag.Text = ; clientSubscriptionHandle = 1; active = ; updateRate = 1000; deadBand = 0.0f; itemIdent[numTags].ItemName = nombreTag; revisedUpdateRate; { daServerMgt.Subscribe(clientSubscriptionHandle, active, updateRate, revisedUpdateRate, deadBand, itemIdent, activeServerSubscriptionHandle); (itemIdent[numTags].ResultID.Succeeded == ) { .Show( + itemIdent[ ].ItemName.ToString()); } { dGridTags.Rows.Insert(0, itemIdent[numTags].ItemName.ToString()); numTags++; } }
else
falsefalse
false
private void
for int
new
null
private void string
if
intbool trueintfloat
inttry
out refout
if false
else
//nos desconectamos del servidor
//cambiamos los gráficos para simbolizar la desconexión
//esta función crea 10 instancias vacías para nuestro arreglo//ItemIdentifier[] itemIdent = new ItemIdentifier[10];//al momento de registrar cada una se le cambia el nombre, la idea es llenar//el arreglo de manera temporal para no provocar error con valores nulos
//no importa si se registra o no, el texto del Tag se borra//parámetros de la conexión
//actualizamos el nombre del Tag
//este método da de alta el grupo de Tags
//si llega a este punto el registro fue exitoso
//incrementamos el identificador del arreglo
"Conectarse a KepServer"""
""
""
"Falla: "
ColorColor
ItemIdentifier
MessageBox numTags
( ex) { .Show( + ex.Message.ToString()); } } { .Show( ); } }
Creamos la función DataChanged que actúa como interrupción cada vez que algun Tag registrado sufre algún cambio.
DataChanged( clientSubscription, allQualitiesGood, noErrors, [] itemValues) { { ( itemValue itemValues) { (itemValue.ResultID.Succeeded) { ventana = ();
ventana.parametrosTag( itemValue.ClientHandle.ToString(), itemValue.Value.ToString(), itemValue.Quality.Name.ToString(), itemValue.TimeStamp.ToString()); ventana.Show(); } } } ( ex) { .Show( , ex.ToString()); } }
Finalmente en el botón para registrar los Tags: btnRegistrarTag_Click( sender, e)
{ (txtTag.Text != ) { validaSuscribir = ; ( x = 0; x <= 9; x++) { (txtTag.Text == itemIdent[x].ItemName) { validaSuscribir = ; .Show( ); ; } } (validaSuscribir) {SuscribirDatos(txtTag.Text); } } { .Show( ); } }
catch
else
private void int bool bool
try
foreach in
if
new
catch
private void object
if
bool truefor int
if
false
break
if
else
Exception
MessageBox
MessageBox
ItemValueCallback
ItemValueCallback
frmVentanaDispositivo frmVentanaDispositivo
ExceptionMessageBox
EventArgs
MessageBox
MessageBox
"Fallo: "
"solo podemos registrar 10 Tags"
"Ocurrió un error, la razón es ... "
""
"este Tag ya esta dado de alta"
"Falta el nombre del Tag"
//si el numero de Tags no pasa de 10
//Este método se dispara ante cualquier cambio en los tags registrados
//busca en todos los tags registrados para encontrar cual tiene un cambio
//creamos una instancia del formulario para la ventana del Tag
//mandamos 4 parámetros al método publico en la clase frmVentanaDispositivo();//public void parametrosTag(string direccion,string valor,
//string calidad, string tiempo)
//Mandamos como parámetro el nombre del tag
//bandera para validar suscripción
//buscamos entre todos los Tags para ver si ya esta dado de alta
//no va a permitir que se suscriba
//si no encontro algún valor igual nos permite registrarlo
188
Ahora en el otro formulario arrastramos un control Label:
En su código agregamos un método público que nos permita mandarle datos desde frmCliente. System; System.Collections.Generic; System.ComponentModel; System.Data; System.Drawing; System.Linq; System.Text; System.Windows.Forms;
PruebasKepserver{ : { frmVentanaDispositivo() { InitializeComponent(); } parametrosTag( direccion, valor, calidad, tiempo) { lblMensaje.Text = + direccion +
+ valor; (valor == ) { .BackColor = .YellowGreen; } { .BackColor = .Red; } } }}
Al ejecutar nos conectamos al servidor y registramos los Tags con el mismo nombre que tienen en la pantalla de KepServer.
Para el ejemplo de este curso los Tags grabados en el PLC Koyo DL06 y KepServer son:Channel1.PLCAutomationDirect.X.accionamientoChannel1.PLCAutomationDirect.X.confirmaciónChannel1.PLCAutomationDirect.X.encendidoEs necesario que el nombre sea exactamente el mismo, respetando acentos y mayúsculas.
usingusingusingusingusingusingusingusingnamespace
public partial class
public
public void string stringstring string
if
this
else
this
frmVentanaDispositivo Form
Color
Color
"La entrada: " " Tiene un valor: "
"True"
189
LabelName: lblMensaje
Registramos los Tags uno por uno.
Por ser una versión Demo de ClientAce nos salta esta ventana:
Ahora cuando se va registrando cada nuevo Tag salta la ventana con los valores nuevos.
Si usted mueve el interruptor del Tag registrado debe observar como se crea otra ventana con los datos.
Con este ejercicio comprobamos que es posible conectarnos a un PLC desde C#.NET aplicando el software de Kepware.
190
DISPOSITIVOS MÓVILES EN C#.NET
Práctica: Monitoreo y Control con SmartphoneEn este ejercicio vamos a crear un proyecto para recibir mensajes por un puerto serial bluetooth y mandar comandos de activación a otra aplicación.
La aplicación consiste en monitorear una tabla de alarmas y activar 4 motores aireadores desde el Smartphone.
Posiblemente ocupe actualizar VisualStudio con los SDK, descargue los marcados dependiendo su sistema operativo e idioma.
191
Por defecto nos aparece un SmartPhone rectangular, para cambiarlo usamos la propiedad FormFactor: Windows Mobile 6 Standard Landscape QVGA
Creamos un nuevo proyecto tipo Smart Device y seleccionamos Windows Mobile 6 Standard SDK.
Renombramos Form1.cs con frmPrincipal.cs
192
Aparte agregamos otro formulario con las mismas características:
Le asignamos el nombre: frmPlanta.cs
Nos queda:
Nuestro proyecto solamente manejará dos formularios, en el primero vamos a monitorear una tabla de alarmas y en el segundo activaremos 4 motores aireadores.
Vamos a realizar una prueba de comunicación:
1.- Conectamos nuestro SmartPhone o PDA a la PC por su cable USB.
2.- Ejecutamos nuestro proyecto
.
A continuación salta una ventana donde aparecen las opciones de compilación:
Si nuestro Smartphone esta conectado elegimos la opción: Windows Mobile 6 Standard Device, en caso contrario elegimos el emulador mas parecido al dispositivo real.
Los emuladores permiten probar el código sin ocupar un dispositivo real, sin embargo no cubren todas las funciones, en este caso de los puertos Bluetooth que aplicaremos en este curso.
El ejercicio que vamos a codificar necesita un dispositivo real con puerto Bluetooth y al menos un puerto COM virtual.
Si fue posible cargar los formularios a nuestro dispositivo real estamos listos para agregar el código.
Una de las diferencias que tienen los proyectos para móviles es que la organización de las dependencias es diferente, si ocupamos implementar capas de negocio y datos las vamos a incorporar como carpetas en el mismo proyecto.Dando click derecho sobre el proyecto agregamos una carpeta como se muestra, la nombramos “Capa Negocio”.
Ahora dando click derecho sobre la carpeta CapaNegocio agregamos 3 nuevas clases con los nombres:
El código para la clase Alarmas.cs nos define 3 propiedades de tipo string donde guardaremos mensajes de alarmas.
Es importante que el namespace tenga el mismo nombre que el proyecto: MonitoreoSmartPhone
usingusingusingusingnamespace
class
private stringpublic string
getreturn
setvalue
private stringpublic string
getreturn
setvalue
private stringpublic string
getreturn
set value
System; System.Collections.Generic; System.Linq; System.Text;
MonitoreoSmartPhone{ { _hora; Hora { { _hora; } { _hora = ; } } _concepto; Concepto { { _concepto; } { _concepto = ; } } _acción; Acción { { _acción; } { _acción = ; } } }}
Alarmas//en esta propiedad guardamos la hora de la Alarma
//Esta propiedad guarda el conceptode la Alarma//Ejemplo: "Sobrecalentamiento caldera 5A"
//En esta propiedad se guarda la Acción recomendable para arreglar la//falla para facilitar la operación y estandarizar el proceso// Ejemplo: "Ajustar el control PID"
193
En la clase AlarmasArrayList.cs guardamos la colección de objetos alarma y creamos dos métodos, uno para agregar cada mensaje y otro para crear una tabla de datos que posteriormente se incrustará en un Datagrid del dispositivo mobil.
System; System.Linq; System.Collections.Generic; System.ComponentModel; System.Data; System.Text; System.Collections;
MonitoreoSmartPhone{ : { agregaAlarma( alarma) { .Add(alarma); } TraeTabla() { tablaMuestras = (); tabla = (); tablaMuestras.Tables.Add(tabla);
tablaMuestras.Tables[0].TableName = ; tablaMuestras.Tables[0].Columns.Add( ); tablaMuestras.Tables[0].Columns.Add( ); tablaMuestras.Tables[0].Columns.Add( ); ( a ) { linea = tablaMuestras.Tables[0].NewRow(); linea[0] = a.Hora; linea[1] = a.Concepto; linea[2] = a.Acción; tablaMuestras.Tables[0].Rows.Add(linea); } tablaMuestras.Tables[0]; } }}
Finalmente creamos la clase Dispositivos.cs, en ella guardamos el estado actual de 4 motores aireadores que se activarán en la pantalla móvil.
System; System.Linq; System.Collections.Generic; System.Text;
MonitoreoSmartPhone{ { _activaAireador1= ; ActivaAireador1 { { _activaAireador1; } { _activaAireador1 = ; } }
usingusingusingusingusingusingusingnamespace
class
public voidthis
publicnew
new
foreach in this
return
usingusingusingusingnamespace
classprivate bool falsepublic bool
getreturn
setvalue
AlarmasArrayList ArrayList
Alarmas
DataTableDataSet DataSetDataTable DataTable
AlarmasDataRow
Dispositivos
//este método crea el arreglo de alarmas
//este método crea una tabla a partir del arraylist de alarmas
//nombre de la tabla
//agregamos las tres columnas de nuestra tabla:
//llenamos la tabla con cada objeto Alarma de nuestro Arraylist
//nos regresa una tabla ordenada con todas las alarmas para poder//incrustarla en el DataGrid
//en esta clase guardamos el estado actual de los dispositivos
"Alarmas"
"Hora""Alarma""Accion"
Recuerde que heredamos de Arraylist y debe estar incorporado System.Collections
194
Es importante que el namespace tenga el mismo nombre que el proyecto: MonitoreoSmartPhone
195
_activaAireador2 = ; ActivaAireador2 { { _activaAireador2; } { _activaAireador2 = ; } } _activaAireador3 = ; ActivaAireador3 { { _activaAireador3; } { _activaAireador3 = ; } } _activaAireador4 = ; ActivaAireador4 { { _activaAireador4; } { _activaAireador4 = ; } } }}
private bool falsepublic bool
getreturn
setvalue
private bool falsepublic bool
getreturn
setvalue
private bool falsepublic bool
getreturn
setvalue
Ahora en nuestro frmPrincipal modificamos la resolución y deshabilitamos la propiedad “skin” donde aparece el fondo de los botones.
El SmartPhone aplicado en este ejercicio es el Sony Erickson modelo Aspen, sin embargo el código también puede funcionar con PDA's con windows mobile de versiones anteriores. Para este modelo la resolución de pantalla es de 320x240 sin embargo al momento de cargar la aplicación los controles se ven mas chicos, para arreglarlo cambiamos el pixelaje de nuestros formularios a 430x 240.Agregamos los siguientes controles y modificamos las propiedades.
DataGridName: dGridAlarmas
CheckBoxName: chkPuertoSerieText: Abrir Puerto Serie
LabelName: lblPuertoText: Msg
TextBoxName: txtDatoSerie
SerialPortName: serialPort 9,600 baudios, databits 8, paridad None, para este Smartphone seleccionamos el COM0 por Blueetooth.
mainMenu1 viene por defecto
MenuItemsName: menuItemDispositivos, Text: Dispositivos Name: menuItemActualizaTabla, Text: Actualizar Tabla
Es importante conocer que este Datagrid para móviles es diferente al DatagridView de las aplicaciones de escritorio, en especial se encuentra muy limitado en sus métodos ya que no es posible insertar líneas paso por paso, debemos hacerlo por medio de una tabla.
En estos pasos vamos a configurarlo para “Mapear” una tabla con tres columnas: Hora, Alarma, Acción.Configuramos el Datagrid de la siguiente forma:
En el dataGridTableStyle escribimos Alarmas en la propiedad MappingName.
A la propiedad GridColumnStyles agregamos 3 columnas con las siguientes características:
Para el código de frmPrincipal creamos las siguientes instancias:
System; System.Linq; System.Collections.Generic; System.ComponentModel; System.Data; System.Drawing; System.Text; System.Windows.Forms; System.Collections;
usingusingusingusingusingusingusingusingusing
namespace
public partial class
newnew
stringpublic
MonitoreoSmartPhone{ : { arrayAlarmas = (); aireadores = (); datoSerial; frmPrincipal() { InitializeComponent(); }
frmPrincipal Form
AlarmasArrayList AlarmasArrayListDispositivos Dispositivos
Para instertar bien los datos es necesario que los nombres del mapeo coincidan con la tabla que se insertará a futuro.
196
El código para abrir el puerto serie es similar a los ejemplos anteriores. chkPuertoSerie_CheckStateChanged( sender, e)
{ (chkPuertoSerie.Checked) { { (!serialPort.IsOpen) { serialPort.Open(); } chkPuertoSerie.Text = ; chkPuertoSerie.BackColor = .YellowGreen; lblPuerto.Text = + serialPort.PortName.ToString(); } { lblPuerto.Text= ; } } { { (serialPort.IsOpen) { serialPort.Close(); chkPuertoSerie.BackColor = .Red; } } { lblPuerto.Text= ; } } }
En el evento DataReceived del SerialPort: serialPort_DataReceived( sender,
System.IO.Ports. e) {
}
También creamos las funciones: analisisCadena( dato) {
[] arreglocadena = dato.Split( ); alarma = (); alarma.Hora= arreglocadena[0]; alarma.Concepto= arreglocadena[1]; alarma.Acción= arreglocadena[2]; arrayAlarmas.agregaAlarma(alarma); }
private void objectif
try
if
catch
elsetry
if
catch
private void object
private void string
string
new
EventArgs
Color
Color
SerialDataReceivedEventArgs
Alarmas Alarmas
//si el puerto esta cerrado lo abre
//habilita el botón solo si esta abierto el puerto
//recibimos una cadena con este formato// Hora + "+" + Alarma + "+" + Acción// "12:30 hrs + Sobrecalentamiento + Checar Bomba 1-5"
// Split crea un arreglo de los datos separados por un '+'
//guardamos los datos en nuestro objeto Alarma
//agregamos este mensaje a la colección de alarmas
"Puerto Abierto"
"COM: "
"No pude abrir el puerto COM"
"Algo anda mal no pude cerrar el COM"
'+'
{ serialPortChat.ReadTimeout = 2000; datoSerial = serialPortChat.ReadTo( ); .Invoke( (TextoRecibido)); } { .Show( ); serialPortChat.DiscardInBuffer(); }
try
this new
catch
//intentamos leer el puerto//espera 2 segundos por el dato
//almacena toda la cadena de datos hasta que lee "-FIN-"
//ahora recorta los datos e invoca al manejador
//no llego la cadena completa, interceptamos el error
//borra los datos del buffer de entrada
"-FIN-"
"Error, no recibí el identificador final de la cadena -FIN-"
EventHandler
MessageBox
197
198
PictureBoxEn la propiedad Image y SizeMode asignar a todos: aireador_OFF_small.jpgSizeMode: StretchImage
Name: picBox1Name: picBox2Name: picBox3Name: picBox4
CheckBoxName: chkAireador1 Text: Aireador 1Name: chkAireador2 Text: Aireador 2Name: chkAireador3 Text: Aireador 3Name: chkAireador4 Text: Aireador 4
mainMenu1 viene por defecto
Name: menuCerrar
private void object
public void
if
private void object
new
private void object
TextoRecibido( sender, e) { txtDatoSerie.Text = datoSerial; analisisCadena(datoSerial); }
comandoActivacion( chk) { (!serialPort.IsOpen) { serialPort.Open(); } serialPort.Write(chk.Name.ToString() + + chk.Checked.ToString() + ); }
Seleccionando cada botón de los menús en su evento click escribimos:
menuItemDispositivos_Click( sender, e) { planta = (); planta.Show(); }
menuItemActualizaTabla_Click( sender, e) { tabla = arrayAlarmas.TraeTabla(); dGridAlarmas.DataSource = tabla; }
EventArgs
CheckBox
EventArgs
frmPlanta frmPlanta
EventArgs
DataTable
//creamos este método publico para recibir valores //desde el otro formulario de dispositivos
//al cambiar de formualrio se puede cerrar el puerto, por eso se vuelve a abrir
//nos manda el valor del ChkAireador elegido
//Abrimos el otro formulario
//actualizamos el dataGrid con el método TraeTabla
//tabla: trae los valores organizados en 3 columnas: Hora, Alarma, Acción
" : " "Fin"
A nuestros 4 controles CheckBox les agregamos un método compartido en su evento Click, lo nombramos “AireadorElegido”.
Seccionando el botón del menú activamos su evento Click.
usingusingusingusingusingnamespace
public partial class
newpublic
private void object
string
if
switch
casenew
breakcase
newbreak
casenew
breakcase
newbreak
else
switch
System.ComponentModel; System.Data; System.Drawing; System.Text; System.Windows.Forms;
MonitoreoSmartPhone{ : { principal = (); frmPlanta() { InitializeComponent(); } AireadorElegido( sender, e) { ruta = System.IO. .GetDirectoryName( System.Reflection. .GetExecutingAssembly().GetName().CodeBase); ruta = ruta.Replace( , ); check = ( )sender; principal.comandoActivacion(check); (check.Checked) { (check.Text) { : picBox1.Image = System.Drawing. (ruta + ); ; : picBox2.Image = System.Drawing. (ruta + ); ; : picBox3.Image = System.Drawing. (ruta + ); ; : picBox4.Image = System.Drawing. (ruta + ); ; } } { (check.Text) {
frmPlanta Form
frmPrincipal frmPrincipal
EventArgs
PathAssembly
CheckBox CheckBox
Bitmap
Bitmap
Bitmap
Bitmap
//con este método cambiamos el gráfico del dispositivo //y mandamos el aviso al otro formulario
//se activa la casilla
// se desactiva la casilla
"file:\\" ""
"Aireador 1"@"\aireador_ON_small.jpg"
"Aireador 2"@"\aireador_ON_small.jpg"
"Aireador 3"@"\aireador_ON_small.jpg"
"Aireador 4"@"\aireador_ON_small.jpg"
199
: picBox1.Image = System.Drawing. (ruta + ); ;
: picBox2.Image = System.Drawing. (ruta + ); ; : picBox3.Image = System.Drawing. (ruta + ); ; : picBox4.Image = System.Drawing. (ruta + ); ; } } } menuCerrar_Click( sender, e) { principal.Dispose(); .Close(); } }}
Las imágenes que descargamos contienen el aireador en estado ON y OFF.
casenew
break case
newbreak
casenew
breakcase
newbreak
private void object
this
"Aireador 1"@"\aireador_OFF_small.jpg"
"Aireador 2"@"\aireador_OFF_small.jpg"
"Aireador 3"@"\aireador_OFF_small.jpg"
"Aireador 4"@"\aireador_OFF_small.jpg"
Bitmap
Bitmap
Bitmap
Bitmap
EventArgs
Para que el dispositivo pueda cargarlas en tiempo de ejecución debemos guardarla en los archivos de programa del SmartPhone o PDA, en este caso no tenemos la carpeta Bin/Debug.
IMPORTANTE: Las imágenes deben ser de un tamaño chico (menor de 8kbytes), de lo contrario se provocará un error por falta de memoria.
Para probar este proyecto es necesario conectarse a una PC con Bluetooth y otra aplicación en C# que mande los mensajes de alarma y reciba los comandos de activación de los aireadores.
Si su PC no tiene Bluetooth puede utilizar un Dongle como BlueSoleil para crear estos puertos COM virtuales. El sofware de Bluesoleil permite conectarse a cualquier dispositivo Bluetooth por medio de este panel:
200
Hasta ahora ya tenemos el código para el Smartphone, pero nos falta sincronizarlo por medio de un puerto virtual Bluetooth y otra aplicación de escritorio que no mande y reciba nuestros comandos.
201
En el administrador de dispositivos de la PC puede ver el ID de los puertos COM virtuales:
Cuando se asocia al SmartPhone debe aparecer el puerto COM que puede utilizar en la aplicación de escritorio de C#.
Para mandar los mensajes podemos usar los ejemplos anteriores de comunicación Rs232, solo agregamos un botón que mande un mensaje con la sintaxis que requiere nuestro SmartPhone para separar las alarmas.
Al abrir el puerto nuestro botón para mandar alarma debe mandar los mensajes separados por un '+' y con la palabra -FIN-
btnAlarma_Click( sender, e) { serialPort.Write( ); }
private void object EventArgs
"12:30 hrs + Sobrecalentamiento + Checar Bomba 1-5 -FIN-"
Finalmente tenemos un monitoreo y control inalámbrico en el rango de operación de nuestro Dongle Bluetooth.
Puede crear un proyecto nuevo de windows forms o modificar un ejemplo anterior.
Con este tema damos por concluido nuestro curso.
Gracias por su participación
Atte.Ing. Aaron Castro Bazua
Con este tema damos por concluido nuestro curso.
Gracias por su participación
Atte.Ing. Aaron Castro Bazua