¡Buen camino a todos!

XE6 trae novedades interesantes, como el App Tethering, del que ya hemos ido conociendo bastantes detalles, bien a través de las demos incluidas en el entorno, bien en los distintos vídeos de promoción, que se han dado a conocer con motivo de las presentaciones. Y me pareció  interesante compartir con vosotros al menos una entrada eminentemente práctica, que habitualmente catalogamos como Taller de Delphi Básico. Esa era y es, básicamente la idea, y en cierta forma, tengo confianza  de que al acabar de leerla, remueva la sana curiosidad para ver si efectivamente pueda ser un tema del que se saquéis provecho,  y os lleve a ver las demos existentes en Embarcadero o en los enlaces que se van a recomendar y si fuera así, creo que habrá valido la pena.

Para empezar, comentaría que tenía en mente un montón de buenos propósitos e ideas para abordarla. Tras ver atentamente uno de los primeros vídeos de Embarcadero sobre App Tethering, en los enlaces recomendados de Jim McKeeth en su muro de Facebook, esta nueva característica despertó especialmente mi curiosidad. Siempre existen, dentro de las novedades, características que especialmente nos llaman la atención. La vislumbraba como algo con proyección y útil en un sentido muy práctico, a pesar de que el nombre que habían elegido, App Tethering me dejaba un tanto frío. Confieso que siempre me he llevado fatal con eso de los nombres y las tecnologías. ¿Tethering? ¿Qué es eso de App Tethering? -me preguntaba al visitar el enlace…

En la linea inferior se muestra ese primer enlace que refería mas arriba y que me sirvió para un primer contacto con el tema:

App Tethering Delphi XE6. – Jim McKeeth.

Rápidamente enviaba el link en el grupo de Facebook Delphi Solidario. Ese ritual se ha convertido en una costumbre… Le di a un MeGusta merecido mientras retenía en la mente algunas ideas que mostraba el vídeo: una aplicación desde un dispositivo móvil capturaba una imagen de la cámara y la enviaba a otras aplicaciones de escritorio, todo ello a través de la red local y de una forma aparentemente muy sencilla. Posteriormente, encontraría otras demos extendiendo esa idea, pero casi siempre centradas en el área de los dispositivos móviles. Otra de las demos de Embarcadero nos mostraría a un usuario manipulando de forma remota una aplicación multimedia de escritorio, con los controles típicos de los reproductores de música desde su móvil Android.

El concepto de App Tethering…

La doc wiki de Embarcadero, y sobretodo el área de novedades What’s New in Delphi and C++Builder XE6, es uno de los primeros sitios donde encontrar esa información preliminar. De esta lectura podemos destacar las ideas esenciales:

Si tus aplicaciones usan app tethering podrán…

  • Descubrir otras aplicaciones que lo soporten (ya en el mismo dispositivo o en otros distintos)
  • Compartir datos entre ellas permitiendo tanto los tipos de datos estándar como los de tipo stream.
  • Ejecutar acciones remotas.

Ciertamente todo esto no es algo nuevo… Esas tres ideas, han formado parte de algunos de los escenarios que hemos enfrentado desde nuestra experiencia como programadores. No es nuevo que puedan existir cualesquiera servicios escuchando en algunos de los puertos, y DataSnap ya nos permitía la ejecución de métodos remotos o compartir el tipo stream o los datos básicos como cadenas de texto. Lo que cambia quizás es el contexto sobre el que todas estas tecnologías se proyectan y combinan y se ahonda en la visión de Embarcadero sobre nuestra herramienta en el contexto de las múltiples plataformas. Vemos que esa idea subyace y se va repitiendo en cada nueva versión: a riesgo de ser pesado, se incide en una única herramienta y un mismo código fuente que interactua con múltiples plataformas. Una plataforma de servicios comunes que nos pueda abstraer de lidiar los detalles específicos de cada plataforma cuando no sea relevante que tengamos que hacerlo. Cada paso de Embarcadero que vamos conociendo, en buena medida nos intenta acercar a esta visión y sin duda, la nueva característica, el App Tethering, vuelve a poner en el centro al desarrollador en ese sentido,

Se que dicho así puede resultar un tanto vago pero si volvemos la mirada de nuevo a esos tres puntos, (-entendiendo que nos encontramos en un punto de partida y no en un punto de llegada-), podemos apreciar como algo notable contar con una alternativa mas que nos permitirá compartir datos simples o complejos, entre aplicaciones que se ejecuten en una o distintas plataformas y sin importarnos que existan varios protocolos de comunicación disponibles, y sean utilizados para que dichas aplicaciones se descubran, ejecutando cuantas acciones sean necesarias en esa comunicación que a fin de cuentas, es lo verdaderamente importante. Y todo eso de forma transparente, ocupándonos mas del contenido, por decirlo de alguna forma, y no tanto del cómo.

Lo del punto de partida y punto de llegada lo digo mas que todo, en la linea de que he podido leer comentarios en cuanto a la limitación de que solo exista al momento actual la comunicación vía Ethernet, o comentarios en cuanto a que no se ha definido una capa segura de comunicación, por citar algunos de ellos. En ese sentido, se puede leer al final de ese documento de novedades de Embarcadero, en el área que afecta a los cambios en la RTL y refiriendo a la nueva funcionalidad:

The app tethering feature does not depend on a specific transport or protocol. In XE6, the RTL supports Ethernet connections between applications on the same local areal network (LAN), including applications running on the same device. You can implement your own adapters to support new transports and protocols using the app tethering API.

