__init__.py 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. #
  2. # Copyright (C) 2014-2015 UAVCAN Development Team <uavcan.org>
  3. #
  4. # This software is distributed under the terms of the MIT License.
  5. #
  6. # Author: Ben Dyer <ben_dyer@mac.com>
  7. # Pavel Kirienko <pavel.kirienko@zubax.com>
  8. #
  9. """
  10. Python UAVCAN package.
  11. Supported Python versions: 3.2+, 2.7.
  12. """
  13. from __future__ import division, absolute_import, print_function, unicode_literals
  14. import os
  15. import sys
  16. import struct
  17. import pkg_resources
  18. import time
  19. from logging import getLogger
  20. try:
  21. # noinspection PyStatementEffect
  22. time.monotonic # Works natively in Python 3.3+
  23. except AttributeError:
  24. try:
  25. # noinspection PyPackageRequirements,PyUnresolvedReferences
  26. import monotonic # 3rd party dependency for old versions @UnresolvedImport
  27. # monotonic_wrapper is a temporary work around for https://github.com/UAVCAN/pyuavcan/issues/22
  28. # monotonic_wrapper stops arguments being passed to monotonic.monotonic
  29. def monotonic_wrapper(*args, **kwargs):
  30. return monotonic.monotonic()
  31. time.monotonic = monotonic_wrapper
  32. except ImportError:
  33. time.monotonic = time.time # Last resort - using non-monotonic time; this is no good but oh well
  34. print('''The package 'monotonic' is not available, the library will use real time instead of monotonic time.
  35. This implies that the library may misbehave if system clock is adjusted while the library is running.
  36. In order to fix this problem, consider either option:
  37. 1. Switch to Python 3.
  38. 2. Install the missing package, e.g. using pip:
  39. pip install monotonic''', file=sys.stderr)
  40. class UAVCANException(Exception):
  41. pass
  42. from .version import __version__
  43. import uavcan.node as node
  44. from uavcan.node import make_node
  45. from uavcan.driver import make_driver
  46. import uavcan.dsdl as dsdl
  47. import uavcan.transport as transport
  48. from uavcan.transport import get_uavcan_data_type, \
  49. get_active_union_field, switch_union_field, is_union, \
  50. get_constants, get_fields, \
  51. is_request, is_response
  52. from uavcan.introspect import value_to_constant_name, to_yaml
  53. TRANSFER_PRIORITY_LOWEST = 31
  54. TRANSFER_PRIORITY_HIGHEST = 0
  55. logger = getLogger(__name__)
  56. class Module(object):
  57. pass
  58. class Namespace(object):
  59. """Provides a nice object-based way to look up UAVCAN data types."""
  60. def __init__(self):
  61. self.__namespaces = set()
  62. # noinspection PyProtectedMember
  63. def _path(self, attrpath):
  64. """Returns the namespace object at the given .-separated path,
  65. creating any namespaces in the path that don't already exist."""
  66. attr, _, subpath = attrpath.partition(".")
  67. if attr not in self.__dict__:
  68. self.__dict__[attr] = Namespace()
  69. self.__namespaces.add(attr)
  70. if subpath:
  71. return self.__dict__[attr]._path(subpath)
  72. else:
  73. return self.__dict__[attr]
  74. def _namespaces(self):
  75. """Returns the top-level namespaces in this object"""
  76. return set(self.__namespaces)
  77. MODULE = Module()
  78. DATATYPES = {}
  79. TYPENAMES = {}
  80. # noinspection PyProtectedMember
  81. def load_dsdl(*paths, **args):
  82. """
  83. Loads the DSDL files under the given directory/directories, and creates
  84. types for each of them in the current module's namespace.
  85. If the exclude_dist argument is not present, or False, the DSDL
  86. definitions installed with this package will be loaded first.
  87. Also adds entries for all datatype (ID, kind)s to the DATATYPES
  88. dictionary, which maps datatype (ID, kind)s to their respective type
  89. classes.
  90. """
  91. global DATATYPES, TYPENAMES
  92. paths = list(paths)
  93. # Try to prepend the built-in DSDL files
  94. # TODO: why do we need try/except here?
  95. # noinspection PyBroadException
  96. try:
  97. if not args.get("exclude_dist", None):
  98. dsdl_path = pkg_resources.resource_filename(__name__, "dsdl_files") # @UndefinedVariable
  99. paths = [os.path.join(dsdl_path, "uavcan")] + paths
  100. custom_path = os.path.join(os.path.expanduser("~"), "uavcan_vendor_specific_types")
  101. if os.path.isdir(custom_path):
  102. paths += [f for f in [os.path.join(custom_path, f) for f in os.listdir(custom_path)]
  103. if os.path.isdir(f)]
  104. except Exception:
  105. pass
  106. root_namespace = Namespace()
  107. dtypes = dsdl.parse_namespaces(paths)
  108. for dtype in dtypes:
  109. namespace, _, typename = dtype.full_name.rpartition(".")
  110. root_namespace._path(namespace).__dict__[typename] = dtype
  111. TYPENAMES[dtype.full_name] = dtype
  112. if dtype.default_dtid:
  113. DATATYPES[(dtype.default_dtid, dtype.kind)] = dtype
  114. # Add the base CRC to each data type capable of being transmitted
  115. dtype.base_crc = dsdl.crc16_from_bytes(struct.pack("<Q", dtype.get_data_type_signature()))
  116. logger.debug("DSDL Load {: >30} DTID: {: >4} base_crc:{: >8}"
  117. .format(typename, dtype.default_dtid, hex(dtype.base_crc)))
  118. def create_instance_closure(closure_type, _mode=None):
  119. # noinspection PyShadowingNames
  120. def create_instance(*args, **kwargs):
  121. if _mode:
  122. assert '_mode' not in kwargs, 'Mode cannot be supplied to service type instantiation helper'
  123. kwargs['_mode'] = _mode
  124. return transport.CompoundValue(closure_type, *args, **kwargs)
  125. return create_instance
  126. dtype._instantiate = create_instance_closure(dtype)
  127. if dtype.kind == dtype.KIND_SERVICE:
  128. dtype.Request = create_instance_closure(dtype, _mode='request')
  129. dtype.Response = create_instance_closure(dtype, _mode='response')
  130. namespace = root_namespace._path("uavcan")
  131. for top_namespace in namespace._namespaces():
  132. MODULE.__dict__[str(top_namespace)] = namespace.__dict__[top_namespace]
  133. MODULE.__dict__["thirdparty"] = Namespace()
  134. for ext_namespace in root_namespace._namespaces():
  135. if str(ext_namespace) != "uavcan":
  136. # noinspection PyUnresolvedReferences
  137. MODULE.thirdparty.__dict__[str(ext_namespace)] = root_namespace.__dict__[ext_namespace]
  138. __all__ = ["dsdl", "transport", "load_dsdl", "DATATYPES", "TYPENAMES"]
  139. # Hack to support dynamically-generated attributes at the top level of the
  140. # module. It doesn't feel right but it's recommended by Guido:
  141. # https://mail.python.org/pipermail/python-ideas/2012-May/014969.html
  142. MODULE.__dict__ = globals()
  143. MODULE._module = sys.modules[MODULE.__name__]
  144. MODULE._pmodule = MODULE
  145. sys.modules[MODULE.__name__] = MODULE
  146. # Completing package initialization with loading default DSDL definitions
  147. load_dsdl()
  148. # Importing modules that may be dependent on the standard DSDL types
  149. import uavcan.app as app