Primeros Pasos con DDS OpenSplice

De ChuWiki

Vamos con un pequeño Hola Mundo de DDS utilizando la implementación Community de OpenSplice, la gratis :)

Aquí el código completo del ejemplo con DDS

Instalación[editar]

Vamos a la página de la versión community de OpenSplice y nos bajamos la instalación que más nos guste, adecuada a nuestro sistema operativo. Eso sí, hay que registrarse ....

En mi caso, me he bajado el zip de ejecutables ya preparados para mi sistema operativo linux. Sólo hay que desempaquetar.

Una vez desempaquetado, hay un fichero release.com al que damos permisos de ejecución (en caso de linux) y lo ejecutamos desde una terminal bash, de forma que nos deja todas las variables de entorno preparadas para la instalación

chuidiang@javier-linux:~/DDS-OpenSplice/x86_64.linux$ pwd
/home/chuidiang/DDS-OpenSplice/x86_64.linux

chuidiang@javier-linux:~/DDS-OpenSplice/x86_64.linux$ ls
bin         demos  etc       include     jar  release.com
custom_lib  docs   examples  index.html  lib  src

chuidiang@javier-linux:~/DDS-OpenSplice/x86_64.linux$ . ./release.com
<<< Vortex OpenSplice HDE Release 6.7.171127OSS For x86_64.linux, Date 2017-11-27 >>>

chuidiang@javier-linux:~/DDS-OpenSplice/x86_64.linux$ env | egrep x86_64\.linux
OSPL_URI=file:///home/chuidiang/DDS-OpenSplice/x86_64.linux/etc/config/ospl.xml
OSPL_HOME=/home/chuidiang/DDS-OpenSplice/x86_64.linux
LD_LIBRARY_PATH=/home/chuidiang/DDS-OpenSplice/x86_64.linux/lib
CPATH=/home/chuidiang/DDS-OpenSplice/x86_64.linux/include:/home/chuidiang/DDS-OpenSplice/x86_64.linux/include/sys:
PATH=/home/chuidiang/DDS-OpenSplice/x86_64.linux/bin:/home/chuidiang/bin:/home/chuidiang/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/opt/jdk1.8.0_131/bin:/snap/bin:/opt/apache-maven-3.5.0/bin:/opt/gradle-3.5/bin
PWD=/home/chuidiang/DDS-OpenSplice/x86_64.linux
OSPL_TMPL_PATH=/home/chuidiang/DDS-OpenSplice/x86_64.linux/etc/idlpp

Hemos ejecutado con ". ./release.com", lo del punto delante es para que nuestro terminal bash "herede" las variables de entorno que define el script, si no lo hacemos así, no las tendremos.

El comando env | egrep x86_64\.linux nos permite mostrar todas las variables de entorno y filtrar para ver sólo aquellas que tienen x86_64\.linux. Esto es un pequeño truco para ver más o menos qué variables nos ha creado. Vemos las OSPL_HOME y demás, vemos que ha tocado LD_LIBRARY_PATH para que el sistema operativo encuentre las librerías de OpenSplice y que ha tocado PATH para que encuentre los ejecutables.

Algunas de estas variables son importantes para que nuestro programa luego funcione. Deben estar definidas en nuestro entorno de desarrollo (eclipse, idea, netbeans) o en el terminal donde pretendamos ejecutar nuestro programa ya compilado.

Generar fichero IDL y código[editar]

Para empezar con DDS, una de las primeras cosas que hay que hacer es generar unos tipos de datos en un fichero IDL (Interface Definition Language). Este fichero es de texto, con una sintaxis similar pero no igual a la de C y ahí debemos definir tipos de datos que son los que querremos que nuestras aplicaciones intercambien a través de DDS. El siguiente es un ejemplo sencillo de fichero IDL para nuestras pruebas

module First {
   struct AnStruct {
      long id;
      string aText;
   }; 
#pragma keylist AnStruct id
};

Definimos un módulo (First de nombre, por poner algo), dentro las estructuras que queramos. Hay toda una sintaxis que permite definir tipos, enumerados, etc, etc. Hemos definido una estructura de nombre AnStruct que tiene dos campos, un id y un texto.

