summaryrefslogtreecommitdiff
path: root/python/rvi_ws.py
blob: cb73591ce468dc0da68b70a5ce5513a94abaf1cb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
#
# Copyright (C) 2016, 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/
#
# rvi_ws 0.1.0
#  
# This module is the websocket implementation of for RVI services. 

# To use this code you will follow these steps.
# 1. Import the rvi_ws class using "import rvi_ws"
# 2. Instantiate a rvi_ws_client class object specifying the bundle_id, debug params, and host
#     ex. 
#         rvi_client = rvi_ws.rvi_ws_client(bundle_id = <string bundle name>, host = <string host url>, debug = <True|False>)
# 3. Create a dictionary of services and their callback functions. The keys of the dictionary should be the string service name
#    name and the value should be the pointer to the callback function.
#    ex.
#         def hello(...):
#             ...
#         def world(...):
#             ...
#
#         rvi_client.register_services({"hello":hello, "world":world})
#
# 4. Register and run the services by invoking the services_run() function in the rvi_ws_client class. This will block all other code
#    execution until the websocket connection closes in which case will return a None value.
#    ex.
#         rvi_client.services_run()
#
# Optionally there are a few more utility functions included in the rvi_ws_client class mainly setting properties of the class.
#

import websocket
import json
import threading

try:
    import thread
# TODO use Threading instead of _thread in python3
except ImportError:
    import _thread as thread

# rvi_ws_client will be in charge of handling all communication via websockets between the service bundle and RVI.

class rvi_ws_client:

    def __init__(self, bundle_id = None, debug = False, host = "ws://localhost:9008"):

        self.DEBUG = debug
        self.service_bundle_id = bundle_id
        self.callback_funcs = {}
        self.host = host

    # set_ws_debug takes in parameter debug_status which is type bool. Will toggle on or off all websocket related debug messages.    
    def set_ws_debug(self):

        if self.DEBUG:
            websocket.enableTrace(True)
        else:
            websocket.enableTrace(False)

    # on_error will print an error if the websocket application encounters any and prints if debug is toggled on
    def on_error(self,ws, error):
        pass

    # TODO unregister service for clean close of websocket, for time being will just print out debug
    def on_close(self,ws):
        pass
    # What to do on the open of the application. Note we must register_services for this to do anything.
    def on_open(self,ws):

        def run(*args):

            payload = {}
            payload['json-rpc'] = "2.0"
            payload['id'] = "0"
            payload['method'] = "register_service"

            for service_name, callback in self.callback_funcs.items():
                payload['params'] = {"service_name":self.service_bundle_id+"/"+service_name}        
                ws.send(json.dumps(payload))

        opening = threading.Thread(target=run)
        opening.start()

    # on_message will route the message from the websocket to it's corresponding callback function that registered the service
    def on_message(self,ws, message):

        message_dict = json.loads(message)

        try:
            if (message_dict['method'] == 'message') and (message_dict['params']['service_name'][(2+len(self.service_bundle_id)):] in self.callback_funcs):
                self.callback_funcs[message_dict['params']['service_name'][(2+len(self.service_bundle_id)):]](**message_dict['params']['parameters'])
        except:
            pass


    # set_host will expect a string parameter that will change the class variable host which will connect to our websocket server
    def set_host(self,target_host):

        self.host = target_host
        return True

    # register_services will take in a dictionary of services to register. 
    # The keys of the dictionary will be the service name and the value will be the callback function
    # will return success on successfully changing the class' callback function dictionary
    # Please make sure that the service name keys are strings.
    # For example:
    #     services = {
    #         <service_name>:<callback function>,
    #         <service_name>:<callback function>,
    #         ....
    #     }
    def register_services(self,services):

        self.callback_funcs = services
        return True
    # set_service_bundle expects a string parameter that will change the class variable service_bundle_id as the bundle_id tro register
    def set_service_bundle(self, service_bundle):
        self.service_bundle_id = service_bundle
        return True

    # services_run is a callable function for after everything is set to start the websocket client.
    # Will block all remaining code until the websocket connection is broken. In which case will return None 
    def services_run(self):

        if self.service_bundle_id == None:
            raise NameError('No Specified bundle_id')

        self.set_ws_debug()

        ws = websocket.WebSocketApp(self.host,
                                    on_message = self.on_message,
                                    on_error = self.on_error,
                                    on_close = self.on_close)
        ws.on_open = self.on_open

        return ws.run_forever()