Important changes to repositories hosted on mbed.com
Mbed hosted mercurial repositories are deprecated and are due to be permanently deleted in July 2026.
To keep a copy of this software download the repository Zip archive or clone locally using Mercurial.
It is also possible to export all your personal repositories from the account settings page.
Dependents: UAVCAN UAVCAN_Subscriber
libuavcan/include/uavcan/protocol/node_info_retriever.hpp@0:dfe6edabb8ec, 2018-04-14 (annotated)
- Committer:
- RuslanUrya
- Date:
- Sat Apr 14 10:25:32 2018 +0000
- Revision:
- 0:dfe6edabb8ec
Initial commit
Who changed what in which revision?
User | Revision | Line number | New contents of line |
---|---|---|---|
RuslanUrya | 0:dfe6edabb8ec | 1 | /* |
RuslanUrya | 0:dfe6edabb8ec | 2 | * Copyright (C) 2015 Pavel Kirienko <pavel.kirienko@gmail.com> |
RuslanUrya | 0:dfe6edabb8ec | 3 | */ |
RuslanUrya | 0:dfe6edabb8ec | 4 | |
RuslanUrya | 0:dfe6edabb8ec | 5 | #ifndef UAVCAN_PROTOCOL_NODE_INFO_RETRIEVER_HPP_INCLUDED |
RuslanUrya | 0:dfe6edabb8ec | 6 | #define UAVCAN_PROTOCOL_NODE_INFO_RETRIEVER_HPP_INCLUDED |
RuslanUrya | 0:dfe6edabb8ec | 7 | |
RuslanUrya | 0:dfe6edabb8ec | 8 | #include <uavcan/build_config.hpp> |
RuslanUrya | 0:dfe6edabb8ec | 9 | #include <uavcan/debug.hpp> |
RuslanUrya | 0:dfe6edabb8ec | 10 | #include <uavcan/util/multiset.hpp> |
RuslanUrya | 0:dfe6edabb8ec | 11 | #include <uavcan/node/service_client.hpp> |
RuslanUrya | 0:dfe6edabb8ec | 12 | #include <uavcan/node/timer.hpp> |
RuslanUrya | 0:dfe6edabb8ec | 13 | #include <uavcan/protocol/node_status_monitor.hpp> |
RuslanUrya | 0:dfe6edabb8ec | 14 | #include <uavcan/protocol/GetNodeInfo.hpp> |
RuslanUrya | 0:dfe6edabb8ec | 15 | |
RuslanUrya | 0:dfe6edabb8ec | 16 | namespace uavcan |
RuslanUrya | 0:dfe6edabb8ec | 17 | { |
RuslanUrya | 0:dfe6edabb8ec | 18 | /** |
RuslanUrya | 0:dfe6edabb8ec | 19 | * Classes that need to receive GetNodeInfo responses should implement this interface. |
RuslanUrya | 0:dfe6edabb8ec | 20 | */ |
RuslanUrya | 0:dfe6edabb8ec | 21 | class UAVCAN_EXPORT INodeInfoListener |
RuslanUrya | 0:dfe6edabb8ec | 22 | { |
RuslanUrya | 0:dfe6edabb8ec | 23 | public: |
RuslanUrya | 0:dfe6edabb8ec | 24 | /** |
RuslanUrya | 0:dfe6edabb8ec | 25 | * Called when a response to GetNodeInfo request is received. This happens shortly after the node restarts or |
RuslanUrya | 0:dfe6edabb8ec | 26 | * becomes online for the first time. |
RuslanUrya | 0:dfe6edabb8ec | 27 | * @param node_id Node ID of the node |
RuslanUrya | 0:dfe6edabb8ec | 28 | * @param response Node info struct |
RuslanUrya | 0:dfe6edabb8ec | 29 | */ |
RuslanUrya | 0:dfe6edabb8ec | 30 | virtual void handleNodeInfoRetrieved(NodeID node_id, const protocol::GetNodeInfo::Response& node_info) = 0; |
RuslanUrya | 0:dfe6edabb8ec | 31 | |
RuslanUrya | 0:dfe6edabb8ec | 32 | /** |
RuslanUrya | 0:dfe6edabb8ec | 33 | * Called when the retriever decides that the node does not support the GetNodeInfo service. |
RuslanUrya | 0:dfe6edabb8ec | 34 | * This method will never be called if the number of attempts is unlimited. |
RuslanUrya | 0:dfe6edabb8ec | 35 | */ |
RuslanUrya | 0:dfe6edabb8ec | 36 | virtual void handleNodeInfoUnavailable(NodeID node_id) = 0; |
RuslanUrya | 0:dfe6edabb8ec | 37 | |
RuslanUrya | 0:dfe6edabb8ec | 38 | /** |
RuslanUrya | 0:dfe6edabb8ec | 39 | * This call is routed directly from @ref NodeStatusMonitor. |
RuslanUrya | 0:dfe6edabb8ec | 40 | * Default implementation does nothing. |
RuslanUrya | 0:dfe6edabb8ec | 41 | * @param event Node status change event |
RuslanUrya | 0:dfe6edabb8ec | 42 | */ |
RuslanUrya | 0:dfe6edabb8ec | 43 | virtual void handleNodeStatusChange(const NodeStatusMonitor::NodeStatusChangeEvent& event) |
RuslanUrya | 0:dfe6edabb8ec | 44 | { |
RuslanUrya | 0:dfe6edabb8ec | 45 | (void)event; |
RuslanUrya | 0:dfe6edabb8ec | 46 | } |
RuslanUrya | 0:dfe6edabb8ec | 47 | |
RuslanUrya | 0:dfe6edabb8ec | 48 | /** |
RuslanUrya | 0:dfe6edabb8ec | 49 | * This call is routed directly from @ref NodeStatusMonitor. |
RuslanUrya | 0:dfe6edabb8ec | 50 | * Default implementation does nothing. |
RuslanUrya | 0:dfe6edabb8ec | 51 | * @param msg Node status message |
RuslanUrya | 0:dfe6edabb8ec | 52 | */ |
RuslanUrya | 0:dfe6edabb8ec | 53 | virtual void handleNodeStatusMessage(const ReceivedDataStructure<protocol::NodeStatus>& msg) |
RuslanUrya | 0:dfe6edabb8ec | 54 | { |
RuslanUrya | 0:dfe6edabb8ec | 55 | (void)msg; |
RuslanUrya | 0:dfe6edabb8ec | 56 | } |
RuslanUrya | 0:dfe6edabb8ec | 57 | |
RuslanUrya | 0:dfe6edabb8ec | 58 | virtual ~INodeInfoListener() { } |
RuslanUrya | 0:dfe6edabb8ec | 59 | }; |
RuslanUrya | 0:dfe6edabb8ec | 60 | |
RuslanUrya | 0:dfe6edabb8ec | 61 | /** |
RuslanUrya | 0:dfe6edabb8ec | 62 | * This class automatically retrieves a response to GetNodeInfo once a node appears online or restarts. |
RuslanUrya | 0:dfe6edabb8ec | 63 | * It does a number of attempts in case if there's a communication failure before assuming that the node does not |
RuslanUrya | 0:dfe6edabb8ec | 64 | * implement the GetNodeInfo service. All parameters are pre-configured with sensible default values that should fit |
RuslanUrya | 0:dfe6edabb8ec | 65 | * virtually any use case, but they can be overriden if needed - refer to the setter methods below for details. |
RuslanUrya | 0:dfe6edabb8ec | 66 | * |
RuslanUrya | 0:dfe6edabb8ec | 67 | * Defaults are pre-configured so that the class is able to query 123 nodes (node ID 1..125, where 1 is our local |
RuslanUrya | 0:dfe6edabb8ec | 68 | * node and 1 is one node that implements GetNodeInfo service, hence 123) of which none implements GetNodeInfo |
RuslanUrya | 0:dfe6edabb8ec | 69 | * service in under 5 seconds. The 5 second limitation is imposed by UAVCAN-compatible bootloaders, which are |
RuslanUrya | 0:dfe6edabb8ec | 70 | * unlikely to wait for more than that before continuing to boot. In case if this default value is not appropriate |
RuslanUrya | 0:dfe6edabb8ec | 71 | * for the end application, the request interval can be overriden via @ref setRequestInterval(). |
RuslanUrya | 0:dfe6edabb8ec | 72 | * |
RuslanUrya | 0:dfe6edabb8ec | 73 | * Following the above explained requirements, the default request interval is defined as follows: |
RuslanUrya | 0:dfe6edabb8ec | 74 | * request interval [ms] = floor(5000 [ms] bootloader timeout / 123 nodes) |
RuslanUrya | 0:dfe6edabb8ec | 75 | * Which yields 40 ms. |
RuslanUrya | 0:dfe6edabb8ec | 76 | * |
RuslanUrya | 0:dfe6edabb8ec | 77 | * Given default service timeout 1000 ms and the defined above request frequency 40 ms, the maximum number of |
RuslanUrya | 0:dfe6edabb8ec | 78 | * concurrent requests will be: |
RuslanUrya | 0:dfe6edabb8ec | 79 | * max concurrent requests = ceil(1000 [ms] timeout / 40 [ms] request interval) |
RuslanUrya | 0:dfe6edabb8ec | 80 | * Which yields 25 requests. |
RuslanUrya | 0:dfe6edabb8ec | 81 | * |
RuslanUrya | 0:dfe6edabb8ec | 82 | * Keep the above equations in mind when changing the default request interval. |
RuslanUrya | 0:dfe6edabb8ec | 83 | * |
RuslanUrya | 0:dfe6edabb8ec | 84 | * Obviously, if all calls are completing in under (request interval), the number of concurrent requests will never |
RuslanUrya | 0:dfe6edabb8ec | 85 | * exceed one. This is actually the most likely scenario. |
RuslanUrya | 0:dfe6edabb8ec | 86 | * |
RuslanUrya | 0:dfe6edabb8ec | 87 | * Note that all nodes are queried in a round-robin fashion, regardless of their uptime, number of requests made, etc. |
RuslanUrya | 0:dfe6edabb8ec | 88 | * |
RuslanUrya | 0:dfe6edabb8ec | 89 | * Events from this class can be routed to many listeners, @ref INodeInfoListener. |
RuslanUrya | 0:dfe6edabb8ec | 90 | */ |
RuslanUrya | 0:dfe6edabb8ec | 91 | class UAVCAN_EXPORT NodeInfoRetriever : public NodeStatusMonitor |
RuslanUrya | 0:dfe6edabb8ec | 92 | , TimerBase |
RuslanUrya | 0:dfe6edabb8ec | 93 | { |
RuslanUrya | 0:dfe6edabb8ec | 94 | public: |
RuslanUrya | 0:dfe6edabb8ec | 95 | enum { MaxNumRequestAttempts = 254 }; |
RuslanUrya | 0:dfe6edabb8ec | 96 | enum { UnlimitedRequestAttempts = 0 }; |
RuslanUrya | 0:dfe6edabb8ec | 97 | |
RuslanUrya | 0:dfe6edabb8ec | 98 | private: |
RuslanUrya | 0:dfe6edabb8ec | 99 | typedef MethodBinder<NodeInfoRetriever*, |
RuslanUrya | 0:dfe6edabb8ec | 100 | void (NodeInfoRetriever::*)(const ServiceCallResult<protocol::GetNodeInfo>&)> |
RuslanUrya | 0:dfe6edabb8ec | 101 | GetNodeInfoResponseCallback; |
RuslanUrya | 0:dfe6edabb8ec | 102 | |
RuslanUrya | 0:dfe6edabb8ec | 103 | struct Entry |
RuslanUrya | 0:dfe6edabb8ec | 104 | { |
RuslanUrya | 0:dfe6edabb8ec | 105 | uint32_t uptime_sec; |
RuslanUrya | 0:dfe6edabb8ec | 106 | uint8_t num_attempts_made; |
RuslanUrya | 0:dfe6edabb8ec | 107 | bool request_needed; ///< Always false for unknown nodes |
RuslanUrya | 0:dfe6edabb8ec | 108 | bool updated_since_last_attempt; ///< Always false for unknown nodes |
RuslanUrya | 0:dfe6edabb8ec | 109 | |
RuslanUrya | 0:dfe6edabb8ec | 110 | Entry() |
RuslanUrya | 0:dfe6edabb8ec | 111 | : uptime_sec(0) |
RuslanUrya | 0:dfe6edabb8ec | 112 | , num_attempts_made(0) |
RuslanUrya | 0:dfe6edabb8ec | 113 | , request_needed(false) |
RuslanUrya | 0:dfe6edabb8ec | 114 | , updated_since_last_attempt(false) |
RuslanUrya | 0:dfe6edabb8ec | 115 | { |
RuslanUrya | 0:dfe6edabb8ec | 116 | #if UAVCAN_DEBUG |
RuslanUrya | 0:dfe6edabb8ec | 117 | StaticAssert<sizeof(Entry) <= 8>::check(); |
RuslanUrya | 0:dfe6edabb8ec | 118 | #endif |
RuslanUrya | 0:dfe6edabb8ec | 119 | } |
RuslanUrya | 0:dfe6edabb8ec | 120 | }; |
RuslanUrya | 0:dfe6edabb8ec | 121 | |
RuslanUrya | 0:dfe6edabb8ec | 122 | struct NodeInfoRetrievedHandlerCaller |
RuslanUrya | 0:dfe6edabb8ec | 123 | { |
RuslanUrya | 0:dfe6edabb8ec | 124 | const NodeID node_id; |
RuslanUrya | 0:dfe6edabb8ec | 125 | const protocol::GetNodeInfo::Response& node_info; |
RuslanUrya | 0:dfe6edabb8ec | 126 | |
RuslanUrya | 0:dfe6edabb8ec | 127 | NodeInfoRetrievedHandlerCaller(NodeID arg_node_id, const protocol::GetNodeInfo::Response& arg_node_info) |
RuslanUrya | 0:dfe6edabb8ec | 128 | : node_id(arg_node_id) |
RuslanUrya | 0:dfe6edabb8ec | 129 | , node_info(arg_node_info) |
RuslanUrya | 0:dfe6edabb8ec | 130 | { } |
RuslanUrya | 0:dfe6edabb8ec | 131 | |
RuslanUrya | 0:dfe6edabb8ec | 132 | bool operator()(INodeInfoListener* key) |
RuslanUrya | 0:dfe6edabb8ec | 133 | { |
RuslanUrya | 0:dfe6edabb8ec | 134 | UAVCAN_ASSERT(key != UAVCAN_NULLPTR); |
RuslanUrya | 0:dfe6edabb8ec | 135 | key->handleNodeInfoRetrieved(node_id, node_info); |
RuslanUrya | 0:dfe6edabb8ec | 136 | return false; |
RuslanUrya | 0:dfe6edabb8ec | 137 | } |
RuslanUrya | 0:dfe6edabb8ec | 138 | }; |
RuslanUrya | 0:dfe6edabb8ec | 139 | |
RuslanUrya | 0:dfe6edabb8ec | 140 | template <typename Event> |
RuslanUrya | 0:dfe6edabb8ec | 141 | struct GenericHandlerCaller |
RuslanUrya | 0:dfe6edabb8ec | 142 | { |
RuslanUrya | 0:dfe6edabb8ec | 143 | void (INodeInfoListener::* const method)(Event); |
RuslanUrya | 0:dfe6edabb8ec | 144 | Event event; |
RuslanUrya | 0:dfe6edabb8ec | 145 | |
RuslanUrya | 0:dfe6edabb8ec | 146 | GenericHandlerCaller(void (INodeInfoListener::*arg_method)(Event), Event arg_event) |
RuslanUrya | 0:dfe6edabb8ec | 147 | : method(arg_method) |
RuslanUrya | 0:dfe6edabb8ec | 148 | , event(arg_event) |
RuslanUrya | 0:dfe6edabb8ec | 149 | { } |
RuslanUrya | 0:dfe6edabb8ec | 150 | |
RuslanUrya | 0:dfe6edabb8ec | 151 | bool operator()(INodeInfoListener* key) |
RuslanUrya | 0:dfe6edabb8ec | 152 | { |
RuslanUrya | 0:dfe6edabb8ec | 153 | UAVCAN_ASSERT(key != UAVCAN_NULLPTR); |
RuslanUrya | 0:dfe6edabb8ec | 154 | (key->*method)(event); |
RuslanUrya | 0:dfe6edabb8ec | 155 | return false; |
RuslanUrya | 0:dfe6edabb8ec | 156 | } |
RuslanUrya | 0:dfe6edabb8ec | 157 | }; |
RuslanUrya | 0:dfe6edabb8ec | 158 | |
RuslanUrya | 0:dfe6edabb8ec | 159 | enum { DefaultNumRequestAttempts = 16 }; |
RuslanUrya | 0:dfe6edabb8ec | 160 | enum { DefaultTimerIntervalMSec = 40 }; ///< Read explanation in the class documentation |
RuslanUrya | 0:dfe6edabb8ec | 161 | |
RuslanUrya | 0:dfe6edabb8ec | 162 | /* |
RuslanUrya | 0:dfe6edabb8ec | 163 | * State |
RuslanUrya | 0:dfe6edabb8ec | 164 | */ |
RuslanUrya | 0:dfe6edabb8ec | 165 | Entry entries_[NodeID::Max]; // [1, NodeID::Max] |
RuslanUrya | 0:dfe6edabb8ec | 166 | |
RuslanUrya | 0:dfe6edabb8ec | 167 | Multiset<INodeInfoListener*> listeners_; |
RuslanUrya | 0:dfe6edabb8ec | 168 | |
RuslanUrya | 0:dfe6edabb8ec | 169 | ServiceClient<protocol::GetNodeInfo, GetNodeInfoResponseCallback> get_node_info_client_; |
RuslanUrya | 0:dfe6edabb8ec | 170 | |
RuslanUrya | 0:dfe6edabb8ec | 171 | MonotonicDuration request_interval_; |
RuslanUrya | 0:dfe6edabb8ec | 172 | |
RuslanUrya | 0:dfe6edabb8ec | 173 | mutable uint8_t last_picked_node_; |
RuslanUrya | 0:dfe6edabb8ec | 174 | |
RuslanUrya | 0:dfe6edabb8ec | 175 | uint8_t num_attempts_; |
RuslanUrya | 0:dfe6edabb8ec | 176 | |
RuslanUrya | 0:dfe6edabb8ec | 177 | /* |
RuslanUrya | 0:dfe6edabb8ec | 178 | * Methods |
RuslanUrya | 0:dfe6edabb8ec | 179 | */ |
RuslanUrya | 0:dfe6edabb8ec | 180 | const Entry& getEntry(NodeID node_id) const { return const_cast<NodeInfoRetriever*>(this)->getEntry(node_id); } |
RuslanUrya | 0:dfe6edabb8ec | 181 | Entry& getEntry(NodeID node_id) |
RuslanUrya | 0:dfe6edabb8ec | 182 | { |
RuslanUrya | 0:dfe6edabb8ec | 183 | if (node_id.get() < 1 || node_id.get() > NodeID::Max) |
RuslanUrya | 0:dfe6edabb8ec | 184 | { |
RuslanUrya | 0:dfe6edabb8ec | 185 | handleFatalError("NodeInfoRetriever NodeID"); |
RuslanUrya | 0:dfe6edabb8ec | 186 | } |
RuslanUrya | 0:dfe6edabb8ec | 187 | return entries_[node_id.get() - 1]; |
RuslanUrya | 0:dfe6edabb8ec | 188 | } |
RuslanUrya | 0:dfe6edabb8ec | 189 | |
RuslanUrya | 0:dfe6edabb8ec | 190 | void startTimerIfNotRunning() |
RuslanUrya | 0:dfe6edabb8ec | 191 | { |
RuslanUrya | 0:dfe6edabb8ec | 192 | if (!TimerBase::isRunning()) |
RuslanUrya | 0:dfe6edabb8ec | 193 | { |
RuslanUrya | 0:dfe6edabb8ec | 194 | TimerBase::startPeriodic(request_interval_); |
RuslanUrya | 0:dfe6edabb8ec | 195 | UAVCAN_TRACE("NodeInfoRetriever", "Timer started, interval %s sec", request_interval_.toString().c_str()); |
RuslanUrya | 0:dfe6edabb8ec | 196 | } |
RuslanUrya | 0:dfe6edabb8ec | 197 | } |
RuslanUrya | 0:dfe6edabb8ec | 198 | |
RuslanUrya | 0:dfe6edabb8ec | 199 | NodeID pickNextNodeToQuery(bool& out_at_least_one_request_needed) const |
RuslanUrya | 0:dfe6edabb8ec | 200 | { |
RuslanUrya | 0:dfe6edabb8ec | 201 | out_at_least_one_request_needed = false; |
RuslanUrya | 0:dfe6edabb8ec | 202 | |
RuslanUrya | 0:dfe6edabb8ec | 203 | for (unsigned iter_cnt_ = 0; iter_cnt_ < (sizeof(entries_) / sizeof(entries_[0])); iter_cnt_++) // Round-robin |
RuslanUrya | 0:dfe6edabb8ec | 204 | { |
RuslanUrya | 0:dfe6edabb8ec | 205 | last_picked_node_++; |
RuslanUrya | 0:dfe6edabb8ec | 206 | if (last_picked_node_ > NodeID::Max) |
RuslanUrya | 0:dfe6edabb8ec | 207 | { |
RuslanUrya | 0:dfe6edabb8ec | 208 | last_picked_node_ = 1; |
RuslanUrya | 0:dfe6edabb8ec | 209 | } |
RuslanUrya | 0:dfe6edabb8ec | 210 | UAVCAN_ASSERT((last_picked_node_ >= 1) && |
RuslanUrya | 0:dfe6edabb8ec | 211 | (last_picked_node_ <= NodeID::Max)); |
RuslanUrya | 0:dfe6edabb8ec | 212 | |
RuslanUrya | 0:dfe6edabb8ec | 213 | const Entry& entry = getEntry(last_picked_node_); |
RuslanUrya | 0:dfe6edabb8ec | 214 | |
RuslanUrya | 0:dfe6edabb8ec | 215 | if (entry.request_needed) |
RuslanUrya | 0:dfe6edabb8ec | 216 | { |
RuslanUrya | 0:dfe6edabb8ec | 217 | out_at_least_one_request_needed = true; |
RuslanUrya | 0:dfe6edabb8ec | 218 | |
RuslanUrya | 0:dfe6edabb8ec | 219 | if (entry.updated_since_last_attempt && |
RuslanUrya | 0:dfe6edabb8ec | 220 | !get_node_info_client_.hasPendingCallToServer(last_picked_node_)) |
RuslanUrya | 0:dfe6edabb8ec | 221 | { |
RuslanUrya | 0:dfe6edabb8ec | 222 | UAVCAN_TRACE("NodeInfoRetriever", "Next node to query: %d", int(last_picked_node_)); |
RuslanUrya | 0:dfe6edabb8ec | 223 | return NodeID(last_picked_node_); |
RuslanUrya | 0:dfe6edabb8ec | 224 | } |
RuslanUrya | 0:dfe6edabb8ec | 225 | } |
RuslanUrya | 0:dfe6edabb8ec | 226 | } |
RuslanUrya | 0:dfe6edabb8ec | 227 | |
RuslanUrya | 0:dfe6edabb8ec | 228 | return NodeID(); // No node could be found |
RuslanUrya | 0:dfe6edabb8ec | 229 | } |
RuslanUrya | 0:dfe6edabb8ec | 230 | |
RuslanUrya | 0:dfe6edabb8ec | 231 | virtual void handleTimerEvent(const TimerEvent&) |
RuslanUrya | 0:dfe6edabb8ec | 232 | { |
RuslanUrya | 0:dfe6edabb8ec | 233 | bool at_least_one_request_needed = false; |
RuslanUrya | 0:dfe6edabb8ec | 234 | const NodeID next = pickNextNodeToQuery(at_least_one_request_needed); |
RuslanUrya | 0:dfe6edabb8ec | 235 | |
RuslanUrya | 0:dfe6edabb8ec | 236 | if (next.isUnicast()) |
RuslanUrya | 0:dfe6edabb8ec | 237 | { |
RuslanUrya | 0:dfe6edabb8ec | 238 | UAVCAN_ASSERT(at_least_one_request_needed); |
RuslanUrya | 0:dfe6edabb8ec | 239 | getEntry(next).updated_since_last_attempt = false; |
RuslanUrya | 0:dfe6edabb8ec | 240 | const int res = get_node_info_client_.call(next, protocol::GetNodeInfo::Request()); |
RuslanUrya | 0:dfe6edabb8ec | 241 | if (res < 0) |
RuslanUrya | 0:dfe6edabb8ec | 242 | { |
RuslanUrya | 0:dfe6edabb8ec | 243 | get_node_info_client_.getNode().registerInternalFailure("NodeInfoRetriever GetNodeInfo call"); |
RuslanUrya | 0:dfe6edabb8ec | 244 | } |
RuslanUrya | 0:dfe6edabb8ec | 245 | } |
RuslanUrya | 0:dfe6edabb8ec | 246 | else |
RuslanUrya | 0:dfe6edabb8ec | 247 | { |
RuslanUrya | 0:dfe6edabb8ec | 248 | if (!at_least_one_request_needed) |
RuslanUrya | 0:dfe6edabb8ec | 249 | { |
RuslanUrya | 0:dfe6edabb8ec | 250 | TimerBase::stop(); |
RuslanUrya | 0:dfe6edabb8ec | 251 | UAVCAN_TRACE("NodeInfoRetriever", "Timer stopped"); |
RuslanUrya | 0:dfe6edabb8ec | 252 | } |
RuslanUrya | 0:dfe6edabb8ec | 253 | } |
RuslanUrya | 0:dfe6edabb8ec | 254 | } |
RuslanUrya | 0:dfe6edabb8ec | 255 | |
RuslanUrya | 0:dfe6edabb8ec | 256 | virtual void handleNodeStatusChange(const NodeStatusChangeEvent& event) |
RuslanUrya | 0:dfe6edabb8ec | 257 | { |
RuslanUrya | 0:dfe6edabb8ec | 258 | const bool was_offline = !event.was_known || |
RuslanUrya | 0:dfe6edabb8ec | 259 | (event.old_status.mode == protocol::NodeStatus::MODE_OFFLINE); |
RuslanUrya | 0:dfe6edabb8ec | 260 | |
RuslanUrya | 0:dfe6edabb8ec | 261 | const bool offline_now = event.status.mode == protocol::NodeStatus::MODE_OFFLINE; |
RuslanUrya | 0:dfe6edabb8ec | 262 | |
RuslanUrya | 0:dfe6edabb8ec | 263 | if (was_offline || offline_now) |
RuslanUrya | 0:dfe6edabb8ec | 264 | { |
RuslanUrya | 0:dfe6edabb8ec | 265 | Entry& entry = getEntry(event.node_id); |
RuslanUrya | 0:dfe6edabb8ec | 266 | |
RuslanUrya | 0:dfe6edabb8ec | 267 | entry.request_needed = !offline_now; |
RuslanUrya | 0:dfe6edabb8ec | 268 | entry.num_attempts_made = 0; |
RuslanUrya | 0:dfe6edabb8ec | 269 | |
RuslanUrya | 0:dfe6edabb8ec | 270 | UAVCAN_TRACE("NodeInfoRetriever", "Offline status change: node ID %d, request needed: %d", |
RuslanUrya | 0:dfe6edabb8ec | 271 | int(event.node_id.get()), int(entry.request_needed)); |
RuslanUrya | 0:dfe6edabb8ec | 272 | |
RuslanUrya | 0:dfe6edabb8ec | 273 | if (entry.request_needed) |
RuslanUrya | 0:dfe6edabb8ec | 274 | { |
RuslanUrya | 0:dfe6edabb8ec | 275 | startTimerIfNotRunning(); |
RuslanUrya | 0:dfe6edabb8ec | 276 | } |
RuslanUrya | 0:dfe6edabb8ec | 277 | } |
RuslanUrya | 0:dfe6edabb8ec | 278 | |
RuslanUrya | 0:dfe6edabb8ec | 279 | listeners_.forEach( |
RuslanUrya | 0:dfe6edabb8ec | 280 | GenericHandlerCaller<const NodeStatusChangeEvent&>(&INodeInfoListener::handleNodeStatusChange, event)); |
RuslanUrya | 0:dfe6edabb8ec | 281 | } |
RuslanUrya | 0:dfe6edabb8ec | 282 | |
RuslanUrya | 0:dfe6edabb8ec | 283 | virtual void handleNodeStatusMessage(const ReceivedDataStructure<protocol::NodeStatus>& msg) |
RuslanUrya | 0:dfe6edabb8ec | 284 | { |
RuslanUrya | 0:dfe6edabb8ec | 285 | Entry& entry = getEntry(msg.getSrcNodeID()); |
RuslanUrya | 0:dfe6edabb8ec | 286 | |
RuslanUrya | 0:dfe6edabb8ec | 287 | if (msg.uptime_sec < entry.uptime_sec) |
RuslanUrya | 0:dfe6edabb8ec | 288 | { |
RuslanUrya | 0:dfe6edabb8ec | 289 | entry.request_needed = true; |
RuslanUrya | 0:dfe6edabb8ec | 290 | entry.num_attempts_made = 0; |
RuslanUrya | 0:dfe6edabb8ec | 291 | |
RuslanUrya | 0:dfe6edabb8ec | 292 | startTimerIfNotRunning(); |
RuslanUrya | 0:dfe6edabb8ec | 293 | } |
RuslanUrya | 0:dfe6edabb8ec | 294 | entry.uptime_sec = msg.uptime_sec; |
RuslanUrya | 0:dfe6edabb8ec | 295 | entry.updated_since_last_attempt = true; |
RuslanUrya | 0:dfe6edabb8ec | 296 | |
RuslanUrya | 0:dfe6edabb8ec | 297 | listeners_.forEach(GenericHandlerCaller<const ReceivedDataStructure<protocol::NodeStatus>&>( |
RuslanUrya | 0:dfe6edabb8ec | 298 | &INodeInfoListener::handleNodeStatusMessage, msg)); |
RuslanUrya | 0:dfe6edabb8ec | 299 | } |
RuslanUrya | 0:dfe6edabb8ec | 300 | |
RuslanUrya | 0:dfe6edabb8ec | 301 | void handleGetNodeInfoResponse(const ServiceCallResult<protocol::GetNodeInfo>& result) |
RuslanUrya | 0:dfe6edabb8ec | 302 | { |
RuslanUrya | 0:dfe6edabb8ec | 303 | Entry& entry = getEntry(result.getCallID().server_node_id); |
RuslanUrya | 0:dfe6edabb8ec | 304 | |
RuslanUrya | 0:dfe6edabb8ec | 305 | if (result.isSuccessful()) |
RuslanUrya | 0:dfe6edabb8ec | 306 | { |
RuslanUrya | 0:dfe6edabb8ec | 307 | /* |
RuslanUrya | 0:dfe6edabb8ec | 308 | * Updating the uptime here allows to properly handle a corner case where the service response arrives |
RuslanUrya | 0:dfe6edabb8ec | 309 | * after the device has restarted and published its new NodeStatus (although it's unlikely to happen). |
RuslanUrya | 0:dfe6edabb8ec | 310 | */ |
RuslanUrya | 0:dfe6edabb8ec | 311 | entry.uptime_sec = result.getResponse().status.uptime_sec; |
RuslanUrya | 0:dfe6edabb8ec | 312 | entry.request_needed = false; |
RuslanUrya | 0:dfe6edabb8ec | 313 | listeners_.forEach(NodeInfoRetrievedHandlerCaller(result.getCallID().server_node_id, |
RuslanUrya | 0:dfe6edabb8ec | 314 | result.getResponse())); |
RuslanUrya | 0:dfe6edabb8ec | 315 | } |
RuslanUrya | 0:dfe6edabb8ec | 316 | else |
RuslanUrya | 0:dfe6edabb8ec | 317 | { |
RuslanUrya | 0:dfe6edabb8ec | 318 | if (num_attempts_ != UnlimitedRequestAttempts) |
RuslanUrya | 0:dfe6edabb8ec | 319 | { |
RuslanUrya | 0:dfe6edabb8ec | 320 | entry.num_attempts_made++; |
RuslanUrya | 0:dfe6edabb8ec | 321 | if (entry.num_attempts_made >= num_attempts_) |
RuslanUrya | 0:dfe6edabb8ec | 322 | { |
RuslanUrya | 0:dfe6edabb8ec | 323 | entry.request_needed = false; |
RuslanUrya | 0:dfe6edabb8ec | 324 | listeners_.forEach(GenericHandlerCaller<NodeID>(&INodeInfoListener::handleNodeInfoUnavailable, |
RuslanUrya | 0:dfe6edabb8ec | 325 | result.getCallID().server_node_id)); |
RuslanUrya | 0:dfe6edabb8ec | 326 | } |
RuslanUrya | 0:dfe6edabb8ec | 327 | } |
RuslanUrya | 0:dfe6edabb8ec | 328 | } |
RuslanUrya | 0:dfe6edabb8ec | 329 | } |
RuslanUrya | 0:dfe6edabb8ec | 330 | |
RuslanUrya | 0:dfe6edabb8ec | 331 | public: |
RuslanUrya | 0:dfe6edabb8ec | 332 | NodeInfoRetriever(INode& node) |
RuslanUrya | 0:dfe6edabb8ec | 333 | : NodeStatusMonitor(node) |
RuslanUrya | 0:dfe6edabb8ec | 334 | , TimerBase(node) |
RuslanUrya | 0:dfe6edabb8ec | 335 | , listeners_(node.getAllocator()) |
RuslanUrya | 0:dfe6edabb8ec | 336 | , get_node_info_client_(node) |
RuslanUrya | 0:dfe6edabb8ec | 337 | , request_interval_(MonotonicDuration::fromMSec(DefaultTimerIntervalMSec)) |
RuslanUrya | 0:dfe6edabb8ec | 338 | , last_picked_node_(1) |
RuslanUrya | 0:dfe6edabb8ec | 339 | , num_attempts_(DefaultNumRequestAttempts) |
RuslanUrya | 0:dfe6edabb8ec | 340 | { } |
RuslanUrya | 0:dfe6edabb8ec | 341 | |
RuslanUrya | 0:dfe6edabb8ec | 342 | /** |
RuslanUrya | 0:dfe6edabb8ec | 343 | * Starts the retriever. |
RuslanUrya | 0:dfe6edabb8ec | 344 | * Destroy the object to stop it. |
RuslanUrya | 0:dfe6edabb8ec | 345 | * Returns negative error code. |
RuslanUrya | 0:dfe6edabb8ec | 346 | */ |
RuslanUrya | 0:dfe6edabb8ec | 347 | int start(const TransferPriority priority = TransferPriority::OneHigherThanLowest) |
RuslanUrya | 0:dfe6edabb8ec | 348 | { |
RuslanUrya | 0:dfe6edabb8ec | 349 | int res = NodeStatusMonitor::start(); |
RuslanUrya | 0:dfe6edabb8ec | 350 | if (res < 0) |
RuslanUrya | 0:dfe6edabb8ec | 351 | { |
RuslanUrya | 0:dfe6edabb8ec | 352 | return res; |
RuslanUrya | 0:dfe6edabb8ec | 353 | } |
RuslanUrya | 0:dfe6edabb8ec | 354 | |
RuslanUrya | 0:dfe6edabb8ec | 355 | res = get_node_info_client_.init(priority); |
RuslanUrya | 0:dfe6edabb8ec | 356 | if (res < 0) |
RuslanUrya | 0:dfe6edabb8ec | 357 | { |
RuslanUrya | 0:dfe6edabb8ec | 358 | return res; |
RuslanUrya | 0:dfe6edabb8ec | 359 | } |
RuslanUrya | 0:dfe6edabb8ec | 360 | get_node_info_client_.setCallback(GetNodeInfoResponseCallback(this, |
RuslanUrya | 0:dfe6edabb8ec | 361 | &NodeInfoRetriever::handleGetNodeInfoResponse)); |
RuslanUrya | 0:dfe6edabb8ec | 362 | // Note: the timer will be started ad-hoc |
RuslanUrya | 0:dfe6edabb8ec | 363 | return 0; |
RuslanUrya | 0:dfe6edabb8ec | 364 | } |
RuslanUrya | 0:dfe6edabb8ec | 365 | |
RuslanUrya | 0:dfe6edabb8ec | 366 | /** |
RuslanUrya | 0:dfe6edabb8ec | 367 | * This method forces the class to re-request uavcan.protocol.GetNodeInfo from all nodes as if they |
RuslanUrya | 0:dfe6edabb8ec | 368 | * have just appeared in the network. |
RuslanUrya | 0:dfe6edabb8ec | 369 | */ |
RuslanUrya | 0:dfe6edabb8ec | 370 | void invalidateAll() |
RuslanUrya | 0:dfe6edabb8ec | 371 | { |
RuslanUrya | 0:dfe6edabb8ec | 372 | NodeStatusMonitor::forgetAllNodes(); |
RuslanUrya | 0:dfe6edabb8ec | 373 | get_node_info_client_.cancelAllCalls(); |
RuslanUrya | 0:dfe6edabb8ec | 374 | |
RuslanUrya | 0:dfe6edabb8ec | 375 | for (unsigned i = 0; i < (sizeof(entries_) / sizeof(entries_[0])); i++) |
RuslanUrya | 0:dfe6edabb8ec | 376 | { |
RuslanUrya | 0:dfe6edabb8ec | 377 | entries_[i] = Entry(); |
RuslanUrya | 0:dfe6edabb8ec | 378 | } |
RuslanUrya | 0:dfe6edabb8ec | 379 | // It is not necessary to reset the last picked node index |
RuslanUrya | 0:dfe6edabb8ec | 380 | } |
RuslanUrya | 0:dfe6edabb8ec | 381 | |
RuslanUrya | 0:dfe6edabb8ec | 382 | /** |
RuslanUrya | 0:dfe6edabb8ec | 383 | * Adds one listener. Does nothing if such listener already exists. |
RuslanUrya | 0:dfe6edabb8ec | 384 | * May return -ErrMemory if there's no space to add the listener. |
RuslanUrya | 0:dfe6edabb8ec | 385 | */ |
RuslanUrya | 0:dfe6edabb8ec | 386 | int addListener(INodeInfoListener* listener) |
RuslanUrya | 0:dfe6edabb8ec | 387 | { |
RuslanUrya | 0:dfe6edabb8ec | 388 | if (listener != UAVCAN_NULLPTR) |
RuslanUrya | 0:dfe6edabb8ec | 389 | { |
RuslanUrya | 0:dfe6edabb8ec | 390 | removeListener(listener); |
RuslanUrya | 0:dfe6edabb8ec | 391 | return (UAVCAN_NULLPTR == listeners_.emplace(listener)) ? -ErrMemory : 0; |
RuslanUrya | 0:dfe6edabb8ec | 392 | } |
RuslanUrya | 0:dfe6edabb8ec | 393 | else |
RuslanUrya | 0:dfe6edabb8ec | 394 | { |
RuslanUrya | 0:dfe6edabb8ec | 395 | return -ErrInvalidParam; |
RuslanUrya | 0:dfe6edabb8ec | 396 | } |
RuslanUrya | 0:dfe6edabb8ec | 397 | } |
RuslanUrya | 0:dfe6edabb8ec | 398 | |
RuslanUrya | 0:dfe6edabb8ec | 399 | /** |
RuslanUrya | 0:dfe6edabb8ec | 400 | * Removes the listener. |
RuslanUrya | 0:dfe6edabb8ec | 401 | * If the listener was not registered, nothing will be done. |
RuslanUrya | 0:dfe6edabb8ec | 402 | */ |
RuslanUrya | 0:dfe6edabb8ec | 403 | void removeListener(INodeInfoListener* listener) |
RuslanUrya | 0:dfe6edabb8ec | 404 | { |
RuslanUrya | 0:dfe6edabb8ec | 405 | if (listener != UAVCAN_NULLPTR) |
RuslanUrya | 0:dfe6edabb8ec | 406 | { |
RuslanUrya | 0:dfe6edabb8ec | 407 | listeners_.removeAll(listener); |
RuslanUrya | 0:dfe6edabb8ec | 408 | } |
RuslanUrya | 0:dfe6edabb8ec | 409 | else |
RuslanUrya | 0:dfe6edabb8ec | 410 | { |
RuslanUrya | 0:dfe6edabb8ec | 411 | UAVCAN_ASSERT(0); |
RuslanUrya | 0:dfe6edabb8ec | 412 | } |
RuslanUrya | 0:dfe6edabb8ec | 413 | } |
RuslanUrya | 0:dfe6edabb8ec | 414 | |
RuslanUrya | 0:dfe6edabb8ec | 415 | unsigned getNumListeners() const { return listeners_.getSize(); } |
RuslanUrya | 0:dfe6edabb8ec | 416 | |
RuslanUrya | 0:dfe6edabb8ec | 417 | /** |
RuslanUrya | 0:dfe6edabb8ec | 418 | * Number of attempts to retrieve GetNodeInfo response before giving up on the assumption that the service is |
RuslanUrya | 0:dfe6edabb8ec | 419 | * not implemented. |
RuslanUrya | 0:dfe6edabb8ec | 420 | * Zero is a special value that can be used to set unlimited number of attempts, @ref UnlimitedRequestAttempts. |
RuslanUrya | 0:dfe6edabb8ec | 421 | */ |
RuslanUrya | 0:dfe6edabb8ec | 422 | uint8_t getNumRequestAttempts() const { return num_attempts_; } |
RuslanUrya | 0:dfe6edabb8ec | 423 | void setNumRequestAttempts(const uint8_t num) |
RuslanUrya | 0:dfe6edabb8ec | 424 | { |
RuslanUrya | 0:dfe6edabb8ec | 425 | num_attempts_ = min(static_cast<uint8_t>(MaxNumRequestAttempts), num); |
RuslanUrya | 0:dfe6edabb8ec | 426 | } |
RuslanUrya | 0:dfe6edabb8ec | 427 | |
RuslanUrya | 0:dfe6edabb8ec | 428 | /** |
RuslanUrya | 0:dfe6edabb8ec | 429 | * Request interval also implicitly defines the maximum number of concurrent requests. |
RuslanUrya | 0:dfe6edabb8ec | 430 | * Read the class documentation for details. |
RuslanUrya | 0:dfe6edabb8ec | 431 | */ |
RuslanUrya | 0:dfe6edabb8ec | 432 | MonotonicDuration getRequestInterval() const { return request_interval_; } |
RuslanUrya | 0:dfe6edabb8ec | 433 | void setRequestInterval(const MonotonicDuration interval) |
RuslanUrya | 0:dfe6edabb8ec | 434 | { |
RuslanUrya | 0:dfe6edabb8ec | 435 | if (interval.isPositive()) |
RuslanUrya | 0:dfe6edabb8ec | 436 | { |
RuslanUrya | 0:dfe6edabb8ec | 437 | request_interval_ = interval; |
RuslanUrya | 0:dfe6edabb8ec | 438 | if (TimerBase::isRunning()) |
RuslanUrya | 0:dfe6edabb8ec | 439 | { |
RuslanUrya | 0:dfe6edabb8ec | 440 | TimerBase::startPeriodic(request_interval_); |
RuslanUrya | 0:dfe6edabb8ec | 441 | } |
RuslanUrya | 0:dfe6edabb8ec | 442 | } |
RuslanUrya | 0:dfe6edabb8ec | 443 | else |
RuslanUrya | 0:dfe6edabb8ec | 444 | { |
RuslanUrya | 0:dfe6edabb8ec | 445 | UAVCAN_ASSERT(0); |
RuslanUrya | 0:dfe6edabb8ec | 446 | } |
RuslanUrya | 0:dfe6edabb8ec | 447 | } |
RuslanUrya | 0:dfe6edabb8ec | 448 | |
RuslanUrya | 0:dfe6edabb8ec | 449 | /** |
RuslanUrya | 0:dfe6edabb8ec | 450 | * These methods are needed mostly for testing. |
RuslanUrya | 0:dfe6edabb8ec | 451 | */ |
RuslanUrya | 0:dfe6edabb8ec | 452 | bool isRetrievingInProgress() const { return TimerBase::isRunning(); } |
RuslanUrya | 0:dfe6edabb8ec | 453 | |
RuslanUrya | 0:dfe6edabb8ec | 454 | uint8_t getNumPendingRequests() const |
RuslanUrya | 0:dfe6edabb8ec | 455 | { |
RuslanUrya | 0:dfe6edabb8ec | 456 | const unsigned num = get_node_info_client_.getNumPendingCalls(); |
RuslanUrya | 0:dfe6edabb8ec | 457 | UAVCAN_ASSERT(num <= 0xFF); |
RuslanUrya | 0:dfe6edabb8ec | 458 | return static_cast<uint8_t>(num); |
RuslanUrya | 0:dfe6edabb8ec | 459 | } |
RuslanUrya | 0:dfe6edabb8ec | 460 | }; |
RuslanUrya | 0:dfe6edabb8ec | 461 | |
RuslanUrya | 0:dfe6edabb8ec | 462 | } |
RuslanUrya | 0:dfe6edabb8ec | 463 | |
RuslanUrya | 0:dfe6edabb8ec | 464 | #endif // Include guard |