DDS viene en su directorio bin con una herramienta idlpp (IDL Pre Processor) que a partir del fichero IDL genera ficheros en nuestro lenguaje de programación favorito, siempre que esté soportado (C, C++, Java)

En nuestro fichero IDL hemos puesto un pragma para indicar de la estructura AnStruct cual sería el campo que hace de clave, en nuestro caso id.

#pragma keylist <estructura> <campo clave>

No es obligatorio que haya claves, pero idlpp presupone que si tiene clave es una estructura de "alto nivel" que vamos a usar en nuestra aplicación para transmitirla a través de DDS y genera más código para ella. Si no ponemos clave a la estructura, idlpp piensa que es una estructura "secundaria", que irá dentro de otras de más alto nivel, y no genera para ella tanto código. Por ello, para nuestro ejemplo, ponemos clave, de forma que genere todo el código necesario para trabajar cómodamente.

Una vez definidas todas las variables de entorno con el script module.com que comentamos antes, y con el fichero first.idl creado y el contenido que acabamos de mencionar, nos vamos con nuestro bash al directorio donde está el fichero first.idl y ejecutamos el comando idlpp

chuidiang@javier-linux:/tmp$ ls first.idl
first.idl

chuidiang@javier-linux:/tmp$ idlpp -l java -S first.idl 

chuidiang@javier-linux:/tmp$ ls First
AnStructDataReaderHelper.java          AnStructDataWriterImpl.java
AnStructDataReaderHolder.java          AnStructDataWriter.java
AnStructDataReaderImpl.java            AnStructDataWriterOperations.java
AnStructDataReader.java                AnStructHolder.java
AnStructDataReaderOperations.java      AnStruct.java
AnStructDataReaderViewHelper.java      AnStructMetaHolder.java
AnStructDataReaderViewHolder.java      AnStructSeqHolder.java
AnStructDataReaderViewImpl.java        AnStructTypeSupportHelper.java
AnStructDataReaderView.java            AnStructTypeSupportHolder.java
AnStructDataReaderViewOperations.java  AnStructTypeSupport.java
AnStructDataWriterHelper.java          AnStructTypeSupportOperations.java
AnStructDataWriterHolder.java

Los parámetros que hemos puesto al comando son:

  • -l java para que genere código java
  • -S para que genere código que funciona sin necesidad de otras instalaciones. Si no ponemos esta opción -S (Standalone), necesitaremos tener una instalación de CORBA funcionando y el código generado utilizará dicha instalación.
  • first.idl es el fichero con nuestra estructura

Como el módulo en el fichero IDL es First, las clases java se crean en el paquete First dentro del directorio First. Vemos el mogollón de clases que se han generado ahí. No las usaremos todas, así a bote pronto, creo que nos hacen falta unas cinco de ellas.

Vamos con el código

Proyecto en nuestro IDE[editar]

Creamos un proyecto java en nuestro IDE favorito. Tenemos que añadir varias cosas:

Los fuentes que se acaban de generar con el idlpp, por supuesto.

Como hemos dicho que nuestro código java queremos que sea Standalone, sin un CORBA instalado, necesitamos añadir la librería dcpssaj.jar que encontraremos en el directorio jar de la instalación de OpenSplice. Según el tipo de fuente generado, el jar puede ser distinto, así que no los añadas todos, sólo este que acabamos de mencionar.

Necesitamos además librerías nativas, así que cuando arranquemos nuestro programa, debemos poner algo como

-Djava.library.path=/home/chuidiang/DDS-OpenSplice/x86_64.linux/lib/

es decir, en la variable java.library.path añadir el directorio lib de OpenSplice

Adicionalmente, tenemos que asegurarnos que algunas de las variables de entorno que define module.com están definidas en nuestro IDE, en concreto

LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/chuidiang/DDS-OpenSplice/x86_64.linux/lib
OSPL_URI=file:///home/chuidiang/DDS-OpenSplice/x86_64.linux/etc/config/ospl.xml
OSPL_HOME=/home/chuidiang/DDS-OpenSplice/x86_64.linux/

