Showing posts with label Design patterns. Show all posts
Showing posts with label Design patterns. Show all posts

Friday, 8 January 2010

Review from the passing scene

In this article I'll be reviewing all the interesting posts I've published where the set up of the VLO framework is involved. From working with the canvas to using a Force-directed graph layout I'll show you the vast amount of information I've posted, and this will help me to think "Where the project is?" and "Where the project is going to?".

Sunday, 1 November 2009

Cálculo de rutas de transporte parte V

En éste artículo os entrego la última versión de la aplicación con mejoras adicionales sobre la selección de caminos. Continuando con la IV parte, he modificado ligeramente el algoritmo para identificar y marcar diferentes destinos en función de un parámetro situado en la propia conexión. Por ejemplo, si nos encontramos en un ítem que tiene 2 caminos a seguir, éstos deben estar numerados para indicar cual tomar.

Si éste elemento es un dispositivo físico, de alguna manera deberá posicionarse para dirigirse hacia un sitio u otro. De ésta manera si nos encontramos con la siguiente situación:

Indicamos numéricamente que si queremos ir del dispositivo 40 al 11, éste debe estar en su posición '2' y si quiere dirigirse hacia el 39, en la posición '1'. Físicamente representaría que por ejemplo una válvula debe estar posicionada en un sentido (abierta) para acceder a 11 y cerrada para 39 por ejemplo. De éstos ejemplos encontraremos muchísimos en la selección de caminos, y es interesante pararse un momento y pensar sobre éstos y buscar una solución elegante.

De momento he hecho que el nombramiento de las conexiones sea automático y sin ningún orden. Camino que selecciono, camino que marco. Ahora el cálculo es más eficiente, más rápido y con mejor disponibilidad a la hora de hacer pequeños cálculos para obtener éstos parámetros.

Si nos fijamos en el siguiente ejemplo:

Veréis que cada enlace está marcado y cuando generamos la salida para el cálculo de rutas, podemos ver como identifica cada uno de los nodos:

En verde se marcan los orígenes, en azul los elementos bloqueados y en fucsia los destinos. Además ésta versión dispone de las siguientes mejoras:
  • Selección total de los nodos
  • Movimiento total de los nodos mediante botones
  • Enlaces de bloqueo identificados en azul (no se editarán)
Aquí os dejo la última versión:


Thursday, 29 October 2009

Cálculo de rutas de transporte parte IV

En ésta entrega, hago el primer desarrollo profesional de mi framework VLO sobre un ejemplo real para el cálculo de rutas. Como ya sabéis, el VLO framework es una plataforma de desarrollo desarrollada en Delphi 2010 que pretende obtener un vínculo gráfico con un Objeto. De ésta manera esa porción de memoria que estamos utilizando se está representado en pantalla utilizando una serie de conceptos gráficos. Llevo trabajando en éste proyecto unos 2 meses y medio y como podéis ver, si se planifica bien el trabajo, se tienen las ideas claras y partimos con un buen background en orientación a objetos y diseño utilizando patrones podemos crear herramientas increíbles.

Luego vino la implementación de la plataforma sobre ejemplos reales, al principio podía crear diagramas de grafos y calcular la ruta más corta utilizando Dijkstra. Después le di una parte mucha más gráfica y implementé un sistema multi capas para poder disponer de texto, imagen y caja en el mismo componente. A partir de ahí y con otras muchas ideas (gracias a los morning pages) apareció el cálculo de rutas. Hace ya bastantes años que me dedico al sector de la automatización industrial y la mayoría de aplicaciones que desarrollo son para cumplir los requisitos que necesitan éste tipo de industria.

Por lo tanto, continuando con ésta última versión de la aplicación, veréis que hay un pequeño cambio y es que ahora ya tengo implementado el algoritmo para reconocer scada's (Vijeo Citect) utilizando la Graphics Builder Automation Interface, y aquí ya vi un filón.

¿En qué consiste ésta última versión?. Pues bien, si disponemos de un diagrama real de Scada, por ejemplo como el siguiente:

Podemos ver una configuración típica donde se transporta el producto desde unos silos de origen a unos recipientes destino. Ahora lo que hay que hacer, es escanear la librería de "Genies" (Objetos del Vijeo Citect) con Thundax Genies Scanner y volcar los datos en la ubicación de los resources de Thundax Box Manager (para que disponga de todas las imagenes de la librería y así poder representar las boxes con la imagen correcta).