El párrafo remarcado ahonda en lo que ya comentaba, el código actual sienta las bases para que pueda extenderse hacia nuevas vías de comunicación y si bien, soporta al día de hoy las conexiones a través de Ethernet, en el marco de las áreas de red local, abre la posibilidad de que sean creados nuevos adaptadores y protocolos de comunicación, bien por Embarcadero o bien por otras empresas, de forma que permitan que se comuniquen nuestras aplicaciones con App Tethering donde Ethernet no esté disponible. Quizás sea una conexión mediante Bluetooth o USB… es algo que el tiempo nos dirá en que sentido evoluciona. Pero el hecho innegable de que ya se trabaje en esa dirección es lo que a mi me fuerza a valorar muy positivamente esta nueva funcionalidad.

 Del concepto a la práctica del taller, pasando por Neftalí…

 Comentaba que tenía en mente un montón de buenos propósitos e ideas para abordarla y de hecho ya había empezado a recopilar información para estructurar el tema dentro de la entrada, cuando me llegó el enlace del último artículo de mi amigo y compañero en DelpHispano, Germán Estévez, Neftalí, donde se abordaba precisamente este tema. A Germán, no voy a presentarlo porque lleva muchos años con nosotros, en la comunidad y todos le conocéis. Se ha ganado con merecimiento la participación en el proyecto MVP de Embarcadero, con los distintos artículos publicados en su blog a lo largo de los años y su participación en la comunidad, Esta ultima entrada me pareció especialmente buena y relataba de forma extensa los detalles principales del App Tethering pero a su vez la exposición se hacia desde la claridad. El enlace lo podéis encontrar accediendo a su blog pero no obstante, inmediatamente abajo muestro el enlace a la entrada y al vídeo que grabó Germán para complementarla.

Tethering; Operaciones básicas… - Neftalí

Puede ser un buen momento para quienes no la hayáis leído perdáis unos minutos en esa tarea ya que su contenido me parece una muy buena documentación para iniciarse en el tema. Nosotros nos apoyaremos también en ella y partiremos de que ya habéis hecho esta lectura previo a leer esta entrada.

Seguimos adelante…

Como concepto, la idea de App Tethering se materializa en dos componentes que son la parte visible del sistema, ambos integrados en la paleta de componentes de la herramienta:

TTetheringManager y TTetheringAppProfile.

Familiarmente, y dentro del articulo, nos referiremos  como Manager en las referencias al primero de ellos y Perfil cuando citemos al segundo.

 Habitualmente la imagen que se nos presenta viene a ser similar a esta

tethering1

 Como veis, desde una perspectiva visual, la imagen, fruto de mi poca pericia, ha acabado siendo semejante a un muñeco de nieve, como analogía que representaba el esquema. En la base, con un peso mayor en mi opinión, el conjunto de clases que colaboraban con TTetheringManager para establecer cómo se descubrían las aplicaciones y cómo se emparejaban para fijar esa comunicación. Y en la cabeza, coloreado de rojo, se representaba el perfil, la instancia de la clase TTetheringAppProfile, que representa la comunicación en si misma: los datos que se comparten y todo lo que pueden englobar: suscripción de recursos, intercambio de información y ejecución remota de acciones.

El estereotipo de la imagen anterior, es la primera asociación de ideas que se desprende de los principales ejemplos que se han compartido en las redes, sin embargo, la realidad descubre que nuestro componente manager, gestiona en la variable FRegisteredProfiles una lista de perfiles con los que va a quedar vinculado.

Permitid me que os lo muestre en el mismo código fuente, en la unidad:

System.Tether.Manager

que es la que contiene el conjunto de clases base que estructuran las aplicaciones de Tethering. A este nivel, muchas de las clases que se declaran son clases genéricas y con responsabilidad estructural y por ello, se firman buena parte de métodos abstractos y virtuales, estableciendo cómo se van a relacionar los actores que toman parte, pero delegando en los descendientes como se concretarán. Podemos ojear el constructor de la clase TTetheringManager para confirmar esto y sacar alguna idea mas.

constructor TTetheringManager.Create(AOwner: TComponent);
begin
 inherited Create(AOwner);


 FEnabled := True;
 Text := Name;

 FAdapters := TObjectList<TTetheringAdapter>.Create;
 FRegisteredProfiles := TList<TTetheringProfile>.Create;
 FRemoteManagers := TList<TTetheringManagerInfo>.Create;
 FRemoteAutoManagers := TList<TTetheringManagerInfo>.Create;
 FRemoteProfiles := TList<TTetheringProfileInfo>.Create;
 FPairedManagers := TList<TTetheringManagerInfo>.Create;
 FKnownManagers := TStringList.Create;
 FTempPasswords := TDictionary<string, string>.Create;
end;

Veamos… Una lista de adaptadores, FAdapters, intimamente ligados a la vía de comunicación que se registrará para encontrarse las aplicaciones. Una lista de perfiles locales, FRegisteredProfiles, asociados a nuestro manager. Una lista de managers remotos, FRemoteManagers, que almacenará nuestro punto de acceso a las distintas aplicaciones descubiertas. Y finalmente, nos interesa resaltar la lista que representa los perfiles que nos ha descubierto cada manager remoto, FRemoteProfiles. Hay mas listas, como podéis ver, pero estas serán las que habitualmente manejareis.

Así que para nuestro taller, y rompiendo esa imagen del muñeco de nieve, me incliné a que nuestra demo incluyera un manager y dos perfiles, en lugar de uno solo, buscando el aspecto mas didáctico. La imagen resultante se parecía mas a un ratón…

:-)

 tethering2

 Hemos citado una de las unidades, System.Tether.Manager, pero hay tres unidades mas, formando parte de lo que Embarcadero ha querido llamar el API de las aplicaciones App Tethering:

  • System.Tether.AppProfile
  • System.Tether.NetworkAdapter
  • System.Tether.TCPProtocol