En ejecución tiene que encontrar varias librerías nativas, así que tenemos que asegurarnos que en LD_LIBRARY_PATH está el directorio lib. La opción -Djava.library.path permite que java encuentre la primera librería nativa que tiene que arrancar, pero esta tira a su vez de otras que sólo será capaz de encontrar si el directorio lib está en LD_LIBRARY_PATH

OSPL_URI apunta a un fichero de configuración por defecto adecuado para arrancar opensplice de forma sencilla. Cuando seamos más expertos, podríamos crear ficheros de configuración más a nuestro gusto y para cubrir nuestras necesidades.

OSPL_HOME el directorio donde tenemos instalado OpenSplice.

Y listo, ya nos podemos poner a programar

Un primer vistazo al programa[editar]

Esto es lo que tiene que hacer nuestro programa Hola Mundo

        createDomainParticipantFactory();
        createParticipant();
        registerType();
        createTopic();


        createPublisher();
        createWriter();

        createSubscriber();
        createReader();


        startReading();


        startWriting();

Son las siguientes cosas, y aprovechamos para contar conceptos de DDS. Luego veremos con detalle cada uno de esos métodos

  • Creamos un DomainParticipantFactory. Un Dominio en DDS es un "espacio de trabajo". Ahí es donde meteremos todos nuestros datos. Podemos crear varios dominios para separar los datos que no tengan que ver entre sí. También es normal que aplicaciones distintas usen cada una su propio dominio, de forma que puden compartir DDS y trabajar sin mezclar los datos de unos y otros.
  • Crear un Participant del Dominio. Este Participant es "alguien" que se asocia al dominio y que podrá escribir y leer datos de ese dominio, y no de otros. A partir del Participant se crearán todas las entidades que necesitemos para escribir y leer datos, que irán automáticamente asociadas al dominio del Participant.
  • Registrar el tipo de dato. Tenemos que decirle a DDS que tenemos un tipo de dato que es AnStruct (de nuestro IDL)
  • Crear Topic. Dentro de DDS, un topic es un "almacén" donde meteremos un dato concreto y donde otros podrán leer ese dato concreto. Al crear el topic debemos darle un nombre y decir de qué tipo es el dato que vamos a guardar ahí (AnStruct). En una comparativa de programación, es como declarar una variable dándole un nombre y un tipo. Podemos crear todos los topic que queramos para un tipo de dato concreto.
  • Crear un Publisher. Un Publisher es alguien que va a publicar datos en un dominio concreto. Se crea a partir del participante y va, por tanto, asociado al dominio del participante.
  • Crear un Writer. A partir del Publisher, creamos todos los Writer que necesitemos. Cada Writer va asociado a un Topic concreto y puede escribir datos en ese topic siempre que lo desee.
  • Crear un Subscriber. Al igual que el Publisher, a partir de un Participant se crea un Subscriber, que es alguien asociado a un dominio concreto, el del Participant a partir del cual es creado y que tiene interés en recibir datos de ese dominio.
  • Crear un Reader. A partir del Subscriber crearemos los Reader que necesitemos. Cada Reader va a asociado a un Topic concreto y por tanto será capaz de leer datos de ese Topic.
  • Suscribimos al Reader al topic del ejemplo y empezamos a leer.
  • Hacemos que el Writer empiece a escribir.

Vamos ahora con los detalles de código...

Creación del DomainParticipantFactory[editar]

Este es el contenido del método

   private DomainParticipantFactory domainParticipantFactory;
   ...
   private void createDomainParticipantFactory() {
       domainParticipantFactory = DomainParticipantFactory.get_instance();
   }

Es sencillo, basta llamar al método get_instance() de la clase DomainParticipantFactory.

Creación de un Participant[editar]

