Este post detalla cómo llevar a cabo la implementación de un servicio Dharma sobre Ice. Para simplificar la exposición se ha utilizado un dummy de tipo DUO, para centrarnos aquí en cómo ofrecer ese servicio ya creado como un servicio Dharma.
En el siguiente enlace http://arco.esi.uclm.es/public/video/tutorials/dharma-service.mp4 encontraréis un vídeo donde se detallan los pasos a seguir para la ejecución de este ejemplo completo.
Interfaz Slice
El módulo propuesto implementa el modelo semántico propuesto en [1] y descrito en El modelo semántico de Dharma. El código está disponible en el repositorio de Dharma y también es paquete Debian, con el nombre dharma.
#include <PropertyService/PropertyService.ice> module Semantic { interface EventSink { void report(string sourceid, string magnitude, PropertyService::Thing value, PropertyService::ThingDict metadata); }; interface Device extends PropertyService::PropertySet {}; interface DeviceFactory { Device* make(string role); }; interface Service { void performAction(string action, PropertyService::ThingDict params, EventSink* callback); }; interface ServiceLocator { Service* lookup(string action, PropertyService::ThingDict params); }; interface SconeService { string sconeRequest(string request); }; sequence<PropertyService::TypeCode> TypeCodeSeq; sequence<string> StringSeq; interface TypeMapper{ TypeCodeSeq getTypes(StringSeq names); PropertyService::TypeCode getType(string name); }; };
Instanciación ortogonal de métodos a través del método «performAction«
Lo más relevante es el mecanismo propuesto para dar soporte a la instanciación automática de servicios, a través del método «performAction» de la interfaz «Service». Todo servicio Dharma implementará esta interfaz garantizando así que todos las acciones que ese servicio realice se instanciarán de manera ortogonal a través de este método.
Al contrario de lo que es un middleware tradicional, en el que la lista de acciones que un servicio es capaz de realizar se codifican como métodos de la interfaz, en Dharma, esas acciones están contenidas en la base de conocimiento (Scone). Es por eso que afirmamos que en Dharma, se extrae la semántica del código para llevarla a la base de conocimiento.
Búsqueda semántica de servicios que ofrecen una determinada acción
La interfaz «ServiceLocator» será la que dé soporte a la búsqueda semántica. A través de su método «lookup» se pedirá a Scone la lista de servicios que son capaces de realizar una determinada acción. Es importante hacer énfasis en que la búsqueda en Scone será una búsqueda semántica y no en base a un matching de términos. Así por ejemplo, si buscamos un servicio que ofrezca la acción «iluminar», obtendremos una lista de todos aquellos servicios que tengan un método cuyo contexto después de la acción sea el de emisión de luz, incluyendo no sólo el encendido de bombillas (acción «light-up«) sino cualquier otro como por ejemplo, encendido de un monitor, subido de persianas, etc.
Ejemplo de uso del módulo Semantic
En el repositorio de Dharma hay un directorio de ejemplos donde pueden encontrarse ejemplos de uso de esta librería, totalmente funcionales. En este post vamos a utilizar el ejemplo del semáforo «semaphore-libdharma-scone» que utiliza un dummy y por lo tanto podemos ejecutarlo sin necesidad de tener ningún hardware específico. A continuación se detallan los pasos a seguir:
1.Instalación del paquete DUO que tiene varios tipos de dummies (byte, bool, string):
$ apt-get install duo
Utilizaremos un dummy tipo string, de tal forma que podamos ver por consola la orden recibida por este dummy. Para ello, lo lanzaremos de la siguiente manera:
$ duo-dummy-string --host 127.0.0.1 --port 1234 --id Semaphore --no-gui RW
Una vez hecho esto ya tenemos el servicio listo para recibir invocaciones.
2. Registro del servicio en la base de conocimiento
El proceso de registro está bastante automatizado gracias al siguiente Makefile parametrizado:
#!/usr/bin/make -f # -*- mode: makefile -*- include arco/build.mk SCONE_ADDR = 127.0.0.1 5000 SERVICE_PROXY ?= "Semaphore -d -e 1.0:udp -h 192.168.0.20 -p 65000" SERVICE_REGISTER = $(PROJECT_DIR)/knowledge/semaphore-service-register.lisp SERVICE_KNOWLEDGE = $(PROJECT_DIR)/knowledge/semaphore-service.lisp all: register: cat $(SERVICE_KNOWLEDGE) | grep -v "^;" | grep -v "^$$" | ncat $(SCONE_ADDR) @$(PROJECT_DIR)/bin/register-service.py "$(SERVICE_REGISTER)" "$(SERVICE_PROXY)"
Como se puede observar, el proceso de registro se redirige a dos archivos de configuración: «semaphore-service-register.lisp» y «semaphore-service.lisp«. El primero de ellos es el encargado de registrar el servicio en Scone,
(in-context {dharma}) (new-indv {semaphore-service-instance} {semaphore-service}) (x-is-the-y-of-z {$proxy} {offered-service} {semaphore-service-instance})
Para ello, utilizando el lenguaje de Scone, indicamos que el servicio «semaphore-service-instance» es un servicio de tipo «semaphore-service» y que además está disponible en el proxy SERVICE_PROXY «Semaphore -d -e 1.0:udp -h 192.168.0.20 -p 65000» (parámetro del Makefile). El segundo archivo de configuración tiene toda la información semántica relativa a un semáforo, incluyendo las acciones que éste realiza.
3. Cliente para testear el funcionamiento del servicio Dharma
Nada se ha comentado aún acerca de los detalles de implementación del duo-dummy-service, como por ejemplo, cómo instanciar los métodos que ofrece. Esto es lo realmente interesante del modelo propuesto en Dharma.
Sólo necesitamos conocer las acciones que este ofrece. Como semáforo que es, este ofrecerá acciones que nos permitirán poner el semáforo a un color o, expresarlo de otra manera como por ejemplo, dar paso, precaución o parar. En nuestro cliente, buscaremos por lo tanto un servicio que ofrezca la acción «set-semaphore-color» y esto lo haremos de la siguiente manera:
service = broker.lookup_service("set-semaphore-color")
En este ejemplo simplificado suponemos que sólo devuelve un servicio (en el futuro veremos como filtrar los resultados por localización u otro tipo de propiedades). Ese servicio puede ahora ejecutar la acción «poner semáforo en verde» a través del método genérico de instanciación «performAction«:
service.performAction(args.action, {'color': thing.from_str("green")}, None)
Como se puede observar, el método «performAction» recibe como primer argumento la acción a ejecutar y como segundo la lista de propiedades, a modo de diccionario «nombre-de-propiedad: valor», como en este caso «color: verde». El tercer argumento, que en este ejemplo no es necesario, es un canal en el que el servicio podría publicar la información generada, para el caso en el que el servicio debiera devolver algún tipo de información.
Este ejemplo se puede hacer para que reciba por línea de comandos la acción y el color:
#!/usr/bin/env python # -*- coding: utf-8; mode: python; -*- import sys import thing from dharma import Broker from argparse import ArgumentParser class Client(object): def run(self, args): broker = Broker(args) args = self.get_args(broker) service = broker.lookup_service(args.action) service.performAction(args.action, {'color': thing.from_str(args.color)}, None) def get_args(self, broker): args = broker.args parser = ArgumentParser() parser.add_argument("-a", help="set action to send", required=True, dest="action", choices=["set-semaphore-color", "get-semaphore-state"]) parser.add_argument("-c", help="if action is set-semaphore-color, set color", default="red", dest="color", choices=["red", "orange", "green"]) args = parser.parse_args(args[1:]) return args if __name__ == '__main__': sys.exit(Client().run(sys.argv))
4. Otras cuestiones a tener en cuenta
Como el servicio duo-dummy-string no implementa la interfaz service, es necesario hacer un wrapper de las invocaciones Dharma a DUO.
class DharmaDUOWrapper(object): def __init__(self, proxy, ids): self.proxy = proxy self.ids = ids def performAction(self, action, params, sink): if "::DUO::IBool::W" in self.ids: proxy = DUO.IBool.WPrx.uncheckedCast(self.proxy) value = action_to_bool(action) proxy.set(value, Ice.Identity()) return if "::DUO::IString::W" in self.ids: proxy = DUO.IString.WPrx.uncheckedCast(self.proxy) value = params['color'].value proxy.set(value, Ice.Identity()) return raise NotImplementedError
Sólo los dummies booleanos y string han sido considerados. En un futuro, si algún otro tipo fuera necesario, debería completarse el método.
La clase Broker deberá también completarse para utilizar el wrapper. Para ello, una vez obtenido el servicio en base a su ID, en el método «get_service_by_ids«, si éste fuera un servicio DUO, se devolvería un objeto de tipo «DharmaDUOWrapper«.
def get_service_by_ids(self, service, ids): if not is_duo_service(ids): raise UnsupportedService(service) return DharmaDUOWrapper(service, ids)
REFERENCIAS
[1] Maria J. Santofimia, Scott E. Fahlman, Xavier del Toro, Francisco Moya, Juan Carlos López:
A semantic model for actions and events in ambient intelligence. Eng. Appl. of AI 24(8): 1432-1445 (2011) https://arco.esi.uclm.es/public/papers/2011-EAAI.pdf