La segunda, por ejemplo, System.Tether.NetworkAdapter, representa al adaptador real que utilizaremos para que se descubran los aplicaciones, basado en Ethernet, que es la única vía actual de comunicación en este tipo de aplicaciones.  En su interior se declara la clase

TTetheringNetworkAdapter = class(TTetheringAdapter)
private const
 TetheringStartDiscoverManagers = 'TetheringStartDiscoverManagers';
 TetheringStartRequestPair = 'TetheringStartRequestPair';
 TetheringStartRequestUnPair = 'TetheringStartRequestUnPair';
 TetheringStartDiscoverProfiles = 'TetheringStartDiscoverProfiles';
 TetheringStartNotification = 'TetheringStartNotification';
 TetheringNewManager = 'TetheringNewManager';
 TetheringShutdown = 'TetheringShutdown';
 TetheringLocalhost = 'localhost';
 public const
 AdapterID = 'Network';
private
...

que fijará tanto los mensajes que se van a intercambiar para descubrirse como la forma en la que se van a encontrar. Doy por supuesto que se entiende que el encontrarse o descubrirse no es casual… :-)   ¡No son dos amigos que se ven en una calle y que aprovechen para ponerse al día o contarse sus desventuras!. En estas unidades podéis ahondar en detalles.

Para verlo, si os parece, podríamos tomar como referencia el método público, DiscoverManagers(), que será uno de los procedimientos usados para descubrir las aplicaciones remotas (este método encontrará los managers remotos en los que podamos tener interés y que se estén ejecutando.

procedure TTetheringManager.DiscoverManagers(Timeout: Cardinal);
var
 I: Integer;
begin
 if Enabled then
 begin
 for I := 0 to FAdapters.Count - 1 do
 FAdapters[I].StartManagersDiscovery(Timeout);
 end
 else
 raise ETetheringException.CreateFmt(SDisabledManager, [Identifier]);
end;

El manager delega esta responsabilidad en el adaptador para la tarea, pero como decíamos en lineas anteriores, en el nivel que nos encontramos nuestra clase no conoce quien va a ejecutar realmente la acción (ni siquiera le preocupa). Se limita a recorrer la lista de adaptadores registrados en el sistema; este registro es algo que se ha hecho previamente en la zona de inicializacion de la unidad System.Tether.NetworkAdapter

...
initialization
 TTetheringAdapters.RegisterAdapter(TTetheringNetworkAdapter, TTetheringNetworkAdapter.AdapterID);

finalization
 TTetheringAdapters.UnRegisterAdapter(TTetheringNetworkAdapter.AdapterID);

y para cada uno de estos adaptadores, ejecuta el método genérico de la clase adaptador, StartManagersDiscovery() en la clase final.

En realidad, uno de los puntos cruciales para que el manager invoque al adaptador correcto se produjo en el momento en el que se registró el perfil dentro de nuestro manager. Al ser algo que se gestiona en tiempo de diseño, desde el inspector de objetos, habitualmente pasa desapercibido. Asignamos a nuestro perfil, TAppTetheringProfile la propiedad Manager, para crear el vinculo entre los dos componentes y en tiempo de ejecución además de ésto,  se producen dos efectos colaterales, representados en los métodos:

RegisterProfile() y SetupProtocols
procedure TTetheringProfile.SetManager(const AManager: TTetheringManager);
begin
 if (FManager <> AManager) and (FManager <> nil) then
 FManager.UnRegisterProfile(Self);

 FManager := AManager;
 if AManager <> nil then
 begin
 AManager.RegisterProfile(Self);
 SetupProtocols;
 end;
end;
procedure TTetheringManager.RegisterProfile(const AProfile: TTetheringProfile);
begin
 if FRegisteredProfiles.IndexOf(AProfile) < 0 then
 begin
 // Create all needed adapters for the manager.
 AutoCreateAdapters;
 FRegisteredProfiles.Add(AProfile);
 end;
end;

Como veis, la invocación del registro del perfil, a su vez encadena registro de los adaptadores, en función de la lista de adaptadores disponible.

procedure TTetheringManager.AutoCreateAdapters;
var
 AdapterType: TTetheringAdapterType;
begin
 if (not (csDesigning in ComponentState)) and (FAdapters.Count = 0) then
 for AdapterType in TTetheringAdapters.Adapters do
 RegisterAdapter(TTetheringAdapters.GetAdapterInstance(AdapterType));
end;

y este registro realmente va a servirse de una función que permitirá conocer que adaptador debe finalmente crear, descubriendo a través del registro que se hizo al inicio, donde se ligó la clase a instanciar con el identificador de tipo. Cuando hacemos la llamada GetAdapterInstance() estamos retornando la instancia del adaptador mediante el que vamos a descubrir las aplicaciones y se va a unir a esta lista de adaptadores disponibles. Algo similar descubriríamos al analizar SetupProtocols() pero con respecto a las comunicaciones de los perfiles.

He querido extenderme en estos puntos porque os permite vislumbrar un sistema donde en un futuro van a encajar nuevos adaptadores y protocolos y existirán otras unidades adicionales en el API en la fase de registro. Esto es lo que comentaba Embarcadero al inicio de la entrada y que supuestamente el futuro pueda traer, bien de la mano del propio Embarcadero o bien de la mano de algún partner asociado o empresa involucrada.

Francamente, y ahora que estamos en confianza, os confieso que el estudio de estas cuatro unidades es un tanto árido, si se puede calificar así por cuanto aparecen gran cantidad de clases y tipos con nombres similares y solo cuando pasas algunas horas de lectura atenta empiezas a entender un poco el hilo y las relaciones entre ellas. Además, para acabar de rematar, existe la dificultad adicional al descubrir que algunas de esas clases que encierran hilos de ejecución para evitar el bloqueo del hilo principal de la aplicación, en las principales tareas, unido a que se gestionan  con invocaciones de métodos anónimos. Se completa así un panorama propio de una buena taza de café, Hay muchos detalles enriquecedores en el código y mi consejo es que lo disfrutéis, como una vía de ese continuo aprendizaje al que estamos abocados.

De la teoría a la práctica…

Al presentar algunas de las listas que iba a gestionar el componente TTetheringManager, nos dará la oportunidad de conocer algunos tipos que van a ser ampliamente usados como retornos de numerosos métodos. Concretamente me refería a los registros TTetheringManagerInfo y TTetheringProfileInfo. Como es fácilmente previsible, el primero ofrecerá información básica sobre la clase TTetheringManager y el segundo sobre la clase TAppTetheringProfile

TTetheringManagerInfo = record 
  ManagerIdentifier: string; 
  ManagerName: string; 
  ManagerText: string; 
  ConnectionString: string; 
  Adapter: TTetheringAdapter; 
end;

TTetheringProfileInfo = record 
  ManagerIdentifier: string; 
  ProfileIdentifier: string; 
  ProfileText: string; 
  ProfileGroup: string; 
  ProfileType: TTetheringProfileType; 
  AllowedConnections: TTetheringAllowedConnections; 
end;

 Tanto uno como otro, en su contexto, pueden ser necesarios dentro de la firma de eventos o en muchos de los métodos que invocaremos.

También vamos a reconocer algo familiar para todos, dentro del concepto de Identificador único, que es una cadena con la estructura  TGuid['{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}'] 

TGUID = packed record
 D1: LongWord;
 D2: Word;
 D3: Word;
 D4: array[0..7] of Byte;
end;

 y que servirá para identificar las instancias de managers o perfiles, (remotos o locales), e incluso de los recursos. Cada manager y cada perfil se liga a un identificador único y será de utilidad para identificar en esas listas y dentro de recorridos, y operar en razón de esta identificación.

Con todo esto, permitid que demos un pequeño paso adelante, y nos entretenga una aplicación que nos muestre información de la ejecución de algunas tareas básicas. Para simplificar, vamos a escribir una ÚNICA aplicación, que podremos instanciarla varias veces dentro de la misma máquina o desde distintas máquinas de la misma red.

También añadimos que existan dos perfiles asociados al componente manager. Podemos llamarlos Profile A FMX y Profile B FMX para diferenciarlos y asignamos un grupo llamado A al primero y un grupo llamado B al segundo. El grupo es clave cuando se utiliza los procesos de descubrimiento de forma automática, algo que sucede cuando invocamos AutoConnect() y el sistema intenta encontrar perfiles dentro de los managers remotos que puedan comunicarse. Los distinguiremos visualmente con un circulo ROJO para el perfil A y VERDE en el perfil B.

Igualmente, para hacerlo un poco mas interesante, el perfil A permitirá hacer una tarea distinta de B. El perfil A podrá suscribir un recurso remoto en otro perfil A descubierto y esa suscripción servirá para actualizar la hora (la idea de Germán me pareció interesante porque era ilustrativa pero nosotros, vamos a utilizar 3 barras, respectivamente para las horas, los minutos y los segundos. Con el termino de suscripción entendemos que le estamos pidiendo al perfil remoto que nos informe de que dicho recurso ha cambiado y esta notificación remota nos permite la sincronía o cualquier operación deseada tras el cambio.

Por el contrario, el perfil B utilizará recursos temporales y permitirá enviar una cadena y un stream. Lo que haremos para simplificarlo será utilizar la cadena que escriba el usuario en un simple TEdit y enviarla a otro perfil B remoto, junto a un stream que la contiene y que a la recepción, tras la lectura en destino, escriba ambos texto (coincidirán lógicamente): el de la cadena enviada y la contenida dentro del stream.

Así que atentos en esos puntos. Si pulsáis la acción de suscribir sobre el perfil remoto B os dirá que el recurso no fue encontrado. ¡Claro! La propiedad Resources del perfil B no contiene nada. Cada oveja con su pareja.

Divide y …

Tras varios intentos que no me llegaron a gustar, acabé usando un componente TTabControl en el marco de un aplicación de FireMonkey, para así mostrar la información que me parecía mas interesante de los dos componentes. Y dentro de este TTabControl, abrí en tiempo de diseño y mediante su editor, seis pestañas para albergar cada área especifica.

Lo podéis ver en la siguiente imagen, que os da una idea del comentario anterior.

01_perfil manager

Así pues tenemos en el ejemplo:

  • 1ª Pestaña.- Información del manager de la aplicación.
  • 2º Pestaña – Información de los perfiles asociados al manager.
  • 3ª Pestaña – Información de los managers remotos encontrados
  • 4ª Pestaña – Información de los emparejamientos entre nuestro manager y otros managers remotos.
  • 5 º Pestaña – Información de los perfiles remotos.
  • 6ª Pestaña – Información en forma de log de las distintas acciones que vamos ejecutando.

La disposición también es importante ya que podemos actuar en algunas de ellas, haciendo un recorrido de izquierda a derecha, en la lógica que un usuario usaría App Tethering para intercambiar información entre dos perfiles. Al iniciarse la ejecución, la aplicación nos muestra la información de nuestro manager y de los distintos perfiles. El paso siguiente seria descubrir otras aplicaciones (pestaña 3 que nos mostraría los managers remotos que hemos encontrado y la posibilidad de emparejarlos. Por ello cuando accedamos a la pestaña 3, veremos varios botones que nos permitirán operar. La acción de emparejamiento, haría que pudiéramos visualizar el éxito del mismo en la pestaña 4, que mostraría el contenido de la lista asociada a estos pares. Y del resultado de ese emparejamiento, la pestaña 5 nos ofrece la información de los perfiles remotos encontrados y ejecutar acciones o intercambiar datos, de la forma que comentábamos anteriormente.

La última pestaña nos servirá de forma especial para visualizar toda esta información ya que añadiremos, de igual forma que lo hizo Germán en su articulo, una rutina para generar el log de los eventos que se van disparando y de las métodos ejecutados. Mi idea era basicamente, hacer un log de los eventos principales y que pudieramos discutir sobre ello, o sacar alguna reflexión de interés.

1ª y 2ª Pestaña: Obtener información del manager y de los perfiles.

Veamos el procedimiento que hemos utilizado para recoger la información del manager y que visualizará a dentro de un componente ListBox. Ambas pestañas tendrán una función meramente informativa dentro de la demo, razón por la que me he decantado por ese componente.

procedure TDemoTethering.GetLogInfoManager;
var
 fTetheringAdapter: TTetheringAdapter;
begin
 lbxManager.Items.Clear;
 lbxManager.Items.Add('ManagerIdentifier: '+ttManager.Identifier);
 lbxManager.Items.Add('ManagerText: '+ttManager.Text);
 lbxManager.Items.Add('<Lista de Adapadores>');
 for fTetheringAdapter in ttManager.Adapters do
 begin
    if fTetheringAdapter is TTetheringNetworkAdapter then
    begin
       lbxManager.Items.Add('<');
       lbxManager.Items.Add('AdapterID: '+ TTetheringNetworkAdapter(fTetheringAdapter).AdapterID);
       lbxManager.Items.Add('AdapterType: '+ TTetheringNetworkAdapter(fTetheringAdapter).AdapterType);
       lbxManager.Items.Add('Clase: '+ TTetheringNetworkAdapter(fTetheringAdapter).ToString);
       lbxManager.Items.Add('>');
    end;
 end;
 lbxManager.Items.Add('<Fin de Lista de Adapadores>');
 lbxManager.Items.Add('---------------------------');
end;

Básicamente, añadimos en cada ítem la información básica del manager y de la lista de adaptadores que va a utilizar. No tiene nada de especial. Cada vez que es invocado vacía nuestro listbox y lo rellena con la información actual.

La segunda pestaña, que se puede ver en la imagen inmediatamente inferior, hace referencia a los distintos perfiles vinculados al manager. De igual forma, pero operando sobre  la lista RegisteredProfiles , que recorreremos. e Igualmente cuidaremos de vaciar el listbox previamente, para que nos ofrezca información real de los distintos perfiles registrados en cada llamada, junto al recurso que creamos dentro del perfil A para que sea suscrito en un momento posterior.

procedure TDemoTethering.GetLogInfoRegisteredProfiles;
var
 MyProfile: TTetheringProfile;
 i: integer;
begin
  lbxProfilesApp.Items.Clear;
  for MyProfile in ttManager.RegisteredProfiles do
  begin
    lbxProfilesApp.Items.Add('Identificador: '+ MyProfile.Identifier);
    lbxProfilesApp.Items.Add('Text: '+MyProfile.Text);
    lbxProfilesApp.Items.Add('Tipo: '+MyProfile.ProfileType);
    lbxProfilesApp.Items.Add('Group: '+MyProfile.Group);
    lbxProfilesApp.Items.Add('---------------------------');
  end;
  for i := 0 to ttAppProfileA.Resources.Count-1 do
     lbxProfilesApp.Items.Add('Recurso local '+ttAppProfileA.Resources.Items[i].Name);
end;

02_perfiles apicacion

 3ª Pestaña: Obtener información y acceso a las aplicaciones remotas

La tercera pestaña la usaremos para descubrir aplicaciones tethering en el entorno de red (o en el mismo equipo). Descubrirlas y descubrir los managers remotos es básicamente lo mismo. Al descubrir la lista de managers remotos, estamos accediendo a la puerta de las aplicaciones con las que queremos comunicar y éste nos va a dar acceso a los perfiles, que serán quienes realmente intercambien información. Es como el peaje de una autopista para circular, donde el tiquet que pagamos es la validación de una contraseña, paso requerido para el proceso de emparejamiento.

En lugar de un componente TListBox usaremos la rejilla TGrid, que se rellenará con los datos de la lista RemoteManagers, mostrando en cada fila cuatro datos básicos como puedan ser el ManagerIdentifier, ManagerText, ConnectionString y AdapterType. De estos, realmente el mas importante es el identificador, que señala de forma única cada instancia de TTetheringManager, local o remota.

En la imagen podéis ver esta información.

03_discover managers

Para rellenar el TGrid, nos valemos del metodo GetValue() de la rejilla. Esta no trabaja exactamente igual que la que conocemos a través de la VCL pues no crea una cache de datos y necesitamos decirle como tiene que obtener los datos de cada campo cada vez que necesita actualizar la información que contiene.

procedure TDemoTethering.grdRemoteManagersGetValue(Sender: TObject; const Col, Row: Integer;
 var Value: TValue);
var
 sGroup: String;
begin
  if (Row < ttManager.RemoteManagers.Count) and (Row >= 0) then
  begin
    case Col of
       0: Value:= ttManager.RemoteManagers[Row].ManagerIdentifier; //ManagerIdentifier //hasgroup
       1: Value:= ttManager.RemoteManagers[Row].ManagerText; //group
       2: Value:= ttManager.RemoteManagers[Row].ConnectionString;
       3: Value:= ttManager.RemoteManagers[Row].Adapter.AdapterType;
    end;
  end;
end;

Al pie del componente TGrid aparecen 3 botones que nos permitirán probar: Descubrir managers remotos, Emparejarlos y la acción contraria, Romper el emparejamiento. La primera de ellas no requiere parámetros y basta que el manager invoque el método DiscoverManagers(), que iniciará la búsqueda.

Al final del proceso, nuestro grid mostrará distintas filas y nos permitirá, mediante la selección de una de ellas, entregar como parámetro la estructura TTetheringManagerInfo en las llamadas de PairManager/UnPairManager, que suceden al pulsar los botones que acompañan. Podemos obtener recorriendo la lista y comparando el identificador del manager remoto de la fila seleccionada.

Veamos el código de dos dos primeros (UnpairManager es similar a PairManager y lo omito)

procedure TDemoTethering.btnDiscoverManagersClick(Sender: TObject);
begin
  _Log('Descubriendo nuevos managers...');
  ttManager.DiscoverManagers();
end;

procedure TDemoTethering.btnPairManagerClick(Sender: TObject);
var
 IDManagerString: String;
 fTTetheringManagerInfo: TTetheringManagerInfo;
begin
  IDManagerString:= SelectedRemoteManagerIdentifier;
  _Log('Emparejando ' + ttManager.Identifier + ' y ' + IDManagerString);
  for fTTetheringManagerInfo in ttManager.RemoteManagers do
  begin
    if fTTetheringManagerInfo.ManagerIdentifier = IDManagerString then
    begin
      ttManager.PairManager(fTTetheringManagerInfo);
      Break;
    end;
  end;
  tabControl1.ActiveTab:= tabPairedManagers;
  ShowMessage('Si ha tenido exito existirá contenido en las pestañas PairedManagers y RemoteManagers.'#13#10+
 'Pulsa esta última para proseguir...');
end;

4ª Pestaña: Obtener información de los emparejamientos

De forma similar a como operamos en la primera y segunda pestaña, mostramos la información de la lista PairedManagers para visualizarla en un listbox. Este es el procedimiento y una captura de la información que nos muestra.

procedure TDemoTethering.GetLogInfoPairedManagers;
var
 fTTetheringManagerInfo: TTetheringManagerInfo;
begin
  lbxPairedManagers.Items.Clear;
  for fTTetheringManagerInfo in ttManager.PairedManagers do
  begin
    lbxPairedManagers.Items.Add('ManagerIdentifier: '+fTTetheringManagerInfo.ManagerIdentifier);
    lbxPairedManagers.Items.Add('ManagerText: '+fTTetheringManagerInfo.ManagerText);
    lbxPairedManagers.Items.Add('ConectionString='+fTTetheringManagerInfo.ConnectionString);
    lbxPairedManagers.Items.Add('AdapterType='+fTTetheringManagerInfo.Adapter.AdapterType);
    lbxPairedManagers.Items.Add('---------------------------');
  end;
end;

04_paired managers

 

Pero esta cuarta pestaña es un paso intermedio meramente informativo, dado que el objetivo final es intercambiar información y para ello, directamente accederíamos a la siguiente área que va a contener los perfiles remotos encontrados durante el emparejamiento.

5ª Pestaña: Obtener información de los perfiles remotos y comunicación.

También en este caso, he optado por un componente TGrid para visualizar la información de los perfiles remotos descubiertos. Recordad que éstos, eran almacenados en la lista RemoteProfiles de nuestro manager.

05_perfiles remotos    La imagen muestra en una de las columnas de la rejilla el circulo rojo para identificar los perfiles remotos del grupo A y el circulo verde para los del grupo B. Visualmente nos ayudará para seleccionar uno u otro en función de que usemos las acciones de suscripción/desuscripción, representadas en los dos botones inmediatamente inferiores a la rejilla para los perfiles del grupo A y la acción de enviar un texto o un stream para las acciones del grupo B.

En la parte mas inferior del formulario, veremos la respuesta de estos sucesos. Al suscribirnos a otro perfil del grupo A, actualizaremos nuestras barras de progreso emulando los datos de la hora. Y finalmente, en la parte derecha, podremos visualizar el envío que nos ha hecho un perfil remoto del grupo B.

Se que parece un poco lioso, pero la practica, si veis la demo y la ejecutais no  creo que os dejará lugar a duda.

La rutina de actualización de la rejilla, será similar a la que existía para el manager, en la pestaña 3.

procedure TDemoTethering.grdRemoteProfilesGetValue(Sender: TObject; const Col,
 Row: Integer; var Value: TValue);
var
 sGroup: String;
 fConnection: TTetheringAllowedConnection;
 sAllowedConnections: String;
begin
  if (Row < ttManager.RemoteProfiles.Count) and (Row >= 0) then
  begin
    sGroup:= ttManager.RemoteProfiles[Row].ProfileGroup;
    case Col of
      0: Value:= TValue.from<Boolean>(sGroup <> ''); //hasgroup
      1: Value:= TValue.From<string>(sGroup); //group
      2: if sGroup = 'GrupoA' then Value:= TValue.From<TBitmap>(fBitmapRED)
         else if sGroup = 'GrupoB' then Value:= TValue.From<TBitmap>(fBitmapGREEN)
              else Value:= TValue.Empty;
      3: Value:= ttManager.RemoteProfiles[Row].ManagerIdentifier;
      4: Value:= ttManager.RemoteProfiles[Row].ProfileIdentifier;
      5: Value:= ttManager.RemoteProfiles[Row].ProfileType;
      6: Value:= ttManager.RemoteProfiles[Row].ProfileText;
      7: begin
           sAllowedConnections:= '';
           for fConnection in ttManager.RemoteProfiles[Row].AllowedConnections do
              sAllowedConnections := sAllowedConnections + fConnection.ProtocolType + ',' + fConnection.AdapterType + ',' + fConnection.Connection + ';';
          Value:= sAllowedConnections;
         end;
    end;
 end;
end;

La accion de descubrir nuevos perfiles remotos también es natural al componente TTetheringManager que básicamente se limita a entregar como parámetro de la invocación la información que representa al manager remoto: la estructura TTetheringManagerInfo. Invocaremos DiscoverProfiles( ) para cada uno de los managers de los que deseamos obtener dicha información (o para uno de ellos dependiendo que queráis hacer).

procedure TDemoTethering.btnDiscoverProfilesClick(Sender: TObject);
var
 fTTetheringManagerInfo: TTetheringManagerInfo;
begin
  _Log('Descubriendo nuevos perfiles de aplicación...');
  with ttManager do 
  begin
    for fTTetheringManagerInfo in RemoteManagers do 
       DiscoverProfiles(fTTetheringManagerInfo);
  end;
end;

Quizás convendría ahora decir, que todas estas llamadas, tanto ésta, como la de DiscoverManagers, PairManager o UnPairManager, vistas anteriormente, tienen su correspondiente evento asociado con la finalización de la llamada, que representa una notificación de que ha finalizado, recibiendo según el caso alguna estructura indicativa en alguno de los parámetros del evento. En este caso, por ejemplo el evento OnEndProfilesDiscovery nos permitirá hacer un log y anotar la información recibida.

procedure TDemoTethering.ttManagerEndProfilesDiscovery(const Sender: TObject;
 const RemoteProfiles: TTetheringProfileInfoList);
var
 RemoteProfileInfo: TTetheringProfileInfo;
begin
  for RemoteProfileInfo in RemoteProfiles do
  begin
    _Log('Descubierto un Perfil Remoto...');
    _Log(TTetheringManager.ProfileInfoToString(RemoteProfileInfo));
  end;
  HazLogYActualiza('Finalizado descubriendo nuevos perfiles aplicacion...');
end;

 Así que el código que he escrito, se limita a dar respuesta a los distintos eventos que me han parecido interesantes, o que tenían relación con la acción que se estaba ejecutando, con el fin de que pudierais ver el orden en que se van disparando y nos de una idea mas cercana de lo que representa esa conversación que establecen entre si.

En lo que respecta a la acción de suscribir un recurso, que permitiría que le notifiquemos a un perfil remoto que deseamos ser avisados cada vez que cambia el recurso en el que estamos interesados y que os incluyo en el siguiente cuadro, me quedaría con el detalle de la llamada SelectedProfileInfo

procedure TDemoTethering.btnSuscribeResourceClick(Sender: TObject);
var
 fRemoteProfileInfo: TTetheringProfileInfo;
 fRemoteResource: TRemoteResource;
 IDProfileString: String;
begin
  fRemoteProfileInfo:= SelectedProfileInfo;
  if fRemoteProfileInfo.ProfileGroup = ttAppProfileA.Group then
  begin
    if ttAppProfileA.Connect(fRemoteProfileInfo) then
    begin
      _Log('Ha tenido exito la conexion de los dos perfiles');

      fRemoteResource:= ttAppProfileA.GetRemoteResourceValue(fRemoteProfileInfo, 'otra_cadena');
      if Assigned(fRemoteResource) then
      begin
        ttAppProfileA.SubscribeToRemoteItem(fRemoteProfileInfo, fRemoteResource);
        _Log('Te has suscrito al recurso remoto '+ fRemoteResource.Name + ' de ' + fRemoteProfileInfo.ProfileIdentifier);
      end
      else begin
        _Log(fRemoteProfileInfo.ProfileIdentifier + ' no tiene ningun recurso con el nombre '+ fRemoteResource.Name);
        _Log('No puedes suscribirte...');
      end;
    end
    else _Log('No ha tenido exito la conexion de los dos perfiles');
  end
  else raise Exception.Create('El perfil no pertene al grupo correcto para ejecutar esta accion...');
end;

 En un principio había optado por escribir otro bucle for in similar a los que ya hay escritos en el código de la aplicación. Sin embargo, la revisión de las fuentes me permtió advertir la existencia de una función de clase, que nos podía venir perfecta en esa tarea.

Veamos que implementa SelectedProfileInfo, que devolvería la información del perfil remoto seleccionado en la rejilla.

function TDemoTethering.SelectedProfileInfo: TTetheringProfileInfo;
var
 fProfileIdentifier: String;
 fManagerIdentifier: String;
 fProfileText: String;
 fProfileGroup: String;
 fProfileType: String;
 fAllowedConnections: String;
 sProfileString: String;
 fRemoteProfileInfo: TTetheringProfileInfo;
 fStream: TStringStream;
 sTexto: String;
 iPosition: Double;

 function GetValue(AIndex: Integer): String;
 begin
 Result:= MyGrid(grdRemoteProfiles).GetValue(AIndex,grdRemoteProfiles.Selected).AsString;
 end;

begin

 fManagerIdentifier := GetValue(chColManagerIdentifier.Index);
 fProfileIdentifier := GetValue(chColProfileIdentifier.Index);
 fProfileText := GetValue(chColProfileText.Index);
 fProfileGroup := GetValue(chcolGroup.Index);
 fProfileType := GetValue(chColProfileType.Index);
 fAllowedConnections:= GetValue(chColAllowedConnections.Index);

 sProfileString := '';
 sProfileString := sProfileString + fManagerIdentifier + TetheringSeparator;
 sProfileString := sProfileString + fProfileIdentifier + TetheringSeparator;
 sProfileString := sProfileString + fProfileText + TetheringSeparator;
 sProfileString := sProfileString + fProfileGroup + TetheringSeparator;
 sProfileString := sProfileString + fProfileType + TetheringSeparator;
 sProfileString := sProfileString + fAllowedConnections + TetheringSeparator;

 Result:= TTetheringManager.StringToProfileInfo(sProfileString);
end;

 De esa forma alternativa, mediante la función de clase StringToProfileInfo (resaltada en color amarillo), se nos permite convertir la cadena que representa los datos del perfil en el objeto TTetheringProfileInfo, ampliamente usado. A la inversa, también existe otra función de clase para convertir el registro en una cadena.

6ª Pestaña: Obtener información de log interno y propio

Este último aparado nos servirá para visualizar un log de todo cuanto va sucediendo en nuestra aplicación, especialmente lo que afecta a los distintos pasos que hemos conocido,

Quizás de esta área, la parte que mas o pueda interesar es la que se encierra en este fragmento del código de la aplicación: la función de clase RegisterLog(), que registrará el método que implementamos para obtener el log interno

procedure TDemoTethering._LogInterno(const Msg: string);
begin
  if Assigned(memInfo) then _Log(Msg);
end;

procedure TDemoTethering.ConectarLogInterno(AConectar: Boolean);
var
 fAdapter: TTetheringAdapter;
begin
  for fAdapter in ttManager.Adapters do
  begin
    if fAdapter is TTetheringNetworkAdapter then
    begin
      if AConectar then
        TTetheringNetworkAdapter(fAdapter).RegisterLog(_LogInterno)
      else TTetheringNetworkAdapter(fAdapter).RegisterLog(nil);
    end;
  end;
end;

procedure TDemoTethering.btnConectarLogInternoClick(Sender: TObject);
begin
  ConectarLogInterno(true);
end;

procedure TDemoTethering._Log(const AMsg: String); 
begin 
  memInfo.Lines.Add(FormatDateTime('hh:mm:ss.zzz ', Now) + '>'+ AMsg); 
  memInfo.Lines.Add(''); 
end;

 Y os puede interesar porque os podría permitir obtener un log de lo que sucede entre bastidores, cuando en necesitéis saber que mensajes están intercambiando y resulte complicado seguir la pista al código. En realidad no es algo que esté documentado (yo no había encontrado referencias a la clase TTetheringLog pero la inspección del código fuente y el sentido común me hizo suponer que existiera y que pudiera tener sentido dentro de un contexto de hilado múltiple, que puede ser tan problemático en ocasiones.

 TTetheringLog = class
 private class var
  FLogs: TQueue<string>;
  FTetheringLog: TTetheringDoLog;
 private
  class constructor Create;
  class destructor Destroy;
  class procedure QueueLog;
  class procedure DoLog(const Msg: string);
 public
  class procedure Log(const Msg: string);
  class procedure RegisterLog(const DoLog: TTetheringDoLog);
 end;

Esta clase, oculta en la zona de implementación, será usada en el hilo TTetheringNetworkManagerCommunicationThread creado dentro del propio adaptador. Si mi intuición no me engaña el diseñador lo intuyó necesario durante fase de diseño de la propia comunicación y el intercambio de mensajes.

Tenéis mas información en la unidad

System.Tether.NetworkAdapter

dentro de la clase TTetheringNetworkAdapter.

 TTetheringNetworkAdapter = class(TTetheringAdapter)
...
 private
 FUDPClient: IIPUDPServer;
 FCommunicationThread: TTetheringNetworkManagerCommunicationThread;
     FEvent: TEvent;

Esta imagen os muestra una captura de ambos logs: el que hemos implementado dentro de los eventos de ambos componentes y el resultante de los procesos internos.

06_log manager

 

Pulsa  aquí para descargar el código fuente de la entrada.

Si os parece lo dejamos en este punto, toda vez que disponéis del código fuente para verlo con detalle.

Usar la demo y extraer alguna conclusión

Mi propuesta para esta entrada es que uséis tanto esta demo como la que Germán incluyó en su blog y que intercambiemos reflexiones y comentarios en el área de taller del blog de Delphi Básico, previo registro. Y os invito a que durante estas dos semanas de la primera quincena de Junio, que va a quedar abierto un hilo para este cometido, compartamos esas ideas. Os pido especialmente que los mensajes sean referidos exclusivamente al tema que nos ocupa.

Para finalizar y a modo de conclusión, os he incluido un fichero pdf que contiene unas lineas de log combinadas en orden cronológico, resultantes de la ejecuciones simultaneas de la demo en el mismo equipo, donde salvé individualmente cada fichero y ya de una forma manual me entretuve en ordenar esa comunicación en un solo texto.

Download (PDF, 286KB)

Mi deseo como siempre, de que os haya sido de ayuda.

Rate: 0

  1. Osvaldo says:

    Buen ejemplo muy instructivo y gracias por compartir tu conocimiento , y abusando de tu ayuda me gustaría si me puedes resolver un problema que me surgió la aplicación que se conecta me marca un error a la hora de cerrarse relacionada con los sockets me podrías orientar de antemano agradezco tu ayuda

  2. Salvador says:

    Hola Osvaldo:

    Gracias por el comentario.

    No das muchos detalles del error y tampoco estoy seguro de si te refieres al demo que se incluye en la entrada o a uno tuyo propio, en algunas pruebas que hayas hecho.
    De cualquier forma, la demo que se incluye tampoco es representativa de una aplicación de tethering habitual, y quizás por ello sea mas susceptible de generar alguna excepción ya que el propio monitoreo de todos los eventos para la creación del log pueda contribuir en el error que comentas (algún tipo de bloqueo a nivel de hilo al intentar acceder a la aplicación o simplemente que pierde el acceso al hilo principal y existe alguna llamada posterior a una referencia que no sea válida).

    Un saludo,

    Salvador

Leave a Reply

Tu dirección de correo electrónico no será publicada. Los campos necesarios están marcados *


8 − cuatro =

You may use these HTML tags and attributes:

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Spam Protection by WP-SpamFree