ports.py
Go to the documentation of this file.
1 # -*- Python -*-
2 # -*- coding: utf-8 -*-
3 
4 '''rtctree
5 
6 Copyright (C) 2009-2014
7  Geoffrey Biggs
8  RT-Synthesis Research Group
9  Intelligent Systems Research Institute,
10  National Institute of Advanced Industrial Science and Technology (AIST),
11  Japan
12  All rights reserved.
13 Licensed under the Eclipse Public License -v 1.0 (EPL)
14 http://www.opensource.org/licenses/eclipse-1.0.txt
15 
16 File: ports.py
17 
18 Objects representing ports and connections.
19 
20 Do not create port objects directly. Call the parse_port function, which will
21 create the correct type of port object automatically.
22 
23 '''
24 
25 
26 import RTC
27 import threading
28 
29 from rtctree.exceptions import *
30 from rtctree.utils import build_attr_string, dict_to_nvlist, nvlist_to_dict
31 
32 
33 ##############################################################################
34 ## API functions
35 
36 def parse_port(port_obj, owner):
37  '''Create a port object of the correct type.
38 
39  The correct port object type is chosen based on the port.port_type
40  property of port_obj.
41 
42  @param port_obj The CORBA PortService object to wrap.
43  @param owner The owner of this port. Should be a Component object or None.
44  @return The created port object.
45 
46  '''
47  profile = port_obj.get_port_profile()
48  props = nvlist_to_dict(profile.properties)
49  if props['port.port_type'] == 'DataInPort':
50  return DataInPort(port_obj, owner)
51  elif props['port.port_type'] == 'DataOutPort':
52  return DataOutPort(port_obj, owner)
53  elif props['port.port_type'] == 'CorbaPort':
54  return CorbaPort(port_obj, owner)
55  else:
56  return Port(port_obj, owner)
57 
58 
59 ##############################################################################
60 ## Base port object
61 
62 class Port(object):
63  '''Base class representing a port of a component.
64 
65  Do not create Port objects directly. Call parse_port().
66 
67  '''
68  def __init__(self, port_obj=None, owner=None, *args, **kwargs):
69  '''Base port constructor.
70 
71  @param port_obj The CORBA PortService object to wrap.
72  @param owner The owner of this port. Should be a Component object or
73  None.
74 
75  '''
76  super(Port, self).__init__(*args, **kwargs)
77  self._obj = port_obj
78  self._connections = None
79  self._owner = owner
80  self._mutex = threading.RLock()
81  self._parse()
82 
83  def connect(self, dests=[], name=None, id='', props={}):
84  '''Connect this port to other ports.
85 
86  After the connection has been made, a delayed reparse of the
87  connections for this and the destination port will be triggered.
88 
89  @param dests A list of the destination Port objects. Must be provided.
90  @param name The name of the connection. If None, a suitable default
91  will be created based on the names of the two ports.
92  @param id The ID of this connection. If None, one will be generated by
93  the RTC implementation.
94  @param props Properties of the connection. Required values depend on
95  the type of the two ports being connected.
96  @raises IncompatibleDataPortConnectionPropsError, FailedToConnectError
97 
98  '''
99  with self._mutex:
100  if self.porttype == 'DataInPort' or self.porttype == 'DataOutPort':
101  for prop in props:
102  if prop in self.properties:
103  if props[prop] not in self.properties[prop] and \
104  'Any' not in self.properties[prop]:
105  # Invalid property selected
106  raise IncompatibleDataPortConnectionPropsError
107  for d in dests:
108  if prop in d.properties:
109  if props[prop] not in d.properties[prop] and \
110  'Any' not in d.properties[prop]:
111  # Invalid property selected
112  raise IncompatibleDataPortConnectionPropsError
113  if not name:
114  name = self.name + '_'.join([d.name for d in dests])
115  props = dict_to_nvlist(props)
116  profile = RTC.ConnectorProfile(name, id,
117  [self._obj] + [d._obj for d in dests], props)
118  return_code, profile = self._obj.connect(profile)
119  if return_code != RTC.RTC_OK:
120  raise FailedToConnectError(return_code)
121  self.reparse_connections()
122  for d in dests:
123  d.reparse_connections()
124 
125  def disconnect_all(self):
126  '''Disconnect all connections to this port.'''
127  with self._mutex:
128  for conn in self.connections:
129  self.object.disconnect(conn.id)
130  self.reparse_connections()
131 
132  def get_connection_by_dest(self, dest):
133  '''DEPRECATED. Search for a connection between this and another port.'''
134  with self._mutex:
135  for conn in self.connections:
136  if conn.has_port(self) and conn.has_port(dest):
137  return conn
138  return None
139 
140  def get_connections_by_dest(self, dest):
141  '''Search for all connections between this and another port.'''
142  with self._mutex:
143  res = []
144  for c in self.connections:
145  if c.has_port(self) and c.has_port(dest):
146  res.append(c)
147  return res
148 
149  def get_connections_by_dests(self, dests):
150  '''Search for all connections involving this and all other ports.'''
151  with self._mutex:
152  res = []
153  for c in self.connections:
154  if not c.has_port(self):
155  continue
156  for d in dests:
157  if not c.has_port(d):
158  continue
159  res.append(c)
160  return res
161 
162  def get_connection_by_id(self, id):
163  '''Search for a connection on this port by its ID.'''
164  with self._mutex:
165  for conn in self.connections:
166  if conn.id == id:
167  return conn
168  return None
169 
170  def get_connection_by_name(self, name):
171  '''Search for a connection to or from this port by name.'''
172  with self._mutex:
173  for conn in self.connections:
174  if conn.name == name:
175  return conn
176  return None
177 
178  def reparse(self):
179  '''Reparse the port.'''
180  self._parse()
181  self.reparse_connections()
182 
184  '''Reparse the connections this port is involved in.'''
185  with self._mutex:
186  self._connections = None
187 
188  @property
189  def connections(self):
190  '''A list of connections to or from this port.
191 
192  This list will be created at the first reference to this property.
193  This means that the first reference may be delayed by CORBA calls,
194  but others will return quickly (unless a delayed reparse has been
195  triggered).
196 
197  '''
198  with self._mutex:
199  if not self._connections:
200  self._connections = [Connection(cp, self) \
201  for cp in self._obj.get_connector_profiles()]
202  return self._connections
203 
204  @property
205  def is_connected(self):
206  '''Check if this port is connected to any other ports.'''
207  with self._mutex:
208  if self.connections:
209  return True
210  return False
211 
212  @property
213  def name(self):
214  '''The name of this port.'''
215  with self._mutex:
216  return self._name
217 
218  @property
219  def object(self):
220  '''The PortService object that represents the port.'''
221  with self._mutex:
222  return self._obj
223 
224  @property
225  def owner(self):
226  '''This port's owner (usually a Component object).'''
227  with self._mutex:
228  return self._owner
229 
230  @property
231  def porttype(self):
232  '''The type of port this is.
233 
234  Valid values are any class that @ref parse_port can create.
235 
236  '''
237  return self.__class__.__name__
238 
239  @property
240  def properties(self):
241  '''Properties of the port.'''
242  with self._mutex:
243  return self._properties
244 
245  def _parse(self):
246  # Parse the PortService object to build a port profile.
247  with self._mutex:
248  profile = self._obj.get_port_profile()
249  self._name = profile.name
250  self._properties = nvlist_to_dict(profile.properties)
251  if self.owner:
252  prefix = self.owner.instance_name + '.'
253  if self._name.startswith(prefix):
254  self._name = self._name[len(prefix):]
255 
256 
257 ##############################################################################
258 ## Data port objects
259 
260 class DataPort(Port):
261  '''Specialisation of the Port class for data ports.
262 
263  Do not create DataPort objects directly. Call parse_port().
264 
265  '''
266  def __init__(self, port_obj=None, owner=None, *args, **kwargs):
267  '''DataPort constructor.
268 
269  @param port_obj The CORBA PortService object to wrap.
270  @param owner The owner of this port. Should be a Component object or
271  None.
272 
273  '''
274  super(DataPort, self).__init__(port_obj=port_obj, owner=owner, *args,
275  **kwargs)
276 
277  def connect(self, dests=[], name=None, id='', props={}):
278  '''Connect this port to other DataPorts.
279 
280  After the connection has been made, a delayed reparse of the
281  connections for this and the destination port will be triggered.
282 
283  @param dests A list of the destination Port objects. Must be provided.
284  @param name The name of the connection. If None, a suitable default
285  will be created based on the names of the two ports.
286  @param id The ID of this connection. If None, one will be generated by
287  the RTC implementation.
288  @param props Properties of the connection. Suitable defaults will be
289  set for required values if they are not already present.
290  @raises WrongPortTypeError
291 
292  '''
293  # Data ports can only connect to opposite data ports
294  with self._mutex:
295  new_props = props.copy()
296  ptypes = [d.porttype for d in dests]
297  if self.porttype == 'DataInPort':
298  if 'DataOutPort' not in ptypes:
299  raise WrongPortTypeError
300  if self.porttype == 'DataOutPort':
301  if 'DataInPort' not in ptypes:
302  raise WrongPortTypeError
303  if 'dataport.dataflow_type' not in new_props:
304  new_props['dataport.dataflow_type'] = 'push'
305  if 'dataport.interface_type' not in new_props:
306  new_props['dataport.interface_type'] = 'corba_cdr'
307  if 'dataport.subscription_type' not in new_props:
308  new_props['dataport.subscription_type'] = 'new'
309  if 'dataport.data_type' not in new_props:
310  new_props['dataport.data_type'] = \
311  self.properties['dataport.data_type']
312  super(DataPort, self).connect(dests=dests, name=name, id=id,
313  props=new_props)
314 
315 
317  '''Specialisation of the DataPort class for input ports.
318 
319  Do not create DataInPort objects directly. Call parse_port().
320 
321  '''
322  pass
323 
324 
325 class DataOutPort(DataPort):
326  '''Specialisation of the DataPort class for output ports.
327 
328  Do not create DataOutPort objects directly. Call parse_port().
329 
330  '''
331  pass
332 
333 
334 ##############################################################################
335 ## CORBA port objects
336 
338  '''Specialisation of the Port class for service ports.
339 
340  Do not create CorbaPort objects directly. Call parse_port().
341 
342  '''
343  def __init__(self, port_obj=None, owner=None, *args, **kwargs):
344  '''CorbaPort constructor.
345 
346  @param port_obj The CORBA PortService object to wrap.
347  @param owner The owner of this port. Should be a Component object or
348  None.
349 
350  '''
351  super(CorbaPort, self).__init__(port_obj=port_obj, owner=owner,
352  *args, **kwargs)
353  self._interfaces = None
354 
355  def connect(self, dests=None, name=None, id='', props={}):
356  '''Connect this port to other CorbaPorts.
357 
358  After the connection has been made, a delayed reparse of the
359  connections for this and the destination port will be triggered.
360 
361  @param dests A list of the destination Port objects. Must be provided.
362  @param name The name of the connection. If None, a suitable default
363  will be created based on the names of the two ports.
364  @param id The ID of this connection. If None, one will be generated by
365  the RTC implementation.
366  @param props Properties of the connection. Suitable defaults will be
367  set for required values if they are not already present.
368  @raises WrongPortTypeError, MismatchedInterfacesError,
369  MismatchedPolarityError
370 
371  '''
372  with self._mutex:
373  # Corba ports can only connect to corba ports of the opposite
374  # polarity
375  for d in dests:
376  if not d.porttype == 'CorbaPort':
377  raise WrongPortTypeError
378  # Check the interfaces and their respective polarities match
379  if self.interfaces:
380  for d in dests:
381  if not d.interfaces:
382  raise MismatchedInterfacesError
383  for intf in self.interfaces:
384  for d in dests:
385  match = d.get_interface_by_instance_name(
386  intf.instance_name)
387  if not match:
388  raise MismatchedInterfacesError
389  if intf.polarity == match.polarity:
390  # Polarity should be opposite
391  raise MismatchedPolarityError
392  else:
393  for d in dests:
394  if d.interfaces:
395  raise MismatchedInterfacesError
396  # Make the connection
397  new_props = props.copy()
398  if 'port.port_type' not in new_props:
399  new_props['port.port_type'] = 'CorbaPort'
400  super(CorbaPort, self).connect(dests=dests, name=name, id=id,
401  props=new_props)
402 
404  '''Get an interface of this port by instance name.'''
405  with self._mutex:
406  for intf in self.interfaces:
407  if intf.instance_name == name:
408  return intf
409  return None
410 
411  @property
412  def interfaces(self):
413  '''The list of interfaces this port provides or uses.
414 
415  This list will be created at the first reference to this property.
416  This means that the first reference may be delayed by CORBA calls,
417  but others will return quickly (unless a delayed reparse has been
418  triggered).
419 
420  '''
421  with self._mutex:
422  if not self._interfaces:
423  profile = self._obj.get_port_profile()
424  self._interfaces = [SvcInterface(intf) \
425  for intf in profile.interfaces]
426  return self._interfaces
427 
428 
429 ##############################################################################
430 ## Service port interface object
431 
432 class SvcInterface(object):
433  '''Object representing the interface used by a service port.'''
434  def __init__(self, intf_obj=None, *args, **kwargs):
435  '''Constructor.
436 
437  @param intf_obj The CORBA PortInterfaceProfile object to wrap.
438 
439  '''
440  super(SvcInterface, self).__init__(*args, **kwargs)
441  self._obj = intf_obj
442  self._mutex = threading.RLock()
443  self._parse()
444 
445  def polarity_as_string(self, add_colour=True):
446  '''Get the polarity of this interface as a string.
447 
448  @param add_colour If True, ANSI colour codes will be added to the
449  string.
450  @return A string describing the polarity of this interface.
451 
452  '''
453  with self._mutex:
454  if self.polarity == self.PROVIDED:
455  result = 'Provided', ['reset']
456  elif self.polarity == self.REQUIRED:
457  result = 'Required', ['reset']
458  if add_colour:
459  return build_attr_string(result[1], supported=add_colour) + \
460  result[0] + build_attr_string('reset',
461  supported=add_colour)
462  else:
463  return result[0]
464 
465  def reparse(self):
466  '''Reparse the interface object.'''
467  self._parse()
468 
469  @property
470  def instance_name(self):
471  '''Instance name of the interface.'''
472  with self._mutex:
473  return self._instance_name
474 
475  @property
476  def polarity(self):
477  '''Polarity of this interface.'''
478  with self._mutex:
479  return self._polarity
480 
481  @property
482  def polarity_string(self):
483  '''The polarity of this interface as a coloured string.'''
484  with self._mutex:
485  return self.polarity_as_string()
486 
487  @property
488  def type_name(self):
489  '''Type name of the interface.'''
490  with self._mutex:
491  return self._type_name
492 
493  def _parse(self):
494  # Parse the PortInterfaceProfile object.
495  with self._mutex:
496  self._instance_name = self._obj.instance_name
497  self._type_name = self._obj.type_name
498  if self._obj.polarity == RTC.PROVIDED:
499  self._polarity = self.PROVIDED
500  else:
501  self._polarity = self.REQUIRED
502 
503  ## Constant for provided interface polarity.
504  PROVIDED = 1
505  ## Constant for required interface polarity.
506  REQUIRED = 2
507 
508 
509 ##############################################################################
510 ## Connection object
511 
512 class Connection(object):
513  '''An object representing a connection between two or more ports.'''
514  def __init__(self, conn_profile_obj=None, owner=None, *args, **kwargs):
515  '''Constructor.
516 
517  @param conn_profile_obj The CORBA ConnectorProfile object to wrap.
518  @param owner The owner of this connection. If the creator of this
519  object is not a Port object (or derivative thereof), this
520  value should be set to None.
521 
522  '''
523  super(Connection, self).__init__(*args, **kwargs)
524  self._obj = conn_profile_obj
525  self._owner = owner
526  self._mutex = threading.RLock()
527  self._parse()
528 
529  def __str__(self):
530  return 'Connection {0} (ID: {1}), properties {2}, with ports '\
531  '{3}'.format(self._name, self._id, self._properties, self._ports)
532 
533  def disconnect(self):
534  '''Disconnect this connection.'''
535  with self._mutex:
536  if not self.ports:
537  raise NotConnectedError
538  # Some of the connection participants may not be in the tree,
539  # causing the port search in self.ports to return ('Unknown', None)
540  # for those participants. Search the list to find the first
541  # participant that is in the tree (there must be at least one).
542  p = self.ports[0][1]
543  ii = 1
544  while not p and ii < len(self.ports):
545  p = self.ports[ii][1]
546  ii += 1
547  if not p:
548  raise UnknownConnectionOwnerError
549  p.object.disconnect(self.id)
550 
551  def has_port(self, port):
552  '''Return True if this connection involves the given Port object.
553 
554  @param port The Port object to search for in this connection's ports.
555 
556  '''
557  with self._mutex:
558  for p in self.ports:
559  if not p[1]:
560  # Port owner not in tree, so unknown
561  continue
562  if port.object._is_equivalent(p[1].object):
563  return True
564  return False
565 
566  def reparse(self):
567  '''Reparse the connection.'''
568  self._parse()
569 
570  @property
571  def id(self):
572  '''The ID of the connection.'''
573  with self._mutex:
574  return self._id
575 
576  @property
577  def name(self):
578  '''The name of the connection.'''
579  with self._mutex:
580  return self._name
581 
582  @property
583  def owner(self):
584  '''This connection's owner, if created by a Port object.'''
585  with self._mutex:
586  return self._owner
587 
588  @property
589  def ports(self):
590  '''The list of ports involved in this connection.
591 
592  The result is a list of tuples, (port name, port object). Each port
593  name is a full path to the port (e.g. /localhost/Comp0.rtc:in) if
594  this Connection object is owned by a Port, which is in turn owned by
595  a Component in the tree. Otherwise, only the port's name will be used
596  (in which case it will be the full port name, which will include the
597  component name, e.g. 'ConsoleIn0.in'). The full path can be used to
598  find ports in the tree.
599 
600  If, for some reason, the owner node of a port cannot be found, that
601  entry in the list will contain ('Unknown', None). This typically means
602  that a component's name has been clobbered on the name server.
603 
604  This list will be created at the first reference to this property.
605  This means that the first reference may be delayed by CORBA calls,
606  but others will return quickly (unless a delayed reparse has been
607  triggered).
608 
609  '''
610  def has_port(node, args):
611  if node.get_port_by_ref(args):
612  return node
613  return None
614 
615  with self._mutex:
616  if not self._ports:
617  self._ports = []
618  for p in self._obj.ports:
619  # My owner's owner is a component node in the tree
620  if self.owner and self.owner.owner:
621  root = self.owner.owner.root
622  owner_nodes = [n for n in root.iterate(has_port,
623  args=p, filter=['is_component']) if n]
624  if not owner_nodes:
625  self._ports.append(('Unknown', None))
626  else:
627  port_owner = owner_nodes[0]
628  port_owner_path = port_owner.full_path_str
629  port_name = p.get_port_profile().name
630  prefix = port_owner.instance_name + '.'
631  if port_name.startswith(prefix):
632  port_name = port_name[len(prefix):]
633  self._ports.append((port_owner_path + ':' + \
634  port_name, parse_port(p, self.owner.owner)))
635  else:
636  self._ports.append((p.get_port_profile().name,
637  parse_port(p, None)))
638  return self._ports
639 
640  @property
641  def properties(self):
642  '''The connection's properties dictionary.'''
643  with self._mutex:
644  return self._properties
645 
646  def _parse(self):
647  # Parse the ConnectorProfile object.
648  with self._mutex:
649  self._name = self._obj.name
650  self._id = self._obj.connector_id
651  self._ports = None
652  self._properties = nvlist_to_dict(self._obj.properties)
653 
654 
655 # vim: tw=79
656 
def __init__(self, intf_obj=None, args, kwargs)
Definition: ports.py:434
int PROVIDED
Constant for provided interface polarity.
Definition: ports.py:504
def instance_name(self)
Definition: ports.py:470
def owner(self)
Definition: ports.py:225
Service port interface object.
Definition: ports.py:432
def __init__(self, conn_profile_obj=None, owner=None, args, kwargs)
Definition: ports.py:514
def connect(self, dests=[], name=None, id='', props={})
Definition: ports.py:83
def __init__(self, port_obj=None, owner=None, args, kwargs)
Definition: ports.py:266
CORBA port objects.
Definition: ports.py:337
def name(self)
Definition: ports.py:213
def object(self)
Definition: ports.py:219
def __init__(self, port_obj=None, owner=None, args, kwargs)
Definition: ports.py:68
Connection object.
Definition: ports.py:512
def get_connections_by_dests(self, dests)
Definition: ports.py:149
def get_connections_by_dest(self, dest)
Definition: ports.py:140
def properties(self)
Definition: ports.py:641
def interfaces(self)
Definition: ports.py:412
def disconnect_all(self)
Definition: ports.py:125
def get_connection_by_id(self, id)
Definition: ports.py:162
def has_port(self, port)
Definition: ports.py:551
def get_connection_by_name(self, name)
Definition: ports.py:170
def get_connection_by_dest(self, dest)
Definition: ports.py:132
def disconnect(self)
Definition: ports.py:533
def connections(self)
Definition: ports.py:189
def polarity_as_string(self, add_colour=True)
Definition: ports.py:445
def get_interface_by_instance_name(self, name)
Definition: ports.py:403
def build_attr_string(attrs, supported=True)
Definition: utils.py:62
def dict_to_nvlist(dict)
Definition: utils.py:161
def connect(self, dests=None, name=None, id='', props={})
Definition: ports.py:355
def reparse(self)
Definition: ports.py:178
def nvlist_to_dict(nvlist)
Definition: utils.py:169
def polarity_string(self)
Definition: ports.py:482
def parse_port(port_obj, owner)
API functions.
Definition: ports.py:36
def _parse(self)
Definition: ports.py:245
def is_connected(self)
Definition: ports.py:205
def reparse_connections(self)
Definition: ports.py:183
def __init__(self, port_obj=None, owner=None, args, kwargs)
Definition: ports.py:343
def properties(self)
Definition: ports.py:240
int REQUIRED
Constant for required interface polarity.
Definition: ports.py:506
Data port objects.
Definition: ports.py:260
Base port object.
Definition: ports.py:62
def connect(self, dests=[], name=None, id='', props={})
Definition: ports.py:277


rtctree
Author(s): Geoffrey Biggs
autogenerated on Fri Jun 7 2019 21:56:24