498 lines
18 KiB
C++
498 lines
18 KiB
C++
#pragma once
|
|
|
|
#include <queue>
|
|
#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<adc::AdcDeviceNetClientSessionError> : 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<AdcDeviceNetClientSessionError>(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<int>(ec), adc::AdcDeviceNetClientSessionErrorCategory::get());
|
|
}
|
|
|
|
} // namespace std
|
|
|
|
|
|
|
|
namespace adc
|
|
{
|
|
|
|
template <typename IdentT = std::string, interfaces::adc_logger_c LoggerT = utils::AdcOstreamLogger<char>>
|
|
class AdcDeviceNetClient : public AdcGenericNetClient<IdentT, LoggerT>
|
|
{
|
|
typedef AdcGenericNetClient<IdentT, LoggerT> base_t;
|
|
|
|
public:
|
|
template <interfaces::adc_netservice_c NetServiceT, traits::adc_hashable_c SessionIdentT = std::string>
|
|
class Session : public std::enable_shared_from_this<Session<NetServiceT, SessionIdentT>>
|
|
{
|
|
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<std::string> default_server_resp_t;
|
|
// asynchronous callback callable type for ADC device getter/setter/executor
|
|
typedef std::function<void(ServerResponseType, default_server_resp_t)> async_callback_func_t;
|
|
|
|
struct netsession_ctx_t {
|
|
AdcDeviceNetClient* clientPtr;
|
|
std::chrono::milliseconds recvTimeout;
|
|
std::chrono::milliseconds sendTimeout;
|
|
};
|
|
|
|
typedef std::vector<char> 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 <traits::adc_range_of_output_char_range R>
|
|
R deviceNames(ServerResponseType& rtype)
|
|
{
|
|
// expected respond: ACK NAMES DEV1 DEV2 ...
|
|
// return DEV1 DEV2 ... (or error description 'code category what')
|
|
return deviceFuncHelper<R>(constants::ADC_DEVICE_NETPROTO_KEY_NAMES, rtype);
|
|
}
|
|
|
|
default_server_resp_t deviceNames(ServerResponseType& rtype)
|
|
{
|
|
return deviceNames<default_server_resp_t>(rtype);
|
|
}
|
|
|
|
// bind device to the client
|
|
template <traits::adc_range_of_output_char_range R, traits::adc_input_char_range DevNameT>
|
|
R bindDevice(DevNameT&& dev_name, ServerResponseType& rtype)
|
|
{
|
|
// expected respond: ACK DEVICE DEV_NAME
|
|
// return DEV_NAME (or error description 'code category what')
|
|
return deviceFuncHelper<R>(constants::ADC_DEVICE_NETPROTO_KEY_DEVICE, rtype,
|
|
std::forward<DevNameT>(dev_name));
|
|
}
|
|
|
|
template <traits::adc_input_char_range DevNameT>
|
|
default_server_resp_t bindDevice(DevNameT&& dev_name, ServerResponseType& rtype)
|
|
{
|
|
return bindDevice<default_server_resp_t>(std::forward<DevNameT>(dev_name), rtype);
|
|
}
|
|
|
|
|
|
// execute a command
|
|
template <traits::adc_range_of_output_char_range R, traits::adc_input_char_range CmdNameT>
|
|
R exec(CmdNameT&& cmd_name, ServerResponseType& rtype)
|
|
{
|
|
// expected respond: ACK CMD CMD_NAME
|
|
// return CMD_NAME ... (or error description 'code category what')
|
|
return deviceFuncHelper<R>(constants::ADC_DEVICE_NETPROTO_KEY_CMD, rtype, std::forward<CmdNameT>(cmd_name));
|
|
}
|
|
|
|
template <traits::adc_input_char_range CmdNameT>
|
|
default_server_resp_t exec(CmdNameT&& cmd_name, ServerResponseType& rtype)
|
|
{
|
|
return exec<default_server_resp_t>(std::forward<CmdNameT>(cmd_name), rtype);
|
|
}
|
|
|
|
|
|
// get an attribute value
|
|
template <traits::adc_range_of_output_char_range R, traits::adc_input_char_range AttrNameT>
|
|
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<R>(constants::ADC_DEVICE_NETPROTO_KEY_GET, rtype,
|
|
std::forward<AttrNameT>(attr_name));
|
|
}
|
|
|
|
template <traits::adc_input_char_range AttrNameT>
|
|
default_server_resp_t getAttr(AttrNameT&& attr_name, ServerResponseType& rtype)
|
|
{
|
|
return getAttr<default_server_resp_t>(std::forward<AttrNameT>(attr_name), rtype);
|
|
}
|
|
|
|
|
|
// set an attribute value
|
|
template <traits::adc_range_of_output_char_range R,
|
|
traits::adc_input_char_range AttrNameT,
|
|
typename ValueT,
|
|
typename... ValueTs>
|
|
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<R>(constants::ADC_DEVICE_NETPROTO_KEY_SET, rtype, std::forward<ValueT>(value),
|
|
std::forward<ValueTs>(values)...);
|
|
}
|
|
|
|
template <traits::adc_input_char_range AttrNameT, typename ValueT, typename... ValueTs>
|
|
default_server_resp_t setAttr(AttrNameT&& attr_name,
|
|
ServerResponseType& rtype,
|
|
ValueT&& value,
|
|
ValueTs&&... values)
|
|
{
|
|
return setAttr<default_server_resp_t>(std::forward<AttrNameT>(attr_name), rtype,
|
|
std::forward<ValueT>(value), std::forward<ValueTs>(values)...);
|
|
}
|
|
|
|
// ADC device helper methods (asynchronous)
|
|
|
|
template <std::convertible_to<async_callback_func_t> CallbackT>
|
|
auto asyncDeviceNames(CallbackT&& callback_func)
|
|
{
|
|
return asyncDeviceFuncHelper(constants::ADC_DEVICE_NETPROTO_KEY_NAMES,
|
|
std::forward<CallbackT>(callback_func));
|
|
}
|
|
|
|
template <std::convertible_to<async_callback_func_t> 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<CallbackT>(callback_func), std::forward<DevNameT>(dev_name));
|
|
}
|
|
|
|
template <std::convertible_to<async_callback_func_t> 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<CallbackT>(callback_func),
|
|
std::forward<CmdNameT>(cmd_name));
|
|
}
|
|
|
|
template <std::convertible_to<async_callback_func_t> 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<CallbackT>(callback_func),
|
|
std::forward<AttrNameT>(attr_name));
|
|
}
|
|
|
|
template <std::convertible_to<async_callback_func_t> 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<CallbackT>(callback_func),
|
|
std::forward<AttrNameT>(attr_name), std::forward<ValueT>(value),
|
|
std::forward<ValueTs>(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 <traits::adc_range_of_output_char_range R>
|
|
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<R>(1);
|
|
} else if (dev_msg.isERROR()) {
|
|
type = RESP_ERROR;
|
|
return dev_msg.template attrs<R>();
|
|
} else {
|
|
throw std::system_error(AdcDeviceNetClientSessionError::ERROR_UNEXPECTED_SERVER_RESPOND);
|
|
}
|
|
}
|
|
|
|
template <traits::adc_range_of_output_char_range R>
|
|
R getFromServer(std::string_view key, const typename netservice_t::send_msg_t& bytes, ServerResponseType& type)
|
|
{
|
|
auto rbytes = sendRecv(bytes);
|
|
|
|
return checkServerRespond<R>(key, rbytes, type);
|
|
}
|
|
|
|
template <traits::adc_range_of_output_char_range R, typename... ArgTs>
|
|
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<ArgTs>(args)...);
|
|
|
|
return getFromServer<R>(key, bytes, rtype);
|
|
}
|
|
|
|
|
|
template <std::convertible_to<async_callback_func_t> CallbackT, typename... ArgTs>
|
|
auto asyncDeviceFuncHelper(std::string_view key, CallbackT&& callback_func, ArgTs&&... args)
|
|
{
|
|
auto bytes = std::shared_ptr<typename netservice_t::send_msg_t>();
|
|
|
|
AdcDeviceProtoMessage msg(*bytes);
|
|
msg.setKeyValue(key, std::forward<ArgTs>(args)...);
|
|
|
|
asyncSendRecv(*bytes, [bytes, key, wrapper = traits::adc_pf_wrapper(std::forward<CallbackT>(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<default_server_resp_t>(key, rmsg, type);
|
|
|
|
std::forward<CallbackT>(std::get<0>(wrapper))(type, attrs);
|
|
|
|
} catch (const std::system_error& err) {
|
|
_clientPtr->logError("An error occured while getting server respond: {}", err.what());
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
template <traits::adc_input_char_range SendMsgT>
|
|
auto sendRecv(const SendMsgT& send_msg)
|
|
{
|
|
_netService.send(send_msg, _sendTimeout);
|
|
return _netService.receive(_recvTimeout);
|
|
}
|
|
|
|
template <traits::adc_input_char_range SendMsgT, typename TokenT>
|
|
auto asyncSendRecv(const SendMsgT& send_msg, TokenT&& token)
|
|
{
|
|
return _netService.asyncSend(
|
|
send_msg,
|
|
[wrapper = traits::adc_pf_wrapper(std::forward<TokenT>(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 <traits::adc_range_of_input_char_range ArgRangeT = std::vector<std::string>>
|
|
class AdcNetClientSendQueue
|
|
{
|
|
public:
|
|
// <netproto-key, args ...>
|
|
typedef std::tuple<std::string_view, ArgRangeT> 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 <traits::adc_input_char_range... ElemTs>
|
|
AdcNetClientSendQueue& addToQueue(std::string key, ElemTs&&... elems)
|
|
{
|
|
if constexpr (sizeof...(ElemTs)) {
|
|
_queue.push({key, ArgRangeT()});
|
|
|
|
addToQueueHelper(std::get<1>(_queue.back()), std::forward<ElemTs>(elems)...);
|
|
}
|
|
|
|
return *this;
|
|
}
|
|
|
|
|
|
template <traits::adc_input_char_range CmdNameT>
|
|
AdcNetClientSendQueue& addCmdToQueue(CmdNameT&& cmd_name)
|
|
{
|
|
return addToQueue(constants::ADC_DEVICE_NETPROTO_KEY_CMD, std::forward<CmdNameT>(cmd_name));
|
|
}
|
|
|
|
template <traits::adc_input_char_range AttrNameT>
|
|
AdcNetClientSendQueue& addGetAttrToQueue(AttrNameT&& attr_name)
|
|
{
|
|
return addToQueue(constants::ADC_DEVICE_NETPROTO_KEY_GET, std::forward<AttrNameT>(attr_name));
|
|
}
|
|
|
|
template <traits::adc_input_char_range AttrNameT,
|
|
traits::adc_input_char_range ValueT,
|
|
traits::adc_input_char_range... ValueTs>
|
|
AdcNetClientSendQueue& addSetAttrToQueue(AttrNameT&& attr_name, ValueT&& value, ValueTs&&... values)
|
|
{
|
|
return addToQueue(constants::ADC_DEVICE_NETPROTO_KEY_SET, std::forward<AttrNameT>(attr_name),
|
|
std::forward<ValueT>(value), std::forward<ValueTs>(values)...);
|
|
}
|
|
|
|
AdcNetClientSendQueue& addGetNamesToQueue()
|
|
{
|
|
return addToQueue(constants::ADC_DEVICE_NETPROTO_KEY_NAMES);
|
|
}
|
|
|
|
template <traits::adc_input_char_range DeviceNameT>
|
|
AdcNetClientSendQueue& addBindDevToQueue(DeviceNameT&& dev_name)
|
|
{
|
|
return addToQueue(constants::ADC_DEVICE_NETPROTO_KEY_DEVICE, std::forward<DeviceNameT>(dev_name));
|
|
}
|
|
|
|
protected:
|
|
std::queue<queue_elem_t> _queue;
|
|
|
|
template <traits::adc_input_char_range ElemT, traits::adc_input_char_range... ElemTs>
|
|
void addToQueueHelper(ArgRangeT& args, ElemT&& elem, ElemTs&&... elems)
|
|
{
|
|
using el_t = std::ranges::range_value_t<ArgRangeT>;
|
|
|
|
if constexpr (std::same_as<el_t, std::remove_cvref_t<ElemT>>) {
|
|
std::ranges::copy(std::ranges::single_view(elem), std::back_inserter(args));
|
|
} else {
|
|
std::span<const char> sp;
|
|
|
|
if constexpr (std::is_array_v<std::remove_cvref_t<ElemT>>) {
|
|
sp = std::string_view(elem);
|
|
} else {
|
|
sp = std::span<const char>(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<ElemTs>(elems)...);
|
|
}
|
|
}
|
|
};
|
|
|
|
} // namespace adc
|