def construct_subsystem(self, args: argparse.Namespace) -> object: """ We use object instead of Node because the Node class requires generated code to be generated. """ from pyuavcan import application from pyuavcan.application import heartbeat_publisher node_info = pyuavcan.dsdl.update_from_builtin(application.NodeInfo(), args.node_info_fields) _logger.debug('Node info: %r', node_info) transport = self._transport_factory.construct_subsystem(args) presentation = pyuavcan.presentation.Presentation(transport) node = application.Node(presentation, info=node_info) try: # Configure the heartbeat publisher. if args.heartbeat_fields.pop('uptime', None) is not None: _logger.warning('Specifying uptime has no effect because it will be overridden by the node.') node.heartbeat_publisher.health = \ args.heartbeat_fields.pop('health', heartbeat_publisher.Health.NOMINAL) node.heartbeat_publisher.mode = \ args.heartbeat_fields.pop('mode', heartbeat_publisher.Mode.OPERATIONAL) node.heartbeat_publisher.vendor_specific_status_code = args.heartbeat_fields.pop( 'vendor_specific_status_code', os.getpid() & (2 ** min(pyuavcan.dsdl.get_model(heartbeat_publisher.Heartbeat) ['vendor_specific_status_code'].data_type.bit_length_set) - 1) ) _logger.debug('Node heartbeat: %r', node.heartbeat_publisher.make_message()) if args.heartbeat_fields: raise ValueError(f'Unrecognized heartbeat fields: {args.heartbeat_fields}') # Check the node-ID configuration. if not self._allow_anonymous and node.presentation.transport.local_node_id is None: raise ValueError('The specified transport is configured in anonymous mode, ' 'which cannot be used with the selected command. ' 'Please specify the node-ID explicitly, or use a different transport.') # Configure the transfer-ID map. # Register save on exit even if we're anonymous because the local node-ID may be provided later. self._register_output_transfer_id_map_save_at_exit(node.presentation) # Restore if we have a node-ID. If we don't, no restoration will take place even if the node-ID is # provided later. This behavior is acceptable for CLI; a regular UAVCAN application will not need # to deal with saving/restoration at all since this use case is specific to CLI only. if node.presentation.transport.local_node_id is not None: tid_map_path = _get_output_transfer_id_file_path(node.presentation.transport.local_node_id, node.presentation.transport.descriptor) _logger.debug('Output TID map file: %s', tid_map_path) tid_map = self._restore_output_transfer_id_map(tid_map_path) _logger.debug('Output TID map with %d records from %s', len(tid_map), tid_map_path) _logger.debug('Output TID map dump: %r', tid_map) # noinspection PyTypeChecker presentation.output_transfer_id_map.update(tid_map) # type: ignore else: _logger.debug('Output TID map not restored because the local node is anonymous.') return node except Exception: node.close() raise
def __call__(self, transport: Transport, name_suffix: str, allow_anonymous: bool) -> object: """ We use ``object`` for return type instead of Node because the Node class requires generated code to be generated. """ _logger.debug("Constructing node using %r with %r and name %r", self, transport, name_suffix) if not re.match(r"[a-z][a-z0-9_]*[a-z0-9]", name_suffix): # pragma: no cover raise ValueError( f"Internal error: Poorly chosen node name suffix: {name_suffix!r}" ) try: from pyuavcan import application except ImportError as ex: from yakut.cmd.compile import make_usage_suggestion raise click.UsageError(make_usage_suggestion(ex.name)) try: node_info = pyuavcan.dsdl.update_from_builtin( application.NodeInfo(), self.node_info) except (ValueError, TypeError) as ex: raise click.UsageError( f"Node info fields are not valid: {ex}") from ex if len(node_info.name) == 0: node_info.name = f"org.uavcan.yakut.{name_suffix}" _logger.debug("Node info: %r", node_info) presentation = pyuavcan.presentation.Presentation(transport) node = application.Node(presentation, info=node_info) try: # Configure the heartbeat publisher. try: if self.heartbeat_period is not None: node.heartbeat_publisher.period = self.heartbeat_period if self.heartbeat_priority is not None: node.heartbeat_publisher.priority = self.heartbeat_priority if self.heartbeat_vssc is not None: node.heartbeat_publisher.vendor_specific_status_code = self.heartbeat_vssc else: node.heartbeat_publisher.vendor_specific_status_code = os.getpid( ) % 100 except ValueError as ex: raise click.UsageError( f"Invalid heartbeat parameters: {ex}") from ex _logger.debug( "Node heartbeat: %s, period: %s, priority: %s", node.heartbeat_publisher.make_message(), node.heartbeat_publisher.period, node.heartbeat_publisher.priority, ) # Check the node-ID configuration. if not allow_anonymous and node.presentation.transport.local_node_id is None: raise click.UsageError( "The specified transport is configured in anonymous mode, which cannot be used with this command." ) # Configure the transfer-ID map. # Register save on exit even if we're anonymous because the local node-ID may be provided later. _register_output_transfer_id_map_save_at_exit(node.presentation) # Restore if we have a node-ID. If we don't, no restoration will take place even if the node-ID is # provided later. This behavior is acceptable for CLI; a regular UAVCAN application will not need # to deal with saving/restoration at all since this use case is specific to CLI only. path = _get_output_transfer_id_map_path( node.presentation.transport) tid_map_restored = False if path is not None: tid_map = _restore_output_transfer_id_map(path) if tid_map: _logger.debug("Restored output TID map from %s: %r", path, tid_map) presentation.output_transfer_id_map.update(tid_map) tid_map_restored = True if not tid_map_restored: _logger.debug("Could not restore output TID map from %s", path) _logger.info("Constructed node: %s", node) return node except Exception: node.close() raise