Vamos con el código para crear un Participant, "alguien" asociado a un dominio dentro de DDS.

    private DomainParticipant participant;
    ...
    private void createParticipant() {
        int domain = DOMAIN_ID_DEFAULT.value;

        participant = domainParticipantFactory.create_participant(
                domain, PARTICIPANT_QOS_DEFAULT.value, null, STATUS_MASK_NONE.value
        );
        System.out.println("Creado domain participant");
    }

Los dominios en DDS van identificados por un número y podemos poner el número que queramos, siempre que todos estén de acuerdo en qué número representa cada dominio. Podemos poner como parámetro un número de dominio que nos apetezca, siempre que todas las aplicaciones que tienen que compartir datos estén de acuerdo en usar el mismo dominio. De todas formas, para ganar en versatilidad, la forma habitual es definir este dominio en el fichero de configuración ospl.xml al que apunta la variable OSPL_URI. Para que DDS sepa que el número de dominio que debe usar es el de ese fichero, en este método debemos pasar como número de dominio el de la constante DOMAIN_ID_DEFAULT.value.

Y ahora basta llamar a domainParticipantFactory.create_participant() para crear un Participant asociado a ese dominio. Como parámetros:

  • el dominio, pasamos DOMAIN_ID_DEFAULT.value porque queremos que coja el del fichero de configuración al que apunta la variable OSPL_URI.
  • La calidad de servicio deseada por defecto. En DDS, la calidad de servicio es hasta que punto estamos seguros de la entrega de un mensaje desde que alguien lo envía hasta que el subscriptor lo recibe. Podemos poner una calidad de servicio que obligue a la entrega, incluso aunque el subscriptor se deconecte varios dias y vuela a conectarse varios días después de que el mensaje se ha enviado, hasta el extermo opuesto, que el subscriptor sólo recibe el mensaje si está atento a él y si no, lo pierde. Entre medias hay un montón de variantes. Y la calidad de servicio se puede fijar en muchos sitios : en el publicador, en el subscriptor, en el topic concreto, etc. DDS hará un esfuerzo por casar de forma adecuada estas calidades de servicio si las ponemos distintas en cada lado, o dará error si no puede casarlas. Por ejemplo, un publisher puede decir que quiere garantizar la entrega del mensaje, pero el subscriptor puede decir que no tiene tanto interés y que no pasa nada si se lo pierde. Ponemos como parámetro PARTICIPANT_QOS_DEFAULT.value que es la calidad que DDS considera adecuada para que sea la de defecto de un Participant
  • Un listener para enterarnos de lo que pase en ese dominio, como participantes. Si ponemos un Listener, podemos enterarnos de cuando se crean nuevos participantes, nuevos topics, si hay errores en la creación de todas estas cosas, etc, etc. De momento no tenemos interés en saber qué pasa en el dominio, así que null para listener.
  • Una máscara para indicar en qué cosas tenemos interés. Como no hemos pasado listener, ponemos STATUS_MASK_NONE.value para indicar que no tenemos interés en nada.

Registrar un Tipo[editar]

El código para registrar el tipo de dato AnStruct que se generó a partir de nuestro IDL es el siguiente

    String anStructTypeName;
    ...
    private void registerType() {
        AnStructTypeSupport anStructTS = new AnStructTypeSupport();
        anStructTypeName = anStructTS.get_type_name();
        anStructTS.register_type(participant, anStructTypeName);
        System.out.println("registrado tipo");
    }

Tenemos, para facilitarnos la vida, que usar la clase AnStructTypeSupport que generó idlpp. La instanciamos, obtenemos el nombre del tipo con get_type_name() y usando la instancia de AnStructTypeSupport llamamos a register_type() para registrar el tipo. Pasamos como parámetro el Participant y el nombre del tipo.

Crear un Topic[editar]