Una vez escaneada una librería como la presentada en la imagen, ya podemos iniciar Thundax Box Manager y escanear la pantalla del Scada con la aplicación utilizando la librería de automatización. Sobretodo antes, visualizar en las opciones de la aplicación Thundax Genies Scanner la ubicación de los resources:


Ahora, iniciamos Thundax Box Manager, y con la pantalla seleccionada del Scada, nos dirigimos al menú Citect -> Load Page Diagram, y en pocos segundos, tendremos cargada la misma estructura en mi aplicación (solo las genies, los símbolos no, porque no representan ninguna estructura de control, es decir, no tienen ninguna lógica dentro del proceso):

Ahora solo tenemos que perder 15 segundos en realizar las conexiones lógicas entre elementos o ítems y luego calcularemos las rutas:

Tenemos que acordarnos de marcar cuales son los orígenes y cuales los destinos:

Ahora entenderéis el concepto de "Bloqueo o Interlock" con la siguiente imagen:

Si os fijáis el producto pasa por la cinta superior, y hay 2 válvulas que sacan el producto. Pero tenemos que tener en cuenta que solo se puede abrir 1 a la vez, ya que sinó enviariamos producto a un destino diferente al requerido. Por lo tanto debe estar bloqueada la otra válvula y no abrirse durante la ejecución de la ruta seleccionada. De ahí que exista el objeto "interlock", y que se gestione en el cálculo de la ruta de transporte.

Ahora solo tenemos que ejecutar "Citect -> Calc Transport Routes" et voilà!, ya tenemos generadas las 100 rutas disponibles que hay en menos de 100 ms.


Además podemos ver la ruta seleccionada haciendo click en la ruta:

Aún quede mucho trabajo por delante, pero me lo tomo con mucha ilusión al ver que por fin el programa tiene una meta asequible: Crear rutas de transporte para que otro programa pueda trabajar con éstas y ejecutarlas. Lo interesante de todo ésto es que podemos ahorrar un montón de tiempo intentando averiguar la cantidad de rutas que tenemos y sobretodo intentar el poderlas implementar gráficamente manteniendo como expliqué hace 2 meses un mapa lógico sobre una estructura que carece de inteligencia.

Aquí os dejo las últimas versiones de mi aplicación:
He cambiado la privacidad de la aplicación y de momento la he hecho "trial de 30 días" esperando crear un paquete más completo con sus diferentes opciones. Espero que la lectura haya sido entretenida!.

Friday, 9 October 2009

Automation Object Library para Vijeo Citect con Delphi parte III

Aquí os dejo una aplicación interesante utilizando la Graphic Builder Automation Interface que trae Vijeo Citect. Ésta aplicación que he creado la utilizaré posteriormente con Thundax Box Manager para gestionar la librería que trae Citect. Mi idea es poder leer toda la librería de nuestro proyecto de citect y guardar también su imagen en la librería de imagenes de Thundax Box Manager. De ésta manera, al crear el mapa mental de la aplicación, podré incrustar la imagen que toque de la librería con ésta nueva aplicación que he creado. La aplicación se llama Thundax Genies Scanner y permite escanear una pantalla de vijeo citect, y a la vez permite incrustar una imagen del clipboard para ir recortando las diferentes imagenes de la librería e ir guardandolas en formato .bmp dentro de la librería de Thundax Box Manager. La cosa funciona de la siguiente manera, primero si disponemos de una pantalla con todos los elementos de nuestra librería emplastados en la página activa (Citect Graphic Builder):

Ahora solo tenemos que marcar la ventana activa y hacer ALT + PRINT SCREEN y se bolcará una copia de la imagen en el clipboard. Ahora iniciamos la aplicación Thundax Genies Scanner, y en el menú "Clipboard" hacemos "paste", y aparecerá la imagen de la aplicación Graphic builder dentro de Thundax Genies Scanner.

Ahora, desde la opción de menú File -> Scan Objects, veremos como el algoritmo busca el objeto en la pantalla del Vijeo Citect (Citect Graphic Builder) y extrae todas sus propiedades que se listarán en el árbol de la izquierda, y además se copiará el símbolo de la propia imagen incrustada en la aplicación, para crear una librería gráfica de símbolos.

Si ejecutamos, el resultado es el siguiente:

Ahora podemos disponer de toda nuestra librería de objetos de citect exportada para que la aplicación Thundax Box Manager con el VLO Framework pueda trabajar con ésta. En los siguientes artículos las cosas se irán poniendo mucho más interesantes ya que empezaré a implementar los algoritmos para trabajar con todos éstos datos.


