diff options
author | Magnus Feuer <mfeuer@jaguarlandrover.com> | 2015-02-12 11:32:59 -0800 |
---|---|---|
committer | Magnus Feuer <mfeuer@jaguarlandrover.com> | 2015-02-12 11:32:59 -0800 |
commit | c3804481dc7f615584f74fb94626b7f900a4ce24 (patch) | |
tree | 059c9f191d08d85e8399f7ecac41508351f31818 /python | |
parent | 8586ccb7554c19b6ebe8eadf4fd622b68f5fced7 (diff) | |
download | rvi_core-c3804481dc7f615584f74fb94626b7f900a4ce24.tar.gz |
Added simplified RVI integration
Diffstat (limited to 'python')
-rw-r--r-- | python/README.md | 48 | ||||
-rwxr-xr-x | python/rvi_call.py | 67 | ||||
-rwxr-xr-x | python/rvi_service.py | 99 | ||||
-rw-r--r-- | python/rvilib.py | 144 |
4 files changed, 358 insertions, 0 deletions
diff --git a/python/README.md b/python/README.md new file mode 100644 index 0000000..a768d1f --- /dev/null +++ b/python/README.md @@ -0,0 +1,48 @@ +Copyright (C) 2014, Jaguar Land Rover + +This document is licensed under Creative Commons +Attribution-ShareAlike 4.0 International. + + +# RVI PYTHON INTEGRATION + +This directory contains the ```rvilib.py``` file necessary to have +your python code send and receive RVI service invocations. + +Also included are two test programs that use ```rvilib.py``` : + +1. ```rvi_service.py```<br> +Registers a single service with an RVI node and then prints out +a message when that service is invoked. + +2. ```rvi_call.py```<br> +Invokes a service registered in an RVI network. + +These two commands can be used to quickly test connectivity between +various RVI nodes. + +# SETUP +In order for ```rvilib.py``` to work, it needs jsonrplib installed from + +[https://github.com/joshmarshall/jsonrpclib](https://github.com/joshmarshall/jsonrpclib). + +Install by using one of the following commands: + + easy_install jsonrpclib + +or + + pip install jsonrpclib + + +You can use rvilib.py by simply copying it into your project. + +# CALLING A SERVICE + +See ```rvi_call.py``` for how to invoke RVI-based services. + +# IMPLEMENTING A SERVICE + +See ```rvi_service.py``` for how to implement and register RVI-based services. + + diff --git a/python/rvi_call.py b/python/rvi_call.py new file mode 100755 index 0000000..015f590 --- /dev/null +++ b/python/rvi_call.py @@ -0,0 +1,67 @@ +#!/usr/bin/python + +# +# Copyright (C) 2014, Jaguar Land Rover +# +# This program is licensed under the terms and conditions of the +# Mozilla Public License, version 2.0. The full text of the +# Mozilla Public License is at https://www.mozilla.org/MPL/2.0/ +# +# +# Simple RVI service caller +# + +import sys +from rvilib import RVI +import threading +import time + +def usage(): + print "Usage:", sys.argv[0], " RVI-node service key=val ..." + print " RVI-node DNS name or IP of host running RVI" + print " service Service to invoke in RVI." + print " key=val Named arguments to provide to service." + print + print "Example: ./callrvi.py http://rvi1.nginfotpdx.net:8801 \\" + print " jlr.com/vin/aaron/4711/test/ping \\" + print " arg1=val1 arg2=val2" + + sys.exit(255) + + +# +# Check that we have the correct arguments +# +if len(sys.argv) <3: + usage() + +progname = sys.argv[0] +rvi_node = sys.argv[1] +service = sys.argv[2] +args = [] +i=3 +while i < len(sys.argv): + print sys.argv[i] + [k, v] = sys.argv[i].split('=') + args = args + [{ k: v}] + i = i + 1 + + + +# +# Setup an outbound JSON-RPC connection to the backend RVI node +# Service Edge. +# +rvi = RVI(rvi_node) + + +print "RVI Node: ", rvi_node +print "Service: ", service +print "args: ", args + +rvi.message(service, args) + + + + + diff --git a/python/rvi_service.py b/python/rvi_service.py new file mode 100755 index 0000000..3c08417 --- /dev/null +++ b/python/rvi_service.py @@ -0,0 +1,99 @@ +#!/usr/bin/python + +# +# Copyright (C) 2015, Jaguar Land Rover +# +# This program is licensed under the terms and conditions of the +# Mozilla Public License, version 2.0. The full text of the +# Mozilla Public License is at https://www.mozilla.org/MPL/2.0/ +# +# +# Register a service specified by command line with an RVI node. +# Print out a message when the service gets invoked. +# +import sys +from rvilib import RVI + +def usage(): + print "Usage:", sys.argv[0], "<rvi_url> <service_name>" + print " <rvi_url> URL of Service Edge on a local RVI node" + print " <service_name> URL of Service to register" + print + print "The RVI Service Edge URL can be found in" + print "[backend,vehicle].config as" + print "env -> rvi -> components -> service_edge -> url" + print + print "The Service Edge URL is also logged as a notice when the" + print "RVI node is started." + print + print "Example: ./rvi_service.py http://rvi1.nginfotpdx.net:8801 /test/some_service" + sys.exit(255) + + +# +# Our general handler, registered with rvi.register_service() below. +# +# You can also explicitly name the arguments, but then +# the sender has to match the argument names. + +# For example: +# rvi_call.py http://localhost:8801 jlr.com/vin/test a=1 b=2 c=3 -> +# def service(a,b,c) +# +def service_invoked(**args): + print + print "Service invoked!" + print "args:", args + print + sys.stdout.write("Press enter to quit: ") + sys.stdout.flush() + return ['ok'] + +def services_available(services): + print + print "Services available: ", services + sys.stdout.write("Press enter to quit: ") + sys.stdout.flush() + + return ['ok'] + +def services_unavailable(services): + print + print "Services not available: ", services + + sys.stdout.write("Press enter to quit: ") + sys.stdout.flush() + return ['ok'] + +if len(sys.argv) != 3: + usage() + +# Grab the URL to use +[ progname, rvi_node_url, service_name ] = sys.argv + + +# Setup a connection to the local RVI node +rvi = RVI(rvi_node_url) + +# Starting the thread that handles incoming calls is +# not really necessary since register_service will do it for us. + +rvi.start_serve_thread() + +# Register our service and invoke 'service_invoked' if we +# get an incoming JSON-RPC call to it from the RVI node +# +full_service_name = rvi.register_service(service_name, service_invoked) + +# Tie callbacks to be invoked as the RVI node reports to us +# about other services being available. +rvi.set_services_available_callback(services_available) +rvi.set_services_unavailable_callback(services_unavailable) + +print "RVI General Service." +print "RVI node URL: ", rvi_node_url +print "Service: ", full_service_name + +raw_input('Press enter to quit: ') +rvi.shutdown() +sys.exit(0) diff --git a/python/rvilib.py b/python/rvilib.py new file mode 100644 index 0000000..700e032 --- /dev/null +++ b/python/rvilib.py @@ -0,0 +1,144 @@ +# +# Copyright (C) 2014, Jaguar Land Rover +# +# This program is licensed under the terms and conditions of the +# Mozilla Public License, version 2.0. The full text of the +# Mozilla Public License is at https://www.mozilla.org/MPL/2.0/ +# +# rbilib.py 0.3.0 +# +# This moduke +from jsonrpclib.SimpleJSONRPCServer import SimpleJSONRPCServer +from jsonrpclib import Server +import random +import time +import threading + +class RVI(SimpleJSONRPCServer): + # address is either localhost or the ip address of self. + # 0.0.0.0 should work to listen to all addresses, but haven't been tested + # Port is either a fixed port (integer) or a tuple with a start and stop + # port that should be used as a random interval to pick the port from + def __init__(self, rvi_node_url, address='localhost', port=(20001,59999)): + if isinstance(port, tuple) == True: + (start_port, stop_port) = port + self.rvi_port = random.randint(start_port, stop_port) + else: + self.rvi_port = port + + self.rvi_address = address + print self.rvi_address + print self.rvi_port + SimpleJSONRPCServer.__init__(self,addr=((self.rvi_address, self.rvi_port)), logRequests=False) + self.rvi_client = Server(rvi_node_url) + self.serve_thread = False + + # Set the callback to invoke when RVI reports that one or more new + # services are available for invocation somewhere in the network. + # Arguments will be an array of fully qualified service available. + def set_services_available_callback(self, function): + self.register_function(function, 'services_available') + + # Set the callback to invoke when RVI reports that one or more previously + # available services are no longer available for invocation + # + # Arguments will be an array of fully qualified service names no longer available. + def set_services_unavailable_callback(self, function): + self.register_function(function, 'services_unavailable') + + # Register a service in this python program that should + # be reached from the rest of the network. + # + # service_name is the local service name ('/my_stuff/set_fan_speed') + # + # function is a python function reference to invoke when an + # JSON-RPC call for service_nameis received from RVI. + # + # This function will return the fully qualified name of the registered service. + # + # Thus, if you call: + # + # register_service('/my_stuff/set_fan_speed', set_fan_speed) + # + # you will get back the fully qualified service name assigned: + # + # "jlr.com/vin/YV1TS32G54F123456/my_stuff/set_fan_speed" + # + # This is the globally accessible name for the service that can be used anywhere + # in an RVI network to access your service. + # + def register_service(self, service_name, function): + # Register service_name within SimpleJSONRPCServer so that + # when it gets invoked with the given URL suffic, it will call 'function'. + # + # I.e. when self.url() + service_name gets called by the RVI node, we + # will dispatch that call to 'function'. + # + print "Will register {} to function {}".format(service_name, function) + self.register_function(function, service_name) + + # Register the service name with the RVI node so that we get called + # at the URL we are listening on (self.url()). + # + res = self.rvi_client.register_service(service=service_name, network_address=self.url()) + + # Check if we have a thread going + if self.serve_thread == False: + print "No thread started, will do so now" + self.start_serve_thread() + + + # Return the fully qualified service name + return res['service'] + + def start_serve_thread(self): + self.serve_thread = threading.Thread(target=self.serve_forever) + self.serve_thread.start() + + # + # Return the URL that we've setup for incoming JSON-RPC calls + # from the RVI node. + # + def url(self): + return 'http://' + self.rvi_address + ':' + str(self.rvi_port) + + + # + # Send a message to the RVI node to be forwarded to the + # service that registered 'service_name' in the network. + # + def message(self, service_name, parameters, timeout = int(time.time()) + 60 ): + print "message({}, {}, {})".format(service_name, parameters, timeout) + self.rvi_client.message(calling_service= "not_used", + service_name = service_name, + timeout = timeout, + parameters = parameters) + + # + # Check if method is 'message', if so dispatch on + # name 'service_name' instead. + # + def _dispatch(self, method, params): + if method == 'message': + print "Will dispatch message to: " + params['service_name'] + dict_param = {} + # Extract the 'parameters' element from the top level JSON-RPC + # 'param'. + # Convert 'parameters' from [{'vin': 1234}, {hello: 'world'}] to + # a regular dictionary: {'vin': 1234, hello: 'world'} + + # print "Parameters:", params['parameters'] + msg_params = params['parameters'] + for i in range(0, len(msg_params)): + for j in range(0, len(msg_params[i].keys())): + print "params", msg_params[i].keys()[j], "=", msg_params[i].values()[j] + dict_param[msg_params[i].keys()[j]] = msg_params[i].values()[j] + + # print "Parameter disctionary: ", dict_param + # print + # Ship the processed dispatch info upward. + return SimpleJSONRPCServer._dispatch(self, params['service_name'], dict_param) + + # Fallthrough to all other methods. + # Will handle service_re3 + return SimpleJSONRPCServer._dispatch(self,method, params) |