10 from __future__
import division, absolute_import, print_function, unicode_literals
16 from logging
import getLogger
22 from pyuavcan_v0
import UAVCANException
25 DEFAULT_NODE_STATUS_INTERVAL = 1.0
26 DEFAULT_SERVICE_TIMEOUT = 1.0
27 DEFAULT_TRANSFER_PRIORITY = 20
30 logger = getLogger(__name__)
34 """This class implements a simple non-blocking event scheduler.
35 It supports one-shot and periodic events.
39 if sys.version_info[0] > 2:
50 def delayfunc_impostor(duration):
52 raise SayNoToBlockingSchedulingException(
'No!')
54 self._scheduler = sched.scheduler(time.monotonic, delayfunc_impostor)
59 except SayNoToBlockingSchedulingException:
60 q = self._scheduler.queue
61 return q[0][0]
if q
else None
63 self._run_scheduler = run_scheduler
66 class EventHandle(object):
84 def defer(self, timeout_seconds, callback):
85 """This method allows to invoke the callback with specified arguments once the specified amount of time.
86 :returns: EventHandle object. Call .remove() on it to cancel the event.
89 event = self.
_scheduler.enter(timeout_seconds, priority, callback, ())
93 """This method allows to invoke the callback periodically, with specified time intervals.
94 Note that the scheduler features zero phase drift.
95 :returns: EventHandle object. Call .remove() on it to cancel the event.
99 def caller(scheduled_deadline):
101 scheduled_deadline += period_seconds
102 event_holder[0] = self.
_scheduler.enterabs(scheduled_deadline, priority, caller, (scheduled_deadline,))
105 first_deadline = self.
_scheduler.timefunc() + period_seconds
106 event_holder = [self.
_scheduler.enterabs(first_deadline, priority, caller, (first_deadline,))]
110 """Returns true if there is at least one pending event in the queue.
116 def __init__(self, transfer, node, payload_attr_name):
117 setattr(self, payload_attr_name, transfer.payload)
151 uavcan_type.KIND_SERVICE:
True,
152 uavcan_type.KIND_MESSAGE:
False
156 if inspect.isclass(handler):
157 def class_handler_adapter(event):
158 h = handler(event, **kwargs)
165 return self.
add_handler(uavcan_type, class_handler_adapter)
170 result = handler(event, **kwargs)
173 raise UAVCANException(
'Service request handler did not return a response [%r, %r]' %
174 (uavcan_type, handler))
175 self.
_node.respond(result,
176 transfer.source_node_id,
177 transfer.transfer_id,
178 transfer.transfer_priority)
180 if result
is not None:
181 raise UAVCANException(
'Message request handler did not return None [%r, %r]' %
182 (uavcan_type, handler))
184 entry = uavcan_type, call
192 for uavcan_type, wrapper
in self.
_handlers:
197 except Exception
as e:
198 logger.error(
'Transfer handler exception', exc_info=
True)
204 TRANSFER_DIRECTION_INCOMING =
'rx'
205 TRANSFER_DIRECTION_OUTGOING =
'tx'
212 hook(transfer, **kwargs)
217 setattr(transfer,
'direction', direction)
223 logger.error(
'Transfer hook exception', exc_info=
True)
227 def __init__(self, can_driver, node_id=None, node_status_interval=None,
228 mode=None, node_info=None, catch_handler_exceptions=True,
231 It is recommended to use make_node() rather than instantiating this type directly.
233 :param can_driver: CAN bus driver object. Calling close() on a node object closes its driver instance.
235 :param node_id: Node ID of the current instance. Defaults to None, which enables passive mode.
237 :param node_status_interval: NodeStatus broadcasting interval. Defaults to DEFAULT_NODE_STATUS_INTERVAL.
239 :param mode: Initial operating mode (INITIALIZATION, OPERATIONAL, etc.); defaults to INITIALIZATION.
241 :param node_info: Structure of type pyuavcan_v0.protocol.GetNodeInfo.Response, responded with when the local
242 node is queried for its node info.
243 :param catch_handler_exceptions: If true, exceptions raised from message
244 handlers will be caught and logged. If
245 False, spin() will raise exceptions
246 raised from callbacks.
266 self.
health = pyuavcan_v0.protocol.NodeStatus().HEALTH_OK
267 self.
mode = pyuavcan_v0.protocol.NodeStatus().MODE_INITIALIZATION
if mode
is None else mode
270 node_status_interval = node_status_interval
or DEFAULT_NODE_STATUS_INTERVAL
274 def on_get_node_info(e):
275 logger.debug(
'GetNodeInfo request from %r', e.transfer.source_node_id)
278 self.
node_info = node_info
or pyuavcan_v0.protocol.GetNodeInfo.Response()
279 self.
add_handler(pyuavcan_v0.protocol.GetNodeInfo, on_get_node_info)
293 if not (1 <= value <= 127):
294 raise ValueError(
'Invalid Node ID [%d]' % value)
304 if not raw_frame.extended:
307 frame =
transport.Frame(raw_frame.id, raw_frame.data, raw_frame.ts_monotonic, raw_frame.ts_real)
310 if not transfer_frames:
314 transfer.from_frames(transfer_frames)
318 if (transfer.service_not_message
and not transfer.request_not_response)
and \
319 transfer.dest_node_id == self.
_node_id:
330 elif not transfer.service_not_message
or transfer.dest_node_id == self.
_node_id:
353 msg = pyuavcan_v0.protocol.NodeStatus()
359 :param hook: Callable hook; expected signature: hook(transfer).
360 :param kwargs: Extra arguments for the hook.
361 :return: A handle object that can be used to unregister the hook by calling remove() on it.
367 Adds a handler for the specified data type.
368 :param uavcan_type: DSDL data type. Only transfers of this type will be accepted for this handler.
369 :param handler: The handler. This must be either a callable or a class.
370 :param **kwargs: Extra arguments for the handler.
371 :return: A remover object that can be used to unregister the handler as follows:
372 x = node.add_handler(...)
373 # Remove the handler like this:
377 print('The handler has been removed successfully')
379 print('There is no such handler')
384 """Removes all handlers for the specified DSDL data type.
390 Runs background processes until timeout expires.
391 Note that all processing is implemented in one thread.
392 :param timeout: The method will return once this amount of time expires.
393 If None, the method will never return.
394 If zero, the method will handle only those events that are ready, then return immediately.
397 deadline = (time.monotonic() + timeout)
if timeout
is not None else sys.float_info.max
401 if next_event_at
is None:
402 next_event_at = sys.float_info.max
404 read_timeout =
min(next_event_at, deadline) - time.monotonic()
405 read_timeout =
max(read_timeout, 0)
406 read_timeout =
min(read_timeout, 1)
413 while time.monotonic() < deadline:
424 def request(self, payload, dest_node_id, callback, priority=None, timeout=None):
431 dest_node_id=dest_node_id,
432 transfer_id=transfer_id,
433 transfer_priority=priority
or DEFAULT_TRANSFER_PRIORITY,
434 service_not_message=
True,
435 request_not_response=
True)
441 for frame
in transfer.to_frames():
442 self.
_can_driver.send(frame.message_id, frame.bytes, extended=
True)
456 timeout = timeout
or DEFAULT_SERVICE_TIMEOUT
457 timeout_caller_handle = self.
defer(timeout, on_timeout)
460 def timeout_cancelling_wrapper(event):
461 timeout_caller_handle.try_remove()
468 logger.debug(
"Node.request(dest_node_id={0:d}): sent {1!r}".
format(dest_node_id, payload))
470 def respond(self, payload, dest_node_id, transfer_id, priority):
476 dest_node_id=dest_node_id,
477 transfer_id=transfer_id,
478 transfer_priority=priority,
479 service_not_message=
True,
480 request_not_response=
False
485 for frame
in transfer.to_frames():
486 self.
_can_driver.send(frame.message_id, frame.bytes, extended=
True)
488 logger.debug(
"Node.respond(dest_node_id={0:d}, transfer_id={0:d}, priority={0:d}): sent {1!r}"
489 .
format(dest_node_id, transfer_id, priority, payload))
497 transfer_id=transfer_id,
498 transfer_priority=priority
or DEFAULT_TRANSFER_PRIORITY,
499 service_not_message=
False)
503 for frame
in transfer.to_frames():
504 self.
_can_driver.send(frame.message_id, frame.bytes, extended=
True)
511 """Constructs a node instance with specified CAN device.
512 :param can_device_name: CAN device name, e.g. "/dev/ttyACM0", "COM9", "can0".
513 :param kwargs: These arguments will be supplied to the CAN driver factory and to the node constructor.
515 can = driver.make_driver(can_device_name, **kwargs)
516 return Node(can, **kwargs)
529 class Service(object):