Ya hemos registrado el tipo, ahora debemos crear al menos un Topic con este tipo, que será el almacén donde se publiquen y de donde se recojan datos de este tipo. El código para hacerlo es el siguiente

    public static final String TOPIC_NAME = "Struct_1";
    private Topic anStructTopic;
    ...
    private void createTopic() {

        TopicQosHolder topicQosHolder = new TopicQosHolder();
        participant.get_default_topic_qos(topicQosHolder);
        topicQosHolder.value.reliability.kind =
                ReliabilityQosPolicyKind.RELIABLE_RELIABILITY_QOS;
        topicQosHolder.value.durability.kind = DurabilityQosPolicyKind.TRANSIENT_DURABILITY_QOS;

        anStructTopic = participant.create_topic(
                TOPIC_NAME,
                anStructTypeName,
                topicQosHolder.value,
                null,
                STATUS_MASK_NONE.value);

        System.out.println("registrado topic");
    }

Lo primero es decidir la calidad de servicio para este Topic. Si queremos que sea entregable al 100% o si sólo se entrega si alguien está pendiente de él. Para ello, creamos una instancia de TopicQosHolder, la rellenamos con la calidad por defecto del participant llamando a participant.get_default_topic_qos(topicQosHolder) y luego modificamos lo que nos interse. En este caso y a modo de ejemplo:

  • Reliability. Puede ser RELIABLE o BEST_EFFORT, o sea, se entrega sí o sí (y el writer puede quedarse bloqueado hasta que se entregue) o solo se intenta entregar si es posible.
  • Durability. El tiempo que el mensaje está disponible, puede variar con estos valores VOLATILE_DURABILITY_QOS, TRANSIENT_LOCAL_DURABILITY_QOS, TRANSIENT_DURABILITY_QOS, PERSISTENT_DURABILITY_QOS. Si ponemos el primero, el dato no se almacena de ninguna forma y solo lo leerán los lectores que estén disponibles en ese momento. Si un lector se une más tarde, ese dato ya no existe. En el extremo opuesto, si ponemos PERSISTENT, el dato se almacena. Si un lector se une más tarde, el dato todavía está disponible.

Una vez definida la calidad de servicio, sólo nos queda crear el Topic llamando a participant.create_topic() pasando como parámetros:

  • Un nombre de Topic.
  • El nombre con el que registramos el tipo en su momento.
  • La calidad de servicio para el topic, que acabmos de crear
  • Un listener para enterarnos de eventos en el Topic, como errores en el intento de creación del Topic. Pasamos null, porque de momento no tenemos interés.
  • Una máscara con el tipo de eventos para los que tenemos interés en el Listener. Como no hemos pasado Listener, no tenemos interés en ningún evento.

Crear un Publisher[editar]

Vamos ahora a crear un Publisher, a partir del Participant. El código puede ser como el siguiente

    private Publisher publisher;
    private final String partitionName = new String("AnStructPartition");
    ...
    private void createPublisher() {

        PublisherQosHolder publisherQosHolder = new PublisherQosHolder();
        participant.get_default_publisher_qos(publisherQosHolder);
        publisherQosHolder.value.partition.name = new String[1];
        publisherQosHolder.value.partition.name[0] = partitionName;

        publisher = participant.create_publisher(
                publisherQosHolder.value, null, STATUS_MASK_NONE.value);
        System.out.println("registrado publisher");
    }

Creamos una instancia de la calidad de servicio para un Publisher, es decir, una instancia de PublisherQosHolder. Rellenamos dicha instancia con los valores por defecto para un Publisher, usando el método participant.get_default_publisher_qos(). Cambiamos lo que nos interese, en concreto, ponemos un nombre de partición.

Comentamos antes que en DDS podemos crear dominios, de forma que aplicaciones que no tienen nada que ver las unas con las otras puedan compartir un mismo DDS, estando los datos de cada dominio totalmente aislados de los demás. Además del dominio, los datos se pueden partir en particiones. Dentro de un dominio, pueden crearse particiones de forma que una misma aplicación pueda a su vez repartir los datos en grupos más o menos aislados entre sí o al menos, que estén relacionados entre sí. Por ejemplo, una aplicación de ventas puede tener una partición para productos y stock, otra para clientes, etc.

Cuando creamos un Publisher podemos decir a qué particiones tiene acceso. En el campo ublisherQosHolder.value.partition.name podemos poner un array de String, en el que cada String es el nombre de una partición a la que el Publisher tiene acceso. En este caso, creamos un array con una sola posición y ponemos un nombre cualquiera.

