node_monitor.py
Go to the documentation of this file.
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 from __future__ import division, absolute_import, print_function, unicode_literals
11 import time
12 from logging import getLogger
13 import pyuavcan_v0
14 
15 
16 logger = getLogger(__name__)
17 
18 
19 class NodeMonitor(object):
20  TIMEOUT = pyuavcan_v0.protocol.NodeStatus().OFFLINE_TIMEOUT_MS / 1000 # @UndefinedVariable
21  TRANSFER_PRIORITY = pyuavcan_v0.TRANSFER_PRIORITY_LOWEST - 1
22  MIN_RETRY_INTERVAL = 0.5
23  MAX_RETRIES = 10
24 
25  class Entry:
26  def __init__(self):
27  self.node_id = None
28  self.status = None
29  self.info = None
30  self.monotonic_timestamp = None
31  self._remaining_retries = NodeMonitor.MAX_RETRIES
32 
33  @property
34  def discovered(self):
35  return self.info is not None or self._remaining_retries <= 0
36 
37  def _update_from_status(self, e):
38  self.monotonic_timestamp = e.transfer.ts_monotonic
39  self.node_id = e.transfer.source_node_id
40  if self.status and e.message.uptime_sec < self.status.uptime_sec:
41  self._remaining_retries = NodeMonitor.MAX_RETRIES
42  self.info = None
43  self.status = e.message
44  if self.info:
45  self.info.status = self.status
46 
47  def _update_from_info(self, e):
48  self._remaining_retries = NodeMonitor.MAX_RETRIES
49  self.monotonic_timestamp = e.transfer.ts_monotonic
50  self.node_id = e.transfer.source_node_id
51  self.status = e.response.status
52  self.info = e.response
53 
54  def _register_retry(self):
55  assert self._remaining_retries > 0
56  self._remaining_retries -= 1
57 
58  def __str__(self):
59  return '%d:%s' % (self.node_id, self.info if self.info else self.status)
60 
61  __repr__ = __str__
62 
63  class UpdateEvent:
64  EVENT_ID_NEW = 'new'
65  EVENT_ID_INFO_UPDATE = 'info_update'
66  EVENT_ID_OFFLINE = 'offline'
67 
68  def __init__(self, entry, event_id):
69  self.entry = entry
70  self.event_id = event_id
71 
72  def __str__(self):
73  return self.event_id + ':' + str(self.entry)
74 
75  __repr__ = __str__
76 
78  def __init__(self, remover):
79  self._remover = remover
80 
81  def remove(self):
82  self._remover()
83 
84  def try_remove(self):
85  try:
86  self._remover()
87  except ValueError:
88  pass
89 
90  def __init__(self, node):
92  self._handle = node.add_handler(pyuavcan_v0.protocol.NodeStatus, self._on_node_status) # @UndefinedVariable
93  self._registry = {} # {node_id: Entry}
94  self._timer = node.periodic(1, self._remove_stale)
95 
96  def add_update_handler(self, callback):
97  """
98  Args:
99  callback: The specified callback will be invoked when:
100  - A new node appears
101  - Node info for an existing node gets updated
102  - Node goes offline
103  Returns: Call remove() or try_remove() on the returned object to unregister the handler.
104  """
105  self._update_callbacks.append(callback)
106  return self.UpdateHandlerRemover(lambda: self._update_callbacks.remove(callback))
107 
108  def _call_event_handlers(self, event):
109  for cb in self._update_callbacks:
110  cb(event)
111 
112  def exists(self, node_id):
113  """
114  Args:
115  node_id: Returns True if the given node ID exists, false otherwise
116  """
117  return node_id in self._registry
118 
119  def get(self, node_id):
120  """
121  Args:
122  node_id: Returns an Entry instance for the given node ID.
123  If the requested node ID does not exist, throws KeyError.
124  """
125  if (self._registry[node_id].monotonic_timestamp + self.TIMEOUT) < time.monotonic():
126  self._call_event_handlers(self.UpdateEvent(self._registry[node_id],
127  self.UpdateEvent.EVENT_ID_OFFLINE))
128  del self._registry[node_id]
129  return self._registry[node_id]
130 
131  def get_all_node_id(self):
132  """Returns a generator or an iterable containing all currently active node ID."""
133  return self._registry.keys()
134 
135  def find_all(self, predicate):
136  """Returns a generator that produces a sequence of Entry objects for which the predicate returned True.
137  Args:
138  predicate: A callable that returns a value coercible to bool.
139  """
140  for _nid, entry in self._registry.items():
141  if predicate(entry):
142  yield entry
143 
145  """Reports whether there are nodes whose node info is still unknown."""
146  undiscovered = self.find_all(lambda e: not e.discovered)
147  return len(list(undiscovered)) == 0
148 
149  def close(self):
150  """Stops the instance. The registry will not be cleared."""
151  self._handle.remove()
152  self._timer.remove()
153 
154  def _remove_stale(self):
155  for nid, e in list(self._registry.items())[:]:
156  if (e.monotonic_timestamp + self.TIMEOUT) < time.monotonic():
157  del self._registry[nid]
158  self._call_event_handlers(self.UpdateEvent(e, self.UpdateEvent.EVENT_ID_OFFLINE))
159 
160  def _on_node_status(self, e):
161  node_id = e.transfer.source_node_id
162 
163  try:
164  entry = self.get(node_id)
165  new_entry = False
166  except KeyError:
167  entry = self.Entry()
168  entry._info_requested_at = 0
169  self._registry[node_id] = entry
170  new_entry = True
171 
172  # noinspection PyProtectedMember
173  entry._update_from_status(e)
174  if new_entry:
175  self._call_event_handlers(self.UpdateEvent(entry, self.UpdateEvent.EVENT_ID_NEW))
176 
177  should_retry_now = entry.monotonic_timestamp - entry._info_requested_at > self.MIN_RETRY_INTERVAL
178 
179  if not entry.discovered and should_retry_now and not e.node.is_anonymous:
180  entry._info_requested_at = entry.monotonic_timestamp
181  # noinspection PyProtectedMember
182  entry._register_retry()
183  e.node.request(pyuavcan_v0.protocol.GetNodeInfo.Request(), node_id, # @UndefinedVariable
184  priority=self.TRANSFER_PRIORITY, callback=self._on_info_response)
185 
186  def _on_info_response(self, e):
187  if not e:
188  return
189 
190  try:
191  entry = self.get(e.transfer.source_node_id)
192  except KeyError:
193  entry = self.Entry()
194  self._registry[e.transfer.source_node_id] = entry
195 
196  # noinspection PyProtectedMember
197  entry._update_from_info(e)
198 
199  hw_unique_id = "".join(format(c, "02X") for c in e.response.hardware_version.unique_id)
200  msg = (
201  "[#{0:03d}:pyuavcan_v0.protocol.GetNodeInfo] " +
202  "software_version.major={1:d} " +
203  "software_version.minor={2:d} " +
204  "software_version.vcs_commit={3:08x} " +
205  "software_version.image_crc={4:016X} " +
206  "hardware_version.major={5:d} " +
207  "hardware_version.minor={6:d} " +
208  "hardware_version.unique_id={7!s} " +
209  "name={8!r}"
210  ).format(
211  e.transfer.source_node_id,
212  e.response.software_version.major,
213  e.response.software_version.minor,
214  e.response.software_version.vcs_commit,
215  e.response.software_version.image_crc,
216  e.response.hardware_version.major,
217  e.response.hardware_version.minor,
218  hw_unique_id,
219  e.response.name.decode()
220  )
221  logger.info(msg)
222 
223  self._call_event_handlers(self.UpdateEvent(entry, self.UpdateEvent.EVENT_ID_INFO_UPDATE))
pyuavcan_v0.app.node_monitor.NodeMonitor.get_all_node_id
def get_all_node_id(self)
Definition: node_monitor.py:131
pyuavcan_v0.app.node_monitor.NodeMonitor.UpdateEvent.__init__
def __init__(self, entry, event_id)
Definition: node_monitor.py:68
pyuavcan_v0.app.node_monitor.NodeMonitor.get
def get(self, node_id)
Definition: node_monitor.py:119
pyuavcan_v0.app.node_monitor.NodeMonitor.Entry
Definition: node_monitor.py:25
pyuavcan_v0.app.node_monitor.NodeMonitor.TIMEOUT
int TIMEOUT
Definition: node_monitor.py:20
pyuavcan_v0.app.node_monitor.NodeMonitor.Entry.info
info
Definition: node_monitor.py:29
pyuavcan_v0.app.node_monitor.NodeMonitor.UpdateEvent.__str__
def __str__(self)
Definition: node_monitor.py:72
pyuavcan_v0.app.node_monitor.NodeMonitor.Entry._remaining_retries
_remaining_retries
Definition: node_monitor.py:31
pyuavcan_v0.app.node_monitor.NodeMonitor.are_all_nodes_discovered
def are_all_nodes_discovered(self)
Definition: node_monitor.py:144
pyuavcan_v0.app.node_monitor.NodeMonitor.Entry._register_retry
def _register_retry(self)
Definition: node_monitor.py:54
pyuavcan_v0.app.node_monitor.NodeMonitor._remove_stale
def _remove_stale(self)
Definition: node_monitor.py:154
libuavcan_dsdl_compiler.str
str
Definition: libuavcan_dsdl_compiler/__init__.py:22
pyuavcan_v0.app.node_monitor.NodeMonitor.UpdateHandlerRemover._remover
_remover
Definition: node_monitor.py:79
pyuavcan_v0.app.node_monitor.NodeMonitor._handle
_handle
Definition: node_monitor.py:92
pyuavcan_v0.app.node_monitor.NodeMonitor.Entry.__str__
def __str__(self)
Definition: node_monitor.py:58
pyuavcan_v0.app.node_monitor.NodeMonitor.Entry.__init__
def __init__(self)
Definition: node_monitor.py:26
pyuavcan_v0.app.node_monitor.NodeMonitor.__init__
def __init__(self, node)
Definition: node_monitor.py:90
pyuavcan_v0.app.node_monitor.NodeMonitor._update_callbacks
_update_callbacks
Definition: node_monitor.py:91
pyuavcan_v0.app.node_monitor.NodeMonitor.UpdateEvent
Definition: node_monitor.py:63
pyuavcan_v0.app.node_monitor.NodeMonitor.UpdateEvent.entry
entry
Definition: node_monitor.py:69
pyuavcan_v0.app.node_monitor.NodeMonitor.find_all
def find_all(self, predicate)
Definition: node_monitor.py:135
pyuavcan_v0.app.node_monitor.NodeMonitor
Definition: node_monitor.py:19
pyuavcan_v0.app.node_monitor.NodeMonitor._on_info_response
def _on_info_response(self, e)
Definition: node_monitor.py:186
pyuavcan_v0.app.node_monitor.NodeMonitor.MIN_RETRY_INTERVAL
float MIN_RETRY_INTERVAL
Definition: node_monitor.py:22
pyuavcan_v0.app.node_monitor.NodeMonitor.Entry.monotonic_timestamp
monotonic_timestamp
Definition: node_monitor.py:30
pyuavcan_v0.app.node_monitor.NodeMonitor.Entry._update_from_status
def _update_from_status(self, e)
Definition: node_monitor.py:37
pyuavcan_v0.app.node_monitor.NodeMonitor._registry
_registry
Definition: node_monitor.py:93
pyuavcan_v0.app.node_monitor.NodeMonitor._on_node_status
def _on_node_status(self, e)
Definition: node_monitor.py:160
pyuavcan_v0.app.node_monitor.NodeMonitor.UpdateHandlerRemover.__init__
def __init__(self, remover)
Definition: node_monitor.py:78
pyuavcan_v0.app.node_monitor.NodeMonitor._timer
_timer
Definition: node_monitor.py:94
pyuavcan_v0.app.node_monitor.NodeMonitor.add_update_handler
def add_update_handler(self, callback)
Definition: node_monitor.py:96
pyuavcan_v0.app.node_monitor.NodeMonitor.Entry.node_id
node_id
Definition: node_monitor.py:27
pyuavcan_v0.app.node_monitor.NodeMonitor.Entry.discovered
def discovered(self)
Definition: node_monitor.py:34
pyuavcan_v0.app.node_monitor.NodeMonitor.UpdateHandlerRemover.remove
def remove(self)
Definition: node_monitor.py:81
pyuavcan_v0.app.node_monitor.NodeMonitor.TRANSFER_PRIORITY
int TRANSFER_PRIORITY
Definition: node_monitor.py:21
pyuavcan_v0.app.node_monitor.NodeMonitor.close
def close(self)
Definition: node_monitor.py:149
pyuavcan_v0.app.node_monitor.NodeMonitor.Entry.status
status
Definition: node_monitor.py:28
pyuavcan_v0.app.node_monitor.NodeMonitor.UpdateEvent.event_id
event_id
Definition: node_monitor.py:70
pyuavcan_v0.app.node_monitor.NodeMonitor.UpdateHandlerRemover
Definition: node_monitor.py:77
pyuavcan_v0.app.node_monitor.NodeMonitor.UpdateHandlerRemover.try_remove
def try_remove(self)
Definition: node_monitor.py:84
pyuavcan_v0.app.node_monitor.NodeMonitor.exists
def exists(self, node_id)
Definition: node_monitor.py:112
test.format
format
Definition: dsdl/test.py:6
pyuavcan_v0.app.node_monitor.NodeMonitor._call_event_handlers
def _call_event_handlers(self, event)
Definition: node_monitor.py:108
pyuavcan_v0.app.node_monitor.NodeMonitor.Entry._update_from_info
def _update_from_info(self, e)
Definition: node_monitor.py:47


uavcan_communicator
Author(s):
autogenerated on Fri Dec 13 2024 03:10:02