Monday, 5 October 2009

Automation Object Library para Vijeo Citect con Delphi parte II

Continuando con la primera parte de éste artículo, voy a seguir con la explicación dada en esa entrega y ver como va evolucionando la integración de la idea comentada en ese artículo sobre el nuevo framework VLO. Ésta parte es muy interesante y llevo trabajando en ésto casi todo el fin de semana, incluso ahora estoy dedicando un rato en crear unos vídeos para mostrar lo interesante de la aplicación, pero aún se me encallan un poquito.

En éste artículo voy a comentar los siguientes pasos que ha realizado mi aplicación "Thundax Box manager" para implementar el nuevo framework VLO (Visually Linked Objects) para poder escanear una pantalla del Scada (Vijeo Citect 7.0) y generar su símil en la aplicación Thundax Box Manager y así como dije en su día, crear un mapa mental de lo que realmente hay en la aplicación. Ésto nos servirá más adelante para hacer cosas mucho más potentes que ya os iré explicando.

En definitiva, la primera propuesta era encontrar la manera de obtener todos los objetos y sus propiedades utilizando la librería de automatización que lleva el Vijeo Citect que además es un TLB (type library Borland), por lo tanto si tengo una pantalla típica hecha con Vijeo Citect:

Puedo ver las genies "engine" y "valve" y el símbolo "tank". Pues bien, he creado un pequeño algoritmo que se encarga de escanear éstos objetos y devolverme sus propiedades (nombre, librería, tags, posición, etc).

El resultado de ejecutar éste algoritmo es lo siguiente:

Se muestra en éste formulario la información referente a cada genie, más la información de sus propiedades. Si consultamos en el Scada el punto de animación 35, podemos ver que es el motor y que las propiedades capturadas coinciden con las de la pantalla:


Aquí podréis descargar la aplicación utilizada para el escaneado de propiedades. Podéis comprobar vuestras aplicaciones y ver si devuelve bien o no los parámetros:
Lo más interesante ahora, es que puedo perfeccionar éste algoritmo y utilizar mi framework VLO para representar la información de la siguiente manera:

Y ahora que tengo los mismos objetos que en el Scada, puedo hacer lo siguiente:

¿Veis por dónde van los tiros, no?. Ahora puedo dar una cierta inteligencia a éstos objetos y empezar a generar mucha más información porqué de empezar siendo una imagen (elementos posicionados en un diagrama para simular algo), acaba siendo un diagrama en una plataforma que me permite crear un mapa mental de lo que existe.

¿Y ahora que?. Pues a seguir perfeccionadolo. Al final hay que conseguir poder dibujar ciertos procesos dentro de mi framework y luego empezar a generar diversa información más sutil, como rutas de transporte, enclavamientos entre equipos, etc, etc, etc. Todo ésto con la ayuda de Delphi, una buena base de OO y un buen diseño de patrones.

En mi opinión ésto es algo que creo yo que tendrá tirada. Aún no he visto nada de ésto en el mercado (que yo conozca), ya que no es como un control distribuido, sino que es algo más personalizable. Poco a poco iré implementando más funcionalidades hasta poder disponer de algo un poco global y que no cueste mucho su implementación y que sea muy escalable.




Thursday, 1 October 2009

Implementación de un patrón Memento

El patrón Memento tiene la finalidad de almacenar el estado de un objeto para su posterior recuperación de manera sencilla. Lo que hay que hacer es crear una estructura que tendrá en cuenta el estado de nuestros objetos en un instante determinado y luego poder recuperar el instante adecuado. De ésta manera podemos modificar el estado de nuestros objetos y lo que hacemos es como una fotografía (snapshot) de su estado actual. Después de hacer las modificaciones podemos "deshacer" (undo) las modificaciones volviendo a la fotografía anterior. Podemos ir guardando tantas fotos como queramos y navegar por éstas de manera fácil.

El patrón Memento define 3 roles diferentes:
  1. Originator - El objeto que sabe cómo guardarse.
  2. CareTaker - el objeto que se sabe por qué y cuando el Originator tiene que guardarse y restaurarse.
  3. Memento - la casilla de almacenado que el Originator escribe y lee y es guiada por el CareTaker.
La estructura básica es la siguiente:

