diff --git a/libuavcan/include/uavcan/node/service_client.hpp b/libuavcan/include/uavcan/node/service_client.hpp index 5c689167a4..7cc0dac1b1 100644 --- a/libuavcan/include/uavcan/node/service_client.hpp +++ b/libuavcan/include/uavcan/node/service_client.hpp @@ -69,6 +69,33 @@ static Stream& operator<<(Stream& s, const ServiceCallResult& scr) } +class ServiceClientBase : protected DeadlineHandler +{ +protected: + MonotonicDuration request_timeout_; + bool pending_; + + ServiceClientBase(INode& node) + : DeadlineHandler(node.getScheduler()) + , request_timeout_(getDefaultRequestTimeout()) + , pending_(false) + { } + + virtual ~ServiceClientBase() { } + + int prepareToCall(INode& node, const char* dtname, NodeID server_node_id, TransferID& out_transfer_id); + +public: + bool isPending() const { return pending_; } + + static MonotonicDuration getDefaultRequestTimeout() { return MonotonicDuration::fromMSec(1000); } + static MonotonicDuration getMinRequestTimeout() { return MonotonicDuration::fromMSec(10); } + static MonotonicDuration getMaxRequestTimeout() { return MonotonicDuration::fromMSec(60000); } + + using DeadlineHandler::getDeadline; +}; + + template = UAVCAN_CPP11 typename Callback_ = std::function&)> @@ -79,7 +106,7 @@ template ::Type > - , protected DeadlineHandler + , public ServiceClientBase { public: typedef DataType_ DataType; @@ -96,70 +123,21 @@ private: PublisherType publisher_; Callback callback_; - MonotonicDuration request_timeout_; - bool pending_; bool isCallbackValid() const { return try_implicit_cast(callback_, true); } - void invokeCallback(ServiceCallResultType& result) - { - if (isCallbackValid()) - { - callback_(result); - } - else - { - handleFatalError("Invalid caller callback"); - } - } + void invokeCallback(ServiceCallResultType& result); - void handleReceivedDataStruct(ReceivedDataStructure& response) - { - assert(response.getTransferType() == TransferTypeServiceResponse); - const TransferListenerType* const listener = SubscriberType::getTransferListener(); - if (listener) - { - const typename TransferListenerType::ExpectedResponseParams erp = listener->getExpectedResponseParams(); - ServiceCallResultType result(ServiceCallResultType::Success, erp.src_node_id, response); - cancel(); - invokeCallback(result); - } - else - { - assert(0); - cancel(); - } - } + void handleReceivedDataStruct(ReceivedDataStructure& response); - void handleDeadline(MonotonicTime) - { - const TransferListenerType* const listener = SubscriberType::getTransferListener(); - if (listener) - { - const typename TransferListenerType::ExpectedResponseParams erp = listener->getExpectedResponseParams(); - ReceivedDataStructure& ref = SubscriberType::getReceivedStructStorage(); - ServiceCallResultType result(ServiceCallResultType::ErrorTimeout, erp.src_node_id, ref); - - UAVCAN_TRACE("ServiceClient", "Timeout from nid=%i, dtname=%s", - erp.src_node_id.get(), DataType::getDataTypeFullName()); - cancel(); - invokeCallback(result); - } - else - { - assert(0); - cancel(); - } - } + void handleDeadline(MonotonicTime); public: explicit ServiceClient(INode& node, const Callback& callback = Callback()) : SubscriberType(node) - , DeadlineHandler(node.getScheduler()) + , ServiceClientBase(node) , publisher_(node, getDefaultRequestTimeout()) , callback_(callback) - , request_timeout_(getDefaultRequestTimeout()) - , pending_(false) { setRequestTimeout(getDefaultRequestTimeout()); #if UAVCAN_DEBUG @@ -174,102 +152,9 @@ public: return publisher_.init(); } - int call(NodeID server_node_id, const RequestType& request) // TODO: Refactor, move into a non-template subclass! - { - /* - * Cancelling the pending request - */ - cancel(); - if (!isCallbackValid()) - { - UAVCAN_TRACE("ServiceClient", "Invalid callback"); - return -ErrInvalidParam; - } - pending_ = true; + int call(NodeID server_node_id, const RequestType& request); - /* - * Determining the Data Type ID - */ - GlobalDataTypeRegistry::instance().freeze(); - const DataTypeDescriptor* const descr = - GlobalDataTypeRegistry::instance().find(DataTypeKindService, DataType::getDataTypeFullName()); - if (!descr) - { - UAVCAN_TRACE("ServiceClient", "Type [%s] is not registered", DataType::getDataTypeFullName()); - cancel(); - return -ErrUnknownDataType; - } - - /* - * Determining the Transfer ID - */ - const OutgoingTransferRegistryKey otr_key(descr->getID(), TransferTypeServiceRequest, server_node_id); - const MonotonicTime otr_deadline = - SubscriberType::getNode().getMonotonicTime() + TransferSender::getDefaultMaxTransferInterval(); - TransferID* const otr_tid = SubscriberType::getNode().getDispatcher().getOutgoingTransferRegistry() - .accessOrCreate(otr_key, otr_deadline); - if (!otr_tid) - { - UAVCAN_TRACE("ServiceClient", "OTR access failure, dtd=%s", descr->toString().c_str()); - cancel(); - return -ErrMemory; - } - const TransferID transfer_id = *otr_tid; - otr_tid->increment(); - - /* - * Starting the subscriber - */ - const int subscriber_res = SubscriberType::startAsServiceResponseListener(); - if (subscriber_res < 0) - { - UAVCAN_TRACE("ServiceClient", "Failed to start the subscriber, error: %i", subscriber_res); - cancel(); - return subscriber_res; - } - - /* - * Configuring the listener so it will accept only the matching response - */ - TransferListenerType* const tl = SubscriberType::getTransferListener(); - if (!tl) - { - assert(0); // Must have been created - cancel(); - return -ErrLogic; - } - const typename TransferListenerType::ExpectedResponseParams erp(server_node_id, transfer_id); - tl->setExpectedResponseParams(erp); - - /* - * Registering the deadline handler - */ - DeadlineHandler::startWithDelay(request_timeout_); - - /* - * Publishing the request - */ - const int publisher_res = publisher_.publish(request, TransferTypeServiceRequest, server_node_id, transfer_id); - if (!publisher_res) - { - cancel(); - } - return publisher_res; - } - - void cancel() - { - pending_ = false; - SubscriberType::stop(); - DeadlineHandler::stop(); - TransferListenerType* const tl = SubscriberType::getTransferListener(); - if (tl) - { - tl->stopAcceptingAnything(); - } - } - - bool isPending() const { return pending_; } + void cancel(); const Callback& getCallback() const { return callback_; } void setCallback(const Callback& cb) { callback_ = cb; } @@ -280,10 +165,6 @@ public: * Request timeouts * There is no such config as TX timeout - TX timeouts are configured automagically according to request timeouts */ - static MonotonicDuration getDefaultRequestTimeout() { return MonotonicDuration::fromMSec(1000); } - static MonotonicDuration getMinRequestTimeout() { return MonotonicDuration::fromMSec(10); } - static MonotonicDuration getMaxRequestTimeout() { return MonotonicDuration::fromMSec(60000); } - MonotonicDuration getRequestTimeout() const { return request_timeout_; } void setRequestTimeout(MonotonicDuration timeout) { @@ -293,8 +174,133 @@ public: publisher_.setTxTimeout(timeout); request_timeout_ = std::max(timeout, publisher_.getTxTimeout()); // No less than TX timeout } - - using DeadlineHandler::getDeadline; }; +// ---------------------------------------------------------------------------- + +template +void ServiceClient::invokeCallback(ServiceCallResultType& result) +{ + if (isCallbackValid()) + { + callback_(result); + } + else + { + handleFatalError("Invalid caller callback"); + } +} + +template +void ServiceClient::handleReceivedDataStruct(ReceivedDataStructure& response) +{ + assert(response.getTransferType() == TransferTypeServiceResponse); + const TransferListenerType* const listener = SubscriberType::getTransferListener(); + if (listener) + { + const typename TransferListenerType::ExpectedResponseParams erp = listener->getExpectedResponseParams(); + ServiceCallResultType result(ServiceCallResultType::Success, erp.src_node_id, response); + cancel(); + invokeCallback(result); + } + else + { + assert(0); + cancel(); + } +} + +template +void ServiceClient::handleDeadline(MonotonicTime) +{ + const TransferListenerType* const listener = SubscriberType::getTransferListener(); + if (listener) + { + const typename TransferListenerType::ExpectedResponseParams erp = listener->getExpectedResponseParams(); + ReceivedDataStructure& ref = SubscriberType::getReceivedStructStorage(); + ServiceCallResultType result(ServiceCallResultType::ErrorTimeout, erp.src_node_id, ref); + + UAVCAN_TRACE("ServiceClient", "Timeout from nid=%i, dtname=%s", + erp.src_node_id.get(), DataType::getDataTypeFullName()); + cancel(); + invokeCallback(result); + } + else + { + assert(0); + cancel(); + } +} + +template +int ServiceClient::call(NodeID server_node_id, const RequestType& request) +{ + cancel(); + if (!isCallbackValid()) + { + UAVCAN_TRACE("ServiceClient", "Invalid callback"); + return -ErrInvalidParam; + } + + /* + * Common procedures that don't depend on the struct data type + */ + TransferID transfer_id; + const int prep_res = + prepareToCall(SubscriberType::getNode(), DataType::getDataTypeFullName(), server_node_id, transfer_id); + if (prep_res < 0) + { + UAVCAN_TRACE("ServiceClient", "Failed to prepare the call, error: %i", prep_res); + cancel(); + return prep_res; + } + + /* + * Starting the subscriber + */ + const int subscriber_res = SubscriberType::startAsServiceResponseListener(); + if (subscriber_res < 0) + { + UAVCAN_TRACE("ServiceClient", "Failed to start the subscriber, error: %i", subscriber_res); + cancel(); + return subscriber_res; + } + + /* + * Configuring the listener so it will accept only the matching response + */ + TransferListenerType* const tl = SubscriberType::getTransferListener(); + if (!tl) + { + assert(0); // Must have been created + cancel(); + return -ErrLogic; + } + const typename TransferListenerType::ExpectedResponseParams erp(server_node_id, transfer_id); + tl->setExpectedResponseParams(erp); + + /* + * Publishing the request + */ + const int publisher_res = publisher_.publish(request, TransferTypeServiceRequest, server_node_id, transfer_id); + if (!publisher_res) + { + cancel(); + } + return publisher_res; +} + +template +void ServiceClient::cancel() +{ + pending_ = false; + SubscriberType::stop(); + DeadlineHandler::stop(); + TransferListenerType* const tl = SubscriberType::getTransferListener(); + if (tl) + { + tl->stopAcceptingAnything(); + } +} + } diff --git a/libuavcan/src/node/uc_service_client.cpp b/libuavcan/src/node/uc_service_client.cpp new file mode 100644 index 0000000000..1a00bedb43 --- /dev/null +++ b/libuavcan/src/node/uc_service_client.cpp @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2014 Pavel Kirienko + */ + +#include + +namespace uavcan +{ + +int ServiceClientBase::prepareToCall(INode& node, const char* dtname, NodeID server_node_id, + TransferID& out_transfer_id) +{ + pending_ = true; + + /* + * Determining the Data Type ID + */ + GlobalDataTypeRegistry::instance().freeze(); + const DataTypeDescriptor* const descr = GlobalDataTypeRegistry::instance().find(DataTypeKindService, dtname); + if (!descr) + { + UAVCAN_TRACE("ServiceClient", "Type [%s] is not registered", dtname); + return -ErrUnknownDataType; + } + + /* + * Determining the Transfer ID + */ + const OutgoingTransferRegistryKey otr_key(descr->getID(), TransferTypeServiceRequest, server_node_id); + const MonotonicTime otr_deadline = node.getMonotonicTime() + TransferSender::getDefaultMaxTransferInterval(); + TransferID* const otr_tid = + node.getDispatcher().getOutgoingTransferRegistry().accessOrCreate(otr_key, otr_deadline); + if (!otr_tid) + { + UAVCAN_TRACE("ServiceClient", "OTR access failure, dtd=%s", descr->toString().c_str()); + return -ErrMemory; + } + out_transfer_id = *otr_tid; + otr_tid->increment(); + + /* + * Registering the deadline handler + */ + DeadlineHandler::startWithDelay(request_timeout_); + return 0; +} + +}