#pragma once #include #include "../common/adc_utils.h" #include "adc_device_netmsg.h" #include "adc_netclient.h" namespace adc { enum class AdcDeviceNetClientSessionError : int { ERROR_OK, ERROR_INVALID_SERVER_RESPOND, ERROR_UNEXPECTED_SERVER_RESPOND, ERROR_UNKNOWN_ERROR }; } // place here to allow clang compilation namespace std { template <> class is_error_code_enum : public true_type { }; } // namespace std namespace adc { struct AdcDeviceNetClientSessionErrorCategory : std::error_category { AdcDeviceNetClientSessionErrorCategory() : std::error_category() {} const char* name() const noexcept { return "ADC_DEVICE_NESERVER_SESSION"; } std::string message(int ec) const { AdcDeviceNetClientSessionError err = static_cast(ec); switch (err) { case AdcDeviceNetClientSessionError::ERROR_OK: return "OK"; case AdcDeviceNetClientSessionError::ERROR_INVALID_SERVER_RESPOND: return "invalid server respond message"; case AdcDeviceNetClientSessionError::ERROR_UNEXPECTED_SERVER_RESPOND: return "unexpected server respond message"; case AdcDeviceNetClientSessionError::ERROR_UNKNOWN_ERROR: return "catch unhandled exception"; default: return "UNKNOWN"; } } static const AdcDeviceNetClientSessionErrorCategory& get() { static const AdcDeviceNetClientSessionErrorCategory constInst; return constInst; } }; } // namespace adc namespace std { inline std::error_code make_error_code(adc::AdcDeviceNetClientSessionError ec) { return std::error_code(static_cast(ec), adc::AdcDeviceNetClientSessionErrorCategory::get()); } } // namespace std namespace adc { template > class AdcDeviceNetClient : public AdcGenericNetClient { typedef AdcGenericNetClient base_t; public: template class Session : public std::enable_shared_from_this> { public: enum ServerResponseType { RESP_INVALID, RESP_ACK, RESP_ERROR, RESP_UNEXPECTED }; typedef SessionIdentT netsession_ident_t; typedef NetServiceT netservice_t; // default server respond type typedef std::vector default_server_resp_t; // asynchronous callback callable type for ADC device getter/setter/executor typedef std::function async_callback_func_t; struct netsession_ctx_t { AdcDeviceNetClient* clientPtr; std::chrono::milliseconds recvTimeout; std::chrono::milliseconds sendTimeout; }; typedef std::vector message_t; // Session(const netsession_ident_t& id, netservice_t srv, AdcDeviceNetServer* srv_ptr) Session(const netsession_ident_t& id, netservice_t srv, netsession_ctx_t ctx) : _ident(id), _netService(std::move(srv)), _clientPtr(ctx.clientPtr), _recvTimeout(ctx.recvTimeout), _sendTimeout(ctx.sendTimeout) { _clientPtr->logInfo("Create client-to-server session with ID = {} (addr = {}, thread = {})", _ident, (void*)this, utils::AdcThisThreadId()); } virtual ~Session() { _clientPtr->logInfo("Delete client-to-server session with ID = {} (addr = {}, thread = {})", _ident, (void*)this, utils::AdcThisThreadId()); } netsession_ident_t ident() const { return _ident; } void start() { _clientPtr->logInfo("Start client-to-server session with ID = {} (addr = {}, thread = {})", _ident, (void*)this, utils::AdcThisThreadId()); } void stop() { _clientPtr->logInfo("Stop client-to-server session with ID = {} (addr = {}, thread = {})", _ident, (void*)this, utils::AdcThisThreadId()); _netService.close(); } // ADC device helper methods (blocking) // get names of devices template R deviceNames(ServerResponseType& rtype) { // expected respond: ACK NAMES DEV1 DEV2 ... // return DEV1 DEV2 ... (or error description 'code category what') return deviceFuncHelper(constants::ADC_DEVICE_NETPROTO_KEY_NAMES, rtype); } default_server_resp_t deviceNames(ServerResponseType& rtype) { return deviceNames(rtype); } // bind device to the client template R bindDevice(DevNameT&& dev_name, ServerResponseType& rtype) { // expected respond: ACK DEVICE DEV_NAME // return DEV_NAME (or error description 'code category what') return deviceFuncHelper(constants::ADC_DEVICE_NETPROTO_KEY_DEVICE, rtype, std::forward(dev_name)); } template default_server_resp_t bindDevice(DevNameT&& dev_name, ServerResponseType& rtype) { return bindDevice(std::forward(dev_name), rtype); } // execute a command template R exec(CmdNameT&& cmd_name, ServerResponseType& rtype) { // expected respond: ACK CMD CMD_NAME // return CMD_NAME ... (or error description 'code category what') return deviceFuncHelper(constants::ADC_DEVICE_NETPROTO_KEY_CMD, rtype, std::forward(cmd_name)); } template default_server_resp_t exec(CmdNameT&& cmd_name, ServerResponseType& rtype) { return exec(std::forward(cmd_name), rtype); } // get an attribute value template R getAttr(AttrNameT&& attr_name, ServerResponseType& rtype) { // expected respond: ACK GET ATTR_NAME ATTR_VALUE // return ATTR_NAME ATTR_VALUE (or error description 'code category what') return deviceFuncHelper(constants::ADC_DEVICE_NETPROTO_KEY_GET, rtype, std::forward(attr_name)); } template default_server_resp_t getAttr(AttrNameT&& attr_name, ServerResponseType& rtype) { return getAttr(std::forward(attr_name), rtype); } // set an attribute value template R setAttr(AttrNameT&& attr_name, ServerResponseType& rtype, ValueT&& value, ValueTs&&... values) { // expected respond: ACK SET ATTR_NAME ATTR_VALUE // return ATTR_NAME ATTR_VALUE (or error description 'code category what') return deviceFuncHelper(constants::ADC_DEVICE_NETPROTO_KEY_SET, rtype, std::forward(value), std::forward(values)...); } template default_server_resp_t setAttr(AttrNameT&& attr_name, ServerResponseType& rtype, ValueT&& value, ValueTs&&... values) { return setAttr(std::forward(attr_name), rtype, std::forward(value), std::forward(values)...); } // ADC device helper methods (asynchronous) template CallbackT> auto asyncDeviceNames(CallbackT&& callback_func) { return asyncDeviceFuncHelper(constants::ADC_DEVICE_NETPROTO_KEY_NAMES, std::forward(callback_func)); } template CallbackT, traits::adc_input_char_range DevNameT> auto asyncBindDevice(CallbackT&& callback_func, DevNameT&& dev_name) { return asyncDeviceFuncHelper(constants::ADC_DEVICE_NETPROTO_KEY_DEVICE, std::forward(callback_func), std::forward(dev_name)); } template CallbackT, traits::adc_input_char_range CmdNameT> auto asyncExec(CallbackT&& callback_func, CmdNameT&& cmd_name) { return asyncDeviceFuncHelper(constants::ADC_DEVICE_NETPROTO_KEY_CMD, std::forward(callback_func), std::forward(cmd_name)); } template CallbackT, traits::adc_input_char_range AttrNameT> auto asyncGetAttr(CallbackT&& callback_func, AttrNameT&& attr_name) { return asyncDeviceFuncHelper(constants::ADC_DEVICE_NETPROTO_KEY_GET, std::forward(callback_func), std::forward(attr_name)); } template CallbackT, traits::adc_input_char_range AttrNameT, typename ValueT, typename... ValueTs> auto asyncSetAttr(CallbackT&& callback_func, AttrNameT&& attr_name, ValueT&& value, ValueTs&&... values) { return asyncDeviceFuncHelper(constants::ADC_DEVICE_NETPROTO_KEY_SET, std::forward(callback_func), std::forward(attr_name), std::forward(value), std::forward(values)...); } protected: netsession_ident_t _ident; netservice_t _netService; AdcDeviceNetClient* _clientPtr; std::chrono::milliseconds _recvTimeout = std::chrono::seconds(5); std::chrono::milliseconds _sendTimeout = std::chrono::seconds(5); // main 'run' method virtual void run() = 0; // helper methods template R checkServerRespond(std::string_view key, const auto& bytes, ServerResponseType& type) const { AdcDeviceProtoMessage dev_msg(bytes); if (!dev_msg.isValid()) { throw std::system_error(AdcDeviceNetClientSessionError::ERROR_INVALID_SERVER_RESPOND); } if (dev_msg.isACK(key)) { type = RESP_ACK; return dev_msg.template attrs(1); } else if (dev_msg.isERROR()) { type = RESP_ERROR; return dev_msg.template attrs(); } else { throw std::system_error(AdcDeviceNetClientSessionError::ERROR_UNEXPECTED_SERVER_RESPOND); } } template R getFromServer(std::string_view key, const typename netservice_t::send_msg_t& bytes, ServerResponseType& type) { auto rbytes = sendRecv(bytes); return checkServerRespond(key, rbytes, type); } template R deviceFuncHelper(std::string_view key, ServerResponseType& rtype, ArgTs&&... args) { typename netservice_t::send_msg_t bytes; AdcDeviceProtoMessage msg(bytes); msg.setKeyValue(key, std::forward(args)...); return getFromServer(key, bytes, rtype); } template CallbackT, typename... ArgTs> auto asyncDeviceFuncHelper(std::string_view key, CallbackT&& callback_func, ArgTs&&... args) { auto bytes = std::shared_ptr(); AdcDeviceProtoMessage msg(*bytes); msg.setKeyValue(key, std::forward(args)...); asyncSendRecv(*bytes, [bytes, key, wrapper = traits::adc_pf_wrapper(std::forward(callback_func)), this](auto err, auto rmsg) mutable { if (err) { _clientPtr->logError("An error occured while receiving server respond: {}", netservice_t::formattableError(err)); } else { try { ServerResponseType type; auto attrs = checkServerRespond(key, rmsg, type); std::forward(std::get<0>(wrapper))(type, attrs); } catch (const std::system_error& err) { _clientPtr->logError("An error occured while getting server respond: {}", err.what()); } } }); } template auto sendRecv(const SendMsgT& send_msg) { _netService.send(send_msg, _sendTimeout); return _netService.receive(_recvTimeout); } template auto asyncSendRecv(const SendMsgT& send_msg, TokenT&& token) { return _netService.asyncSend( send_msg, [wrapper = traits::adc_pf_wrapper(std::forward(token)), this](auto err) { if (err) { this->logError("An error occured while sending the message: {}", netservice_t::formattableError(err)); } else { _netService.asyncReceive(std::get<0>(wrapper), _recvTimeout); } }, _sendTimeout); } }; // end of 'Session' class declaration using base_t::base_t; virtual ~AdcDeviceNetClient() {} }; template > class AdcNetClientSendQueue { public: // typedef std::tuple queue_elem_t; AdcNetClientSendQueue() = default; size_t queueSize() const { return _queue.size(); } AdcNetClientSendQueue& addToQueue(std::string_view key, const ArgRangeT& args) { _queue.push({key, args}); return *this; } template AdcNetClientSendQueue& addToQueue(std::string key, ElemTs&&... elems) { if constexpr (sizeof...(ElemTs)) { _queue.push({key, ArgRangeT()}); addToQueueHelper(std::get<1>(_queue.back()), std::forward(elems)...); } return *this; } template AdcNetClientSendQueue& addCmdToQueue(CmdNameT&& cmd_name) { return addToQueue(constants::ADC_DEVICE_NETPROTO_KEY_CMD, std::forward(cmd_name)); } template AdcNetClientSendQueue& addGetAttrToQueue(AttrNameT&& attr_name) { return addToQueue(constants::ADC_DEVICE_NETPROTO_KEY_GET, std::forward(attr_name)); } template AdcNetClientSendQueue& addSetAttrToQueue(AttrNameT&& attr_name, ValueT&& value, ValueTs&&... values) { return addToQueue(constants::ADC_DEVICE_NETPROTO_KEY_SET, std::forward(attr_name), std::forward(value), std::forward(values)...); } AdcNetClientSendQueue& addGetNamesToQueue() { return addToQueue(constants::ADC_DEVICE_NETPROTO_KEY_NAMES); } template AdcNetClientSendQueue& addBindDevToQueue(DeviceNameT&& dev_name) { return addToQueue(constants::ADC_DEVICE_NETPROTO_KEY_DEVICE, std::forward(dev_name)); } protected: std::queue _queue; template void addToQueueHelper(ArgRangeT& args, ElemT&& elem, ElemTs&&... elems) { using el_t = std::ranges::range_value_t; if constexpr (std::same_as>) { std::ranges::copy(std::ranges::single_view(elem), std::back_inserter(args)); } else { std::span sp; if constexpr (std::is_array_v>) { sp = std::string_view(elem); } else { sp = std::span(elem); } std::ranges::copy(std::views::transform(std::ranges::single_view(sp), [](const auto& val) { el_t el; std::ranges::copy(val, std::back_inserter(el)); return el; }), std::back_inserter(args)); } if constexpr (sizeof...(ElemTs)) { addToQueue(args, std::forward(elems)...); } } }; } // namespace adc