Al implementar este patrón puedo salvar el diferente estado de las cajas (TBox) y luego volver a su estado anterior. Debido a que ahora podemos alinear las cajas en una determinada posición, me interesa poder volver a su estado anterior ya que ahora podemos equivocarnos y alinear los objetos de una forma no deseada, como por ejemplo:

1. Disponemos de las cajas mal posicionadas:

2. Las alineamos (Top Alignment) y guardamos su estado:

3. Cometemos un error con el alineamiento:
En éste punto, tenemos las cajas mal posicionadas, y con un simple click del mouse, podemos hacer "undo" y volver al estado anterior guardado para evitar éste tipo de errores.

  • Implementando la solución:
Aquí os dejo la estructura y el código fuente de la solución con Delphi. Se parece mucho a la solución que corre por la red en java ya que tiene la misma estructura.

El código fuente (uMemento.pas):


unit uMemento;

interface

uses
uBox, contnrs;

type
TMemento = class(TObject)
FboxListState: TBoxList;
public
constructor Create(boxListState: TBoxList);
function getSavedState(): TBoxList;
end;

TOriginator = class(TObject)
FboxListState: TBoxList;
public
procedure SetState(boxListState: TBoxList);
function SaveToMemento(): TMemento;
function restoreFromMemento(memento: TMemento): TBoxList;
end;

TCareTaker = class(TObject)
FSavedStates: TObjectList;
public
constructor Create();
destructor Destroy(); override;
procedure addMemento(memento: TMemento);
function thereAreMementos(): boolean;
function getLastMemento(): TMemento;
end;

implementation

uses
SysUtils;

{ TMemento }

constructor TMemento.Create(boxListState: TBoxList);
begin
Self.FBoxListState := boxListState.Clone;
end;

function TMemento.getSavedState: TBoxList;
begin
result := Self.FBoxListState;
end;

{ TOriginator }

function TOriginator.restoreFromMemento(memento: TMemento): TBoxList;
begin
Self.FboxListState := memento.getSavedState;
result := Self.FboxListState;
end;

function TOriginator.SaveToMemento: TMemento;
begin
result := TMemento.Create(Self.FBoxListState);
end;

procedure TOriginator.SetState(boxListState: TBoxList);
begin
Self.FBoxListState := BoxListState;
end;

{ TCareTaker }

procedure TCareTaker.addMemento(memento: TMemento);
begin
FSavedStates.Add(memento);
end;

constructor TCareTaker.Create;
begin
FSavedStates := TObjectList.Create();
end;

destructor TCareTaker.Destroy;
begin
FreeAndNil(FSavedStates);
inherited;
end;

function TCareTaker.getLastMemento(): TMemento;
begin
if (FsavedStates.Count > 0) then
result := (FSavedStates.Items[FsavedStates.Count - 1] as TMemento);
FSavedStates.Remove(FSavedStates.Items[FsavedStates.Count - 1]);
end;

function TCareTaker.thereAreMementos: boolean;
begin
result := (FsavedStates.Count > 0);
end;

end.


La llamada para guardar el estado de las cajas:


procedure TfrmMain.SaveState1Click(Sender: TObject);
begin
Originator.SetState(boxList);
CareTaker.addMemento(Originator.SaveToMemento());
end;


La recuperación del último estado (undo):


procedure TfrmMain.UndoState1Click(Sender: TObject);
begin
if CareTaker.thereAreMementos() then
boxList := Originator.restoreFromMemento(CareTaker.getLastMemento());
end;


Espero que os sirva de ayuda!.

  • Enlaces de interés:
Source Making.



Wednesday, 30 September 2009

Implementación de un patrón Composite

El patrón composite permite construir objetos complejos a partir de unos más simples. Éste lo he implementado en mi unnamed framework para la copia de propiedades entre objetos. Si os fijáis en la estructura de la plataforma, tanto la Tbox como TLine tienen propiedades muy similares con muy pocas diferencias. Dada la similitud entre éstas he decidido aplicar éste patrón para obtener unas propiedades abstractas las cuáles generan diferentes propiedades para TLine y TBox. De ésta manera puedo construir propiedades más complejas a través de unas más simples. Ésto me permite también separar totalmente de la lógica de control el concepto de propiedad dentro de los objetos. Antes cuando modificaba un objeto lo hacia todo en sí, ahora solo tengo en cuenta las propiedades (y éstas son las importantes). Al objeto caja le tiene que dar igual si le modifican alguna de sus propiedades básicas de dibujo, es el propio objeto propiedad quien se tiene que enterar de éstos cambios y negociarlos con los diferentes objetos.

