diff --git a/config/alarms.json b/config/alarms.json index 94a5296..1c603b0 100644 --- a/config/alarms.json +++ b/config/alarms.json @@ -1,46 +1,58 @@ [ { "rule_id": "RTU_TEMP_HIGH_CRITICAL", - "device_id": "rtu_temp_sensor_lab", - "data_point_name": "temperature", - "compare_type": "GT", - "threshold": 30.0, - "level": "CRITICAL", + "device_id": "rtu_temp_sensor_lab", + "data_point_name": "temperature", + "compare_type": "GT", + "threshold": 30.0, + "level": "CRITICAL", "debounce_seconds": 60, - "alarm_mqtt_topic": "alarms/rtu_lab", + "alarm_mqtt_topic": "alarms/rtu_lab", "message_template": "RTU温度传感器'{device_id}' 检测到温度过高 ({value}°C),已达临界值 {threshold}°C!", - "clear_message_template": "RTU温度传感器'{device_id}' 温度已恢复正常 ({value}°C)。" + "clear_message_template": "RTU温度传感器'{device_id}' 温度已恢复正常 ({value}°C)。" + }, + { + "rule_id": "TEST_TEMP_GT_10_WARNING", + "device_id": "rtu_temp_sensor_lab", + "data_point_name": "temperature", + "compare_type": "GT", + "threshold": 10.0, + "level": "CRITICAL", + "debounce_seconds": 0, + "alarm_mqtt_topic": "alarms/rtu_lab", + "message_template": "测试告警 设备 '{device_id}' 温度 ({value}°C) 高于测试阈值 {threshold}°C!", + "clear_message_template": "【测试告警解除】设备 '{device_id}' 温度 ({value}°C) 已恢复正常。" }, { "rule_id": "ANY_TEMP_CRITICAL", - "device_id": "*", + "device_id": "*", "data_point_name": "temperature", - "compare_type": "GT", + "compare_type": "GT", "threshold": 40.0, "level": "CRITICAL", - "alarm_mqtt_topic": "alarms/critical", + "alarm_mqtt_topic": "alarms/critical", "message_template": "【全局报警】任意设备 '{device_id}' 的温度 ({value}°C) 达到或超过 {threshold}°C。", "clear_message_template": "【全局报警解除】设备 '{device_id}' 的温度 ({value}°C) 已恢复。" }, { "rule_id": "SYSTEM_CPU_HIGH", - "device_id": "proxy_system", - "data_point_name": "cpu_usage", - "compare_type": "GT", - "threshold": 90.0, - "level": "WARNING", - "debounce_seconds": 30, + "device_id": "proxy_system", + "data_point_name": "cpu_usage", + "compare_type": "GT", + "threshold": 90.0, + "level": "WARNING", + "debounce_seconds": 30, "message_template": "代理系统 CPU 利用率 ({value}%) 超过 {threshold}%,请检查!", "clear_message_template": "代理系统 CPU 利用率已恢复 ({value}%)。" }, { "rule_id": "SYSTEM_MEM_HIGH", - "device_id": "proxy_system", - "data_point_name": "mem_usage_percentage", - "compare_type": "GT", - "threshold": 80.0, - "level": "WARNING", - "debounce_seconds": 30, + "device_id": "proxy_system", + "data_point_name": "mem_usage_percentage", + "compare_type": "GT", + "threshold": 80.0, + "level": "WARNING", + "debounce_seconds": 30, "message_template": "代理系统内存使用率 ({value}%) 超过 {threshold}%,请检查!", "clear_message_template": "代理系统内存使用率已恢复 ({value}%)。" } diff --git a/config/devices.json b/config/devices.json index 99da3d9..5fa5843 100644 --- a/config/devices.json +++ b/config/devices.json @@ -23,7 +23,7 @@ ] }, { - "enabled": true, + "enabled": false, "device_id": "rotary encoder", "port_path": "/dev/ttyS7", "baud_rate": 9600, diff --git a/src/alarm/alarm_service.cc b/src/alarm/alarm_service.cc index d61f6f0..dc3a811 100644 --- a/src/alarm/alarm_service.cc +++ b/src/alarm/alarm_service.cc @@ -1,10 +1,11 @@ // alarm/alarm_service.cpp #include "alarm_service.h" -#include "dataStorage/data_storage.h" #include #include +#include "dataStorage/data_storage.h" + AlarmService::AlarmService(boost::asio::io_context& io, PiperTTSInterface& tts_service, MqttClient& mqtt_client) @@ -270,7 +271,7 @@ void AlarmService::trigger_alarm_action(AlarmRule& rule, double value, .count(); // 1. 语音播报 (加入队列) - schedule_tts(message); + // schedule_tts(message); // 2. 发布到MQTT std::string topic = @@ -429,5 +430,51 @@ bool AlarmService::parse_rules_from_file( return false; } + return true; +} + +bool AlarmService::manually_clear_alarm(const std::string& rule_id, + const std::string& device_id) { + auto it = m_alarm_states.find(rule_id); + if (it == m_alarm_states.end()) { + spdlog::warn( + "Cannot manually clear alarm: Rule '{}' not found in state map.", + rule_id); + return false; + } + + AlarmState& state = it->second; + state.current_state = AlarmStateType::NORMAL; + state.debounce_timer.cancel(); // 确保计时器被取消 + spdlog::info("[ALARM MANUALLY CLEARED] (Rule: {}) for Device: {}", rule_id, + device_id); + + // 2. 写入数据库日志 + AlarmEvent event; + event.rule_id = rule_id; + event.device_id = device_id; + event.status = AlarmEventStatus::CLEARED; // 标记为 CLEARED + event.message = "Alarm manually cleared by user."; + event.trigger_value = 0.0; + event.timestamp_ms = std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()) + .count(); + + for (const auto& rule : m_rules) { + if (rule.rule_id == rule_id) { + event.level = AlarmRule::levelToString(rule.level); + break; + } + } + if (event.level.empty()) { + event.level = "INFO"; + } + + if (!DataStorage::getInstance().storeAlarmEvent(event)) { + spdlog::error("Failed to store MANUALLY CLEARED alarm event for rule: {}", + rule_id); + return false; + } + return true; } \ No newline at end of file diff --git a/src/alarm/alarm_service.h b/src/alarm/alarm_service.h index 4e37f65..45709d1 100644 --- a/src/alarm/alarm_service.h +++ b/src/alarm/alarm_service.h @@ -15,7 +15,7 @@ #include "alarm_event.h" #include "mqtt/mqtt_client.h" #include "spdlog/spdlog.h" -#include "tts/piper_tts_interface.h" +#include "tts/piper_tts_interface.h" using json = nlohmann::json; @@ -39,6 +39,15 @@ class AlarmService { nlohmann::json getActiveAlarmsJson(); nlohmann::json getAlarmHistoryJson(int limit); + /** + * @brief [新增] 手动清除一个当前激活的告警 + * @param rule_id 要清除的规则ID + * @param device_id 触发该规则的设备ID + * @return 成功则返回 true + */ + bool manually_clear_alarm(const std::string& rule_id, + const std::string& device_id); + private: void check_rule_against_value(AlarmRule& rule, double value, const std::string& actual_device_id); @@ -62,8 +71,8 @@ class AlarmService { std::vector m_rules; std::map m_alarm_states; - std::string m_rules_config_path; - std::mutex m_rules_mutex; + std::string m_rules_config_path; + std::mutex m_rules_mutex; // TTS 播报队列 std::mutex m_tts_queue_mutex; diff --git a/src/modbus/modbus_rtu_client.cc b/src/modbus/modbus_rtu_client.cc index 268d0ee..cdd0f90 100644 --- a/src/modbus/modbus_rtu_client.cc +++ b/src/modbus/modbus_rtu_client.cc @@ -1,517 +1,697 @@ #include "modbus_rtu_client.h" -#include -#include -#include -#include -#include -#include -#include #include #include +#include + +#include +#include +#include +#include +#include +#include uint16_t calculateModbusCRC(const std::vector& data) { - uint16_t crc = 0xFFFF; - for (uint8_t byte : data) { - crc ^= byte; - for (int i = 0; i < 8; ++i) { - if (crc & 0x0001) { - crc >>= 1; - crc ^= 0xA001; - } else { - crc >>= 1; - } - } + uint16_t crc = 0xFFFF; + for (uint8_t byte : data) { + crc ^= byte; + for (int i = 0; i < 8; ++i) { + if (crc & 0x0001) { + crc >>= 1; + crc ^= 0xA001; + } else { + crc >>= 1; + } } - return crc; + } + return crc; } -ModbusRequest::ModbusRequest(uint8_t id, uint8_t fc, uint16_t addr, uint16_t qty_or_val_in_read, const std::vector& values) - : slave_id(id), function_code(fc), start_address(addr), quantity(qty_or_val_in_read), write_values(values) {} +ModbusRequest::ModbusRequest(uint8_t id, uint8_t fc, uint16_t addr, + uint16_t qty_or_val_in_read, + const std::vector& values) + : slave_id(id), + function_code(fc), + start_address(addr), + quantity(qty_or_val_in_read), + write_values(values) {} -ModbusRequest ModbusRequest::createReadRegistersRequest(uint8_t id, uint8_t fc, uint16_t addr, uint16_t qty) { - if (fc != 0x03 && fc != 0x04) { - throw std::invalid_argument("createReadRegistersRequest expects FC 0x03 or 0x04."); - } - return ModbusRequest(id, fc, addr, qty); +ModbusRequest ModbusRequest::createReadRegistersRequest(uint8_t id, uint8_t fc, + uint16_t addr, + uint16_t qty) { + if (fc != 0x03 && fc != 0x04) { + throw std::invalid_argument( + "createReadRegistersRequest expects FC 0x03 or 0x04."); + } + return ModbusRequest(id, fc, addr, qty); } -ModbusRequest ModbusRequest::createWriteSingleRegisterRequest(uint8_t id, uint16_t addr, uint16_t value) { - return ModbusRequest(id, 0x06, addr, 1, {value}); +ModbusRequest ModbusRequest::createWriteSingleRegisterRequest(uint8_t id, + uint16_t addr, + uint16_t value) { + return ModbusRequest(id, 0x06, addr, 1, {value}); } -ModbusRequest ModbusRequest::createWriteMultipleRegistersRequest(uint8_t id, uint16_t addr, const std::vector& values) { - if (values.empty()) { - throw std::invalid_argument("createWriteMultipleRegistersRequest: values vector cannot be empty."); - } - return ModbusRequest(id, 0x10, addr, static_cast(values.size()), values); +ModbusRequest ModbusRequest::createWriteMultipleRegistersRequest( + uint8_t id, uint16_t addr, const std::vector& values) { + if (values.empty()) { + throw std::invalid_argument( + "createWriteMultipleRegistersRequest: values vector cannot be empty."); + } + return ModbusRequest(id, 0x10, addr, static_cast(values.size()), + values); } std::vector ModbusRequest::toRawBytes() const { - std::vector pdu_payload; - pdu_payload.push_back(static_cast((start_address >> 8) & 0xFF)); - pdu_payload.push_back(static_cast(start_address & 0xFF)); + std::vector pdu_payload; + pdu_payload.push_back(static_cast((start_address >> 8) & 0xFF)); + pdu_payload.push_back(static_cast(start_address & 0xFF)); - if (function_code == 0x03 || function_code == 0x04) { - pdu_payload.push_back(static_cast((quantity >> 8) & 0xFF)); - pdu_payload.push_back(static_cast(quantity & 0xFF)); - } else if (function_code == 0x06) { - if (write_values.size() != 1) { - throw std::runtime_error("ModbusRequest error: FC 0x06 requires exactly one value in write_values."); - } - pdu_payload.push_back(static_cast((write_values[0] >> 8) & 0xFF)); - pdu_payload.push_back(static_cast(write_values[0] & 0xFF)); - } else if (function_code == 0x10) { - if (write_values.empty() || quantity != write_values.size()) { - throw std::runtime_error("ModbusRequest error: FC 0x10 requires non-empty write_values matching quantity."); - } - pdu_payload.push_back(static_cast((quantity >> 8) & 0xFF)); - pdu_payload.push_back(static_cast(quantity & 0xFF)); - pdu_payload.push_back(static_cast(write_values.size() * 2)); - for (uint16_t val : write_values) { - pdu_payload.push_back(static_cast((val >> 8) & 0xFF)); - pdu_payload.push_back(static_cast(val & 0xFF)); - } - } else { - throw std::invalid_argument("Unsupported function code for request generation: " + std::to_string(function_code)); + if (function_code == 0x03 || function_code == 0x04) { + pdu_payload.push_back(static_cast((quantity >> 8) & 0xFF)); + pdu_payload.push_back(static_cast(quantity & 0xFF)); + } else if (function_code == 0x06) { + if (write_values.size() != 1) { + throw std::runtime_error( + "ModbusRequest error: FC 0x06 requires exactly one value in " + "write_values."); } + pdu_payload.push_back(static_cast((write_values[0] >> 8) & 0xFF)); + pdu_payload.push_back(static_cast(write_values[0] & 0xFF)); + } else if (function_code == 0x10) { + if (write_values.empty() || quantity != write_values.size()) { + throw std::runtime_error( + "ModbusRequest error: FC 0x10 requires non-empty write_values " + "matching quantity."); + } + pdu_payload.push_back(static_cast((quantity >> 8) & 0xFF)); + pdu_payload.push_back(static_cast(quantity & 0xFF)); + pdu_payload.push_back(static_cast(write_values.size() * 2)); + for (uint16_t val : write_values) { + pdu_payload.push_back(static_cast((val >> 8) & 0xFF)); + pdu_payload.push_back(static_cast(val & 0xFF)); + } + } else { + throw std::invalid_argument( + "Unsupported function code for request generation: " + + std::to_string(function_code)); + } - std::vector adu; - adu.reserve(pdu_payload.size() + 4); - adu.push_back(slave_id); - adu.push_back(function_code); - adu.insert(adu.end(), pdu_payload.begin(), pdu_payload.end()); + std::vector adu; + adu.reserve(pdu_payload.size() + 4); + adu.push_back(slave_id); + adu.push_back(function_code); + adu.insert(adu.end(), pdu_payload.begin(), pdu_payload.end()); - uint16_t crc = calculateModbusCRC(adu); - adu.push_back(static_cast(crc & 0xFF)); - adu.push_back(static_cast((crc >> 8) & 0xFF)); - return adu; + uint16_t crc = calculateModbusCRC(adu); + adu.push_back(static_cast(crc & 0xFF)); + adu.push_back(static_cast((crc >> 8) & 0xFF)); + return adu; } -ModbusResponse::ModbusResponse() : slave_id(0), function_code(0), is_exception(false), exception_code(0), received_crc(0), crc_ok(false) {} +ModbusResponse::ModbusResponse() + : slave_id(0), + function_code(0), + is_exception(false), + exception_code(0), + received_crc(0), + crc_ok(false) {} std::vector ModbusResponse::getRegisters() const { - std::vector registers; - if (is_exception) { - throw std::runtime_error("Attempted to get registers from a Modbus exception response."); - } + std::vector registers; + if (is_exception) { + throw std::runtime_error( + "Attempted to get registers from a Modbus exception response."); + } - if (function_code == 0x03 || function_code == 0x04) { - if (pdu_data.empty()) return {}; - uint8_t byte_count = pdu_data[0]; - if (byte_count % 2 != 0) { - throw std::runtime_error("Received odd number of data bytes for registers (after byte count field)."); - } - if (pdu_data.size() < (1 + byte_count)) { - throw std::runtime_error("Received data length mismatch with byte count field in Modbus response."); - } - for (size_t i = 1; i < 1 + byte_count; i += 2) { - registers.push_back((static_cast(pdu_data[i]) << 8) | pdu_data[i+1]); - } - } else if (function_code == 0x06 || function_code == 0x10) { - for (size_t i = 0; i < pdu_data.size(); i += 2) { - if (i + 1 < pdu_data.size()) { - registers.push_back((static_cast(pdu_data[i]) << 8) | pdu_data[i+1]); - } - } - } else { - for (size_t i = 0; i < pdu_data.size(); i += 2) { - if (i + 1 < pdu_data.size()) { - registers.push_back((static_cast(pdu_data[i]) << 8) | pdu_data[i+1]); - } - } + if (function_code == 0x03 || function_code == 0x04) { + if (pdu_data.empty()) return {}; + uint8_t byte_count = pdu_data[0]; + if (byte_count % 2 != 0) { + throw std::runtime_error( + "Received odd number of data bytes for registers (after byte count " + "field)."); } - return registers; + if (pdu_data.size() < (1 + byte_count)) { + throw std::runtime_error( + "Received data length mismatch with byte count field in Modbus " + "response."); + } + for (size_t i = 1; i < 1 + byte_count; i += 2) { + registers.push_back((static_cast(pdu_data[i]) << 8) | + pdu_data[i + 1]); + } + } else if (function_code == 0x06 || function_code == 0x10) { + for (size_t i = 0; i < pdu_data.size(); i += 2) { + if (i + 1 < pdu_data.size()) { + registers.push_back((static_cast(pdu_data[i]) << 8) | + pdu_data[i + 1]); + } + } + } else { + for (size_t i = 0; i < pdu_data.size(); i += 2) { + if (i + 1 < pdu_data.size()) { + registers.push_back((static_cast(pdu_data[i]) << 8) | + pdu_data[i + 1]); + } + } + } + return registers; } - void ModbusResponse::print() const { - std::cout << "Response: Slave ID: 0x" << std::hex << static_cast(slave_id) - << ", FC: 0x" << static_cast(function_code); - if (is_exception) { - std::cout << ", EXCEPTION: 0x" << static_cast(exception_code) << std::dec; - std::cout << " ("; - switch (exception_code) { - case 0x01: std::cout << "ILLEGAL FUNCTION"; break; - case 0x02: std::cout << "ILLEGAL DATA ADDRESS"; break; - case 0x03: std::cout << "ILLEGAL DATA VALUE"; break; - case 0x04: std::cout << "SLAVE DEVICE FAILURE"; break; - case 0x05: std::cout << "ACKNOWLEDGE"; break; - case 0x06: std::cout << "SLAVE DEVICE BUSY"; break; - default: std::cout << "UNKNOWN"; break; - } - std::cout << ")"; - } else { - std::cout << ", PDU Data (" << pdu_data.size() << " bytes): ["; - for (uint8_t byte : pdu_data) { - std::cout << std::setw(2) << std::setfill('0') << static_cast(byte) << " "; - } - if (!pdu_data.empty()) { - } - std::cout << "]"; + std::cout << "Response: Slave ID: 0x" << std::hex + << static_cast(slave_id) << ", FC: 0x" + << static_cast(function_code); + if (is_exception) { + std::cout << ", EXCEPTION: 0x" << static_cast(exception_code) + << std::dec; + std::cout << " ("; + switch (exception_code) { + case 0x01: + std::cout << "ILLEGAL FUNCTION"; + break; + case 0x02: + std::cout << "ILLEGAL DATA ADDRESS"; + break; + case 0x03: + std::cout << "ILLEGAL DATA VALUE"; + break; + case 0x04: + std::cout << "SLAVE DEVICE FAILURE"; + break; + case 0x05: + std::cout << "ACKNOWLEDGE"; + break; + case 0x06: + std::cout << "SLAVE DEVICE BUSY"; + break; + default: + std::cout << "UNKNOWN"; + break; } - std::cout << ", CRC: 0x" << std::setw(4) << std::setfill('0') << received_crc; - std::cout << ", CRC OK: " << (crc_ok ? "True" : "False") << std::dec << std::endl; + std::cout << ")"; + } else { + std::cout << ", PDU Data (" << pdu_data.size() << " bytes): ["; + for (uint8_t byte : pdu_data) { + std::cout << std::setw(2) << std::setfill('0') << static_cast(byte) + << " "; + } + if (!pdu_data.empty()) { + } + std::cout << "]"; + } + std::cout << ", CRC: 0x" << std::setw(4) << std::setfill('0') << received_crc; + std::cout << ", CRC OK: " << (crc_ok ? "True" : "False") << std::dec + << std::endl; } -ModbusRTUClient::ModbusRTUClient(const std::string& port_path, unsigned int baud_rate, - int databits, int stopbits, char parity) - : portPath_(port_path), baudRate_(baud_rate), databits_(databits), stopbits_(stopbits), parity_(parity), +ModbusRTUClient::ModbusRTUClient(const std::string& port_path, + unsigned int baud_rate, int databits, + int stopbits, char parity) + : portPath_(port_path), + baudRate_(baud_rate), + databits_(databits), + stopbits_(stopbits), + parity_(parity), serialFd_(-1) {} -ModbusRTUClient::~ModbusRTUClient() { +ModbusRTUClient::~ModbusRTUClient() { closePort(); } + +bool ModbusRTUClient::setPortSettings(const std::string& port_path, + unsigned int baud_rate, int databits, + int stopbits, char parity) { + bool settings_changed = + (portPath_ != port_path || baudRate_ != baud_rate || + databits_ != databits || stopbits_ != stopbits || parity_ != parity); + + if (serialFd_ != -1 && settings_changed) { + std::cout << "Port settings changed. Closing current port and re-opening." + << std::endl; closePort(); -} + } -bool ModbusRTUClient::setPortSettings(const std::string& port_path, unsigned int baud_rate, - int databits, int stopbits, char parity) { - bool settings_changed = (portPath_ != port_path || baudRate_ != baud_rate || - databits_ != databits || stopbits_ != stopbits || - parity_ != parity); + portPath_ = port_path; + baudRate_ = baud_rate; + databits_ = databits; + stopbits_ = stopbits; + parity_ = parity; - if (serialFd_ != -1 && settings_changed) { - std::cout << "Port settings changed. Closing current port and re-opening." << std::endl; - closePort(); - } + if (serialFd_ == -1 || settings_changed) { + return _openPortInternal_(); + } - portPath_ = port_path; - baudRate_ = baud_rate; - databits_ = databits; - stopbits_ = stopbits; - parity_ = parity; - - if (serialFd_ == -1 || settings_changed) { - return _openPortInternal_(); - } - - std::cout << "Port " << portPath_ << " already open with specified settings." << std::endl; - return true; + std::cout << "Port " << portPath_ << " already open with specified settings." + << std::endl; + return true; } void ModbusRTUClient::closePort() { - if (serialFd_ != -1) { - if (close(serialFd_) == 0) { - std::cout << "Serial port " << portPath_ << " closed." << std::endl; - } else { - std::cerr << "Error " << errno << " closing serial port " << portPath_ << ": " << strerror(errno) << std::endl; - } - serialFd_ = -1; + if (serialFd_ != -1) { + if (close(serialFd_) == 0) { + std::cout << "Serial port " << portPath_ << " closed." << std::endl; + } else { + std::cerr << "Error " << errno << " closing serial port " << portPath_ + << ": " << strerror(errno) << std::endl; } + serialFd_ = -1; + } } ModbusResponse ModbusRTUClient::sendAndReceive(const ModbusRequest& request) { - if (serialFd_ == -1) { - throw std::runtime_error("Serial port is not open or configured. Call setPortSettings() first."); - } + if (serialFd_ == -1) { + throw std::runtime_error( + "Serial port is not open or configured. Call setPortSettings() first."); + } - std::vector request_adu = request.toRawBytes(); + std::vector request_adu = request.toRawBytes(); - tcflush(serialFd_, TCIFLUSH); - ssize_t bytes_written = write(serialFd_, request_adu.data(), request_adu.size()); - if (bytes_written == -1 || bytes_written != static_cast(request_adu.size())) { + tcflush(serialFd_, TCIFLUSH); + ssize_t bytes_written = + write(serialFd_, request_adu.data(), request_adu.size()); + if (bytes_written == -1 || + bytes_written != static_cast(request_adu.size())) { + std::stringstream ss; + ss << "Error writing to serial port: " << strerror(errno) + << ". Attempted to write " << request_adu.size() + << " bytes, actually wrote " << bytes_written << "."; + throw std::runtime_error(ss.str()); + } + + // std::cout << "Sent ADU: "; + // for (uint8_t byte : request_adu) { + // std::cout << std::setw(2) << std::setfill('0') << std::hex << + // static_cast(byte) << " "; + // } + // std::cout << std::dec << std::endl; + + std::vector response_adu_buffer; + auto start_time = std::chrono::high_resolution_clock::now(); + const auto overall_timeout_duration = std::chrono::milliseconds(1000); + const auto inter_byte_timeout_duration = std::chrono::milliseconds(50); + + for (size_t i = 0; i < 2; ++i) { + uint8_t byte; + ssize_t bytes_read_now = read(serialFd_, &byte, 1); + if (bytes_read_now > 0) { + response_adu_buffer.push_back(byte); + start_time = std::chrono::high_resolution_clock::now(); + } else if (bytes_read_now == 0) { + if (std::chrono::high_resolution_clock::now() - start_time > + overall_timeout_duration) { std::stringstream ss; - ss << "Error writing to serial port: " << strerror(errno) << ". Attempted to write " << request_adu.size() << " bytes, actually wrote " << bytes_written << "."; + ss << "Read initial 2 bytes (SlaveID/FC) timeout (" + << std::chrono::duration_cast( + overall_timeout_duration) + .count() + << "ms): " + << "received only " << response_adu_buffer.size() << " bytes."; throw std::runtime_error(ss.str()); - } - - std::cout << "Sent ADU: "; - for (uint8_t byte : request_adu) { - std::cout << std::setw(2) << std::setfill('0') << std::hex << static_cast(byte) << " "; - } - std::cout << std::dec << std::endl; - - std::vector response_adu_buffer; - auto start_time = std::chrono::high_resolution_clock::now(); - const auto overall_timeout_duration = std::chrono::milliseconds(1000); - const auto inter_byte_timeout_duration = std::chrono::milliseconds(50); - - for (size_t i = 0; i < 2; ++i) { - uint8_t byte; - ssize_t bytes_read_now = read(serialFd_, &byte, 1); - if (bytes_read_now > 0) { - response_adu_buffer.push_back(byte); - start_time = std::chrono::high_resolution_clock::now(); - } else if (bytes_read_now == 0) { - if (std::chrono::high_resolution_clock::now() - start_time > overall_timeout_duration) { - std::stringstream ss; - ss << "Read initial 2 bytes (SlaveID/FC) timeout (" << std::chrono::duration_cast(overall_timeout_duration).count() << "ms): " - << "received only " << response_adu_buffer.size() << " bytes."; - throw std::runtime_error(ss.str()); - } - std::this_thread::sleep_for(std::chrono::milliseconds(1)); - i--; - } else { - std::stringstream ss; - ss << "Error reading from serial port (initial bytes): " << strerror(errno); - throw std::runtime_error(ss.str()); - } - } - - if (!response_adu_buffer.empty() && response_adu_buffer[0] != request.slave_id) { - std::stringstream ss; - ss << "Modbus response slave ID mismatch. Expected 0x" << std::hex << static_cast(request.slave_id) - << ", received 0x" << static_cast(response_adu_buffer[0]) << std::dec << "."; - throw std::runtime_error(ss.str()); - } - - ModbusResponse modbus_response; - modbus_response.slave_id = response_adu_buffer[0]; - modbus_response.function_code = response_adu_buffer[1]; - - bool is_exception_response = (response_adu_buffer[1] == (request.function_code | 0x80)); - - size_t expected_pdu_payload_len = 0; - if (is_exception_response) { - expected_pdu_payload_len = 1; + } + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + i--; } else { - switch (modbus_response.function_code) { - case 0x03: - // TODO: 读寄存器后续完成 - case 0x04: - while (response_adu_buffer.size() < 3) { - uint8_t byte; - ssize_t bytes_read_now = read(serialFd_, &byte, 1); - if (bytes_read_now > 0) { - response_adu_buffer.push_back(byte); - start_time = std::chrono::high_resolution_clock::now(); - } else if (bytes_read_now == 0) { - if (std::chrono::high_resolution_clock::now() - start_time > inter_byte_timeout_duration) { - std::stringstream ss; - ss << "Inter-byte timeout reading ByteCount for FC 0x" << std::hex << static_cast(modbus_response.function_code) << std::dec << "."; - throw std::runtime_error(ss.str()); - } - if (std::chrono::high_resolution_clock::now() - start_time > overall_timeout_duration) { - std::stringstream ss; - ss << "Overall timeout reading ByteCount. Received " << response_adu_buffer.size() << " bytes."; - throw std::runtime_error(ss.str()); - } - std::this_thread::sleep_for(std::chrono::milliseconds(1)); - } else { - std::stringstream ss; - ss << "Error reading from serial port (byte count): " << strerror(errno); - throw std::runtime_error(ss.str()); - } - } - expected_pdu_payload_len = 1 + response_adu_buffer[2]; - break; - case 0x06: - expected_pdu_payload_len = 4; - break; - case 0x10: - expected_pdu_payload_len = 4; - break; - default: - throw std::runtime_error("Unsupported Modbus function code for detailed response parsing: 0x" + std::to_string(modbus_response.function_code)); - } + std::stringstream ss; + ss << "Error reading from serial port (initial bytes): " + << strerror(errno); + throw std::runtime_error(ss.str()); } + } - size_t total_frame_len = 2 + expected_pdu_payload_len + 2; - while (response_adu_buffer.size() < total_frame_len) { - uint8_t byte; - ssize_t bytes_read_now = read(serialFd_, &byte, 1); - if (bytes_read_now > 0) { + if (!response_adu_buffer.empty() && + response_adu_buffer[0] != request.slave_id) { + std::stringstream ss; + ss << "Modbus response slave ID mismatch. Expected 0x" << std::hex + << static_cast(request.slave_id) << ", received 0x" + << static_cast(response_adu_buffer[0]) << std::dec << "."; + throw std::runtime_error(ss.str()); + } + + ModbusResponse modbus_response; + modbus_response.slave_id = response_adu_buffer[0]; + modbus_response.function_code = response_adu_buffer[1]; + + bool is_exception_response = + (response_adu_buffer[1] == (request.function_code | 0x80)); + + size_t expected_pdu_payload_len = 0; + if (is_exception_response) { + expected_pdu_payload_len = 1; + } else { + switch (modbus_response.function_code) { + case 0x03: + // TODO: 读寄存器后续完成 + case 0x04: + while (response_adu_buffer.size() < 3) { + uint8_t byte; + ssize_t bytes_read_now = read(serialFd_, &byte, 1); + if (bytes_read_now > 0) { response_adu_buffer.push_back(byte); start_time = std::chrono::high_resolution_clock::now(); - } else if (bytes_read_now == 0) { - if (std::chrono::high_resolution_clock::now() - start_time > inter_byte_timeout_duration) { - std::stringstream ss; - ss << "Inter-byte timeout during response read. After " << response_adu_buffer.size() << " bytes, received no more data for " << std::chrono::duration_cast(inter_byte_timeout_duration).count() << "ms."; - throw std::runtime_error(ss.str()); + } else if (bytes_read_now == 0) { + if (std::chrono::high_resolution_clock::now() - start_time > + inter_byte_timeout_duration) { + std::stringstream ss; + ss << "Inter-byte timeout reading ByteCount for FC 0x" << std::hex + << static_cast(modbus_response.function_code) << std::dec + << "."; + throw std::runtime_error(ss.str()); } - if (std::chrono::high_resolution_clock::now() - start_time > overall_timeout_duration) { - std::stringstream ss; - ss << "Overall timeout after receiving " << response_adu_buffer.size() << " bytes out of expected " << total_frame_len << " bytes."; - throw std::runtime_error(ss.str()); + if (std::chrono::high_resolution_clock::now() - start_time > + overall_timeout_duration) { + std::stringstream ss; + ss << "Overall timeout reading ByteCount. Received " + << response_adu_buffer.size() << " bytes."; + throw std::runtime_error(ss.str()); } std::this_thread::sleep_for(std::chrono::milliseconds(1)); - } else { + } else { std::stringstream ss; - ss << "Error reading from serial port (PDU/CRC): " << strerror(errno); + ss << "Error reading from serial port (byte count): " + << strerror(errno); throw std::runtime_error(ss.str()); + } } + expected_pdu_payload_len = 1 + response_adu_buffer[2]; + break; + case 0x06: + expected_pdu_payload_len = 4; + break; + case 0x10: + expected_pdu_payload_len = 4; + break; + default: + throw std::runtime_error( + "Unsupported Modbus function code for detailed response parsing: " + "0x" + + std::to_string(modbus_response.function_code)); } + } - modbus_response.is_exception = is_exception_response; - if (is_exception_response) { - modbus_response.exception_code = response_adu_buffer[2]; - modbus_response.pdu_data.push_back(response_adu_buffer[2]); - } else { - modbus_response.pdu_data.assign(response_adu_buffer.begin() + 2, - response_adu_buffer.begin() + 2 + expected_pdu_payload_len); - } - - if (response_adu_buffer.size() >= 2) { - modbus_response.received_crc = (static_cast(response_adu_buffer[response_adu_buffer.size() - 1]) << 8) | response_adu_buffer[response_adu_buffer.size() - 2]; - } else { - throw std::runtime_error("Response buffer too small to contain CRC."); - } - - std::vector adu_without_crc(response_adu_buffer.begin(), response_adu_buffer.end() - 2); - uint16_t calculated_crc = calculateModbusCRC(adu_without_crc); - modbus_response.crc_ok = (modbus_response.received_crc == calculated_crc); - - if (!modbus_response.crc_ok) { + size_t total_frame_len = 2 + expected_pdu_payload_len + 2; + while (response_adu_buffer.size() < total_frame_len) { + uint8_t byte; + ssize_t bytes_read_now = read(serialFd_, &byte, 1); + if (bytes_read_now > 0) { + response_adu_buffer.push_back(byte); + start_time = std::chrono::high_resolution_clock::now(); + } else if (bytes_read_now == 0) { + if (std::chrono::high_resolution_clock::now() - start_time > + inter_byte_timeout_duration) { std::stringstream ss; - ss << "CRC mismatch in response. Calculated 0x" << std::setw(4) << std::setfill('0') << std::hex << calculated_crc - << ", received 0x" << std::setw(4) << std::setfill('0') << modbus_response.received_crc << std::dec; + ss << "Inter-byte timeout during response read. After " + << response_adu_buffer.size() << " bytes, received no more data for " + << std::chrono::duration_cast( + inter_byte_timeout_duration) + .count() + << "ms."; throw std::runtime_error(ss.str()); - } - - if (modbus_response.is_exception) { + } + if (std::chrono::high_resolution_clock::now() - start_time > + overall_timeout_duration) { std::stringstream ss; - ss << "Modbus Exception Response (slave_id=0x" << std::hex << static_cast(modbus_response.slave_id) - << ", FC=0x" << (static_cast(modbus_response.function_code) & 0x7F) << ", ExceptionCode=0x" - << static_cast(modbus_response.exception_code) << std::dec << "): "; - switch (modbus_response.exception_code) { - case 0x01: ss << "ILLEGAL FUNCTION"; break; - case 0x02: ss << "ILLEGAL DATA ADDRESS"; break; - case 0x03: ss << "ILLEGAL DATA VALUE"; break; - case 0x04: ss << "SLAVE DEVICE FAILURE"; break; - case 0x05: ss << "ACKNOWLEDGE"; break; - case 0x06: ss << "SLAVE DEVICE BUSY"; break; - default: ss << "UNKNOWN EXCEPTION"; break; - } + ss << "Overall timeout after receiving " << response_adu_buffer.size() + << " bytes out of expected " << total_frame_len << " bytes."; throw std::runtime_error(ss.str()); + } + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } else { + std::stringstream ss; + ss << "Error reading from serial port (PDU/CRC): " << strerror(errno); + throw std::runtime_error(ss.str()); } + } - std::cout << "Received ADU: "; - for (uint8_t byte : response_adu_buffer) { - std::cout << std::setw(2) << std::setfill('0') << std::hex << static_cast(byte) << " "; + modbus_response.is_exception = is_exception_response; + if (is_exception_response) { + modbus_response.exception_code = response_adu_buffer[2]; + modbus_response.pdu_data.push_back(response_adu_buffer[2]); + } else { + modbus_response.pdu_data.assign( + response_adu_buffer.begin() + 2, + response_adu_buffer.begin() + 2 + expected_pdu_payload_len); + } + + if (response_adu_buffer.size() >= 2) { + modbus_response.received_crc = + (static_cast( + response_adu_buffer[response_adu_buffer.size() - 1]) + << 8) | + response_adu_buffer[response_adu_buffer.size() - 2]; + } else { + throw std::runtime_error("Response buffer too small to contain CRC."); + } + + std::vector adu_without_crc(response_adu_buffer.begin(), + response_adu_buffer.end() - 2); + uint16_t calculated_crc = calculateModbusCRC(adu_without_crc); + modbus_response.crc_ok = (modbus_response.received_crc == calculated_crc); + + if (!modbus_response.crc_ok) { + std::stringstream ss; + ss << "CRC mismatch in response. Calculated 0x" << std::setw(4) + << std::setfill('0') << std::hex << calculated_crc << ", received 0x" + << std::setw(4) << std::setfill('0') << modbus_response.received_crc + << std::dec; + throw std::runtime_error(ss.str()); + } + + if (modbus_response.is_exception) { + std::stringstream ss; + ss << "Modbus Exception Response (slave_id=0x" << std::hex + << static_cast(modbus_response.slave_id) << ", FC=0x" + << (static_cast(modbus_response.function_code) & 0x7F) + << ", ExceptionCode=0x" + << static_cast(modbus_response.exception_code) << std::dec << "): "; + switch (modbus_response.exception_code) { + case 0x01: + ss << "ILLEGAL FUNCTION"; + break; + case 0x02: + ss << "ILLEGAL DATA ADDRESS"; + break; + case 0x03: + ss << "ILLEGAL DATA VALUE"; + break; + case 0x04: + ss << "SLAVE DEVICE FAILURE"; + break; + case 0x05: + ss << "ACKNOWLEDGE"; + break; + case 0x06: + ss << "SLAVE DEVICE BUSY"; + break; + default: + ss << "UNKNOWN EXCEPTION"; + break; } - std::cout << std::dec << std::endl; - - return modbus_response; + throw std::runtime_error(ss.str()); + } + + // std::cout << "Received ADU: "; + // for (uint8_t byte : response_adu_buffer) { + // std::cout << std::setw(2) << std::setfill('0') << std::hex << + // static_cast(byte) << " "; + // } + // std::cout << std::dec << std::endl; + + return modbus_response; } -std::vector ModbusRTUClient::readHoldingRegisters(uint8_t slave_id, uint16_t start_address, uint16_t quantity) { - ModbusRequest request = ModbusRequest::createReadRegistersRequest(slave_id, 0x03, start_address, quantity); - ModbusResponse response = sendAndReceive(request); - return response.getRegisters(); +std::vector ModbusRTUClient::readHoldingRegisters( + uint8_t slave_id, uint16_t start_address, uint16_t quantity) { + ModbusRequest request = ModbusRequest::createReadRegistersRequest( + slave_id, 0x03, start_address, quantity); + ModbusResponse response = sendAndReceive(request); + return response.getRegisters(); } -std::vector ModbusRTUClient::readInputRegisters(uint8_t slave_id, uint16_t start_address, uint16_t quantity) { - ModbusRequest request = ModbusRequest::createReadRegistersRequest(slave_id, 0x04, start_address, quantity); - ModbusResponse response = sendAndReceive(request); - return response.getRegisters(); +std::vector ModbusRTUClient::readInputRegisters( + uint8_t slave_id, uint16_t start_address, uint16_t quantity) { + ModbusRequest request = ModbusRequest::createReadRegistersRequest( + slave_id, 0x04, start_address, quantity); + ModbusResponse response = sendAndReceive(request); + return response.getRegisters(); } -void ModbusRTUClient::writeSingleRegister(uint8_t slave_id, uint16_t address, uint16_t value) { - ModbusRequest request = ModbusRequest::createWriteSingleRegisterRequest(slave_id, address, value); - sendAndReceive(request); +void ModbusRTUClient::writeSingleRegister(uint8_t slave_id, uint16_t address, + uint16_t value) { + ModbusRequest request = + ModbusRequest::createWriteSingleRegisterRequest(slave_id, address, value); + sendAndReceive(request); } -void ModbusRTUClient::writeMultipleRegisters(uint8_t slave_id, uint16_t start_address, const std::vector& values) { - ModbusRequest request = ModbusRequest::createWriteMultipleRegistersRequest(slave_id, start_address, values); - sendAndReceive(request); +void ModbusRTUClient::writeMultipleRegisters( + uint8_t slave_id, uint16_t start_address, + const std::vector& values) { + ModbusRequest request = ModbusRequest::createWriteMultipleRegistersRequest( + slave_id, start_address, values); + sendAndReceive(request); } - bool ModbusRTUClient::_openPortInternal_() { - if (portPath_.empty() || baudRate_ == 0) { - std::cerr << "Error: Serial port path or baud rate not set. Use setPortSettings first." << std::endl; - return false; - } + if (portPath_.empty() || baudRate_ == 0) { + std::cerr << "Error: Serial port path or baud rate not set. Use " + "setPortSettings first." + << std::endl; + return false; + } - if (serialFd_ != -1) { - closePort(); - } + if (serialFd_ != -1) { + closePort(); + } - serialFd_ = open(portPath_.c_str(), O_RDWR | O_NOCTTY | O_SYNC); - if (serialFd_ < 0) { - std::cerr << "Error " << errno << " opening serial port " << portPath_ << ": " << strerror(errno) << std::endl; - return false; - } + serialFd_ = open(portPath_.c_str(), O_RDWR | O_NOCTTY | O_SYNC); + if (serialFd_ < 0) { + std::cerr << "Error " << errno << " opening serial port " << portPath_ + << ": " << strerror(errno) << std::endl; + return false; + } - struct termios tty; - if (tcgetattr(serialFd_, &tty) != 0) { - std::cerr << "Error " << errno << " from tcgetattr: " << strerror(errno) << std::endl; - close(serialFd_); - serialFd_ = -1; - return false; - } + struct termios tty; + if (tcgetattr(serialFd_, &tty) != 0) { + std::cerr << "Error " << errno << " from tcgetattr: " << strerror(errno) + << std::endl; + close(serialFd_); + serialFd_ = -1; + return false; + } - speed_t speed_const = B0; - switch (baudRate_) { - case 9600: speed_const = B9600; break; - case 19200: speed_const = B19200; break; - case 38400: speed_const = B38400; break; - case 57600: speed_const = B57600; break; - case 115200: speed_const = B115200; break; - case 230400: speed_const = B230400; break; - case 460800: speed_const = B460800; break; - case 500000: speed_const = B500000; break; - case 576000: speed_const = B576000; break; - case 921600: speed_const = B921600; break; - case 1000000: speed_const = B1000000; break; - case 1152000: speed_const = B1152000; break; - case 2000000: speed_const = B2000000; break; - default: - std::cerr << "Unsupported baud rate: " << baudRate_ << ". Please specify one of 9600, 19200, 38400, 57600, 115200, etc." << std::endl; - close(serialFd_); - serialFd_ = -1; - return false; - } - cfsetospeed(&tty, speed_const); - cfsetispeed(&tty, speed_const); + speed_t speed_const = B0; + switch (baudRate_) { + case 9600: + speed_const = B9600; + break; + case 19200: + speed_const = B19200; + break; + case 38400: + speed_const = B38400; + break; + case 57600: + speed_const = B57600; + break; + case 115200: + speed_const = B115200; + break; + case 230400: + speed_const = B230400; + break; + case 460800: + speed_const = B460800; + break; + case 500000: + speed_const = B500000; + break; + case 576000: + speed_const = B576000; + break; + case 921600: + speed_const = B921600; + break; + case 1000000: + speed_const = B1000000; + break; + case 1152000: + speed_const = B1152000; + break; + case 2000000: + speed_const = B2000000; + break; + default: + std::cerr + << "Unsupported baud rate: " << baudRate_ + << ". Please specify one of 9600, 19200, 38400, 57600, 115200, etc." + << std::endl; + close(serialFd_); + serialFd_ = -1; + return false; + } + cfsetospeed(&tty, speed_const); + cfsetispeed(&tty, speed_const); - tty.c_cflag &= ~CSIZE; - switch (databits_) { - case 7: tty.c_cflag |= CS7; break; - case 8: tty.c_cflag |= CS8; break; - default: std::cerr << "Unsupported databits: " << databits_ << std::endl; close(serialFd_); serialFd_ = -1; return false; - } + tty.c_cflag &= ~CSIZE; + switch (databits_) { + case 7: + tty.c_cflag |= CS7; + break; + case 8: + tty.c_cflag |= CS8; + break; + default: + std::cerr << "Unsupported databits: " << databits_ << std::endl; + close(serialFd_); + serialFd_ = -1; + return false; + } - if (parity_ == 'N' || parity_ == 'n') { - tty.c_cflag &= ~PARENB; - tty.c_iflag &= ~INPCK; - } else if (parity_ == 'E' || parity_ == 'e') { - tty.c_cflag |= PARENB; - tty.c_cflag &= ~PARODD; - tty.c_iflag |= INPCK; - } else if (parity_ == 'O' || parity_ == 'o') { - tty.c_cflag |= PARENB | PARODD; - tty.c_iflag |= INPCK; - } else { - std::cerr << "Unsupported parity: " << parity_ << ". Use 'N', 'E', or 'O'." << std::endl; close(serialFd_); serialFd_ = -1; return false; - } + if (parity_ == 'N' || parity_ == 'n') { + tty.c_cflag &= ~PARENB; + tty.c_iflag &= ~INPCK; + } else if (parity_ == 'E' || parity_ == 'e') { + tty.c_cflag |= PARENB; + tty.c_cflag &= ~PARODD; + tty.c_iflag |= INPCK; + } else if (parity_ == 'O' || parity_ == 'o') { + tty.c_cflag |= PARENB | PARODD; + tty.c_iflag |= INPCK; + } else { + std::cerr << "Unsupported parity: " << parity_ << ". Use 'N', 'E', or 'O'." + << std::endl; + close(serialFd_); + serialFd_ = -1; + return false; + } - if (stopbits_ == 1) { - tty.c_cflag &= ~CSTOPB; - } else if (stopbits_ == 2) { - tty.c_cflag |= CSTOPB; - } else { - std::cerr << "Unsupported stopbits: " << stopbits_ << ". Use 1 or 2." << std::endl; close(serialFd_); serialFd_ = -1; return false; - } + if (stopbits_ == 1) { + tty.c_cflag &= ~CSTOPB; + } else if (stopbits_ == 2) { + tty.c_cflag |= CSTOPB; + } else { + std::cerr << "Unsupported stopbits: " << stopbits_ << ". Use 1 or 2." + << std::endl; + close(serialFd_); + serialFd_ = -1; + return false; + } - tty.c_cflag &= ~CRTSCTS; - tty.c_cflag |= CREAD | CLOCAL; // Enable receiver, ignore modem control lines + tty.c_cflag &= ~CRTSCTS; + tty.c_cflag |= CREAD | CLOCAL; // Enable receiver, ignore modem control lines - tty.c_lflag &= ~ICANON; // Disable canonical mode - tty.c_lflag &= ~ECHO; // Disable echo - tty.c_lflag &= ~ECHOE; // Disable erasure - tty.c_lflag &= ~ECHONL; // Disable new-line echo - tty.c_lflag &= ~ISIG; // Disable interpretation of INTR, QUIT and SUSP + tty.c_lflag &= ~ICANON; // Disable canonical mode + tty.c_lflag &= ~ECHO; // Disable echo + tty.c_lflag &= ~ECHOE; // Disable erasure + tty.c_lflag &= ~ECHONL; // Disable new-line echo + tty.c_lflag &= ~ISIG; // Disable interpretation of INTR, QUIT and SUSP - tty.c_iflag &= ~(IXON | IXOFF | IXANY); // Turn off s/w flow ctrl - tty.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL); // Disable any special handling of received bytes + tty.c_iflag &= ~(IXON | IXOFF | IXANY); // Turn off s/w flow ctrl + tty.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | + ICRNL); // Disable any special handling of received bytes - tty.c_oflag &= ~OPOST; // Prevent special interpretation of output bytes (e.g. newline chars) - tty.c_oflag &= ~ONLCR; // Prevent conversion of newline to carriage return/line feed + tty.c_oflag &= ~OPOST; // Prevent special interpretation of output bytes + // (e.g. newline chars) + tty.c_oflag &= + ~ONLCR; // Prevent conversion of newline to carriage return/line feed - // Blocking read with 100ms timeout for each byte (VTIME = 10 * 100ms = 1s, VMIN = 0 for non-blocking read if no bytes) - // If VTIME is set, read() returns after VTIME * 0.1 seconds from the last byte received. - // If VMIN is 0 and VTIME is > 0, read will block for VTIME or until one character is received. - tty.c_cc[VMIN] = 0; // minimum number of characters to read before returning - tty.c_cc[VTIME] = 10; // timeout in 0.1s units (1 second total timeout if no chars received) + // Blocking read with 100ms timeout for each byte (VTIME = 10 * 100ms = 1s, + // VMIN = 0 for non-blocking read if no bytes) If VTIME is set, read() returns + // after VTIME * 0.1 seconds from the last byte received. If VMIN is 0 and + // VTIME is > 0, read will block for VTIME or until one character is received. + tty.c_cc[VMIN] = 0; // minimum number of characters to read before returning + tty.c_cc[VTIME] = 10; // timeout in 0.1s units (1 second total timeout if no + // chars received) - if (tcsetattr(serialFd_, TCSANOW, &tty) != 0) { - std::cerr << "Error " << errno << " from tcsetattr: " << strerror(errno) << std::endl; - close(serialFd_); - serialFd_ = -1; - return false; - } + if (tcsetattr(serialFd_, TCSANOW, &tty) != 0) { + std::cerr << "Error " << errno << " from tcsetattr: " << strerror(errno) + << std::endl; + close(serialFd_); + serialFd_ = -1; + return false; + } - std::cout << "Successfully configured serial port " << portPath_ - << " (" << baudRate_ << " " << databits_ << parity_ << stopbits_ << ")." << std::endl; - return true; + std::cout << "Successfully configured serial port " << portPath_ << " (" + << baudRate_ << " " << databits_ << parity_ << stopbits_ << ")." + << std::endl; + return true; } diff --git a/src/web/web_server.cc b/src/web/web_server.cc index 8f5bdf7..cd19586 100644 --- a/src/web/web_server.cc +++ b/src/web/web_server.cc @@ -154,4 +154,69 @@ void WebServer::setup_routes() { return res; } }); + + CROW_ROUTE((*this), "/api/alarms/reload").methods("POST"_method)([this]() { + spdlog::info("Web API: Received request to reload alarm rules..."); + + bool success = m_alarm_service.reload_rules(); + + if (success) { + crow::json::wvalue response_json; + response_json["status"] = "success"; + response_json["message"] = "Alarm rules reloaded successfully."; + + auto res = crow::response(200, response_json.dump()); + res.set_header("Content-Type", "application/json"); + return res; + + } else { + crow::json::wvalue error_json; + error_json["status"] = "error"; + error_json["message"] = + "Failed to reload alarm rules. Check service logs for details."; + auto res = crow::response(500, error_json.dump()); + res.set_header("Content-Type", "application/json"); + return res; + } + }); + + CROW_ROUTE((*this), "/api/alarms/clear") + .methods("POST"_method)([this](const crow::request& req) { + crow::json::rvalue j_body; + try { + j_body = crow::json::load(req.body); + } catch (const std::exception& e) { + spdlog::warn("Failed to parse request body for /api/alarms/clear: {}", + e.what()); + auto res = crow::response(400, "{\"error\":\"Invalid JSON body.\"}"); + res.set_header("Content-Type", "application/json"); + return res; + } + + if (!j_body.has("rule_id") || !j_body.has("device_id")) { + auto res = crow::response( + 400, "{\"error\":\"Missing 'rule_id' or 'device_id'.\"}"); + res.set_header("Content-Type", "application/json"); + return res; + } + + std::string rule_id = j_body["rule_id"].s(); + std::string device_id = j_body["device_id"].s(); + + bool success = m_alarm_service.manually_clear_alarm(rule_id, device_id); + + if (success) { + auto res = crow::response( + 200, "{\"status\":\"success\", \"message\":\"Alarm cleared.\"}"); + res.set_header("Content-Type", "application/json"); + return res; + } else { + auto res = + crow::response(404, + "{\"status\":\"error\", \"message\":\"Alarm not " + "found or not active.\"}"); + res.set_header("Content-Type", "application/json"); + return res; + } + }); } \ No newline at end of file