/* * Copyright (C) 2014 Pavel Kirienko */ #include #include #include #include #include #include #include "clock.hpp" #include "internal/transport/can/can.hpp" template struct ServiceCallResultHandler { typedef typename uavcan::ServiceCallResult::Status StatusType; StatusType last_status; uavcan::NodeID last_server_node_id; typename DataType::Response last_response; void handleResponse(const uavcan::ServiceCallResult& result) { // TODO: stream uavcan::ServiceCallResult<> directly std::cout << "Service call result [" << DataType::getDataTypeFullName() << "], success=" << result.isSuccessful() << ":\n"; std::cout << result.response << std::endl; last_status = result.status; last_response = result.response; last_server_node_id = result.server_node_id; } bool match(StatusType status, uavcan::NodeID server_node_id, const typename DataType::Response& response) const { return status == last_status && server_node_id == last_server_node_id && response == last_response; } typedef uavcan::MethodBinder&)> Binder; Binder bind() { return Binder(this, &ServiceCallResultHandler::handleResponse); } }; static void stringServiceServerCallback(const uavcan::ReceivedDataStructure& req, root_ns_a::StringService::Response& rsp) { rsp.string_response = "Request string: "; rsp.string_response += req.string_request.c_str(); } struct MakeshiftNode : uavcan::Noncopyable { uavcan::PoolAllocator pool; uavcan::PoolManager<1> poolmgr; SystemClockDriver clock_driver; uavcan::MarshalBufferProvider<> buffer_provider; uavcan::OutgoingTransferRegistry<8> otr; uavcan::Scheduler scheduler; MakeshiftNode(uavcan::ICanDriver& can_driver, uavcan::NodeID self_node_id) : otr(poolmgr) , scheduler(can_driver, poolmgr, clock_driver, otr, self_node_id) { poolmgr.addPool(&pool); } void spin(uavcan::MonotonicDuration duration) { scheduler.spin(clock_driver.getMonotonic() + duration); } }; struct PairableCanDriver : public uavcan::ICanDriver, public uavcan::ICanIface { uavcan::ISystemClock& clock; PairableCanDriver* other; std::queue read_queue; PairableCanDriver(uavcan::ISystemClock& clock) : clock(clock) , other(NULL) { } void linkTogether(PairableCanDriver* with) { this->other = with; with->other = this; } uavcan::ICanIface* getIface(int iface_index) { if (iface_index == 0) return this; return NULL; } int getNumIfaces() const { return 1; } int select(int& inout_write_iface_mask, int& inout_read_iface_mask, uavcan::MonotonicTime blocking_deadline) { assert(other); if (inout_read_iface_mask == 1) inout_read_iface_mask = read_queue.size() ? 1 : 0; if (inout_read_iface_mask || inout_write_iface_mask) return 1; while (clock.getMonotonic() < blocking_deadline) usleep(1000); return 0; } int send(const uavcan::CanFrame& frame, uavcan::MonotonicTime) { assert(other); other->read_queue.push(frame); return 1; } int receive(uavcan::CanFrame& out_frame, uavcan::MonotonicTime& out_ts_monotonic, uavcan::UtcTime& out_ts_utc) { assert(other); assert(read_queue.size()); out_frame = read_queue.front(); read_queue.pop(); out_ts_monotonic = clock.getMonotonic(); out_ts_utc = clock.getUtc(); return 1; } int configureFilters(const uavcan::CanFilterConfig*, int) { return -1; } int getNumFilters() const { return 0; } uint64_t getNumErrors() const { return 0; } }; TEST(ServiceClient, Basic) { SystemClockDriver clock; PairableCanDriver can_a(clock), can_b(clock); can_a.linkTogether(&can_b); MakeshiftNode node_a(can_a, 1), node_b(can_b, 2); // Type registration uavcan::GlobalDataTypeRegistry::instance().reset(); uavcan::DefaultDataTypeRegistrator _registrator; // Server uavcan::Server server(node_a.scheduler, node_a.poolmgr, node_a.buffer_provider); ASSERT_EQ(1, server.start(stringServiceServerCallback)); { // Caller typedef uavcan::ServiceCallResult ResultType; typedef uavcan::ServiceClient::Binder > ClientType; ServiceCallResultHandler handler; ClientType client1(node_b.scheduler, node_b.poolmgr, node_b.buffer_provider); ClientType client2(node_b.scheduler, node_b.poolmgr, node_b.buffer_provider); ClientType client3(node_b.scheduler, node_b.poolmgr, node_b.buffer_provider); client1.setCallback(handler.bind()); client2.setCallback(client1.getCallback()); client3.setCallback(client1.getCallback()); client3.setRequestTimeout(uavcan::MonotonicDuration::fromMSec(100)); ASSERT_EQ(1, node_a.scheduler.getDispatcher().getNumServiceRequestListeners()); ASSERT_EQ(0, node_b.scheduler.getDispatcher().getNumServiceResponseListeners()); // NOT listening! root_ns_a::StringService::Request request; request.string_request = "Hello world"; ASSERT_LT(0, client1.call(1, request)); // OK ASSERT_LT(0, client2.call(1, request)); // OK ASSERT_LT(0, client3.call(99, request)); // Will timeout! ASSERT_EQ(3, node_b.scheduler.getDispatcher().getNumServiceResponseListeners()); // Listening now! ASSERT_TRUE(client1.isPending()); ASSERT_TRUE(client2.isPending()); ASSERT_TRUE(client3.isPending()); node_a.spin(uavcan::MonotonicDuration::fromMSec(10)); node_b.spin(uavcan::MonotonicDuration::fromMSec(10)); ASSERT_EQ(1, node_b.scheduler.getDispatcher().getNumServiceResponseListeners()); // Third is still listening! ASSERT_FALSE(client1.isPending()); ASSERT_FALSE(client2.isPending()); ASSERT_TRUE(client3.isPending()); // Validating root_ns_a::StringService::Response expected_response; expected_response.string_response = "Request string: Hello world"; ASSERT_TRUE(handler.match(ResultType::Success, 1, expected_response)); node_a.spin(uavcan::MonotonicDuration::fromMSec(100)); node_b.spin(uavcan::MonotonicDuration::fromMSec(100)); ASSERT_FALSE(client1.isPending()); ASSERT_FALSE(client2.isPending()); ASSERT_FALSE(client3.isPending()); ASSERT_EQ(0, node_b.scheduler.getDispatcher().getNumServiceResponseListeners()); // Third has timed out :( // Validating ASSERT_TRUE(handler.match(ResultType::ErrorTimeout, 99, root_ns_a::StringService::Response())); // Stray request ASSERT_LT(0, client3.call(99, request)); // Will timeout! ASSERT_TRUE(client3.isPending()); ASSERT_EQ(1, node_b.scheduler.getDispatcher().getNumServiceResponseListeners()); } // All destroyed - nobody listening ASSERT_EQ(0, node_b.scheduler.getDispatcher().getNumServiceResponseListeners()); }