La nueva estructura de las propiedades es la siguiente:

Ahora tanto la estructura de Tbox como TLine ha quedado reducida a:

Como podéis ver la gestión de las conexiones la he migrado a una unidad nueva ya que es mejor tenerlo todo separado.

Con la solución propuesta anteriormente, ahora podemos hacer ésto desde el lienzo personalizado:

Ésto nos permite copiar las propiedades de nuestros objetos utilizando la TAbstractProperty, de ésta manera la podemos pasar a cualquier objeto ya que todas las propiedades heredan de ésta clase.

Si nos fijamos en la siguiente imagen veréis que tanto la primera caja como la línea tiene asignada la misma propiedad:

En enlace de la aplicación es el siguiente:
NOTA: La aplicación ha cambiado substancialmente y el intentar cargar un diagrama anterior a ésta versión puede que acabe en error. Aún tengo que gestionar todo el tema de los ficheros y su almacenado.


Wednesday, 23 September 2009

Implementando el Patrón Prototype

Continuando con la implementación de Thundax Box Manager, he cambiado el diseño de TBox para implementar un patrón prototipo y poder crear copias de ésta clase. Por lo tanto, utilizando el patrón Prototype podremos duplicar objetos clonando sus instancias. Con Delphi es un poco más complicado que con java y hay que picar un poco más de código. Si nos fijamos ahora en la configuración de Tbox veremos que ahora ya no hereda de TObject, sino que hay una interface de por medio llamada ICloneable y que nos permite la funcionalidad de poder clonar el objeto en si. No he encontrado ninguna implementación buena por la red de éste patrón, y la verdad es que no es muy complicada. Con otros lenguajes la cosa es más sencilla, por ejemplo java o C# donde ya existe la clonación de objetos.
Si observamos el nuevo diagrama UML del diseño de TBox tenemos:

La clase TBox implementa la interface ICloneable:

De ésta manera las instancias de TBox tienen la posibilidad de clonarse. Ésto me permite poder realizar copias de mis instancias en el Canvas.

En la última versión de ThundaxBoxManager v1.0.0 build 149.exe podremos realizar copias de los objetos incrustados utilizando el botón derecho del mouse y marcando las opciones de Copiar y Pegar. Además hay la opción de eliminar tanto cajas como conexiones.

Como podéis ver, el editor cada vez va cogiendo más color e incluso podemos realizar la exportación del gráfico a un fichero en bmp:

  • Enlaces de interés:
Creating Patterns in C#.
Cloning Objects.
Prototype Pattern.
Cast Constructor.
Explicit Interface Implementation.

Tuesday, 22 September 2009

tiOPF (Object Persistence Framework)

Hoy os traigo un framework bastante interesante, el tiOPF (Object Persistence Framework for Free Pascal & Delphi). A través de éste framework podemos gestionar la persistencia de nuestros objetos conectándolos a una Base de datos o otro medio que nos proponga éste. En la web encontraréis toda la información referente a su instalación (que es bastante fácil). Solo tenemos que descargarnos la última versión disponible de la red. Yo he descargado la versión tiOPF-Win32-2.5.0.1229.zip y la he probado sobre Delphi 2007. En algún feed de la red he encontrado que migraran el proyecto para hacerlo compatible con Delphi 2010 y así hacer un framework más potente. Una vez tenemos el framework en nuestro equipo, abrimos el proyecto de la ruta:
\tiOPF\tiOPF2\Compilers\Delphi2007\tiOPF_D2007.groupproj.

De ésta manera se nos abren los siguientes proyectos:


Hacemos un build de todos e instalamos el package tiOPFGUIDsgn2007.bpl. Una vez instalado aparecerá un mensaje con los componentes instalados:

Luego tenemos que acordarnos de enlazar las fuentes en Library Path:

Aquí os dejo los componentes instalados:

En la misma ruta del package, encontraréis una serie de ejemplos diferentes para la utilización de éste framework. Además en la web encontraréis un manual bastante completo de su funcionamiento y como empezar con éste framework.

En definitiva, os recomiendo que paséis por la web y le echéis un vistazo al trabajo realizado. La verdad es que está bastante bien y tiene bien solucionada la parte de persistencia con la Base de datos.

Monday, 21 September 2009

Implementando el patrón Adapter o Facade

