Go to the documentation of this file.00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033 import rospy
00034 import time
00035
00036 from rosbridge_library.internal.exceptions import InvalidArgumentException
00037 from rosbridge_library.internal.exceptions import MissingArgumentException
00038
00039
00040 from rosbridge_library.capabilities.fragmentation import Fragmentation
00041
00042
00043 try:
00044 import ujson as json
00045 except ImportError:
00046 try:
00047 import simplejson as json
00048 except ImportError:
00049 import json
00050
00051
00052 def is_number(s):
00053 try:
00054 float(s)
00055 return True
00056 except ValueError:
00057 return False
00058
00059 class Protocol:
00060 """ The interface for a single client to interact with ROS.
00061
00062 See rosbridge_protocol for the default protocol used by rosbridge
00063
00064 The lifecycle for a Protocol instance is as follows:
00065 - Pass incoming messages from the client to incoming
00066 - Propagate outgoing messages to the client by overriding outgoing
00067 - Call finish to clean up resources when the client is finished
00068
00069 """
00070
00071
00072
00073 fragment_size = None
00074 png = None
00075
00076 buffer = ""
00077 old_buffer = ""
00078 busy = False
00079
00080
00081
00082 delay_between_messages = 0.01
00083 request_list = None
00084
00085 parameters = None
00086
00087 def __init__(self, client_id):
00088 """ Keyword arguments:
00089 client_id -- a unique ID for this client to take. Uniqueness is
00090 important otherwise there will be conflicts between multiple clients
00091 with shared resources
00092
00093 """
00094 self.client_id = client_id
00095 self.capabilities = []
00096 self.operations = {}
00097
00098 if self.parameters:
00099 self.fragment_size = self.parameters["max_message_size"]
00100 self.delay_between_messages = self.parameters["delay_between_messages"]
00101
00102
00103
00104 def incoming(self, message_string=""):
00105 """ Process an incoming message from the client
00106
00107 Keyword arguments:
00108 message_string -- the wire-level message sent by the client
00109
00110 """
00111 self.buffer = self.buffer + message_string
00112 msg = None
00113
00114
00115
00116 try:
00117 msg = self.deserialize(self.buffer)
00118 self.buffer = ""
00119
00120
00121
00122 except Exception, e:
00123
00124
00125
00126
00127
00128
00129
00130
00131
00132
00133
00134
00135
00136 opening_brackets = [i for i, letter in enumerate(self.buffer) if letter == '{']
00137 closing_brackets = [i for i, letter in enumerate(self.buffer) if letter == '}']
00138
00139 for start in opening_brackets:
00140 for end in closing_brackets:
00141 try:
00142 msg = self.deserialize(self.buffer[start:end+1])
00143 if msg.get("op",None) != None:
00144
00145 self.buffer = self.buffer[end+1:len(self.buffer)]
00146
00147 break
00148 except Exception,e:
00149
00150
00151 pass
00152
00153 if msg != None:
00154 break
00155
00156
00157 if msg is None:
00158 return
00159
00160
00161 mid = None
00162 if "id" in msg:
00163 mid = msg["id"]
00164 if "op" not in msg:
00165 if "receiver" in msg:
00166 self.log("error", "Received a rosbridge v1.0 message. Please refer to rosbridge.org for the correct format of rosbridge v2.0 messages. Original message was: %s" % message_string)
00167 else:
00168 self.log("error", "Received a message without an op. All messages require 'op' field with value one of: %s. Original message was: %s" % (self.operations.keys(), message_string), mid)
00169 return
00170 op = msg["op"]
00171 if op not in self.operations:
00172 self.log("error", "Unknown operation: %s. Allowed operations: %s" % (op, self.operations.keys()), mid)
00173 return
00174
00175
00176 if "fragment_size" in msg.keys():
00177 self.fragment_size = msg["fragment_size"]
00178
00179 if "message_intervall" in msg.keys() and is_number(msg["message_intervall"]):
00180 self.delay_between_messages = msg["message_intervall"]
00181 if "png" in msg.keys():
00182 self.png = msg["msg"]
00183
00184
00185 try:
00186 self.operations[op](msg)
00187 except Exception as exc:
00188 self.log("error", "%s: %s" % (op, str(exc)), mid)
00189
00190
00191
00192 if len(self.buffer) > 0:
00193
00194 if self.old_buffer != self.buffer:
00195 self.old_buffer = self.buffer
00196 self.incoming()
00197
00198
00199
00200 def outgoing(self, message):
00201 """ Pass an outgoing message to the client. This method should be
00202 overridden.
00203
00204 Keyword arguments:
00205 message -- the wire-level message to send to the client
00206
00207 """
00208 pass
00209
00210 def send(self, message, cid=None):
00211 """ Called internally in preparation for sending messages to the client
00212
00213 This method pre-processes the message then passes it to the overridden
00214 outgoing method.
00215
00216 Keyword arguments:
00217 message -- a dict of message values to be marshalled and sent
00218 cid -- (optional) an associated id
00219
00220 """
00221 serialized = self.serialize(message, cid)
00222 if serialized is not None:
00223 if self.png == "png":
00224
00225
00226 pass
00227
00228 fragment_list = None
00229 if self.fragment_size != None and len(serialized) > self.fragment_size:
00230 mid = message.get("id", None)
00231
00232
00233
00234 fragment_list = Fragmentation(self).fragment(message, self.fragment_size, mid )
00235
00236
00237 if fragment_list != None:
00238 for fragment in fragment_list:
00239 self.outgoing(json.dumps(fragment))
00240
00241
00242 time.sleep(self.delay_between_messages)
00243
00244 else:
00245 self.outgoing(serialized)
00246 time.sleep(self.delay_between_messages)
00247
00248 def finish(self):
00249 """ Indicate that the client is finished and clean up resources.
00250
00251 All clients should call this method after disconnecting.
00252
00253 """
00254 for capability in self.capabilities:
00255 capability.finish()
00256
00257 def serialize(self, msg, cid=None):
00258 """ Turns a dictionary of values into the appropriate wire-level
00259 representation.
00260
00261 Default behaviour uses JSON. Override to use a different container.
00262
00263 Keyword arguments:
00264 msg -- the dictionary of values to serialize
00265 cid -- (optional) an ID associated with this. Will be logged on err.
00266
00267 Returns a JSON string representing the dictionary
00268 """
00269 try:
00270 return json.dumps(msg)
00271 except:
00272 if cid is not None:
00273
00274 self.log("error", "Unable to serialize %s message to client"
00275 % msg["op"], cid)
00276 return None
00277
00278 def deserialize(self, msg, cid=None):
00279
00280 """ Turns the wire-level representation into a dictionary of values
00281
00282 Default behaviour assumes JSON. Override to use a different container.
00283
00284 Keyword arguments:
00285 msg -- the wire-level message to deserialize
00286 cid -- (optional) an ID associated with this. Is logged on error
00287
00288 Returns a dictionary of values
00289
00290 """
00291 try:
00292 return json.loads(msg)
00293 except Exception, e:
00294
00295
00296
00297
00298
00299
00300
00301
00302
00303
00304
00305
00306
00307 raise
00308
00309
00310 def register_operation(self, opcode, handler):
00311 """ Register a handler for an opcode
00312
00313 Keyword arguments:
00314 opcode -- the opcode to register this handler for
00315 handler -- a callback function to call for messages with this opcode
00316
00317 """
00318 self.operations[opcode] = handler
00319
00320 def unregister_operation(self, opcode):
00321 """ Unregister a handler for an opcode
00322
00323 Keyword arguments:
00324 opcode -- the opcode to unregister the handler for
00325
00326 """
00327 if opcode in self.operations:
00328 del self.operations[opcode]
00329
00330 def add_capability(self, capability_class):
00331 """ Add a capability to the protocol.
00332
00333 This method is for convenience; assumes the default capability
00334 constructor
00335
00336 Keyword arguments:
00337 capability_class -- the class of the capability to add
00338
00339 """
00340 self.capabilities.append(capability_class(self))
00341
00342 def log(self, level, message, lid=None):
00343 """ Log a message to the client. By default just sends to stdout
00344
00345 Keyword arguments:
00346 level -- the logger level of this message
00347 message -- the string message to send to the user
00348 lid -- an associated for this log message
00349
00350 """
00351 stdout_formatted_msg = None
00352 if lid is not None:
00353 stdout_formatted_msg = "[Client %s] [id: %s] %s" % (self.client_id, lid, message)
00354 else:
00355 stdout_formatted_msg = "[Client %s] %s" % (self.client_id, message)
00356
00357 if level == "error" or level == "err":
00358 rospy.logerr(stdout_formatted_msg)
00359 elif level == "warning" or level == "warn":
00360 rospy.logwarn(stdout_formatted_msg)
00361 elif level == "info" or level == "information":
00362 rospy.loginfo(stdout_formatted_msg)
00363 else:
00364 rospy.logdebug(stdout_formatted_msg)