ranczo-io/libs/modbus.cpp
Bartosz Wieczorek e58f3fc139 fix mem problems
2025-11-07 11:23:05 +01:00

149 lines
5.3 KiB
C++

#include "config.hpp"
#include <spdlog/spdlog.h>
#include <ranczo-io/utils/modbus.hpp>
#include <boost/asio.hpp>
#include <boost/asio/associated_executor.hpp>
#include <boost/asio/this_coro.hpp>
#include <boost/smart_ptr/shared_ptr.hpp>
#include <modbus/modbus.h>
#include <cerrno>
#include <cstdint>
#include <memory>
#include <string>
#include <string_view>
namespace ranczo {
ModbusTcpContext::~ModbusTcpContext() {
if(ctx_) {
::modbus_close(ctx_.get());
ctx_.reset();
}
pool_.join();
}
awaitable_expected< void > ModbusTcpContext::async_connect() {
co_return co_await async_call< void >([this](modbus_t *& out_ctx) -> expected< void > {
spdlog::info("Modbus create ctx for {}:{}", host_.c_str(), port_);
modbus_t * raw = ::modbus_new_tcp(host_.c_str(), port_);
if(!raw)
return unexpected(errno_errc());
std::unique_ptr< modbus_t, CtxDeleter > ctx(raw);
if(::modbus_connect(ctx.get()) == -1) {
spdlog::error("Modbus failed to conenct to {}:{}", host_.c_str(), port_);
return unexpected(errno_errc());
}
_connected = true;
spdlog::info("Modbus connected to {}:{}", host_.c_str(), port_);
// ustaw sensowne timeouty (opcjonalnie)
// timeval tv{2, 0};
// ::modbus_set_response_timeout(ctx.get(), tv.tv_sec, tv.tv_usec);
// ::modbus_set_byte_timeout(ctx.get(), tv.tv_sec, tv.tv_usec);
std::scoped_lock lk(mx_);
ctx_ = std::move(ctx);
out_ctx = ctx_.get();
return {};
});
}
bool ModbusTcpContext::connected() const {
return _connected;
}
awaitable_expected< void > ModbusTcpContext::async_close() {
co_return co_await async_call< void >([this](modbus_t *&) -> expected< void > {
std::scoped_lock lk(mx_);
if(ctx_) {
::modbus_close(ctx_.get());
ctx_.reset();
_connected = false;
}
return {};
});
}
boost::system::error_code ModbusTcpContext::errno_errc() {
return {errno, boost::system::generic_category()};
}
std::shared_ptr< ModbusTcpContext >
ModbusTcpContext::create(boost::asio::io_context & io, std::string host, int port, std::size_t pool_size) {
return std::make_shared< ModbusTcpContext >(io, std::move(host), port, pool_size);
}
template < typename T, typename F >
awaitable_expected< T > ModbusTcpContext::call_with_lock(F && op) {
co_return co_await async_call< T >([this, op = std::forward< F >(op)](modbus_t *& out) -> expected< T > {
std::scoped_lock lk(mx_);
if(!ctx_)
return unexpected(make_error_code(boost::system::errc::not_connected));
out = ctx_.get();
return op(out);
});
}
template < typename T, typename Maker >
awaitable_expected< T > ModbusTcpContext::async_call(Maker && maker) {
namespace asio = boost::asio;
// Initiator wyprodukuje expected<T>
auto initiator = [this, maker = std::forward< Maker >(maker)](auto && completion_handler) mutable {
auto ex = asio::get_associated_executor(completion_handler, io_.get_executor());
asio::post(pool_, [this, maker = std::move(maker), completion_handler = std::move(completion_handler), ex]() mutable {
// maker może potrzebować zwrócić też wskaźnik na ctx (referencja wyjściowa)
modbus_t * ctx_ptr = nullptr;
expected< T > result = maker(ctx_ptr);
asio::post(ex, [completion_handler = std::move(completion_handler), result = std::move(result)]() mutable {
completion_handler(std::move(result));
});
});
};
co_return co_await asio::async_initiate< decltype(asio::use_awaitable), void(expected< T >) >(
std::move(initiator), asio::use_awaitable_t<>{});
}
awaitable_expected< uint16_t > ModbusDevice::async_read_holding_register(uint16_t address) {
address -= 1;
auto ctx = ctx_; // kopia shared_ptr dla bezpieczeństwa w tasku
co_return co_await ctx->call_with_lock< std::uint16_t >([this, address](modbus_t * c) -> expected< std::uint16_t > {
if(::modbus_set_slave(c, unit_id_) == -1) {
spdlog::error("Modbus modbus_set_slave for {}/{} failed with {}", unit_id_, address, errno);
return unexpected(errno_errc());
}
uint16_t val = 0;
int rc = ::modbus_read_registers(c, static_cast< int >(address), 1, &val);
if(rc == -1) {
spdlog::error("Modbus modbus_read_registers for {}/{} failed with {}", unit_id_, address, errno);
return unexpected(errno_errc());
}
return val;
});
}
awaitable_expected< void > ModbusDevice::async_write_coil(uint16_t address, bool value) {
auto ctx = ctx_;
co_return co_await ctx->call_with_lock< void >([this, address, value](modbus_t * c) -> expected< void > {
if(::modbus_set_slave(c, unit_id_) == -1){
spdlog::error("Modbus modbus_set_slave for {}/{} failed with {}", unit_id_, address, errno);
return unexpected(errno_errc());
}
const int rc = ::modbus_write_bit(c, static_cast< int >(address), value ? 1 : 0);
if(rc == -1){
spdlog::error("Modbus modbus_write_bit for {}/{} failed with {}", unit_id_, address, errno);
return unexpected(errno_errc());
}
return {};
});
}
} // namespace ranczo