En éste artículo os mostraré la utilización de éste Patrón -> Adapter o Facade que nos sirve para crear una clase que adapta otra clase. Con éste patrón transformamos una interfaz en otra, de ésta manera podemos crear una clase reusable que coopera con clases no relacionadas. Si os acordáis del diseño que propuse para la generación de TLine, implementé un abstract Factory para devolver diferentes tipos de líneas. Pues bien, ahora el problema que tengo es que necesito poder convertir una clase en otra y lo haré utilizando un patrón Adapter. Si observamos el diagrama UML que colgué hace unos días sobre la implementación de la jerarquía de TAbstractLine y sus descendientes tenemos:

Como podéis comprobar, la diversas factorías me permiten generar diferentes tipos de líneas. Mediante el estilo escogido, puedo crear las líneas con la configuración declarada en la factoría. El funcionamiento es bastante simple y me permite potenciar el polimorfismo entre líneas, ya que yo solo tengo un componente TAbstractLine que es el que se dibuja, y según la factoría se crea el tipo que se selecciona.

Ahora el problema reside en que deseo convertir un tipo de línea en otra. ¿Cómo lo hago?, en primer momento puede parecer que el diseño no es correcto, que no tendríamos que haber realizado una factoría y unir todas las líneas en una sola clase e ir intercambiando las propiedades para dibujar una cosa o la otra. Pero ésto no es así, cada clase está pensada para funcionar de ésta manera, y que en un futuro pueda crecer sin tocar mucho código. La solución para la implementación de la solución es simple, utilizando un patrón Adapter. Mediante éste patrón, lo que hago es coger un objeto de la clase TAbstractLine, ya sea del tipo TAbstractSimpleLine o TAbstractDottedDobleArrowLine, etc.

Mediante éste patrón, conseguiremos de una forma elegante, sin tener que tocar nada de la estructura creada anteriormente una clase que nos permite adaptar una clase hacia otra. De ésta manera cuando en la aplicación yo modifique el componente "Línea" y cambie las opciones de "Simple Arrow" a "Doble Arrow", lo que hará el adapter es cambiar las 2 clases y la OO mediante Polimorfismo se encargará de hacer el resto, ya que el evento "Draw" ya sabe desde donde se tiene que ejecutar.

El código de ejemplo de la clase es el siguiente:


//Adapted Class
type
TAdaptedLine = Class(TObject)
FObject : TAbstractLine;
procedure getProperties(const Abstract1: TAbstractLine; var Abstract2: TAbstractLine);
constructor Create(kind : TTypeLine; obj : TAbstractLine);
End;

function getAdaptedLine(kind : TTypeLine; obj : TAbstractLine) : TAbstractLine;

implementation

{ TAdaptedLine }

constructor TAdaptedLine.Create(kind : TTypeLine; obj: TAbstractLine);
var
factory : TLinesFactory;
begin
simple := TSimpleLinesFactory.Create(obj.FCanvas);
case kind of
SimpleLine: FObject := factory.GetLine;
SimpleArrowLine: FObject := factory.GetLineArrow;
SimpleDoubleArrowLine: FObject := factory.GetLineDoubleArrow;
DottedLine: FObject := factory.GetLine;
DottedArrowLine: FObject := factory.GetLineArrow;
DottedDoubleArrowLine: factory := dotted.GetLineDoubleArrow;
noLine: FObject := nil;
end;
getProperties(obj, FObject);
FreeAndNil(factory);
end;

function getAdaptedLine(kind : TTypeLine; obj : TAbstractLine) : TAbstractLine;
var
adapted : TAdaptedLine;
resObj : TAbstractLine;
begin
adapted := TAdaptedLine.Create(kind, obj);
resObj := adapted.FObject;
FreeAndNil(adapted);
result := resObj;
end;


  • Enlaces de interés:
The Adapter Pattern.
The Adapter and Facade Patterns.
Introduction to Designing Patterns with Delphi.

Thursday, 27 August 2009

Automation Object Library para Vijeo Citect con Delphi parte I


