#include "mission/modbus_trigger_service.hpp" #include #include #include #include #include #include namespace lm { namespace { constexpr int kMinCoilId = 1001; constexpr int kMaxCoilId = 2000; bool coilIdValid(int coil_id) { return coil_id >= kMinCoilId && coil_id <= kMaxCoilId; } int modbusAddressToCoilId(uint16_t address) { return static_cast(address); } } // namespace ModbusTriggerService::ModbusTriggerService(MissionStore& store, EnqueueFn enqueue_fn, int tcp_port) : store_(store), enqueue_fn_(std::move(enqueue_fn)), tcp_port_(tcp_port) { tcp_thread_ = std::thread([this] { tcpLoop(); }); } ModbusTriggerService::~ModbusTriggerService() { stop_ = true; if (tcp_thread_.joinable()) tcp_thread_.join(); } nlohmann::json ModbusTriggerService::coilStates() const { std::lock_guard lock(mu_); nlohmann::json out = nlohmann::json::array(); for (int coil = kMinCoilId; coil <= kMaxCoilId; ++coil) { const bool value = coils_.count(coil) ? coils_.at(coil) : false; if (value || store_.findTriggerByCoil(coil)) { out.push_back({{"coil_id", coil}, {"value", value}}); } } return out; } bool ModbusTriggerService::writeCoil(int coil_id, bool value, std::string& err) { if (!coilIdValid(coil_id)) { err = "coil_id must be between 1001 and 2000"; return false; } std::optional mission_id; { std::lock_guard lock(mu_); const bool prev = coils_.count(coil_id) ? coils_.at(coil_id) : false; coils_[coil_id] = value; if (!prev && value) { const auto trigger = store_.findTriggerByCoil(coil_id); if (trigger) mission_id = trigger->value("mission_id", ""); } } if (mission_id) { nlohmann::json req = {{"mission_id", *mission_id}, {"source", "modbus:" + std::to_string(coil_id)}}; return enqueue_fn_(req, err); } return true; } bool ModbusTriggerService::fireCoil(int coil_id, std::string& err) { return writeCoil(coil_id, true, err); } void ModbusTriggerService::handleTcpClient(int client_fd) { std::vector buffer(260); const ssize_t n = recv(client_fd, buffer.data(), buffer.size(), 0); if (n < 12) return; const uint8_t function = buffer[7]; if (function == 0x05 && n >= 12) { const uint16_t address = (static_cast(buffer[8]) << 8) | buffer[9]; const uint16_t value = (static_cast(buffer[10]) << 8) | buffer[11]; const int coil_id = modbusAddressToCoilId(address); std::string err; writeCoil(coil_id, value == 0xFF00, err); send(client_fd, buffer.data(), static_cast(n), 0); } } void ModbusTriggerService::tcpLoop() { const int server_fd = socket(AF_INET, SOCK_STREAM, 0); if (server_fd < 0) return; int opt = 1; setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); sockaddr_in addr{}; addr.sin_family = AF_INET; addr.sin_addr.s_addr = INADDR_ANY; addr.sin_port = htons(static_cast(tcp_port_)); if (bind(server_fd, reinterpret_cast(&addr), sizeof(addr)) < 0) { close(server_fd); return; } if (listen(server_fd, 8) < 0) { close(server_fd); return; } while (!stop_) { fd_set fds; FD_ZERO(&fds); FD_SET(server_fd, &fds); timeval tv{0, 200000}; if (select(server_fd + 1, &fds, nullptr, nullptr, &tv) <= 0) continue; const int client = accept(server_fd, nullptr, nullptr); if (client < 0) continue; handleTcpClient(client); close(client); } close(server_fd); } } // namespace lm