libuav original

Dependents:   UAVCAN UAVCAN_Subscriber

Embed: (wiki syntax)

« Back to documentation index

Show/hide line numbers firmware_update_trigger.hpp Source File

firmware_update_trigger.hpp

00001 /*
00002  * Copyright (C) 2015 Pavel Kirienko <pavel.kirienko@gmail.com>
00003  */
00004 
00005 #ifndef UAVCAN_PROTOCOL_FIRMWARE_UPDATE_TRIGGER_HPP_INCLUDED
00006 #define UAVCAN_PROTOCOL_FIRMWARE_UPDATE_TRIGGER_HPP_INCLUDED
00007 
00008 #include <uavcan/build_config.hpp>
00009 #include <uavcan/debug.hpp>
00010 #include <uavcan/node/service_client.hpp>
00011 #include <uavcan/util/method_binder.hpp>
00012 #include <uavcan/util/map.hpp>
00013 #include <uavcan/protocol/node_info_retriever.hpp>
00014 // UAVCAN types
00015 #include <uavcan/protocol/file/BeginFirmwareUpdate.hpp>
00016 
00017 namespace uavcan
00018 {
00019 /**
00020  * Application-specific firmware version checking logic.
00021  * Refer to @ref FirmwareUpdateTrigger for details.
00022  */
00023 class IFirmwareVersionChecker
00024 {
00025 public:
00026     /**
00027      * This value is limited by the pool block size minus some extra data. Please refer to the Map<> documentation
00028      * for details. If this size is set too high, the compilation will fail in the Map<> template.
00029      */
00030     enum { MaxFirmwareFilePathLength = 40 };
00031 
00032     /**
00033      * This type is used to store path to firmware file that the target node will retrieve using the
00034      * service uavcan.protocol.file.Read. Note that the maximum length is limited due to some specifics of
00035      * libuavcan (@ref MaxFirmwareFilePathLength), this is NOT a protocol-level limitation.
00036      */
00037     typedef MakeString<MaxFirmwareFilePathLength>::Type FirmwareFilePath;
00038 
00039     /**
00040      * This method will be invoked when the class obtains a response to GetNodeInfo request.
00041      *
00042      * @param node_id                   Node ID that this GetNodeInfo response was received from.
00043      *
00044      * @param node_info                 Actual node info structure; refer to uavcan.protocol.GetNodeInfo for details.
00045      *
00046      * @param out_firmware_file_path    The implementation should return the firmware image path via this argument.
00047      *                                  Note that this path must be reachable via uavcan.protocol.file.Read service.
00048      *                                  Refer to @ref FileServer and @ref BasicFileServer for details.
00049      *
00050      * @return                          True - the class will begin sending update requests.
00051      *                                  False - the node will be ignored, no request will be sent.
00052      */
00053     virtual bool shouldRequestFirmwareUpdate(NodeID node_id, const protocol::GetNodeInfo::Response& node_info,
00054                                              FirmwareFilePath& out_firmware_file_path) = 0;
00055 
00056     /**
00057      * This method will be invoked when a node responds to the update request with an error. If the request simply
00058      * times out, this method will not be invoked.
00059      * Note that if by the time of arrival of the response the node is already removed, this method will not be called.
00060      *
00061      * SPECIAL CASE: If the node responds with ERROR_IN_PROGRESS, the class will assume that further requesting
00062      *               is not needed anymore. This method will not be invoked.
00063      *
00064      * @param node_id                   Node ID that returned this error.
00065      *
00066      * @param error_response            Contents of the error response. It contains error code and text.
00067      *
00068      * @param out_firmware_file_path    New firmware path if a retry is needed. Note that this argument will be
00069      *                                  initialized with old path, so if the same path needs to be reused, this
00070      *                                  argument should be left unchanged.
00071      *
00072      * @return                          True - the class will continue sending update requests with new firmware path.
00073      *                                  False - the node will be forgotten, new requests will not be sent.
00074      */
00075     virtual bool shouldRetryFirmwareUpdate(NodeID node_id,
00076                                            const protocol::file::BeginFirmwareUpdate::Response& error_response,
00077                                            FirmwareFilePath& out_firmware_file_path) = 0;
00078 
00079     /**
00080      * This node is invoked when the node responds to the update request with confirmation.
00081      * Note that if by the time of arrival of the response the node is already removed, this method will not be called.
00082      *
00083      * Implementation is optional; default one does nothing.
00084      *
00085      * @param node_id   Node ID that confirmed the request.
00086      *
00087      * @param response  Actual response.
00088      */
00089     virtual void handleFirmwareUpdateConfirmation(NodeID node_id,
00090                                                   const protocol::file::BeginFirmwareUpdate::Response& response)
00091     {
00092         (void)node_id;
00093         (void)response;
00094     }
00095 
00096     virtual ~IFirmwareVersionChecker() { }
00097 };
00098 
00099 /**
00100  * This class subscribes to updates from @ref NodeInfoRetriever in order to detect nodes that need firmware
00101  * updates. The decision process of whether a firmware update is needed is relayed to the application via
00102  * @ref IFirmwareVersionChecker. If the application confirms that the update is needed, this class will begin
00103  * sending uavcan.protocol.file.BeginFirmwareUpdate periodically (period is configurable) to every node that
00104  * needs an update in a round-robin fashion. There are the following termination conditions for the periodical
00105  * sending process:
00106  *
00107  * - The node responds with confirmation. In this case the class will forget about the node on the assumption
00108  *   that its job is done here. Confirmation will be reported to the application via the interface.
00109  *
00110  * - The node responds with an error, and the error code is not ERROR_IN_PROGRESS. In this case the class will
00111  *   request the application via the interface mentioned above about its further actions - either give up or
00112  *   retry, possibly with a different firmware.
00113  *
00114  * - The node responds with error ERROR_IN_PROGRESS. In this case the class behaves exactly in the same way as if
00115  *   response was successful (because the firmware is alredy being updated, so the goal is fulfilled).
00116  *   Confirmation will be reported to the application via the interface.
00117  *
00118  * - The node goes offline or restarts. In this case the node will be immediately forgotten, and the process
00119  *   will repeat again later because the node info retriever re-queries GetNodeInfo every time when a node restarts.
00120  *
00121  * Since the target node (i.e. node that is being updated) will try to retrieve the specified firmware file using
00122  * the file services (uavcan.protocol.file.*), the provided firmware path must be reachable for the file server
00123  * (@ref FileServer, @ref BasicFileServer). Normally, an application that serves as UAVCAN firmware update server
00124  * will include at least the following components:
00125  * - this firmware update trigger;
00126  * - dynamic node ID allocation server;
00127  * - file server.
00128  *
00129  * Implementation details: the class uses memory pool to keep the list of nodes that have not responded yet, which
00130  * limits the maximum length of the path to the firmware file, which is covered in @ref IFirmwareVersionChecker.
00131  * To somewhat relieve the maximum path length limitation, the class can be supplied with a common prefix that
00132  * will be prepended to firmware pathes before sending requests.
00133  * Interval at which requests are being sent is configurable, but the default value should cover the needs of
00134  * virtually all use cases (as always).
00135  */
00136 class FirmwareUpdateTrigger : public INodeInfoListener,
00137                               private TimerBase
00138 {
00139     typedef MethodBinder<FirmwareUpdateTrigger*,
00140         void (FirmwareUpdateTrigger::*)(const ServiceCallResult<protocol::file::BeginFirmwareUpdate>&)>
00141             BeginFirmwareUpdateResponseCallback;
00142 
00143     typedef IFirmwareVersionChecker::FirmwareFilePath FirmwareFilePath;
00144 
00145     enum { DefaultRequestIntervalMs = 1000 };   ///< Shall not be less than default service response timeout.
00146 
00147     struct NextNodeIDSearchPredicate : ::uavcan::Noncopyable
00148     {
00149         enum { DefaultOutput = 0xFFU };
00150 
00151         const FirmwareUpdateTrigger& owner;
00152         uint8_t output;
00153 
00154         NextNodeIDSearchPredicate(const FirmwareUpdateTrigger& arg_owner)
00155             : owner(arg_owner)
00156             , output(DefaultOutput)
00157         { }
00158 
00159         bool operator()(const NodeID node_id, const FirmwareFilePath&)
00160         {
00161             if (node_id.get() > owner.last_queried_node_id_ &&
00162                 !owner.begin_fw_update_client_.hasPendingCallToServer(node_id))
00163             {
00164                 output = min(output, node_id.get());
00165             }
00166             return false;
00167         }
00168     };
00169 
00170     /*
00171      * State
00172      */
00173     ServiceClient<protocol::file::BeginFirmwareUpdate, BeginFirmwareUpdateResponseCallback> begin_fw_update_client_;
00174 
00175     IFirmwareVersionChecker& checker_;
00176 
00177     NodeInfoRetriever* node_info_retriever_;
00178 
00179     Map<NodeID, FirmwareFilePath>  pending_nodes_;
00180 
00181     MonotonicDuration request_interval_;
00182 
00183     FirmwareFilePath common_path_prefix_;
00184 
00185     mutable uint8_t last_queried_node_id_;
00186 
00187     /*
00188      * Methods of INodeInfoListener
00189      */
00190     virtual void handleNodeInfoUnavailable(NodeID node_id)
00191     {
00192         UAVCAN_TRACE("FirmwareUpdateTrigger", "Node ID %d could not provide GetNodeInfo response", int(node_id.get()));
00193         pending_nodes_.remove(node_id); // For extra paranoia
00194     }
00195 
00196     virtual void handleNodeInfoRetrieved(const NodeID node_id, const protocol::GetNodeInfo::Response& node_info)
00197     {
00198         FirmwareFilePath firmware_file_path;
00199         const bool update_needed = checker_.shouldRequestFirmwareUpdate(node_id, node_info, firmware_file_path);
00200         if (update_needed)
00201         {
00202             UAVCAN_TRACE("FirmwareUpdateTrigger", "Node ID %d requires update; file path: %s",
00203                          int(node_id.get()), firmware_file_path.c_str());
00204             trySetPendingNode(node_id, firmware_file_path);
00205         }
00206         else
00207         {
00208             UAVCAN_TRACE("FirmwareUpdateTrigger", "Node ID %d does not need update", int(node_id.get()));
00209             pending_nodes_.remove(node_id);
00210         }
00211     }
00212 
00213     virtual void handleNodeStatusChange(const NodeStatusMonitor::NodeStatusChangeEvent& event)
00214     {
00215         if (event.status.mode == protocol::NodeStatus::MODE_OFFLINE)
00216         {
00217             pending_nodes_.remove(event.node_id);
00218             UAVCAN_TRACE("FirmwareUpdateTrigger", "Node ID %d is offline hence forgotten", int(event.node_id.get()));
00219         }
00220     }
00221 
00222     /*
00223      * Own methods
00224      */
00225     INode& getNode() { return begin_fw_update_client_.getNode(); }
00226 
00227     void trySetPendingNode(const NodeID node_id, const FirmwareFilePath& path)
00228     {
00229         if (UAVCAN_NULLPTR != pending_nodes_.insert(node_id, path))
00230         {
00231             if (!TimerBase::isRunning())
00232             {
00233                 TimerBase::startPeriodic(request_interval_);
00234                 UAVCAN_TRACE("FirmwareUpdateTrigger", "Timer started");
00235             }
00236         }
00237         else
00238         {
00239             getNode().registerInternalFailure("FirmwareUpdateTrigger OOM");
00240         }
00241     }
00242 
00243     NodeID pickNextNodeID() const
00244     {
00245         // We can't do index search because indices are unstable in Map<>
00246         // First try - from the current node up
00247         NextNodeIDSearchPredicate s1(*this);
00248         (void)pending_nodes_.find<NextNodeIDSearchPredicate&>(s1);
00249 
00250         if (s1.output != NextNodeIDSearchPredicate::DefaultOutput)
00251         {
00252             last_queried_node_id_ = s1.output;
00253         }
00254         else if (last_queried_node_id_ != 0)
00255         {
00256             // Nothing was found, resetting the selector and trying again
00257             UAVCAN_TRACE("FirmwareUpdateTrigger", "Node selector reset, last value: %d", int(last_queried_node_id_));
00258             last_queried_node_id_ = 0;
00259 
00260             NextNodeIDSearchPredicate s2(*this);
00261             (void)pending_nodes_.find<NextNodeIDSearchPredicate&>(s2);
00262 
00263             if (s2.output != NextNodeIDSearchPredicate::DefaultOutput)
00264             {
00265                 last_queried_node_id_ = s2.output;
00266             }
00267         }
00268         else
00269         {
00270             ; // Hopeless
00271         }
00272         UAVCAN_TRACE("FirmwareUpdateTrigger", "Next node ID to query: %d, pending nodes: %u, pending calls: %u",
00273                      int(last_queried_node_id_), pending_nodes_.getSize(),
00274                      begin_fw_update_client_.getNumPendingCalls());
00275         return last_queried_node_id_;
00276     }
00277 
00278     void handleBeginFirmwareUpdateResponse(const ServiceCallResult<protocol::file::BeginFirmwareUpdate>& result)
00279     {
00280         if (!result.isSuccessful())
00281         {
00282             UAVCAN_TRACE("FirmwareUpdateTrigger", "Request to %d has timed out, will retry",
00283                          int(result.getCallID().server_node_id.get()));
00284             return;
00285         }
00286 
00287         FirmwareFilePath* const old_path = pending_nodes_.access(result.getCallID().server_node_id);
00288         if (old_path == UAVCAN_NULLPTR)
00289         {
00290             // The entry has been removed, assuming that it's not needed anymore
00291             return;
00292         }
00293 
00294         const bool confirmed =
00295             result.getResponse().error == protocol::file::BeginFirmwareUpdate::Response::ERROR_OK ||
00296             result.getResponse().error == protocol::file::BeginFirmwareUpdate::Response::ERROR_IN_PROGRESS;
00297 
00298         if (confirmed)
00299         {
00300             UAVCAN_TRACE("FirmwareUpdateTrigger", "Node %d confirmed the update request",
00301                          int(result.getCallID().server_node_id.get()));
00302             pending_nodes_.remove(result.getCallID().server_node_id);
00303             checker_.handleFirmwareUpdateConfirmation(result.getCallID().server_node_id, result.getResponse());
00304         }
00305         else
00306         {
00307             UAVCAN_ASSERT(old_path != UAVCAN_NULLPTR);
00308             UAVCAN_ASSERT(TimerBase::isRunning());
00309             // We won't have to call trySetPendingNode(), because we'll directly update the old path via the pointer
00310 
00311             const bool update_needed =
00312                 checker_.shouldRetryFirmwareUpdate(result.getCallID().server_node_id, result.getResponse(), *old_path);
00313 
00314             if (!update_needed)
00315             {
00316                 UAVCAN_TRACE("FirmwareUpdateTrigger", "Node %d does not need retry",
00317                              int(result.getCallID().server_node_id.get()));
00318                 pending_nodes_.remove(result.getCallID().server_node_id);
00319             }
00320         }
00321     }
00322 
00323     virtual void handleTimerEvent(const TimerEvent&)
00324     {
00325         if (pending_nodes_.isEmpty())
00326         {
00327             TimerBase::stop();
00328             UAVCAN_TRACE("FirmwareUpdateTrigger", "Timer stopped");
00329             return;
00330         }
00331 
00332         const NodeID node_id = pickNextNodeID();
00333         if (!node_id.isUnicast())
00334         {
00335             return;
00336         }
00337 
00338         FirmwareFilePath* const path = pending_nodes_.access(node_id);
00339         if (path == UAVCAN_NULLPTR)
00340         {
00341             UAVCAN_ASSERT(0);   // pickNextNodeID() returned a node ID that is not present in the map
00342             return;
00343         }
00344 
00345         protocol::file::BeginFirmwareUpdate::Request req;
00346 
00347         req.source_node_id = getNode().getNodeID().get();
00348         if (!common_path_prefix_.empty())
00349         {
00350             req.image_file_remote_path.path += common_path_prefix_.c_str();
00351             req.image_file_remote_path.path.push_back(protocol::file::Path::SEPARATOR);
00352         }
00353         req.image_file_remote_path.path += path->c_str();
00354 
00355         UAVCAN_TRACE("FirmwareUpdateTrigger", "Request to %d with path: %s",
00356                      int(node_id.get()), req.image_file_remote_path.path.c_str());
00357 
00358         const int call_res = begin_fw_update_client_.call(node_id, req);
00359         if (call_res < 0)
00360         {
00361             getNode().registerInternalFailure("FirmwareUpdateTrigger call");
00362         }
00363     }
00364 
00365 public:
00366     FirmwareUpdateTrigger(INode& node, IFirmwareVersionChecker& checker)
00367         : TimerBase(node)
00368         , begin_fw_update_client_(node)
00369         , checker_(checker)
00370         , node_info_retriever_(UAVCAN_NULLPTR)
00371         , pending_nodes_(node.getAllocator())
00372         , request_interval_(MonotonicDuration::fromMSec(DefaultRequestIntervalMs))
00373         , last_queried_node_id_(0)
00374     { }
00375 
00376     ~FirmwareUpdateTrigger()
00377     {
00378         if (node_info_retriever_ != UAVCAN_NULLPTR)
00379         {
00380             node_info_retriever_->removeListener(this);
00381         }
00382     }
00383 
00384     /**
00385      * Starts the object. Once started, it can't be stopped unless destroyed.
00386      *
00387      * @param node_info_retriever       The object will register itself against this retriever.
00388      *                                  When the destructor is called, the object will unregister itself.
00389      *
00390      * @param common_path_prefix        If set, this path will be prefixed to all firmware pathes provided by the
00391      *                                  application interface. The prefix does not need to end with path separator;
00392      *                                  the last trailing one will be removed (so use '//' if you need '/').
00393      *                                  By default the prefix is empty.
00394      *
00395      * @return                          Negative error code.
00396      */
00397     int start(NodeInfoRetriever& node_info_retriever,
00398               const FirmwareFilePath& arg_common_path_prefix = FirmwareFilePath(),
00399               const TransferPriority priority = TransferPriority::OneHigherThanLowest)
00400     {
00401         /*
00402          * Configuring the node info retriever
00403          */
00404         node_info_retriever_ = &node_info_retriever;
00405 
00406         int res = node_info_retriever_->addListener(this);
00407         if (res < 0)
00408         {
00409             return res;
00410         }
00411 
00412         /*
00413          * Initializing the prefix, removing only the last trailing path separator.
00414          */
00415         common_path_prefix_ = arg_common_path_prefix;
00416 
00417         if (!common_path_prefix_.empty() &&
00418             *(common_path_prefix_.end() - 1) == protocol::file::Path::SEPARATOR)
00419         {
00420             common_path_prefix_.resize(uint8_t(common_path_prefix_.size() - 1U));
00421         }
00422 
00423         /*
00424          * Initializing the client
00425          */
00426         res = begin_fw_update_client_.init(priority);
00427         if (res < 0)
00428         {
00429             return res;
00430         }
00431         begin_fw_update_client_.setCallback(
00432             BeginFirmwareUpdateResponseCallback(this, &FirmwareUpdateTrigger::handleBeginFirmwareUpdateResponse));
00433 
00434         // The timer will be started ad-hoc
00435         return 0;
00436     }
00437 
00438     /**
00439      * Interval at which uavcan.protocol.file.BeginFirmwareUpdate requests are being sent.
00440      * Note that default value should be OK for any use case.
00441      */
00442     MonotonicDuration getRequestInterval() const { return request_interval_; }
00443     void setRequestInterval(const MonotonicDuration interval)
00444     {
00445         if (interval.isPositive())
00446         {
00447             request_interval_ = interval;
00448             if (TimerBase::isRunning())     // Restarting with new interval
00449             {
00450                 TimerBase::startPeriodic(request_interval_);
00451             }
00452         }
00453         else
00454         {
00455             UAVCAN_ASSERT(0);
00456         }
00457     }
00458 
00459     /**
00460      * This method is mostly needed for testing.
00461      * When triggering is not in progress, the class consumes zero CPU time.
00462      */
00463     bool isTimerRunning() const { return TimerBase::isRunning(); }
00464 
00465     unsigned getNumPendingNodes() const
00466     {
00467         const unsigned ret = pending_nodes_.getSize();
00468         UAVCAN_ASSERT((ret > 0) ? isTimerRunning() : true);
00469         return ret;
00470     }
00471 
00472 };
00473 
00474 }
00475 
00476 #endif // Include guard