Después de éstos días de descanso y reflexión, aún estoy de vacaciones y me he parado un momento para publicar algo en lo que he estado trabajando éstos últimos días. Aquí os hago un pequeño adelanto de una de mis próximas aplicaciones utilizando todas las aplicaciones explicadas anteriormente. (sobre geometría computacional, utilización del canvas, etc.) Ésta idea bastante innovadora se utilizará para generar pantallas SCADA con mucha más facilidad utilizando las herramientas de las que dispone Vijeo Citect para la automatización de sus pantallas. Hace mucho tiempo que me dedico al sector de la Automatización Industrial y he desarrollado varios programas para la manipulación de Scada's de Vijeo Citect. Pues bien, he desarrollado un pequeño programa que permite utilizar la Automation Object Library (ctdraw32.tlb) para hacer la manipulación mediante delphi, y así aprovechar toda su potencia. Mediante la utilización de un patrón IoC y objetos COM, creo una jerarquía de clases idónea para la manipulación de ésta librería y así interaccionar con las pantallas de Citect. Ésta librería no tiene ningún secreto, es más en la ayuda de la misma aplicación aparece descrito cómo utilizarla mediante Visual basic:

Aunque es un poco ortodoxo (utilizando un lenguaje sin OO..puf!) lo bueno es poder utilizar Delphi para jugar con ésta librería gráfica. Lo primero que tenemos que hacer es importar el fichero TLB (Borland type Library) a nuestro proyecto. La instalación de éstos tipos de ficheros están explicados en algunos de mis artículos, aunque aquí os lo vuelvo a explicar:

  • Importando el componente ctdraw32.tlb:
Una vez tenemos creado nuestro proyecto, solo tenemos que ir a Component -> Import Component, y una vez dentro del asistente, tenemos que importar una Type Library:

Ésta librería viene por defecto con la instalación de Vijeo Citect 7.0, y es la Graphic Library 6.1. Yo la tengo ubicada en la ruta C:\Program Files\Citect\CitectSCADA 7\Bin para mayor comodidad por tema del sistema operativo, ya que trabajo con windows vista y aún hay alguna cosa que se le resiste. Una vez generado el fichero GraphicsBuilder_TLB.pas, lo importamos a nuestro proyecto para poder manipular sus interfaces y automatizar las pantallas del Scada.

Ahora, podemos jugar con ésta librería y diseñar las pantallas como queramos utilizando nuestro propio canvas para ir manipulando las genies del SCADA. Aquí os dejo una muestra de una pantalla de la web de Schneider Automation, para que veáis como queda una pantalla típica:


Los que ya han tocado éste SCADA, saben que la implementación de sus objetos se hace mediante genies y supergenies. Pues bien, utilizando ésta librería podemos insertar un objeto en pantalla haciendo una llamada por su nombre a una genie y emplastarla en la pantalla en la ubicación que queramos. Luego podemos ir modificando sus posiciones e incluso utilizar pipes de conexión para simular un enlace físico. Mi objetivo es utilizar la POO mediante Delphi para crear enlaces inteligentes entre los objetos insertados en la pantalla. De ésta manera la aplicación tiene un mapa mental de cómo están ubicados los objetos y de ésta manera llegar a hacer diversas cosas con ellos (ya os lo explicaré más adelante).
  • Creación de la jerarquía de clases:
Como os he explicado en el inicio de mi artículo, he utilizado un patrón IoC (Inversion Of Control) porque he creado diversas clases para manejar la creación de éstos objetos. Utilizando éste patrón consigo que la llamada de los eventos de dibujo no los haga el propio componente, sino que delego ésta responsabilidad a un gestor que se encargará de realizar la llamada del objeto y de su pintado en pantalla. Es el mismo principio que el de Hollywood (No me llames tú, ya te llamaremos nosotros).

Aquí os dejo el primer diseño de la aplicación:


Mediante ésta configuración, podremos generar los diferentes objetos (motores, válvulas, etc) y asignarles propiedades (Nombre, Identificador, tag, etc). Luego el dispatcher se encarga de envolver los componentes del TLB (hace de Wrapper class). Luego toda la gestión de los objetos la hace el Painter que es el que se encarga de hacer las llamadas a los diferentes objetos y realizar el dibujado en la pantalla del Scada.

Luego mediante la llamada de éstos objetos, podemos crear las pantallas automáticamente:

procedure TMyApp.DrawItems(Sender: TObject);
var
    MyComObject: IGraphicsBuilder2;
    myCitectPainter: TCitectPainter;
    myElement : TCitectElement;
begin
    MyComObject := TCitectDispatcher.Create;
    myCitectPainter := TCitectPainter.Create(MyComObject, 'include','standard', 'normal');

    myElement := TCitectElement.create('motor_1_east', 'motors', Point(300, 300));
    myElement.AddParam('Tag', 'Test_Tag');
    myElement.InitLine := Point(myElement.position.x + 71, myElement.position.y + 18);

    myCitectPainter.Draw(myElement);
    myCitectPainter.InsertProperties(myElement);

    FreeAndNil(myElement);

    myCitectPainter.Save();

    FreeAndNil(myCitectPainter);
    MyComObject := nil;
