Recientemente he tenido que impartir un curso sobre Robotics Studio en el Innovation Center de Mondragón, centro de innovación dedicado a tecnologías embebidas. Robotics Studio MSRS, es un herramienta estupenda para que profesionales y entusiastas empiecen a programar aplicaciones robóticas.
Sin entrar a valorar mucho lo que es una aplicación robótica, diré que, básicamente es lo mismo que cualquier software que construimos en nuestro pc. Entrada de datos, que se procesan de alguna manera y se devuelve de otra manera. Como siempre se nos ha dicho el desarrollo de software es como una caja negra que procesa datos. Pues en las aplicaciones robóticas, ocurre lo mismo, salvo que los datos vienen de los diferentes sensores que el robot incorpora. Así que básicamente nos dedicamos a trabajar con esos datos provenientes del robot y a hacer algo con ellos.
Si bien es cierto que el desarrollo de software ha cambiado mucho desde sus inicios, podemos decir que Robotics Studio, representa esa evolución que el desarrollo de software tuvo con respecto al desarrollo de aplicaciones. Decir que Robotics Studio está pensado para orquestar todos esos datos provenientes del robot desde el PC, aunque también se puede “orquestar” desde un móvil. MSRS está diseñado con .NET así que podemos usar cualquiera de nuestro lenguaje favorito de .NET para programar aplicaciones robóticas.
Robotics Studio está compuesto básicamente de dos componentes principales, CCR (Concurrency and Coordinator runtime) y DSS (Descentrailized Software Services)
Como se empieza con el SDK?
CCR
Pues bien, hay que tener varios conceptos claros a la hora de programar aplicaciones para Robotics Studio, como se ha dicho antes una aplicación robótica recibe los datos del robot y los procesa. Ante esta sencilla frase se encuentra el porqué del modelo de desarrollo de MSRS. Un problema que nos podemos encontrar a la hora de programar estas aplicaciones es que los datos de los sensores pueden venir en cualquier momento, y todos a la vez con lo que tenemos que ser capaces de poder lidiar con esta concurrencia desde el primer momento. Pero eso no es lo único porque además de que los sensores nos pueden dar muchos valores a la vez, podemos trabajar con UI y sincronizar todos esos datos con un formulario de Windows Forms, así que, aunque la cosa parece sencilla, en breve se complica.
Para solucionar este problema de base, la concurrencia, MSRS tiene una librería que nos va a permitir programar estás aplicaciones robóticas sin tener que preocuparnos, mucho, de la concurrencia. Si lo pensamos por un momento, tenemos una cola de datos en la que el robot va insertando los datos provenientes de su sensor, y una aplicación que va sacando esos datos de la cola. Esta cola, por supuesto, ha de ser segura frente a subprocesos y concurrente. Para ello disponemos, en MSRS, de una clase muy especial, Port<T>, que nos permite crear ese puerto de comunicación entre un dispositivo y la aplicación. Esta clase Port<T> que es genérica, permite que podamos definir el tipo de datos proveniente de nuestro sensor de manera personalizada.
Una vez que ya tenemos el puerto definido en cuanto al tipo de dato que va a procesar tenemos que ser capaces de procesar de alguna manera y de forma individual los valores que se van introduciendo en el puerto. Como he dicho anteriormente, MSRS tiene en mente constantemente el desarrollo de aplicaciones concurrentes, así que para poder procesar esas tareas tenemos que tener nuestro propio ThreadPool.
Dentro de MSRS, tenemos dos clases especiales, Dispatcher y DispatcherQueue, que nos permite crear un dispatcher para generar Threads trabajadores que proceses las tareas provenientes de los Port<T> y DispatcherQueue que nos va a permitir que podamos encolar las tareas de procesamiento de los puertos, pudiendo así procesar desde varios puertos en la misma cola. Cada tarea individual se procesa en un Thread diferente para que así la aplicación sea concurrente. Estas dos clases son las clases que MSRS nos provee para que nosotros podamos escribir el procesamiento de las tareas. Podemos configurar el número de Threads que queremos por Dispatcher y puede haber más de un Dispatcher por proceso y dominio de aplicación.
Pues bien una vez que tenemos definido como se van a procesar las tareas, tenemos que sacar los elementos de los Port<T> y especificar el delegado que será el que se encargue de procesar los elementos de ese puerto. Para esta tarea tenemos los Arbiters, que es una clase estática que permite crear un proceso automático para extraer los valores del Port<T> y procesarlos. Aquí tenemos un ejemplo completo.
public void SimplePort() { Port<string> puerto = new Port<string>(); Dispatcher d = new Dispatcher(0, ThreadPriority.Normal, DispatcherOptions.UseBackgroundThreads, ApartmentState.STA, "ejemplo"); DispatcherQueue q = new DispatcherQueue("cola", d); Arbiter.Activate(q, Arbiter.Receive<string>(true, puerto, delegate(string item) { Console.WriteLine(item); } )); for (int x = 0; x < 1000; x++) { puerto.Post(DateTime.Now.ToString()); } }
Si nos fijamos con la clase Arbiter, activamos el procesamiento en el DispatcherQueue (q), los elementos que se van recibiendo del Port<T> puerto. Además de especificar con un delegado anónimo el método que procesará los mensajes recibidos en el Port<T>.
Este es el modo sencillo y automático de procesar las tareas restantes del puerto, sin nos fijamos un poco más abajo podemos encontrar como se postean los mensajes en el Port<T> y esos mensajes se van procesando de manera asíncrona.
Podemos tener varios DispatcherQueue en un Dispatcher, y el motivo es que podemos querer diferentes tipos de retención de mensajes en nuestra cola. Imaginemos por un momento que estamos procesando imágenes procedentes de la cámara del robot, pero por algún motivo tenemos 50 imágenes restantes por procesar, por motivos de rendimiento solo nos puede interesar procesar las últimas 25 imágenes recibidas y que las que se quedaron atrás en el tiempo simplemente que se descarten.
Al final la clase Arbiter a lo que se dedica es a procesar Tareas en los Dispatcher, tareas que están definidas en la interfaz ITask. Está interfaz que nosotros podemos implementar de alguna manera para poder definir nuestras propias tareas, es la clase Arbiter la que se encarga de exponer esa funcionalidad de Tareas de una manera más comida para el programador sin tener que estár constantemente implementando la interfaz.
De hecho podemos utilizar los Dispatcher sin necesidad de Port<T> simplemente como un procesador de tareas. En este ejemplo de código lo podemos ver.
void RunFromHandler() { Dispatcher d = new Dispatcher(2, "Test Pool"); DispatcherQueue q = new DispatcherQueue("Test Queue", d); ITask[] t = new ITask[100000]; for (int x = 0; x < t.Length; x++) { t[x] = Arbiter.FromHandler(SimpleHandler); } Arbiter.Activate(q, t); } void SimpleHandler() { Console.WriteLine("Start Simple Handler Thread {0}", Thread.CurrentThread.ManagedThreadId); for (int i = 0; i < 10; i++) { Wait(10); Console.Write(i + " "); } Console.WriteLine("Finished Simple Handler Thread {0}", Thread.CurrentThread.ManagedThreadId); }
Esto es una breve introducción del CCR que seguiré expandiendo en otros artículos, mostrado como se pueden hacer composiciones de tareas más complejas y como este CCR se puede usar en aplicaciones que no sean robóticas ni dentro del MSRS en donde la concurrencia es un problema desde el inicio del proyecto.
Excelente post … por fin alguien se anima a escribir sobre Robotics Studio :D, seguiré con interés estos posts
Saludos
Muchas gracias !!
llevo tiempo pensando en comprarme un lego mindstorm!
Creo que al final voy a realizar la inversion
Saludos!