ADC/net/adc_device_netclient.h
2024-11-19 00:28:52 +03:00

363 lines
12 KiB
C++

#pragma once
#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;
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)
typedef std::vector<std::string> default_server_resp_t;
// get names of devices
template <traits::adc_range_of_output_char_range R>
R deviceNames(ServerResponseType& rtype)
{
typename netservice_t::send_msg_t bytes;
AdcDeviceProtoMessage msg(bytes);
msg.names();
using m_t = std::remove_cvref_t<decltype(msg)>;
// expected respond: ACK NAMES DEV1 DEV2 ...
// return DEV1 DEV2 ... (or error description 'code category what')
return _getFromServer<R>(m_t::NAMES_KEY, bytes, 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)
{
typename netservice_t::send_msg_t bytes;
AdcDeviceProtoMessage msg(bytes);
msg.device(std::forward<DevNameT>(dev_name));
using m_t = std::remove_cvref_t<decltype(msg)>;
// expected respond: ACK DEVICE DEV_NAME
// return DEV_NAME ... (or error description 'code category what')
return _getFromServer<R>(m_t::DEVICE_KEY, bytes, rtype);
}
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)
{
typename netservice_t::send_msg_t bytes;
AdcDeviceProtoMessage msg(bytes);
msg.cmd(std::forward<CmdNameT>(cmd_name));
using m_t = std::remove_cvref_t<decltype(msg)>;
// expected respond: ACK CMD CMD_NAME
// return CMD_NAME ... (or error description 'code category what')
return _getFromServer<R>(m_t::CMD_KEY, bytes, rtype);
}
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)
{
typename netservice_t::send_msg_t bytes;
AdcDeviceProtoMessage msg(bytes);
msg.get(std::forward<AttrNameT>(attr_name));
using m_t = std::remove_cvref_t<decltype(msg)>;
// expected respond: ACK GET ATTR_NAME ATTR_VALUE
// return ATTR_NAME ATTR_VALUE (or error description 'code category what')
return _getFromServer<R>(m_t::GET_KEY, bytes, rtype);
}
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)
{
typename netservice_t::send_msg_t bytes;
AdcDeviceProtoMessage msg(bytes);
msg.set(std::forward<AttrNameT>(attr_name), std::forward<ValueT>(value), std::forward<ValueTs>(values)...);
using m_t = std::remove_cvref_t<decltype(msg)>;
// expected respond: ACK SET ATTR_NAME ATTR_VALUE
// return ATTR_NAME ATTR_VALUE (or error description 'code category what')
return _getFromServer<R>(m_t::SET_KEY, bytes, rtype);
}
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)...);
}
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 _getFromServer(std::string_view key, const typename netservice_t::send_msg_t& bytes, ServerResponseType& type)
{
auto rbytes = sendRecv(bytes);
AdcDeviceProtoMessage dev_msg(rbytes);
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);
}
}
void _sendRecvCallbackWrapper(auto err,
typename netservice_t::recv_msg_t msg,
auto&& ack_func,
auto&& error_func)
{
if (err) {
_clientPtr->logError("An error occured while receiving server respond: {}",
netservice_t::formattableError(err));
} else {
AdcDeviceProtoMessage dev_msg(msg);
if (!dev_msg.isValid()) {
_clientPtr->logError("Invalid server respond");
return;
}
if (dev_msg.isACK()) {
std::forward<decltype(ack_func)>(ack_func)(dev_msg.attrs());
} else if (dev_msg.isERROR()) {
std::forward<decltype(error_func)>(error_func)(dev_msg.attrs());
} else {
_clientPtr->logError("Unexpectable server respond (msg key: {})", dev_msg.key());
}
}
};
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);
}
template <traits::adc_input_char_range SendMsgT>
auto sendRecv(const SendMsgT& send_msg)
{
_netService.send(send_msg, _sendTimeout);
return _netService.receive(_recvTimeout);
}
}; // end of 'Session' class declaration
using base_t::base_t;
virtual ~AdcDeviceNetClient() {}
};
} // namespace adc