end;


Aquí os dejo la versión beta de la aplicación ImportCitectObjectsTHDX.exe, descargarla y probarla para que veáis lo que hace sobre la aplicación. Mediante el botón de test, veréis que realiza la inserción de 2 motores, 2 válvulas y realiza la conexión entre ellos utilizando 2 pipes:

Como podéis ver, la codificación mediante mi jerarquía de clases difiere bastante de su actual codificación utilizando visual basic:

Dim GraphicsBuilder As IGraphicsBuilder2
Set GraphicsBuilder = New GraphicsBuilder.GraphicsBuilder
With GraphicsBuilder
 .Visible = True
 .PageNew "include", "standard", "normal", 0, True, True
 .LibraryObjectPlace "include", "motors", "motor_1_east", 0,
True
 .PositionAt 300, 500
 .LibraryObjectPutProperty "Tag", "Test_Tag"
 .DrawLine 100, 100, 300, 300
 .AttributeLineColour = 120
 .PageSaveAs "Example", "TEST"
 .PageClose
 .Visible = False
End With

Set GraphicsBuilder = Nothing


Utilizando mi aplicación, podremos realizar llamadas a diversos objetos, realizar la persisténcia de éstos para la siguiente ejecución de la aplicación, parametrizar los parámetros de las genies y realizar conexiones mediante un mapa mental. La siguiente codificación nos permite dibujar lo que véis en en dibujo anterior:

uses
    CitectElement, CitectPainter, CitectDispatcher, GraphicsBuilder_TLB;

procedure TMyApp.test1(Sender: TObject);
var
    MyComObject: IGraphicsBuilder2;
    myCitectPainter: TCitectPainter;
    myElement, myElement2, myElement3, myElement4: TCitectElement;
begin
    MyComObject := TCitectDispatcher.Create;

    myElement := TCitectElement.create('motor_1_east', 'motors', Point(300, 500));
    myElement.AddParam('Tag', 'Test_Tag');
    myElement.InitLine := Point(myElement.position.x + 71,myElement.position.y + 18);

    myElement2 := TCitectElement.create('motor_1_east', 'motors', Point(400, 500));
    myElement2.AddParam('Tag', 'Test_Tag2');
    myElement2.InitLine := Point(myElement2.position.x + 71,myElement2.position.y + 18);

    myElement3 := TCitectElement.create('valve1_e', 'valves', Point(378, 561));
    myElement3.AddParam('Tag', 'Test_Tag2');
    myElement3.EndLine := Point(myElement3.position.x + 5,myElement3.position.y);

    myElement4 := TCitectElement.create('valve1_e', 'valves', Point(478, 561));
    myElement4.AddParam('Tag', 'Test_Tag2');
    myElement4.EndLine := Point(myElement4.position.x + 5,myElement4.position.y);

    myCitectPainter := TCitectPainter.Create(MyComObject, 'include','standard', 'normal');

    myCitectPainter.Draw(myElement);
    myCitectPainter.InsertProperties(myElement);

    myCitectPainter.Draw(myElement2);
    myCitectPainter.InsertProperties(myElement2);

    myCitectPainter.Draw(myElement3);
    myCitectPainter.InsertProperties(myElement3);

    myCitectPainter.Draw(myElement4);
    myCitectPainter.InsertProperties(myElement4);

    myCitectPainter.DrawPipe(myElement, myElement3);
    myCitectPainter.DrawPipe(myElement2, myElement4);

    myCitectPainter.Save();

    FreeAndNil(myElement);
    FreeAndNil(myElement2);
    FreeAndNil(myCitectPainter);
    MyComObject := nil;

end;

Ahora, solo queda trabajar e ir mejorando la aplicación para llegar a tenen una aplicación que cumpla con las necesidades descritas y que permita una escalabilidad a la hora de ir añadiendo nuevas genies.

En mi opinión, la aplicación tiene mucho futuro, y hay mucho mercado que está utilizando éste SCADA para la visualización y control en las fábricas. De ésta manera, podemos automatizar la creación de nuestras pantallas simplemente configurando los objetos sobre un lienzo y luego lanzandolos hacia citect para que los dibuje.