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