Ya solo queda crear el Publisher, llamando a participant.create_publisher(). Como parámetros:

  • La calidad de servicio del Publisher, en el que además hemos puesto el nombre de la partición.
  • Un listener con el que podemos enterarnos de diversos errores y eventos del Publisher. De momento no tenemos interés, así que null.
  • Una máscara que indica en qué eventos tenemos interés en el Listener. No queremos ninguno entre otras cosas porque no hemos puesto Listener.

Crear un Writer[editar]

Ahora, a partir del Publisher y para cada Topic que nos interese, podemos crear un Writer para poder meter datos en ese Topic. El código puede ser como el siguiente

    private AnStructDataWriter anStructDataWriter;
    ...
    private void createWriter() {

        DataWriter dataWriter = publisher.create_datawriter(
                anStructTopic,
                DATAWRITER_QOS_USE_TOPIC_QOS.value,
                null,
                STATUS_MASK_NONE.value);

        anStructDataWriter = AnStructDataWriterHelper.narrow(dataWriter);
    }

Sólo tenemos que llamar al método create_datawriter() del publisher pasando los siguientes parámetros:

  • El topic
  • La calidad de servicio deseada. Para no complicarnos la vida pasamos una constante que indica que se use la que tenga el Topic.
  • Un listener para enterarnos de eventos en el DataWriter. Pasamos null dado nuestro interés nulo.
  • Una máscara, indicando en qué eventos tenemos interés, que es en ninguno.

AnStructDataWriterHelper es una de las clases creada por la herramienta idlpp a partir de nuestro idl. La llamada narrow() sólo hace un cast del DataWriter obtenido a AnStructDataWriter, clase también generada por idlpp y que nos resultará más cómoda de usar para escribir datos con ella.

Crear un Subscriber[editar]

Para crear un Subscriber el código puede ser como el siguiente

    private Subscriber subscriber;
    ...
    private void createSubscriber() {
        SubscriberQosHolder subQos = new SubscriberQosHolder();
        participant.get_default_subscriber_qos(subQos);
        subQos.value.partition.name = new String[1];
        subQos.value.partition.name[0] = partitionName;

        subscriber = participant.create_subscriber(
                subQos.value, null, STATUS_MASK_NONE.value);
    }

Igual que con el Publisher, creamos su calidad de servicio, la rellenamos con el valor por defecto para subscriber a partir del Participant y le ponemos el nombre de la partición. Es importante que el nombre de la partición coincida con el nombre que pusimos en el Publisher, porque si no uno escribirá en una partición y el otro leerá de otra, por lo que no se verán nunca aunque el Topic sea el mismo.

Para crear el subscriber se llama a participant.create_subscriber() y como parámetros, lo mismo que con el Publisher, lo merece la pena explicarlo otra vez.

Crear un Reader[editar]

A partir del Subscriber, se crean los Reader que queramos para todos los Topic que queramos. El código puede ser tal que así

    private AnStructDataReader reader;
    ...
    private void createReader() {
        DataReader datareader = subscriber.create_datareader(
                anStructTopic,
                DATAREADER_QOS_USE_TOPIC_QOS.value,
                null,
                STATUS_MASK_NONE.value);

        reader = AnStructDataReaderHelper.narrow(datareader);
    }

Similar al Writer, se llama al método subscriber.create_datareader() y se le pasan como parámetro el Topic, la calidad de servicio deseada para el Reader que ponemos el valor que tenga el Topic, un listener null porque no tenemos interés en ninguno de los eventos que nos pueda proporcionar el Reader y una máscara indicando que no tenemos interés en ningún evento.

AnStructDataReaderHelper es una clase creada por idlpp a partir de nuestro fichero idl y su método narrow() sólo hace un cast de DataReader a AnStructDataReader, de forma que nos será más fácil usar esta clase para recoger datos del Topic.

Empezamos a leer[editar]

