/** * @RDE_HEADER@ * Generic Function Topic Server (FTS Replier) * $Id$ * $HeadURL$ * $Author$ */ #pragma once #include #include "generic_fts_internal.h" #include "connext.h" #include #include #include #include #include #include #include #ifndef tr_lts #define tr_lts(ctx, t) (t) #endif namespace FTS { template class HasStatusFunction { template static std::true_type testSignature(void (T::*)(const Core::FtReplyState&)); template static decltype(testSignature(&T::replyState)) test(std::nullptr_t); template static std::false_type test(...); public: using type = decltype(test(nullptr)); static const bool value = type::value; }; template struct TopicNames; template class GenericFtsImpl final : public GenericFts , public GenericFtsInternal { public: using RequestTopic = TOPICTYPE_REQUEST; using ReplyTopic = TOPICTYPE_REPLY; using TopicNameHelper = TopicNames>; private: using SubscriberCallback = std::function; template ::value, int>::type = 0> void sendReplyState(Core::FtReplyState replyState, const rti::core::SampleIdentity &requestSampleIdentity) { connext::WriteSample reply; reply.data().replyState(replyState); bool bLastTelegram = ((replyState != Core::FtReplyState_def::InProgress) && (replyState != Core::FtReplyState_def::NotStarted)); m_replier->send_reply(reply, requestSampleIdentity, bLastTelegram); } template ::value, int>::type = 0> void sendReplyState(Core::FtReplyState, const rti::core::SampleIdentity &) { // nothing to do here } template ::value, int>::type = 0> void setReply(T &data, Core::FtReplyState status) { data.replyState(status); } template ::value, int>::type = 0> void setReply(T &, Core::FtReplyState) { // nothing to do here } public: GenericFtsImpl(const dds::domain::DomainParticipant &domainParticipant, const dds::pub::Publisher &ddsPublisher, const dds::sub::Subscriber &ddsSubscriber, DDSGeneric::WaitSetHandler *wsHandler) : GenericFts() , GenericFtsInternal() , m_wsHandler(wsHandler) , m_subscriberCallback(nullptr) , m_logger(DDS::getLogger(TopicNameHelper::functionTopicName(), DDS::DataType::FUNCTION_TOPIC_SERVER)) { Q_ASSERT(domainParticipant != nullptr); Q_ASSERT(ddsPublisher != nullptr); Q_ASSERT(ddsSubscriber != nullptr); Q_ASSERT(wsHandler != nullptr); qRegisterMetaType (TopicNameHelper::requestTopicTypeName()); connext::ReplierParams replierParams(domainParticipant); //DEBUG_LOG(m_logger) << "m_domainParticipant: " << m_domainParticipant; //requesterParams.service_name("FunctionTopic"); replierParams.request_topic_name(TopicNameHelper::requestTopicName()); replierParams.reply_topic_name(TopicNameHelper::replyTopicName()); replierParams.publisher(ddsPublisher); replierParams.subscriber(ddsSubscriber); replierParams.datareader_qos(readerQos(m_logger)); replierParams.datawriter_qos(writerQos(m_logger)); //DEBUG_LOG(m_logger) << "=> Static request using request/reply topic names: " // << requestTopicName << ", reply: " << replyTopicName; m_replier = new connext::Replier(replierParams); m_readCondition = new dds::sub::cond::ReadCondition(m_replier->get_request_datareader(), dds::sub::status::SampleState::not_read()); m_wsHandler->attachWaitSet(*m_readCondition, this); } ~GenericFtsImpl() { m_wsHandler->detachWaitSet(*m_readCondition, this); delete m_readCondition; delete m_replier; } bool setSubscriberCallback(const SubscriberCallback &subscriberCallback) { if (m_subscriberCallback) { WARNING_LOG(m_logger) << "Can not set subscriber callback - already set ..."; return false; } m_subscriberCallback = subscriberCallback; return true; } /** * @brief * * Send a single reply back to the requester * * @param requestIdentity * @param replyData */ bool sendReply(const rti::core::SampleIdentity &requestSampleIdentity, const ReplyTopic &replyData) override final { connext::WriteSampleRef reply; rti::pub::WriteParams replyParams; reply.set_data(const_cast(&replyData)); reply.set_info(&replyParams); m_replier->send_reply(reply, requestSampleIdentity); return true; } /** * @brief * * Send a block of reply informations back to the requester * The replies will have a correctly set replyState member (all will have InProgress, the last on Completed) * * @param requestIdentity * @param replyData */ bool sendReply(const rti::core::SampleIdentity &requestIdentity, const QVector &replyData) override final { DEBUG_LOG(m_logger) << TopicNameHelper::requestTopicName() << " - IN sendReply"; for(int i = 0; i < replyData.size(); ++i) { if (HasStatusFunction::value) { ReplyTopic rt = replyData.at(i); setReply(rt, i == replyData.size() - 1 ? Core::FtReplyState::Completed : Core::FtReplyState::InProgress); if (!sendReply(requestIdentity, rt)) return false; } else { if (!sendReply(requestIdentity, replyData.at(i))) return false; } } return true; } protected: // ------------------------------------------------------------------------------------------------------------------- // FunctionTopics => defined in *.idl // ------------------------------------------------------------------------------------------------------------------- /** * @brief * Get the requested data and send them on to the object that will supply the data * * @param request topic data */ void processRequest(const connext::Sample &request) { DEBUG_LOG(m_logger) << TopicNameHelper::requestTopicName() << " - processRequest: " << connext::DebugInfo::toReadable(request.identity()); qint64 age = qAbs(qint64(request.info().source_timestamp().to_secs()) - time(nullptr)); if (age > 10) // sec { std::ostringstream ss; ss << request.data(); std::string s = ss.str(); int slen = s.length(); static int max_slen = 1024; if (slen > max_slen) { s.resize(max_slen); s.append("..."); } DEBUG_LOG(m_logger) << "received a request for topic " << TopicNameHelper::requestTopicName() << " but it was outdated - ignoring. " << " local time: " << (time(nullptr) - 10) << "[s], source timestamp: " << qint64(request.info().source_timestamp().to_secs()) << "[s], -> age: " << age << "[s], data: " << s; ReplyTopic rt; rt.errorString(tr_lts("dds", "Topic %1 was outdated and therefore ignored. Does the client time match the server time?")); rt.errorParams({TopicNameHelper::requestTopicName()}); sendReply(request.identity(), rt); return; } sendReplyState(Core::FtReplyState::InProgress, request.identity()); m_subscriberCallback(request.identity(), request.data()); } /** * @brief * * This method waits for requests from a FTC. * When it receives a request topic, it will get the requested data * and send it via DDS Replier back to the FTC * */ void processRequests() override { DEBUG_LOG(m_logger) << TopicNameHelper::requestTopicName() << " - processRequests"; while (true) { connext::Sample request; { std::lock_guard l(m_requestMutex); if (m_requests.empty()) { DEBUG_LOG(m_logger) << "No more requests"; return; } DEBUG_LOG(m_logger) << TopicNameHelper::requestTopicName() << " - read one request for "; request = m_requests.front(); m_requests.pop(); } if (!request.info().valid()) { WARNING_LOG(m_logger) << TopicNameHelper::requestTopicName() << " - No valid data"; continue; } processRequest(request); } DEBUG_LOG(m_logger) << TopicNameHelper::requestTopicName() << " - processRequests finished"; } bool enqueueRequests() override { bool bValidDataRead = false; connext::Sample request; while (m_replier->receive_request(request)) { if (!request.info().valid()) continue; bValidDataRead = true; std::lock_guard l(m_requestMutex); m_requests.push(request); } return bValidDataRead; } private: // Replier (with request and reply topic (see above) connext::Replier *m_replier; dds::sub::cond::ReadCondition *m_readCondition; // no ownership DDSGeneric::WaitSetHandler *m_wsHandler; std::queue> m_requests; std::mutex m_requestMutex; // Callback for data processing SubscriberCallback m_subscriberCallback; NGL::INGLLogger &m_logger; }; } // namespace FTS