diff options
-rw-r--r-- | TODO | 6 | ||||
-rw-r--r-- | curl_scripts/device_message.sh | 5 | ||||
-rw-r--r-- | hvac_demo/README.md | 11 | ||||
-rwxr-xr-x | hvac_demo/hvac_emulator.py | 237 | ||||
-rwxr-xr-x | hvac_demo/hvac_subscription_service.py | 99 | ||||
-rw-r--r-- | hvac_demo/rvi_json_rpc_server.py | 33 | ||||
-rw-r--r-- | priv/setup_device.config | 3 |
7 files changed, 391 insertions, 3 deletions
@@ -0,0 +1,6 @@ +In doc and code, convert JSON-RPC param "parameters" to "arguments" in order to avoid + confusion with JSON-RPC "params" + +In doc and code, rename "target" "service" or potentially "target service" + + diff --git a/curl_scripts/device_message.sh b/curl_scripts/device_message.sh index 81c144c..6a28cdd 100644 --- a/curl_scripts/device_message.sh +++ b/curl_scripts/device_message.sh @@ -20,10 +20,11 @@ curl -u $USER_AUTH -k -X POST $URL -d @- << EOF "params": { "calling_service": "hvac_app", - "target": "jlr.com/backend/hvac/publish_fan_speed", + "target": "jlr.com/backend/hvac/subscribe", "timeout": 1405099531, "parameters": [ - { "speed": 5 } + { "vin": 1234 }, + { "subscribing_service": "jlr.com/vin/1234/hvac/updates" } ] } } diff --git a/hvac_demo/README.md b/hvac_demo/README.md new file mode 100644 index 0000000..01821d2 --- /dev/null +++ b/hvac_demo/README.md @@ -0,0 +1,11 @@ +The HVAC service is a test service that registers with the backend server to handle a very simple pub/sub setup. + +Accepted commands are: + +subscribe(vin, subscriber) -> Any updates to the given vin should be sent as publish command to the given subscriber (a service running on a device). +unsubscribe(vin, subscriber) -> Remove subscription conenction between subscriber and vin. + +publish(vin, key, value) -> A given vin is updating a key with a new value. All subscribers to the vin will be notified. + +Dependencies: +pip install jsonrpclib diff --git a/hvac_demo/hvac_emulator.py b/hvac_demo/hvac_emulator.py new file mode 100755 index 0000000..a9cc863 --- /dev/null +++ b/hvac_demo/hvac_emulator.py @@ -0,0 +1,237 @@ +#!/usr/bin/python + +# +# Emulate a mobile device or an IVI. +# +# This emulator connects to an RVI Service Edge as a service and +# takes on one of two roles: +# +# 1) mobile device +# In this mode the emulator connects to the Service Edge of the +# backend server (since P2P will be supported in RVI Project +# Milestone 2), and subscribes to HVAC updates (temp, fan speed, +# etc) updates from an IVI with a given VIN. The emulator can also +# update HVAC values, simulating HVAC GUI input on the phone +# screen, and send it off to the (backend server) Service Edge for +# further distribution to the targeted IVI. +# +# 2) IVI +# In this mode, the emulator connects to the Service Edge of a +# vehicle (device) and subscribes to updates from one or more +# mobile devices. Local updates, simulating GUI input on the +# HVAC screen, can be entered at the command line and will be +# distributed to the phone(s) subscribing to updates from +# the IVI. +# +# +# In both modes, the emulator sends its updated HVAC values to the +# service jlr.com/backend/hvac/subscription_service/publish. This +# service will look up VIN targeted by the update and send out the +# same data to all services that are subscribing to that VIN. +# +# In mobile device mode, the emulator will subscribe to updated +# values published to to the VIN value "ivi_[vin]" where vin is the +# VIN number of the IVI. When a emulator, still in mobile device +# mode, sends out an updated value (entered in the python console), +# it will publish the value using the vin "mobile_[vin]" +# +# Converesely, an emulator in IVI mode will sbuscribe to +# "mobile_[vin]", and publish to "ivi_[vin]". +# +# This setup allows the mobile device emulator to receive updates +# entered on the IVI HVAC screen (sent out by the IVI to +# "ivi_[vin]"), while the IVI emulator receives updates entered on +# the mobile device screen (sent out by the mobile device to +# "mobile_[vin]". +# +# When the emulator connects in IVI mode to a device RVI node, the +# node's configured service prefix (see priv/setup_device.config, +# node_service_prefix) will have the VIN number as a part of the +# prefix. The emulator will thus register its service as /hvac/publish, +# which, prefixed with the node service prefix, gives it a complete name +# of jlr.com/vin/[vin]/hvac/publish. +# +# In mobile device mode, the emulator connects to the backend RVI node, which +# has a service prefix of jlr.com/backend. In this mode, the emulator +# will register with a phone number as a part of its service name, where +# the phone number is specified as a command line argument to the +# emulator. +# +# Since the backend RVI node has a service prefix of jlr.com/backend +# (see priv/setup_backend.config), the mobile device emulator will +# have a complete service name of: +# jlr.com/backend/mobile/[phone_number]/hvac/publish +# +# [phone_number] is given at the command line. +# +# +# Usage: +# +# python hvac_emulator.py <rvi_url> [mobile <phone_number> | ivi] +# +# mob +# The rvi_url is the local URL of the RVI Service Edge. +# See +# +# Example: +# python hvac_emulator.pyh mobile 9491231234 http://127.0.0.1:8811 +# +# python hvac_emulator.pyh ivi saw222992212 http://127.0.0.1:8800 +# +import sys +from rvi_json_rpc_server import RVIJSONRPCServer +import jsonrpclib +import random +import threading + +# The subscriber service (MQTT server equivalent), that +# manages subscriptions and distributes received publish commands to +# the relevant subscribers. +# +SUBSCRIPTION_SERVICE_BASE='jlr.com/backend/subscription_service' +SUBSCRIBE_SERVICE=SUBSCRIPTION_SERVICE_BASE+'/subscribe' +UNSUBSCRIBE_SERVICE=SUBSCRIPTION_SERVICE_BASE+'/unsubscribe' +PUBLISH_SERVICE=SUBSCRIPTION_SERVICE_BASE+'/publish' + +# +# Publish command is invoked by the +# subscriber server above when it receives a publish command +# from a VIN that this emulator subscribes to. +# +def publish(vin, key, value): + print "Publish invoked!" + print "vin:", viny + print "key:", key + print "value:", value + return ['ok'] + +def usage(): + print "Usage:", sys.argv[0], "<rvi_url> mobile <phone_number> <vin> | ivi" + print " ivi Emulate an IVI." + print " mobile <phone_number> <vin> Emulate a mobile device with number given in" + print " <phone_number> number, communicating with <vin>" + print " <rvi_url> URL of RVI Service Edge on local host" + print + print "The RVI Service Edge URL can be found in" + print "priv/setup_[backend,device].config as" + print "env -> rvi -> components -> service_edge -> url" + sys.exit(255) + + +# Setup self's server that we will receive incoming calls from service edge on. + +# +# Setup a localhost URL, using a random port, that we will listen to +# incoming JSON-RPC publish calls on, delivered by our RVI service +# edge (specified by rvi_url). +# +emulator_service_host = 'localhost' +emulator_service_port = random.randint(20001, 59999) + + + + + +# +# Check that we have the correct arguments +# +if len(sys.argv) == 5: + [ progname, rvi_url, mode, phone_number, vin ] = sys.argv + if mode != 'mobile': + print + print "Second argument, when three parameters are specified, must " + print "be 'mobile', not", mode, "." + print + usage() + + # We are in mobile device mode. Setup the vin numbers for publish + pub_vin = "mobile_"+vin + sub_vin = "ivi_"+vin + + # Setup the service name we will register with. + # The complete service name will be: jlr.com/backend/mobile/<phone_nr>/hvac/publish + emulator_service_name = '/mobile/'+phone_number+'/hvac/publish' + + print "Will run in mobile device mode." + print "Backend server node URL: ", rvi_url + print "Phone Number: ", phone_number + print "VIN: ", vin + +elif len(sys.argv) == 3: + [ progname, rvi_url, mode ] = sys.argv + if mode != 'ivi': + print + print "Second argument, when two are specified, must " + print "be 'ivi', not", mode, "." + print + usage() + + # setup the service name we will register with + # The complete service name will be: jlr.com/vin/<vin>/hvac/publish + emulator_service_name = '/hvac/publish' + + # Setup an outbound JSON-RPC connection to the RVI Service Edeg. + rvi_server = jsonrpclib.Server(rvi_url) + + # Register our HVAC IVI/mobile emulator service with the RVI Service Edge, + # allowing the RVI to forward requests to the service name to the + # given network addresss (URL): + res = rvi_server.register_service(service = emulator_service_name, + network_address = + 'http://'+emulator_service_host + ':' + str(emulator_service_port)) + + # The returned full service name contains the VIN number that we want: + # jlr.com/vin/<vin>/hvac/publish + # We need to dig out the <vin> bit + + print "res = ", res + svc_name = res['service'] + + [ t1, t2, vin, t3, t4] = svc_name.split('/') + print "vin:", vin, + + # We are in mobile device mode. Setup the vin numbers for publish + pub_vin = "ivi_"+vin + sub_vin = "mobile_"+vin + + print "Will run in IVI mode." + print "Device node URL: ", rvi_url + print "VIN: ", vin +else: + usage() + + +# Regsiter self's service with the backend server RVI node +# See rvi_json_rpc_server.py._dispatch() for details on how +# ncoming JSON-RPC requests are mapped to local funcitons. +# +emulator_service = RVIJSONRPCServer(((emulator_service_host, emulator_service_port))) +emulator_service.register_function(publish, emulator_service_name) + +# Send of a subscribe to the subscription service running on the +# backend. See hvac_subscription_service.py.subscribe() for details. + +rvi_server.message(calling_service = emulator_service_name, + target = SUBSCRIBE_SERVICE, + timeout = 0, + parameters = [{ u'vin': sub_vin, + u'subscribing_service': emulator_service_name}]) + +# Create a thread to handle incoming stuff so that we can do input +# in order to get new values +thr = threading.Thread(target=emulator_service.serve_forever) +thr.start() + +while True: + # Read a line and split it into a key val pair + [k, v] = raw_input('Enter <key> <val>: ').split(' ') + + # Send out update to the subscriber + rvi_server.message(calling_service=emulator_service_name, + target = PUBLISH_SERVICE, + timeout = 0, + parameters = [{ u'vin': pub_vin, + u'key': k, + u'val': v}]) + + print('Key {} set to {} for vin{}'. format(k, v, vin)) diff --git a/hvac_demo/hvac_subscription_service.py b/hvac_demo/hvac_subscription_service.py new file mode 100755 index 0000000..88c8435 --- /dev/null +++ b/hvac_demo/hvac_subscription_service.py @@ -0,0 +1,99 @@ +#!/usr/bin/python + +from jsonrpclib.SimpleJSONRPCServer import SimpleJSONRPCServer +import jsonrpclib +from rvi_json_rpc_server import RVIJSONRPCServer + +RVI_SERVICE_EDGE='http://localhost:8801' +HVAC_SERVER=('localhost', 8901) + +# vin_subs is a simple dictionary with vin numbers as keys and arrays +# of services as values. +# During a subscribe operation, the provided service is added +# as a list element in value, where the list hangs under the key +# with value 'vin' +# +# Thus: +# { '1234': [ 'jlr.com/vin/5555/hvac_update_ui', 'jlr.com/vin/4711/hvac_update_ui'] +vin_subs = [] + +def subscribe(vin, subscribing_service): + print "Subscribe" + print "vin:", vin + print "subscribing_service:", subscribing_service + + # Delete any existing service with the same name + try: + vin_subs[vin].remove(subscribing_service) + except Err: + pass + + # Add the subscribing service + vin_subs[vin].append(subscribing_service) + return ['ok'] + + +def unsubscribe(vin, subscribing_service): + print "Unsubscribe" + print "vin:", vin + print "subscribing_service:", subscribing_service + + # Delete any existing service with the same name + try: + vin_subs[vin].remove(subscribing_service) + except Err: + pass + + return ['ok'] + +def publish(vin, key, value): + print "Publish" + print "vin:", vin + print "key:", key + print "value:", value + + # Delete any existing service with the same name + try: + subs = vin_subs[vin] + except Err: + print "No subscribers for vin:", vin + return ['ok'] + + for sub in subs: + print "Sending publish to", sub + rvi_server.message(calling_service = '/hvac/publish', + target = sub, + timeout = 0, + parameters = [{ u'key': key}, {u'value': value }]) + + +# Setup self's server that we will receive incoming calls from service edge on. +hvac_server = RVIJSONRPCServer(HVAC_SERVER) +# hvac_server = SimpleJSONRPCServer(HVAC_SERVER) + +hvac_server.register_function(subscribe, '/hvac/subscriber_service/subscribe') +hvac_server.register_function(unsubscribe, '/hvac/subscriber_service/unsubscribe') +hvac_server.register_function(publish, '/hvac/subscriber_service/publish') + + +# Register our services with the service edge and have +# it associate our service name with the URL that the hvac_server is listening on +rvi_server = jsonrpclib.Server(RVI_SERVICE_EDGE) + +res = rvi_server.register_service(service = '/subscription_service/subscribe', + network_address = 'http://localhost:8901') + +print "Registered service /subscription_service/subscribe as", res['service'] + +res = rvi_server.register_service(service = '/subscription_service/unsubscribe', + network_address = 'http://localhost:8901') + +print "Registered service /subscription_service/unsubscribe as", res['service'] + +res = rvi_server.register_service(service = '/subscription_service/publish', + network_address = 'http://localhost:8901') + +print "Registered service /subscription_service/publish as", res['service'] + + +hvac_server.serve_forever() diff --git a/hvac_demo/rvi_json_rpc_server.py b/hvac_demo/rvi_json_rpc_server.py new file mode 100644 index 0000000..7235756 --- /dev/null +++ b/hvac_demo/rvi_json_rpc_server.py @@ -0,0 +1,33 @@ +from jsonrpclib.SimpleJSONRPCServer import SimpleJSONRPCServer +import jsonrpclib + +class RVIJSONRPCServer(SimpleJSONRPCServer): + # Check if method is 'message', if so dispatch on + # name 'target' instead. + def _dispatch(self, method, params): + print "dispatch:", params + if method == 'message': + print "Will dispatch message to: " + params['target'] + 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 params['parameters'] + msg_params = params['parameters'] + for i in range(0, len(msg_params)): + print "params ", msg_params[i].keys()[0], " = ", msg_params[i].values()[0] + dict_param[msg_params[i].keys()[0]] = msg_params[i].values()[0] + + print "DICT: ", dict_param + # Ship the processed dispatch info upward. + return SimpleJSONRPCServer._dispatch(self, params['target'], dict_param) + + print "Method:", method + for x in params: + print "params ", x, " = ",params[x] + return SimpleJSONRPCServer._dispatch(self,message, params) + + print "---" + diff --git a/priv/setup_device.config b/priv/setup_device.config index 3ef2770..55accd7 100644 --- a/priv/setup_device.config +++ b/priv/setup_device.config @@ -44,7 +44,8 @@ {rvi, [ - { node_address, "127.0.0.1:9991" }, %% Should matcch bert_rpc_server below + %% Should matcch bert_rpc_server below + { node_address, "127.0.0.1:9991" }, { node_service_prefix, "jlr.com/vin/1234/"}, { backend_address, "127.0.0.1:9990" }, %% See data_link_device:send_data() for details |