Ahora hacemos el código necesario para leer del Topic. El código es el siguiente

    private void startReading() {
        new Thread() {
            public void run() {

                StatusCondition statuscondition = reader.get_statuscondition();
                statuscondition.set_enabled_statuses(DATA_AVAILABLE_STATUS.value);

                WaitSet ws = new WaitSet();
                ws.attach_condition(statuscondition);

                Duration_t duration = new Duration_t();
                duration.sec = 10000;

                ConditionSeqHolder conditions = new ConditionSeqHolder();

                while (true) {
                    AnStructSeqHolder seqHolder = new AnStructSeqHolder();
                    SampleInfoSeqHolder infoSeq = new SampleInfoSeqHolder();


                    int waitStatus = ws._wait(conditions, duration);
                    if (waitStatus == RETCODE_OK.value) {

                        reader.take(
                                seqHolder,
                                infoSeq,
                                LENGTH_UNLIMITED.value,
                                ANY_SAMPLE_STATE.value,
                                ANY_VIEW_STATE.value,
                                ALIVE_INSTANCE_STATE.value);


                        if (null != seqHolder.value) {
                            for (int i = 0; i < seqHolder.value.length; i++) {
                                System.out.println(seqHolder.value[i].id);
                                System.out.println(seqHolder.value[i].aText);
                                System.out.println("-----------");
                            }
                        }
                    }


                }
            }
        }.start();
    }

Creamos un hilo de lectura y lo arrancamos.

Para leer podemos usar los métodos read() o take() del Reader. El primero lee el dato, pero no lo retira. En la siguiente lectura, seguirá estando. El método take() lo retira, así que en la siguiente lectura no estará disponible si nadie vuelve a ponerlo.

Como los métodos read() y take() no se quedan bloqueados en espera del dato, vamos a programar un bloqueo en espera de datos. DDS nos ofrece la clase WaitSet junto con otras auxiliares para programar un bloque hasta que haya datos disponibles. WaitSet necesita dos cosas: Una condición para saber cuando desbloquearse y un tiempo de espera máximo para desbloquearse aunque no ocurra la condición.

La condición de que el Reader tenga datos disponibles se consigue con el siguiente trozo de código

StatusCondition statuscondition = reader.get_statuscondition();
statuscondition.set_enabled_statuses(DATA_AVAILABLE_STATUS.value);

El timeout para desbloqueo si no llegan datos no es más que una instancia de Duration_t, que rellenamos con 10 segundos (10000 ms)

Duration_t duration = new Duration_t();
duration.sec = 10000;

Solo nos queda instanciar WaitSet, asociarle la condición y llamarlo

WaitSet ws = new WaitSet();
ws.attach_condition(statuscondition);
...
ws._wait(conditions, duration);

_wait() devolverá DDS.RETCODE_OK si hay datos o DDS.RETCODE_TIMEOUT si salta por Timeout. Si obtenemos RETCODE_OK, podemos leer datos del reader con read() o take().

Para leer datos, necesitamos crear una instancia de AnStructSeqHolder y otra de SampleInfoSeqHolder. Las llamadas a take() o read() se encargarán de rellenar esas estructuras. En la primera, AnStructSeqHolder meterá los datos, en la segunda SampleInfoSeqHolder meterá info asociada a esos datos, como el timestamp en que ese dato se ha creado o modificado en DDS. La línea de código es esta

reader.take(
   seqHolder,
   infoSeq,
   LENGTH_UNLIMITED.value,
   ANY_SAMPLE_STATE.value,
   ANY_VIEW_STATE.value,
   ALIVE_INSTANCE_STATE.value);

Pasamos el seqHolder para que meta dentro los datos leídos y el infoSeq para que llene la información adicional de esos datos como timestamp en que se ha recibido. El resto de campos son filtros para que nos diga qué tipo de datos queremos recibir.

  • Cuántos datos queremos recibir como máximo. LENGHT_UNLIMITED es que queremos todos los que haya disponibles.
  • Estado de los datos deseados. El estado es si este Reader ya ha accedido o no a esos datos. Al poner ANY_SAMPLE_STATE estamos indicando que queremos los datos, hayamos accedido o no antes a ellos. Podríamos poner también DDS_READ_SAMPLE_STATE o bien DDS_NOT_READ_SAMPLE_STATE.
  • Estado de la vista. Indica si el dato es la primera vez que aparece en el sistema o no. Hemos puesto que queremos cualquier dato, sea o no la primera vez que aparece en el sistema. Otros posibles valores son DDS_NEW_VIEW_STATE o bien DDS_NOT_NEW_VIEW_STATE. ¿Cuándo aparece un dato por primera vez en el sistema?. Cada dato vimos en el fichero idl que podemos ponerle un id. La primera vez que aparece un id nuevo, será un new view state. Una vez leído, si el dato se modifica pero mantiene el id, ya no será new view state, pero sí DDS_NOT_READ_SAMPLE_STATE. Esto es útil para que nuestra aplicación haga algo la primera vez y sólo la primera vez que un nuevo dato (una nueva entidad) entra en nuestro sitema.
  • El estado de la instancia, que hemos puesto sólo instancias vivas. Otros estados posibles son ALIVE_INSTANCE_STATE, NOT_ALIVE_DISPOSED_INSTANCE_STATE, NOT_ALIVE_y NO_WRITERS_INSTANCE_STATE. El dato puede tener writers activos, o puede no tener writers activos o puede tener writers activos uno de los cuales ha borrado el dato con un dispose(). Podemos querer datos en los que no hay writers activos o que los writers han liberado dichos datos, pero debemos ser conscientes de que esos datos pueden ser antiguos, o no tener ya sentido.

Empezamos a Escribir[editar]

Con el Writer podemos escribir en el Topic. El código es como este

    private void startWriting() throws InterruptedException {
        AnStruct data = new AnStruct();
        int counter=1;
        data.id = counter;
        data.aText = "text" + data.id;

        while (true) {
            anStructDataWriter.write(data, HANDLE_NIL.value);
            data.id = counter%5;
            counter++;
            data.aText = "text" + counter;
            Thread.sleep(500);
            System.out.println("va");
        }
    }

En esta ocasión no hemos hecho un hilo, no hay ningún motivo especial. Solo que como en nuestro programa principal llamamos primero a startReadin() y luego a startWritting(), para que la ejecución no se quede bloqueada en startReading(), ahí lanzamos un hilo. Como no llamamos a nada detrás de startWritting(), no nos hace falta el hilo.

Simplemente rellenamos la estructura, teniendo en cuenta que el id hace de clave y si repetimos id se sobre escribe en DDS la estructura. Luego llamamos a anStructDataWriter.write() pasando el dato a escribir y un handle, que para no complicarnos la vida será HANDLE_NIL.

¿Qué es eso del handle?. Es algo totalmente opcional, funciona perfectamente si ponemos siempre HANDLE_NIL, pero podemos hacerlo más eficiente si lo usamos correctamente. Veamos cómo.

Los datos AnStruct llevan una clave id. Imaginemos que se crea un dato con una clave de valor 33, que ese dato durante su vida va a verse modificado y que finalmente dejamos de modificarlo. Para hacer el proceso de escritura más eficiente, podemos:

  • En el momento de crear el dato, registrarlo en DDS con su clave y obtenemos a cambio un handle (identificador) para ese dato.
  • Cada vez que queramos escribir ese dato en DDS (crearlo, modificarlo o borrarlo) podemos usar la operación correspondiente usando ese handle. Esto ayuda a DDS a hacer más efciente la operación.
  • Cuando ya no vayamos a actualizar más el dato, basta con des-registrarlo. Esto dirá a los Reader que ese dato ya no tiene un Writer activo y que no espera más cambios en ese dato.

El código, con todo este registro, sería:

// Creacion de una estructura con sus datos
AnStruct data = new AnStruct();
data.id=11;
data.aText="text"+11;

// Se registra
long handle = anStructDataWriter.register_instance(data);

// se modifica todas las veces que se quiera
anStructDataWriter.write(data, handle);

// Y se des-registra cuando ya no la vayamos a modificar mas
anStructDataWriter.unregister_instance(data, handle);